diff --git a/packages/esm-covid-app/src/ReportFilters.tsx b/packages/esm-covid-app/src/ReportFilters.tsx new file mode 100644 index 000000000..e275e66c4 --- /dev/null +++ b/packages/esm-covid-app/src/ReportFilters.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Dropdown, Button, Accordion, AccordionItem, DatePicker, DatePickerInput } from '@carbon/react'; +import styles from './home.component.scss'; +import { ComboBox } from '@carbon/react'; + +const ReportFilters = ({ + config, + reportId, + setReportId, + ptrackerId, + setPtrackerId, + personUuid, + setPersonUuid, + startDate, + setStartDate, + endDate, + setEndDate, + handleSubmit, + showFilters, + handleFiltersToggle, +}) => { + const handleDateChange = (setter) => (event) => { + const date = event[0] ? event[0].toISOString().split('T')[0] : ''; + setter(date); + }; + + return ( + + +
+
+
+ (item ? item.name : '')} + onChange={({ selectedItem }) => { + if (selectedItem) { + setReportId(selectedItem.reportId || ''); + setPtrackerId(selectedItem.ptrackerId || ''); + setPersonUuid(selectedItem.personUuid || ''); + } + }} + /> + + + + + + + +
+
+
+
+
+ ); +}; + +export default ReportFilters; diff --git a/packages/esm-covid-app/src/config-schema.ts b/packages/esm-covid-app/src/config-schema.ts index 17535f111..9cba61635 100644 --- a/packages/esm-covid-app/src/config-schema.ts +++ b/packages/esm-covid-app/src/config-schema.ts @@ -89,9 +89,8 @@ export const configSchema = { CovidAssessmentFormName: 'COVID Assessment Form', CovidOutcomeFormName: 'COVID Outcome Form', CovidVaccinationFormName: 'COVID Vaccination Form', - CovidLabTestFormName: 'COVID Lab Test', - CovidLabResultFormName: 'COVID Lab Result Form', - CovidLabCancellationFormName: 'Lab Order Cancellation', + CovidLabTestFormName: 'COVID Lab Test Form', + CCovidLabCancellationFormName: 'Lab Order Cancellation', CovidSampleCollectionFormName: 'Sample Collection', CovidLabOrderFormName: 'COVID Lab Order Form', }, @@ -110,12 +109,19 @@ export const configSchema = { covidSampleCollectionFormUuid: '371d19b6-485f-11ec-99cc-1fdd2d4e9d88', }, }, + reports: { + _type: Type.Array, + _description: 'Reports and their associated UUIDs.', + _default: [ + { name: 'Covid-19 Report', uuid: 'ecabd559-14f6-4c65-87af-1254dfdf1304' }, + { name: 'HTS Report', uuid: '3ffa5a53-fc65-4a1e-a434-46dbcf1c2de2' }, + { name: 'ADX-HIV Report', uuid: '2f236b1-b0b5-4ecc-9037-681c23fb45bd' }, + { + name: 'Mother HIV Status Report', + ptrackerId: '12345A232567', + personUuid: 'bd49d697-b1de-49b9-95c2-6031fb1375fd', + reportId: 'mother_hiv_status', + }, + ], + }, }; - -export interface ConfigObject { - encounterTypes: Object; - obsConcepts: Object; - formNames: object; - cohorts: object; - formUuids: Object; -} diff --git a/packages/esm-covid-app/src/dashboard.meta.ts b/packages/esm-covid-app/src/dashboard.meta.ts index d88764052..0c2ccf043 100644 --- a/packages/esm-covid-app/src/dashboard.meta.ts +++ b/packages/esm-covid-app/src/dashboard.meta.ts @@ -1,4 +1,4 @@ -import { Coronavirus } from '@carbon/react/icons'; +import { Coronavirus, VisualRecognition } from '@carbon/react/icons'; // Patient Chart Dashboards export const covidPatientChartMeta = { @@ -39,6 +39,17 @@ export const covidClinicalViewDashboardMeta = { title: 'COVID', }; +export const covidCasesDashboardMeta = { + name: 'covid-dashboard', + icon: VisualRecognition, + slot: 'covid-dashboard-slot', + title: 'Covid Test', + isFolder: true, + folderTitle: 'Covid Test', + folderIcon: VisualRecognition, + isHidde: true, +}; + export const covid19CasesDashboardMeta = { name: 'covid-cases', slot: 'covid-cases-dashboard-slot', @@ -47,3 +58,13 @@ export const covid19CasesDashboardMeta = { folderTitle: 'COVID', folderIcon: Coronavirus, }; + +export const reportingDemoDashboardMeta = { + name: 'covid-report', + icon: VisualRecognition, + slot: 'covid-reporting-dashboard-slot', + title: 'Reporting Demo', + isFolder: true, + folderTitle: 'Reporting Demo', + folderIcon: VisualRecognition, +}; diff --git a/packages/esm-covid-app/src/home.component.scss b/packages/esm-covid-app/src/home.component.scss new file mode 100644 index 000000000..d7f92af12 --- /dev/null +++ b/packages/esm-covid-app/src/home.component.scss @@ -0,0 +1,133 @@ +.container { + padding: 2rem; +} +.homeContainer { + padding: 1rem; +} +.dropdownItem { + display: flex; + align-items: center; +} +.layer { + display: flex; + justify-content: center; + align-items: center; + height: 300px; +} + +.tile { + padding: 2rem; + text-align: center; +} + +.content { + font-size: 1.25rem; + color: #5a5a5a; +} + +.explainer { + color: #777; +} + +.form { + display: flex; + flex-direction: column; + gap: 1rem; +} +.formContainer { + display: flex; + flex-direction: column; +} + +.datePickerContainer { + display: flex; + align-items: center; + gap: 16px; +} + +.fetchButtonContainer { + margin-left: 16px; + display: flex; + align-items: center; +} + +.datePickerContainer > * { + flex: 1; +} + +.datePickerInput { + min-width: 120px; +} + +.button { + max-height: 40px; + line-height: 40px; + font-size: 14px; + padding: 0 16px; + margin-top: 1rem; + max-width: 120px; + align-items: center; +} + + +.datePickerContainer { + display: flex; + gap: 1rem; +} + +.fetchButtonContainer { + display: flex; + justify-content: flex-end; +} + +.dataTableContainer { + margin-top: 2rem; + padding: 1rem; + border: solid 1px #e0e0e0; + height: 100vh; +} + +.dataTableFullContainer { + margin-top: 2rem; + padding: 1rem; + height: 100vh; +} + +.tableContainer { + margin-top: 1rem; +} + +.toolbarWrapper { + display: flex; + justify-content: space-between; + align-items: center; +} + +.toolbarContent { + display: flex; + gap: 1rem; +} + +.searchbox { + flex-grow: 1; +} + +.tileContainer { + display: flex; + justify-content: center; + align-items: center; + height: 200px; +} + +.tileContent { + text-align: center; +} + +.content { + font-size: 1.25rem; + color: #5a5a5a; +} + +.pagination { + margin-top: 1rem; +} \ No newline at end of file diff --git a/packages/esm-covid-app/src/home.component.tsx b/packages/esm-covid-app/src/home.component.tsx index c3748ced2..c92d0e469 100644 --- a/packages/esm-covid-app/src/home.component.tsx +++ b/packages/esm-covid-app/src/home.component.tsx @@ -3,7 +3,7 @@ import { OHRIWelcomeSection } from '@ohri/openmrs-esm-ohri-commons-lib'; import CovidHomePatientTabs from './covid/dashboard/patient-list-tabs/covid-patient-list-tabs.component'; import CovidSummaryTiles from './covid/dashboard/summary-tiles/covid-summary-tiles.component'; -const Homecomponent = () => { +const Home = () => { return (
@@ -13,4 +13,4 @@ const Homecomponent = () => { ); }; -export default Homecomponent; +export default Home; diff --git a/packages/esm-covid-app/src/index.ts b/packages/esm-covid-app/src/index.ts index 93f65014f..79f2d1a80 100644 --- a/packages/esm-covid-app/src/index.ts +++ b/packages/esm-covid-app/src/index.ts @@ -9,11 +9,13 @@ import { covidClinicalViewDashboardMeta, covid19CasesDashboardMeta, covidPatientChartMeta, + reportingDemoDashboardMeta, } from './dashboard.meta'; import { createOHRIDashboardLink, createOHRIGroupedLink } from '@ohri/openmrs-esm-ohri-commons-lib'; import { createDashboardGroup, createDashboardLink } from '@openmrs/esm-patient-common-lib'; import { configSchema } from './config-schema'; -import rootComponent from './root.component'; +import Root from './root.component'; +import ReportRoot from './report-root'; export const importTranslation = require.context('../translations', false, /.json$/, 'lazy'); @@ -69,4 +71,7 @@ export const covidClinicalViewDashboardLink = getSyncLifecycle( options, ); export const covidCasesDashboardLink = getSyncLifecycle(createOHRIGroupedLink(covid19CasesDashboardMeta), options); -export const covidCasesDashboard = getSyncLifecycle(rootComponent, options); +export const covidCasesDashboard = getSyncLifecycle(Root, options); + +export const reportingDemoDashboardLink = getSyncLifecycle(createOHRIGroupedLink(reportingDemoDashboardMeta), options); +export const reportingDemoDashboard = getSyncLifecycle(ReportRoot, options); diff --git a/packages/esm-covid-app/src/report-home.tsx b/packages/esm-covid-app/src/report-home.tsx new file mode 100644 index 000000000..c1ebd13ce --- /dev/null +++ b/packages/esm-covid-app/src/report-home.tsx @@ -0,0 +1,155 @@ +import React, { useState, useMemo } from 'react'; +import useSWR from 'swr'; +import { + DataTable, + Table, + TableHead, + TableRow, + TableHeader, + TableBody, + TableCell, + Layer, + Tile, + DataTableSkeleton, +} from '@carbon/react'; +import { OHRIWelcomeSection } from '@ohri/openmrs-esm-ohri-commons-lib'; +import { openmrsFetch, useConfig, restBaseUrl } from '@openmrs/esm-framework'; +import styles from './home.component.scss'; +import capitalize from 'lodash/capitalize'; +import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib'; +import ReportFilters from './ReportFilters'; +import { useTranslation } from 'react-i18next'; + +const snakeCaseToCapitalizedWords = (snakeCaseString) => + snakeCaseString + .split('_') + .map((word) => capitalize(word)) + .join(' '); + +const fetcher = (url) => openmrsFetch(url).then((res) => res.json()); + +const ReportComponent = () => { + const config = useConfig(); + const { t } = useTranslation(); + const [reportId, setReportId] = useState('mother_hiv_status'); + const [ptrackerId, setPtrackerId] = useState(''); + const [personUuid, setPersonUuid] = useState(''); + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + const [showFilters, setShowFilters] = useState(false); + const [reportRequested, setReportRequested] = useState(false); + + const url = useMemo(() => { + if (!startDate || !endDate) return null; + return `${restBaseUrl}/mamba/report?report_id=${reportId}&ptracker_id=${ptrackerId}&person_uuid=${personUuid}&start_date=${startDate}&end_date=${endDate}`; + }, [reportId, ptrackerId, personUuid, startDate, endDate]); + + const { data, error, mutate } = useSWR(url, fetcher, { revalidateOnFocus: false }); + + const headers = useMemo(() => { + if (!data || !data.results.length) return []; + return data.results[0].record.map((column) => ({ + key: column.column, + header: snakeCaseToCapitalizedWords(column.column), + })); + }, [data]); + + const rows = useMemo(() => { + if (!data || !data.results.length) return []; + return data.results.map((result) => ({ + id: result.serialId.toString(), + ...result.record.reduce( + (acc, column) => ({ + ...acc, + [column.column]: column.value === null ? '-' : column.value, + }), + {}, + ), + })); + }, [data]); + + const loading = !data && !error && reportRequested; + + const handleFiltersToggle = () => setShowFilters(!showFilters); + + const handleSubmit = (e) => { + e.preventDefault(); + setReportRequested(true); + mutate(); + }; + + return ( +
+ + + {loading ? ( + + ) : error ? ( +
+ + +

{t('errorLadigData', 'Error loading data')}

+

{t('pleaseTryAgain', 'Please try again later')}

+
+
+
+ ) : rows.length === 0 || !reportRequested ? ( +
+ + + +

{t('noDataToDisplay', 'No data to display')}

+

+ {t('useReportsAboveToBuild', 'Use the report filters above to build your reports')} +

+
+
+
+ ) : ( +
+ + {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => ( + + + + {headers.map((header) => ( + + {header.header} + + ))} + + + + {rows.map((row) => ( + + {headers.map((header) => ( + {row[header.key] || '-'} + ))} + + ))} + +
+ )} +
+
+ )} +
+ ); +}; + +export default ReportComponent; diff --git a/packages/esm-covid-app/src/report-root.tsx b/packages/esm-covid-app/src/report-root.tsx new file mode 100644 index 000000000..f9ef72a7f --- /dev/null +++ b/packages/esm-covid-app/src/report-root.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { SWRConfig } from 'swr'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import ReportComponent from './report-home'; + +const swrConfiguration = { + // Maximum number of retries when the backend returns an error + errorRetryCount: 3, +}; + +const ReportRoot: React.FC = () => { + const reportingBasename = window.getOpenmrsSpaBase() + 'home/covid-report'; + + return ( +
+ + + + } /> + + + +
+ ); +}; + +export default ReportRoot; diff --git a/packages/esm-covid-app/src/routes.json b/packages/esm-covid-app/src/routes.json index f7a8a6f3f..b0cb6ed6b 100644 --- a/packages/esm-covid-app/src/routes.json +++ b/packages/esm-covid-app/src/routes.json @@ -21,6 +21,26 @@ "slot": "patient-chart-dashboard-slot", "component": "covidPatientChartDashboard" }, + + + { + "name": "covid-dashboard", + "slot": "covid-dashboard", + "component": "covidCasesDashboardLink", + "meta": { + + "name": "covid-dashboard", + "slot": "covid-dashboard-slot", + "cofig": { + "title": "Covid Dashboard", + "columns": 1, + "layoutMode": "anchored" + }, + "title": "Covid Dashboard" + } + }, + + { "name": "covid-assessments-dashboard", "slot": "ohri-covid-patient-chart-slot", @@ -76,7 +96,7 @@ "meta": { "name": "covid-cases", "slot": "covid-cases-dashboard-slot", - "title": "TB Treatment" + "title": "Covid Cases" }, "order": 5 }, @@ -84,6 +104,23 @@ "name": "covid-cases-dashboard", "slot": "covid-cases-dashboard-slot", "component": "covidCasesDashboard" + }, + { + "name": "covid-report-dashboard-ext", + "slot": "homepage-dashboard-slot", + "component": "reportingDemoDashboardLink", + "meta": { + "name": "covid-report", + "slot": "covid-reporting-dashboard-slot", + "title": "Covid Report" + }, + "order": 5 + }, + { + "name": "covid-reporting-demo-dashboard", + "slot": "covid-reporting-dashboard-slot", + "component": "reportingDemoDashboard" } + ] }