From b657400c59fbb945ff9a14465b42125c144f1b43 Mon Sep 17 00:00:00 2001 From: Sampo Tawast Date: Tue, 1 Oct 2024 12:54:01 +0300 Subject: [PATCH] feat: useRouterNavigation hook to centralize and perform "back" actions througout the app --- .../handler/public/locales/en/common.json | 2 +- .../handler/public/locales/sv/common.json | 2 +- .../alterationHandling/AlterationHandling.tsx | 3 - .../AlterationHandlingForm.tsx | 18 ++- .../applicationForm/ApplicationForm.tsx | 5 +- .../applicationHeader/ApplicationHeader.tsx | 2 +- .../applicationList/ApplicationList.tsx | 8 +- .../applicationList/HandlerIndex.tsx | 12 +- .../HandlingApplicationActions.tsx | 2 +- .../HandlingApplicationActionsAhjo.tsx | 24 +++- .../ReceivedApplicationActions.tsx | 21 ++-- .../components/statusLabel/StatusLabel.tsx | 13 +- .../useRouterNavigation.ts | 119 ++++++++++++++++++ .../handler/src/hooks/useFormActions.tsx | 6 +- .../src/hooks/useHandlerReviewActions.ts | 9 +- frontend/benefit/handler/src/pages/_app.tsx | 21 +++- .../shared/src/utils/localstorage.utils.ts | 11 ++ 17 files changed, 233 insertions(+), 45 deletions(-) create mode 100644 frontend/benefit/handler/src/hooks/applicationHandling/useRouterNavigation.ts diff --git a/frontend/benefit/handler/public/locales/en/common.json b/frontend/benefit/handler/public/locales/en/common.json index c65443badb..c33762d551 100644 --- a/frontend/benefit/handler/public/locales/en/common.json +++ b/frontend/benefit/handler/public/locales/en/common.json @@ -997,7 +997,7 @@ "calculationOutOfDate": "Laskelma ei ole ajan tasalla" }, "actions": { - "close": "Peruuta", + "close": "Sulje", "handle": "Merkitse käsitellyksi", "returnToApplication": "Palaa hakemukseen", "returnToAlterationList": "Palaa muutosilmoituksiin" diff --git a/frontend/benefit/handler/public/locales/sv/common.json b/frontend/benefit/handler/public/locales/sv/common.json index c65443badb..c33762d551 100644 --- a/frontend/benefit/handler/public/locales/sv/common.json +++ b/frontend/benefit/handler/public/locales/sv/common.json @@ -997,7 +997,7 @@ "calculationOutOfDate": "Laskelma ei ole ajan tasalla" }, "actions": { - "close": "Peruuta", + "close": "Sulje", "handle": "Merkitse käsitellyksi", "returnToApplication": "Palaa hakemukseen", "returnToAlterationList": "Palaa muutosilmoituksiin" diff --git a/frontend/benefit/handler/src/components/alterationHandling/AlterationHandling.tsx b/frontend/benefit/handler/src/components/alterationHandling/AlterationHandling.tsx index d1ee58df29..4e62b34c1d 100644 --- a/frontend/benefit/handler/src/components/alterationHandling/AlterationHandling.tsx +++ b/frontend/benefit/handler/src/components/alterationHandling/AlterationHandling.tsx @@ -140,9 +140,6 @@ const AlterationHandling = (): JSX.Element => { alteration={alteration} onError={onError} onSuccess={onSuccess} - onClose={() => - router.push(`${ROUTES.APPLICATION}?id=${application.id}`) - } /> ) : ( diff --git a/frontend/benefit/handler/src/components/alterationHandling/AlterationHandlingForm.tsx b/frontend/benefit/handler/src/components/alterationHandling/AlterationHandlingForm.tsx index 17cc98cc0e..71f341f5ef 100644 --- a/frontend/benefit/handler/src/components/alterationHandling/AlterationHandlingForm.tsx +++ b/frontend/benefit/handler/src/components/alterationHandling/AlterationHandlingForm.tsx @@ -15,6 +15,7 @@ import useAlterationHandlingForm from 'benefit/handler/components/alterationHand import { $CustomNotesActions } from 'benefit/handler/components/applicationReview/actions/handlingApplicationActions/HandlingApplicationActions.sc'; import Sidebar from 'benefit/handler/components/sidebar/Sidebar'; import { DEFAULT_MINIMUM_RECOVERY_AMOUNT } from 'benefit/handler/constants'; +import { useRouterNavigation } from 'benefit/handler/hooks/applicationHandling/useRouterNavigation'; import { Application, ApplicationAlteration, @@ -46,7 +47,6 @@ type Props = { alteration: ApplicationAlteration; onError: (error: AxiosError) => void; onSuccess: (isRecoverable: boolean) => void; - onClose: () => void; }; const handleAlterationCsvDownload = (): void => { @@ -58,7 +58,6 @@ const AlterationHandlingForm = ({ alteration, onError, onSuccess, - onClose, }: Props): JSX.Element => { const { t, @@ -83,6 +82,8 @@ const AlterationHandlingForm = ({ const [isMessagesDrawerVisible, toggleMessagesDrawerVisibility] = useState(false); + const { navigateBack } = useRouterNavigation(null, null, null, true); + const getErrorMessage = (fieldName: string): string | undefined => getErrorText(formik.errors, formik.touched, fieldName, t, isSubmitted); @@ -238,10 +239,11 @@ const AlterationHandlingForm = ({ <$TalpaGuideText> {t(`${translationBase}.talpaCsv.guideText`)} - + onSubmit={handleAlterationCsvDownload} + /> @@ -250,8 +252,12 @@ const AlterationHandlingForm = ({ <$StickyBarWrapper> <$StickyBarColumn> - ) : ( - )} diff --git a/frontend/benefit/handler/src/components/applicationReview/actions/handlingApplicationActions/HandlingApplicationActionsAhjo.tsx b/frontend/benefit/handler/src/components/applicationReview/actions/handlingApplicationActions/HandlingApplicationActionsAhjo.tsx index 46b4203fbc..6af56679d1 100644 --- a/frontend/benefit/handler/src/components/applicationReview/actions/handlingApplicationActions/HandlingApplicationActionsAhjo.tsx +++ b/frontend/benefit/handler/src/components/applicationReview/actions/handlingApplicationActions/HandlingApplicationActionsAhjo.tsx @@ -1,5 +1,6 @@ import Sidebar from 'benefit/handler/components/sidebar/Sidebar'; import { APPLICATION_LIST_TABS } from 'benefit/handler/constants'; +import { useRouterNavigation } from 'benefit/handler/hooks/applicationHandling/useRouterNavigation'; import { APPLICATION_STATUSES } from 'benefit-shared/constants'; import { Application } from 'benefit-shared/types/application'; import { @@ -71,6 +72,12 @@ const HandlingApplicationActions: React.FC = ({ onDoneConfirmation, } = useHandlingApplicationActions(application); + const { navigateBack } = useRouterNavigation( + application?.status, + application?.batch?.status, + application?.archived + ); + const lastStep = stepState.activeStepIndex === Number(stepState.steps?.length) - 1; const router = useRouter(); @@ -86,9 +93,13 @@ const HandlingApplicationActions: React.FC = ({ (): void => void router.push({ pathname: '/', - query: { tab: APPLICATION_LIST_TABS.HANDLING }, + query: { + tab: APPLICATION_LIST_TABS[ + application?.status as unknown as keyof typeof APPLICATION_LIST_TABS + ], + }, }), - [router] + [router, application.status] ); const effectSaveAndClose = (): void => { @@ -97,7 +108,7 @@ const HandlingApplicationActions: React.FC = ({ isSavingAndClosing ) { setIsSavingAndClosing(false); - navigateToIndex(); + void navigateBack(); } }; @@ -134,6 +145,7 @@ const HandlingApplicationActions: React.FC = ({ stepState.activeStepIndex, isSavingAndClosing, navigateToIndex, + navigateBack, ]); React.useEffect(() => { setIsSavingAndClosing(false); @@ -254,7 +266,7 @@ const HandlingApplicationActions: React.FC = ({ setIsSavingAndClosing(true); }; - const handleClose = (): void => navigateToIndex(); + const handleClose = (): void => void navigateBack(); return ( <$Wrapper data-testid={dataTestId}> @@ -262,6 +274,7 @@ const HandlingApplicationActions: React.FC = ({ + {application.status === APPLICATION_STATUSES.HANDLING && ( + <$GridCell $colSpan={2}> - <$GridCell> - - ); }; diff --git a/frontend/benefit/handler/src/components/statusLabel/StatusLabel.tsx b/frontend/benefit/handler/src/components/statusLabel/StatusLabel.tsx index 8147f6c1a8..64b21bc17b 100644 --- a/frontend/benefit/handler/src/components/statusLabel/StatusLabel.tsx +++ b/frontend/benefit/handler/src/components/statusLabel/StatusLabel.tsx @@ -4,12 +4,17 @@ import React from 'react'; import { $StatusLabel } from './StatusLabel.sc'; -const StatusLabel: React.FC<{ status: APPLICATION_STATUSES }> = ({ - status, -}) => { +const StatusLabel: React.FC<{ + status: APPLICATION_STATUSES; + archived?: boolean; +}> = ({ status, archived }) => { const { t } = useTranslation(); return ( - <$StatusLabel status={status}>{t(`common:status.${status}`)} + <$StatusLabel status={status}> + {t(`common:status.${status}`)} + {archived && + ` / ${t('common:header.navigation.archive').toLocaleLowerCase()}`} + ); }; diff --git a/frontend/benefit/handler/src/hooks/applicationHandling/useRouterNavigation.ts b/frontend/benefit/handler/src/hooks/applicationHandling/useRouterNavigation.ts new file mode 100644 index 0000000000..61e938628f --- /dev/null +++ b/frontend/benefit/handler/src/hooks/applicationHandling/useRouterNavigation.ts @@ -0,0 +1,119 @@ +import { APPLICATION_LIST_TABS, ROUTES } from 'benefit/handler/constants'; +import { APPLICATION_STATUSES, BATCH_STATUSES } from 'benefit-shared/constants'; +import { useRouter } from 'next/router'; +import React from 'react'; +import { getSessionStorageItem } from 'shared/utils/localstorage.utils'; + +type NavigationProps = { + navigateBack: () => Promise | void; +}; + +export const useRouterNavigation = ( + status?: APPLICATION_STATUSES, + batchStatus?: BATCH_STATUSES, + isArchived?: boolean, + forceRouteToAlterations = false, + forceRouteToApplicationId?: string +): NavigationProps => { + const router = useRouter(); + + const statusToTabId = React.useCallback((): + | APPLICATION_LIST_TABS + | number => { + if (batchStatus === BATCH_STATUSES.DECIDED_ACCEPTED) { + return APPLICATION_LIST_TABS.IN_PAYMENT; + } + + switch (status) { + case APPLICATION_STATUSES.DRAFT: + return APPLICATION_LIST_TABS.DRAFT; + + case APPLICATION_STATUSES.RECEIVED: + return APPLICATION_LIST_TABS.RECEIVED; + + case APPLICATION_STATUSES.HANDLING: + return APPLICATION_LIST_TABS.HANDLING; + + case APPLICATION_STATUSES.INFO_REQUIRED: + return APPLICATION_LIST_TABS.HANDLING; + + case APPLICATION_STATUSES.ACCEPTED: + return APPLICATION_LIST_TABS.ACCEPTED; + + case APPLICATION_STATUSES.REJECTED: + return APPLICATION_LIST_TABS.REJECTED; + + default: + return 0; + } + }, [status, batchStatus]); + + /** + * Get the previous location from the session storage. If not available or split fails, fallback to the root. + * @returns string - previous location URI or root + */ + const getPreviousLocationData = React.useCallback(() => { + try { + const history = getSessionStorageItem('history')?.split(',') || []; + return { + pathname: history.length > 1 ? history.at(-2) : ROUTES.HOME, + length: history.length, + }; + } catch (_) { + return { + pathname: ROUTES.HOME, + length: 1, + }; + } + }, []); + + const navigateBack = React.useCallback((): Promise | void => { + if (forceRouteToAlterations) { + return router.push(ROUTES.ALTERATIONS); + } + if (forceRouteToApplicationId) { + return router.push( + `${ROUTES.APPLICATION}?id=${forceRouteToApplicationId}` + ); + } + + const previousLocation = getPreviousLocationData().pathname; + + // When coming from the alteration page (open application in new window), return to the alteration page + if ( + getPreviousLocationData().length === 1 && + document.referrer.includes(ROUTES.ALTERATIONS) + ) { + return router.push(ROUTES.ALTERATIONS); + } + + if (isArchived) { + // When looking matching applications, return to the archive with search params + if (ROUTES.HOME && /\?appNo|&appNo/.test(previousLocation)) { + return router.push(previousLocation); + } + + // Otherwise just return to archive + return router.push(ROUTES.APPLICATIONS_ARCHIVE); + } + + const isApplicationView = + router.pathname.includes(ROUTES.APPLICATION) && router.query?.id; + + if (isApplicationView) { + return router.push(`/?tab=${statusToTabId()}`); + } + return router.push(previousLocation); + }, [ + forceRouteToAlterations, + router, + forceRouteToApplicationId, + getPreviousLocationData, + isArchived, + statusToTabId, + ]); + + return { + navigateBack, + }; +}; diff --git a/frontend/benefit/handler/src/hooks/useFormActions.tsx b/frontend/benefit/handler/src/hooks/useFormActions.tsx index 1ad2b64bfc..88ab70b43f 100644 --- a/frontend/benefit/handler/src/hooks/useFormActions.tsx +++ b/frontend/benefit/handler/src/hooks/useFormActions.tsx @@ -31,6 +31,7 @@ import { convertToBackendDateFormat, parseDate } from 'shared/utils/date.utils'; import { getNumberValue, stringToFloatValue } from 'shared/utils/string.utils'; import snakecaseKeys from 'snakecase-keys'; +import { useRouterNavigation } from './applicationHandling/useRouterNavigation'; import { useApplicationFormContext } from './useApplicationFormContext'; import useCreateApplicationQuery from './useCreateApplicationQuery'; import useDeleteApplicationQuery from './useDeleteApplicationQuery'; @@ -130,6 +131,7 @@ const useFormActions = ( useUpdateApplicationQuery(); const { t } = useTranslation(); + const { navigateBack } = useRouterNavigation(application.status); React.useEffect(() => { const error = @@ -414,9 +416,9 @@ const useFormActions = ( result?.employee?.first_name, result?.employee?.last_name ); + const applicantName = fullName ? `(${fullName})` : ''; const applicationNumber = result?.application_number ?? ''; - hdsToast({ autoDismissTime: 5000, type: 'success', @@ -426,8 +428,8 @@ const useFormActions = ( applicantName, }), }); + await navigateBack(); - await router.push('/'); return result; } catch (error) { // useEffect will catch this error diff --git a/frontend/benefit/handler/src/hooks/useHandlerReviewActions.ts b/frontend/benefit/handler/src/hooks/useHandlerReviewActions.ts index e489be682d..b117098afe 100644 --- a/frontend/benefit/handler/src/hooks/useHandlerReviewActions.ts +++ b/frontend/benefit/handler/src/hooks/useHandlerReviewActions.ts @@ -1,4 +1,3 @@ -import { ROUTES } from 'benefit/handler/constants'; import AppContext from 'benefit/handler/context/AppContext'; import { CalculationFormProps, @@ -8,13 +7,13 @@ import { Application, ApplicationData } from 'benefit-shared/types/application'; import { ErrorData } from 'benefit-shared/types/common'; import camelcaseKeys from 'camelcase-keys'; import isEmpty from 'lodash/isEmpty'; -import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; import React, { useEffect } from 'react'; import { convertToBackendDateFormat } from 'shared/utils/date.utils'; import { stringToFloatValue } from 'shared/utils/string.utils'; import snakecaseKeys from 'snakecase-keys'; +import { useRouterNavigation } from './applicationHandling/useRouterNavigation'; import { useApplicationActions } from './useApplicationActions'; import useUpdateApplicationQuery from './useUpdateApplicationQuery'; @@ -32,8 +31,6 @@ const useHandlerReviewActions = ( ): HandlerReviewActions => { const updateApplicationQuery = useUpdateApplicationQuery(); - const router = useRouter(); - const { handledApplication } = React.useContext(AppContext); const { updateStatus } = useApplicationActions(application); @@ -190,8 +187,10 @@ const useHandlerReviewActions = ( void updateApplicationQuery.mutate(getSalaryBenefitData(values)); }; + const { navigateBack } = useRouterNavigation(application.status); + const onSaveAndClose = (): void => { - void router.push(ROUTES.HOME); + void navigateBack(); }; return { diff --git a/frontend/benefit/handler/src/pages/_app.tsx b/frontend/benefit/handler/src/pages/_app.tsx index 2a93c64297..3f97f529ae 100644 --- a/frontend/benefit/handler/src/pages/_app.tsx +++ b/frontend/benefit/handler/src/pages/_app.tsx @@ -15,12 +15,17 @@ import { } from 'benefit-shared/backend-api/backend-api'; import { setAppLoaded } from 'benefit-shared/utils/common'; import { AppProps } from 'next/app'; +import { useRouter } from 'next/router'; import { appWithTranslation } from 'next-i18next'; import React, { useEffect } from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import BackendAPIProvider from 'shared/backend-api/BackendAPIProvider'; import BaseApp from 'shared/components/app/BaseApp'; import useLocale from 'shared/hooks/useLocale'; +import { + getSessionStorageItem, + setSessionStorageItem, +} from 'shared/utils/localstorage.utils'; import Layout from '../layout/Layout'; @@ -28,10 +33,24 @@ const queryClient = new QueryClient(); const App: React.FC = (appProps) => { const locale = useLocale(); + const router = useRouter(); useEffect(() => { setAppLoaded(); - }, []); + }, [router.pathname, router.isReady]); + + useEffect(() => { + const pathname = window.location.pathname + window.location.search; + const history = getSessionStorageItem('history'); + const historyArray = history ? history.split(',') : []; + const samePage = historyArray.at(-1) === pathname; + + if (router.isReady && !samePage) { + const newHistory = + historyArray.length > 0 ? `${history},${pathname}` : pathname; + setSessionStorageItem('history', newHistory); + } + }, [router.pathname, router.isReady]); return ( export const removeLocalStorageItem = (key: string): void => IS_CLIENT && localStorage.removeItem(key); +export const getSessionStorageItem = (key: string): string => + IS_CLIENT ? sessionStorage.getItem(key) || '' : ''; + +export const setSessionStorageItem = (key: string, value: string): void => + IS_CLIENT && sessionStorage.setItem(key, value); + +export const removeSessionStoragItem = (key: string): void => + IS_CLIENT && sessionStorage.removeItem(key); + /* eslint-enable scanjs-rules/identifier_localStorage */ +/* eslint-enable scanjs-rules/identifier_sessionStorage */