Skip to content

Commit

Permalink
feat: minor additions and fixes to decision drafting (HL-1274) (#2942)
Browse files Browse the repository at this point in the history
* fix: save and close only when calculation is in place

* refactor(shared): add possibility to scroll into any element by using selector

* fix: replace placeholder ahjo id with real one

* feat: focusAndScroll to the first invalid element

* chore: fix missing month unit

* fix: use bold font as semibold was too light on Windows

* feat: add tab query param to url when tab is clicked

user can return to same tab if application is clicked then navigated to index page with back button

* feat: close buttons navigate back to the index page's 'handling' tab
  • Loading branch information
sirtawast committed Apr 26, 2024
1 parent a65bb5b commit f6cf6fb
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 47 deletions.
5 changes: 2 additions & 3 deletions frontend/benefit/handler/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@
"calculationReview": {
"tableCaption": "Tukijaksot",
"dates": "Tukijakso",
"duration": "Kesto",
"duration": "Kesto (kk)",
"perMonth": "Tuki kuukaudessa",
"amount": "Tuki yhteensä"
},
Expand Down Expand Up @@ -906,8 +906,7 @@
"decisionText": "Päätös",
"justificationText": "Päätöksen perustelut",
"staticTitle": "Avustukset työnantajille, Työllisyyspalvelut, Helsinki-lisä, ",
"application": "hakemus",
"ahjoIdentifier": "HEL 2024-000172"
"application": "hakemus"
},
"submitted": {
"title": "Päätösehdotus on lähetetty Ahjoon",
Expand Down
5 changes: 2 additions & 3 deletions frontend/benefit/handler/public/locales/fi/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@
"calculationReview": {
"tableCaption": "Tukijaksot",
"dates": "Tukijakso",
"duration": "Kesto",
"duration": "Kesto (kk)",
"perMonth": "Tuki kuukaudessa",
"amount": "Tuki yhteensä"
},
Expand Down Expand Up @@ -906,8 +906,7 @@
"decisionText": "Päätös",
"justificationText": "Päätöksen perustelut",
"staticTitle": "Avustukset työnantajille, Työllisyyspalvelut, Helsinki-lisä, ",
"application": "hakemus",
"ahjoIdentifier": "HEL 2024-000172"
"application": "hakemus"
},
"submitted": {
"title": "Päätösehdotus on lähetetty Ahjoon",
Expand Down
5 changes: 2 additions & 3 deletions frontend/benefit/handler/public/locales/sv/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@
"calculationReview": {
"tableCaption": "Tukijaksot",
"dates": "Tukijakso",
"duration": "Kesto",
"duration": "Kesto (kk)",
"perMonth": "Tuki kuukaudessa",
"amount": "Tuki yhteensä"
},
Expand Down Expand Up @@ -906,8 +906,7 @@
"decisionText": "Päätös",
"justificationText": "Päätöksen perustelut",
"staticTitle": "Avustukset työnantajille, Työllisyyspalvelut, Helsinki-lisä, ",
"application": "hakemus",
"ahjoIdentifier": "HEL 2024-000172"
"application": "hakemus"
},
"submitted": {
"title": "Päätösehdotus on lähetetty Ahjoon",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,6 @@ export const $CalculationReviewTableWrapper = styled.div`
table > tbody > tr:last-child td {
background-color: ${(props) => props.theme.colors.coatOfArmsLight};
font-weight: 500;
font-weight: 600;
}
`;
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Sidebar from 'benefit/handler/components/sidebar/Sidebar';
import { HANDLED_STATUSES } from 'benefit/handler/constants';
import {
APPLICATION_LIST_TABS,
HANDLED_STATUSES,
} from 'benefit/handler/constants';
import { APPLICATION_STATUSES } from 'benefit-shared/constants';
import { Application } from 'benefit-shared/types/application';
import {
Expand All @@ -17,7 +20,10 @@ import * as React from 'react';
import Modal from 'shared/components/modal/Modal';
import showErrorToast from 'shared/components/toast/show-error-toast';
import theme from 'shared/styles/theme';
import { focusAndScroll } from 'shared/utils/dom.utils';
import {
focusAndScroll,
focusAndScrollToSelector,
} from 'shared/utils/dom.utils';

import useDecisionProposalDraftMutation from '../../../../hooks/applicationHandling/useDecisionProposalDraftMutation';
import {
Expand Down Expand Up @@ -77,13 +83,22 @@ const HandlingApplicationActions: React.FC<Props> = ({

const [isSavingAndClosing, setIsSavingAndClosing] = React.useState(false);

const navigateToIndex = React.useCallback(
(): void =>
void router.push({
pathname: '/',
query: { tab: APPLICATION_LIST_TABS.HANDLING },
}),
[router]
);

const effectSaveAndClose = (): void => {
if (
data?.review_step === stepState.activeStepIndex + 1 &&
isSavingAndClosing
) {
setIsSavingAndClosing(false);
void router.push('/');
navigateToIndex();
}
};

Expand Down Expand Up @@ -119,11 +134,18 @@ const HandlingApplicationActions: React.FC<Props> = ({
router,
stepState.activeStepIndex,
isSavingAndClosing,
navigateToIndex,
]);
React.useEffect(() => {
setIsSavingAndClosing(false);
}, [isError]);

const isCalculationInvalid = (): boolean =>
(application.calculation.rows.length === 0 &&
handledApplication?.status === APPLICATION_STATUSES.ACCEPTED) ||
isRecalculationRequired ||
isCalculationsErrors;

const validateNextStep = (currentStepIndex: number): boolean => {
if (application.status === APPLICATION_STATUSES.INFO_REQUIRED) {
focusAndScroll('header-info-needed');
Expand All @@ -133,39 +155,54 @@ const HandlingApplicationActions: React.FC<Props> = ({
);
return true;
}
const missing = {
status: !handledApplication?.status,
calculation:
(application.calculation.rows.length === 0 &&
handledApplication?.status === APPLICATION_STATUSES.ACCEPTED) ||
isRecalculationRequired ||
isCalculationsErrors,
logEntry:
handledApplication?.logEntryComment?.length <= 0 &&
handledApplication?.status === APPLICATION_STATUSES.REJECTED,
handler: false,
// Use longer length to take HTML tags into account
decisionText: handledApplication?.decisionText?.length <= 10,
justificationText: handledApplication?.justificationText?.length <= 10,
const fields = {
missing: {
status: !handledApplication?.status,
calculation: isCalculationInvalid(),
logEntry:
handledApplication?.logEntryComment?.length <= 0 &&
handledApplication?.status === APPLICATION_STATUSES.REJECTED,
handler: false,
// Use longer length to take HTML tags into account
decisionText: handledApplication?.decisionText?.length <= 10,
justificationText: handledApplication?.justificationText?.length <= 10,
},
id: {
status: '#proccessRejectedRadio',
calculation: '#endDate',
logEntry: '#proccessRejectedRadio',
handler: '#radio-decision-maker-handler',
decisionText: '[data-testid="decisionText"]',
justificationText: '[data-testid="justificationText"]',
},
};

const errorStep1 =
missing.status || missing.calculation || missing.logEntry;
fields.missing.status ||
fields.missing.calculation ||
fields.missing.logEntry;

let errorStep2 = false;
if (currentStepIndex > 0) {
missing.handler = !['handler', 'manager'].includes(
fields.missing.handler = !['handler', 'manager'].includes(
handledApplication?.handlerRole
);

errorStep2 =
missing.decisionText || missing.justificationText || missing.handler;
fields.missing.decisionText ||
fields.missing.justificationText ||
fields.missing.handler;
}

if (errorStep1 || errorStep2) {
const missingFields = Object.keys(missing).filter((key) => missing[key]);
const missingFields = Object.keys(fields.missing).filter(
(key) => fields.missing[key]
);
let interval = 0;
missingFields.forEach((key) => {
missingFields.forEach((key, index) => {
if (index === 0) {
focusAndScrollToSelector(String(fields.id[key]));
}
setTimeout(() => {
showErrorToast(
t('common:review.decisionProposal.errors.title'),
Expand All @@ -187,7 +224,7 @@ const HandlingApplicationActions: React.FC<Props> = ({
applicationId: application.id,
});
} else {
// Final step, just open confirmation modal
// Final step, just open confirmation modal before submitting
onDoneConfirmation();
}
};
Expand All @@ -201,6 +238,15 @@ const HandlingApplicationActions: React.FC<Props> = ({
};

const handleSaveAndClose = (): void => {
if (isCalculationInvalid()) {
focusAndScroll('endDate');
showErrorToast(
t('common:review.decisionProposal.errors.title'),
t(`common:review.decisionProposal.errors.fields.calculation`)
);

return;
}
updateApplication({
...handledApplication,
reviewStep: stepState.activeStepIndex + 1,
Expand All @@ -209,7 +255,7 @@ const HandlingApplicationActions: React.FC<Props> = ({
setIsSavingAndClosing(true);
};

const handleClose = (): void => void router.push('/');
const handleClose = (): void => navigateToIndex();

return (
<$Wrapper data-testid={dataTestId}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ const ApplicationReviewStep3: React.FC<HandlingStepProps> = ({
weight="400"
$css={{ marginTop: 0, marginBottom: theme.spacing.s }}
/>
<p>{t('common:review.decisionProposal.preview.ahjoIdentifier')}</p>

<p>{application?.ahjoCaseId}</p>
<Heading
header={`${t(
'common:review.decisionProposal.preview.decisionText'
Expand Down
9 changes: 9 additions & 0 deletions frontend/benefit/handler/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,12 @@ export const PAY_SUBSIDIES_OVERRIDE = {
disabilityOrIllness: false,
durationInMonthsRounded: '',
};

export enum APPLICATION_LIST_TABS {
ALL = '0',
DRAFT = '1',
RECEIVED = '2',
HANDLING = '3',
ACCEPTED = '4',
REJECTED = '5',
}
28 changes: 21 additions & 7 deletions frontend/benefit/handler/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import theme from 'shared/styles/theme';
import { useApplicationList } from '../components/applicationList/useApplicationList';
import { useApplicationListData } from '../components/applicationList/useApplicationListData';
import { $BackgroundWrapper } from '../components/layout/Layout';
import { ALL_APPLICATION_STATUSES } from '../constants';
import { ALL_APPLICATION_STATUSES, APPLICATION_LIST_TABS } from '../constants';

const ApplicantIndex: NextPage = () => {
const {
Expand Down Expand Up @@ -65,6 +65,8 @@ const ApplicantIndex: NextPage = () => {
const router = useRouter();
const { tab } = router.query;
const [activeTab, setActiveTab] = React.useState<number | null>(null);
const updateTabToUrl = (tabNumber: APPLICATION_LIST_TABS): void =>
window.history.pushState({ tab }, '', `/?tab=${tabNumber}`);

React.useEffect(() => {
if (!router.isReady) return;
Expand All @@ -86,31 +88,43 @@ const ApplicantIndex: NextPage = () => {
<Container>
<Tabs theme={theme.components.tabs} initiallyActiveTab={activeTab}>
<Tabs.TabList style={{ marginBottom: 'var(--spacing-m)' }}>
<Tabs.Tab>
<Tabs.Tab
onClick={() => updateTabToUrl(APPLICATION_LIST_TABS.ALL)}
>
{getListHeadingByStatus('all', ALL_APPLICATION_STATUSES)}
</Tabs.Tab>
<Tabs.Tab>
<Tabs.Tab
onClick={() => updateTabToUrl(APPLICATION_LIST_TABS.DRAFT)}
>
{getListHeadingByStatus(APPLICATION_STATUSES.DRAFT, [
APPLICATION_STATUSES.DRAFT,
])}
</Tabs.Tab>
<Tabs.Tab>
<Tabs.Tab
onClick={() => updateTabToUrl(APPLICATION_LIST_TABS.RECEIVED)}
>
{getListHeadingByStatus(APPLICATION_STATUSES.RECEIVED, [
APPLICATION_STATUSES.RECEIVED,
])}
</Tabs.Tab>
<Tabs.Tab>
<Tabs.Tab
onClick={() => updateTabToUrl(APPLICATION_LIST_TABS.HANDLING)}
>
{getListHeadingByStatus(APPLICATION_STATUSES.HANDLING, [
APPLICATION_STATUSES.HANDLING,
APPLICATION_STATUSES.INFO_REQUIRED,
])}
</Tabs.Tab>
<Tabs.Tab>
<Tabs.Tab
onClick={() => updateTabToUrl(APPLICATION_LIST_TABS.ACCEPTED)}
>
{getListHeadingByStatus(APPLICATION_STATUSES.ACCEPTED, [
APPLICATION_STATUSES.ACCEPTED,
])}
</Tabs.Tab>
<Tabs.Tab>
<Tabs.Tab
onClick={() => updateTabToUrl(APPLICATION_LIST_TABS.REJECTED)}
>
{getListHeadingByStatus(APPLICATION_STATUSES.REJECTED, [
APPLICATION_STATUSES.REJECTED,
])}
Expand Down
26 changes: 22 additions & 4 deletions frontend/shared/src/utils/dom.utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
export const focusAndScroll = (elementId: string | undefined): void => {
// eslint-disable-next-line unicorn/prefer-query-selector
const element = elementId ? document.getElementById(elementId) : null;

const handleScroll = (element?: HTMLElement): void => {
if (!element) {
return window.scrollTo({
top: 0,
Expand All @@ -22,3 +19,24 @@ export const focusAndScroll = (elementId: string | undefined): void => {
behavior: 'smooth',
});
};

/**
* Focus and scroll to element using it's id
* @param elementId
* @returns void
*/
export const focusAndScroll = (elementId: string): void => {
// eslint-disable-next-line unicorn/prefer-query-selector
const element = document.getElementById(elementId);
return handleScroll(element);
};

/**
* Focus and scroll to any element using query selector
* @param elementSelector
* @returns void
*/
export const focusAndScrollToSelector = (elementSelector: string): void => {
const element = document.querySelector(elementSelector);
return handleScroll(element as HTMLElement);
};

0 comments on commit f6cf6fb

Please sign in to comment.