Skip to content

Commit

Permalink
Merge pull request #1732 from techmatters/CHI-1994-form_refactor
Browse files Browse the repository at this point in the history
CHI-1994: HRM form refactor
  • Loading branch information
stephenhand authored Oct 19, 2023
2 parents ae665cd + d0873c5 commit f41edf8
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 80 deletions.
28 changes: 20 additions & 8 deletions plugin-hrm-form/src/components/HrmForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,37 @@
*/

/* eslint-disable react/prop-types */
import React from 'react';
import { connect } from 'react-redux';
import React, { Dispatch } from 'react';
import { connect, ConnectedProps } from 'react-redux';

import { CaseLayout } from '../styles/case';
import CallTypeButtons from './callTypeButtons';
import TabbedForms from './tabbedForms';
import Case from './case';
import CSAMReport from './CSAMReport/CSAMReport';
import { RootState } from '../states';
import type { CustomITask, Case as CaseForm } from '../types/types';
import type { CustomITask, Case as CaseForm, Contact } from '../types/types';
import { newContactCSAMApi } from './CSAMReport/csamReportApi';
import { completeTask, submitContactForm } from '../services/formSubmissionHelpers';
import { completeTask } from '../services/formSubmissionHelpers';
import findContactByTaskSid from '../states/contacts/findContactByTaskSid';
import { namespace, routingBase } from '../states/storeNamespaces';
import { ContactMetadata } from '../states/contacts/types';
import { submitContactFormAsyncAction } from '../states/contacts/saveContact';

type OwnProps = {
task: CustomITask;
featureFlags: { [flag: string]: boolean };
};

// eslint-disable-next-line no-use-before-define
type Props = OwnProps & ReturnType<typeof mapStateToProps>;
type Props = OwnProps & ConnectedProps<typeof connector>;

const HrmForm: React.FC<Props> = ({ routing, task, featureFlags, savedContact, metadata }) => {
const HrmForm: React.FC<Props> = ({ routing, task, featureFlags, savedContact, metadata, finaliseContact }) => {
if (!routing) return null;
const { route } = routing;

const onNewCaseSaved = async (caseForm: CaseForm) => {
await submitContactForm(task, savedContact, metadata, caseForm);
await finaliseContact(savedContact, metadata, caseForm);
await completeTask(task);
};

Expand All @@ -52,6 +54,7 @@ const HrmForm: React.FC<Props> = ({ routing, task, featureFlags, savedContact, m
return (
<TabbedForms
task={task}
contactId={savedContact?.id}
csamClcReportEnabled={featureFlags.enable_csam_clc_report}
csamReportEnabled={featureFlags.enable_csam_report}
/>
Expand Down Expand Up @@ -82,4 +85,13 @@ const mapStateToProps = (state: RootState, { task }: OwnProps) => {
return { routing: routingState.tasks[task.taskSid], savedContact, metadata };
};

export default connect(mapStateToProps, null)(HrmForm);
const mapDispatchToProps = (dispatch: Dispatch<any>, { task }: OwnProps) => {
return {
finaliseContact: (contact: Contact, metadata: ContactMetadata, caseForm: CaseForm) =>
dispatch(submitContactFormAsyncAction(task, contact, metadata, caseForm)),
};
};

const connector = connect(mapStateToProps, mapDispatchToProps);

export default connector(HrmForm);
18 changes: 9 additions & 9 deletions plugin-hrm-form/src/components/contact/ContactDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import CSAMReport from '../CSAMReport/CSAMReport';
import { existingContactCSAMApi } from '../CSAMReport/csamReportApi';
import { getAseloFeatureFlags } from '../../hrmConfig';
import { configurationBase, contactFormsBase, csamReportBase, namespace } from '../../states/storeNamespaces';
import { ContactRawJson } from '../../types/types';

type OwnProps = {
contactId: string;
Expand All @@ -58,6 +59,7 @@ const ContactDetails: React.FC<Props> = ({
draftContact,
enableEditing = true,
draftCsamReport,
updateDraftForm,
// eslint-disable-next-line sonarjs/cognitive-complexity
}) => {
const version = savedContact?.rawJson.definitionVersion;
Expand Down Expand Up @@ -98,14 +100,11 @@ const ContactDetails: React.FC<Props> = ({
initialValues={section.getFormValues(definitionVersion, draftContact)[formPath]}
display={true}
autoFocus={true}
updateFormActionDispatcher={dispatch => values =>
dispatch(
updateDraft(contactId, {
rawJson: {
[formPath]: values[formPath],
},
}),
)}
updateForm={values =>
updateDraftForm({
[formPath]: values[formPath],
})
}
contactId={contactId}
/>
</EditContactSection>
Expand Down Expand Up @@ -149,9 +148,10 @@ const ContactDetails: React.FC<Props> = ({
);
};

const mapDispatchToProps = (dispatch: Dispatch<{ type: string } & Record<string, any>>) => ({
const mapDispatchToProps = (dispatch: Dispatch<{ type: string } & Record<string, any>>, { contactId }: OwnProps) => ({
updateDefinitionVersion: (version: string, definitionVersion: DefinitionVersion) =>
dispatch(ConfigActions.updateDefinitionVersion(version, definitionVersion)),
updateDraftForm: (form: Partial<ContactRawJson>) => dispatch(updateDraft(contactId, { rawJson: form })),
});

const mapStateToProps = (state: RootState, { contactId }: OwnProps) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
*/

/* eslint-disable react/prop-types */
import React, { Dispatch } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import React from 'react';
import type { FormDefinition, LayoutDefinition } from 'hrm-form-definitions';
import { useFormContext } from 'react-hook-form';

Expand All @@ -43,13 +42,13 @@ type OwnProps = {
| ContactRawJson['caseInformation'];
autoFocus?: boolean;
extraChildrenRight?: React.ReactNode;
updateFormActionDispatcher?: (dispatch: Dispatch<any>) => (values: any) => void;
updateForm?: (values: any) => void;
contactId?: string;
taskSid?: string;
};

// eslint-disable-next-line no-use-before-define
type Props = OwnProps & ConnectedProps<typeof connector>;
type Props = OwnProps;

const ContactDetailsSectionForm: React.FC<Props> = ({
display,
Expand Down Expand Up @@ -105,11 +104,4 @@ const ContactDetailsSectionForm: React.FC<Props> = ({

ContactDetailsSectionForm.displayName = 'TabbedFormTab';

const mapDispatchToProps = (dispatch, ownProps: OwnProps) => ({
updateForm: ownProps.updateFormActionDispatcher(dispatch),
});

const connector = connect(null, mapDispatchToProps);
const connected = connector(ContactDetailsSectionForm);

export default connected;
export default ContactDetailsSectionForm;
101 changes: 55 additions & 46 deletions plugin-hrm-form/src/components/tabbedForms/TabbedForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/* eslint-disable react/no-multi-comp */
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable react/prop-types */
import React from 'react';
import React, { Dispatch } from 'react';
import SearchIcon from '@material-ui/icons/Search';
import { FormProvider, useForm } from 'react-hook-form';
import { connect, ConnectedProps } from 'react-redux';
Expand All @@ -30,8 +30,8 @@ import { RootState } from '../../states';
import { removeOfflineContact } from '../../services/formSubmissionHelpers';
import { changeRoute } from '../../states/routing/actions';
import { emptyCategories } from '../../states/contacts/reducer';
import { NewCaseSubroutes, TabbedFormSubroutes } from '../../states/routing/types';
import { ContactRawJson, CustomITask, isOfflineContactTask, Contact } from '../../types/types';
import { AppRoutes, NewCaseSubroutes, TabbedFormSubroutes } from '../../states/routing/types';
import { ContactRawJson, CustomITask, isOfflineContactTask, Contact, isOfflineContact } from '../../types/types';
import { Box, Row, StyledTabs, TabbedFormsContainer, TabbedFormTabContainer } from '../../styles/HrmStyles';
import FormTab from '../common/forms/FormTab';
import Search from '../search';
Expand All @@ -46,14 +46,15 @@ import CSAMReportButton from './CSAMReportButton';
import CSAMAttachments from './CSAMAttachments';
import { forExistingContact } from '../../states/contacts/issueCategorizationStateApi';
import { newCSAMReportActionForContact } from '../../states/csam-report/actions';
import { CSAMReportTypes } from '../../states/csam-report/types';
import { CSAMReportType, CSAMReportTypes } from '../../states/csam-report/types';
// Ensure ww import any custom components that might be used in a form
import '../contact/ResourceReferralList';
import { updateDraft } from '../../states/contacts/existingContacts';
import { ContactDraftChanges, updateDraft } from '../../states/contacts/existingContacts';
import { getUnsavedContact } from '../../states/contacts/getUnsavedContact';
import asyncDispatch from '../../states/asyncDispatch';
import { updateContactInHrmAsyncAction } from '../../states/contacts/saveContact';
import { configurationBase, contactFormsBase, namespace, routingBase } from '../../states/storeNamespaces';
import { setEditContactPageOpen } from '../../states/contacts/actions';

// eslint-disable-next-line react/display-name
const mapTabsComponents = (errors: any) => (t: TabbedFormSubroutes) => {
Expand All @@ -77,10 +78,10 @@ const mapTabsComponents = (errors: any) => (t: TabbedFormSubroutes) => {

const isEmptyCallType = callType => [null, undefined, ''].includes(callType);

const mapTabsToIndex = (task: CustomITask, contactForm: Partial<ContactRawJson>): TabbedFormSubroutes[] => {
const mapTabsToIndex = (contact: Contact, contactForm: Partial<ContactRawJson>): TabbedFormSubroutes[] => {
const isCallerType = contactForm.callType === callTypes.caller;

if (isOfflineContactTask(task)) {
if (isOfflineContact(contact)) {
if (isNonDataCallType(contactForm.callType)) return ['contactlessTask'];

return isCallerType
Expand All @@ -97,6 +98,7 @@ const mapTabsToIndex = (task: CustomITask, contactForm: Partial<ContactRawJson>)

type OwnProps = {
task: CustomITask;
contactId: string;
csamReportEnabled: boolean;
csamClcReportEnabled: boolean;
};
Expand All @@ -105,9 +107,7 @@ type OwnProps = {
type Props = OwnProps & ConnectedProps<typeof connector>;

const TabbedForms: React.FC<Props> = ({
dispatch,
routing,
task,
savedContact,
draftContact,
updatedContact,
Expand All @@ -116,6 +116,14 @@ const TabbedForms: React.FC<Props> = ({
csamClcReportEnabled,
editContactFormOpen,
isCallTypeCaller,
updateDraftForm,
newCSAMReport,
saveDraft,
clearCallType,
openCSAMReport,
backToCallTypeSelect,
navigateToTab,
task,
}) => {
const methods = useForm({
shouldFocusError: false,
Expand Down Expand Up @@ -146,35 +154,32 @@ const TabbedForms: React.FC<Props> = ({

if (!currentDefinitionVersion) return null;

const taskId = task.taskSid;
const isCallerType = updatedContact.rawJson.callType === callTypes.caller;

const onSelectSearchResult = (searchResult: Contact) => {
const selectedIsCaller = searchResult.rawJson.callType === callTypes.caller;
if (isCallerType && selectedIsCaller && isCallTypeCaller) {
dispatch(
updateDraft(savedContact.id, { rawJson: { callerInformation: searchResult.rawJson.callerInformation } }),
);
dispatch(changeRoute({ route: 'tabbed-forms', subroute: 'callerInformation' }, taskId));
updateDraftForm({ callerInformation: searchResult.rawJson.callerInformation });
navigateToTab('callerInformation');
} else {
dispatch(updateDraft(savedContact.id, { rawJson: { childInformation: searchResult.rawJson.childInformation } }));
dispatch(changeRoute({ route: 'tabbed-forms', subroute: 'childInformation' }, taskId));
updateDraftForm({ childInformation: searchResult.rawJson.childInformation });
navigateToTab('childInformation');
}
};

const handleBackButton = async () => {
if (!hasTaskControl(task)) return;
await asyncDispatch(dispatch)(updateContactInHrmAsyncAction(savedContact, { rawJson: { callType: '' } }, taskId));
dispatch(changeRoute({ route: 'select-call-type' }, taskId));
await clearCallType(savedContact);
backToCallTypeSelect();
};

const tabsToIndex = mapTabsToIndex(task, getUnsavedContact(savedContact, draftContact).rawJson);
const tabsToIndex = mapTabsToIndex(savedContact, getUnsavedContact(savedContact, draftContact).rawJson);
const tabs = tabsToIndex.map(mapTabsComponents(methods.errors));

const handleTabsChange = async (t: number) => {
const tab = tabsToIndex[t];
await asyncDispatch(dispatch)(updateContactInHrmAsyncAction(savedContact, draftContact, taskId));
dispatch(changeRoute({ route: 'tabbed-forms', subroute: tab, autoFocus: false }, taskId));
await saveDraft(savedContact, draftContact);
navigateToTab(tab);
};

const { subroute, autoFocus } = routing;
Expand All @@ -191,7 +196,7 @@ const TabbedForms: React.FC<Props> = ({
}

const optionalButtons =
isOfflineContactTask(task) && subroute === 'contactlessTask'
isOfflineContact(savedContact) && subroute === 'contactlessTask'
? [
{
label: 'CancelOfflineContact',
Expand All @@ -216,12 +221,12 @@ const TabbedForms: React.FC<Props> = ({
csamClcReportEnabled={csamClcReportEnabled}
csamReportEnabled={csamReportEnabled}
handleChildCSAMType={() => {
dispatch(newCSAMReportActionForContact(savedContact.id, CSAMReportTypes.CHILD, true));
dispatch(changeRoute({ route: 'csam-report', subroute: 'form', previousRoute: routing }, taskId));
newCSAMReport(CSAMReportTypes.CHILD);
openCSAMReport(routing);
}}
handleCounsellorCSAMType={() => {
dispatch(newCSAMReportActionForContact(savedContact.id, CSAMReportTypes.COUNSELLOR, true));
dispatch(changeRoute({ route: 'csam-report', subroute: 'form', previousRoute: routing }, taskId));
newCSAMReport(CSAMReportTypes.COUNSELLOR);
openCSAMReport(routing);
}}
/>
</Box>
Expand Down Expand Up @@ -270,10 +275,7 @@ const TabbedForms: React.FC<Props> = ({
initialValues={callerInformation}
display={subroute === 'callerInformation'}
autoFocus={autoFocus}
updateFormActionDispatcher={dispatch => values =>
dispatch(
updateDraft(savedContact.id, { rawJson: { callerInformation: values.callerInformation } }),
)}
updateForm={values => updateDraftForm({ callerInformation: values.callerInformation })}
contactId={savedContact.id}
/>
</TabbedFormTabContainer>
Expand All @@ -288,10 +290,7 @@ const TabbedForms: React.FC<Props> = ({
initialValues={childInformation}
display={subroute === 'childInformation'}
autoFocus={autoFocus}
updateFormActionDispatcher={dispatch => values =>
dispatch(
updateDraft(savedContact.id, { rawJson: { childInformation: values.childInformation } }),
)}
updateForm={values => updateDraftForm({ childInformation: values.childInformation })}
contactId={savedContact.id}
/>
</TabbedFormTabContainer>
Expand All @@ -312,10 +311,7 @@ const TabbedForms: React.FC<Props> = ({
display={subroute === 'caseInformation'}
autoFocus={autoFocus}
extraChildrenRight={csamAttachments}
updateFormActionDispatcher={dispatch => values =>
dispatch(
updateDraft(savedContact.id, { rawJson: { caseInformation: values.caseInformation } }),
)}
updateForm={values => updateDraftForm({ caseInformation: values.caseInformation })}
contactId={savedContact.id}
/>
</TabbedFormTabContainer>
Expand All @@ -328,7 +324,7 @@ const TabbedForms: React.FC<Props> = ({
contactId={savedContact.id}
task={task}
nextTab={() => handleTabsChange(tabIndex + 1)}
saveUpdates={() => asyncDispatch(dispatch)(updateContactInHrmAsyncAction(savedContact, draftContact))}
saveUpdates={() => saveDraft(savedContact, draftContact)}
// TODO: move this two functions to a separate file to centralize "handle task completions"
showNextButton={tabIndex !== 0 && tabIndex < tabs.length - 1}
showSubmitButton={showSubmitButton}
Expand All @@ -344,12 +340,9 @@ const TabbedForms: React.FC<Props> = ({

TabbedForms.displayName = 'TabbedForms';

const mapStateToProps = (state: RootState, ownProps: OwnProps) => {
const routing = state[namespace][routingBase].tasks[ownProps.task.taskSid];
const { savedContact, draftContact, metadata } =
Object.values(state[namespace][contactFormsBase].existingContacts).find(
cs => cs.savedContact.taskId === ownProps.task.taskSid,
) ?? {};
const mapStateToProps = (state: RootState, { task, contactId }: OwnProps) => {
const routing = state[namespace][routingBase].tasks[task.taskSid];
const { savedContact, draftContact, metadata } = state[namespace][contactFormsBase].existingContacts[contactId] || {};
const editContactFormOpen = state[namespace][contactFormsBase].editingContact;
const { currentDefinitionVersion } = state[namespace][configurationBase];
const { isCallTypeCaller } = state[namespace][contactFormsBase];
Expand All @@ -365,7 +358,23 @@ const mapStateToProps = (state: RootState, ownProps: OwnProps) => {
};
};

const connector = connect(mapStateToProps);
const mapDispatchToProps = (dispatch: Dispatch<any>, { contactId, task }: OwnProps) => ({
updateDraftForm: (form: Partial<ContactRawJson>) => dispatch(updateDraft(contactId, { rawJson: form })),
saveDraft: (savedContact: Contact, draftContact: ContactDraftChanges) =>
asyncDispatch(dispatch)(updateContactInHrmAsyncAction(savedContact, draftContact, task.taskSid)),
clearCallType: (savedContact: Contact) =>
asyncDispatch(dispatch)(updateContactInHrmAsyncAction(savedContact, { rawJson: { callType: '' } }, task.taskSid)),
newCSAMReport: (csamReportType: CSAMReportType) =>
dispatch(newCSAMReportActionForContact(contactId, csamReportType, true)),
openCSAMReport: (previousRoute: AppRoutes) =>
dispatch(changeRoute({ route: 'csam-report', subroute: 'form', previousRoute }, task.taskSid)),
navigateToTab: (tab: TabbedFormSubroutes) =>
dispatch(changeRoute({ route: 'tabbed-forms', subroute: tab, autoFocus: false }, task.taskSid)),
backToCallTypeSelect: () => dispatch(changeRoute({ route: 'select-call-type' }, task.taskSid)),
setModalLayout: () => dispatch(setEditContactPageOpen()),
});

const connector = connect(mapStateToProps, mapDispatchToProps);
const connected = connector(TabbedForms);

export default connected;
Loading

0 comments on commit f41edf8

Please sign in to comment.