diff --git a/plugin-hrm-form/src/___tests__/components/CSAMReport/CSAMReport.test.tsx b/plugin-hrm-form/src/___tests__/components/CSAMReport/CSAMReport.test.tsx index 680a5f5ba9..9ed786aad8 100644 --- a/plugin-hrm-form/src/___tests__/components/CSAMReport/CSAMReport.test.tsx +++ b/plugin-hrm-form/src/___tests__/components/CSAMReport/CSAMReport.test.tsx @@ -19,44 +19,41 @@ import * as React from 'react'; import { toHaveNoViolations } from 'jest-axe'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { StorelessThemeProvider } from '@twilio/flex-ui'; +import { Provider } from 'react-redux'; +import configureMockStore from 'redux-mock-store'; import '@testing-library/jest-dom/extend-expect'; import { CSAMReportTypes, isCounsellorTaskEntry, CSAMReportStateEntry } from '../../../states/csam-report/types'; import '../../mockGetConfig'; -import { CSAMReportScreen, Props } from '../../../components/CSAMReport/CSAMReport'; -import { childInitialValues, initialValues } from '../../../components/CSAMReport/CSAMReportFormDefinition'; +import { CSAMReportScreen } from '../../../components/CSAMReport/CSAMReport'; +import { initialValues } from '../../../components/CSAMReport/CSAMReportFormDefinition'; import { CSAMPage, CSAMReportApi } from '../../../components/CSAMReport/csamReportApi'; +import { namespace, configurationBase } from '../../../states/storeNamespaces'; +import { RootState } from '../../../states'; +import { RecursivePartial } from '../../RecursivePartial'; console.error = () => undefined; expect.extend(toHaveNoViolations); -const taskSid = 'task-sid'; const workerSid = 'worker-sid'; let mockCSAMReportApi: CSAMReportApi; let dispatcherMocks; -const setupProps = ( - currentPage: CSAMPage, - csamReportState: CSAMReportStateEntry, -): Props & { alertSpy: jest.SpyInstance } => { +const mockStore = configureMockStore([]); + +const setupMockState = (currentPage: CSAMPage, csamReportState: CSAMReportStateEntry): RecursivePartial => { (mockCSAMReportApi.currentPage as jest.Mock).mockReturnValue(currentPage); (mockCSAMReportApi.reportState as jest.Mock).mockReturnValue(csamReportState); + return { - alertSpy: jest.spyOn(window, 'alert'), - updateChildForm: dispatcherMocks.updateChildReportDispatcher, - updateCounsellorForm: dispatcherMocks.updateCounsellorReportDispatcher, - updateStatus: dispatcherMocks.updateStatusDispatcher, - navigate: dispatcherMocks.navigationActionDispatcher, - addCSAMReportEntry: dispatcherMocks.addReportDispatcher, - exit: dispatcherMocks.exitActionDispatcher, - pickReportType: dispatcherMocks.pickReportTypeDispatcher, - csamReportState, - currentPage, - setEditPageOpen: jest.fn(), - setEditPageClosed: jest.fn(), - counselorsHash: { workerSid }, - api: mockCSAMReportApi, + [namespace]: { + [configurationBase]: { + counselors: { + hash: { [workerSid]: workerSid }, + }, + }, + }, }; }; @@ -64,50 +61,26 @@ const renderCSAMReportScreen = ( subrouteParam = CSAMPage.Form, csamReportStateParam: CSAMReportStateEntry = { form: initialValues, reportType: CSAMReportTypes.COUNSELLOR }, ) => { - const { - alertSpy, - updateChildForm, - updateCounsellorForm, - updateStatus, - addCSAMReportEntry, - csamReportState, - currentPage, - counselorsHash, - setEditPageClosed, - setEditPageOpen, - navigate, - exit, - api, - } = setupProps(subrouteParam, csamReportStateParam); + const alertSpy = jest.spyOn(window, 'alert'); + const mockState = setupMockState(subrouteParam, csamReportStateParam); + const store = mockStore(mockState); render( - + + + , ); return { alertSpy, - updateStatus, - addCSAMReportEntry, - csamReportState, - counselorsHash, - navigate, - api, + updateStatus: dispatcherMocks.updateStatusDispatcher, + addCSAMReportEntry: dispatcherMocks.addReportDispatcher, + csamReportState: csamReportStateParam, + counselorsHash: { workerSid }, + navigate: dispatcherMocks.navigationActionDispatcher, + api: mockCSAMReportApi, }; }; @@ -119,6 +92,7 @@ beforeEach(() => { updateChildReportDispatcher: jest.fn(), updateCounsellorReportDispatcher: jest.fn(), updateStatusDispatcher: jest.fn(), + pickReportTypeDispatcher: jest.fn(), }; mockCSAMReportApi = { @@ -127,7 +101,7 @@ beforeEach(() => { navigationActionDispatcher: () => dispatcherMocks.navigationActionDispatcher, updateChildReportDispatcher: () => dispatcherMocks.updateChildReportDispatcher, updateCounsellorReportDispatcher: () => dispatcherMocks.updateCounsellorReportDispatcher, - updateStatusDispatcher: () => dispatcherMocks.updateCounsellorReportDispatcher, + updateStatusDispatcher: () => dispatcherMocks.updateStatusDispatcher, pickReportTypeDispatcher: () => dispatcherMocks.pickReportTypeDispatcher, saveReport: jest.fn(), currentPage: jest.fn(), diff --git a/plugin-hrm-form/src/___tests__/components/case/caseOverview/EditCaseOverview.test.tsx b/plugin-hrm-form/src/___tests__/components/case/caseOverview/EditCaseOverview.test.tsx index 21f8f329b0..7571bd7c29 100644 --- a/plugin-hrm-form/src/___tests__/components/case/caseOverview/EditCaseOverview.test.tsx +++ b/plugin-hrm-form/src/___tests__/components/case/caseOverview/EditCaseOverview.test.tsx @@ -49,7 +49,9 @@ const state: RecursivePartial = { list: [], hash: { worker1: 'worker1 name' }, }, - definitionVersions: {}, + definitionVersions: { + 'as-v1': null, + }, currentDefinitionVersion: { caseStatus: { open: { @@ -129,10 +131,10 @@ describe('Test EditCaseOverview', () => { mockReset(); ownProps = { task: task as StandaloneITask, - definitionVersion: mockV1, can: () => true, }; store.dispatch = jest.fn(); + state[namespace].configuration.definitionVersions['as-v1'] = mockV1; }); test('Test close functionality', async () => { render( diff --git a/plugin-hrm-form/src/components/CSAMReport/CSAMReport.tsx b/plugin-hrm-form/src/components/CSAMReport/CSAMReport.tsx index f72c5e870a..dec795ca5e 100644 --- a/plugin-hrm-form/src/components/CSAMReport/CSAMReport.tsx +++ b/plugin-hrm-form/src/components/CSAMReport/CSAMReport.tsx @@ -14,10 +14,10 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -import React, { Dispatch } from 'react'; +import React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { CircularProgress } from '@material-ui/core'; -import { connect, ConnectedProps } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import CSAMReportStatusScreen from './CSAMReportStatusScreen'; import CSAMReportCounsellorForm from './CSAMReportCounsellorForm'; @@ -30,45 +30,24 @@ import CSAMReportChildForm from './CSAMReportChildForm'; import { getHrmConfig, getTemplateStrings } from '../../hrmConfig'; import { configurationBase, namespace } from '../../states/storeNamespaces'; -type OwnProps = { +type Props = { api: CSAMReportApi; }; -const mapStateToProps = (state: RootState, { api }: OwnProps) => ({ - csamReportState: api.reportState(state), - currentPage: api.currentPage(state), - counselorsHash: state[namespace][configurationBase].counselors.hash, -}); - -const mapDispatchToProps = (dispatch: Dispatch, { api }: OwnProps) => { - return { - updateCounsellorForm: api.updateCounsellorReportDispatcher(dispatch), - updateChildForm: api.updateChildReportDispatcher(dispatch), - updateStatus: api.updateStatusDispatcher(dispatch), - navigate: api.navigationActionDispatcher(dispatch), - exit: api.exitActionDispatcher(dispatch), - addCSAMReportEntry: api.addReportDispatcher(dispatch), - pickReportType: api.pickReportTypeDispatcher(dispatch), - }; -}; - -// eslint-disable-next-line no-use-before-define -export type Props = OwnProps & ConnectedProps; - // exported for test purposes -export const CSAMReportScreen: React.FC = ({ - updateChildForm, - updateCounsellorForm, - updateStatus, - navigate, - exit, - addCSAMReportEntry, - csamReportState, - currentPage, - counselorsHash, - api, - pickReportType, -}) => { +export const CSAMReportScreen: React.FC = ({ api }) => { + const dispatch = useDispatch(); + const csamReportState = useSelector((state: RootState) => api.reportState(state)); + const currentPage = useSelector((state: RootState) => api.currentPage(state)); + const counselorsHash = useSelector((state: RootState) => state[namespace][configurationBase].counselors.hash); + + const updateCounsellorForm = api.updateCounsellorReportDispatcher(dispatch); + const updateChildForm = api.updateChildReportDispatcher(dispatch); + const updateStatus = api.updateStatusDispatcher(dispatch); + const navigate = api.navigationActionDispatcher(dispatch); + const exit = api.exitActionDispatcher(dispatch); + const addCSAMReportEntry = api.addReportDispatcher(dispatch); + const pickReportType = api.pickReportTypeDispatcher(dispatch); const methods = useForm({ reValidateMode: 'onChange' }); const { workerSid } = getHrmConfig(); @@ -186,7 +165,4 @@ export const CSAMReportScreen: React.FC = ({ CSAMReportScreen.displayName = 'CSAMReportScreen'; -const connector = connect(mapStateToProps, mapDispatchToProps); -const connected = connector(CSAMReportScreen); - -export default connected; +export default CSAMReportScreen; diff --git a/plugin-hrm-form/src/components/NavigableContainer/index.tsx b/plugin-hrm-form/src/components/NavigableContainer/index.tsx index 65b3a92013..019367e07f 100644 --- a/plugin-hrm-form/src/components/NavigableContainer/index.tsx +++ b/plugin-hrm-form/src/components/NavigableContainer/index.tsx @@ -14,7 +14,7 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -import { connect, ConnectedProps } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { Template } from '@twilio/flex-ui'; import React from 'react'; import { Close } from '@material-ui/icons'; @@ -24,8 +24,8 @@ import { namespace } from '../../states/storeNamespaces'; import { RootState } from '../../states'; import { getCurrentBaseRoute, getCurrentTopmostRouteStackForTask } from '../../states/routing/getRoute'; import { isRouteModal } from '../../states/routing/types'; -import { changeRoute, newCloseModalAction, newGoBackAction } from '../../states/routing/actions'; -import { Contact, CustomITask, StandaloneITask } from '../../types/types'; +import { newCloseModalAction, newGoBackAction } from '../../states/routing/actions'; +import { CustomITask, StandaloneITask } from '../../types/types'; import { Box, HiddenText, Row } from '../../styles'; import { StyledBackButton, HeaderCloseButton } from '../../styles/buttons'; import { LargeBackIcon, NavigableContainerBox, NavigableContainerContentBox, NavigableContainerTitle } from './styles'; @@ -33,58 +33,44 @@ import useFocus from '../../utils/useFocus'; type FocusTarget = 'back' | 'close'; -type OwnProps = { +type Props = { task: CustomITask | StandaloneITask; titleCode: string; titleValues?: Record; onGoBack?: () => void; onCloseModal?: () => void; - focusPriority: FocusTarget[]; + focusPriority?: FocusTarget[]; noOverflow?: boolean; -}; - -const mapStateToProps = ({ [namespace]: { routing } }: RootState, { task: { taskSid } }: OwnProps) => { - const routeStack = getCurrentTopmostRouteStackForTask(routing, taskSid); - return { - routing: routeStack[routeStack.length - 1], - hasHistory: routeStack.length > 1, - isModal: isRouteModal(getCurrentBaseRoute(routing, taskSid)), - }; -}; - -const mapDispatchToProps = (dispatch, ownProps) => { - const taskId = ownProps.task.taskSid; - - return { - goBack: () => dispatch(newGoBackAction(taskId)), - closeModal: () => { - dispatch(newCloseModalAction(taskId)); - }, - viewContactDetails: ({ id }: Contact) => { - dispatch(changeRoute({ route: 'contact', subroute: 'view', id: id.toString() }, taskId)); - }, - }; -}; - -const connector = connect(mapStateToProps, mapDispatchToProps); - -type Props = OwnProps & ConnectedProps & StyledProps; +} & Partial; const NavigableContainer: React.FC = ({ children, - goBack, - onGoBack = () => goBack(), - closeModal, - onCloseModal = () => closeModal(), + task, + onGoBack, + onCloseModal, titleCode, titleValues = {}, - hasHistory, - isModal, focusPriority = ['back', 'close'], - routing, noOverflow, ...boxProps }) => { + const dispatch = useDispatch(); + const { taskSid } = task; + + const routeStack = useSelector((state: RootState) => + getCurrentTopmostRouteStackForTask(state[namespace].routing, taskSid), + ); + const routing = routeStack[routeStack.length - 1]; + const hasHistory = routeStack.length > 1; + const isModal = useSelector((state: RootState) => + isRouteModal(getCurrentBaseRoute(state[namespace].routing, taskSid)), + ); + + const goBack = () => dispatch(newGoBackAction(taskSid)); + const closeModal = () => dispatch(newCloseModalAction(taskSid)); + + const handleGoBack = onGoBack || goBack; + const handleCloseModal = onCloseModal || closeModal; const validFocusPriority = (focusPriority ?? []).filter( target => (target === 'back' && hasHistory) || (target === 'close' && isModal), ); @@ -99,7 +85,7 @@ const NavigableContainer: React.FC = ({ {hasHistory && ( { if (shouldFocus('back')) { @@ -119,7 +105,7 @@ const NavigableContainer: React.FC = ({ {isModal && ( { @@ -140,4 +126,4 @@ const NavigableContainer: React.FC = ({ ); }; -export default connector(NavigableContainer); +export default NavigableContainer; diff --git a/plugin-hrm-form/src/components/case/Case.tsx b/plugin-hrm-form/src/components/case/Case.tsx index 40d77e7acf..f687782e6c 100644 --- a/plugin-hrm-form/src/components/case/Case.tsx +++ b/plugin-hrm-form/src/components/case/Case.tsx @@ -15,9 +15,8 @@ */ import React, { useEffect, useState } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { CircularProgress } from '@material-ui/core'; -import { AnyAction, bindActionCreators } from 'redux'; import { RootState } from '../../states'; import { getDefinitionVersion } from '../../services/ServerlessService'; @@ -44,8 +43,6 @@ import { CenteredContainer } from './styles'; import EditCaseOverview from './caseOverview/EditCaseOverview'; import * as ContactActions from '../../states/contacts/existingContacts'; import { getHrmConfig, getTemplateStrings } from '../../hrmConfig'; -import asyncDispatch from '../../states/asyncDispatch'; -import { removeFromCaseAsyncAction } from '../../states/contacts/saveContact'; import { selectCurrentTopmostRouteForTask } from '../../states/routing/getRoute'; import { selectCurrentDefinitionVersion, selectDefinitionVersions } from '../../states/configuration/selectDefinitions'; import FullTimelineView from './timeline/FullTimelineView'; @@ -58,33 +55,41 @@ export const isStandaloneITask = (task): task is StandaloneITask => { return task && task.taskSid === 'standalone-task-sid'; }; -type OwnProps = { +type Props = { task: CustomITask | StandaloneITask; handleClose?: () => void; onNewCaseSaved?: (savedCase: CaseType) => Promise; }; -// eslint-disable-next-line no-use-before-define -type Props = OwnProps & ConnectedProps; - -const Case: React.FC = ({ - task, - connectedCaseId, - counselorsHash, - removeConnectedCase, - redirectToNewCase, - closeModal, - handleClose = closeModal, - routing, - // loadCase, - loadContacts, - releaseAllContacts, - openPrintModal, - onNewCaseSaved = () => Promise.resolve(), - contextContact, - ...props -}) => { +const Case: React.FC = ({ task, handleClose, onNewCaseSaved = () => Promise.resolve() }) => { + const dispatch = useDispatch(); const [loading, setLoading] = useState(false); + + const currentRoute = useSelector((state: RootState) => selectCurrentTopmostRouteForTask(state, task.taskSid)); + const connectedCaseId = isCaseRoute(currentRoute) ? currentRoute.caseId : undefined; + const contactId = useSelector((state: RootState) => selectContextContactId(state, task.taskSid, 'case', 'home')); + const counselorsHash = useSelector(selectCounselorsHash); + const definitionVersions = useSelector(selectDefinitionVersions); + const currentDefinitionVersion = useSelector(selectCurrentDefinitionVersion); + const routing = currentRoute as CaseRoute; + const contextContact = useSelector( + (state: RootState) => selectContactStateByContactId(state, contactId)?.savedContact, + ); + + const closeModal = () => dispatch(RoutingActions.newCloseModalAction(task.taskSid)); + const redirectToNewCase = (caseId: string) => + dispatch( + RoutingActions.changeRoute( + { route: 'case', subroute: 'home', caseId, isCreating: true }, + task.taskSid, + ChangeRouteMode.Replace, + ), + ); + const openPrintModal = (caseId: CaseType['id']) => { + dispatch(RoutingActions.newOpenModalAction({ route: 'case', subroute: 'case-print-view', caseId }, task.taskSid)); + }; + const releaseAllContacts = (referenceId: string) => dispatch(ContactActions.releaseAllContacts(referenceId)); + const { connectedCase, loading: loadingCase } = useCase({ caseId: connectedCaseId, referenceId: `case-details-${task.taskSid}`, @@ -98,6 +103,8 @@ const Case: React.FC = ({ const { workerSid } = getHrmConfig(); const strings = getTemplateStrings(); + const actualHandleClose = handleClose || closeModal; + useEffect(() => { if (routing.isCreating && !routing.caseId && contextContact?.caseId) { redirectToNewCase(contextContact.caseId); @@ -105,7 +112,6 @@ const Case: React.FC = ({ }); const version = connectedCase?.definitionVersion ?? connectedCase?.info.definitionVersion; - const { updateDefinitionVersion, definitionVersions } = props; /** * Check if the definitionVersion for this case exists in redux, and look for it if not. @@ -113,15 +119,20 @@ const Case: React.FC = ({ useEffect(() => { const fetchDefinitionVersions = async () => { const definitionVersion = await getDefinitionVersion(version); - updateDefinitionVersion(connectedCase, version, definitionVersion); + dispatch( + ConfigActions.updateDefinitionVersion( + connectedCase.definitionVersion ?? connectedCase.info.definitionVersion, + definitionVersion, + ), + ); }; if (version && !definitionVersions[version]) { fetchDefinitionVersions(); } - }, [connectedCase, definitionVersions, task.taskSid, updateDefinitionVersion, version]); + }, [connectedCase, definitionVersions, dispatch, task.taskSid, version]); - const definitionVersion = props.definitionVersions[version]; + const definitionVersion = definitionVersions[version]; if (loading || loadingCase) { return ( @@ -159,7 +170,7 @@ const Case: React.FC = ({ const handleCloseCase = async () => { releaseAllContacts(`case-${connectedCase.id}`); - handleClose(); + actualHandleClose(); }; if (isAddCaseSectionRoute(routing) || isViewCaseSectionRoute(routing) || isEditCaseSectionRoute(routing)) { @@ -226,52 +237,4 @@ const Case: React.FC = ({ Case.displayName = 'Case'; -const mapStateToProps = (state: RootState, { task }: OwnProps) => { - const currentRoute = selectCurrentTopmostRouteForTask(state, task.taskSid); - const connectedCaseId = isCaseRoute(currentRoute) ? currentRoute.caseId : undefined; - const contactId = selectContextContactId(state, task.taskSid, 'case', 'home'); - - return { - connectedCaseId, - counselorsHash: selectCounselorsHash(state), - definitionVersions: selectDefinitionVersions(state), - currentDefinitionVersion: selectCurrentDefinitionVersion(state), - routing: currentRoute as CaseRoute, - contextContact: selectContactStateByContactId(state, contactId)?.savedContact, - }; -}; - -const mapDispatchToProps = (dispatch, { task }: OwnProps) => { - const caseAsyncDispatch = asyncDispatch(dispatch); - const updateCaseDefinition = (connectedCase: CaseType, taskSid: string, definition) => { - dispatch( - ConfigActions.updateDefinitionVersion( - connectedCase.definitionVersion ?? connectedCase.info.definitionVersion, - definition, - ), - ); - }; - return { - redirectToNewCase: (caseId: string) => - dispatch( - RoutingActions.changeRoute( - { route: 'case', subroute: 'home', caseId, isCreating: true }, - task.taskSid, - ChangeRouteMode.Replace, - ), - ), - closeModal: () => dispatch(RoutingActions.newCloseModalAction(task.taskSid)), - openPrintModal: (caseId: CaseType['id']) => { - dispatch(RoutingActions.newOpenModalAction({ route: 'case', subroute: 'case-print-view', caseId }, task.taskSid)); - }, - removeConnectedCase: (contactId: string) => caseAsyncDispatch(removeFromCaseAsyncAction(contactId)), - updateDefinitionVersion: updateCaseDefinition, - releaseAllContacts: bindActionCreators(ContactActions.releaseAllContacts, dispatch), - loadContacts: bindActionCreators(ContactActions.loadContacts, dispatch), - }; -}; - -const connector = connect(mapStateToProps, mapDispatchToProps); -const connected = connector(Case); - -export default connected; +export default Case; diff --git a/plugin-hrm-form/src/components/case/caseOverview/CaseTextAreaEntry.tsx b/plugin-hrm-form/src/components/case/caseOverview/CaseTextAreaEntry.tsx index 8423057e4d..8fc5f96103 100644 --- a/plugin-hrm-form/src/components/case/caseOverview/CaseTextAreaEntry.tsx +++ b/plugin-hrm-form/src/components/case/caseOverview/CaseTextAreaEntry.tsx @@ -17,7 +17,7 @@ /* eslint-disable react/prop-types */ import React from 'react'; import { Template } from '@twilio/flex-ui'; -import { connect } from 'react-redux'; +import { useSelector } from 'react-redux'; import { CaseOverviewTypeEntry } from 'hrm-form-definitions'; import { CaseDetailsBorder, CaseSectionFont, CaseStyledTextArea } from '../styles'; @@ -26,17 +26,15 @@ import { getTemplateStrings } from '../../../hrmConfig'; import selectCurrentRouteCaseState from '../../../states/case/selectCurrentRouteCase'; import { RootState } from '../../../states'; -type OwnProps = { +type Props = { task: CustomITask | StandaloneITask; textareaFields?: (CaseOverviewTypeEntry & { placeholder?: string; })[]; }; -// eslint-disable-next-line no-use-before-define -type Props = OwnProps & ReturnType; - -const CaseTextAreaEntry: React.FC = ({ connectedCaseState, textareaFields }) => { +const CaseTextAreaEntry: React.FC = ({ task, textareaFields }) => { + const connectedCaseState = useSelector((state: RootState) => selectCurrentRouteCaseState(state, task.taskSid)); const strings = getTemplateStrings(); const { connectedCase } = connectedCaseState; @@ -73,8 +71,4 @@ const CaseTextAreaEntry: React.FC = ({ connectedCaseState, textareaFields CaseTextAreaEntry.displayName = 'CaseTextAreaEntry'; -const mapStateToProps = (state: RootState, { task }: OwnProps) => { - return { connectedCaseState: selectCurrentRouteCaseState(state, task.taskSid) }; -}; - -export default connect(mapStateToProps)(CaseTextAreaEntry); +export default CaseTextAreaEntry; diff --git a/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx b/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx index eee758bb7f..288a4bf9cf 100644 --- a/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx +++ b/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx @@ -18,11 +18,11 @@ /* eslint-disable react/prop-types */ import React, { useEffect, useMemo } from 'react'; import { Template } from '@twilio/flex-ui'; -import { connect } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { FieldValues, FormProvider, SubmitErrorHandler, useForm } from 'react-hook-form'; import { FormDefinition, FormInputType } from 'hrm-form-definitions'; import { isEqual } from 'lodash'; -import { AnyAction, bindActionCreators } from 'redux'; +import { AnyAction } from 'redux'; import { BottomButtonBar, @@ -34,7 +34,6 @@ import { TwoColumnLayout, } from '../../../styles'; import { RootState } from '../../../states'; -import * as RoutingActions from '../../../states/routing/actions'; import { newCloseModalAction, newGoBackAction } from '../../../states/routing/actions'; import type { Case, CaseOverview, CustomITask, StandaloneITask } from '../../../types/types'; import { recordingErrorHandler } from '../../../fullStory'; @@ -62,51 +61,24 @@ export type EditCaseOverviewProps = { can: (action: PermissionActionType) => boolean; }; -const mapStateToProps = (state: RootState, { task }: EditCaseOverviewProps) => { - const connectedCaseState = selectCurrentRouteCaseState(state, task.taskSid); - const historyDetails = selectCaseHistoryDetails(state, connectedCaseState?.connectedCase); - const workingCopy = connectedCaseState?.caseWorkingCopy.caseSummary; - const isUpdating = (connectedCaseState?.outstandingUpdateCount ?? 0) > 0; - const definitionVersion = selectDefinitionVersionForCase(state, connectedCaseState?.connectedCase); - return { connectedCaseState, workingCopy, definitionVersion, historyDetails, isUpdating }; -}; - -const mapDispatchToProps = (dispatch, { task }: EditCaseOverviewProps) => { - const updateCaseAsyncDispatch = asyncDispatch(dispatch); - return { - changeRoute: bindActionCreators(RoutingActions.changeRoute, dispatch), - initialiseWorkingCopy: bindActionCreators(initialiseCaseSummaryWorkingCopy, dispatch), - updateWorkingCopy: bindActionCreators(updateCaseSummaryWorkingCopy, dispatch), - closeActions: (caseId: string, closeModal: boolean) => { - dispatch(removeCaseSummaryWorkingCopy(caseId)); - dispatch(closeModal ? newCloseModalAction(task.taskSid) : newGoBackAction(task.taskSid)); - }, - updateCaseAsyncAction: (caseId: Case['id'], overview: CaseOverview, status: Case['status']) => - updateCaseAsyncDispatch(updateCaseOverviewAsyncAction(caseId, overview, status)), - }; -}; - -type Props = EditCaseOverviewProps & ReturnType & ReturnType; - const enum DialogState { Closed, OpenForBack, OpenForClose, } -const EditCaseOverview: React.FC = ({ - task, - historyDetails, - connectedCaseState, - workingCopy, - initialiseWorkingCopy, - updateWorkingCopy, - closeActions, - can, - updateCaseAsyncAction, - isUpdating, - definitionVersion, -}) => { +const EditCaseOverview: React.FC = ({ task, can }) => { + const dispatch = useDispatch(); + + const connectedCaseState = useSelector((state: RootState) => selectCurrentRouteCaseState(state, task.taskSid)); + const historyDetails = useSelector((state: RootState) => + selectCaseHistoryDetails(state, connectedCaseState?.connectedCase), + ); + const workingCopy = connectedCaseState?.caseWorkingCopy.caseSummary; + const isUpdating = (connectedCaseState?.outstandingUpdateCount ?? 0) > 0; + const definitionVersion = useSelector((state: RootState) => + selectDefinitionVersionForCase(state, connectedCaseState?.connectedCase), + ); const { connectedCase, availableStatusTransitions } = connectedCaseState ?? {}; const caseOverviewFields = definitionVersion?.caseOverview; @@ -180,15 +152,16 @@ const EditCaseOverview: React.FC = ({ useEffect(() => { if (!workingCopy) { - initialiseWorkingCopy(connectedCase.id, initialValues); + dispatch(initialiseCaseSummaryWorkingCopy(connectedCase.id, initialValues)); } - }, [connectedCase.id, initialValues, initialiseWorkingCopy, workingCopy]); + }, [connectedCase.id, initialValues, dispatch, workingCopy]); const form = useCreateFormFromDefinition({ definition: formDefinition, initialValues, parentsPath: '', - updateCallback: () => updateWorkingCopy(connectedCase.id, getValues() as CaseSummaryWorkingCopy), + updateCallback: () => + dispatch(updateCaseSummaryWorkingCopy(connectedCase.id, getValues() as CaseSummaryWorkingCopy)), isItemEnabled: item => item.name === 'status' || can(PermissionActions.EDIT_CASE_OVERVIEW), shouldFocusFirstElement: false, }); @@ -215,12 +188,15 @@ const EditCaseOverview: React.FC = ({ const { status: oldStatus, id } = connectedCaseState.connectedCase; const { status, ...updatedInfoValues } = workingCopy; - await updateCaseAsyncAction(id, updatedInfoValues, status === oldStatus ? undefined : status); + await asyncDispatch(dispatch)( + updateCaseOverviewAsyncAction(id, updatedInfoValues, status === oldStatus ? undefined : status), + ); }; const saveAndLeave = async () => { await save(); - closeActions(connectedCase.id, false); + dispatch(removeCaseSummaryWorkingCopy(connectedCase.id)); + dispatch(newGoBackAction(task.taskSid)); }; const strings = getTemplateStrings(); @@ -231,7 +207,8 @@ const EditCaseOverview: React.FC = ({ const checkForEdits = (closeModal: boolean) => { if (isEqual(workingCopy, savedForm)) { - closeActions(connectedCase.id, closeModal); + dispatch(removeCaseSummaryWorkingCopy(connectedCase.id)); + dispatch(closeModal ? newCloseModalAction(task.taskSid) : newGoBackAction(task.taskSid)); } else setDialogState(closeModal ? DialogState.OpenForClose : DialogState.OpenForBack); }; @@ -240,8 +217,8 @@ const EditCaseOverview: React.FC = ({ checkForEdits(false)} + onCloseModal={() => checkForEdits(true)} data-testid="Case-EditCaseOverview" > @@ -268,7 +245,14 @@ const EditCaseOverview: React.FC = ({ data-testid="CloseCaseDialog" openDialog={dialogState === DialogState.OpenForClose || dialogState === DialogState.OpenForBack} setDialog={() => setDialogState(DialogState.Closed)} - handleDontSaveClose={() => closeActions(connectedCase.id, dialogState === DialogState.OpenForClose)} + handleDontSaveClose={() => { + dispatch(removeCaseSummaryWorkingCopy(connectedCase.id)); + dispatch( + dialogState === DialogState.OpenForClose + ? newCloseModalAction(task.taskSid) + : newGoBackAction(task.taskSid), + ); + }} handleSaveUpdate={methods.handleSubmit(saveAndLeave, onError)} /> @@ -278,4 +262,4 @@ const EditCaseOverview: React.FC = ({ EditCaseOverview.displayName = 'EditCaseOverview'; -export default connect(mapStateToProps, mapDispatchToProps)(EditCaseOverview); +export default EditCaseOverview; diff --git a/plugin-hrm-form/src/components/case/casePrint/CasePrintView.tsx b/plugin-hrm-form/src/components/case/casePrint/CasePrintView.tsx index a7cbac9ca0..91b436a4f7 100644 --- a/plugin-hrm-form/src/components/case/casePrint/CasePrintView.tsx +++ b/plugin-hrm-form/src/components/case/casePrint/CasePrintView.tsx @@ -210,7 +210,11 @@ const CasePrintView: React.FC = ({ task }) => { ); return ( - dispatch(RoutingActions.newGoBackAction(task.taskSid))}> + dispatch(RoutingActions.newGoBackAction(task.taskSid))} + > {loading || !sectionTypeNames.every(sectionName => sectionTimelines[sectionName]) || !contactTimeline ? ( diff --git a/plugin-hrm-form/src/components/case/timeline/FullTimelineView.tsx b/plugin-hrm-form/src/components/case/timeline/FullTimelineView.tsx index aecdbd7c27..c598e58bfe 100644 --- a/plugin-hrm-form/src/components/case/timeline/FullTimelineView.tsx +++ b/plugin-hrm-form/src/components/case/timeline/FullTimelineView.tsx @@ -14,8 +14,8 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -import React, { Dispatch } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { Template } from '@twilio/flex-ui'; import NavigableContainer from '../../NavigableContainer'; @@ -37,69 +37,52 @@ type MyProps = { const TIMELINE_PAGE_SIZE = 25; const MAIN_TIMELINE_ID = 'prime-timeline'; -const mapStateToProps = (state: RootState, { task }: MyProps) => { - const { caseId, page = 0 } = selectCurrentTopmostRouteForTask(state, task.taskSid) as CaseTimelineRoute; - return { - page, - activityCount: selectTimelineCount(state, caseId, MAIN_TIMELINE_ID), - caseId, - }; -}; - -const mapDispatchToProps = (dispatch: Dispatch, { task }: MyProps) => { - return { - changePage: (caseId: string) => (page: number) => { - dispatch( - changeRoute({ route: 'case', subroute: 'timeline', caseId, page }, task.taskSid, ChangeRouteMode.Replace), - ); - dispatch( - newGetTimelineAsyncAction( - caseId, - 'prime-timeline', - ['note', 'referral'], - true, - { - offset: page * TIMELINE_PAGE_SIZE, - limit: TIMELINE_PAGE_SIZE, - }, - `case-${caseId}`, - ), - ); - }, - }; -}; - -const connector = connect(mapStateToProps, mapDispatchToProps); +const FullTimelineView: React.FC = ({ task }: MyProps) => { + const dispatch = useDispatch(); -type Props = MyProps & ConnectedProps; + const { caseId, page = 0 } = useSelector((state: RootState) => + selectCurrentTopmostRouteForTask(state, task.taskSid), + ) as CaseTimelineRoute; + const activityCount = useSelector((state: RootState) => selectTimelineCount(state, caseId, MAIN_TIMELINE_ID)); -const FullTimelineView: React.FC = ({ task, page, activityCount, changePage, caseId }: Props) => { - const changePageForThisCase = changePage(caseId); + const changePageForThisCase = (page: number) => { + dispatch(changeRoute({ route: 'case', subroute: 'timeline', caseId, page }, task.taskSid, ChangeRouteMode.Replace)); + dispatch( + newGetTimelineAsyncAction( + caseId, + 'prime-timeline', + ['note', 'referral'], + true, + { + offset: page * TIMELINE_PAGE_SIZE, + limit: TIMELINE_PAGE_SIZE, + }, + `case-${caseId}`, + ), + ); + }; return ( - - - -

-