From 39e50470af5a6bdf0a59166bd6b27a70df057421 Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:58:07 +0100 Subject: [PATCH 1/2] feat: restructure repeatable stages --- src/webapp/components/survey/SurveyForm.tsx | 110 ++++++++---------- .../components/survey/SurveyStageSection.tsx | 71 +++++++++++ .../components/survey/hook/useSurveyForm.ts | 57 ++++++++- 3 files changed, 175 insertions(+), 63 deletions(-) create mode 100644 src/webapp/components/survey/SurveyStageSection.tsx diff --git a/src/webapp/components/survey/SurveyForm.tsx b/src/webapp/components/survey/SurveyForm.tsx index 63a2ff18..01301100 100644 --- a/src/webapp/components/survey/SurveyForm.tsx +++ b/src/webapp/components/survey/SurveyForm.tsx @@ -12,11 +12,9 @@ import styled from "styled-components"; import { getSurveyDisplayName } from "../../../domain/utils/PPSProgramsHelper"; import { SurveyFormOUSelector } from "./SurveyFormOUSelector"; import { SurveySection } from "./SurveySection"; +import { SurveyStageSection } from "./SurveyStageSection"; import { useHistory } from "react-router-dom"; import useReadOnlyAccess from "./hook/useReadOnlyAccess"; -import { GridSection } from "./GridSection"; -import _c from "../../../domain/entities/generic/Collection"; -import { TableSection } from "./TableSection"; export interface SurveyFormProps { hideForm: () => void; @@ -24,7 +22,7 @@ export interface SurveyFormProps { formType: SURVEY_FORM_TYPES; } -const CancelButton = withStyles(() => ({ +export const CancelButton = withStyles(() => ({ root: { color: "white", backgroundColor: "#bd1818", @@ -42,6 +40,7 @@ export const SurveyForm: React.FC = props => { const { questionnaire, + surveyStages, loading, setLoading, currentOrgUnit, @@ -110,69 +109,58 @@ export const SurveyForm: React.FC = props => { /> )} - {questionnaire?.stages?.map(stage => { + + {surveyStages.map(stage => { if (!stage?.isVisible) return null; return ( - - {i18n.t(`Stage - ${stage.title}`)} - - {stage.sections.map(section => { - if (!section.isVisible || section.isAntibioticSection) return null; - - if (section.isSpeciesSection) - return ( - - updateQuestion(question, stage.id) - } - viewOnly={hasReadOnlyAccess} - /> - ); - if (section.isAntibioticTreatmentHospitalEpisodeSection) - return ( - + {"repeatableStages" in stage ? ( + <> + {stage.repeatableStages.map(repeatableStage => ( + + + {i18n.t(`Stage - ${stage.title}`)} + + + {repeatableStage.sections.map(section => ( + + ))} + + ))} + + + + + + ) : ( + <> + {i18n.t(`Stage - ${stage.title}`)} + + {stage.sections.map(section => ( + - updateQuestion(question, stage.id) - } + stage={stage} viewOnly={hasReadOnlyAccess} + removeProgramStage={removeProgramStage} + updateQuestion={updateQuestion} /> - ); - - return ( - - updateQuestion(question, stage.id) - } - questions={section.questions} - viewOnly={hasReadOnlyAccess} - /> - ); - })} - - {stage.repeatable && ( - - - {stage.isAddedByUser && ( - removeProgramStage(stage.id)} - > - {i18n.t(`Remove ${stage.title}`)} - - )} - + ))} + )} ); diff --git a/src/webapp/components/survey/SurveyStageSection.tsx b/src/webapp/components/survey/SurveyStageSection.tsx new file mode 100644 index 00000000..68a5f826 --- /dev/null +++ b/src/webapp/components/survey/SurveyStageSection.tsx @@ -0,0 +1,71 @@ +import i18n from "@eyeseetea/d2-ui-components/locales"; +import { QuestionnaireStage } from "../../../domain/entities/Questionnaire/Questionnaire"; +import { Question } from "../../../domain/entities/Questionnaire/QuestionnaireQuestion"; +import { QuestionnaireSection } from "../../../domain/entities/Questionnaire/QuestionnaireSection"; +import { Id } from "../../../domain/entities/Ref"; +import { GridSection } from "./GridSection"; +import { CancelButton } from "./SurveyForm"; +import { SurveySection } from "./SurveySection"; +import { TableSection } from "./TableSection"; +import styled from "styled-components"; + +type SurveyStageSectionProps = { + section: QuestionnaireSection; + stage: QuestionnaireStage; + viewOnly: boolean; + removeProgramStage: (stageId: Id) => void; + updateQuestion: (question: Question, stageId?: Id) => void; +}; + +export const SurveyStageSection: React.FC = props => { + const { section, stage, viewOnly, removeProgramStage, updateQuestion } = props; + + switch (true) { + case !section.isVisible: + case section.isAntibioticSection: + return null; + case section.isSpeciesSection: + return ( + updateQuestion(question, stage.id)} + viewOnly={viewOnly} + /> + ); + case section.isAntibioticTreatmentHospitalEpisodeSection: + return ( + updateQuestion(question, stage.id)} + viewOnly={viewOnly} + /> + ); + default: + return ( + <> + updateQuestion(question, stage.id)} + questions={section.questions} + viewOnly={viewOnly} + /> + + {stage.repeatable && stage.isAddedByUser && ( + + removeProgramStage(stage.id)} + > + {i18n.t(`Remove ${stage.title}`)} + + + )} + + ); + } +}; + +const PaddedDiv = styled.div` + padding: 15px 0; +`; diff --git a/src/webapp/components/survey/hook/useSurveyForm.ts b/src/webapp/components/survey/hook/useSurveyForm.ts index bf8678d8..e61f4d25 100644 --- a/src/webapp/components/survey/hook/useSurveyForm.ts +++ b/src/webapp/components/survey/hook/useSurveyForm.ts @@ -1,6 +1,9 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useAppContext } from "../../../contexts/app-context"; -import { Questionnaire } from "../../../../domain/entities/Questionnaire/Questionnaire"; +import { + Questionnaire, + QuestionnaireStage, +} from "../../../../domain/entities/Questionnaire/Questionnaire"; import { SURVEY_FORM_TYPES } from "../../../../domain/entities/Survey"; import { OrgUnitAccess } from "../../../../domain/entities/User"; import { useCurrentSurveys } from "../../../contexts/current-surveys-context"; @@ -9,6 +12,19 @@ import useReadOnlyAccess from "./useReadOnlyAccess"; import { useCurrentModule } from "../../../contexts/current-module-context"; import { Code, Question } from "../../../../domain/entities/Questionnaire/QuestionnaireQuestion"; import { Id } from "../../../../domain/entities/Ref"; +import { Maybe } from "../../../../utils/ts-utils"; +import _c from "../../../../domain/entities/generic/Collection"; + +type RepeatableStage = { + title: string; + sortOrder: number; + repeatable: boolean; + isVisible: boolean; + code: Code; + repeatableStages: QuestionnaireStage[]; +}; + +type SurveyStage = QuestionnaireStage | RepeatableStage; export function useSurveyForm(formType: SURVEY_FORM_TYPES, eventId: string | undefined) { const { compositionRoot, currentUser, ppsHospitals, prevalenceHospitals } = useAppContext(); @@ -203,8 +219,13 @@ export function useSurveyForm(formType: SURVEY_FORM_TYPES, eventId: string | und [compositionRoot.surveys, questionnaire] ); + const surveyStages = useMemo(() => { + return buildSurveyStages(questionnaire); + }, [questionnaire]); + return { questionnaire, + surveyStages, setQuestionnaire, loading, currentOrgUnit, @@ -217,3 +238,35 @@ export function useSurveyForm(formType: SURVEY_FORM_TYPES, eventId: string | und removeProgramStage, }; } + +const buildSurveyStages = (questionnaire: Maybe): SurveyStage[] => { + if (!questionnaire) return []; + + const { stages } = questionnaire; + const nonRepeatableStages = stages.filter(stage => !stage.repeatable); + const repeatableStages = stages.filter(stage => stage.repeatable); + + const groupedRepeatableStages = _c(repeatableStages) + .groupBy(stage => stage.title) + .mapValues(([title, questionnaireStages]) => { + const questionnaireStage = questionnaireStages[0]; + if (!questionnaireStage) return undefined; + + const { sortOrder, isVisible, code, repeatable } = questionnaireStage; + + return { + code: code, + isVisible: isVisible, + repeatable: repeatable, + repeatableStages: questionnaireStages, + sortOrder: sortOrder, + title: title, + }; + }) + .values() + .filter((stage): stage is RepeatableStage => stage !== undefined); + + return _c([...nonRepeatableStages, ...groupedRepeatableStages]) + .sortBy(stage => stage.sortOrder) + .value(); +}; From 3b51bdad88fedb946c50d100a163933129178c1a Mon Sep 17 00:00:00 2001 From: Chukwudumebi Onwuli <37223065+deeonwuli@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:10:59 +0100 Subject: [PATCH 2/2] fix: update POT files --- i18n/en.pot | 7 +++++-- i18n/es.po | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f8e50356..6f17f01e 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,12 +5,15 @@ 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-12-20T16:08:06.715Z\n" -"PO-Revision-Date: 2024-12-20T16:08:06.715Z\n" +"POT-Creation-Date: 2025-01-21T09:58:39.001Z\n" +"PO-Revision-Date: 2025-01-21T09:58:39.001Z\n" msgid "There was a problem with \"{{name}}\" - {{prop}} is not set" msgstr "" +msgid "There was an error processing the form" +msgstr "" + msgid "Facilities" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index 3cf6158f..ae16bee7 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,15 +1,19 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-12-20T16:08:06.715Z\n" +"POT-Creation-Date: 2025-01-21T09:58:39.001Z\n" "PO-Revision-Date: 2018-10-25T09:02:35.143Z\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" + msgid "There was a problem with \"{{name}}\" - {{prop}} is not set" msgstr "" +msgid "There was an error processing the form" +msgstr "" + msgid "Facilities" msgstr ""