From e51163e982b3c4876002a982ecddc389224f71ef Mon Sep 17 00:00:00 2001 From: 9sneha-n <9sneha.n@gmail.com> Date: Thu, 4 Jul 2024 16:16:28 +0530 Subject: [PATCH 1/7] feat: created validated table and provide complete button --- src/domain/utils/ModuleProperties.ts | 1 + .../ListOfDatasets.tsx | 26 +- .../current-data-submission/UploadsTable.tsx | 12 +- .../UploadsTableBody.tsx | 256 +++++++++++++++++- .../components/upload/ReviewDataSummary.tsx | 8 +- 5 files changed, 277 insertions(+), 26 deletions(-) diff --git a/src/domain/utils/ModuleProperties.ts b/src/domain/utils/ModuleProperties.ts index 9d3b841a..4731d9b3 100644 --- a/src/domain/utils/ModuleProperties.ts +++ b/src/domain/utils/ModuleProperties.ts @@ -13,6 +13,7 @@ type ModuleDetails = { title: string; description: string; }; + primaryFileType: string; secondaryUploadLabel?: string; secondaryFileType: string; diff --git a/src/webapp/components/current-data-submission/ListOfDatasets.tsx b/src/webapp/components/current-data-submission/ListOfDatasets.tsx index e1f6111d..4050dc77 100644 --- a/src/webapp/components/current-data-submission/ListOfDatasets.tsx +++ b/src/webapp/components/current-data-submission/ListOfDatasets.tsx @@ -27,9 +27,17 @@ export const getCompletedUploads = (upload: GlassUploadsState) => { } }; +export const getValidatedUploads = (upload: GlassUploadsState) => { + if (upload.kind === "loaded") { + return upload.data.filter((row: UploadsDataItem) => row.status.toLowerCase() === "validated"); + } +}; + function getNotCompletedUploads(upload: GlassUploadsState) { if (upload.kind === "loaded") { - return upload.data.filter((row: UploadsDataItem) => row.status.toLowerCase() !== "completed"); + return upload.data.filter( + (row: UploadsDataItem) => row.status.toLowerCase() === "uploaded" || row.status.toLowerCase() === "imported" + ); } } @@ -54,6 +62,7 @@ export const ListOfDatasets: React.FC = ({ setRefetchStatus const hasCurrentUserCaptureAccess = useGlassCaptureAccess(); const completeUploads = getCompletedUploads(uploads); + const validatedUploads = getValidatedUploads(uploads); const incompleteUploads = getNotCompletedUploads(uploads); const dataSubmissionId = useCurrentDataSubmissionId( @@ -105,10 +114,9 @@ export const ListOfDatasets: React.FC = ({ setRefetchStatus = ({ setRefetchStatus )} + {validatedUploads && validatedUploads.length > 0 && ( + + )} {incompleteUploads && incompleteUploads.length > 0 && ( >; + showComplete?: boolean; } // TODO: replace Table with MUI Datagrid -export const UploadsTable: React.FC = ({ title, items, className, refreshUploads }) => { +export const UploadsTable: React.FC = ({ + title, + items, + className, + refreshUploads, + showComplete, +}) => { const { currentModuleAccess } = useCurrentModuleContext(); return ( @@ -75,10 +82,11 @@ export const UploadsTable: React.FC = ({ title, items, classN {i18n.t("Download File")} {i18n.t("Delete")} + {showComplete && {i18n.t("Complete")}} - + diff --git a/src/webapp/components/current-data-submission/UploadsTableBody.tsx b/src/webapp/components/current-data-submission/UploadsTableBody.tsx index 3a921fd7..6f0cc340 100644 --- a/src/webapp/components/current-data-submission/UploadsTableBody.tsx +++ b/src/webapp/components/current-data-submission/UploadsTableBody.tsx @@ -5,6 +5,7 @@ import i18n from "@eyeseetea/d2-ui-components/locales"; import dayjs from "dayjs"; import { UploadsDataItem } from "../../entities/uploads"; import { DeleteOutline } from "@material-ui/icons"; +import { CheckCircleOutline } from "@material-ui/icons"; import { useAppContext } from "../../contexts/app-context"; import { ConfirmationDialog, useSnackbar } from "@eyeseetea/d2-ui-components"; import { CircularProgress } from "material-ui"; @@ -21,13 +22,17 @@ import { moduleProperties } from "../../../domain/utils/ModuleProperties"; import { ImportSummaryErrors } from "../../../domain/entities/data-entry/ImportSummary"; import { ImportSummaryErrorsDialog } from "../import-summary-errors-dialog/ImportSummaryErrorsDialog"; import { glassColors } from "../../pages/app/themes/dhis2.theme"; +import { useQuestionnaires } from "./Questionnaires"; +import { useCurrentDataSubmissionId } from "../../hooks/useCurrentDataSubmissionId"; +import { useCurrentUserGroupsAccess } from "../../hooks/useCurrentUserGroupsAccess"; export interface UploadsTableBodyProps { rows?: UploadsDataItem[]; refreshUploads: React.Dispatch>; + showComplete?: boolean; } -export const UploadsTableBody: React.FC = ({ rows, refreshUploads }) => { +export const UploadsTableBody: React.FC = ({ rows, refreshUploads, showComplete }) => { const { compositionRoot } = useAppContext(); const snackbar = useSnackbar(); @@ -35,9 +40,11 @@ export const UploadsTableBody: React.FC = ({ rows, refres const { currentOrgUnitAccess: { orgUnitId, orgUnitName }, } = useCurrentOrgUnitContext(); - const [open, setOpen] = React.useState(false); + const [deleteOpen, setDeleteOpen] = React.useState(false); + const [completeOpen, setCompleteOpen] = React.useState(false); const [importSummaryErrorsToShow, setImportSummaryErrorsToShow] = React.useState(null); const [rowToDelete, setRowToDelete] = useState(); + const [rowToComplete, setRowToComplete] = useState(); const { currentPeriod } = useCurrentPeriodContext(); @@ -49,13 +56,31 @@ export const UploadsTableBody: React.FC = ({ rows, refres currentPeriod ); const hasCurrentUserCaptureAccess = useGlassCaptureAccess(); + const [questionnaires] = useQuestionnaires(); + const dataSubmissionId = useCurrentDataSubmissionId( + currentModuleAccess.moduleId, + currentModuleAccess.moduleName, + currentOrgUnitAccess.orgUnitId, + currentPeriod + ); + const { captureAccessGroup } = useCurrentUserGroupsAccess(); - const showConfirmationDialog = (rowToDelete: UploadsDataItem) => { + const showDeleteConfirmationDialog = (rowToDelete: UploadsDataItem) => { setRowToDelete(rowToDelete); - setOpen(true); + setDeleteOpen(true); }; - const hideConfirmationDialog = () => { - setOpen(false); + + const showCompleteConfirmationDialog = (rowToComplete: UploadsDataItem) => { + setRowToComplete(rowToComplete); + setCompleteOpen(true); + }; + + const hideDeleteConfirmationDialog = () => { + setDeleteOpen(false); + }; + + const hideCompleteConfirmationDialog = () => { + setCompleteOpen(false); }; const downloadFile = (fileId: string, fileName: string) => { @@ -78,7 +103,7 @@ export const UploadsTableBody: React.FC = ({ rows, refres //2. Delete corresponding document from DHIS //3. Delete corresponding 'upload' and 'document' from Datastore const deleteDataset = () => { - hideConfirmationDialog(); + hideDeleteConfirmationDialog(); if (rowToDelete) { let primaryFileToDelete: UploadsDataItem | undefined, secondaryFileToDelete: UploadsDataItem | undefined; //For AMR, Ris file is mandatory, so there will be a ris file with given batch id. @@ -203,7 +228,7 @@ export const UploadsTableBody: React.FC = ({ rows, refres () => { refreshUploads({}); //Trigger re-render of parent setLoading(false); - hideConfirmationDialog(); + hideDeleteConfirmationDialog(); }, error => { snackbar.error("Error deleting file"); @@ -213,7 +238,7 @@ export const UploadsTableBody: React.FC = ({ rows, refres } else { refreshUploads({}); //Trigger re-render of parent setLoading(false); - hideConfirmationDialog(); + hideDeleteConfirmationDialog(); } }, error => { @@ -286,7 +311,7 @@ export const UploadsTableBody: React.FC = ({ rows, refres () => { refreshUploads({}); //Trigger re-render of parent setLoading(false); - hideConfirmationDialog(); + hideDeleteConfirmationDialog(); snackbar.info(message); }, error => { @@ -322,6 +347,164 @@ export const UploadsTableBody: React.FC = ({ rows, refres } }; + const setCompleteStatus = useCallback(() => { + if (!rowToComplete) return; + + let primaryFileToComplete: UploadsDataItem | undefined, secondaryFileToComplete: UploadsDataItem | undefined; + + if ( + moduleProperties.get(currentModuleAccess.moduleName)?.isSecondaryFileApplicable && + moduleProperties.get(currentModuleAccess.moduleName)?.isSecondaryRelated + ) { + if ( + rowToComplete.fileType.toLowerCase() === + moduleProperties.get(currentModuleAccess.moduleName)?.primaryFileType.toLowerCase() + ) { + primaryFileToComplete = rowToComplete; + secondaryFileToComplete = rows + ?.filter(sample => sample.correspondingRisUploadId === rowToComplete.id) + ?.at(0); + } else { + secondaryFileToComplete = rowToComplete; + primaryFileToComplete = rows?.filter(ris => ris.id === rowToComplete.correspondingRisUploadId)?.at(0); + } + } else if (!moduleProperties.get(currentModuleAccess.moduleName)?.isSecondaryRelated) { + if (rowToComplete.fileType === moduleProperties.get(currentModuleAccess.moduleName)?.primaryFileType) { + primaryFileToComplete = rowToComplete; + } else { + secondaryFileToComplete = rowToComplete; + } + } else { + primaryFileToComplete = rowToComplete; + secondaryFileToComplete = undefined; + } + + if (primaryFileToComplete) { + return compositionRoot.glassUploads.setStatus({ id: primaryFileToComplete.id, status: "COMPLETED" }).run( + () => { + if (!secondaryFileToComplete) { + //If Questionnaires are not applicable to a module, then set status as COMPLETE on + //completion of dataset. + if ( + moduleProperties.get(currentModuleAccess.moduleName)?.completeStatusChange === "DATASET" || + (moduleProperties.get(currentModuleAccess.moduleName)?.completeStatusChange === + "QUESTIONNAIRE_AND_DATASET" && + questionnaires?.every(q => q.isMandatory && q.isCompleted)) + ) { + compositionRoot.glassDataSubmission.setStatus(dataSubmissionId, "COMPLETE").run( + () => { + if (captureAccessGroup.kind === "loaded") { + const userGroupsIds = captureAccessGroup.data.map(cag => { + return cag.id; + }); + const notificationText = `The data submission for ${currentModuleAccess.moduleName} module for year ${currentPeriod} and country ${currentOrgUnitAccess.orgUnitName} has changed to DATA TO BE APPROVED BY COUNTRY`; + + compositionRoot.notifications + .send( + notificationText, + notificationText, + userGroupsIds, + currentOrgUnitAccess.orgUnitPath + ) + .run( + () => {}, + () => {} + ); + setLoading(false); + } + }, + error => { + console.debug( + "Error occurred when setting data submission status, error: " + error + ); + setLoading(false); + } + ); + } + } else { + return compositionRoot.glassUploads + .setStatus({ id: secondaryFileToComplete.id, status: "COMPLETED" }) + .run( + () => { + setLoading(false); + }, + errorMessage => { + snackbar.error(i18n.t(errorMessage)); + setLoading(false); + } + ); + } + }, + errorMessage => { + snackbar.error(i18n.t(errorMessage)); + setLoading(false); + } + ); + } else if (secondaryFileToComplete) { + return compositionRoot.glassUploads.setStatus({ id: secondaryFileToComplete.id, status: "COMPLETED" }).run( + () => { + if ( + moduleProperties.get(currentModuleAccess.moduleName)?.completeStatusChange === + "QUESTIONNAIRE_AND_DATASET" && + questionnaires?.every(q => q.isMandatory && q.isCompleted) + ) { + compositionRoot.glassDataSubmission.setStatus(dataSubmissionId, "COMPLETE").run( + () => { + if (captureAccessGroup.kind === "loaded") { + const userGroupsIds = captureAccessGroup.data.map(cag => { + return cag.id; + }); + const notificationText = `The data submission for ${currentModuleAccess.moduleName} module for year ${currentPeriod} and country ${currentOrgUnitAccess.orgUnitName} has changed to DATA TO BE APPROVED BY COUNTRY`; + + compositionRoot.notifications + .send( + notificationText, + notificationText, + userGroupsIds, + currentOrgUnitAccess.orgUnitPath + ) + .run( + () => {}, + () => {} + ); + } + }, + error => { + console.debug("Error occurred when setting data submission status, error: " + error); + } + ); + } + + setLoading(false); + }, + errorMessage => { + snackbar.error(i18n.t(errorMessage)); + setLoading(false); + } + ); + } + }, [ + captureAccessGroup, + compositionRoot.glassDataSubmission, + compositionRoot.glassUploads, + compositionRoot.notifications, + currentModuleAccess.moduleName, + currentOrgUnitAccess.orgUnitName, + currentOrgUnitAccess.orgUnitPath, + currentPeriod, + dataSubmissionId, + questionnaires, + rowToComplete, + rows, + snackbar, + ]); + + const completeDataset = () => { + hideCompleteConfirmationDialog(); + setLoading(true); + setCompleteStatus(); + }; + const handleShowImportSummaryErrors = useCallback((row: UploadsDataItem) => { if (row.importSummary) { setImportSummaryErrorsToShow(row.importSummary); @@ -338,7 +521,10 @@ export const UploadsTableBody: React.FC = ({ rows, refres - {i18n.t("Deleting Files")} + + {" "} + {rowToDelete ? i18n.t("Deleting Files") : i18n.t("Loading")} + {i18n.t( "This might take several minutes, do not refresh the page or press back." @@ -347,12 +533,12 @@ export const UploadsTableBody: React.FC = ({ rows, refres = ({ rows, refres importSummaryErrorsToShow={importSummaryErrorsToShow} onClose={() => setImportSummaryErrorsToShow(null)} /> + + + + { + "Are you sure you want to complete this upload? Please review the validation reports before completing." + } + + + + setImportSummaryErrorsToShow(null)} + /> @@ -399,7 +607,7 @@ export const UploadsTableBody: React.FC = ({ rows, refres + ) : ( + + )} + + )} {row.importSummary && } ))} diff --git a/src/webapp/components/upload/ReviewDataSummary.tsx b/src/webapp/components/upload/ReviewDataSummary.tsx index 290e279a..46820957 100644 --- a/src/webapp/components/upload/ReviewDataSummary.tsx +++ b/src/webapp/components/upload/ReviewDataSummary.tsx @@ -25,8 +25,6 @@ interface ReviewDataSummaryProps { secondaryFileImportSummary?: ImportSummary | undefined; } -const COMPLETED_STATUS = "COMPLETED"; - export const ReviewDataSummary: React.FC = ({ changeStep, primaryFileImportSummary, @@ -103,7 +101,7 @@ export const ReviewDataSummary: React.FC = ({ const secondaryUploadId = localStorage.getItem("secondaryUploadId"); setIsLoading(true); if (primaryUploadId) { - return compositionRoot.glassUploads.setStatus({ id: primaryUploadId, status: COMPLETED_STATUS }).run( + return compositionRoot.glassUploads.setStatus({ id: primaryUploadId, status: "COMPLETED" }).run( () => { if (!secondaryUploadId) { changeStep(4); @@ -146,7 +144,7 @@ export const ReviewDataSummary: React.FC = ({ } } else { return compositionRoot.glassUploads - .setStatus({ id: secondaryUploadId, status: COMPLETED_STATUS }) + .setStatus({ id: secondaryUploadId, status: "COMPLETED" }) .run( () => { changeStep(4); @@ -165,7 +163,7 @@ export const ReviewDataSummary: React.FC = ({ } ); } else if (secondaryUploadId) { - return compositionRoot.glassUploads.setStatus({ id: secondaryUploadId, status: COMPLETED_STATUS }).run( + return compositionRoot.glassUploads.setStatus({ id: secondaryUploadId, status: "COMPLETED" }).run( () => { if ( moduleProperties.get(currentModuleAccess.moduleName)?.completeStatusChange === From c103310ea648a00774513ea68dad628909d37a75 Mon Sep 17 00:00:00 2001 From: 9sneha-n <9sneha.n@gmail.com> Date: Wed, 7 Aug 2024 23:35:41 +0530 Subject: [PATCH 2/7] feat: set complete status on button click --- i18n/en.pot | 19 +- i18n/es.po | 17 +- src/domain/utils/ModuleProperties.ts | 1 - .../UploadsTableBody.tsx | 182 ++---------------- 4 files changed, 44 insertions(+), 175 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 8c5ef707..c81c3089 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-07-26T10:31:32.732Z\n" -"PO-Revision-Date: 2024-07-26T10:31:32.732Z\n" +"POT-Creation-Date: 2024-08-07T18:05:19.338Z\n" +"PO-Revision-Date: 2024-08-07T18:05:19.338Z\n" msgid "Template {{id}} not loaded" msgstr "" @@ -172,6 +172,9 @@ msgstr "" msgid "You can add up to 6 datasets to this submission with different BATCH IDS." msgstr "" +msgid "Validated Uploads, Review to complete" +msgstr "" + msgid "Uploads with errors, or discarded" msgstr "" @@ -359,9 +362,18 @@ msgstr "" msgid "Delete" msgstr "" +msgid "Complete" +msgstr "" + +msgid "Failed to set completed status" +msgstr "" + msgid "Deleting Files" msgstr "" +msgid "Loading" +msgstr "" + msgid "This might take several minutes, do not refresh the page or press back." msgstr "" @@ -608,9 +620,6 @@ msgstr "" msgid "Choose a Dataset" msgstr "" -msgid "Loading" -msgstr "" - msgid "Choose file type" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index ab01a80c..e718ebdd 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-07-26T10:31:32.732Z\n" +"POT-Creation-Date: 2024-08-07T18:05:19.338Z\n" "PO-Revision-Date: 2018-10-25T09:02:35.143Z\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -173,6 +173,9 @@ msgid "" "You can add up to 6 datasets to this submission with different BATCH IDS." msgstr "" +msgid "Validated Uploads, Review to complete" +msgstr "" + msgid "Uploads with errors, or discarded" msgstr "" @@ -361,9 +364,18 @@ msgstr "" msgid "Delete" msgstr "" +msgid "Complete" +msgstr "" + +msgid "Failed to set completed status" +msgstr "" + msgid "Deleting Files" msgstr "" +msgid "Loading" +msgstr "" + msgid "This might take several minutes, do not refresh the page or press back." msgstr "" @@ -610,9 +622,6 @@ msgstr "" msgid "Choose a Dataset" msgstr "" -msgid "Loading" -msgstr "" - msgid "Choose file type" msgstr "" diff --git a/src/domain/utils/ModuleProperties.ts b/src/domain/utils/ModuleProperties.ts index bc16a3c6..325696b1 100644 --- a/src/domain/utils/ModuleProperties.ts +++ b/src/domain/utils/ModuleProperties.ts @@ -13,7 +13,6 @@ type ModuleDetails = { title: string; description: string; }; - primaryFileType: string; secondaryUploadLabel?: string; secondaryFileType: string; diff --git a/src/webapp/components/current-data-submission/UploadsTableBody.tsx b/src/webapp/components/current-data-submission/UploadsTableBody.tsx index 1ddd3216..a93ed6a0 100644 --- a/src/webapp/components/current-data-submission/UploadsTableBody.tsx +++ b/src/webapp/components/current-data-submission/UploadsTableBody.tsx @@ -22,9 +22,6 @@ import { moduleProperties } from "../../../domain/utils/ModuleProperties"; import { ImportSummaryErrors } from "../../../domain/entities/data-entry/ImportSummary"; import { ImportSummaryErrorsDialog } from "../import-summary-errors-dialog/ImportSummaryErrorsDialog"; import { glassColors } from "../../pages/app/themes/dhis2.theme"; -import { useQuestionnaires } from "./Questionnaires"; -import { useCurrentDataSubmissionId } from "../../hooks/useCurrentDataSubmissionId"; -import { useCurrentUserGroupsAccess } from "../../hooks/useCurrentUserGroupsAccess"; export interface UploadsTableBodyProps { rows?: UploadsDataItem[]; @@ -56,14 +53,6 @@ export const UploadsTableBody: React.FC = ({ rows, refres currentPeriod ); const hasCurrentUserCaptureAccess = useGlassCaptureAccess(); - const [questionnaires] = useQuestionnaires(); - const dataSubmissionId = useCurrentDataSubmissionId( - currentModuleAccess.moduleId, - currentModuleAccess.moduleName, - currentOrgUnitAccess.orgUnitId, - currentPeriod - ); - const { captureAccessGroup } = useCurrentUserGroupsAccess(); const showDeleteConfirmationDialog = (rowToDelete: UploadsDataItem) => { setRowToDelete(rowToDelete); @@ -384,160 +373,28 @@ export const UploadsTableBody: React.FC = ({ rows, refres }; const setCompleteStatus = useCallback(() => { - if (!rowToComplete) return; - - let primaryFileToComplete: UploadsDataItem | undefined, secondaryFileToComplete: UploadsDataItem | undefined; - - if ( - moduleProperties.get(currentModuleAccess.moduleName)?.isSecondaryFileApplicable && - moduleProperties.get(currentModuleAccess.moduleName)?.isSecondaryRelated - ) { - if ( - rowToComplete.fileType.toLowerCase() === - moduleProperties.get(currentModuleAccess.moduleName)?.primaryFileType.toLowerCase() - ) { - primaryFileToComplete = rowToComplete; - secondaryFileToComplete = rows - ?.filter(sample => sample.correspondingRisUploadId === rowToComplete.id) - ?.at(0); - } else { - secondaryFileToComplete = rowToComplete; - primaryFileToComplete = rows?.filter(ris => ris.id === rowToComplete.correspondingRisUploadId)?.at(0); - } - } else if (!moduleProperties.get(currentModuleAccess.moduleName)?.isSecondaryRelated) { - if (rowToComplete.fileType === moduleProperties.get(currentModuleAccess.moduleName)?.primaryFileType) { - primaryFileToComplete = rowToComplete; - } else { - secondaryFileToComplete = rowToComplete; - } - } else { - primaryFileToComplete = rowToComplete; - secondaryFileToComplete = undefined; - } - - if (primaryFileToComplete) { - return compositionRoot.glassUploads.setStatus({ id: primaryFileToComplete.id, status: "COMPLETED" }).run( - () => { - if (!secondaryFileToComplete) { - //If Questionnaires are not applicable to a module, then set status as COMPLETE on - //completion of dataset. - if ( - moduleProperties.get(currentModuleAccess.moduleName)?.completeStatusChange === "DATASET" || - (moduleProperties.get(currentModuleAccess.moduleName)?.completeStatusChange === - "QUESTIONNAIRE_AND_DATASET" && - questionnaires?.every(q => q.isMandatory && q.isCompleted)) - ) { - compositionRoot.glassDataSubmission.setStatus(dataSubmissionId, "COMPLETE").run( - () => { - if (captureAccessGroup.kind === "loaded") { - const userGroupsIds = captureAccessGroup.data.map(cag => { - return cag.id; - }); - const notificationText = `The data submission for ${currentModuleAccess.moduleName} module for year ${currentPeriod} and country ${currentOrgUnitAccess.orgUnitName} has changed to DATA TO BE APPROVED BY COUNTRY`; - - compositionRoot.notifications - .send( - notificationText, - notificationText, - userGroupsIds, - currentOrgUnitAccess.orgUnitPath - ) - .run( - () => {}, - () => {} - ); - setLoading(false); - } - }, - error => { - console.debug( - "Error occurred when setting data submission status, error: " + error - ); - setLoading(false); - } - ); - } - } else { - return compositionRoot.glassUploads - .setStatus({ id: secondaryFileToComplete.id, status: "COMPLETED" }) - .run( - () => { - setLoading(false); - }, - errorMessage => { - snackbar.error(i18n.t(errorMessage)); - setLoading(false); - } - ); - } - }, - errorMessage => { - snackbar.error(i18n.t(errorMessage)); - setLoading(false); - } - ); - } else if (secondaryFileToComplete) { - return compositionRoot.glassUploads.setStatus({ id: secondaryFileToComplete.id, status: "COMPLETED" }).run( - () => { - if ( - moduleProperties.get(currentModuleAccess.moduleName)?.completeStatusChange === - "QUESTIONNAIRE_AND_DATASET" && - questionnaires?.every(q => q.isMandatory && q.isCompleted) - ) { - compositionRoot.glassDataSubmission.setStatus(dataSubmissionId, "COMPLETE").run( - () => { - if (captureAccessGroup.kind === "loaded") { - const userGroupsIds = captureAccessGroup.data.map(cag => { - return cag.id; - }); - const notificationText = `The data submission for ${currentModuleAccess.moduleName} module for year ${currentPeriod} and country ${currentOrgUnitAccess.orgUnitName} has changed to DATA TO BE APPROVED BY COUNTRY`; - - compositionRoot.notifications - .send( - notificationText, - notificationText, - userGroupsIds, - currentOrgUnitAccess.orgUnitPath - ) - .run( - () => {}, - () => {} - ); - } - }, - error => { - console.debug("Error occurred when setting data submission status, error: " + error); - } - ); + if (rowToComplete?.id) { + setLoading(true); + compositionRoot.glassUploads + .setStatus({ + id: rowToComplete.id, + status: "COMPLETED", + }) + .run( + () => { + refreshUploads({}); //Trigger re-render of parent + setLoading(false); + }, + () => { + snackbar.error(i18n.t("Failed to set completed status")); + setLoading(false); } - - setLoading(false); - }, - errorMessage => { - snackbar.error(i18n.t(errorMessage)); - setLoading(false); - } - ); + ); } - }, [ - captureAccessGroup, - compositionRoot.glassDataSubmission, - compositionRoot.glassUploads, - compositionRoot.notifications, - currentModuleAccess.moduleName, - currentOrgUnitAccess.orgUnitName, - currentOrgUnitAccess.orgUnitPath, - currentPeriod, - dataSubmissionId, - questionnaires, - rowToComplete, - rows, - snackbar, - ]); + }, [compositionRoot.glassUploads, refreshUploads, rowToComplete, snackbar]); const completeDataset = () => { hideCompleteConfirmationDialog(); - setLoading(true); setCompleteStatus(); }; @@ -558,7 +415,6 @@ export const UploadsTableBody: React.FC = ({ rows, refres - {" "} {rowToDelete ? i18n.t("Deleting Files") : i18n.t("Loading")} @@ -611,10 +467,6 @@ export const UploadsTableBody: React.FC = ({ rows, refres - setImportSummaryErrorsToShow(null)} - /> From 5ee39bccc807a54d3ca28f7a3828beda2c0b2af2 Mon Sep 17 00:00:00 2001 From: Bhavana Narayanan Date: Thu, 8 Aug 2024 11:56:23 +0530 Subject: [PATCH 3/7] Feat:Version Bumped --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 769d0fa4..d637bf42 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "glass", "description": "DHIS2 Glass App", - "version": "1.6.16", + "version": "1.6.17", "license": "GPL-3.0", "author": "EyeSeeTea team", "homepage": ".", From 960df808766dc3e6faae854c1149ae784a3dc019 Mon Sep 17 00:00:00 2001 From: 9sneha-n <9sneha.n@gmail.com> Date: Fri, 23 Aug 2024 11:51:34 +0530 Subject: [PATCH 4/7] fix: change AMR-F status on questionnaire completion --- src/domain/utils/ModuleProperties.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/utils/ModuleProperties.ts b/src/domain/utils/ModuleProperties.ts index 325696b1..b7e331a3 100644 --- a/src/domain/utils/ModuleProperties.ts +++ b/src/domain/utils/ModuleProperties.ts @@ -166,7 +166,7 @@ export const moduleProperties = new Map([ { isbatchReq: false, isQuestionnaireReq: true, - completeStatusChange: "DATASET", + completeStatusChange: "QUESTIONNAIRE", isSecondaryFileApplicable: false, isDryRunReq: false, importLoadingMsg: { From da079983e5b623f03c6f54ffbb36a6718eba9887 Mon Sep 17 00:00:00 2001 From: Ana Garcia Date: Fri, 23 Aug 2024 10:50:21 +0200 Subject: [PATCH 5/7] Allow all countries in Manufacturer country in Product Register attributes --- src/CompositionRoot.ts | 4 ++ .../repositories/CountryDefaultRepository.ts | 44 +++++++++++++++++++ src/domain/entities/Country.ts | 6 +++ src/domain/repositories/CountryRepository.ts | 6 +++ src/domain/usecases/GetAllCountriesUseCase.ts | 12 +++++ .../data-entry/ImportPrimaryFileUseCase.tsx | 8 ++-- .../amc/CustomValidationsAMCProductData.ts | 12 +++-- .../amc/ImportAMCProductLevelData.ts | 16 +++---- .../ImportRISIndividualFungalFile.ts | 10 ++--- ...EAValueFromOrganisationUnitCountryEntry.ts | 10 ++--- src/utils/tests.tsx | 1 + .../UploadsTableBody.tsx | 4 +- .../components/upload/ConsistencyChecks.tsx | 4 +- src/webapp/components/upload/UploadFiles.tsx | 6 +-- src/webapp/contexts/app-context.ts | 2 + src/webapp/pages/app/App.tsx | 3 +- 16 files changed, 111 insertions(+), 37 deletions(-) create mode 100644 src/data/repositories/CountryDefaultRepository.ts create mode 100644 src/domain/entities/Country.ts create mode 100644 src/domain/repositories/CountryRepository.ts create mode 100644 src/domain/usecases/GetAllCountriesUseCase.ts diff --git a/src/CompositionRoot.ts b/src/CompositionRoot.ts index e54faf94..fd56fc50 100644 --- a/src/CompositionRoot.ts +++ b/src/CompositionRoot.ts @@ -91,6 +91,8 @@ import { GetMultipleDashboardUseCase } from "./domain/usecases/GetMultipleDashbo import { DownloadAllDataButtonData } from "./domain/usecases/DownloadAllDataButtonData"; import { DownloadEmptyTemplateUseCase } from "./domain/usecases/DownloadEmptyTemplateUseCase"; import { DownloadPopulatedTemplateUseCase } from "./domain/usecases/DownloadPopulatedTemplateUseCase"; +import { CountryDefaultRepository } from "./data/repositories/CountryDefaultRepository"; +import { GetAllCountriesUseCase } from "./domain/usecases/GetAllCountriesUseCase"; export function getCompositionRoot(instance: Instance) { const api = getD2APiFromInstance(instance); @@ -127,6 +129,7 @@ export function getCompositionRoot(instance: Instance) { const glassAtcRepository = new GlassATCDefaultRepository(dataStoreClient); const atcRepository = new GlassATCDefaultRepository(dataStoreClient); const eventVisualizationRepository = new EventVisualizationAnalyticsDefaultRepository(api); + const countryRepository = new CountryDefaultRepository(api); return { instance: getExecute({ @@ -243,6 +246,7 @@ export function getCompositionRoot(instance: Instance) { }), countries: getExecute({ getInformation: new GetCountryInformationUseCase(countryInformationRepository), + getAll: new GetAllCountriesUseCase(countryRepository), }), glassDashboard: getExecute({ getDashboard: new GetDashboardUseCase(glassModuleRepository), diff --git a/src/data/repositories/CountryDefaultRepository.ts b/src/data/repositories/CountryDefaultRepository.ts new file mode 100644 index 00000000..2a61a074 --- /dev/null +++ b/src/data/repositories/CountryDefaultRepository.ts @@ -0,0 +1,44 @@ +import { D2Api, MetadataPick } from "@eyeseetea/d2-api/2.34"; +import { FutureData } from "../../domain/entities/Future"; +import { apiToFuture } from "../../utils/futures"; +import { CountryRepository } from "../../domain/repositories/CountryRepository"; +import { Country } from "../../domain/entities/Country"; + +export class CountryDefaultRepository implements CountryRepository { + constructor(private api: D2Api) {} + + public getAll(): FutureData { + return apiToFuture( + this.api.models.organisationUnits.get({ + fields: organisationUnitsFields, + paging: false, + level: 3, + }) + ).map(response => { + return this.buildOrgUnits(response.objects); + }); + } + + private buildOrgUnits(d2OrgUnits: D2OrgUnit[]): Country[] { + return d2OrgUnits.map(d2OrgUnit => ({ + id: d2OrgUnit.id, + name: d2OrgUnit.name, + shortName: d2OrgUnit.shortName, + code: d2OrgUnit.code, + })); + } +} + +const organisationUnitsFields = { + id: true, + name: true, + shortName: true, + code: true, + description: { id: true, name: true }, + geometry: true, + organisationUnitGroups: { id: true, name: true }, +} as const; + +type D2OrgUnit = MetadataPick<{ + organisationUnits: { fields: typeof organisationUnitsFields }; +}>["organisationUnits"][number]; diff --git a/src/domain/entities/Country.ts b/src/domain/entities/Country.ts new file mode 100644 index 00000000..721a843e --- /dev/null +++ b/src/domain/entities/Country.ts @@ -0,0 +1,6 @@ +export type Country = { + id: string; + name: string; + shortName: string; + code: string; +}; diff --git a/src/domain/repositories/CountryRepository.ts b/src/domain/repositories/CountryRepository.ts new file mode 100644 index 00000000..8bd96d56 --- /dev/null +++ b/src/domain/repositories/CountryRepository.ts @@ -0,0 +1,6 @@ +import { Country } from "../entities/Country"; +import { FutureData } from "../entities/Future"; + +export interface CountryRepository { + getAll(): FutureData; +} diff --git a/src/domain/usecases/GetAllCountriesUseCase.ts b/src/domain/usecases/GetAllCountriesUseCase.ts new file mode 100644 index 00000000..aad8197c --- /dev/null +++ b/src/domain/usecases/GetAllCountriesUseCase.ts @@ -0,0 +1,12 @@ +import { UseCase } from "../../CompositionRoot"; +import { Country } from "../entities/Country"; +import { FutureData } from "../entities/Future"; +import { CountryRepository } from "../repositories/CountryRepository"; + +export class GetAllCountriesUseCase implements UseCase { + constructor(private countryRepository: CountryRepository) {} + + public execute(): FutureData { + return this.countryRepository.getAll(); + } +} diff --git a/src/domain/usecases/data-entry/ImportPrimaryFileUseCase.tsx b/src/domain/usecases/data-entry/ImportPrimaryFileUseCase.tsx index 8b28d642..fd00c313 100644 --- a/src/domain/usecases/data-entry/ImportPrimaryFileUseCase.tsx +++ b/src/domain/usecases/data-entry/ImportPrimaryFileUseCase.tsx @@ -21,7 +21,7 @@ import { InstanceDefaultRepository } from "../../../data/repositories/InstanceDe import { GlassATCDefaultRepository } from "../../../data/repositories/GlassATCDefaultRepository"; import { AMCProductDataRepository } from "../../repositories/data-entry/AMCProductDataRepository"; import { AMCSubstanceDataRepository } from "../../repositories/data-entry/AMCSubstanceDataRepository"; -import { OrgUnitAccess } from "../../entities/User"; +import { Country } from "../../entities/Country"; export class ImportPrimaryFileUseCase { constructor( @@ -55,7 +55,7 @@ export class ImportPrimaryFileUseCase { countryCode: string, dryRun: boolean, eventListId: string | undefined, - orgUnitsWithAccess: OrgUnitAccess[], + allCountries: Country[], calculatedEventListFileId?: string ): FutureData { switch (moduleName) { @@ -113,7 +113,7 @@ export class ImportPrimaryFileUseCase { module.programs !== undefined ? module.programs.at(0) : undefined, module.name, module.customDataColumns ? module.customDataColumns : [], - orgUnitsWithAccess + allCountries ); }); } @@ -140,7 +140,7 @@ export class ImportPrimaryFileUseCase { orgUnitName, moduleName, period, - orgUnitsWithAccess, + allCountries, calculatedEventListFileId ); } diff --git a/src/domain/usecases/data-entry/amc/CustomValidationsAMCProductData.ts b/src/domain/usecases/data-entry/amc/CustomValidationsAMCProductData.ts index e183d82f..fdfd6087 100644 --- a/src/domain/usecases/data-entry/amc/CustomValidationsAMCProductData.ts +++ b/src/domain/usecases/data-entry/amc/CustomValidationsAMCProductData.ts @@ -6,7 +6,7 @@ import { D2TrackerTrackedEntity } from "@eyeseetea/d2-api/api/trackerTrackedEnti import { GlassATCDefaultRepository } from "../../../../data/repositories/GlassATCDefaultRepository"; import { GlassAtcVersionData, LAST_ATC_CODE_LEVEL, getAtcCodeByLevel } from "../../../entities/GlassAtcVersionData"; import { AMCProductDataRepository } from "../../../repositories/data-entry/AMCProductDataRepository"; -import { OrgUnitAccess } from "../../../entities/User"; +import { Country } from "../../../entities/Country"; const AMR_GLASS_AMC_TEA_ATC = "aK1JpD14imM"; const AMR_GLASS_AMC_TEA_COMBINATION = "mG49egdYK3G"; @@ -34,7 +34,7 @@ export class CustomValidationsAMCProductData { orgUnitId: string, orgUnitName: string, period: string, - orgUnitsWithAccess: OrgUnitAccess[] + allCountries: Country[] ): FutureData { return this.atcRepository.getCurrentAtcVersion().flatMap(atcVersion => { return this.amcProductRepository @@ -47,7 +47,7 @@ export class CustomValidationsAMCProductData { orgUnitName, period, atcVersion, - orgUnitsWithAccess + allCountries ); const dateErrors = this.checkSameEnrollmentDate(teis); @@ -115,7 +115,7 @@ export class CustomValidationsAMCProductData { countryName: string, period: string, atcVersion: GlassAtcVersionData, - orgUnitsWithAccess: OrgUnitAccess[] + allCountries: Country[] ): ConsistencyError[] { const errors = _( teis.map(tei => { @@ -256,9 +256,7 @@ export class CustomValidationsAMCProductData { } } - const productManufacturerCountry = orgUnitsWithAccess.find( - orgUnit => orgUnit.orgUnitId === manufacturerCountryId - ); + const productManufacturerCountry = allCountries.find(country => country.id === manufacturerCountryId); if (manufacturerCountryId && !productManufacturerCountry) { curErrors.push({ diff --git a/src/domain/usecases/data-entry/amc/ImportAMCProductLevelData.ts b/src/domain/usecases/data-entry/amc/ImportAMCProductLevelData.ts index 9fddb3df..ad4772ff 100644 --- a/src/domain/usecases/data-entry/amc/ImportAMCProductLevelData.ts +++ b/src/domain/usecases/data-entry/amc/ImportAMCProductLevelData.ts @@ -25,8 +25,8 @@ import { CODE_PRODUCT_NOT_HAVE_ATC, COMB_CODE_PRODUCT_NOT_HAVE_ATC } from "../.. import { AMCSubstanceDataRepository } from "../../../repositories/data-entry/AMCSubstanceDataRepository"; import { downloadIdsAndDeleteTrackedEntities } from "../utils/downloadIdsAndDeleteTrackedEntities"; import { getStringFromFile } from "../utils/fileToString"; -import { OrgUnitAccess } from "../../../entities/User"; import { getTEAValueFromOrganisationUnitCountryEntry } from "../utils/getTEAValueFromOrganisationUnitCountryEntry"; +import { Country } from "../../../entities/Country"; export const AMC_PRODUCT_REGISTER_PROGRAM_ID = "G6ChA5zMW9n"; export const AMC_RAW_PRODUCT_CONSUMPTION_STAGE_ID = "GmElQHKXLIE"; @@ -57,7 +57,7 @@ export class ImportAMCProductLevelData { orgUnitName: string, moduleName: string, period: string, - orgUnitsWithAccess: OrgUnitAccess[], + allCountries: Country[], calculatedEventListFileId?: string ): FutureData { return this.excelRepository.loadTemplate(file, AMC_PRODUCT_REGISTER_PROGRAM_ID).flatMap(_templateId => { @@ -82,7 +82,7 @@ export class ImportAMCProductLevelData { orgUnitId, orgUnitName, period, - orgUnitsWithAccess + allCountries ).flatMap(entities => { return this.validateTEIsAndEvents( entities, @@ -90,7 +90,7 @@ export class ImportAMCProductLevelData { orgUnitName, period, AMC_PRODUCT_REGISTER_PROGRAM_ID, - orgUnitsWithAccess + allCountries ).flatMap(validationResults => { if (validationResults.blockingErrors.length > 0) { const errorSummary: ImportSummary = { @@ -169,7 +169,7 @@ export class ImportAMCProductLevelData { orgUnitId: Id, orgUnitName: string, period: string, - orgUnitsWithAccess: OrgUnitAccess[] + allCountries: Country[] ): FutureData { return this.trackerRepository .getProgramMetadata(AMC_PRODUCT_REGISTER_PROGRAM_ID, AMC_RAW_PRODUCT_CONSUMPTION_STAGE_ID) @@ -201,7 +201,7 @@ export class ImportAMCProductLevelData { } else if (attr.valueType === "ORGANISATION_UNIT") { currentAttrVal = currentAttribute ? getTEAValueFromOrganisationUnitCountryEntry( - orgUnitsWithAccess, + allCountries, currentAttribute.value, true ) @@ -300,7 +300,7 @@ export class ImportAMCProductLevelData { orgUnitName: string, period: string, programId: string, - orgUnitsWithAccess: OrgUnitAccess[] + allCountries: Country[] ): FutureData { //1. Before running validations, add ids to tei, enrollement and event so thier relationships can be processed. const teisWithId = teis?.map((tei, teiIndex) => { @@ -332,7 +332,7 @@ export class ImportAMCProductLevelData { orgUnitId, orgUnitName, period, - orgUnitsWithAccess + allCountries ), }).flatMap(({ programRuleValidationResults, customRuleValidationsResults }) => { //4. After processing, remove ids to tei, enrollement and events so that they can be imported diff --git a/src/domain/usecases/data-entry/amr-individual-fungal/ImportRISIndividualFungalFile.ts b/src/domain/usecases/data-entry/amr-individual-fungal/ImportRISIndividualFungalFile.ts index b6447423..81d6df83 100644 --- a/src/domain/usecases/data-entry/amr-individual-fungal/ImportRISIndividualFungalFile.ts +++ b/src/domain/usecases/data-entry/amr-individual-fungal/ImportRISIndividualFungalFile.ts @@ -17,8 +17,8 @@ import { ValidationResult } from "../../../entities/program-rules/EventEffectTyp import { ProgramRuleValidationForBLEventProgram } from "../../program-rules-processing/ProgramRuleValidationForBLEventProgram"; import { ProgramRulesMetadataRepository } from "../../../repositories/program-rules/ProgramRulesMetadataRepository"; import { downloadIdsAndDeleteTrackedEntities } from "../utils/downloadIdsAndDeleteTrackedEntities"; -import { OrgUnitAccess } from "../../../entities/User"; import { getTEAValueFromOrganisationUnitCountryEntry } from "../utils/getTEAValueFromOrganisationUnitCountryEntry"; +import { Country } from "../../../entities/Country"; export const AMRIProgramID = "mMAj6Gofe49"; const AMR_GLASS_AMR_TET_PATIENT = "CcgnfemKr5U"; @@ -54,7 +54,7 @@ export class ImportRISIndividualFungalFile { | undefined, moduleName: string, dataColumns: CustomDataColumns, - orgUnitsWithAccess: OrgUnitAccess[] + allCountries: Country[] ): FutureData { if (action === "CREATE_AND_UPDATE") { return this.risIndividualFungalRepository @@ -86,7 +86,7 @@ export class ImportRISIndividualFungalFile { AMRDataProgramStageIdl(), countryCode, period, - orgUnitsWithAccess + allCountries ).flatMap(entities => { return this.runProgramRuleValidations( AMRIProgramIDl, @@ -280,7 +280,7 @@ export class ImportRISIndividualFungalFile { AMRDataProgramStageIdl: string, countryCode: string, period: string, - orgUnitsWithAccess: OrgUnitAccess[] + allCountries: Country[] ): FutureData { return this.trackerRepository.getProgramMetadata(AMRIProgramIDl, AMRDataProgramStageIdl).flatMap(metadata => { const trackedEntities = individualFungalDataItems.map(dataItem => { @@ -293,7 +293,7 @@ export class ImportRISIndividualFungalFile { attribute: attr.id, value: currentAttribute ? getTEAValueFromOrganisationUnitCountryEntry( - orgUnitsWithAccess, + allCountries, currentAttribute.value, true ) diff --git a/src/domain/usecases/data-entry/utils/getTEAValueFromOrganisationUnitCountryEntry.ts b/src/domain/usecases/data-entry/utils/getTEAValueFromOrganisationUnitCountryEntry.ts index 73e1226d..c4014aa3 100644 --- a/src/domain/usecases/data-entry/utils/getTEAValueFromOrganisationUnitCountryEntry.ts +++ b/src/domain/usecases/data-entry/utils/getTEAValueFromOrganisationUnitCountryEntry.ts @@ -1,13 +1,13 @@ -import { OrgUnitAccess } from "../../../entities/User"; +import { Country } from "../../../entities/Country"; export function getTEAValueFromOrganisationUnitCountryEntry( - orgUnitsWithAccess: OrgUnitAccess[], + allCountries: Country[], attributeValue: string, useCode: boolean ): string { return ( - orgUnitsWithAccess.find(orgUnit => { - return useCode ? orgUnit.orgUnitCode === attributeValue : orgUnit.orgUnitName === attributeValue; - })?.orgUnitId || attributeValue + allCountries.find(country => { + return useCode ? country.code === attributeValue : country.name === attributeValue; + })?.id || attributeValue ); } diff --git a/src/utils/tests.tsx b/src/utils/tests.tsx index 4ed0cc99..13fcc160 100644 --- a/src/utils/tests.tsx +++ b/src/utils/tests.tsx @@ -55,6 +55,7 @@ export function getTestContext() { currentUser: getTestUser(), config: getTestConfig(), compositionRoot: getCompositionRoot(instance), + allCountries: [], }; return { api, context }; diff --git a/src/webapp/components/current-data-submission/UploadsTableBody.tsx b/src/webapp/components/current-data-submission/UploadsTableBody.tsx index a93ed6a0..60a52b29 100644 --- a/src/webapp/components/current-data-submission/UploadsTableBody.tsx +++ b/src/webapp/components/current-data-submission/UploadsTableBody.tsx @@ -30,7 +30,7 @@ export interface UploadsTableBodyProps { } export const UploadsTableBody: React.FC = ({ rows, refreshUploads, showComplete }) => { - const { compositionRoot, currentUser } = useAppContext(); + const { compositionRoot, allCountries } = useAppContext(); const snackbar = useSnackbar(); const [loading, setLoading] = useState(false); @@ -153,7 +153,7 @@ export const UploadsTableBody: React.FC = ({ rows, refres primaryFileToDelete.countryCode, false, primaryFileToDelete.eventListFileId, - currentUser.userOrgUnitsAccess, + allCountries, primaryFileToDelete.calculatedEventListFileId ) : Future.success(undefined), diff --git a/src/webapp/components/upload/ConsistencyChecks.tsx b/src/webapp/components/upload/ConsistencyChecks.tsx index 7253f3cd..ce3e41c3 100644 --- a/src/webapp/components/upload/ConsistencyChecks.tsx +++ b/src/webapp/components/upload/ConsistencyChecks.tsx @@ -38,7 +38,7 @@ export const ConsistencyChecks: React.FC = ({ setPrimaryFileImportSummary, setSecondaryFileImportSummary, }) => { - const { compositionRoot, currentUser } = useAppContext(); + const { compositionRoot, allCountries } = useAppContext(); const { currentModuleAccess } = useCurrentModuleContext(); const { currentOrgUnitAccess } = useCurrentOrgUnitContext(); const [fileType, setFileType] = useState("primary"); @@ -166,7 +166,7 @@ export const ConsistencyChecks: React.FC = ({ currentOrgUnitAccess.orgUnitCode, false, "", - currentUser.userOrgUnitsAccess + allCountries ), importSecondaryFileSummary: secondaryFile ? compositionRoot.fileSubmission.secondaryFile( diff --git a/src/webapp/components/upload/UploadFiles.tsx b/src/webapp/components/upload/UploadFiles.tsx index bb65e3b9..b59e389e 100644 --- a/src/webapp/components/upload/UploadFiles.tsx +++ b/src/webapp/components/upload/UploadFiles.tsx @@ -94,7 +94,7 @@ export const UploadFiles: React.FC = ({ isLoadingSecondary, setIsLoadingSecondary, }) => { - const { compositionRoot, currentUser } = useAppContext(); + const { compositionRoot, allCountries } = useAppContext(); const snackbar = useSnackbar(); const [isValidated, setIsValidated] = useState(false); const [isPrimaryFileValid, setIsPrimaryFileValid] = useState(false); @@ -238,7 +238,7 @@ export const UploadFiles: React.FC = ({ orgUnitCode, true, "", - currentUser.userOrgUnitsAccess + allCountries ), importSecondaryFileSummary: secondaryFile ? compositionRoot.fileSubmission.secondaryFile( @@ -377,7 +377,7 @@ export const UploadFiles: React.FC = ({ orgUnitId, orgUnitName, orgUnitCode, - currentUser.userOrgUnitsAccess, + allCountries, setPrimaryFileImportSummary, changeStep, setSecondaryFileImportSummary, diff --git a/src/webapp/contexts/app-context.ts b/src/webapp/contexts/app-context.ts index 5c3cb020..808d9a4a 100644 --- a/src/webapp/contexts/app-context.ts +++ b/src/webapp/contexts/app-context.ts @@ -3,12 +3,14 @@ import { CompositionRoot } from "../../CompositionRoot"; import { UserAccessInfo } from "../../domain/entities/User"; import { D2Api } from "../../types/d2-api"; import { Instance } from "../../data/entities/Instance"; +import { Country } from "../../domain/entities/Country"; export interface AppContextState { api: D2Api; currentUser: UserAccessInfo; compositionRoot: CompositionRoot; instance: Instance; + allCountries: Country[]; } export const AppContext = React.createContext(null); diff --git a/src/webapp/pages/app/App.tsx b/src/webapp/pages/app/App.tsx index 016239b4..d0f259e9 100644 --- a/src/webapp/pages/app/App.tsx +++ b/src/webapp/pages/app/App.tsx @@ -30,6 +30,7 @@ export const App: React.FC = React.memo(function App({ api, d2, instan async function setup() { const compositionRoot = getCompositionRoot(instance); const { data: currentUser } = await compositionRoot.instance.getCurrentUser().runAsync(); + const { data: allCountries } = await compositionRoot.countries.getAll().runAsync(); if (!currentUser) throw new Error("User not logged in"); @@ -37,7 +38,7 @@ export const App: React.FC = React.memo(function App({ api, d2, instan // const isShareButtonVisible = _(appConfig).get("appearance.showShareButton") || false; - setAppContext({ api, currentUser, compositionRoot, instance: instance }); + setAppContext({ api, currentUser, compositionRoot, instance: instance, allCountries: allCountries ?? [] }); // setShowShareButton(isShareButtonVisible); initFeedbackTool(d2, appConfig); setLoading(false); From 3e04feff606e17eabaef4f970a3b53cb91ab0b40 Mon Sep 17 00:00:00 2001 From: Ana Garcia Date: Fri, 23 Aug 2024 12:30:00 +0200 Subject: [PATCH 6/7] Allow open periods from datastore --- .../repositories/InstanceDefaultRepository.ts | 1 + src/domain/entities/GlassModule.ts | 1 + src/domain/entities/User.ts | 1 + src/utils/currentPeriodHelper.ts | 4 +++ .../components/data-file-history/Filter.tsx | 17 +++++++++-- .../hooks/usePopulateDataSubmissionHistory.ts | 29 +++++++++++++++---- 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/data/repositories/InstanceDefaultRepository.ts b/src/data/repositories/InstanceDefaultRepository.ts index 00a38688..0f80096b 100644 --- a/src/data/repositories/InstanceDefaultRepository.ts +++ b/src/data/repositories/InstanceDefaultRepository.ts @@ -85,6 +85,7 @@ export class InstanceDefaultRepository implements InstanceRepository { moduleId: module.id, moduleName: module.name, populateCurrentYearInHistory: module.populateCurrentYearInHistory ? true : false, + startPeriod: module.startPeriod, readAccess: readAccess, captureAccess: writeAccess, usergroups: [...module.userGroups.captureAccess, ...module.userGroups.readAccess], diff --git a/src/domain/entities/GlassModule.ts b/src/domain/entities/GlassModule.ts index e1e16ebd..08db4ed0 100644 --- a/src/domain/entities/GlassModule.ts +++ b/src/domain/entities/GlassModule.ts @@ -43,6 +43,7 @@ export interface GlassModule { programStageId: string; }[]; populateCurrentYearInHistory?: boolean; + startPeriod?: number; customDataColumns?: CustomDataColumns; lineLists?: LineListDetails[]; chunkSizes?: ChunkSizes; diff --git a/src/domain/entities/User.ts b/src/domain/entities/User.ts index 595dc7a3..440ef868 100644 --- a/src/domain/entities/User.ts +++ b/src/domain/entities/User.ts @@ -22,6 +22,7 @@ export interface ModuleAccess { captureAccess: boolean; usergroups: UserGroup[]; populateCurrentYearInHistory: boolean; + startPeriod?: number; } export interface UserAccessInfo { diff --git a/src/utils/currentPeriodHelper.ts b/src/utils/currentPeriodHelper.ts index f1f965f1..05f4966a 100644 --- a/src/utils/currentPeriodHelper.ts +++ b/src/utils/currentPeriodHelper.ts @@ -50,3 +50,7 @@ export const getLastNYearsQuarters = (n = 8) => { return years; }; + +export const getRangeOfYears = (maxYear: number, minYear: number): string[] => { + return Array.from({ length: maxYear - minYear + 1 }, (_, i) => (maxYear - i).toString()); +}; diff --git a/src/webapp/components/data-file-history/Filter.tsx b/src/webapp/components/data-file-history/Filter.tsx index 6661e5fa..7742cc98 100644 --- a/src/webapp/components/data-file-history/Filter.tsx +++ b/src/webapp/components/data-file-history/Filter.tsx @@ -3,7 +3,12 @@ import { Box, FormControl, MenuItem, Select, Typography, InputLabel, withStyles, import { glassColors } from "../../pages/app/themes/dhis2.theme"; import i18n from "@eyeseetea/d2-ui-components/locales"; import { Dispatch, SetStateAction, useMemo } from "react"; -import { getLastNYears, getLastNYearsQuarters } from "../../../utils/currentPeriodHelper"; +import { + getCurrentYear, + getLastNYears, + getLastNYearsQuarters, + getRangeOfYears, +} from "../../../utils/currentPeriodHelper"; import { useCurrentModuleContext } from "../../contexts/current-module-context"; import { useAppContext } from "../../contexts/app-context"; @@ -86,12 +91,18 @@ export const Filter: React.FC = ({ year, setYear, status, setStatus return [...quarters, { label: "All", value: "All" }]; } else { const addCurrentYear = currentModuleAccess.populateCurrentYearInHistory; - const years = getLastNYears(addCurrentYear).map(year => ({ label: year, value: year })); - return [...years, { label: "All", value: "All" }]; + const years = currentModuleAccess.startPeriod + ? getRangeOfYears( + addCurrentYear ? getCurrentYear() : getCurrentYear() - 1, + currentModuleAccess.startPeriod + ) + : getLastNYears(addCurrentYear); + return [...years.map(year => ({ label: year, value: year })), { label: "All", value: "All" }]; } }, [ currentModuleAccess.moduleName, currentModuleAccess.populateCurrentYearInHistory, + currentModuleAccess.startPeriod, currentUser.quarterlyPeriodModules, ]); diff --git a/src/webapp/components/data-submissions-history/hooks/usePopulateDataSubmissionHistory.ts b/src/webapp/components/data-submissions-history/hooks/usePopulateDataSubmissionHistory.ts index 9935a733..faa9c1da 100644 --- a/src/webapp/components/data-submissions-history/hooks/usePopulateDataSubmissionHistory.ts +++ b/src/webapp/components/data-submissions-history/hooks/usePopulateDataSubmissionHistory.ts @@ -3,7 +3,12 @@ import { useAppContext } from "../../../contexts/app-context"; import { useCurrentOrgUnitContext } from "../../../contexts/current-orgUnit-context"; import { useCurrentModuleContext } from "../../../contexts/current-module-context"; import { useGlassDataSubmissionsByModuleAndOU } from "../../../hooks/useGlassDataSubmissionsByModuleAndOU"; -import { getLastNYears, getLastNYearsQuarters } from "../../../../utils/currentPeriodHelper"; +import { + getCurrentYear, + getLastNYears, + getLastNYearsQuarters, + getRangeOfYears, +} from "../../../../utils/currentPeriodHelper"; export function usePopulateDataSubmissionHistory() { const { compositionRoot } = useAppContext(); @@ -31,11 +36,22 @@ export function usePopulateDataSubmissionHistory() { } else { //Check if Data Submissions history is populated const addCurrentYear = currentModuleAccess.populateCurrentYearInHistory; - getLastNYears(addCurrentYear).forEach(year => { - if (!dataSubmissions.data.find(ds => ds.period === year)) { - years.push(year); - } - }); + if (currentModuleAccess.startPeriod) { + const maxYear = addCurrentYear ? getCurrentYear() : getCurrentYear() - 1; + const minYear = currentModuleAccess.startPeriod; + + getRangeOfYears(maxYear, minYear).forEach(year => { + if (!dataSubmissions.data.find(ds => ds.period === year)) { + years.push(year); + } + }); + } else { + getLastNYears(addCurrentYear).forEach(year => { + if (!dataSubmissions.data.find(ds => ds.period === year)) { + years.push(year); + } + }); + } } if (years.length && currentModuleAccess.moduleId !== "" && currentOrgUnitAccess.orgUnitId !== "") { @@ -55,6 +71,7 @@ export function usePopulateDataSubmissionHistory() { } }, [ compositionRoot.glassDataSubmission, + currentModuleAccess, currentModuleAccess.moduleId, currentModuleAccess.moduleName, currentModuleAccess.populateCurrentYearInHistory, From 4bba9123fb21a61f07af0830ba821851a5f563eb Mon Sep 17 00:00:00 2001 From: Bhavana Narayanan Date: Thu, 29 Aug 2024 11:33:44 +0530 Subject: [PATCH 7/7] Feat:Version Bumped --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d637bf42..8e2d0f0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "glass", "description": "DHIS2 Glass App", - "version": "1.6.17", + "version": "1.6.18", "license": "GPL-3.0", "author": "EyeSeeTea team", "homepage": ".",