diff --git a/packages/app/i18n/en.pot b/packages/app/i18n/en.pot index 4f9ddbed9..72ba56233 100644 --- a/packages/app/i18n/en.pot +++ b/packages/app/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-09-06T09:01:38.368Z\n" -"PO-Revision-Date: 2024-09-06T09:01:38.368Z\n" +"POT-Creation-Date: 2024-09-07T15:01:43.543Z\n" +"PO-Revision-Date: 2024-09-07T15:01:43.543Z\n" msgid "Could not determine scorecard's access" msgstr "Could not determine scorecard's access" @@ -101,12 +101,12 @@ msgstr "Can Read" msgid "Can Read and Write" msgstr "Can Read and Write" +msgid "Groups" +msgstr "Groups" + msgid "Save" msgstr "Save" -msgid "Cancel" -msgstr "Cancel" - msgid "Click to configure, drag to rearrange" msgstr "Click to configure, drag to rearrange" @@ -146,6 +146,9 @@ msgstr "Organisation Unit Specific Targets" msgid "Other Periods" msgstr "Other Periods" +msgid "Cancel" +msgstr "Cancel" + msgid "Update" msgstr "Update" @@ -182,8 +185,11 @@ msgstr "Organisation Unit Level" msgid "Add Data source" msgstr "Add Data source" -msgid "Please select at least one data source for this group" -msgstr "Please select at least one data source for this group" +msgid "Default" +msgstr "Default" + +msgid "Add group" +msgstr "Add group" msgid "Add Group" msgstr "Add Group" @@ -212,9 +218,6 @@ msgstr "Select a data source to configure" msgid "Search for Indicator" msgstr "Search for Indicator" -msgid "Groups" -msgstr "Groups" - msgid "Name" msgstr "Name" @@ -239,18 +242,14 @@ msgstr "High is Good" msgid "Show Colors" msgstr "Show Colors" -msgid "Title" -msgstr "Title" +msgid "Additional labels" +msgstr "Additional labels" -msgid "Title is required" -msgstr "Title is required" +msgid "Add new label" +msgstr "Add new label" -msgid "" -"A scorecard with the title '{{value}}' already exists. Please select " -"another title" -msgstr "" -"A scorecard with the title '{{value}}' already exists. Please select " -"another title" +msgid "Title" +msgstr "Title" msgid "Subtitle" msgstr "Subtitle" @@ -261,9 +260,6 @@ msgstr "Description" msgid "Custom Header" msgstr "Custom Header" -msgid "Additional Labels (Tags)" -msgstr "Additional Labels (Tags)" - msgid "Legend Definitions" msgstr "Legend Definitions" @@ -360,6 +356,34 @@ msgstr "" "In this page you can set default options for the scorecard view. These " "options affect the scorecard view" +msgid "N/A" +msgstr "N/A" + +msgid "No Data" +msgstr "No Data" + +msgid "Target Reached/ On Track" +msgstr "Target Reached/ On Track" + +msgid "Progress, but more effort required" +msgstr "Progress, but more effort required" + +msgid "Not on track" +msgstr "Not on track" + +msgid "Title is required" +msgstr "Title is required" + +msgid "Title must have at least 4 characters" +msgstr "Title must have at least 4 characters" + +msgid "" +"A scorecard with the title '{{value}}' already exists. Please select " +"another title" +msgstr "" +"A scorecard with the title '{{value}}' already exists. Please select " +"another title" + msgid "Preparing migration..." msgstr "Preparing migration..." diff --git a/packages/app/package.json b/packages/app/package.json index bac61b0f7..183d36db6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -23,6 +23,7 @@ "@types/cypress": "^1.1.3", "@types/node": "^20.14.11", "@types/react": "^18.3.3", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", @@ -50,6 +51,9 @@ "@dhis2/app-service-datastore": "^1.0.0-alpha.2", "@dhis2/multi-calendar-dates": "^1.2.4", "@dhis2/ui": "^9.11.0", + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/modifiers": "^7.0.0", + "@dnd-kit/utilities": "^3.2.2", "@hisptz/dhis2-analytics": "^2.0.20", "@hisptz/dhis2-ui": "^2.0.17", "@hisptz/dhis2-utils": "^2.0.5", @@ -71,7 +75,7 @@ "lerna": "^8.1.8", "lodash": "^4.17.21", "luxon": "^2.0.2", - "react-beautiful-dnd": "^13.1.0", + "react-beautiful-dnd": "^13.1.1", "react-color": "^2.19.3", "react-dnd": "^15.1.1", "react-dnd-html5-backend": "^15.1.2", diff --git a/packages/app/src/locales/en/translations.json b/packages/app/src/locales/en/translations.json index d1f449a55..2a05d62a0 100644 --- a/packages/app/src/locales/en/translations.json +++ b/packages/app/src/locales/en/translations.json @@ -30,8 +30,8 @@ "No Access": "No Access", "Can Read": "Can Read", "Can Read and Write": "Can Read and Write", + "Groups": "Groups", "Save": "Save", - "Cancel": "Cancel", "Click to configure, drag to rearrange": "Click to configure, drag to rearrange", "Click to {{linkAction}}": "Click to {{linkAction}}", "unlink": "unlink", @@ -45,6 +45,7 @@ "Change": "Change", "Organisation Unit Specific Targets": "Organisation Unit Specific Targets", "Other Periods": "Other Periods", + "Cancel": "Cancel", "Update": "Update", "Period Specific Targets": "Period Specific Targets", "Period": "Period", @@ -57,7 +58,8 @@ "Specific target type": "Specific target type", "Organisation Unit Level": "Organisation Unit Level", "Add Data source": "Add Data source", - "Please select at least one data source for this group": "Please select at least one data source for this group", + "Default": "Default", + "Add group": "Add group", "Add Group": "Add Group", "Configure Data Sources": "Configure Data Sources", "Add a data source group": "Add a data source group", @@ -67,7 +69,6 @@ "View the changes on the preview panel above": "View the changes on the preview panel above", "Select a data source to configure": "Select a data source to configure", "Search for Indicator": "Search for Indicator", - "Groups": "Groups", "Name": "Name", "Label": "Label", "Label is required": "Label is required", @@ -76,13 +77,12 @@ "Display Arrows": "Display Arrows", "High is Good": "High is Good", "Show Colors": "Show Colors", + "Additional labels": "Additional labels", + "Add new label": "Add new label", "Title": "Title", - "Title is required": "Title is required", - "A scorecard with the title '{{value}}' already exists. Please select another title": "A scorecard with the title '{{value}}' already exists. Please select another title", "Subtitle": "Subtitle", "Description": "Description", "Custom Header": "Custom Header", - "Additional Labels (Tags)": "Additional Labels (Tags)", "Legend Definitions": "Legend Definitions", "Select": "Select", "Select default period(s)": "Select default period(s)", @@ -109,6 +109,14 @@ "In this page you can configure the default organisation unit and sharing access for the scorecard.": "In this page you can configure the default organisation unit and sharing access for the scorecard.", "Options": "Options", "In this page you can set default options for the scorecard view. These options affect the scorecard view": "In this page you can set default options for the scorecard view. These options affect the scorecard view", + "N/A": "N/A", + "No Data": "No Data", + "Target Reached/ On Track": "Target Reached/ On Track", + "Progress, but more effort required": "Progress, but more effort required", + "Not on track": "Not on track", + "Title is required": "Title is required", + "Title must have at least 4 characters": "Title must have at least 4 characters", + "A scorecard with the title '{{value}}' already exists. Please select another title": "A scorecard with the title '{{value}}' already exists. Please select another title", "Preparing migration...": "Preparing migration...", "Migrating scorecards": "Migrating scorecards", "of": "of", diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/EditTitle.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/EditTitle.tsx deleted file mode 100644 index 90526355f..000000000 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/EditTitle.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import i18n from "@dhis2/d2-i18n"; -import { Button, ButtonStrip, Input } from "@dhis2/ui"; -import PropTypes from "prop-types"; -import React from "react"; - -export default function EditTitle({ - titleEditValue, - setTitleEditValue, - title, - onClose, - onTitleEditSubmit, -}: any) { - return ( -
-
event.stopPropagation()} - className="column" - > - setTitleEditValue(value)} - /> -
-
- - - - -
-
- ); -} - -EditTitle.propTypes = { - title: PropTypes.string.isRequired, - titleEditValue: PropTypes.string.isRequired, - setTitleEditValue: PropTypes.func.isRequired, - onTitleEditSubmit: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, -}; diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/index.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/index.tsx deleted file mode 100644 index 288618658..000000000 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/index.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import i18n from "@dhis2/d2-i18n"; -import { Button, colors, Tooltip } from "@dhis2/ui"; -import { IconAdd24, IconChevronDown24 } from "@dhis2/ui-icons"; -import { ScorecardIndicatorGroup } from "@scorecard/shared"; -import { isEmpty } from "lodash"; -import PropTypes from "prop-types"; -import React, { forwardRef, useRef, useState } from "react"; -import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd"; -import useDataGroupLayout from "../../../../hooks/useDataGroupLayout"; -import useDataGroupManage from "../../../../hooks/useDataGroupManage"; -import useDataHolders from "../../../../hooks/useDataHolders"; -import DataSourceSelectorModal from "../DataSourceSelectorModal"; -import { Accordion, AccordionDetails, AccordionSummary } from "./Components/Accordions"; -import EditTitle from "./Components/EditTitle"; -import GroupTitle from "./Components/GroupTitle"; -import LinkingContainer from "./Components/LinkingContainer"; - -function DataGroup( - { handleAccordionChange, expanded, index, onDelete, error }: any, - ref: any, -) { - const { - onDataSourceDelete, - onDataSourceAdd, - onTitleEditSubmit, - titleEditValue, - titleEditOpen, - setTitleEditOpen, - setTitleEditValue, - group, - } = useDataGroupManage({ index, expanded }); - - const { onLink, onUnlink, onDragEnd, onExpand } = useDataGroupLayout({ - handleAccordionChange, - index, - }); - - const { dataHolderChunks, selectedDataSourcesIds } = useDataHolders({ - index, - }); - - const { title, id, dataHolders } = group ?? new ScorecardIndicatorGroup(); - const [openAdd, setOpenAdd] = useState(false); - const summaryRef = useRef(); - - return ( - - {(provided: any) => ( - event.stopPropagation()} - expandIcon={} - square - expanded={expanded === id} - onChange={onExpand} - > - - event.stopPropagation()} - {...provided.draggableProps} - {...provided.dragHandleProps} - expandIcon={ - - } - aria-controls={`${id}d-content`} - id={`${id}d--header`} - dataTest="scorecard-group-item" - > - {titleEditOpen ? ( - - ) : ( - - )} - - - -
- {isEmpty(dataHolders) ? ( -
-

- {i18n.t("Add a data source")} -

-
- ) : ( - - - {(provided: any) => ( -
- {dataHolderChunks?.map( - (chunk, i) => ( - - ), - )} - {provided.placeholder} -
- )} -
-
- )} -
- -
-
- {openAdd && ( - setOpenAdd(false)} - open={openAdd} - /> - )} -
-
- )} -
- ); -} - -export default forwardRef(DataGroup); - -DataGroup.propTypes = { - index: PropTypes.number.isRequired, - error: PropTypes.object, - expanded: PropTypes.string, - handleAccordionChange: PropTypes.func, - onDelete: PropTypes.func, -}; diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataSourceHolder/index.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataSourceHolder/index.tsx deleted file mode 100644 index 4293ff15f..000000000 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataSourceHolder/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { colors } from "@dhis2/ui"; -import { ScorecardConfigDirtySelector, ScorecardConfigEditState, ScorecardIndicatorHolder } from "@scorecard/shared"; -import PropTypes from "prop-types"; -import React from "react"; -import { Draggable } from "react-beautiful-dnd"; -import { useFormContext } from "react-hook-form"; -import { useRecoilState, useSetRecoilState } from "recoil"; -import DataSource from "../DataSource"; - -export default function DataSourceHolder({ - dataHolder, - id, - index, - onDelete, -}: any) { - const { dataSources } = dataHolder ?? new ScorecardIndicatorHolder(); - const { trigger } = useFormContext(); - const [scorecardEditState, setScorecardEditState] = useRecoilState( - ScorecardConfigEditState, - ); - const path = [ - "dataGroups", - scorecardEditState.selectedGroupIndex, - "dataHolders", - index, - ]; - const updateDataHolder = useSetRecoilState( - ScorecardConfigDirtySelector({ key: "dataSelection", path }), - ); - - const selected = scorecardEditState.selectedDataHolderIndex === index; - const hasLinked = dataSources?.length > 1; - const onDataSourceDelete = (indicatorIndex: any) => { - if (hasLinked) { - const updatedDataSources = [...dataSources]; - updatedDataSources.splice(indicatorIndex, 1); - updateDataHolder((prevState: any) => - ScorecardIndicatorHolder.set( - prevState, - "dataSources", - updatedDataSources, - ), - ); - return; - } - onDelete(index); - }; - return ( - - {(provided: any) => ( -
-
-
{ - if (id) { - if (await trigger()) { - setScorecardEditState( - (prevState: any) => ({ - ...prevState, - selectedDataHolderIndex: index, - }), - ); - } - } - }} - className="column center" - style={{ - border: hasLinked - ? `1px solid ${colors.grey400}` - : undefined, - background: selected - ? `${colors.teal200}` - : undefined, - padding: hasLinked ? 8 : undefined, - marginBottom: 8, - }} - > - {dataSources?.map((dataGroup: any, index: any) => { - return ( -
- -
- ); - })} -
-
-
- )} -
- ); -} - -DataSourceHolder.propTypes = { - dataHolder: PropTypes.array.isRequired, - id: PropTypes.string.isRequired, - index: PropTypes.number.isRequired, - onDelete: PropTypes.func.isRequired, -}; diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/index.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/index.tsx deleted file mode 100644 index 62273e967..000000000 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import i18n from "@dhis2/d2-i18n"; -import { DataSelection, ScorecardConfigEditState } from "@scorecard/shared"; -import { filter, findIndex, isEmpty, last, remove, set } from "lodash"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { Droppable } from "react-beautiful-dnd"; -import { Controller, useFormContext } from "react-hook-form"; -import { useRecoilValue, useResetRecoilState } from "recoil"; -import { SearchedGroupsState } from "../../DataGroupArea"; -import DataGroup from "./Components/DataGroup"; - -export default function DataGroups() { - const { setValue, watch } = useFormContext(); - const dataSelection = watch("dataSelection"); - const searchedGroups = useRecoilValue(SearchedGroupsState); - const updateDataSelection = useCallback( - (updatedDataSelection: any) => { - setValue("dataSelection", updatedDataSelection); - }, - [setValue], - ); - - const resetEditState = useResetRecoilState(ScorecardConfigEditState); - const { dataGroups: groups } = dataSelection || new DataSelection(); - const [expanded, setExpanded] = useState(last(groups)?.id); - - const handleAccordionChange = - (panel: any) => (event: any, newExpanded: any) => { - setExpanded(newExpanded ? panel : false); - }; - - const onDeleteGroup = (id: any) => { - const updatedGroupList = [...groups]; - remove(updatedGroupList, ["id", id]); - updateDataSelection( - DataSelection.set(dataSelection, "dataGroups", updatedGroupList), - ); - resetEditState(); - }; - - const onGroupUpdate = (index: any, newGroupData: any) => { - const updatedGroupList = [...groups]; - set(updatedGroupList, [index], newGroupData); - updateDataSelection( - DataSelection.set(dataSelection, "dataGroups", updatedGroupList), - ); - }; - - const filteredGroups = useMemo(() => { - if (isEmpty(searchedGroups)) { - return groups; - } else { - return filter(groups, ({ id }) => searchedGroups.includes(id)); - } - }, [searchedGroups, groups]); - - useEffect(() => { - setExpanded(last(filteredGroups)?.id); - }, [filteredGroups, filteredGroups.length]); - - return ( - - {(provided: any) => ( -
-
- {filteredGroups?.map((group: any) => { - const groupIndex = findIndex(groups, [ - "id", - group.id, - ]); - return ( - - !isEmpty(value?.dataHolders) || - i18n.t( - "Please select at least one data source for this group", - ), - }, - }} - name={`dataSelection.dataGroups.${groupIndex}`} - render={({ field, fieldState }) => ( - - )} - /> - ); - })} - {provided.placeholder} -
-
- )} -
- ); -} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/DataGroupArea.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/DataGroupArea.tsx index 1e0eba410..3d01ee06a 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/DataGroupArea.tsx +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/DataGroupArea.tsx @@ -1,122 +1,24 @@ import i18n from "@dhis2/d2-i18n"; -import { Button, Input } from "@dhis2/ui"; -import { IconAdd24 } from "@dhis2/ui-icons"; -import { DataSelection, ScorecardConfigEditState, updateListFromDragAndDrop } from "@scorecard/shared"; -import { debounce, find } from "lodash"; -import PropTypes from "prop-types"; -import React, { useRef, useState } from "react"; -import { DragDropContext } from "react-beautiful-dnd"; -import { useFormContext } from "react-hook-form"; -import { atom, useRecoilCallback, useSetRecoilState } from "recoil"; -import DataGroups from "./Components/DataGroups"; +import React from "react"; +import DataGroups from "./components/DataGroups"; +import { SearchDataItem } from "./components/SearchDataItem"; +import { DataItemSearchProvider } from "./states/searchState"; -export const SearchedGroupsState = atom({ - key: "dataSelectionFilterAtom", - default: [], -}); -export default function DataGroupArea({ onGroupAdd }: any) { - const { setValue, watch } = useFormContext(); - const dataSelection = watch("dataSelection"); - const [keyword, setKeyword] = useState(""); - const setSearchedGroups = useSetRecoilState(SearchedGroupsState); - - const onSearch = useRef( - debounce((keyword) => { - setSearchedGroups(() => { - if (keyword) { - return dataSelection?.dataGroups - .filter((dataHolderGroup: any) => { - return find( - dataHolderGroup?.dataHolders, - (dataHolderIndicator) => { - return find( - dataHolderIndicator?.dataSources, - (dataHolderIndicatorSelections) => { - const searchIndex = - `${dataHolderIndicatorSelections.name} ${dataHolderIndicatorSelections.label}`.toLowerCase(); - return searchIndex.match( - new RegExp( - keyword.toLowerCase(), - ), - ); - }, - ); - }, - ); - }) - ?.map(({ id }: any) => id); - } else { - return []; - } - }); - }, 500), - ); - - const onDragEnd = useRecoilCallback( - ({ set }) => - (result) => { - const { destination, source }: any = result ?? {}; - setValue( - "dataSelection", - DataSelection.set(dataSelection, "dataGroups", [ - ...updateListFromDragAndDrop( - dataSelection?.dataGroups, - source?.index, - destination?.index, - ), - ]), - ); - set(ScorecardConfigEditState, (prevState: any) => { - if (prevState.selectedGroupIndex === source?.index) { - return { - ...prevState, - selectedGroupIndex: destination?.index, - }; - } - return prevState; - }); - }, - [dataSelection, setValue], - ); +export default function DataGroupArea() { return ( -
-
- { - setKeyword(value); - if (value !== "" && value !== undefined) { - onSearch.current(value); - } else { - setSearchedGroups([]); - } - }} - placeholder={i18n.t("Search for Indicator")} - /> -
-

{i18n.t("Groups")}

-
- + +
+
+ +
+

{i18n.t("Groups")}

+
- -
-
- +
-
+ ); } -DataGroupArea.propTypes = { - onGroupAdd: PropTypes.func.isRequired, -}; diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/Accordions.ts b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/Accordions.ts similarity index 100% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/Accordions.ts rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/Accordions.ts diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/DataGroupDraggable.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/DataGroupDraggable.tsx new file mode 100644 index 000000000..8bcc3929c --- /dev/null +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/DataGroupDraggable.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from "react"; +import { useDraggable } from "@dnd-kit/core"; + + +export function DataGroupDraggable({ children, id }: { children: ReactNode, id: string }) { + const { setNodeRef} = useDraggable({ + id + }); + + return ( +
+ {children} +
+ ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/DataGroupDroppable.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/DataGroupDroppable.tsx new file mode 100644 index 000000000..a8e2fe867 --- /dev/null +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/DataGroupDroppable.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from "react"; +import { useDroppable } from "@dnd-kit/core"; + + +export function DataGroupDroppable({ children }: { children: ReactNode }) { + const { setNodeRef } = useDroppable({ + id: `data-groups-droppable` + }); + + return ( +
+ {children} +
+ ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/EditTitle.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/EditTitle.tsx new file mode 100644 index 000000000..91230d6d0 --- /dev/null +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/EditTitle.tsx @@ -0,0 +1,28 @@ +import i18n from "@dhis2/d2-i18n"; +import { Button, ButtonStrip } from "@dhis2/ui"; +import React from "react"; +import { RHFTextInputField } from "@hisptz/dhis2-ui"; + +export default function EditTitle({ + index, + onClose + }: { index: number; onClose: () => void }) { + + return ( +
+
event.stopPropagation()} + className="column" + > + +
+
+ + + +
+
+ ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/GroupTitle.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/GroupTitle.tsx similarity index 51% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/GroupTitle.tsx rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/GroupTitle.tsx index 273c9b1de..9af688d07 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/GroupTitle.tsx +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/GroupTitle.tsx @@ -1,18 +1,26 @@ import i18n from "@dhis2/d2-i18n"; import { Button } from "@dhis2/ui"; -import { ErrorIcon } from "@scorecard/shared"; import { IconButton } from "@material-ui/core"; import { IconDelete24, IconEdit24 } from "@dhis2/ui-icons"; -import PropTypes from "prop-types"; import React from "react"; +import { useBoolean } from "usehooks-ts"; +import EditTitle from "./EditTitle"; +import { useWatch } from "react-hook-form"; +import { ScorecardConfig } from "@hisptz/dhis2-analytics"; export default function GroupTitle({ - setTitleEditOpen, - title, - error, - onDelete, - id, -}: any) { + index, + onDelete + }: { index: number; onDelete: (index: number) => void }) { + const { value: editTitleOpen, setTrue: openEditTitle, setFalse: closeEditTitle } = useBoolean(false); + const title = useWatch({ + name: `dataSelection.dataGroups.${index}.title` + }); + + if (editTitleOpen) { + return ; + } + return (
@@ -20,29 +28,29 @@ export default function GroupTitle({

{ event.stopPropagation(); - setTitleEditOpen(true); + openEditTitle(); }} onClick={(event) => event.stopPropagation()} className="accordion-title group-name-area" > {title}

- {error && ( -

- {error?.message} -

- )} + {/*{error && (*/} + {/* */} + {/* {error?.message}*/} + {/*

*/} + {/*)}*/}
{ event.stopPropagation(); - setTitleEditOpen(true); + openEditTitle(); }} size="small" className="accordion-title-edit" @@ -57,28 +65,21 @@ export default function GroupTitle({ onClick={(_: any, event: any) => { event.stopPropagation(); if (onDelete) { - onDelete(id); + onDelete(index); } }} icon={} > {i18n.t("Delete")} - {error && ( -
- -
- )} + {/*{error && (*/} + {/*
*/} + {/* */} + {/*
*/} + {/*)}*/}
); } -GroupTitle.propTypes = { - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - setTitleEditOpen: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, - error: PropTypes.any, -}; diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/InstructionArea.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/InstructionArea.tsx new file mode 100644 index 000000000..8bd5ec310 --- /dev/null +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/InstructionArea.tsx @@ -0,0 +1,25 @@ +import { isEmpty } from "lodash"; +import Instructions from "../../../../Instructions"; +import DataSourceConfiguration from "../../DataSourceConfiguration"; +import React from "react"; +import { useWatch } from "react-hook-form"; +import { ScorecardConfig } from "@hisptz/dhis2-analytics"; + + +export function InstructionArea() { + const groups = useWatch({ + name: "dataSelection.dataGroups" + }); + + return ( +
+ {isEmpty(groups) ? ( +
+ +
+ ) : ( + + )} +
+ ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/LinkingContainer.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/LinkingContainer.tsx similarity index 57% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/LinkingContainer.tsx rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/LinkingContainer.tsx index e7a75c32a..d7d5887e2 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/Components/LinkingContainer.tsx +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/LinkingContainer.tsx @@ -1,42 +1,41 @@ import i18n from "@dhis2/d2-i18n"; import { Tooltip } from "@dhis2/ui"; -import { ScorecardIndicatorHolder } from "@scorecard/shared"; import { IconButton } from "@material-ui/core"; import { IconLink24 } from "@dhis2/ui-icons"; import UnlinkIcon from "@material-ui/icons/LinkOff"; -import PropTypes from "prop-types"; import React from "react"; import useLinkManage from "../../../../../hooks/useLinkManage"; import DataSourceHolder from "../../DataSourceHolder"; +import { ScorecardDataHolder } from "@hisptz/dhis2-analytics"; -export default function LinkingContainer({ - chunk, - onDataSourceDelete, - onLink, - onUnlink, - dataHolders, -}: any) { - const { linkable, hasLink, onIconClick, getIndex, onUnlinkClick } = - useLinkManage({ onLink, onUnlink, dataHolders, chunk }); +export function LinkingContainer({ + chunk, + onDelete, + onLink, + onUnlink, + groupIndex + }: { groupIndex: number; chunk: Array; onDelete: (index: number) => void, onLink: (index1: number, index2: number) => void, onUnlink: (index: number) => void }) { + const { linkable, hasLink, onIconClick, onUnlinkClick } = + useLinkManage({ onLink, onUnlink, chunk, groupIndex }); return (
- {chunk?.map((source: any) => ( + {chunk?.map((source: ScorecardDataHolder, index) => ( ))} @@ -46,7 +45,7 @@ export default function LinkingContainer({ content={i18n.t("Click to {{linkAction}}", { linkAction: hasLink ? i18n.t("unlink") - : i18n.t("link"), + : i18n.t("link") })} > ); } - -LinkingContainer.propTypes = { - chunk: PropTypes.array.isRequired, - dataHolders: PropTypes.arrayOf( - PropTypes.instanceOf(ScorecardIndicatorHolder), - ).isRequired, - onDataSourceDelete: PropTypes.func.isRequired, - onLink: PropTypes.func.isRequired, - onUnlink: PropTypes.func.isRequired, -}; diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/PreviewArea.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/PreviewArea.tsx new file mode 100644 index 000000000..7a75b8236 --- /dev/null +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/components/PreviewArea.tsx @@ -0,0 +1,24 @@ +import PreviewScorecardTable from "../../../../PreviewScorecardTable"; +import React from "react"; +import { useWatch } from "react-hook-form"; +import { ScorecardConfig } from "@hisptz/dhis2-analytics"; +import { isEmpty } from "lodash"; + + +export function PreviewArea() { + const groups = useWatch({ + name: "dataSelection.dataGroups" + }); + + if (isEmpty(groups)) { + return null; + } + + return ( +
+
+ +
+
+ ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/index.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/index.tsx new file mode 100644 index 000000000..a0b76082c --- /dev/null +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/index.tsx @@ -0,0 +1,127 @@ +import i18n from "@dhis2/d2-i18n"; +import { Button, colors, Tooltip } from "@dhis2/ui"; +import { IconAdd24, IconChevronDown24 } from "@dhis2/ui-icons"; +import React, { useRef, useState } from "react"; +import useDataGroupLayout from "../../../../hooks/useDataGroupLayout"; +import useDataGroupManage from "../../../../hooks/useDataGroupManage"; +import { Accordion, AccordionDetails, AccordionSummary } from "./components/Accordions"; +import GroupTitle from "./components/GroupTitle"; +import { DataGroupDraggable } from "./components/DataGroupDraggable"; +import { isEmpty } from "lodash"; +import { LinkingContainer } from "./components/LinkingContainer"; +import { DragDropContext, Droppable } from "react-beautiful-dnd"; + +export function DataGroup( + { index, onRemove }: { index: number, onRemove: (index: number) => void } +) { + const { + onDataSourceDelete, + onDataSourceAdd, + group + } = useDataGroupManage({ index }); + + const { dataHolderChunks, expanded, toggleExpansion, remove, onLink, onUnlink, onDragEnd } = useDataGroupLayout({ index }); + + const { id, dataHolders } = group; + const [openAdd, setOpenAdd] = useState(false); + const summaryRef = useRef(); + + + return ( + + event.stopPropagation()} + square + expanded={expanded} + onChange={toggleExpansion} + > + + event.stopPropagation()} + expandIcon={ + + } + aria-controls={`${id}d-content`} + id={`${id}d--header`} + > + + + + +
+ {isEmpty(dataHolders) ? ( +
+

+ {i18n.t("Add a data source")} +

+
+ ) : ( + + + {(provided: any) => ( +
+ {dataHolderChunks?.map( + (chunk, i) => ( + + ) + )} + {provided.placeholder} +
+ )} +
+
+ )} +
+ +
+
+ {/*{openAdd && (*/} + {/* setOpenAdd(false)}*/} + {/* open={openAdd}*/} + {/* />*/} + {/*)}*/} +
+
+
+ ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/utils.ts b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/utils.ts similarity index 66% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/utils.ts rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/utils.ts index 02f304f3d..7e8bc5d17 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataGroup/utils.ts +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataGroup/utils.ts @@ -1,18 +1,20 @@ +import { ScorecardDataHolder } from "@hisptz/dhis2-analytics"; + export function getChunkChildIndex( chunkSize = 2, chunkIndex: any, - childIndex: any, + childIndex: any ) { return chunkSize * chunkIndex + childIndex; } -export function customChunk(list = []) { +export function customChunk(list: ScorecardDataHolder[],) { const chunks = []; - const listToModify: any = [...list]; + const listToModify: ScorecardDataHolder[] = [...list]; const i = 0; while (listToModify.length > 0) { - if (listToModify[i]?.dataSources > 1) { + if (listToModify[i]?.dataSources.length > 1) { chunks.push(listToModify.splice(i, 1)); continue; } diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataSource/index.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataSource/index.tsx similarity index 67% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataSource/index.tsx rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataSource/index.tsx index 25be4a85a..5f73c835f 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/DataGroups/Components/DataSource/index.tsx +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/DataGroups/components/DataSource/index.tsx @@ -1,13 +1,13 @@ import i18n from "@dhis2/d2-i18n"; import { Button } from "@dhis2/ui"; -import { getDataSourceShortName, ScorecardIndicator } from "@scorecard/shared"; +import { getDataSourceShortName } from "@scorecard/shared"; import { Avatar } from "@material-ui/core"; import { IconDelete16 } from "@dhis2/ui-icons"; -import PropTypes from "prop-types"; import React from "react"; +import { ScorecardDataSource } from "@hisptz/dhis2-analytics"; -export default function DataSource({ dataSource, index, onDelete }: any) { - const { label, type } = dataSource ?? new ScorecardIndicator(); +export default function DataSource({ dataSource, index, onDelete }: { dataSource: ScorecardDataSource, index: number, onDelete: (index: number) => void }) { + const { label, type } = dataSource; return (
@@ -16,7 +16,7 @@ export default function DataSource({ dataSource, index, onDelete }: any) { {getDataSourceShortName(type)} -

{label}

+

{label}

+
+
+ + + ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/EmptyDataGroups.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/EmptyDataGroups.tsx similarity index 100% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/EmptyDataGroups.tsx rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/EmptyDataGroups.tsx diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/Instructions.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/Instructions.tsx similarity index 100% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/Instructions.tsx rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/Instructions.tsx diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/PreviewScorecardTable/Components/CustomLinkedCell.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/PreviewScorecardTable/Components/CustomLinkedCell.tsx similarity index 100% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/PreviewScorecardTable/Components/CustomLinkedCell.tsx rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/PreviewScorecardTable/Components/CustomLinkedCell.tsx diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/PreviewScorecardTable/Components/PreviewCustomCell.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/PreviewScorecardTable/Components/PreviewCustomCell.tsx similarity index 100% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/PreviewScorecardTable/Components/PreviewCustomCell.tsx rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/PreviewScorecardTable/Components/PreviewCustomCell.tsx diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/PreviewScorecardTable/index.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/PreviewScorecardTable/index.tsx similarity index 100% rename from packages/app/src/modules/ScorecardManagement/components/DataConfiguration/Components/PreviewScorecardTable/index.tsx rename to packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/PreviewScorecardTable/index.tsx diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/SearchDataItem.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/SearchDataItem.tsx new file mode 100644 index 000000000..49492e837 --- /dev/null +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/components/SearchDataItem.tsx @@ -0,0 +1,56 @@ +import i18n from "@dhis2/d2-i18n"; +import { Input } from "@dhis2/ui"; +import React, { useRef, useState } from "react"; +import { debounce, isEmpty } from "lodash"; +import { ScorecardConfig, ScorecardDataGroup } from "@hisptz/dhis2-analytics"; +import { useWatch } from "react-hook-form"; +import { useDataItemSearchState } from "../states/searchState"; + + +export function SearchDataItem() { + const [keyword, setKeyword] = useState(); + const dataGroups = useWatch({ + name: "dataSelection.dataGroups" + }); + const [, setFilteredDataItems] = useDataItemSearchState(); + + const searchGroups = (keyword: string): string[] => { + if (keyword) { + return (dataGroups as ScorecardDataGroup[])?.filter((dataGroup) => { + return dataGroup.dataHolders.find((dataHolder) => { + return !!dataHolder.dataSources.find((dataSource) => { + const searchIndex = `${dataSource.id} ${dataSource.label} ${dataSource.description}`.toLowerCase(); + return searchIndex.match(RegExp(keyword.toLowerCase())); + }); + }); + }) + ?.map(({ id }: any) => id); + } else { + return []; + } + }; + + const onSearch = useRef( + debounce((keyword) => { + if (!isEmpty(keyword)) { + const filteredGroups = searchGroups(keyword); + setFilteredDataItems(filteredGroups); + } else { + + } + }, 500) + ); + + + return ( + { + setKeyword(value); + onSearch.current(value); + }} + placeholder={i18n.t("Search for Indicator")} + /> + ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useDataGroupLayout.ts b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useDataGroupLayout.ts index 4d6268bff..162559c6e 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useDataGroupLayout.ts +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useDataGroupLayout.ts @@ -1,134 +1,76 @@ -import { ScorecardConfigEditState, ScorecardIndicatorGroup, ScorecardIndicatorHolder, updateListFromDragAndDrop } from "@scorecard/shared"; -import { cloneDeep, find, findIndex, head, last, set } from "lodash"; +import { useBoolean } from "usehooks-ts"; +import { useFieldArray, useFormContext } from "react-hook-form"; +import { ScorecardConfig } from "@hisptz/dhis2-analytics"; import { useCallback, useMemo } from "react"; -import { useFormContext } from "react-hook-form"; -import { useRecoilCallback } from "recoil"; +import { customChunk } from "../components/DataGroups/components/DataGroup/utils"; +import { DropResult } from "react-beautiful-dnd"; +import { head } from "lodash"; +import { uid } from "@hisptz/dhis2-utils"; export default function useDataGroupLayout({ - handleAccordionChange, - index, -}: any) { - const { watch, setValue } = useFormContext(); - const path = useMemo( - () => ["dataSelection", "dataGroups", index].join("."), - [index], - ); - - const group = watch(path); - const { id, dataHolders } = group ?? new ScorecardIndicatorGroup(); - const setGroup = useCallback( - (updatedGroup: any) => { - setValue(path, updatedGroup); - }, - [path, setValue], - ); - - const onLink = useRecoilCallback( - ({ snapshot, set }) => - (indexOfMergedHolder: any, indexOfTheDeletedHolder: any) => { - const dataSourceToLink = head( - dataHolders[indexOfTheDeletedHolder]?.dataSources, - ); - const mergedHolder = ScorecardIndicatorHolder.linkDataSource( - dataHolders[indexOfMergedHolder], - dataSourceToLink, - ); - const updatedHolderList = [...dataHolders]; - updatedHolderList.splice(indexOfMergedHolder, 1, mergedHolder); - updatedHolderList.splice(indexOfTheDeletedHolder, 1); - - const selectedDataHolderIndex = snapshot.getLoadable( - ScorecardConfigEditState, - ).contents?.selectedDataHolderIndex; + index + }: { index: number }) { + const { value: expanded, toggle: toggleExpansion } = useBoolean(false); + // @ts-ignore + const { fields, remove, insert } = useFieldArray({ + name: `dataSelection.dataGroups.${index}.dataHolders` + }); + const { getValues, setValue } = useFormContext(); - if ( - selectedDataHolderIndex === indexOfMergedHolder || - selectedDataHolderIndex === indexOfTheDeletedHolder - ) { - set(ScorecardConfigEditState, (prevState: any) => { - return { - ...prevState, - selectedDataHolderIndex: indexOfMergedHolder, - }; - }); - } + const dataHolderChunks = useMemo(() => { + const dataHolders = getValues(`dataSelection.dataGroups.${index}.dataHolders`); + return customChunk(dataHolders); + }, [fields]); - setGroup( - ScorecardIndicatorGroup.set( - group, - "dataHolders", - updatedHolderList, - ), - ); - }, - [dataHolders, group, setGroup], - ); + const onDragEnd = useCallback((result: DropResult) => { + const { source, destination } = result ?? {}; - const onUnlink = useRecoilCallback(({ set: setState }) => (id) => { - //get the linked holder by id - const dataHolder = find(dataHolders, ["id", id]); - const dataHolderIndex = findIndex(dataHolders, ["id", id]); - //create a new holder for the last dataSource - const newDataHolder = new ScorecardIndicatorHolder({ - dataSources: [last(dataHolder?.dataSources)], - }); - const dataHolderToModify = cloneDeep(dataHolder); - const modifiedDataHolder = ScorecardIndicatorHolder.set( - dataHolderToModify, - "dataSources", - dataHolderToModify?.dataSources?.splice(0, 1), - ); - const updatedHolderList = [...dataHolders]; - set(updatedHolderList, [dataHolderIndex], modifiedDataHolder); - updatedHolderList.splice(dataHolderIndex, 0, newDataHolder); - setGroup( - ScorecardIndicatorGroup.set( - group, - "dataHolders", - updatedHolderList, - ), - ); + }, []); - setState(ScorecardConfigEditState, (prevState: any) => { - return { - ...prevState, - selectedDataHolderIndex: undefined, - }; + const onLink = (index1: number, index2: number) => { + //We need to set the first data source of the second index to the second data source of the first and remove the second data holder + const dataHolder = getValues(`dataSelection.dataGroups.${index}.dataHolders.${index1}`); + const secondDataHolder = getValues(`dataSelection.dataGroups.${index}.dataHolders.${index2}`); + setValue(`dataSelection.dataGroups.${index}.dataHolders.${index1}`, { + ...dataHolder, + dataSources: [ + ...dataHolder.dataSources, + head(secondDataHolder.dataSources)! + ] }); - }); + remove(index2); + }; - const onDragEnd = useRecoilCallback(({ set }) => (result) => { - const { destination, source }: any = result || {}; - setGroup( - ScorecardIndicatorGroup.set( - group, - "dataHolders", - updateListFromDragAndDrop( - group?.dataHolders, - source?.index, - destination?.index, - ), - ), - ); - set(ScorecardConfigEditState, (prevState: any) => { - if (prevState.selectedDataHolderIndex === source?.index) { - return { - ...prevState, - selectedDataHolderIndex: destination?.index, - }; - } - return prevState; - }); - }); + const onUnlink = (index: number) => { + //Create a new data holder and send the second data item of the split data holder to the new data holder + const dataHolder = getValues(`dataSelection.dataGroups.${index}.dataHolders.${index}`); + if (dataHolder.dataSources.length > 1) { + const [first, second] = dataHolder.dataSources; + setValue(`dataSelection.dataGroups.${index}.dataHolders.${index}`, { + ...dataHolder, + dataSources: [ + first + ] + }); + //We insert the new data holder right under the existing one + insert(index + 1, { + id: uid(), + dataSources: [ + second + ] + }); + } - const onExpand = (event: any, newExpanded: any) => { - handleAccordionChange(id)(event, newExpanded); }; return { + toggleExpansion, onLink, onUnlink, onDragEnd, - onExpand, + expanded, + dataHolders: fields, + dataHolderChunks, + remove }; } diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useDataHolders.ts b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useDataHolders.ts index 926220c72..e69de29bb 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useDataHolders.ts +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useDataHolders.ts @@ -1,24 +0,0 @@ -import { ScorecardIndicatorGroup } from "@scorecard/shared"; -import { flattenDeep } from "lodash"; -import { useMemo } from "react"; -import { useFormContext } from "react-hook-form"; -import { customChunk } from "../Components/DataGroups/Components/DataGroup/utils"; - -export default function useDataHolders({ index }: any) { - const { watch } = useFormContext(); - const path = useMemo( - () => ["dataSelection", "dataGroups", index].join("."), - [index], - ); - const group = watch(path); - const { dataHolders } = group ?? new ScorecardIndicatorGroup(); - const dataHolderChunks = customChunk(dataHolders); - const selectedDataSourcesIds = flattenDeep( - dataHolders?.map(({ dataSources }: any) => dataSources), - )?.map(({ id }: any) => id); - - return { - dataHolderChunks, - selectedDataSourcesIds, - }; -} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useLinkManage.ts b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useLinkManage.ts index 519061b9a..0c389b6ec 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useLinkManage.ts +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/hooks/useLinkManage.ts @@ -1,29 +1,33 @@ -import { findIndex, head, last } from "lodash"; -import { useCallback } from "react"; +import { head, last } from "lodash"; +import { useFormContext } from "react-hook-form"; +import { ScorecardConfig, ScorecardDataHolder } from "@hisptz/dhis2-analytics"; export default function useLinkManage({ - onLink, - onUnlink, - dataHolders, - chunk, -}: any) { + groupIndex, + onLink, + onUnlink, + chunk + }: { groupIndex: number; chunk: ScorecardDataHolder[], onLink: (index1: number, index2: number) => void, onUnlink: (index1: number) => void }) { const linkable = chunk.length > 1; - const hasLink = head(chunk)?.dataSources?.length > 1; + const { getValues } = useFormContext(); + const hasLink = head(chunk)!.dataSources?.length > 1; - const getIndex = useCallback( - (id: any) => { - return findIndex(dataHolders, ["id", id]); - }, - [dataHolders], - ); const onLinkClick = () => { - const indexOfMergedHolder = getIndex(head(chunk)?.id); - const indexOfDeletedHolder = getIndex(last(chunk)?.id); - onLink(indexOfMergedHolder, indexOfDeletedHolder); + const dataHolders = getValues(`dataSelection.dataGroups.${groupIndex}.dataHolders`) ?? []; + const firstHolderIndex = dataHolders.findIndex((holder) => holder.id === head(chunk)!.id); + const secondHolderIndex = dataHolders.findIndex((holder) => holder.id === last(chunk)!.id); + + if (chunk.length > 1) { + onLink(firstHolderIndex, secondHolderIndex); + } }; const onUnlinkClick = () => { - onUnlink(head(chunk).id); + if (hasLink) { + const dataHolders = getValues(`dataSelection.dataGroups.${groupIndex}.dataHolders`) ?? []; + const holderIndex = dataHolders.findIndex((holder) => holder.id === head(chunk)!.id); + onUnlink(holderIndex); + } }; const onIconClick = () => { @@ -35,7 +39,6 @@ export default function useLinkManage({ hasLink, onIconClick, onLinkClick, - onUnlinkClick, - getIndex, + onUnlinkClick }; } diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/index.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/index.tsx index 4a7fe34fc..f76e3bde0 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/index.tsx +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/index.tsx @@ -1,73 +1,26 @@ -import { ContainerLoader, DataSelection, Help } from "@scorecard/shared"; -import { isEmpty } from "lodash"; -import React, { Suspense, useCallback } from "react"; -import { useFormContext } from "react-hook-form"; -import DataSourceConfiguration from "./Components/DataGroups/Components/DataSourceConfiguration"; -import EmptyDataGroups from "./Components/EmptyDataGroups"; -import Instructions from "./Components/Instructions"; -import PreviewScorecardTable from "./Components/PreviewScorecardTable"; +import { ContainerLoader } from "@scorecard/shared"; +import React, { Suspense } from "react"; import DataGroupArea from "./DataGroupArea"; -import useHelp from "./hooks/useHelp"; -import { generateNewGroupData } from "./utils"; +import { PreviewArea } from "./components/DataGroups/components/DataGroup/components/PreviewArea"; +import { InstructionArea } from "./components/DataGroups/components/DataGroup/components/InstructionArea"; export default function DataConfigurationScorecardForm() { - const { setValue, watch, register } = useFormContext(); - register("dataSelection"); - const dataSelection = watch("dataSelection"); - - const updateDataSelection = useCallback( - (updatedDataSelection: any) => { - setValue("dataSelection", updatedDataSelection); - }, - [setValue] - ); - - const { dataGroups: groups } = dataSelection ?? new DataSelection(); - const helpSteps = useHelp(groups); - - const onGroupAdd = useCallback(() => { - updateDataSelection( - DataSelection.set(dataSelection, "dataGroups", [ - ...(dataSelection?.dataGroups ?? []), - generateNewGroupData(groups) - ]) - ); - }, [dataSelection, groups, updateDataSelection]); return (
-
}> - {isEmpty(groups) ? ( - - ) : ( - - )} +
- {!isEmpty(groups) && ( -
-
- -
-
- )} -
- {isEmpty(groups) ? ( -
- -
- ) : ( - - )} -
+ +
); diff --git a/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/states/searchState.tsx b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/states/searchState.tsx new file mode 100644 index 000000000..191e0e570 --- /dev/null +++ b/packages/app/src/modules/ScorecardManagement/components/DataConfiguration/states/searchState.tsx @@ -0,0 +1,29 @@ +import { createContext, Dispatch, ReactNode, SetStateAction, useContext, useState } from "react"; + + +const DataItemSearchState = createContext([]); +const SetDataItemSearchState = createContext> | null>(null); + +export function useDataItemSearchState() { + const dataItemSearchState = useContext(DataItemSearchState); + const setDataItemSearchState = useContext(SetDataItemSearchState); + if (!dataItemSearchState || !setDataItemSearchState) { + throw new Error(`useDataItemSearchState must be used in a DataItemSearchProvider`); + } + return [ + dataItemSearchState, + setDataItemSearchState + ] as const; +} + +export function DataItemSearchProvider({ children }: { children: ReactNode }) { + const [dataItemSearch, setDataItemSearch] = useState([]); + + return ( + + + {children} + + + ); +} diff --git a/packages/app/src/modules/ScorecardManagement/components/DataSourceConfigurationForm/index.tsx b/packages/app/src/modules/ScorecardManagement/components/DataSourceConfigurationForm/index.tsx index 9fbd262c8..8b0b4ca63 100644 --- a/packages/app/src/modules/ScorecardManagement/components/DataSourceConfigurationForm/index.tsx +++ b/packages/app/src/modules/ScorecardManagement/components/DataSourceConfigurationForm/index.tsx @@ -5,7 +5,7 @@ import { DHIS2ValueTypes } from "@scorecard/shared"; import PropTypes from "prop-types"; import React, { useEffect } from "react"; import { useFormContext } from "react-hook-form"; -import TargetsArea from "../DataConfiguration/Components/DataGroups/Components/DataSourceConfiguration/Components/TargetsArea"; +import TargetsArea from "../DataConfiguration/components/DataGroups/components/DataSourceConfiguration/Components/TargetsArea"; export default function DataSourceConfigurationForm({ path }: any) { const { watch, setValue } = useFormContext(); diff --git a/packages/app/src/modules/ScorecardManagement/components/General/components/PeriodSelector.tsx b/packages/app/src/modules/ScorecardManagement/components/General/components/PeriodSelector.tsx index 96a9a83a6..11d28e2ec 100644 --- a/packages/app/src/modules/ScorecardManagement/components/General/components/PeriodSelector.tsx +++ b/packages/app/src/modules/ScorecardManagement/components/General/components/PeriodSelector.tsx @@ -17,7 +17,6 @@ export function PeriodSelector() { name: "periodSelection.type" }); - console.log({ periodTypeId }); const { field, fieldState } = useController({ name: "periodSelection.periods" }); @@ -52,8 +51,6 @@ export function PeriodSelector() { ], "id").filter((periodType) => periodType.id != periodTypeId).map((periodType) => periodType.id); }, [periodTypeId]); - console.log(filteredPeriodTypes); - return ( <>