@ampath/esm-reports-app 1.0.0-next.8 → 1.0.0-next.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/197.js +1 -1
- package/dist/282.js +1 -1
- package/dist/282.js.map +1 -1
- package/dist/295.css +1 -0
- package/dist/295.css.map +1 -0
- package/dist/295.js +1 -0
- package/dist/295.js.map +1 -0
- package/dist/299.js +2 -0
- package/dist/299.js.LICENSE.txt +3 -0
- package/dist/299.js.map +1 -0
- package/dist/300.js +1 -1
- package/dist/311.js +1 -1
- package/dist/311.js.map +1 -1
- package/dist/335.js +1 -1
- package/dist/353.js +1 -1
- package/dist/353.js.map +1 -1
- package/dist/354.js +2 -0
- package/dist/354.js.LICENSE.txt +20 -0
- package/dist/354.js.map +1 -0
- package/dist/410.js +1 -0
- package/dist/410.js.map +1 -0
- package/dist/457.js +2 -0
- package/dist/457.js.LICENSE.txt +261 -0
- package/dist/457.js.map +1 -0
- package/dist/478.js +1 -1
- package/dist/478.js.map +1 -1
- package/dist/480.js +1 -0
- package/dist/540.js +1 -1
- package/dist/540.js.map +1 -1
- package/dist/549.css +1 -0
- package/dist/549.css.map +1 -0
- package/dist/549.js +1 -0
- package/dist/549.js.map +1 -0
- package/dist/55.js +1 -1
- package/dist/600.js +2 -0
- package/dist/600.js.LICENSE.txt +35 -0
- package/dist/600.js.map +1 -0
- package/dist/652.js +1 -1
- package/dist/757.js +1 -0
- package/dist/757.js.map +1 -0
- package/dist/781.js +1 -0
- package/dist/781.js.map +1 -0
- package/dist/961.js +1 -1
- package/dist/961.js.map +1 -1
- package/dist/99.js +1 -1
- package/dist/99.js.map +1 -0
- package/dist/ampath-esm-reports-app.js +1 -1
- package/dist/ampath-esm-reports-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +4 -2
- package/src/common/daily-summary-table.tsx +55 -0
- package/src/common/linelist-table/index.ts +3 -0
- package/src/common/linelist-table/linelist-columns.ts +48 -0
- package/src/common/linelist-table/linelist-table.component.tsx +442 -0
- package/src/common/linelist-table/linelist-table.scss +306 -0
- package/src/common/linelist-table/linelist-table.test.tsx +120 -0
- package/src/common/linelist-table/use-all-rows.ts +93 -0
- package/src/common/linelist-table/use-column-layout.ts +123 -0
- package/src/common/report-error/index.ts +1 -0
- package/src/common/report-error/show-report-error.ts +25 -0
- package/src/common/report-filters/date-range.ts +137 -0
- package/src/common/report-filters/index.ts +4 -0
- package/src/common/report-filters/report-filters.component.tsx +317 -99
- package/src/common/report-filters/report-filters.scss +264 -32
- package/src/common/report-filters/types.ts +79 -0
- package/src/common/report-filters/use-restore-period.ts +43 -0
- package/src/common/report-header/index.ts +1 -0
- package/src/common/report-header/use-location-details.ts +58 -0
- package/src/common/report-page/download-sheet-pdf.ts +225 -0
- package/src/common/report-page/index.ts +3 -0
- package/src/common/report-page/report-page.component.tsx +32 -0
- package/src/common/report-page/report-page.scss +45 -0
- package/src/common/report-page/use-page-scale.ts +60 -0
- package/src/common/report-placeholder/has-report-data.ts +15 -0
- package/src/common/report-placeholder/index.ts +3 -0
- package/src/common/report-placeholder/report-placeholder.component.tsx +81 -0
- package/src/common/report-placeholder/report-placeholder.scss +48 -0
- package/src/common/report-placeholder/use-generated-location.ts +62 -0
- package/src/common/report-register/index.ts +6 -0
- package/src/common/report-register/register-columns.ts +46 -0
- package/src/common/report-register/register-layout.component.tsx +265 -0
- package/src/common/report-register/register-layout.scss +150 -0
- package/src/common/report-register/register-table.scss +71 -0
- package/src/common/report-register/report-parents.ts +29 -0
- package/src/common/report-register/rows-to-csv.ts +89 -0
- package/src/common/report-register/use-location-name.ts +54 -0
- package/src/common/report-register/use-patient-list.ts +92 -0
- package/src/common/report-skeleton/index.ts +1 -0
- package/src/common/report-skeleton/report-skeleton.component.tsx +46 -0
- package/src/common/report-skeleton/report-skeleton.scss +29 -0
- package/src/config-schema.ts +9 -1
- package/src/dashboard/report-catalog.ts +191 -0
- package/src/dashboard/reports-dasboard.tsx +189 -18
- package/src/dashboard/reports-dashboard.module.scss +357 -14
- package/src/index.ts +13 -1
- package/src/registers/moh-240/moh-240-register.scss +41 -0
- package/src/registers/moh-240/moh-240.component.test.tsx +166 -0
- package/src/registers/moh-240/moh-240.component.tsx +164 -0
- package/src/registers/moh-267/moh-267-register.component.tsx +407 -0
- package/src/registers/moh-267/moh-267-register.scss +298 -0
- package/src/registers/moh-267/moh-267-summary.ts +102 -0
- package/src/registers/moh-267/moh-267.component.tsx +94 -0
- package/src/registers/moh-267/moh-267.types.ts +47 -0
- package/src/registers/moh-362/moh-362-lab-register.component.tsx +551 -0
- package/src/registers/moh-362/moh-362-lab-register.scss +412 -0
- package/src/registers/moh-362/moh-362-linkage-register.component.tsx +215 -0
- package/src/registers/moh-362/moh-362-linkage-register.scss +91 -0
- package/src/registers/moh-362/moh-362.component.tsx +117 -0
- package/src/registers/moh-362/moh-362.scss +11 -0
- package/src/registers/moh-362/moh-362.types.ts +87 -0
- package/src/registers/presumptive-tb/presumptive-tb-register.component.tsx +391 -0
- package/src/registers/presumptive-tb/presumptive-tb-register.scss +262 -0
- package/src/registers/presumptive-tb/presumptive-tb.component.tsx +92 -0
- package/src/registers/presumptive-tb/presumptive-tb.types.ts +88 -0
- package/src/registers/register-catalog.ts +202 -0
- package/src/registers/register-file-name.ts +18 -0
- package/src/registers/tb-lab-register/tb-lab-register-table.component.tsx +116 -0
- package/src/registers/tb-lab-register/tb-lab-register-table.scss +123 -0
- package/src/registers/tb-lab-register/tb-lab-register.columns.ts +164 -0
- package/src/registers/tb-lab-register/tb-lab-register.component.test.tsx +232 -0
- package/src/registers/tb-lab-register/tb-lab-register.component.tsx +153 -0
- package/src/registers/tb4/tb4-parts.tsx +164 -0
- package/src/registers/tb4/tb4-patient-sheet.component.tsx +418 -0
- package/src/registers/tb4/tb4-register.scss +282 -0
- package/src/registers/tb4/tb4-treatment-sheet.component.tsx +349 -0
- package/src/registers/tb4/tb4.component.tsx +120 -0
- package/src/registers/tb4/tb4.types.ts +129 -0
- package/src/reports/acf-summary/acf-summary-form.component.tsx +176 -0
- package/src/reports/acf-summary/acf-summary-linelist.component.tsx +86 -0
- package/src/reports/acf-summary/acf-summary.component.tsx +97 -0
- package/src/reports/acf-summary/acf-summary.counts.ts +37 -0
- package/src/reports/acf-summary/acf-summary.scss +162 -0
- package/src/reports/datatable-wrapper/datatable-wrapper.component.tsx +63 -48
- package/src/reports/moh-240/moh-240-header.component.tsx +62 -0
- package/src/reports/moh-240/moh-240.columns.ts +113 -0
- package/src/reports/moh-240/moh-240.component.tsx +72 -9
- package/src/reports/moh-240/moh-240.scss +40 -0
- package/src/reports/moh-240/sub-reports/moh-240-register.component.tsx +31 -37
- package/src/reports/moh-240/sub-reports/page-summary.component.tsx +143 -106
- package/src/reports/moh-505/moh-505-header.component.tsx +67 -0
- package/src/reports/moh-505/moh-505.component.tsx +441 -0
- package/src/reports/moh-505/moh-505.scss +117 -0
- package/src/reports/moh-705B/moh-204b-register.component.tsx +246 -0
- package/src/reports/moh-705B/moh-204b.columns.ts +39 -0
- package/src/reports/moh-705B/moh-705b.component.tsx +659 -0
- package/src/reports/moh-705B/moh-705b.scss +172 -0
- package/src/reports/moh-705a/moh-705a.component.tsx +661 -0
- package/src/reports/moh-705a/moh-705a.scss +172 -0
- package/src/reports/moh-705a/registers/moh-204a-register.component.tsx +331 -0
- package/src/reports/moh-705a/registers/moh-204a.columns.ts +57 -0
- package/src/reports/moh-705a/type.ts +6 -0
- package/src/reports/moh-706/moh-706-header.component.tsx +66 -0
- package/src/reports/moh-706/moh-706.component.tsx +150 -24
- package/src/reports/moh-706/moh-706.scss +119 -11
- package/src/reports/moh-706/sub-reports/bacteriology/bacteriology.component.tsx +35 -28
- package/src/reports/moh-706/sub-reports/blood-chemistry/blood-chemistry.component.tsx +229 -305
- package/src/reports/moh-706/sub-reports/haematology/haematology.component.tsx +26 -19
- package/src/reports/moh-706/sub-reports/histology-and-cytology/histology-and-cytology.component.tsx +23 -23
- package/src/reports/moh-706/sub-reports/parasitology/parasitology.component.tsx +25 -18
- package/src/reports/moh-706/sub-reports/serology/serology.component.tsx +21 -14
- package/src/reports/moh-706/sub-reports/specimen-referral-to-higher-levels/specimen-referral-to-higher-levels.component.tsx +2 -2
- package/src/reports/moh-706/sub-reports/urine-analysis/urine-analysis.component.tsx +27 -20
- package/src/reports/moh-710/moh-710.component.tsx +815 -632
- package/src/reports/moh-710/moh-710.scss +142 -21
- package/src/reports/moh-710/registers/moh-413/moh-413-columns.ts +22 -0
- package/src/reports/moh-710/registers/moh-413/moh-413.component.tsx +140 -0
- package/src/reports/moh-710/registers/moh-413/moh-413.scss +64 -0
- package/src/reports/moh-710/registers/moh-510/moh-510.columns.ts +40 -0
- package/src/reports/moh-710/registers/moh-510/moh-510.component.tsx +191 -0
- package/src/reports/moh-710/registers/moh-510/moh-510.scss +130 -0
- package/src/reports/moh-710/registers/moh-511-710-register.component.tsx +268 -0
- package/src/reports/moh-710/registers/moh-511-710.columns.ts +52 -0
- package/src/reports/moh-710/registers/type.ts +31 -0
- package/src/reports/moh-711/moh-711.component.tsx +153 -1269
- package/src/reports/moh-711/moh711.scss +135 -20
- package/src/reports/moh-711/registers/moh-333-register.component.tsx +680 -0
- package/src/reports/moh-711/registers/moh-333.columns.ts +119 -0
- package/src/reports/moh-711/registers/moh-405-register.component.tsx +566 -0
- package/src/reports/moh-711/registers/moh-405.columns.ts +120 -0
- package/src/reports/moh-711/registers/moh-406-register.component.tsx +581 -0
- package/src/reports/moh-711/registers/moh-406.columns.ts +133 -0
- package/src/reports/moh-711/registers/moh-407-register.component.tsx +157 -0
- package/src/reports/moh-711/registers/moh-407.columns.ts +24 -0
- package/src/reports/moh-711/registers/moh-407.scss +64 -0
- package/src/reports/moh-711/registers/moh-510-register.component.tsx +55 -0
- package/src/reports/moh-711/registers/moh-511-register.component.tsx +270 -0
- package/src/reports/moh-711/registers/moh-511.columns.ts +59 -0
- package/src/reports/moh-711/registers/moh-512-columns.ts +94 -0
- package/src/reports/moh-711/registers/moh-512.component.tsx +191 -0
- package/src/reports/moh-711/registers/moh-512.scss +64 -0
- package/src/reports/moh-711/registers/tb-register-columns.ts +17 -0
- package/src/reports/moh-711/registers/tb-register.component.tsx +96 -0
- package/src/reports/moh-711/registers/tb-register.scss +64 -0
- package/src/reports/moh-711/registers/type.ts +192 -0
- package/src/reports/moh-711/sections/anc.component.tsx +159 -0
- package/src/reports/moh-711/sections/cervical-cancer.component.tsx +148 -0
- package/src/reports/moh-711/sections/chanis.component.tsx +389 -0
- package/src/reports/moh-711/sections/family-planning.component.tsx +299 -0
- package/src/reports/moh-711/sections/gbv.component.tsx +115 -0
- package/src/reports/moh-711/sections/maternity.component.tsx +321 -0
- package/src/reports/moh-711/sections/medical-social-work.component.tsx +73 -0
- package/src/reports/moh-711/sections/other.component.tsx +47 -0
- package/src/reports/moh-711/sections/physiotherapy.component.tsx +61 -0
- package/src/reports/moh-711/sections/pnc.component.tsx +127 -0
- package/src/reports/moh-711/sections/post-abortion.component.tsx +42 -0
- package/src/reports/moh-711/sections/rehabilitation.component.tsx +57 -0
- package/src/reports/moh-711/sections/report-compiled-by.component.tsx +50 -0
- package/src/reports/moh-711/sections/tb-screening.component.tsx +94 -0
- package/src/reports/moh-717/moh-717.component.tsx +149 -985
- package/src/reports/moh-717/moh717.scss +217 -21
- package/src/reports/moh-717/registers/nutrition-register.component.tsx +48 -0
- package/src/reports/moh-717/sections/finance.component.tsx +42 -0
- package/src/reports/moh-717/sections/inpatient.component.tsx +374 -0
- package/src/reports/moh-717/sections/maternity.component.tsx +126 -0
- package/src/reports/moh-717/sections/medical-records.component.tsx +35 -0
- package/src/reports/moh-717/sections/mortuary.component.tsx +43 -0
- package/src/reports/moh-717/sections/operations.component.tsx +53 -0
- package/src/reports/moh-717/sections/orthopaedic-trauma.component.tsx +98 -0
- package/src/reports/moh-717/sections/outpatient.component.tsx +597 -0
- package/src/reports/moh-717/sections/pharmacy.component.tsx +43 -0
- package/src/reports/moh-717/sections/preparedby.component.tsx +52 -0
- package/src/reports/moh-717/sections/special-services.component.tsx +114 -0
- package/src/reports/moh-729b/moh-729b-form.component.tsx +385 -0
- package/src/reports/moh-729b/moh-729b-linelist.component.tsx +205 -0
- package/src/reports/moh-729b/moh-729b-summary.component.tsx +258 -0
- package/src/reports/moh-729b/moh-729b-summary.config.ts +229 -0
- package/src/reports/moh-729b/moh-729b.component.tsx +157 -0
- package/src/reports/moh-729b/moh-729b.config.ts +235 -0
- package/src/reports/moh-729b/moh-729b.scss +276 -0
- package/src/reports/moh-729b/moh-729b.types.ts +111 -0
- package/src/reports/moh-731/linelist/moh-731-indicators.ts +1122 -0
- package/src/reports/moh-731/linelist/moh-731-linelist-sections.ts +472 -0
- package/src/reports/moh-731/linelist/moh-731-linelist.component.tsx +102 -0
- package/src/reports/moh-731/linelist/use-open-moh-731-linelist.ts +50 -0
- package/src/reports/moh-731/moh-731.component.tsx +269 -0
- package/src/reports/moh-731/moh-731.scss +496 -0
- package/src/reports/moh-731/registers/moh-366.columns.ts +38 -0
- package/src/reports/moh-731/registers/moh-366.component.tsx +471 -0
- package/src/reports/moh-731/registers/prep-register.component.tsx +284 -0
- package/src/reports/moh-731/registers/prep.columns.ts +34 -0
- package/src/reports/moh-731/sections/hiv-and-tb-treatment.component.tsx +1206 -0
- package/src/reports/moh-731/sections/hiv-testing-and-pre-exposure.component.tsx +727 -0
- package/src/reports/moh-731/sections/pmtct.component.tsx +572 -0
- package/src/reports/moh-731/summary/moh-731-summary.component.tsx +313 -0
- package/src/reports/moh-731/summary/moh-731-summary.config.ts +527 -0
- package/src/reports/moh-731/summary/moh-731-summary.scss +247 -0
- package/src/reports/moh-740/moh-740.component.scss +202 -0
- package/src/reports/moh-740/moh-740.component.tsx +1339 -0
- package/src/reports/moh-740/moh-740.resource.ts +54 -0
- package/src/reports/moh-740/registers/moh-222-daily-register/moh-222-daily-register.scss +20 -0
- package/src/reports/moh-740/registers/moh-222-daily-register/moh-222-daily-register.tsx +183 -0
- package/src/reports/moh-740/registers/moh-270-permanent-register/moh-270-permanent-register.scss +6 -0
- package/src/reports/moh-740/registers/moh-270-permanent-register/moh-270-permanent-register.tsx +151 -0
- package/src/reports/moh-740/registers/moh-740-patient-list.scss +0 -0
- package/src/reports/moh-740/registers/moh-740-patient-list.tsx +60 -0
- package/src/reports/moh-740/registers/moh-740.columns.ts +54 -0
- package/src/reports/moh-740/shared/data-cell/data-cell.scss +6 -0
- package/src/reports/moh-740/shared/data-cell/data-cell.tsx +23 -0
- package/src/reports/moh-740/shared/utils/indicator-register-map.ts +159 -0
- package/src/reports/moh-740/types/index.ts +223 -0
- package/src/reports/moh-745/moh-745.component.tsx +2058 -0
- package/src/reports/moh-745/moh-745.scss +142 -0
- package/src/reports/moh-745/registers/moh-412-register.component.scss +6 -0
- package/src/reports/moh-745/registers/moh-412-register.component.tsx +275 -0
- package/src/reports/moh-745/registers/moh-412.columns.ts +52 -0
- package/src/reports/moh-745/registers/type.ts +223 -0
- package/src/reports/table-wrapper/table-row-mapper.component.tsx +20 -5
- package/src/reports/table-wrapper/table-wrapper.component.tsx +23 -14
- package/src/reports/table-wrapper/table-wrapper.scss +1 -16
- package/src/resources/moh-267.resource.ts +27 -0
- package/src/resources/moh-362.resource.ts +27 -0
- package/src/resources/moh-505.resource.ts +69 -0
- package/src/resources/moh-705.resource.ts +118 -0
- package/src/resources/moh-706.resource.ts +80 -0
- package/src/resources/moh-710.resource.ts +60 -9
- package/src/resources/moh-711.resource.ts +206 -9
- package/src/resources/moh-717.resource.ts +26 -9
- package/src/resources/moh-729b.resource.ts +25 -0
- package/src/resources/moh-731.resource.ts +90 -0
- package/src/resources/moh-745.resource.ts +76 -0
- package/src/resources/patient-list-page.ts +66 -0
- package/src/resources/presumptive-tb.resource.ts +27 -0
- package/src/root.component.tsx +79 -8
- package/src/root.scss +36 -0
- package/src/routes.json +3 -1
- package/src/utils/format-indicator.ts +39 -0
- package/src/utils/get-base-url.ts +39 -7
- package/src/utils/utils.ts +30 -1
- package/tsconfig.json +1 -1
- package/webpack.config.js +22 -1
- package/dist/138.js +0 -2
- package/dist/138.js.LICENSE.txt +0 -20
- package/dist/138.js.map +0 -1
- package/dist/184.js +0 -2
- package/dist/184.js.LICENSE.txt +0 -14
- package/dist/184.js.map +0 -1
- package/dist/205.js +0 -1
- package/dist/205.js.map +0 -1
- package/dist/812.js +0 -1
- package/dist/812.js.map +0 -1
- package/dist/879.js +0 -1
- package/dist/879.js.map +0 -1
- package/dist/ampath-esm-reports-app.js.buildmanifest.json +0 -539
- package/dist/main.js +0 -1
- package/dist/main.js.map +0 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Breadcrumb, BreadcrumbItem, Button, Layer, Pagination } from '@carbon/react';
|
|
3
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
4
|
+
import { ArrowLeft, Calendar, Document, Download, Location } from '@carbon/react/icons';
|
|
5
|
+
import { formatDate } from '@openmrs/esm-framework';
|
|
6
|
+
|
|
7
|
+
import { formatIndicatorName } from '../../utils/format-indicator';
|
|
8
|
+
import { showReportError } from '../report-error';
|
|
9
|
+
import { columnsToCsv, downloadCsv, rowsToCsv } from './rows-to-csv';
|
|
10
|
+
import { type RegisterColumn } from './register-columns';
|
|
11
|
+
import { REPORT_PARENTS } from './report-parents';
|
|
12
|
+
|
|
13
|
+
import { RegisterSkeleton } from '../report-skeleton';
|
|
14
|
+
import styles from './register-layout.scss';
|
|
15
|
+
|
|
16
|
+
interface RegisterLayoutProps {
|
|
17
|
+
/** The report this register was opened from, e.g. `MOH-731 Report` */
|
|
18
|
+
parentLabel: string;
|
|
19
|
+
/** Route back to that report, e.g. `/moh-731` */
|
|
20
|
+
parentPath?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Returns to the report in place, for a register that is a view on the
|
|
23
|
+
* report's own route rather than a route of its own.
|
|
24
|
+
*
|
|
25
|
+
* MOH-740 toggles its registers in place, so there is nowhere to navigate:
|
|
26
|
+
* a breadcrumb would link the page to itself. Given this, the trail is
|
|
27
|
+
* replaced by a button that puts the report back.
|
|
28
|
+
*/
|
|
29
|
+
onBack?: () => void;
|
|
30
|
+
/** Register name, e.g. `HIV Care Treatment Daily Activity Register MOH 366` */
|
|
31
|
+
title: string;
|
|
32
|
+
/**
|
|
33
|
+
* The page heading, when the register can name the figure itself. Without
|
|
34
|
+
* it the heading is built from the link's `label` and `gender`.
|
|
35
|
+
*/
|
|
36
|
+
heading?: string;
|
|
37
|
+
/** The location the register was generated for, shown beside the period */
|
|
38
|
+
location?: string;
|
|
39
|
+
isLoading: boolean;
|
|
40
|
+
/** `true` once loaded with nothing to show */
|
|
41
|
+
isEmpty: boolean;
|
|
42
|
+
/** Paging state from `usePatientList` */
|
|
43
|
+
page?: number;
|
|
44
|
+
pageSize?: number;
|
|
45
|
+
total?: number;
|
|
46
|
+
/** `false` when the total is a lower bound the endpoint did not confirm */
|
|
47
|
+
isTotalExact?: boolean;
|
|
48
|
+
onPageChange?: (change: { page: number; pageSize: number }) => void;
|
|
49
|
+
/** Every row across every page, used to build the export */
|
|
50
|
+
fetchAll?: () => Promise<Array<Record<string, any>>>;
|
|
51
|
+
/** The register's own columns. Without them the export falls back to the
|
|
52
|
+
response's raw fields, which will not match the printed form. */
|
|
53
|
+
columns?: RegisterColumn[];
|
|
54
|
+
/**
|
|
55
|
+
* The printed form's own sub-headings (e.g. its column letters), written as
|
|
56
|
+
* a second CSV row so exports stay line-numbered against the paper form.
|
|
57
|
+
*/
|
|
58
|
+
subHeaders?: string[];
|
|
59
|
+
/** The register table. Optional: MOH 510 has no table implemented yet. */
|
|
60
|
+
children?: React.ReactNode;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The frame every drill-down register renders inside.
|
|
65
|
+
*
|
|
66
|
+
* Registers are opened from a value box on a report, so the trail back matters
|
|
67
|
+
* more than a bare Back button: the breadcrumb shows where the figure came
|
|
68
|
+
* from and lets the user step to either level. The table itself is wide -- the
|
|
69
|
+
* MOH 366 register runs to 41 columns -- so it scrolls within the page rather
|
|
70
|
+
* than forcing the whole layout sideways.
|
|
71
|
+
*/
|
|
72
|
+
const GENDERS: Record<string, string> = { M: 'Male', F: 'Female' };
|
|
73
|
+
|
|
74
|
+
const RegisterLayout: React.FC<RegisterLayoutProps> = ({
|
|
75
|
+
parentLabel,
|
|
76
|
+
parentPath,
|
|
77
|
+
onBack,
|
|
78
|
+
title,
|
|
79
|
+
heading: headingProp,
|
|
80
|
+
location,
|
|
81
|
+
isLoading,
|
|
82
|
+
isEmpty,
|
|
83
|
+
page,
|
|
84
|
+
pageSize,
|
|
85
|
+
total,
|
|
86
|
+
isTotalExact = true,
|
|
87
|
+
onPageChange,
|
|
88
|
+
fetchAll,
|
|
89
|
+
columns,
|
|
90
|
+
subHeaders,
|
|
91
|
+
children,
|
|
92
|
+
}) => {
|
|
93
|
+
/* The same register serves every indicator that links to it, so without this
|
|
94
|
+
each drill-down looks identical no matter which box was clicked. */
|
|
95
|
+
const [params] = useSearchParams();
|
|
96
|
+
const [isExporting, setIsExporting] = React.useState(false);
|
|
97
|
+
const indicator = params.get('indicator') ?? params.get('indicators');
|
|
98
|
+
/* The report passes the row label it displayed, so the crumb reads the way
|
|
99
|
+
the figure was labelled on the form. `formatIndicatorName` is the fallback
|
|
100
|
+
for links made before the label was carried through. */
|
|
101
|
+
const label = params.get('label');
|
|
102
|
+
const gender = params.get('gender');
|
|
103
|
+
const startDate = params.get('startDate');
|
|
104
|
+
const endDate = params.get('endDate');
|
|
105
|
+
const locationUuids = params.get('locationUuids');
|
|
106
|
+
|
|
107
|
+
/* Where the drill-down came from. Registers shared by two reports cannot
|
|
108
|
+
name their parent as a constant, so the link says which one it was; the
|
|
109
|
+
props remain the fallback for links made before it was carried. */
|
|
110
|
+
const from = params.get('from');
|
|
111
|
+
const parent = (from && REPORT_PARENTS[from]) || { label: parentLabel, path: parentPath ?? '' };
|
|
112
|
+
|
|
113
|
+
/* The crumb has to carry the period, not just the path: a report that does
|
|
114
|
+
not keep its figures in session storage regenerates from its URL, and
|
|
115
|
+
linking to the bare path would land the user on the placeholder instead of
|
|
116
|
+
the figures the register was opened from. */
|
|
117
|
+
const parentQuery = new URLSearchParams();
|
|
118
|
+
if (locationUuids) parentQuery.append('locationUuids', locationUuids);
|
|
119
|
+
if (startDate) parentQuery.append('startDate', startDate);
|
|
120
|
+
if (endDate) parentQuery.append('endDate', endDate);
|
|
121
|
+
/* back to the report tab the register was opened from, e.g. a summary view */
|
|
122
|
+
const view = params.get('view');
|
|
123
|
+
if (view) parentQuery.append('view', view);
|
|
124
|
+
const section = params.get('section');
|
|
125
|
+
if (section) parentQuery.append('section', section);
|
|
126
|
+
const parentLink = parentQuery.toString() ? `${parent.path}?${parentQuery}` : parent.path;
|
|
127
|
+
|
|
128
|
+
const heading =
|
|
129
|
+
headingProp ??
|
|
130
|
+
(label
|
|
131
|
+
? `${label}${gender && GENDERS[gender] ? ` (${GENDERS[gender]})` : ''}`
|
|
132
|
+
: formatIndicatorName(indicator ?? ''));
|
|
133
|
+
|
|
134
|
+
/* Exports every page, not the one on screen: the DOM only holds the current
|
|
135
|
+
page once paginated, so a scrape would silently export 50 of 2,000. */
|
|
136
|
+
const exportCsv = async () => {
|
|
137
|
+
if (!fetchAll) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setIsExporting(true);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const all = await fetchAll();
|
|
145
|
+
const slug = [title, label ?? indicator, startDate, endDate]
|
|
146
|
+
.filter(Boolean)
|
|
147
|
+
.join('-')
|
|
148
|
+
.replace(/[^a-z0-9]+/gi, '-')
|
|
149
|
+
.replace(/^-|-$/g, '')
|
|
150
|
+
.toLowerCase();
|
|
151
|
+
|
|
152
|
+
downloadCsv(columns ? columnsToCsv(all, columns, { subHeaderRow: subHeaders }) : rowsToCsv(all), `${slug}.csv`);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
showReportError('the register export', error);
|
|
155
|
+
} finally {
|
|
156
|
+
setIsExporting(false);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const canExport = !isLoading && !isEmpty;
|
|
161
|
+
const pageSizeOrDefault = pageSize ?? 50;
|
|
162
|
+
|
|
163
|
+
const period =
|
|
164
|
+
startDate && endDate
|
|
165
|
+
? `${formatDate(new Date(startDate), { mode: 'standard', time: false, noToday: true })} to ${formatDate(
|
|
166
|
+
new Date(endDate),
|
|
167
|
+
{ mode: 'standard', time: false, noToday: true },
|
|
168
|
+
)}`
|
|
169
|
+
: null;
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div className={styles.register}>
|
|
173
|
+
{onBack ? (
|
|
174
|
+
<div className={styles.breadcrumb}>
|
|
175
|
+
<Button kind="ghost" size="sm" renderIcon={ArrowLeft} onClick={onBack} className={styles.back}>
|
|
176
|
+
{parent.label}
|
|
177
|
+
</Button>
|
|
178
|
+
<h3 className={styles.title}>{heading || title}</h3>
|
|
179
|
+
</div>
|
|
180
|
+
) : (
|
|
181
|
+
<Breadcrumb noTrailingSlash className={styles.breadcrumb}>
|
|
182
|
+
<BreadcrumbItem>
|
|
183
|
+
<Link to="/">All reports</Link>
|
|
184
|
+
</BreadcrumbItem>
|
|
185
|
+
<BreadcrumbItem>
|
|
186
|
+
<Link to={parentLink}>{parent.label}</Link>
|
|
187
|
+
</BreadcrumbItem>
|
|
188
|
+
<BreadcrumbItem isCurrentPage>
|
|
189
|
+
<h3 className={styles.title}>{heading || title}</h3>
|
|
190
|
+
</BreadcrumbItem>
|
|
191
|
+
</Breadcrumb>
|
|
192
|
+
)}
|
|
193
|
+
|
|
194
|
+
{/* Register, period and the export sit on one line: they all describe the
|
|
195
|
+
table below, and splitting them stacked two sparse rows above it. */}
|
|
196
|
+
{(indicator || period || canExport) && (
|
|
197
|
+
<div className={styles.context}>
|
|
198
|
+
{/* Register and period read as one caption for the table, so they
|
|
199
|
+
share a panel rather than sitting as two loose fragments. */}
|
|
200
|
+
<span className={styles.summary}>
|
|
201
|
+
<span className={styles.summaryTitle}>{title}</span>
|
|
202
|
+
<span className={styles.period}>
|
|
203
|
+
{period && (
|
|
204
|
+
<>
|
|
205
|
+
<Calendar size={14} />
|
|
206
|
+
{period}
|
|
207
|
+
</>
|
|
208
|
+
)}
|
|
209
|
+
{location && (
|
|
210
|
+
<span className={styles.location}>
|
|
211
|
+
<Location size={14} />
|
|
212
|
+
{location}
|
|
213
|
+
</span>
|
|
214
|
+
)}
|
|
215
|
+
{typeof total === 'number' && total > 0 && (
|
|
216
|
+
<span>{isTotalExact ? `${total.toLocaleString()} patients` : `${total.toLocaleString()}+ patients`}</span>
|
|
217
|
+
)}
|
|
218
|
+
</span>
|
|
219
|
+
</span>
|
|
220
|
+
{canExport && fetchAll && (
|
|
221
|
+
<Button
|
|
222
|
+
kind="tertiary"
|
|
223
|
+
size="sm"
|
|
224
|
+
renderIcon={Download}
|
|
225
|
+
onClick={exportCsv}
|
|
226
|
+
disabled={isExporting}
|
|
227
|
+
className={styles.export}
|
|
228
|
+
>
|
|
229
|
+
{isExporting ? 'Preparing…' : 'Export CSV'}
|
|
230
|
+
</Button>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
|
|
235
|
+
{isLoading && <RegisterSkeleton />}
|
|
236
|
+
|
|
237
|
+
{!isLoading && isEmpty && (
|
|
238
|
+
<Layer className={styles.empty}>
|
|
239
|
+
<Document size={32} className={styles.emptyIcon} />
|
|
240
|
+
<h4>No records to list</h4>
|
|
241
|
+
<p>No clients matched this indicator for the selected period and facility.</p>
|
|
242
|
+
</Layer>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{canExport && (
|
|
246
|
+
<>
|
|
247
|
+
<div className={styles.tableScroll}>{children}</div>
|
|
248
|
+
{onPageChange && typeof total === 'number' && total > pageSizeOrDefault && (
|
|
249
|
+
<Pagination
|
|
250
|
+
className={styles.pagination}
|
|
251
|
+
page={page}
|
|
252
|
+
pageSize={pageSizeOrDefault}
|
|
253
|
+
pageSizes={[25, 50, 100, 200]}
|
|
254
|
+
totalItems={total}
|
|
255
|
+
size="sm"
|
|
256
|
+
onChange={onPageChange}
|
|
257
|
+
/>
|
|
258
|
+
)}
|
|
259
|
+
</>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export default RegisterLayout;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
|
+
|
|
5
|
+
.register {
|
|
6
|
+
padding: layout.$spacing-05 0 layout.$spacing-07;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.breadcrumb {
|
|
10
|
+
margin-bottom: layout.$spacing-05;
|
|
11
|
+
|
|
12
|
+
:global(.cds--breadcrumb-item) {
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
align-items: baseline;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* The register name doubles as the page heading, so it stays an `h3` rather
|
|
19
|
+
than being repeated in a title row of its own. */
|
|
20
|
+
|
|
21
|
+
.title {
|
|
22
|
+
@include type.type-style('productive-heading-03');
|
|
23
|
+
display: inline;
|
|
24
|
+
color: $color-gray-100;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Registers run to dozens of columns, so the table scrolls inside the page
|
|
28
|
+
instead of pushing the whole layout sideways. */
|
|
29
|
+
|
|
30
|
+
.tableScroll {
|
|
31
|
+
overflow-x: auto;
|
|
32
|
+
padding: layout.$spacing-05;
|
|
33
|
+
background-color: $ui-02;
|
|
34
|
+
border: 1px solid $ui-03;
|
|
35
|
+
border-top: none;
|
|
36
|
+
|
|
37
|
+
/* Each register carries its own table margins -- 1rem here, 0.5rem there,
|
|
38
|
+
0.75rem elsewhere -- which land inside this frame and give every register a
|
|
39
|
+
different gap to the border. The padding above is the spacing now, so those
|
|
40
|
+
are levelled out. */
|
|
41
|
+
|
|
42
|
+
table,
|
|
43
|
+
> div {
|
|
44
|
+
margin: 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
.empty {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: layout.$spacing-03;
|
|
54
|
+
padding: layout.$spacing-07 layout.$spacing-05;
|
|
55
|
+
text-align: center;
|
|
56
|
+
background-color: $ui-02;
|
|
57
|
+
border: 1px solid $ui-03;
|
|
58
|
+
border-top: none;
|
|
59
|
+
|
|
60
|
+
h4 {
|
|
61
|
+
@include type.type-style('productive-heading-02');
|
|
62
|
+
color: $color-gray-100;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
p {
|
|
66
|
+
@include type.type-style('body-long-01');
|
|
67
|
+
color: $text-02;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.emptyIcon {
|
|
72
|
+
fill: $ui-04;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* The caption and the table below it are one card: the bar carries the register
|
|
76
|
+
name, the period it covers and the export, and sits flush on whatever follows
|
|
77
|
+
-- skeleton, empty state or table -- so the page reads as a single object
|
|
78
|
+
rather than three stacked fragments. */
|
|
79
|
+
|
|
80
|
+
.context {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: space-between;
|
|
85
|
+
gap: layout.$spacing-03 layout.$spacing-05;
|
|
86
|
+
padding: layout.$spacing-04 layout.$spacing-05;
|
|
87
|
+
background-color: $ui-02;
|
|
88
|
+
border: 1px solid $ui-03;
|
|
89
|
+
border-bottom: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Register name over the period it covers: side by side they made one long
|
|
93
|
+
line that crowded the action; stacked, the name leads and the period reads
|
|
94
|
+
as its caption. */
|
|
95
|
+
|
|
96
|
+
.summary {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
gap: layout.$spacing-02;
|
|
100
|
+
min-width: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.summaryTitle {
|
|
104
|
+
@include type.type-style('body-short-01');
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
color: $color-gray-100;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.period {
|
|
110
|
+
@include type.type-style('label-01');
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
flex-wrap: wrap;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: layout.$spacing-02;
|
|
115
|
+
color: $text-02;
|
|
116
|
+
|
|
117
|
+
svg {
|
|
118
|
+
fill: var(--brand-01);
|
|
119
|
+
transform: translateY(0.125rem);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Location sits after the period as one unit, so on a narrow screen it wraps
|
|
124
|
+
to its own line whole rather than leaving its pin behind */
|
|
125
|
+
.location {
|
|
126
|
+
display: inline-flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: layout.$spacing-02;
|
|
129
|
+
margin-left: layout.$spacing-03;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.export {
|
|
133
|
+
flex-shrink: 0;
|
|
134
|
+
margin-left: auto;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Sits directly under the table, completing the card */
|
|
138
|
+
|
|
139
|
+
.pagination {
|
|
140
|
+
border: 1px solid $ui-03;
|
|
141
|
+
border-top: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* A register rendered as a view on its report's own route has nowhere to
|
|
145
|
+
navigate, so the trail is a button and the register name beside it. */
|
|
146
|
+
|
|
147
|
+
.back {
|
|
148
|
+
padding-inline: 0.25rem;
|
|
149
|
+
min-block-size: auto;
|
|
150
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
2
|
+
|
|
3
|
+
/* The one stylesheet every register table uses.
|
|
4
|
+
*
|
|
5
|
+
* Registers previously borrowed their parent report's sheet, so a report sized
|
|
6
|
+
* for an A4 page dragged its paper typography into its line lists, and the same
|
|
7
|
+
* table rules were restated in half a dozen files that drifted apart. These are
|
|
8
|
+
* screen tables: dense, scrollable, and independent of how their report prints.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.tableContainer {
|
|
12
|
+
width: 100%;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Sized to content rather than to the frame, so a long value -- a full name,
|
|
16
|
+
say -- widens its own column instead of wrapping. `.tableScroll` in the
|
|
17
|
+
layout scrolls when the table outgrows the page. */
|
|
18
|
+
|
|
19
|
+
.table {
|
|
20
|
+
width: auto;
|
|
21
|
+
min-width: 100%;
|
|
22
|
+
margin: 0;
|
|
23
|
+
font-size: 0.75rem;
|
|
24
|
+
border-collapse: collapse;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.tableBordered td,
|
|
28
|
+
.tableBordered th {
|
|
29
|
+
padding: 0.125rem 0.5rem;
|
|
30
|
+
font-size: 0.75rem;
|
|
31
|
+
line-height: 1.3;
|
|
32
|
+
border: 1px solid $ui-04;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tableBordered th {
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
color: $color-gray-100;
|
|
38
|
+
background-color: $ui-01;
|
|
39
|
+
/* headings carry deliberate line breaks, so they wrap where the markup says */
|
|
40
|
+
white-space: normal;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.tableBordered td {
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Long prose columns (diagnoses, referrals, comments) wrap inside a bounded
|
|
48
|
+
width instead of stretching the row past the fold. */
|
|
49
|
+
.tableBordered td.wrapCell {
|
|
50
|
+
white-space: normal;
|
|
51
|
+
min-width: 8rem;
|
|
52
|
+
max-width: 16rem;
|
|
53
|
+
overflow-wrap: anywhere;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.tableStriped tbody tr:nth-of-type(odd) {
|
|
57
|
+
background-color: $ui-01;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.tableStriped tbody tr:hover {
|
|
61
|
+
background-color: $brand-primary-10;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.textCentre {
|
|
65
|
+
text-align: center;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.verticalText {
|
|
69
|
+
writing-mode: vertical-rl;
|
|
70
|
+
transform: rotate(180deg);
|
|
71
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The report each register can be opened from.
|
|
3
|
+
*
|
|
4
|
+
* Several registers serve more than one report -- MOH 333, 405 and 406 are
|
|
5
|
+
* reached from both MOH-711 and MOH-717, and MOH 412 from both MOH-711 and
|
|
6
|
+
* MOH-745. A register cannot name its parent as a constant, then: whichever
|
|
7
|
+
* report it named, the trail would be wrong for everyone arriving from the
|
|
8
|
+
* other one. The link carries `from`, and the trail is resolved from it.
|
|
9
|
+
*/
|
|
10
|
+
export interface ReportParent {
|
|
11
|
+
label: string;
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const REPORT_PARENTS: Record<string, ReportParent> = {
|
|
16
|
+
'acf-summary': { label: 'ACF Facility Summary Tool', path: '/acf-summary' },
|
|
17
|
+
'moh-240': { label: 'MOH-240 Register', path: '/moh-240' },
|
|
18
|
+
'moh-505': { label: 'MOH-505 Report', path: '/moh-505' },
|
|
19
|
+
'moh-705a': { label: 'MOH-705A Report', path: '/moh-705a' },
|
|
20
|
+
'moh-705b': { label: 'MOH-705B Report', path: '/moh-705b' },
|
|
21
|
+
'moh-706': { label: 'MOH-706 Report', path: '/moh-706' },
|
|
22
|
+
'moh-710': { label: 'MOH-710 Report', path: '/moh-710' },
|
|
23
|
+
'moh-711': { label: 'MOH-711 Report', path: '/moh-711' },
|
|
24
|
+
'moh-717': { label: 'MOH-717 Report', path: '/moh-717' },
|
|
25
|
+
'moh-729b': { label: 'MOH 729B F-MAPS Report', path: '/moh-729b' },
|
|
26
|
+
'moh-731': { label: 'MOH-731 Report', path: '/moh-731' },
|
|
27
|
+
'moh-740': { label: 'MOH-740 Report', path: '/moh-740' },
|
|
28
|
+
'moh-745': { label: 'MOH-745 Report', path: '/moh-745' },
|
|
29
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type RegisterColumn } from './register-columns';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serialises line-list rows to CSV.
|
|
5
|
+
*
|
|
6
|
+
* Built from the data rather than from the rendered table: with pagination the
|
|
7
|
+
* DOM only ever holds one page, so scraping it would quietly export 50 rows
|
|
8
|
+
* while looking like it exported all 2,000 -- the worst way for an export to
|
|
9
|
+
* fail. The trade-off is that the file carries the API's own fields rather
|
|
10
|
+
* than the register's printed column headings, which is what a spreadsheet
|
|
11
|
+
* wants anyway.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Forces text Excel cannot mangle. A cell starting with `=`, `+`, `-` or `@`
|
|
16
|
+
* is parsed as a formula (`+2547...` becomes `#NAME?`), and a long digit-only
|
|
17
|
+
* cell is reinterpreted as a number (leading zeros dropped, switched to
|
|
18
|
+
* scientific notation), so both are emitted as an explicit text formula
|
|
19
|
+
* (`"..."` quoted inside `=`, which Excel renders as literal text).
|
|
20
|
+
*/
|
|
21
|
+
const FORMULA_PREFIX = /^[=+\-@]/;
|
|
22
|
+
const LONG_DIGITS = /^\d{9,}$/;
|
|
23
|
+
|
|
24
|
+
function escapeCell(value: unknown, forceText = false): string {
|
|
25
|
+
const text = value === null || value === undefined ? '' : String(value).replace(/\s+/g, ' ').trim();
|
|
26
|
+
|
|
27
|
+
if (forceText && text !== '') {
|
|
28
|
+
return `="${text.replace(/"/g, '""')}"`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (text !== '' && (FORMULA_PREFIX.test(text) || LONG_DIGITS.test(text))) {
|
|
32
|
+
return `="${text.replace(/"/g, '""')}"`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Serialises using the register's own columns, so the file matches the form */
|
|
39
|
+
export function columnsToCsv(
|
|
40
|
+
rows: Array<Record<string, any>>,
|
|
41
|
+
columns: RegisterColumn[],
|
|
42
|
+
options?: {
|
|
43
|
+
/** A second header row (e.g. the printed form's column letters) */
|
|
44
|
+
subHeaderRow?: string[];
|
|
45
|
+
},
|
|
46
|
+
): string {
|
|
47
|
+
const headerRows = [
|
|
48
|
+
columns.map((column) => escapeCell(column.header)).join(','),
|
|
49
|
+
...(options?.subHeaderRow ? [options.subHeaderRow.map((cell) => escapeCell(cell)).join(',')] : []),
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
return [
|
|
53
|
+
...headerRows,
|
|
54
|
+
...rows.map((row, index) => columns.map((column) => escapeCell(column.value(row, index), column.text)).join(',')),
|
|
55
|
+
].join('\r\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Fallback for registers with no column spec yet: the response's own fields */
|
|
59
|
+
export function rowsToCsv(rows: Array<Record<string, any>>): string {
|
|
60
|
+
if (!rows.length) {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Union of keys, not just the first row's: endpoints omit fields that are
|
|
65
|
+
null for a given client, so keying off row one drops columns. */
|
|
66
|
+
const keys = new Set<string>();
|
|
67
|
+
rows.forEach((row) => Object.keys(row).forEach((key) => keys.add(key)));
|
|
68
|
+
const columns = Array.from(keys);
|
|
69
|
+
|
|
70
|
+
return [
|
|
71
|
+
columns.map((column) => escapeCell(column)).join(','),
|
|
72
|
+
...rows.map((row) => columns.map((column) => escapeCell(row[column])).join(',')),
|
|
73
|
+
].join('\r\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Prompts the browser to save `csv` under `filename` */
|
|
77
|
+
export function downloadCsv(csv: string, filename: string): void {
|
|
78
|
+
// the BOM is what makes Excel read the file as UTF-8 rather than latin-1
|
|
79
|
+
const blob = new Blob([`${csv}`], { type: 'text/csv;charset=utf-8;' });
|
|
80
|
+
const url = URL.createObjectURL(blob);
|
|
81
|
+
const link = document.createElement('a');
|
|
82
|
+
|
|
83
|
+
link.href = url;
|
|
84
|
+
link.download = filename;
|
|
85
|
+
document.body.appendChild(link);
|
|
86
|
+
link.click();
|
|
87
|
+
document.body.removeChild(link);
|
|
88
|
+
URL.revokeObjectURL(url);
|
|
89
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { openmrsFetch, useSession } from '@openmrs/esm-framework';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The display name of the location(s) a register was generated for.
|
|
6
|
+
*
|
|
7
|
+
* Read from the link's `locationUuids` rather than the session: the user may
|
|
8
|
+
* have switched location since generating the report, and the register must
|
|
9
|
+
* name the location its rows came from. The session location answers without
|
|
10
|
+
* a request when it is the one asked for; any other is looked up. Several
|
|
11
|
+
* uuids (comma-separated) are named in order.
|
|
12
|
+
*/
|
|
13
|
+
export function useLocationName(locationUuids: string | null | undefined): string {
|
|
14
|
+
const session = useSession();
|
|
15
|
+
const uuids = (locationUuids ?? '')
|
|
16
|
+
.split(',')
|
|
17
|
+
.map((uuid) => uuid.trim())
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
const key = uuids.join(',');
|
|
20
|
+
const sessionUuid = session?.sessionLocation?.uuid;
|
|
21
|
+
const sessionName = session?.sessionLocation?.display;
|
|
22
|
+
|
|
23
|
+
const [names, setNames] = useState<Record<string, string>>({});
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const missing = uuids.filter((uuid) => uuid !== sessionUuid);
|
|
27
|
+
if (missing.length === 0) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const abortController = new AbortController();
|
|
32
|
+
|
|
33
|
+
Promise.all(
|
|
34
|
+
missing.map((uuid) =>
|
|
35
|
+
openmrsFetch(`/ws/rest/v1/location/${uuid}?v=custom:(display)`, { signal: abortController.signal })
|
|
36
|
+
.then((response) => response.json())
|
|
37
|
+
.then((location) => [uuid, location?.display ?? ''] as const)
|
|
38
|
+
.catch(() => [uuid, ''] as const),
|
|
39
|
+
),
|
|
40
|
+
).then((found) => {
|
|
41
|
+
if (!abortController.signal.aborted) {
|
|
42
|
+
setNames(Object.fromEntries(found.filter(([, name]) => name)));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return () => abortController.abort();
|
|
47
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
48
|
+
}, [key, sessionUuid]);
|
|
49
|
+
|
|
50
|
+
return uuids
|
|
51
|
+
.map((uuid) => (uuid === sessionUuid ? sessionName : names[uuid]))
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.join(', ');
|
|
54
|
+
}
|