From edafc7e91fca39b085da7b6b0f13e08a5903889e Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:34:43 +0100 Subject: [PATCH 01/16] feat: format response action timeline autocalculate timeLine value based on the dueDate value --- .../repositories/consts/IncidentActionConstants.ts | 2 -- src/data/repositories/utils/DateTimeHelper.ts | 10 ++++++++++ .../entities/incident-action-plan/ResponseAction.ts | 1 - .../utils/incident-action/GetIncidentActionById.ts | 1 - .../mapIncidentActionToInitialFormState.ts | 11 ----------- .../pages/form-page/mapFormStateToEntityData.ts | 4 ---- .../incident-action-plan/useIncidentActionPlan.ts | 7 +++++-- 7 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/data/repositories/consts/IncidentActionConstants.ts b/src/data/repositories/consts/IncidentActionConstants.ts index 8f7927b4..87c552b7 100644 --- a/src/data/repositories/consts/IncidentActionConstants.ts +++ b/src/data/repositories/consts/IncidentActionConstants.ts @@ -60,7 +60,6 @@ export const responseActionConstants = { subPillar: "RTSL_ZEB_DET_SUB_PILLAR", searchAssignRO: "RTSL_ZEB_DET_SEARCH_ASSIGN_RO", dueDate: "RTSL_ZEB_DET_DUE_DATE", - timeLine: "RTSL_ZEB_DET_TIMELINE", status: "RTSL_ZEB_DET_STATUS", verification: "RTSL_ZEB_DET_VERIFICATION", } as const; @@ -127,7 +126,6 @@ export function getValueFromIncidentResponseAction( RTSL_ZEB_DET_SUB_PILLAR: incidentResponseAction.subPillar || "", RTSL_ZEB_DET_SEARCH_ASSIGN_RO: incidentResponseAction.searchAssignRO?.username || "", RTSL_ZEB_DET_DUE_DATE: incidentResponseAction.dueDate.toISOString(), - RTSL_ZEB_DET_TIMELINE: incidentResponseAction.timeLine || "", RTSL_ZEB_DET_STATUS: incidentResponseAction.status, RTSL_ZEB_DET_VERIFICATION: incidentResponseAction.verification, }; diff --git a/src/data/repositories/utils/DateTimeHelper.ts b/src/data/repositories/utils/DateTimeHelper.ts index 552f297b..cf1f68ab 100644 --- a/src/data/repositories/utils/DateTimeHelper.ts +++ b/src/data/repositories/utils/DateTimeHelper.ts @@ -43,3 +43,13 @@ export function getDateAsLocaleDateString(date: Date): string { return ""; } } + +const getQuarter = (month: number): number => Math.ceil((month + 1) / 3); + +export function formatQuarterString(date: Date): string { + const year = date.getFullYear(); + const month = date.toLocaleString("default", { month: "long" }); + const quarter = getQuarter(date.getMonth()); + + return `Qtr ${quarter}, ${month} ${year}`; +} diff --git a/src/domain/entities/incident-action-plan/ResponseAction.ts b/src/domain/entities/incident-action-plan/ResponseAction.ts index 2c32d2a8..54ad16dd 100644 --- a/src/domain/entities/incident-action-plan/ResponseAction.ts +++ b/src/domain/entities/incident-action-plan/ResponseAction.ts @@ -25,7 +25,6 @@ interface ResponseActionAttrs { subPillar: string; searchAssignRO: Maybe; dueDate: Date; - timeLine: string; status: Status; verification: Verification; } diff --git a/src/domain/usecases/utils/incident-action/GetIncidentActionById.ts b/src/domain/usecases/utils/incident-action/GetIncidentActionById.ts index dff75442..04ff9b40 100644 --- a/src/domain/usecases/utils/incident-action/GetIncidentActionById.ts +++ b/src/domain/usecases/utils/incident-action/GetIncidentActionById.ts @@ -69,7 +69,6 @@ export function getIncidentAction( dueDate: responseActionDataValue?.dueDate ? new Date(responseActionDataValue.dueDate) : new Date(), - timeLine: responseActionDataValue?.timeLine ?? "", status: status, verification: verification, }); diff --git a/src/webapp/pages/form-page/incident-action/mapIncidentActionToInitialFormState.ts b/src/webapp/pages/form-page/incident-action/mapIncidentActionToInitialFormState.ts index 8166e870..0a0f654a 100644 --- a/src/webapp/pages/form-page/incident-action/mapIncidentActionToInitialFormState.ts +++ b/src/webapp/pages/form-page/incident-action/mapIncidentActionToInitialFormState.ts @@ -349,17 +349,6 @@ function getResponseActionSection(options: { showIsRequired: true, disabled: false, }, - { - id: `${responseActionConstants.timeLine}_${index}`, - label: "Time line", - isVisible: true, - errors: [], - value: incidentResponseAction?.timeLine || "", - type: "text", - required: true, - showIsRequired: true, - disabled: false, - }, { id: `${responseActionConstants.status}_${index}`, label: "Status", diff --git a/src/webapp/pages/form-page/mapFormStateToEntityData.ts b/src/webapp/pages/form-page/mapFormStateToEntityData.ts index a8643690..a2a0de10 100644 --- a/src/webapp/pages/form-page/mapFormStateToEntityData.ts +++ b/src/webapp/pages/form-page/mapFormStateToEntityData.ts @@ -557,9 +557,6 @@ function mapFormStateToIncidentResponseAction( const dueDate = allFields.find(field => field.id.includes(`${responseActionConstants.dueDate}_${index}`) )?.value as Date; - const timeLine = allFields.find(field => - field.id.includes(`${responseActionConstants.timeLine}_${index}`) - )?.value as string; const searchAssignROValue = allFields.find(field => field.id.includes(`${responseActionConstants.searchAssignRO}_${index}`) @@ -589,7 +586,6 @@ function mapFormStateToIncidentResponseAction( subActivities: subActivities, subPillar: subPillar, dueDate: dueDate, - timeLine: timeLine, searchAssignRO: searchAssignRO, status: status.id as Status, verification: verification.id as Verification, diff --git a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts index 0674fed2..51ce24ea 100644 --- a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts +++ b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts @@ -2,7 +2,10 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Id } from "../../../domain/entities/Ref"; import { Maybe } from "../../../utils/ts-utils"; import { useAppContext } from "../../contexts/app-context"; -import { getDateAsLocaleDateTimeString } from "../../../data/repositories/utils/DateTimeHelper"; +import { + formatQuarterString, + getDateAsLocaleDateTimeString, +} from "../../../data/repositories/utils/DateTimeHelper"; import { TableColumn, TableRowType } from "../../components/table/BasicTable"; import { getIAPTypeByCode, @@ -180,7 +183,7 @@ const mapIncidentResponseActionToTableRows = ( searchAssignRO: responseAction.searchAssignRO?.username ?? "", status: getStatusTypeByCode(responseAction.status) ?? "", verification: getVerificationTypeByCode(responseAction.verification) ?? "", - timeLine: responseAction.timeLine, + timeLine: formatQuarterString(responseAction.dueDate), dueDate: responseAction.dueDate.toISOString(), })); } else { From e8145244cb980af4f0b41bf213cc49da4d13f06c Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:44:31 +0100 Subject: [PATCH 02/16] feat: reverse response action table rows --- .../useIncidentActionPlan.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts index 51ce24ea..97895110 100644 --- a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts +++ b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts @@ -18,6 +18,7 @@ import { IncidentActionPlan, } from "../../../domain/entities/incident-action-plan/IncidentActionPlan"; import { Option } from "../../components/utils/option"; +import _c from "../../../domain/entities/generic/Collection"; export type IncidentActionFormSummaryData = { subTitle: string; @@ -175,17 +176,20 @@ const mapIncidentResponseActionToTableRows = ( incidentActionPlan: Maybe ): TableRowType[] => { if (incidentActionPlan) { - return incidentActionPlan.responseActions.map(responseAction => ({ - id: responseAction.id, - mainTask: responseAction.mainTask, - subActivities: responseAction.subActivities, - subPillar: responseAction.subPillar, - searchAssignRO: responseAction.searchAssignRO?.username ?? "", - status: getStatusTypeByCode(responseAction.status) ?? "", - verification: getVerificationTypeByCode(responseAction.verification) ?? "", - timeLine: formatQuarterString(responseAction.dueDate), - dueDate: responseAction.dueDate.toISOString(), - })); + return _c(incidentActionPlan.responseActions) + .map(responseAction => ({ + id: responseAction.id, + mainTask: responseAction.mainTask, + subActivities: responseAction.subActivities, + subPillar: responseAction.subPillar, + searchAssignRO: responseAction.searchAssignRO?.username ?? "", + status: getStatusTypeByCode(responseAction.status) ?? "", + verification: getVerificationTypeByCode(responseAction.verification) ?? "", + timeLine: formatQuarterString(responseAction.dueDate), + dueDate: responseAction.dueDate.toISOString(), + })) + .reverse() + .value(); } else { return []; } From 812151e81ef834a018f9c9c5c51c87a94a23744f Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:15:23 +0100 Subject: [PATCH 03/16] feat: change backdrop bg-color for loader --- src/webapp/components/loader/Loader.tsx | 2 +- src/webapp/pages/app/themes/dhis2.theme.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/webapp/components/loader/Loader.tsx b/src/webapp/components/loader/Loader.tsx index f9962ef3..15701443 100644 --- a/src/webapp/components/loader/Loader.tsx +++ b/src/webapp/components/loader/Loader.tsx @@ -18,5 +18,5 @@ const StyledLoaderContainer = styled.div` const StyledBackdrop = styled(Backdrop)` color: ${props => props.theme.palette.common.white}; - z-index: 1; + z-index: 2; `; diff --git a/src/webapp/pages/app/themes/dhis2.theme.ts b/src/webapp/pages/app/themes/dhis2.theme.ts index e6002d57..0cb8d8a8 100644 --- a/src/webapp/pages/app/themes/dhis2.theme.ts +++ b/src/webapp/pages/app/themes/dhis2.theme.ts @@ -370,5 +370,10 @@ export const muiTheme = createTheme({ backgroundColor: colors.grey200, }, }, + MuiBackdrop: { + root: { + backgroundColor: colors.grey, + }, + }, }, }); From dfaaf5d0de7c0da4c80c08ca2cf21766a4ba127e Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:34:43 +0100 Subject: [PATCH 04/16] feat: format response action timeline autocalculate timeLine value based on the dueDate value --- .../repositories/consts/IncidentActionConstants.ts | 2 -- src/data/repositories/utils/DateTimeHelper.ts | 10 ++++++++++ .../entities/incident-action-plan/ResponseAction.ts | 1 - .../utils/incident-action/GetIncidentActionById.ts | 1 - .../mapIncidentActionToInitialFormState.ts | 11 ----------- .../pages/form-page/mapFormStateToEntityData.ts | 4 ---- .../incident-action-plan/useIncidentActionPlan.ts | 3 ++- 7 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/data/repositories/consts/IncidentActionConstants.ts b/src/data/repositories/consts/IncidentActionConstants.ts index 8f7927b4..87c552b7 100644 --- a/src/data/repositories/consts/IncidentActionConstants.ts +++ b/src/data/repositories/consts/IncidentActionConstants.ts @@ -60,7 +60,6 @@ export const responseActionConstants = { subPillar: "RTSL_ZEB_DET_SUB_PILLAR", searchAssignRO: "RTSL_ZEB_DET_SEARCH_ASSIGN_RO", dueDate: "RTSL_ZEB_DET_DUE_DATE", - timeLine: "RTSL_ZEB_DET_TIMELINE", status: "RTSL_ZEB_DET_STATUS", verification: "RTSL_ZEB_DET_VERIFICATION", } as const; @@ -127,7 +126,6 @@ export function getValueFromIncidentResponseAction( RTSL_ZEB_DET_SUB_PILLAR: incidentResponseAction.subPillar || "", RTSL_ZEB_DET_SEARCH_ASSIGN_RO: incidentResponseAction.searchAssignRO?.username || "", RTSL_ZEB_DET_DUE_DATE: incidentResponseAction.dueDate.toISOString(), - RTSL_ZEB_DET_TIMELINE: incidentResponseAction.timeLine || "", RTSL_ZEB_DET_STATUS: incidentResponseAction.status, RTSL_ZEB_DET_VERIFICATION: incidentResponseAction.verification, }; diff --git a/src/data/repositories/utils/DateTimeHelper.ts b/src/data/repositories/utils/DateTimeHelper.ts index 90b28c0e..cec911f3 100644 --- a/src/data/repositories/utils/DateTimeHelper.ts +++ b/src/data/repositories/utils/DateTimeHelper.ts @@ -48,3 +48,13 @@ export function getDateAsLocaleDateString(date: Date): string { export function getISODateAsLocaleDateString(date: string): Date { return moment.utc(date).local().toDate(); } + +const getQuarter = (month: number): number => Math.ceil((month + 1) / 3); + +export function formatQuarterString(date: Date): string { + const year = date.getFullYear(); + const month = date.toLocaleString("default", { month: "long" }); + const quarter = getQuarter(date.getMonth()); + + return `Qtr ${quarter}, ${month} ${year}`; +} diff --git a/src/domain/entities/incident-action-plan/ResponseAction.ts b/src/domain/entities/incident-action-plan/ResponseAction.ts index 2c32d2a8..54ad16dd 100644 --- a/src/domain/entities/incident-action-plan/ResponseAction.ts +++ b/src/domain/entities/incident-action-plan/ResponseAction.ts @@ -25,7 +25,6 @@ interface ResponseActionAttrs { subPillar: string; searchAssignRO: Maybe; dueDate: Date; - timeLine: string; status: Status; verification: Verification; } diff --git a/src/domain/usecases/utils/incident-action/GetIncidentActionById.ts b/src/domain/usecases/utils/incident-action/GetIncidentActionById.ts index 278d0da4..7fd0702a 100644 --- a/src/domain/usecases/utils/incident-action/GetIncidentActionById.ts +++ b/src/domain/usecases/utils/incident-action/GetIncidentActionById.ts @@ -65,7 +65,6 @@ export function getIncidentAction( dueDate: responseActionDataValue?.dueDate ? new Date(responseActionDataValue.dueDate) : new Date(), - timeLine: responseActionDataValue?.timeLine ?? "", status: status, verification: verification, }); diff --git a/src/webapp/pages/form-page/incident-action/mapIncidentActionToInitialFormState.ts b/src/webapp/pages/form-page/incident-action/mapIncidentActionToInitialFormState.ts index 8166e870..0a0f654a 100644 --- a/src/webapp/pages/form-page/incident-action/mapIncidentActionToInitialFormState.ts +++ b/src/webapp/pages/form-page/incident-action/mapIncidentActionToInitialFormState.ts @@ -349,17 +349,6 @@ function getResponseActionSection(options: { showIsRequired: true, disabled: false, }, - { - id: `${responseActionConstants.timeLine}_${index}`, - label: "Time line", - isVisible: true, - errors: [], - value: incidentResponseAction?.timeLine || "", - type: "text", - required: true, - showIsRequired: true, - disabled: false, - }, { id: `${responseActionConstants.status}_${index}`, label: "Status", diff --git a/src/webapp/pages/form-page/mapFormStateToEntityData.ts b/src/webapp/pages/form-page/mapFormStateToEntityData.ts index 25de3fd4..b738a802 100644 --- a/src/webapp/pages/form-page/mapFormStateToEntityData.ts +++ b/src/webapp/pages/form-page/mapFormStateToEntityData.ts @@ -559,9 +559,6 @@ function mapFormStateToIncidentResponseAction( const dueDate = allFields.find(field => field.id.includes(`${responseActionConstants.dueDate}_${index}`) )?.value as Date; - const timeLine = allFields.find(field => - field.id.includes(`${responseActionConstants.timeLine}_${index}`) - )?.value as string; const searchAssignROValue = allFields.find(field => field.id.includes(`${responseActionConstants.searchAssignRO}_${index}`) @@ -591,7 +588,6 @@ function mapFormStateToIncidentResponseAction( subActivities: subActivities, subPillar: subPillar, dueDate: dueDate, - timeLine: timeLine, searchAssignRO: searchAssignRO, status: status.id as Status, verification: verification.id as Verification, diff --git a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts index d3507ce2..ceef42ea 100644 --- a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts +++ b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Id } from "../../../domain/entities/Ref"; import { Maybe } from "../../../utils/ts-utils"; import { useAppContext } from "../../contexts/app-context"; +import { formatQuarterString } from "../../../data/repositories/utils/DateTimeHelper"; import { TableColumn, TableRowType } from "../../components/table/BasicTable"; import { getIAPTypeByCode, @@ -197,7 +198,7 @@ const mapIncidentResponseActionToTableRows = ( searchAssignRO: responseAction.searchAssignRO?.username ?? "", status: getStatusTypeByCode(responseAction.status) ?? "", verification: getVerificationTypeByCode(responseAction.verification) ?? "", - timeLine: responseAction.timeLine, + timeLine: formatQuarterString(responseAction.dueDate), dueDate: responseAction.dueDate.toISOString(), })); } else { From 07363b100107ecc74b5fc0f932235674e10685bf Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:44:31 +0100 Subject: [PATCH 05/16] feat: reverse response action table rows --- .../useIncidentActionPlan.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts index ceef42ea..8f6000bb 100644 --- a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts +++ b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts @@ -17,6 +17,7 @@ import { import { Option } from "../../components/utils/option"; import { useCurrentEventTracker } from "../../contexts/current-event-tracker-context"; import { DiseaseOutbreakEvent } from "../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent"; +import _c from "../../../domain/entities/generic/Collection"; export type IncidentActionFormSummaryData = { subTitle: string; @@ -190,17 +191,20 @@ const mapIncidentResponseActionToTableRows = ( incidentActionPlan: Maybe ): TableRowType[] => { if (incidentActionPlan) { - return incidentActionPlan.responseActions.map(responseAction => ({ - id: responseAction.id, - mainTask: responseAction.mainTask, - subActivities: responseAction.subActivities, - subPillar: responseAction.subPillar, - searchAssignRO: responseAction.searchAssignRO?.username ?? "", - status: getStatusTypeByCode(responseAction.status) ?? "", - verification: getVerificationTypeByCode(responseAction.verification) ?? "", - timeLine: formatQuarterString(responseAction.dueDate), - dueDate: responseAction.dueDate.toISOString(), - })); + return _c(incidentActionPlan.responseActions) + .map(responseAction => ({ + id: responseAction.id, + mainTask: responseAction.mainTask, + subActivities: responseAction.subActivities, + subPillar: responseAction.subPillar, + searchAssignRO: responseAction.searchAssignRO?.username ?? "", + status: getStatusTypeByCode(responseAction.status) ?? "", + verification: getVerificationTypeByCode(responseAction.verification) ?? "", + timeLine: formatQuarterString(responseAction.dueDate), + dueDate: responseAction.dueDate.toISOString(), + })) + .reverse() + .value(); } else { return []; } From 69e0c062424658c38a2f0e3ddac1275fe68b6a80 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:15:23 +0100 Subject: [PATCH 06/16] feat: change backdrop bg-color for loader --- src/webapp/components/loader/Loader.tsx | 2 +- src/webapp/pages/app/themes/dhis2.theme.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/webapp/components/loader/Loader.tsx b/src/webapp/components/loader/Loader.tsx index f9962ef3..15701443 100644 --- a/src/webapp/components/loader/Loader.tsx +++ b/src/webapp/components/loader/Loader.tsx @@ -18,5 +18,5 @@ const StyledLoaderContainer = styled.div` const StyledBackdrop = styled(Backdrop)` color: ${props => props.theme.palette.common.white}; - z-index: 1; + z-index: 2; `; diff --git a/src/webapp/pages/app/themes/dhis2.theme.ts b/src/webapp/pages/app/themes/dhis2.theme.ts index e6002d57..0cb8d8a8 100644 --- a/src/webapp/pages/app/themes/dhis2.theme.ts +++ b/src/webapp/pages/app/themes/dhis2.theme.ts @@ -370,5 +370,10 @@ export const muiTheme = createTheme({ backgroundColor: colors.grey200, }, }, + MuiBackdrop: { + root: { + backgroundColor: colors.grey, + }, + }, }, }); From d9fa36daf6be4d45438671a39462da4e5f21bb89 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:27:53 +0100 Subject: [PATCH 07/16] feat: sort response action table by due date --- src/data/repositories/utils/DateTimeHelper.ts | 2 +- src/webapp/components/table/BasicTable.tsx | 4 +- .../IncidentActionPlanPage.tsx | 2 + .../ResponseActionTable.tsx | 4 +- .../useIncidentActionPlan.ts | 111 +++++++++++++----- 5 files changed, 89 insertions(+), 34 deletions(-) diff --git a/src/data/repositories/utils/DateTimeHelper.ts b/src/data/repositories/utils/DateTimeHelper.ts index cec911f3..5715fe6a 100644 --- a/src/data/repositories/utils/DateTimeHelper.ts +++ b/src/data/repositories/utils/DateTimeHelper.ts @@ -53,7 +53,7 @@ const getQuarter = (month: number): number => Math.ceil((month + 1) / 3); export function formatQuarterString(date: Date): string { const year = date.getFullYear(); - const month = date.toLocaleString("default", { month: "long" }); + const month = date.toLocaleString("default", { month: "short" }); const quarter = getQuarter(date.getMonth()); return `Qtr ${quarter}, ${month} ${year}`; diff --git a/src/webapp/components/table/BasicTable.tsx b/src/webapp/components/table/BasicTable.tsx index 35fa3bd0..b07ff573 100644 --- a/src/webapp/components/table/BasicTable.tsx +++ b/src/webapp/components/table/BasicTable.tsx @@ -45,6 +45,8 @@ interface BasicTableProps { onOrderBy?: (direction: "asc" | "desc") => void; } +const sortableColumnLabels = ["Assessment Date", "Due date"]; + export const BasicTable: React.FC = React.memo( ({ columns, rows, onChange = noop, showRowIndex = false, onOrderBy }) => { const [order, setOrder] = useState<"asc" | "desc">(); @@ -61,7 +63,7 @@ export const BasicTable: React.FC = React.memo( {showRowIndex && } {columns.map(({ value, label }) => - label === "Assessment Date" ? ( + sortableColumnLabels.includes(label) ? ( { summaryError, incidentActionExists, responseActionColumns, + orderByDueDate, saveTableOption, } = useIncidentActionPlan(id); @@ -65,6 +66,7 @@ export const IncidentActionPlanPage: React.FC = React.memo(() => { responseActionColumns={responseActionColumns} responseActionRows={responseActionRows} onChange={saveTableOption} + onOrderBy={orderByDueDate} /> , rowIndex: number, column: TableColumn["value"]) => void; + onOrderBy: (direction: "asc" | "desc") => void; responseActionColumns: TableColumn[]; responseActionRows: { [key: TableColumn["value"]]: string; @@ -16,7 +17,7 @@ type ResponseActionTableProps = { }; export const ResponseActionTable: React.FC = React.memo( - ({ onChange, responseActionColumns, responseActionRows }) => { + ({ onChange, onOrderBy, responseActionColumns, responseActionRows }) => { const { goTo } = useRoutes(); const { icon: responseActionIcon, label: responseActionLabel } = @@ -48,6 +49,7 @@ export const ResponseActionTable: React.FC = React.mem onChange={onChange} columns={responseActionColumns} rows={responseActionRows} + onOrderBy={onOrderBy} /> diff --git a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts index 8f6000bb..54753178 100644 --- a/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts +++ b/src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts @@ -2,7 +2,10 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Id } from "../../../domain/entities/Ref"; import { Maybe } from "../../../utils/ts-utils"; import { useAppContext } from "../../contexts/app-context"; -import { formatQuarterString } from "../../../data/repositories/utils/DateTimeHelper"; +import { + formatQuarterString, + getISODateAsLocaleDateString, +} from "../../../data/repositories/utils/DateTimeHelper"; import { TableColumn, TableRowType } from "../../components/table/BasicTable"; import { getIAPTypeByCode, @@ -32,11 +35,13 @@ export type UIIncidentActionOptions = { export function useIncidentActionPlan(id: Id) { const { compositionRoot, configurations: appConfiguration } = useAppContext(); const { changeCurrentEventTracker, getCurrentEventTracker } = useCurrentEventTracker(); + const currentEventTracker = getCurrentEventTracker(); const [incidentAction, setIncidentAction] = useState(); const [actionPlanSummary, setActionPlanSummary] = useState(); const [responseActionRows, setResponseActionRows] = useState([]); const [globalMessage, setGlobalMessage] = useState(); + const [incidentActionPlan, setIncidentActionPlan] = useState(); const [incidentActionExists, setIncidentActionExists] = useState(false); const [incidentActionOptions, setIncidentActionOptions] = useState(); @@ -91,22 +96,8 @@ export function useIncidentActionPlan(id: Id) { incidentActionPlan => { const incidentActionExists = !!incidentActionPlan?.actionPlan?.id; const incidentActionOptions = incidentActionPlan?.incidentActionOptions; - const currentEventTracker = getCurrentEventTracker(); - if ( - incidentActionExists && - currentEventTracker && - (currentEventTracker.incidentActionPlan?.actionPlan?.lastUpdated !== - incidentActionPlan.actionPlan?.lastUpdated || - currentEventTracker.incidentActionPlan?.responseActions.length !== - incidentActionPlan.responseActions.length) - ) { - const updatedEventTracker = new DiseaseOutbreakEvent({ - ...currentEventTracker, - incidentActionPlan: incidentActionPlan, - }); - changeCurrentEventTracker(updatedEventTracker); - } + setIncidentActionPlan(incidentActionPlan); setIncidentActionExists(incidentActionExists); setIncidentActionOptions(mapIncidentActionOptionsToTable(incidentActionOptions)); setIncidentAction(getIncidentActionFormSummary(incidentActionPlan)); @@ -118,7 +109,58 @@ export function useIncidentActionPlan(id: Id) { setGlobalMessage(`Event tracker with id: ${id} does not exist`); } ); - }, [compositionRoot, id, changeCurrentEventTracker, getCurrentEventTracker, appConfiguration]); + }, [appConfiguration, compositionRoot.incidentActionPlan.get, id]); + + useEffect(() => { + if ( + incidentActionExists && + currentEventTracker && + (currentEventTracker.incidentActionPlan?.actionPlan?.lastUpdated !== + incidentActionPlan?.actionPlan?.lastUpdated || + currentEventTracker.incidentActionPlan?.responseActions.length !== + incidentActionPlan?.responseActions.length) + ) { + const updatedEventTracker = new DiseaseOutbreakEvent({ + ...currentEventTracker, + incidentActionPlan: incidentActionPlan, + }); + + changeCurrentEventTracker(updatedEventTracker); + } + }, [changeCurrentEventTracker, currentEventTracker, incidentActionExists, incidentActionPlan]); + + const orderByDueDate = useCallback( + (direction: "asc" | "desc") => { + setResponseActionRows(prevRows => { + if (direction === "asc") { + const sortedRows = prevRows.sort((a, b) => { + if (!a.dueDate) return -1; + if (!b.dueDate) return 1; + + const dateA = new Date(a.dueDate).toISOString(); + const dateB = new Date(b.dueDate).toISOString(); + + return dateA < dateB ? -1 : dateA > dateB ? 1 : 0; + }); + + return sortedRows; + } else { + const sortedRows = prevRows.sort((a, b) => { + if (!a.dueDate) return -1; + if (!b.dueDate) return -1; + + const dateA = new Date(a.dueDate).toISOString(); + const dateB = new Date(b.dueDate).toISOString(); + + return dateA < dateB ? 1 : dateA > dateB ? -1 : 0; + }); + + return sortedRows; + } + }); + }, + [setResponseActionRows] + ); return { incidentActionExists: incidentActionExists, @@ -128,6 +170,7 @@ export function useIncidentActionPlan(id: Id) { formSummary: incidentAction, responseActionRows: responseActionRows, summaryError: globalMessage, + orderByDueDate: orderByDueDate, }; } @@ -191,20 +234,26 @@ const mapIncidentResponseActionToTableRows = ( incidentActionPlan: Maybe ): TableRowType[] => { if (incidentActionPlan) { - return _c(incidentActionPlan.responseActions) - .map(responseAction => ({ - id: responseAction.id, - mainTask: responseAction.mainTask, - subActivities: responseAction.subActivities, - subPillar: responseAction.subPillar, - searchAssignRO: responseAction.searchAssignRO?.username ?? "", - status: getStatusTypeByCode(responseAction.status) ?? "", - verification: getVerificationTypeByCode(responseAction.verification) ?? "", - timeLine: formatQuarterString(responseAction.dueDate), - dueDate: responseAction.dueDate.toISOString(), - })) - .reverse() - .value(); + return ( + _c(incidentActionPlan.responseActions) + .map(responseAction => ({ + id: responseAction.id, + mainTask: responseAction.mainTask, + subActivities: responseAction.subActivities, + subPillar: responseAction.subPillar, + searchAssignRO: responseAction.searchAssignRO?.username ?? "", + status: getStatusTypeByCode(responseAction.status) ?? "", + verification: getVerificationTypeByCode(responseAction.verification) ?? "", + timeLine: formatQuarterString(responseAction.dueDate), + dueDate: getISODateAsLocaleDateString( + responseAction.dueDate.toISOString() + ).toDateString(), + })) + //DHIS returns events last updated first, zebra app needs it in order of creation, + //so we reverse the order + .reverse() + .value() + ); } else { return []; } From bfd1f04ced3e3a1ee73c2c8a268577267ea8fbb6 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:25:46 +0100 Subject: [PATCH 08/16] fix: remove event from existingtrackertypes on completion --- i18n/en.pot | 16 +++-- .../form-summary/EventTrackerFormSummary.tsx | 27 ++------ .../pages/event-tracker/EventTrackerPage.tsx | 22 ++++++- .../event-tracker/useDiseaseOutbreakEvent.ts | 64 ++++++++++++++++++- 4 files changed, 99 insertions(+), 30 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 392348f3..d1ece4ba 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-11-03T18:35:32.151Z\n" -"PO-Revision-Date: 2024-11-03T18:35:32.151Z\n" +"POT-Creation-Date: 2024-11-07T20:14:07.689Z\n" +"PO-Revision-Date: 2024-11-07T20:14:07.689Z\n" msgid "Low" msgstr "" @@ -87,9 +87,6 @@ msgstr "" msgid "Edit Action Plan" msgstr "" -msgid "Event completed" -msgstr "" - msgid "Edit Details" msgstr "" @@ -174,6 +171,15 @@ msgstr "" msgid "Risks associated with this event have not yet been assessed." msgstr "" +msgid "Complete event" +msgstr "" + +msgid "Confirm" +msgstr "" + +msgid "Are you sure you want to complete this event?" +msgstr "" + msgid "N/A" msgstr "" diff --git a/src/webapp/components/form/form-summary/EventTrackerFormSummary.tsx b/src/webapp/components/form/form-summary/EventTrackerFormSummary.tsx index 2b8f9a45..ea1d6214 100644 --- a/src/webapp/components/form/form-summary/EventTrackerFormSummary.tsx +++ b/src/webapp/components/form/form-summary/EventTrackerFormSummary.tsx @@ -13,46 +13,33 @@ import { FormSummaryData } from "../../../pages/event-tracker/useDiseaseOutbreak import { Maybe } from "../../../../utils/ts-utils"; import { FormType } from "../../../pages/form-page/FormPage"; import { Id } from "../../../../domain/entities/Ref"; -import { useAppContext } from "../../../contexts/app-context"; +import { GlobalMessage } from "../../../pages/form-page/useForm"; export type EventTrackerFormSummaryProps = { id: Id; formType: FormType; formSummary: Maybe; - summaryError: Maybe; + globalMessage: Maybe; + onOpenModal: () => void; }; const ROW_COUNT = 3; export const EventTrackerFormSummary: React.FC = React.memo(props => { - const { compositionRoot } = useAppContext(); - const { id, formType, formSummary, summaryError } = props; + const { id, formType, formSummary, onOpenModal: onCompleteClick, globalMessage } = props; const { goTo } = useRoutes(); const snackbar = useSnackbar(); useEffect(() => { - if (!summaryError) return; + if (!globalMessage) return; - snackbar.error(summaryError); - goTo(RouteName.DASHBOARD); - }, [summaryError, snackbar, goTo]); + snackbar[globalMessage.type](globalMessage.text); + }, [globalMessage, snackbar]); const onEditClick = useCallback(() => { goTo(RouteName.EDIT_FORM, { formType: formType, id: id }); }, [formType, goTo, id]); - const onCompleteClick = useCallback(() => { - compositionRoot.diseaseOutbreakEvent.complete.execute(id).run( - () => { - snackbar.success(i18n.t("Event completed")); - }, - err => { - snackbar.error(i18n.t(`Failed to complete event: ${err.message}`)); - console.error(err); - } - ); - }, [compositionRoot, id, snackbar]); - const editButton = ( + } + > + {i18n.t("Are you sure you want to complete this event?")} + ); }); diff --git a/src/webapp/pages/event-tracker/useDiseaseOutbreakEvent.ts b/src/webapp/pages/event-tracker/useDiseaseOutbreakEvent.ts index 0433f5e2..ba003c21 100644 --- a/src/webapp/pages/event-tracker/useDiseaseOutbreakEvent.ts +++ b/src/webapp/pages/event-tracker/useDiseaseOutbreakEvent.ts @@ -15,6 +15,8 @@ import { User } from "../../components/user-selector/UserSelector"; import { TableRowType } from "../../components/table/BasicTable"; import { RiskAssessmentGrading } from "../../../domain/entities/risk-assessment/RiskAssessmentGrading"; import { mapTeamMemberToUser } from "../form-page/mapEntityToFormState"; +import { useExistingEventTrackerTypes } from "../../contexts/existing-event-tracker-types-context"; +import { GlobalMessage } from "../form-page/useForm"; const EventTypeLabel = "Event type"; const DiseaseLabel = "Disease"; @@ -32,9 +34,12 @@ export type FormSummaryData = { export function useDiseaseOutbreakEvent(id: Id) { const { compositionRoot, configurations } = useAppContext(); const [formSummary, setFormSummary] = useState(); - const [summaryError, setSummaryError] = useState(); + const [globalMessage, setGlobalMessage] = useState>(); const [riskAssessmentRows, setRiskAssessmentRows] = useState([]); const [eventTrackerDetails, setEventTrackerDetails] = useState(); + const [openCompleteModal, setOpenCompleteModal] = useState(false); + const { changeExistingEventTrackerTypes, existingEventTrackerTypes } = + useExistingEventTrackerTypes(); useEffect(() => { compositionRoot.diseaseOutbreakEvent.get.execute(id, configurations).run( @@ -47,7 +52,10 @@ export function useDiseaseOutbreakEvent(id: Id) { }, err => { console.debug(err); - setSummaryError(`Event tracker with id: ${id} does not exist`); + setGlobalMessage({ + type: "error", + text: `Event tracker with id: ${id} does not exist`, + }); } ); }, [compositionRoot.diseaseOutbreakEvent.get, configurations, id]); @@ -141,6 +149,7 @@ export function useDiseaseOutbreakEvent(id: Id) { return []; } }; + const orderByRiskAssessmentDate = useCallback( (direction: "asc" | "desc") => { setRiskAssessmentRows(prevRows => { @@ -170,11 +179,60 @@ export function useDiseaseOutbreakEvent(id: Id) { [setRiskAssessmentRows] ); + const onCompleteClick = useCallback(() => { + compositionRoot.diseaseOutbreakEvent.complete.execute(id).run( + () => { + const eventTrackerName = + eventTrackerDetails?.hazardType ?? eventTrackerDetails?.suspectedDisease?.name; + + const updatedEventTrackerTypes = existingEventTrackerTypes.filter( + eventTrackerType => eventTrackerType !== eventTrackerName + ); + + if (eventTrackerName) { + changeExistingEventTrackerTypes(updatedEventTrackerTypes); + } + + setGlobalMessage({ + type: "success", + text: `Event tracker with id: ${id} has been completed`, + }); + }, + err => { + console.error(err); + setGlobalMessage({ + type: "error", + text: `Failed to complete event: : ${err.message}`, + }); + } + ); + }, [ + changeExistingEventTrackerTypes, + compositionRoot.diseaseOutbreakEvent.complete, + eventTrackerDetails?.hazardType, + eventTrackerDetails?.suspectedDisease?.name, + existingEventTrackerTypes, + id, + ]); + + const onOpenCompleteModal = useCallback( + () => setOpenCompleteModal(true), + [setOpenCompleteModal] + ); + const onCloseCompleteModal = useCallback( + () => setOpenCompleteModal(false), + [setOpenCompleteModal] + ); + return { formSummary, - summaryError, + globalMessage, riskAssessmentRows, eventTrackerDetails, + openCompleteModal, + onCloseCompleteModal, + onCompleteClick, + onOpenCompleteModal, orderByRiskAssessmentDate, }; } From d1a6f6fc0d84fe0c5e706db57f93030094f1bf4a Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:45:31 +0100 Subject: [PATCH 09/16] feat: change iap url structure --- src/webapp/components/layout/side-bar/SideBarContent.tsx | 3 ++- src/webapp/hooks/useRoutes.ts | 4 ++-- src/webapp/pages/form-page/useForm.ts | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/webapp/components/layout/side-bar/SideBarContent.tsx b/src/webapp/components/layout/side-bar/SideBarContent.tsx index 2bc100eb..6b57a6d2 100644 --- a/src/webapp/components/layout/side-bar/SideBarContent.tsx +++ b/src/webapp/components/layout/side-bar/SideBarContent.tsx @@ -70,7 +70,8 @@ export const SideBarContent: React.FC = React.memo( component={NavLink} to={ value === RouteName.EVENT_TRACKER || - value === RouteName.IM_TEAM_BUILDER + value === RouteName.IM_TEAM_BUILDER || + value === RouteName.INCIDENT_ACTION_PLAN ? routes[value].replace( ":id", getCurrentEventTracker()?.id || "" diff --git a/src/webapp/hooks/useRoutes.ts b/src/webapp/hooks/useRoutes.ts index 4f92a630..c2b14cc1 100644 --- a/src/webapp/hooks/useRoutes.ts +++ b/src/webapp/hooks/useRoutes.ts @@ -31,7 +31,7 @@ export const routes: Record = { [RouteName.EDIT_FORM]: `/edit/${formType}/:id`, [RouteName.EVENT_TRACKER]: "/event-tracker/:id", [RouteName.IM_TEAM_BUILDER]: "/incident-management-team-builder/:id", - [RouteName.INCIDENT_ACTION_PLAN]: "/incident-action-plan", + [RouteName.INCIDENT_ACTION_PLAN]: "/:id/incident-action-plan", [RouteName.RESOURCES]: "/resources", [RouteName.DASHBOARD]: "/", } as const; @@ -41,7 +41,7 @@ type RouteParams = { [RouteName.EDIT_FORM]: { formType: FormType; id: string }; [RouteName.EVENT_TRACKER]: { id: string }; [RouteName.IM_TEAM_BUILDER]: { id: string }; - [RouteName.INCIDENT_ACTION_PLAN]: undefined; + [RouteName.INCIDENT_ACTION_PLAN]: { id: string }; [RouteName.RESOURCES]: undefined; [RouteName.DASHBOARD]: undefined; }; diff --git a/src/webapp/pages/form-page/useForm.ts b/src/webapp/pages/form-page/useForm.ts index ecb103ff..f5aad7b1 100644 --- a/src/webapp/pages/form-page/useForm.ts +++ b/src/webapp/pages/form-page/useForm.ts @@ -300,7 +300,10 @@ export function useForm(formType: FormType, id?: Id): State { }); break; case "incident-response-action": - if (currentEventTracker?.id) goTo(RouteName.INCIDENT_ACTION_PLAN); + if (currentEventTracker?.id) + goTo(RouteName.INCIDENT_ACTION_PLAN, { + id: currentEventTracker?.id, + }); setGlobalMessage({ text: i18n.t(`Incident Response Actions saved successfully`), type: "success", From a0a0f557aec61c526843804707697f7c21b0f019 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Fri, 8 Nov 2024 08:41:04 +0100 Subject: [PATCH 10/16] feat: persist eventTrackerType data on form reload --- src/webapp/pages/form-page/useForm.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/webapp/pages/form-page/useForm.ts b/src/webapp/pages/form-page/useForm.ts index f5aad7b1..9d4a69ac 100644 --- a/src/webapp/pages/form-page/useForm.ts +++ b/src/webapp/pages/form-page/useForm.ts @@ -23,6 +23,7 @@ import { import { useExistingEventTrackerTypes } from "../../contexts/existing-event-tracker-types-context"; import { useCheckWritePermission } from "../../hooks/useHasCurrentUserCaptureAccess"; import { useSnackbar } from "@eyeseetea/d2-ui-components"; +import { usePerformanceOverview } from "../dashboard/usePerformanceOverview"; export type GlobalMessage = { text: string; @@ -67,9 +68,18 @@ export function useForm(formType: FormType, id?: Id): State { const [isLoading, setIsLoading] = useState(false); const currentEventTracker = getCurrentEventTracker(); const { existingEventTrackerTypes } = useExistingEventTrackerTypes(); + const { dataPerformanceOverview } = usePerformanceOverview(); useCheckWritePermission(formType); const snackbar = useSnackbar(); + const allDataPerformanceEvents = dataPerformanceOverview?.map( + event => event.hazardType || event.suspectedDisease + ); + const existingEventTrackers = + existingEventTrackerTypes.length === 0 + ? allDataPerformanceEvents + : existingEventTrackerTypes; + useEffect(() => { compositionRoot.getConfigurableForm .execute(formType, currentEventTracker, configurations, id) @@ -79,7 +89,7 @@ export function useForm(formType: FormType, id?: Id): State { setFormLabels(formData.labels); setFormState({ kind: "loaded", - data: mapEntityToFormState(formData, !!id, existingEventTrackerTypes), + data: mapEntityToFormState(formData, !!id, existingEventTrackers), }); }, error => { @@ -99,7 +109,7 @@ export function useForm(formType: FormType, id?: Id): State { id, currentEventTracker, configurations, - existingEventTrackerTypes, + existingEventTrackers, snackbar, goTo, ]); From 9d2a7bba84488e8235c1394c42e82776abee49a8 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:32:07 +0100 Subject: [PATCH 11/16] feat: use incident response officer user group --- .../repositories/TeamMemberD2Repository.ts | 5 ++ .../test/TeamMemberTestRepository.ts | 16 +++++ src/domain/entities/AppConfigurations.ts | 1 + .../repositories/TeamMemberRepository.ts | 1 + .../usecases/GetConfigurationsUseCase.ts | 64 +++++++++++-------- src/utils/tests.tsx | 1 + 6 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/data/repositories/TeamMemberD2Repository.ts b/src/data/repositories/TeamMemberD2Repository.ts index 8913f1e4..20f69a67 100644 --- a/src/data/repositories/TeamMemberD2Repository.ts +++ b/src/data/repositories/TeamMemberD2Repository.ts @@ -9,6 +9,7 @@ import { Future } from "../../domain/entities/generic/Future"; const RTSL_ZEBRA_INCIDENTMANAGER = "RTSL_ZEBRA_INCIDENTMANAGER"; const RTSL_ZEBRA_RISKASSESSOR = "RTSL_ZEBRA_RISKASSESSOR"; const RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_MEMBERS = "RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_MEMBERS"; +const RTSL_ZEBRA_INCIDENT_RESPONSE_OFFICERS = "RTSL_ZEBRA_INCIDENT_RESPONSE_OFFICERS"; export class TeamMemberD2Repository implements TeamMemberRepository { constructor(private api: D2Api) {} @@ -42,6 +43,10 @@ export class TeamMemberD2Repository implements TeamMemberRepository { return this.getTeamMembersByUserGroup(RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_MEMBERS); } + getIncidentResponseOfficers(): FutureData { + return this.getTeamMembersByUserGroup(RTSL_ZEBRA_INCIDENT_RESPONSE_OFFICERS); + } + private getTeamMembersByUserGroup(userGroupCode: string): FutureData { return apiToFuture( this.api.metadata.get({ diff --git a/src/data/repositories/test/TeamMemberTestRepository.ts b/src/data/repositories/test/TeamMemberTestRepository.ts index 38e86470..ae880719 100644 --- a/src/data/repositories/test/TeamMemberTestRepository.ts +++ b/src/data/repositories/test/TeamMemberTestRepository.ts @@ -52,6 +52,22 @@ export class TeamMemberTestRepository implements TeamMemberRepository { return Future.success([teamMember]); } + getIncidentResponseOfficers(): FutureData { + const teamMember: TeamMember = new TeamMember({ + id: "incidentResponseOfficer", + username: "incidentResponseOfficer", + name: `Team Member Name test`, + email: `email@email.com`, + phone: `121-1234`, + teamRoles: undefined, + status: "Available", + photo: new URL("https://www.example.com"), + workPosition: "workPosition", + }); + + return Future.success([teamMember]); + } + getAll(): FutureData { const teamMember: TeamMember = new TeamMember({ id: "test", diff --git a/src/domain/entities/AppConfigurations.ts b/src/domain/entities/AppConfigurations.ts index 3ba0a476..5e531355 100644 --- a/src/domain/entities/AppConfigurations.ts +++ b/src/domain/entities/AppConfigurations.ts @@ -48,5 +48,6 @@ export type Configurations = { all: TeamMember[]; riskAssessors: TeamMember[]; incidentManagers: TeamMember[]; + responseOfficers: TeamMember[]; }; }; diff --git a/src/domain/repositories/TeamMemberRepository.ts b/src/domain/repositories/TeamMemberRepository.ts index 26f94769..68170c7b 100644 --- a/src/domain/repositories/TeamMemberRepository.ts +++ b/src/domain/repositories/TeamMemberRepository.ts @@ -8,4 +8,5 @@ export interface TeamMemberRepository { getIncidentManagers(): FutureData; getRiskAssessors(): FutureData; getForIncidentManagementTeamMembers(): FutureData; + getIncidentResponseOfficers(): FutureData; } diff --git a/src/domain/usecases/GetConfigurationsUseCase.ts b/src/domain/usecases/GetConfigurationsUseCase.ts index ca529279..7b6fd86f 100644 --- a/src/domain/usecases/GetConfigurationsUseCase.ts +++ b/src/domain/usecases/GetConfigurationsUseCase.ts @@ -12,39 +12,47 @@ export class GetConfigurationsUseCase { ) {} public execute(): FutureData { - return this.teamMemberRepository.getIncidentManagers().flatMap(managers => { - return this.teamMemberRepository.getRiskAssessors().flatMap(riskAssessors => { - return this.teamMemberRepository.getAll().flatMap(teamMembers => { - return this.configurationsRepository - .getSelectableOptions() - .flatMap(selectableOptionsResponse => { - const selectableOptions: SelectableOptions = - this.mapOptionsAndTeamMembersToSelectableOptions( - selectableOptionsResponse, - managers, - riskAssessors, - teamMembers - ); - const configurations: Configurations = { - selectableOptions: selectableOptions, - teamMembers: { - all: teamMembers, - riskAssessors: riskAssessors, - incidentManagers: managers, - }, - }; - return Future.success(configurations); - }); - }); - }); - }); + return Future.joinObj({ + allTeamMembers: this.teamMemberRepository.getAll(), + incidentResponseOfficers: this.teamMemberRepository.getIncidentResponseOfficers(), + managers: this.teamMemberRepository.getIncidentManagers(), + riskAssessors: this.teamMemberRepository.getRiskAssessors(), + selectableOptionsResponse: this.configurationsRepository.getSelectableOptions(), + }).flatMap( + ({ + allTeamMembers, + incidentResponseOfficers, + managers, + riskAssessors, + selectableOptionsResponse, + }) => { + const selectableOptions: SelectableOptions = + this.mapOptionsAndTeamMembersToSelectableOptions( + selectableOptionsResponse, + managers, + riskAssessors, + incidentResponseOfficers + ); + + const configurations: Configurations = { + selectableOptions: selectableOptions, + teamMembers: { + all: allTeamMembers, + riskAssessors: riskAssessors, + incidentManagers: managers, + responseOfficers: incidentResponseOfficers, + }, + }; + return Future.success(configurations); + } + ); } mapOptionsAndTeamMembersToSelectableOptions( selectableOptionsResponse: SelectableOptions, managers: TeamMember[], riskAssessors: TeamMember[], - teamMembers: TeamMember[] + incidentResponseOfficers: TeamMember[] ): SelectableOptions { const selectableOptions: SelectableOptions = { eventTrackerConfigurations: { @@ -66,7 +74,7 @@ export class GetConfigurationsUseCase { }, incidentResponseActionConfigurations: { ...selectableOptionsResponse.incidentResponseActionConfigurations, - searchAssignRO: teamMembers, + searchAssignRO: incidentResponseOfficers, }, }; diff --git a/src/utils/tests.tsx b/src/utils/tests.tsx index 71720e0b..3589bfd6 100644 --- a/src/utils/tests.tsx +++ b/src/utils/tests.tsx @@ -64,6 +64,7 @@ export function getTestContext() { all: [], riskAssessors: [], incidentManagers: [], + responseOfficers: [], }, }, }; From 69cd93b3b8d2cb0c129c00913cb1ee21b45592c7 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:56:49 +0100 Subject: [PATCH 12/16] fix: remove timeLine datavalue from response action form --- .../IncidentActionD2Repository.ts | 1 - .../utils/IncidentActionMapper.ts | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/data/repositories/IncidentActionD2Repository.ts b/src/data/repositories/IncidentActionD2Repository.ts index 92424d7e..c15469f3 100644 --- a/src/data/repositories/IncidentActionD2Repository.ts +++ b/src/data/repositories/IncidentActionD2Repository.ts @@ -67,7 +67,6 @@ export type IncidentResponseActionDataValues = { subPillar: Maybe; searchAssignRO: Maybe; dueDate: Maybe; - timeLine: Maybe; status: Maybe; verification: Maybe; }; diff --git a/src/data/repositories/utils/IncidentActionMapper.ts b/src/data/repositories/utils/IncidentActionMapper.ts index cc01fb9e..43d8e881 100644 --- a/src/data/repositories/utils/IncidentActionMapper.ts +++ b/src/data/repositories/utils/IncidentActionMapper.ts @@ -77,7 +77,6 @@ export function mapDataElementsToIncidentResponseActions( const subPillar = getValueById(dataValues, incidentResponseActionsIds.subPillar); const searchAssignRO = getValueById(dataValues, incidentResponseActionsIds.searchAssignRO); const dueDate = getValueById(dataValues, incidentResponseActionsIds.dueDate); - const timeLine = getValueById(dataValues, incidentResponseActionsIds.timeLine); const status = getValueById(dataValues, incidentResponseActionsIds.status) as Status; const verification = getValueById( dataValues, @@ -91,7 +90,6 @@ export function mapDataElementsToIncidentResponseActions( subPillar, searchAssignRO, dueDate, - timeLine, status, verification, }; @@ -173,19 +171,24 @@ export function mapIncidentResponseActionToDataElements( const dataElementValues: Record = getValueFromIncidentResponseAction(incidentResponseAction); - const dataValues: DataValue[] = programStageDataElementsMetadata.map(programStage => { - if (!isStringInIncidentResponseActionCodes(programStage.dataElement.code)) { - throw new Error( - `DataElement code ${programStage.dataElement.code} not found in Incident Action Plan Codes` + const dataValues: DataValue[] = programStageDataElementsMetadata + .filter( + programStageDataElement => + programStageDataElement.dataElement.id !== incidentResponseActionsIds.timeLine + ) + .map(programStage => { + if (!isStringInIncidentResponseActionCodes(programStage.dataElement.code)) { + throw new Error( + `DataElement code ${programStage.dataElement.code} not found in Incident Action Plan Codes` + ); + } + const typedCode: IncidentResponseActionKeyCode = programStage.dataElement.code; + + return getPopulatedDataElement( + programStage.dataElement.id, + dataElementValues[typedCode] ); - } - const typedCode: IncidentResponseActionKeyCode = programStage.dataElement.code; - - return getPopulatedDataElement( - programStage.dataElement.id, - dataElementValues[typedCode] - ); - }); + }); return getIncidentActionTrackerEvent( programStageId, From a975ef4fe8214aaddd580ab465826d4255d13abc Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:31:45 +0100 Subject: [PATCH 13/16] feat: change complete event modal styling --- .../components/simple-modal/SimpleModal.tsx | 22 ++++++++++++++++--- .../pages/event-tracker/EventTrackerPage.tsx | 20 +++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/webapp/components/simple-modal/SimpleModal.tsx b/src/webapp/components/simple-modal/SimpleModal.tsx index a8706b33..cccde01a 100644 --- a/src/webapp/components/simple-modal/SimpleModal.tsx +++ b/src/webapp/components/simple-modal/SimpleModal.tsx @@ -12,10 +12,21 @@ type SimpleModalProps = { open: boolean; onClose: () => void; closeLabel?: string; + alignFooterButtons?: "start" | "center" | "end"; + buttonDirection?: "row" | "column" | "row-reverse" | "column-reverse"; }; export const SimpleModal: React.FC = React.memo( - ({ children, footerButtons, closeLabel, open = false, onClose, title }) => { + ({ + alignFooterButtons, + buttonDirection, + children, + footerButtons, + closeLabel, + open = false, + onClose, + title, + }) => { return ( = React.memo( {children} -