From 71ce774b8f8309ab80d69b708b9057722a53e5a7 Mon Sep 17 00:00:00 2001 From: CynthiaKamau Date: Fri, 27 Sep 2024 15:48:12 +0300 Subject: [PATCH 1/2] O3-4002 Add implementation for configuring dashboard Encounter List Table component --- .../add-columns-modal.component.tsx | 243 ++++++++++++++++++ .../configure-dashboard-modal.component.tsx | 197 ++++++++++++++ .../interactive-builder.component.tsx | 84 +++++- .../interactive-builder.scss | 10 + .../interactive-builder/modals.scss | 9 + .../view-editor/view-editor.component.tsx | 4 +- src/helpers.ts | 7 + src/hooks/useEncounter.tsx | 15 ++ src/hooks/useForm.tsx | 19 ++ src/hooks/useFormConcepts.tsx | 52 ++++ src/index.ts | 10 + src/routes.json | 8 + src/types.ts | 156 ++++++++++- translations/am.json | 25 ++ translations/en.json | 25 ++ translations/es.json | 25 ++ translations/fr.json | 25 ++ translations/he.json | 25 ++ translations/km.json | 25 ++ 19 files changed, 947 insertions(+), 17 deletions(-) create mode 100644 src/components/interactive-builder/add-columns-modal.component.tsx create mode 100644 src/components/interactive-builder/configure-dashboard-modal.component.tsx create mode 100644 src/helpers.ts create mode 100644 src/hooks/useEncounter.tsx create mode 100644 src/hooks/useForm.tsx create mode 100644 src/hooks/useFormConcepts.tsx diff --git a/src/components/interactive-builder/add-columns-modal.component.tsx b/src/components/interactive-builder/add-columns-modal.component.tsx new file mode 100644 index 0000000..61085ac --- /dev/null +++ b/src/components/interactive-builder/add-columns-modal.component.tsx @@ -0,0 +1,243 @@ +import React, { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Button, + Form, + FormGroup, + ModalBody, + ModalFooter, + ModalHeader, + Stack, + TextInput, + Dropdown, + RadioButton, + RadioButtonGroup, + Select, + SelectItem, +} from '@carbon/react'; +import { isDesktop, showSnackbar, useLayoutType } from '@openmrs/esm-framework'; +import { + type Schema, + type Form as FormType, + type TabDefinition, + type ExtensionSlot, + type DashboardConfig, +} from '../../types'; +import styles from './modals.scss'; +import { useForms } from '../../hooks/useForm'; +import { useFormConcepts } from '../../hooks/useFormConcepts'; +import { generateNodeId } from '../../helpers'; + +interface ConfigureDashboardModalProps { + schema: Schema; + onSchemaChange: (schema: Schema) => void; + closeModal: () => void; + slotDetails: DashboardConfig; + tabDefinition: TabDefinition; +} + +const ConfigureDashboardModal: React.FC = ({ + schema, + onSchemaChange, + closeModal, + slotDetails, + tabDefinition, +}) => { + const { t } = useTranslation(); + const desktopLayout = isDesktop(useLayoutType()); + const [columnTitle, setColumnTitle] = useState(''); + const [columnConcept, setColumnConcept] = useState(''); + const [isColumnDate, setIsColumnDate] = useState(false); + const [isColumnLink, setIsColumnLink] = useState(false); + const [selectedForm, setSelectedForm] = useState(); + const { isLoadingForm, forms, formsError } = useForms(); + const { isLoadingFormConcepts, formConcepts, formConceptsError, mutate } = useFormConcepts(selectedForm); + const schemaBasePath = schema['@openmrs/esm-patient-chart-app']; + + const updateSchema = (newColumns) => { + try { + const updatedSchema = { + ...schema, + '@openmrs/esm-patient-chart-app': { + ...schemaBasePath, + extensionSlots: { + ...schemaBasePath.extensionSlots, + [slotDetails?.slot]: { + ...schemaBasePath.extensionSlots[slotDetails?.slot].add, + configure: { + ...schemaBasePath.extensionSlots[slotDetails?.slot]?.configure, + [slotDetails?.slot]: { + ...schemaBasePath.extensionSlots[slotDetails?.slot]?.configure[slotDetails?.slot], + tabDefinitions: schemaBasePath.extensionSlots[slotDetails?.slot]?.configure[ + slotDetails?.slot + ]?.tabDefinitions.map((tabDef) => { + if (tabDef.tabName === tabDefinition.tabName) { + // Match the tab definition you want to update + return { + ...tabDef, + columns: [ + ...(tabDef.columns || []), // Existing columns or empty array + newColumns, // Add newColumns + ], + }; + } + return tabDef; // Return unchanged tab definitions + }), + }, + }, + } as ExtensionSlot, + }, + }, + }; + onSchemaChange(updatedSchema); + + // Show success notification + setColumnTitle(''); + setColumnConcept(''); + setIsColumnDate(false); + setIsColumnLink(false); + showSnackbar({ + title: t('success', 'Success!'), + kind: 'success', + isLowContrast: true, + subtitle: t('tabColumnsCreated', 'Tab columns created'), + }); + } catch (error) { + if (error instanceof Error) { + showSnackbar({ + title: t('errorCreatingTabColumns', 'Error creating tab columns'), + kind: 'error', + subtitle: error.message, + }); + } + } + }; + + const handleCreateColumn = () => { + const newColumns = { + id: generateNodeId(columnTitle), + title: columnTitle, + concept: columnConcept, + isDate: isColumnDate, + isLink: isColumnLink, + }; + + updateSchema(newColumns); + closeModal(); + }; + + const handleFormChange = useCallback(({ selectedItem }) => { + setSelectedForm(selectedItem); + }, []); + + return ( + <> + +
event.preventDefault()}> + + + + item.display} + onChange={handleFormChange} + size={desktopLayout ? 'md' : 'lg'} + className={styles.label} + /> + + ) => setColumnTitle(event.target.value)} + className={styles.label} + /> + setIsColumnDate(event.toString())} + > + + + + + setIsColumnLink(event.toString())} + > + + + + + + +
+ + + + + + ); +}; + +export default ConfigureDashboardModal; diff --git a/src/components/interactive-builder/configure-dashboard-modal.component.tsx b/src/components/interactive-builder/configure-dashboard-modal.component.tsx new file mode 100644 index 0000000..4e6b62b --- /dev/null +++ b/src/components/interactive-builder/configure-dashboard-modal.component.tsx @@ -0,0 +1,197 @@ +import React, { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Button, + Form, + FormGroup, + ModalBody, + ModalFooter, + ModalHeader, + Stack, + TextInput, + Select, + SelectItem, +} from '@carbon/react'; +import { isDesktop, showSnackbar, useLayoutType } from '@openmrs/esm-framework'; +import { type Schema, type Form as FormType, type ExtensionSlot } from '../../types'; +import styles from './modals.scss'; +import { useEncounterTypes } from '../../hooks/useEncounter'; + +interface ConfigureDashboardModalProps { + schema: Schema; + onSchemaChange: (schema: Schema) => void; + closeModal: () => void; + slotName: string; +} + +const ConfigureDashboardModal: React.FC = ({ + schema, + onSchemaChange, + closeModal, + slotName, +}) => { + const { t } = useTranslation(); + const [selectedWidget, setSelectedWidget] = useState(''); + const [tabName, setTabName] = useState(''); + const [displayTitle, setDisplayTitle] = useState(''); + const [encounterType, setEncounterType] = useState(''); + const desktopLayout = isDesktop(useLayoutType()); + const { isLoading, encounterTypes, encounterTypesError } = useEncounterTypes(); + + const availableWidgets = [ + { uuid: 'encounter-list-table-tabs', value: 'Encounter list table' }, + { uuid: 'program-summary', value: 'Encounter tile' }, + ]; + + const newExtensionSlot = { + [slotName]: { + add: [slotName], + configure: { + [slotName]: { + title: tabName, + slotName: slotName, + isExpanded: true, + tabDefinitions: [ + { + tabName: tabName, + headerTitle: tabName, + displayText: displayTitle, + encounterType: encounterType, + columns: [], + }, + ], + }, + }, + } as ExtensionSlot, + }; + const updateSchema = () => { + try { + const updatedSchema = { + ...schema, + '@openmrs/esm-patient-chart-app': { + ...schema['@openmrs/esm-patient-chart-app'], + extensionSlots: { + ...schema['@openmrs/esm-patient-chart-app'].extensionSlots, + ...newExtensionSlot, + }, + }, + }; + + onSchemaChange(updatedSchema); + + // Show success notification + showSnackbar({ + title: t('success', 'Success!'), + kind: 'success', + isLowContrast: true, + subtitle: t('submenuCreated', 'New submenu created'), + }); + } catch (error) { + if (error instanceof Error) { + showSnackbar({ + title: t('errorCreatingSubmenu', 'Error creating submenu'), + kind: 'error', + subtitle: error.message, + }); + } + } + }; + + const handleCreateWidget = () => { + updateSchema(); + closeModal(); + }; + + return ( + <> + +
event.preventDefault()}> + + + + + + + + {selectedWidget === 'encounter-list-table-tabs' && ( + + ) => setTabName(event.target.value)} + className={styles.label} + /> + ) => setDisplayTitle(event.target.value)} + className={styles.label} + /> + + + )} + + +
+ + + + + + ); +}; + +export default ConfigureDashboardModal; diff --git a/src/components/interactive-builder/interactive-builder.component.tsx b/src/components/interactive-builder/interactive-builder.component.tsx index 2b98552..1196c26 100644 --- a/src/components/interactive-builder/interactive-builder.component.tsx +++ b/src/components/interactive-builder/interactive-builder.component.tsx @@ -1,9 +1,8 @@ -import React, { useCallback } from 'react'; -import { showModal } from '@openmrs/esm-framework'; +import React, { useCallback, useState } from 'react'; +import { showModal, useLayoutType, AddIcon, EditIcon } from '@openmrs/esm-framework'; import { useTranslation } from 'react-i18next'; import { v4 as uuidv4 } from 'uuid'; -import { Button, Accordion, AccordionItem } from '@carbon/react'; -import { Add } from '@carbon/react/icons'; +import { Button, Accordion, AccordionItem, Tile } from '@carbon/react'; import { type DynamicExtensionSlot, type Schema } from '../../types'; import styles from './interactive-builder.scss'; @@ -53,6 +52,31 @@ const InteractiveBuilder = ({ schema, onSchemaChange }: InteractiveBuilderProps) }); }, [schema, onSchemaChange]); + const handleConfigureDashboardModal = useCallback( + (slotName) => { + const dispose = showModal('configure-dashboard-modal', { + closeModal: () => dispose(), + schema, + onSchemaChange, + slotName, + }); + }, + [schema, onSchemaChange], + ); + + const handleConfigureColumnsModal = useCallback( + (slotDetails, tabDefinition) => { + const dispose = showModal('configure-columns-modal', { + closeModal: () => dispose(), + schema, + onSchemaChange, + slotDetails, + tabDefinition, + }); + }, + [schema, onSchemaChange], + ); + const getNavGroupTitle = (schema) => { if (schema) { const navGroupKey = Object.keys( @@ -108,17 +132,63 @@ const InteractiveBuilder = ({ schema, onSchemaChange }: InteractiveBuilderProps) {submenuConfig ? ( submenuConfig.add.map((submenuKey) => { const submenuDetails = submenuConfig.configure[submenuKey]; + const subMenuSlot = submenuDetails?.slot; + const getSubMenuSlotDetails = (schema, subMenuSlot) => { + const patientChartApp = schema['@openmrs/esm-patient-chart-app']; + if (patientChartApp && patientChartApp.extensionSlots) { + return patientChartApp.extensionSlots[subMenuSlot]; + } + return null; + }; + const subMenuSlotDetails = getSubMenuSlotDetails(schema, subMenuSlot); return ( -

Menu Slot: {submenuDetails?.slot}

+

+ {t('menuSlot', 'Menu Slot')} : {submenuDetails?.slot} +

{t( 'helperTextForAddDasboards', 'Now configure dashboards to show on the patient chart when this submenu is clicked.', )}

- + + ))} +
@@ -133,7 +203,7 @@ const InteractiveBuilder = ({ schema, onSchemaChange }: InteractiveBuilderProps)