From 4c2d6424cbe8cf7728b9ab26f68f853e86ae16c7 Mon Sep 17 00:00:00 2001 From: gqcorneby Date: Wed, 22 Jan 2025 15:35:09 +0800 Subject: [PATCH 01/14] download template page redesign - add sections in download template page - collapsible orgunit section instead of a checkbox - rearrange advanced options --- .../collapsible-section/Section.tsx | 89 ++++ .../template-selector/TemplateSelector.tsx | 497 ++++++++++-------- 2 files changed, 362 insertions(+), 224 deletions(-) create mode 100644 src/webapp/components/collapsible-section/Section.tsx diff --git a/src/webapp/components/collapsible-section/Section.tsx b/src/webapp/components/collapsible-section/Section.tsx new file mode 100644 index 00000000..0f04d1c5 --- /dev/null +++ b/src/webapp/components/collapsible-section/Section.tsx @@ -0,0 +1,89 @@ +import { Icon, IconButton, makeStyles, Paper } from "@material-ui/core"; +import React from "react"; + +type iconPosition = "left" | "right"; +type arrowStyle = "upDown" | "rightLeft"; + +export interface SectionProps { + isOpen?: boolean; + setOpen?: (open: boolean) => void; + children: React.ReactNode; + title: React.ReactNode; + collapsible?: boolean; + elevation?: number; + iconPos?: iconPosition; + classProps?: { + section?: string; + header?: string; + content?: string; + }; + arrowStyle?: arrowStyle; +} + +export const Section = ({ + children, + title, + collapsible, + isOpen = true, + setOpen = () => {}, + elevation = 1, + iconPos = "right", + classProps = {}, + arrowStyle = "upDown", +}: SectionProps) => { + const classes = useStyles(); + const leftIcon = iconPos === "left" ? classes.leftIcon : null; + const toggle = () => setOpen(!isOpen); + + return ( + +
+ {collapsible && leftIcon && } + {title} + {collapsible && !leftIcon && } +
+
+ {children} +
+
+ ); +}; + +export interface CollapsibleToggleProps { + isOpen: boolean; + arrowStyle: "upDown" | "rightLeft"; +} + +export const CollapsibleToggle = ({ isOpen, arrowStyle }: CollapsibleToggleProps) => { + const classes = useStyles(); + const [open, close] = arrowStyle === "upDown" ? ["up", "down"] : ["right", "left"]; + + return ( + + {isOpen ? `keyboard_arrow_${open}` : `keyboard_arrow_${close}`} + + ); +}; + +const useStyles = makeStyles({ + header: { + margin: 0, + padding: "1em", + paddingLeft: 0, + display: "flex", + justifyContent: "space-between", + alignItems: "center", + borderBottom: "solid 1px #e8edf2", + cursor: "pointer", + }, + noBorder: { border: "none" }, + leftIcon: { + justifyContent: "normal", + gap: 10, + }, + paper: { padding: "1em", paddingTop: "0.5em", marginBottom: "1em" }, + button: { padding: 0 }, +}); diff --git a/src/webapp/components/template-selector/TemplateSelector.tsx b/src/webapp/components/template-selector/TemplateSelector.tsx index 7d60984e..6704e7d1 100644 --- a/src/webapp/components/template-selector/TemplateSelector.tsx +++ b/src/webapp/components/template-selector/TemplateSelector.tsx @@ -1,6 +1,6 @@ import { Id } from "@eyeseetea/d2-api"; import { DatePicker, OrgUnitsSelector } from "@eyeseetea/d2-ui-components"; -import { Checkbox, FormControlLabel, makeStyles } from "@material-ui/core"; +import { Checkbox, FormControlLabel, makeStyles, Tooltip, Typography } from "@material-ui/core"; import _ from "lodash"; import moment from "moment"; import React, { useEffect, useMemo, useState } from "react"; @@ -15,6 +15,8 @@ import { useAppContext } from "../../contexts/app-context"; import Settings from "../../logic/settings"; import { orgUnitListParams } from "../../utils/template"; import { Select, SelectOption } from "../select/Select"; +import { Section } from "../collapsible-section/Section"; +import HelpIcon from "@material-ui/icons/Help"; type DataSource = Record; @@ -81,6 +83,7 @@ export const TemplateSelector = ({ showLanguage: false, } ); + const [showAdvancedOptions, setShowAdvancedOptions] = useState(true); const dataSets = dataSource?.dataSets; const { templateId, id } = state; @@ -152,12 +155,12 @@ export const TemplateSelector = ({ useEffect(() => { const { type, id, templateId, templateType, ...rest } = state; if (type && id && templateId && templateType) { - const orgUnits = filterOrgUnits ? cleanOrgUnitPaths(selectedOrgUnits) : []; + const orgUnits = cleanOrgUnitPaths(selectedOrgUnits); onChange({ type, id, orgUnits, templateId, templateType, ...rest }); } else { onChange(null); } - }, [state, selectedOrgUnits, filterOrgUnits, orgUnitTreeFilter, onChange]); + }, [state, selectedOrgUnits, orgUnitTreeFilter, onChange]); const themeOptions = dataSource ? modelToSelectOption(themes) : []; @@ -211,7 +214,7 @@ export const TemplateSelector = ({ showLanguage: customTemplate?.showLanguage || false, showPeriod: customTemplate?.showPeriod || false, })); - setFilterOrgUnits(false); + clearPopulateDates(); setSelectedOrgUnits([]); } @@ -270,11 +273,12 @@ export const TemplateSelector = ({ setState(state => ({ ...state, filterTEIEnrollmentDate })); }; - const onFilterOrgUnitsChange = (_event: React.ChangeEvent, filterOrgUnits: boolean) => { - const isCustomProgram = state.templateType === "custom" && state.type !== "dataSets"; - const populate = isCustomProgram && filterOrgUnits; - setState(state => ({ ...state, populate })); - clearPopulateDates(); + const onFilterOrgUnitsChange = (filterOrgUnits: boolean) => { + //do not reset populate + // const isCustomProgram = state.templateType === "custom" && state.type !== "dataSets"; + // const populate = isCustomProgram && filterOrgUnits; + // setState(state => ({ ...state, populate })); + // clearPopulateDates(); setFilterOrgUnits(filterOrgUnits); }; @@ -287,90 +291,114 @@ export const TemplateSelector = ({ const showPopulate = !(state.templateType === "custom" && !settings.showPopulateInCustomForms); const selected = state.id && state.templateId ? getOptionValue({ id: state.id, templateId: state.templateId }) : ""; + useEffect(() => { + console.log(selectedOrgUnits); + }, [selectedOrgUnits]); + return ( <> -

{i18n.t("Template")}

- -
- {models.length > 1 && ( -
- -
-
- - {state.type === "dataSets" && state.templateType === "custom" && showPopulate && ( - onCustomFormDateChange(date)} - maxDate={state.endDate} - views={datePickerFormat?.views} - format={datePickerFormat?.format ?? "DD/MM/YYYY"} - InputLabelProps={{ style: { color: "#494949" } }} - /> - )} - - {state.type === "dataSets" && (showPopulate || state.showPeriod) && ( +
{i18n.t("Template")}} + classProps={{ section: classes.section }} + >
+ {models.length > 1 && ( +
+
- )} - {settings.orgUnitSelection !== "import" && showPopulate && ( - <> -

{i18n.t("Organisation units")}

+ {state.type === "dataSets" && state.templateType === "custom" && showPopulate && ( + onCustomFormDateChange(date)} + maxDate={state.endDate} + views={datePickerFormat?.views} + format={datePickerFormat?.format ?? "DD/MM/YYYY"} + InputLabelProps={{ style: { color: "#494949" } }} + /> + )} -
- } - label={ - state.templateType === "custom" - ? i18n.t("Select organisation unit to populate data") - : i18n.t("Select available organisation units to include in the template") - } - /> + {state.type === "dataSets" && (showPopulate || state.showPeriod) && ( +
+
+ onStartDateChange("startDate", date, true)} + maxDate={state.endDate} + views={datePickerFormat?.views} + format={datePickerFormat?.format ?? "DD/MM/YYYY"} + InputLabelProps={{ style: { color: "#494949" } }} + /> +
+
+ onEndDateChange("endDate", date, true)} + minDate={state.startDate} + views={datePickerFormat?.views} + format={datePickerFormat?.format ?? "DD/MM/YYYY"} + InputLabelProps={{ style: { color: "#494949" } }} + /> +
+ )} - {filterOrgUnits && - (!_.isEmpty(orgUnitTreeRootIds) ? ( + {settings.orgUnitSelection !== "import" && showPopulate && ( +
+

+ {i18n.t("Organisation units")} + + ({selectedOrgUnits.length}) + + + + +

+
+ } + > + {!_.isEmpty(orgUnitTreeRootIds) ? (
{i18n.t("User does not have any capture organisations units")}
- ))} - - )} - - {state.templateType !== "custom" &&

{i18n.t("Advanced template properties")}

} - - {availableLanguages.length > 0 && (state.templateType !== "custom" || state.showLanguage) && ( -
-
- ")} - value={state.theme ?? ""} + {userHasReadAccess && !!selectedOrgUnits.length && state.templateType !== "custom" && ( +
{i18n.t("Populate")}}> +
+ } + label={i18n.t("Populate template with data")} />
-
- )} - {userHasReadAccess && filterOrgUnits && state.templateType !== "custom" && ( -
- } - label={i18n.t("Populate template with data")} - /> -
- )} + {/*start/end date*/} + {state.populate && !isCustomDataSet && ( + <> + {!isDataSet && ( +
+ + } + label={i18n.t("Also filter TEI and relationships by their enrollment date")} + /> +
+ )} + +
+
+ onStartDateChange("populateStartDate", date)} + minDate={ + state.type === "dataSets" + ? state.startDate?.startOf(datePickerFormat?.unit ?? "day") + : undefined + } + maxDate={moment.min( + _.compact([ + state.type === "dataSets" && state.endDate, + state.populateEndDate, + ]) + )} + views={datePickerFormat?.views} + format={datePickerFormat?.format ?? "DD/MM/YYYY"} + InputLabelProps={{ style: { color: "#494949" } }} + /> +
+ +
+ onEndDateChange("populateEndDate", date)} + minDate={moment.max( + _.compact([ + state.type === "dataSets" && state.startDate, + state.populateStartDate, + ]) + )} + maxDate={ + state.type === "dataSets" + ? state.endDate?.endOf(datePickerFormat?.unit ?? "day") + : undefined + } + views={datePickerFormat?.views} + format={datePickerFormat?.format ?? "DD/MM/YYYY"} + InputLabelProps={{ style: { color: "#494949" } }} + /> +
+
+ + )} - {state.populate && !isCustomDataSet && ( - <> - {!isDataSet && ( + {state.populate && state.type === "trackerPrograms" && userHasReadAccess && (
- + {i18n.t("TEI and relationships enrollment by organisation unit type")} + +
+
+
- - )} + )} - {state.populate && state.type === "trackerPrograms" && userHasReadAccess && ( -
-

- {i18n.t("TEI and relationships enrollment by organisation unit type")} -

+ {themeOptions.length > 0 && (