Skip to content

Commit

Permalink
Fix - Improved handling of ViewHome clicked statuses
Browse files Browse the repository at this point in the history
  • Loading branch information
gareth.james committed Oct 2, 2023
1 parent 1fc6ab6 commit 0352e6f
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 12 deletions.
62 changes: 58 additions & 4 deletions client/src/layouts/Layout.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { ErrorBoundary } from 'react-error-boundary';
import { Outlet, useOutletContext } from 'react-router-dom';
import PullToRefresh from 'react-simple-pull-to-refresh';
import React, { useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';

import {
setPageMainHeight,
setPageMainScrollTop,
} from '@stores/storeSliceSettings';

import {
useFetchStatusesQuery,
Expand All @@ -17,13 +23,54 @@ import PageHeader from '@components/PageHeader';
import PullToRefreshMessage from '@components/PullToRefreshMessage';

function Layout() {
const dispatch = useDispatch();
const refPageMain = useRef();

const {
data: services,
error,
isLoading,
refetch,
} = useFetchStatusesQuery();

const setRefPageMainHeight = () => {
const refPageMainHeight = refPageMain.current?.clientHeight;
dispatch(setPageMainHeight(refPageMainHeight));
};

const setRefPageMainScrollTop = () => {
const refPageMainScrollTop = refPageMain.current?.scrollTop;
dispatch(setPageMainScrollTop(refPageMainScrollTop));
};

const setRefPageMeasurements = () => {
setRefPageMainHeight();
setRefPageMainScrollTop();
};

useEffect(() => {
setRefPageMeasurements();
}, [ services ]); // Using `services` instead of `refPageMain` as `refPageMain` doesn't calculate the correct value on app init

const scrollTo = (top) => {
refPageMain.current.scrollTo({
behavior: 'instant',
top,
});
};

useEffect(() => {
if (refPageMain.current) {
refPageMain.current.addEventListener('scroll', setRefPageMainScrollTop);
window.addEventListener('resize', setRefPageMeasurements);

return function cleanup() {
refPageMain.current.removeEventListener('scroll', setRefPageMainScrollTop);
window.removeEventListener('resize', setRefPageMeasurements);
};
}
}, [ services ]); // Using `services` instead of `refPageMain` as `refPageMain` doesn't calculate the correct value on app init

const [ menuOpen, setMenuOpen ] = useState(false);

const classes = () => {
Expand Down Expand Up @@ -70,12 +117,15 @@ function Layout() {
setMenuOpen={setMenuOpen}
/>
</header>
<main className="page__main">
<main
className="page__main"
ref={refPageMain}
>
<ErrorBoundary
fallbackRender={ViewErrorGeneric}
>
<Outlet
context={{ services }}
context={{ scrollTo, services }}
/>
</ErrorBoundary>
</main>
Expand All @@ -98,6 +148,10 @@ function Layout() {

export default Layout;

export function useScrollTo() {
return useOutletContext();
}

export function useServices() {
return useOutletContext();
}
2 changes: 2 additions & 0 deletions client/src/stores/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { configureStore } from '@reduxjs/toolkit/';

import { statusApi } from '@api/statusApi';

import settingsReducer from './storeSliceSettings';
import statusReducer from './storeSliceStatus';

export const store = configureStore({
reducer: {
[statusApi.reducerPath]: statusApi.reducer,
settings: settingsReducer,
status: statusReducer,
devTools: PUBLIC_ENV === 'development',
},
Expand Down
29 changes: 29 additions & 0 deletions client/src/stores/storeSliceSettings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
pageMainHeight: 0,
pageMainScrollTop: 0,
prefersReducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
};

export const storeSliceSettings = createSlice({
name: 'settings',
initialState,
reducers: {
setPageMainHeight: (state, action) => {
const { payload } = action;
state.pageMainHeight = payload;
},
setPageMainScrollTop: (state, action) => {
const { payload } = action;
state.pageMainScrollTop = payload;
},
},
});

export const {
setPageMainHeight,
setPageMainScrollTop,
} = storeSliceSettings.actions;

export default storeSliceSettings.reducer;
36 changes: 28 additions & 8 deletions client/src/views/ViewHome.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { NavLink } from 'react-router-dom';
import React, { createRef, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';

import { useServices } from '@layouts/Layout';
import {
useScrollTo,
useServices,
} from '@layouts/Layout';

import Icon from '@components/Icon';
import IconCircleMinus from '@components/icons/IconCircleMinus';
Expand All @@ -15,6 +19,7 @@ import buildPageTitle from '@utils/buildPageTitle';
import { havingTroubleFetchingData } from '@constants/textContent';

function ViewHome() {
const { scrollTo } = useScrollTo();
const { services } = useServices();

if (!services.length) {
Expand All @@ -29,16 +34,31 @@ function ViewHome() {
return service.lineStatuses[0].reason;
};

const ref = useRef(services.map(() => createRef()));
const {
pageMainHeight,
pageMainScrollTop,
} = useSelector((state) => state.settings);

const refServices = useRef(services.map(() => createRef()));
const [ activeService, setActiveService ] = useState(null);
const [ statusReasonVisibility, setStatusReasonVisibility ] = useState({});

useEffect(() => {
if (activeService !== null) {
ref.current[activeService].current.scrollIntoView({
behavior: 'instant',
block: 'center',
});
if (activeService === null) return;

const activeServiceHeight = refServices.current[activeService].current.offsetHeight;
const activeServiceTop = refServices.current[activeService].current.offsetTop;
const activeServiceBottom = activeServiceHeight + activeServiceTop;

if (activeServiceHeight <= pageMainHeight) { // The active service fits on the page
if (activeServiceTop <= pageMainScrollTop) { // The top of the active service is above the top of the page
scrollTo(activeServiceTop);
}
else if (activeServiceBottom > (pageMainHeight + pageMainScrollTop)) { // The bottom of the active service is below the bottom of the page
scrollTo(activeServiceBottom - pageMainHeight);
}
} else { // The active service doesn't fit on the page so we should show as much as possible
scrollTo(activeServiceTop);
}
}, [ activeService ]);

Expand Down Expand Up @@ -86,7 +106,7 @@ function ViewHome() {
<td
className={`home-table__cell home-table__cell--status ${hasStatusReason(service) ? 'clickable' : ''}`.trim()}
onClick={handleClick(service.id, index)}
ref={ref.current[index]}
ref={refServices.current[index]}
>
<div className="home-table__status">
<Status
Expand Down

0 comments on commit 0352e6f

Please sign in to comment.