diff --git a/package.json b/package.json index 95657be..baf2530 100644 --- a/package.json +++ b/package.json @@ -93,11 +93,16 @@ "jest-cli": "^28.1.3", "jest-environment-jsdom": "^28.1.3", "openmrs": "next", + "plotly.js": "^2.24.3", "prettier": "^2.8.8", "pretty-quick": "^3.1.3", + "raw-loader": "^4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-grid-layout": "^1.4.1", "react-i18next": "^11.18.6", + "react-pivottable": "^0.11.0", + "react-plotly.js": "^2.0.0", "react-router-dom": "^6.14.1", "rxjs": "^6.6.7", "swc-loader": "^0.2.3", @@ -107,4 +112,4 @@ "webpack-cli": "^5.1.4" }, "packageManager": "yarn@3.6.2" -} \ No newline at end of file +} diff --git a/src/declarations.d.ts b/src/declarations.d.ts index 9ad4a78..748b2fd 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -110,3 +110,50 @@ declare type DonutData = Array<{ group: string; value: number; }>; + +declare type Report = { + id: string; + label: string; +}; + +declare type ReportProps = { + categoryName: string; + reports: Array; +}; + +declare type Indicator = { + id: string; + label: string; + type?: string; + attributes?: Array; +}; + +declare type IndicatorItem = { + id: string; + label: string; + type?: string; +}; + +declare type ReportParamItem = { + label: string; + type?: string; + expression: string; +}; + +type savedReport = { + id: string; + label: string; + description: string; + type: string; + columns: string; + rows: string; + aggregator: string; + report_request_object: string; +}; + +type savedDashboard = { + uuid: string; + name: string; + description: string; + items: any; +}; diff --git a/src/facility-metrics/helper-components/tab-builder.tsx b/src/facility-metrics/helper-components/tab-builder.tsx index 3502698..defbcf5 100644 --- a/src/facility-metrics/helper-components/tab-builder.tsx +++ b/src/facility-metrics/helper-components/tab-builder.tsx @@ -6,6 +6,7 @@ import HIEDashboard from "../hie-metrics/hie-dashboard.component"; import styles from "./tab-builder.scss"; import FacilityPerformance from "../performance/performance.component"; import EntryStatistics from "../data-entry-statistics/data-entry-statistics.component"; +import UserDashboard from "../user-dashboard/user-dashboard.component"; interface TabItem { name: string; @@ -19,6 +20,10 @@ const TabBuilder: React.FC = () => { name: t("hie", "HIE Metrics"), component: , }, + { + name: t("userDashboard", "User Dashboard"), + component: , + }, { name: t("performance", "Performance"), component: , diff --git a/src/facility-metrics/user-dashboard/user-dasboard.scss b/src/facility-metrics/user-dashboard/user-dasboard.scss new file mode 100644 index 0000000..58a3524 --- /dev/null +++ b/src/facility-metrics/user-dashboard/user-dasboard.scss @@ -0,0 +1,137 @@ +@use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/type'; +@use '@carbon/styles/scss/colors'; +@import '../../root.scss'; +@import '~@openmrs/esm-styleguide/src/vars'; +.createIcon { + padding: 1.0rem; + margin-right: 60px; +} + +.iconButton { + width: 45px; + height: 40px; + background: lightgrey; + font-size: 60px; + display: flex; + justify-content: center; + align-items: center; + padding-right: 30px; +} + +.searchField { + width: 100%; + border: 0 !important; +} + +.searchBox { + padding: 1.0rem; + width: 100%; + display: flex; + justify-content: left; + margin-left: 30px; +} + +.cardContainer { + background-color: $ui-background; + display: flex; + justify-content: space-between; + padding: spacing.$spacing-05; +} + +.sectionTitle { + @include type.type-style('heading-compact-02'); + margin-bottom: spacing.$spacing-04; +} + +.dashboardModal, +.dashboardTabs { + :global(.cds--modal-container) { + min-width: 80%; + min-height: 80%; + } + :global(.pvtAxisContainer), + :global(.pvtRenderers), + :global(.pvtVals), + :global(.modebar-container) { + display: none; + } +} + +.modalInput { + margin-bottom: spacing.$spacing-06; +} + +.dashboardSection { + width: 80%; +} + +.dashboardTabs { + :global(.cds--tabs) { + padding-left: 2rem; + } + :global(.cds--tab-content) { + padding-left: 1.5rem; + } +} + +.layer { + display: flex; + justify-content: center; + align-items: center; + padding: spacing.$spacing-05; + margin: 2rem 1rem; + text-align: center; + border: 1px solid colors.$gray-20; +} + +.tile { + margin: auto; +} + +.content { + @include type.type-style("heading-compact-02"); + color: colors.$gray-70; + margin-top: spacing.$spacing-05; + margin-bottom: spacing.$spacing-03; +} + +.explainer { + @include type.type-style('body-compact-01'); + color: colors.$gray-70; +} + +.dropdownContainer { + margin: 2rem 0 2rem 0; +} + +.dashboardChartContainer { + width: fit-content; + height: auto; + border: 1px solid #8080801f; + box-shadow: 0 0 1px 0 #999999fc; + margin-bottom: spacing.$spacing-06; + cursor: all-scroll; +} + +.tabLabelWrapper { + display: flex; + align-items: center; + :global(.cds--btn--ghost) { + background-color: transparent; + } +} + +.tabOverFlowMenu { + margin-left: -20px; +} + +.tabHeaderText { + overflow-x: hidden; + text-overflow: ellipsis; +} + +.dashboardItemTrash { + text-align: right; + margin: 5px; +} \ No newline at end of file diff --git a/src/facility-metrics/user-dashboard/user-dashboard-illustration.component.tsx b/src/facility-metrics/user-dashboard/user-dashboard-illustration.component.tsx new file mode 100644 index 0000000..e4b1b88 --- /dev/null +++ b/src/facility-metrics/user-dashboard/user-dashboard-illustration.component.tsx @@ -0,0 +1,31 @@ +import React from "react"; + +const UserIllustration: React.FC = () => { + return ( + + + + + + + + + + + ); +}; + +export default UserIllustration; diff --git a/src/facility-metrics/user-dashboard/user-dashboard.component.tsx b/src/facility-metrics/user-dashboard/user-dashboard.component.tsx new file mode 100644 index 0000000..6b933ce --- /dev/null +++ b/src/facility-metrics/user-dashboard/user-dashboard.component.tsx @@ -0,0 +1,295 @@ +import React, { useCallback, useEffect, useState } from "react"; +import UserIllustration from "./user-dashboard-illustration.component"; +import EmptyStateIllustration from "../../components/empty-state/empty-state-illustration.component"; +import styles from "./user-dasboard.scss"; +import PivotTableUI from "react-pivottable/PivotTableUI"; +import TableRenderers from "react-pivottable/TableRenderers"; +import Plot from "react-plotly.js"; +import createPlotlyRenderers from "react-pivottable/PlotlyRenderers"; +import { + Button, + Modal, + TextArea, + TextInput, + Tabs, + Tab, + TabList, + TabPanels, + Layer, + Tile, + Dropdown, + OverflowMenu, + OverflowMenuItem, +} from "@carbon/react"; +import { + Add, + ChartLine, + ChartColumn, + CrossTab, + TrashCan, +} from "@carbon/react/icons"; +import { + useGetSavedDashboards, + useGetSaveReports, + saveDashboard, +} from "./user-dashboard.resource"; +import pivotTableStyles from "!!raw-loader!react-pivottable/pivottable.css"; +import { showNotification, showToast } from "@openmrs/esm-framework"; + +const UserDashboard: React.FC = () => { + const [dashboardTitle, setDashboardTitle] = useState(null); + const [dashboardDescription, setDashboardDescription] = useState< + string | null + >(null); + const [showModal, setShowModal] = useState(false); + const [modalDashboard, setModalDashboard] = useState>([]); + const [selectedOption, setSelectedOption] = useState(null); + const { savedReports } = useGetSaveReports(); + const { mutate, dashboardItems } = useGetSavedDashboards(); + const [selectedIndex, setSelectedIndex] = useState(0); + const handleTabChange = (evt) => { + setSelectedIndex(evt.selectedIndex); + }; + + const handleInputChange = (event) => { + setDashboardTitle(event.target.value); + }; + + const handleTextAreaChange = (event) => { + setDashboardDescription(event.target.value); + }; + + const launchDashboardModal = () => { + setShowModal(true); + }; + + const closeModal = useCallback(() => { + setModalDashboard([]); + setShowModal(false); + }, []); + + const handleDropdownSelect = (selectedReport) => { + setSelectedOption(selectedReport.selectedItem); + const newArray: Array = modalDashboard; + newArray.push(selectedReport.selectedItem); + setModalDashboard(newArray); + }; + + const handleSaveDashboard = useCallback(() => { + const getReportsInDashboard = (items) => { + const reportArray = []; + items?.map((item) => reportArray.push(item.id)); + return reportArray; + }; + + saveDashboard({ + name: dashboardTitle, + description: dashboardDescription, + items: getReportsInDashboard(modalDashboard), + }).then( + ({ status }) => { + if (status === 201) { + showToast({ + critical: true, + title: "Saving Dashboard", + kind: "success", + description: `Dashboard ${dashboardTitle} saved Successfully`, + }); + closeModal(); + mutate(); + } + }, + (error) => { + showNotification({ + title: "Saving Dashboard Failed", + kind: "error", + critical: true, + description: error?.message, + }); + } + ); + }, [ + dashboardTitle, + dashboardDescription, + modalDashboard, + closeModal, + mutate, + ]); + + const handleEditTab = () => { + // This is an empty function + }; + const handleDeleteTab = () => { + // This is an empty function + }; + const handleDeleteChart = () => { + // This is an empty function + }; + + useEffect(() => { + const styleElement = document.createElement("style"); + styleElement.textContent = `${pivotTableStyles}`; + document.head.appendChild(styleElement); + + return () => { + document.head.removeChild(styleElement); + }; + }, []); + + return ( + <> +
+ <> +
+ +
+ +
+ + {dashboardItems.length > 0 ? ( +
+ + + {dashboardItems?.map((tab, index) => ( + +
+ + + + +
{tab.label}
+
+
+ ))} +
+ {dashboardItems?.map((tab) => tab.panel)} +
+
+ ) : ( + + + +

No dashboard items to display

+

+ Use the create dashboard above to build your dashboard +

+
+
+ )} + {showModal && ( + +
+ +