@ampath/esm-reports-app 1.0.0-next.79 → 1.0.0-next.80
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/0.css +1 -0
- package/dist/0.css.map +1 -0
- package/dist/0.js +1 -0
- package/dist/0.js.map +1 -0
- package/dist/410.js +1 -1
- package/dist/410.js.map +1 -1
- package/dist/600.js +1 -1
- package/dist/600.js.map +1 -1
- package/dist/757.js +1 -0
- package/dist/757.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 +1 -1
- package/src/common/report-filters/report-filters.scss +15 -6
- package/src/common/report-page/download-sheet-pdf.ts +193 -27
- package/src/common/report-register/report-parents.ts +2 -0
- package/src/dashboard/report-catalog.ts +18 -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 +469 -310
- package/src/registers/moh-362/moh-362-lab-register.scss +412 -0
- package/src/registers/moh-362/moh-362-linkage-register.component.tsx +188 -93
- package/src/registers/moh-362/moh-362-linkage-register.scss +91 -0
- package/src/registers/moh-362/moh-362.component.tsx +33 -15
- package/src/registers/moh-362/moh-362.types.ts +15 -25
- 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 +37 -3
- package/src/registers/register-file-name.ts +18 -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/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/moh-731.component.tsx +10 -7
- package/src/reports/moh-731/moh-731.scss +3 -6
- package/src/reports/moh-731/summary/moh-731-summary.config.ts +2 -4
- package/src/reports/moh-731/summary/moh-731-summary.scss +5 -0
- package/src/resources/moh-267.resource.ts +27 -0
- package/src/resources/moh-729b.resource.ts +25 -0
- package/src/resources/presumptive-tb.resource.ts +27 -0
- package/src/root.component.tsx +14 -0
- package/dist/177.css +0 -1
- package/dist/177.css.map +0 -1
- package/dist/177.js +0 -1
- package/dist/177.js.map +0 -1
- package/dist/580.js +0 -1
- package/dist/580.js.map +0 -1
- package/src/registers/moh-362/moh-362-register.scss +0 -222
|
@@ -1,117 +1,212 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
+
import { usePageScale } from '../../common/report-page';
|
|
3
4
|
import { clientName, formDate, text, type Moh362Row } from './moh-362.types';
|
|
4
|
-
import
|
|
5
|
+
import sheet from './moh-362-lab-register.scss';
|
|
6
|
+
import styles from './moh-362-linkage-register.scss';
|
|
5
7
|
|
|
6
8
|
interface Moh362LinkageRegisterProps {
|
|
7
9
|
rows: Moh362Row[];
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
/** Rows ruled on each page of the paper form */
|
|
13
|
+
const ROWS_PER_PAGE = 15;
|
|
14
|
+
|
|
15
|
+
/** A4 landscape less its 8mm margins: the page as drawn on screen, inside the report card */
|
|
16
|
+
const PAGE_WIDTH_MM = 297 - 2 * 8;
|
|
17
|
+
|
|
10
18
|
const COLUMN_IDS = 'abcdefghij'.split('');
|
|
11
19
|
|
|
20
|
+
/** Column widths, measured off page 6 of the form, as fractions of the sheet */
|
|
21
|
+
const COLUMN_WIDTHS = [82, 204, 271, 180, 134, 193, 286, 147, 134, 182];
|
|
22
|
+
const COLUMN_TOTAL = COLUMN_WIDTHS.reduce((sum, width) => sum + width, 0);
|
|
23
|
+
|
|
24
|
+
/** Columns split into an upper and a lower cell: (b), (d) and (g) */
|
|
25
|
+
const SPLIT_COLUMNS = new Set(['b', 'd', 'g']);
|
|
26
|
+
|
|
27
|
+
const paginate = (rows: Moh362Row[]) => {
|
|
28
|
+
const pages: Moh362Row[][] = [];
|
|
29
|
+
for (let start = 0; start < rows.length; start += ROWS_PER_PAGE) {
|
|
30
|
+
pages.push(rows.slice(start, start + ROWS_PER_PAGE));
|
|
31
|
+
}
|
|
32
|
+
return pages.length ? pages : [[]];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** A heading holding two labels: upper cell over lower cell */
|
|
36
|
+
const SplitHeader: React.FC<{ upper: React.ReactNode; lower: React.ReactNode }> = ({ upper, lower }) => (
|
|
37
|
+
<th className={styles.splitHeader}>
|
|
38
|
+
<div className={styles.splitStack}>
|
|
39
|
+
<span>{upper}</span>
|
|
40
|
+
<span>{lower}</span>
|
|
41
|
+
</div>
|
|
42
|
+
</th>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
/** One client: two ruled lines, columns (b), (d) and (g) split into upper and lower cells */
|
|
46
|
+
const ClientRows: React.FC<{ row?: Moh362Row; serial: number }> = ({ row, serial }) => {
|
|
47
|
+
if (!row) {
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<tr className={styles.clientUpper}>
|
|
51
|
+
{COLUMN_IDS.map((id) =>
|
|
52
|
+
SPLIT_COLUMNS.has(id) ? <td key={id} className={sheet.upperCell} /> : <td key={id} rowSpan={2} />,
|
|
53
|
+
)}
|
|
54
|
+
</tr>
|
|
55
|
+
<tr className={styles.clientLower}>
|
|
56
|
+
{[...SPLIT_COLUMNS].map((id) => (
|
|
57
|
+
<td key={id} className={sheet.lowerCell} />
|
|
58
|
+
))}
|
|
59
|
+
</tr>
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<tr className={styles.clientUpper}>
|
|
67
|
+
<td rowSpan={2}>{serial}</td>
|
|
68
|
+
<td className={sheet.upperCell}>{text(row.id_number)}</td>
|
|
69
|
+
<td rowSpan={2} className={sheet.left}>
|
|
70
|
+
<span className={sheet.clamp}>{clientName(row)}</span>
|
|
71
|
+
</td>
|
|
72
|
+
<td className={sheet.upperCell}>{text(row.telephone)}</td>
|
|
73
|
+
<td rowSpan={2}>{text(row.client_tested_as)}</td>
|
|
74
|
+
<td rowSpan={2} className={sheet.left}>
|
|
75
|
+
<span className={sheet.clamp}>{text(row.referral_to)}</span>
|
|
76
|
+
</td>
|
|
77
|
+
<td className={`${sheet.upperCell} ${sheet.left}`}>{text(row.handed_to)}</td>
|
|
78
|
+
<td rowSpan={2}>{formDate(row.art_start_date)}</td>
|
|
79
|
+
<td rowSpan={2}>{text(row.ccc_number)}</td>
|
|
80
|
+
<td rowSpan={2} className={sheet.left}>
|
|
81
|
+
<span className={sheet.clamp}>{text(row.screening_remarks)}</span>
|
|
82
|
+
</td>
|
|
83
|
+
</tr>
|
|
84
|
+
<tr className={styles.clientLower}>
|
|
85
|
+
<td className={sheet.lowerCell}>{text(row.nupi)}</td>
|
|
86
|
+
<td className={`${sheet.lowerCell} ${sheet.left}`}>{text(row.residence)}</td>
|
|
87
|
+
{/* the receiving health worker's cadre is not recorded */}
|
|
88
|
+
<td className={sheet.lowerCell} />
|
|
89
|
+
</tr>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
12
94
|
/**
|
|
13
|
-
*
|
|
14
|
-
* the
|
|
15
|
-
* columns (a) to (j), with the total linked at the foot.
|
|
95
|
+
* One A4 landscape page of the register: 15 ruled client rows, and on the last
|
|
96
|
+
* page the total of clients linked across the whole register, in one frame.
|
|
16
97
|
*/
|
|
17
|
-
const
|
|
18
|
-
|
|
98
|
+
const LinkageRegisterPage: React.FC<{
|
|
99
|
+
rows: Moh362Row[];
|
|
100
|
+
firstSerial: number;
|
|
101
|
+
page: number;
|
|
102
|
+
pages: number;
|
|
103
|
+
scale: number;
|
|
104
|
+
/** clients linked in the whole register, given only to the last page */
|
|
105
|
+
totalLinked?: number;
|
|
106
|
+
}> = ({ rows, firstSerial, page, pages, scale, totalLinked }) => (
|
|
107
|
+
<section className={sheet.page} data-register-page style={{ '--page-zoom': scale } as React.CSSProperties}>
|
|
108
|
+
<div className={sheet.runningHead}>
|
|
109
|
+
<span>Ministry Of Health</span>
|
|
110
|
+
<span>MOH 362 HTS LAB, REFERRAL AND LINKAGE REGISTER</span>
|
|
111
|
+
<span>Ver. July 2023</span>
|
|
112
|
+
</div>
|
|
113
|
+
<h4 className={`${sheet.title} ${styles.title}`}>REFERRAL AND LINKAGE REGISTER</h4>
|
|
19
114
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Strategy
|
|
51
|
-
<br />
|
|
52
|
-
(VCT/IT/HIVST
|
|
53
|
-
<br />
|
|
54
|
-
/SNS)
|
|
55
|
-
</th>
|
|
56
|
-
<th className={styles.wide}>Patient referred to</th>
|
|
57
|
-
<th className={styles.wide}>
|
|
58
|
-
Handed over to:
|
|
59
|
-
<br />
|
|
115
|
+
<div className={sheet.frame}>
|
|
116
|
+
<table className={`${sheet.register} ${styles.linkageTable}`}>
|
|
117
|
+
<colgroup>
|
|
118
|
+
{COLUMN_WIDTHS.map((width, index) => (
|
|
119
|
+
<col key={index} style={{ width: `${(width / COLUMN_TOTAL) * 100}%` }} />
|
|
120
|
+
))}
|
|
121
|
+
</colgroup>
|
|
122
|
+
<thead>
|
|
123
|
+
<tr className={styles.headingRow}>
|
|
124
|
+
<th>
|
|
125
|
+
Serial
|
|
126
|
+
<br />
|
|
127
|
+
Number
|
|
128
|
+
</th>
|
|
129
|
+
<SplitHeader
|
|
130
|
+
upper="National Identification Number"
|
|
131
|
+
lower="National Unique Patient Identifier (NUPI) Number"
|
|
132
|
+
/>
|
|
133
|
+
<th>Client Names</th>
|
|
134
|
+
<SplitHeader upper="Telephone Number" lower="Residence" />
|
|
135
|
+
<th>
|
|
136
|
+
Identification Strategy
|
|
137
|
+
<br />
|
|
138
|
+
<span className={styles.regular}>(VCT/IT/HIVST /SNS)</span>
|
|
139
|
+
</th>
|
|
140
|
+
<th>Patient referred to</th>
|
|
141
|
+
<th>
|
|
142
|
+
Handed over to:
|
|
143
|
+
<br />
|
|
144
|
+
<span className={styles.regular}>
|
|
60
145
|
(Indicate Name (upper cell)
|
|
61
146
|
<br />
|
|
62
147
|
and Cadre (lower cell)
|
|
63
|
-
</
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<tr className={styles.columnIds}>
|
|
73
|
-
{COLUMN_IDS.map((id) => (
|
|
74
|
-
<th key={id}>({id})</th>
|
|
75
|
-
))}
|
|
76
|
-
</tr>
|
|
77
|
-
</thead>
|
|
78
|
-
<tbody>
|
|
79
|
-
{rows.map((row, index) => (
|
|
80
|
-
<React.Fragment key={`${row.patient_id}-${row.serial_number}-${index}`}>
|
|
81
|
-
<tr>
|
|
82
|
-
<td rowSpan={2}>{index + 1}</td>
|
|
83
|
-
<td className={styles.upperCell}>{text(row.id_number)}</td>
|
|
84
|
-
<td rowSpan={2} className={styles.left}>
|
|
85
|
-
{clientName(row)}
|
|
86
|
-
</td>
|
|
87
|
-
<td className={styles.upperCell}>{text(row.telephone)}</td>
|
|
88
|
-
<td rowSpan={2}>{text(row.client_tested_as)}</td>
|
|
89
|
-
<td rowSpan={2} className={styles.left}>
|
|
90
|
-
{text(row.referral_to)}
|
|
91
|
-
</td>
|
|
92
|
-
<td className={`${styles.upperCell} ${styles.left}`}>{text(row.handed_to)}</td>
|
|
93
|
-
<td rowSpan={2}>{formDate(row.art_start_date)}</td>
|
|
94
|
-
<td rowSpan={2}>{text(row.ccc_number)}</td>
|
|
95
|
-
<td rowSpan={2} className={styles.left}>
|
|
96
|
-
{text(row.screening_remarks)}
|
|
97
|
-
</td>
|
|
98
|
-
</tr>
|
|
99
|
-
<tr>
|
|
100
|
-
<td className={styles.lowerCell}>{text(row.nupi)}</td>
|
|
101
|
-
<td className={styles.lowerCell}>{text(row.residence)}</td>
|
|
102
|
-
{/* the receiving health worker's cadre is not recorded */}
|
|
103
|
-
<td className={styles.lowerCell} />
|
|
104
|
-
</tr>
|
|
105
|
-
</React.Fragment>
|
|
148
|
+
</span>
|
|
149
|
+
</th>
|
|
150
|
+
<th>Date started on ART</th>
|
|
151
|
+
<th>CCC Number</th>
|
|
152
|
+
<th>Remarks</th>
|
|
153
|
+
</tr>
|
|
154
|
+
<tr className={styles.idRow}>
|
|
155
|
+
{COLUMN_IDS.map((id) => (
|
|
156
|
+
<th key={id}>({id})</th>
|
|
106
157
|
))}
|
|
158
|
+
</tr>
|
|
159
|
+
</thead>
|
|
160
|
+
<tbody>
|
|
161
|
+
{Array.from({ length: ROWS_PER_PAGE }, (_, index) => (
|
|
162
|
+
<ClientRows key={index} row={rows[index]} serial={firstSerial + index} />
|
|
163
|
+
))}
|
|
164
|
+
{totalLinked !== undefined && (
|
|
107
165
|
<tr className={styles.totalRow}>
|
|
108
|
-
<th colSpan={
|
|
166
|
+
<th colSpan={3} className={styles.totalLabel}>
|
|
109
167
|
Totals Clients Linked
|
|
110
168
|
</th>
|
|
111
|
-
<td
|
|
169
|
+
<td colSpan={6} className={styles.totalFill} />
|
|
170
|
+
<td className={styles.totalFigure}>{totalLinked}</td>
|
|
112
171
|
</tr>
|
|
113
|
-
|
|
114
|
-
</
|
|
172
|
+
)}
|
|
173
|
+
</tbody>
|
|
174
|
+
</table>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div className={sheet.pageFoot}>
|
|
178
|
+
<span>
|
|
179
|
+
Page {page} of {pages}
|
|
180
|
+
</span>
|
|
181
|
+
<span>Ver. July 2023</span>
|
|
182
|
+
</div>
|
|
183
|
+
</section>
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* REFERRAL AND LINKAGE REGISTER (Ver. July 2023), page 6 of the MOH 362 form,
|
|
188
|
+
* as A4 landscape pages: the clients who tested HIV positive and were linked to
|
|
189
|
+
* care (started on ART), columns (a) to (j), with the total linked at the foot
|
|
190
|
+
* of the last page.
|
|
191
|
+
*/
|
|
192
|
+
const Moh362LinkageRegister: React.FC<Moh362LinkageRegisterProps> = ({ rows }) => {
|
|
193
|
+
const { ref, scale } = usePageScale<HTMLDivElement>(PAGE_WIDTH_MM);
|
|
194
|
+
const pages = paginate(rows);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div className={sheet.reportFrame}>
|
|
198
|
+
<div className={sheet.viewport} ref={ref}>
|
|
199
|
+
{pages.map((pageRows, index) => (
|
|
200
|
+
<LinkageRegisterPage
|
|
201
|
+
key={index}
|
|
202
|
+
rows={pageRows}
|
|
203
|
+
firstSerial={index * ROWS_PER_PAGE + 1}
|
|
204
|
+
page={index + 1}
|
|
205
|
+
pages={pages.length}
|
|
206
|
+
scale={scale}
|
|
207
|
+
totalLinked={index === pages.length - 1 ? rows.length : undefined}
|
|
208
|
+
/>
|
|
209
|
+
))}
|
|
115
210
|
</div>
|
|
116
211
|
</div>
|
|
117
212
|
);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* REFERRAL AND LINKAGE REGISTER as A4 landscape pages. The page, frame, ruled
|
|
2
|
+
table and split cells come from the lab register's sheet styles; these are
|
|
3
|
+
page 6's own measurements: a shorter heading in larger bold type, and taller
|
|
4
|
+
client rows, 15 to a page, with the total of clients linked on the last. */
|
|
5
|
+
|
|
6
|
+
$ink: #000;
|
|
7
|
+
$rule: 0.6pt solid $ink;
|
|
8
|
+
$thick: 1.4pt solid $ink;
|
|
9
|
+
$dotted: 0.6pt dotted $ink;
|
|
10
|
+
$shade: #f2f2f2; // the lower cells and the totals row
|
|
11
|
+
|
|
12
|
+
$heading-h: 21mm;
|
|
13
|
+
$ids-h: 5.2mm;
|
|
14
|
+
$line-h: 4.6mm; // half a client row: each client takes two lines
|
|
15
|
+
$total-h: 6.7mm;
|
|
16
|
+
|
|
17
|
+
/* the title is set in a serif on this page of the form */
|
|
18
|
+
.title {
|
|
19
|
+
font-family: Georgia, 'Times New Roman', serif;
|
|
20
|
+
font-size: 10pt;
|
|
21
|
+
line-height: 1.2;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.linkageTable .headingRow th {
|
|
25
|
+
height: $heading-h;
|
|
26
|
+
font-size: 7.5pt;
|
|
27
|
+
font-weight: 700;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* the strategy and handed-over headings keep their codes in regular type */
|
|
31
|
+
.linkageTable .headingRow .regular {
|
|
32
|
+
font-weight: 400;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.linkageTable .idRow th {
|
|
36
|
+
height: $ids-h;
|
|
37
|
+
font-size: 8pt;
|
|
38
|
+
font-weight: 700;
|
|
39
|
+
border-top: $thick;
|
|
40
|
+
border-bottom: $thick;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.linkageTable .clientUpper td,
|
|
44
|
+
.linkageTable .clientLower td {
|
|
45
|
+
height: $line-h;
|
|
46
|
+
font-size: 7pt;
|
|
47
|
+
white-space: nowrap;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* a heading holding two labels: upper cell over lower cell, split by a dotted rule */
|
|
51
|
+
.splitHeader {
|
|
52
|
+
padding: 0 !important;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.splitStack {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
height: $heading-h;
|
|
59
|
+
|
|
60
|
+
span {
|
|
61
|
+
display: flex;
|
|
62
|
+
flex: 1;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
padding: 0 0.5mm;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
span:first-child {
|
|
69
|
+
border-bottom: $dotted;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.linkageTable .totalRow > * {
|
|
74
|
+
height: $total-h;
|
|
75
|
+
font-size: 7pt;
|
|
76
|
+
font-weight: 700;
|
|
77
|
+
border-top: $thick;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.totalLabel {
|
|
81
|
+
text-align: left !important;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.totalFill,
|
|
85
|
+
.totalFigure {
|
|
86
|
+
background-color: $shade;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.totalFigure {
|
|
90
|
+
font-size: 8pt !important;
|
|
91
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
2
|
import { useSearchParams } from 'react-router-dom';
|
|
3
3
|
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@carbon/react';
|
|
4
4
|
import { useSession } from '@openmrs/esm-framework';
|
|
5
5
|
|
|
6
6
|
import { ReportFiltersComponent, type ReportPeriod, useRestorePeriod } from '../../common/report-filters';
|
|
7
7
|
import { showReportError } from '../../common/report-error';
|
|
8
|
+
import { downloadSheetsPdf } from '../../common/report-page';
|
|
9
|
+
import { registerFileName } from '../register-file-name';
|
|
8
10
|
import { ReportPlaceholder, useGeneratedLocation } from '../../common/report-placeholder';
|
|
9
11
|
import { ReportSkeleton } from '../../common/report-skeleton';
|
|
10
12
|
import { getMoh362 } from '../../resources/moh-362.resource';
|
|
@@ -47,6 +49,19 @@ const Moh362Register: React.FC = () => {
|
|
|
47
49
|
|
|
48
50
|
useRestorePeriod(generate);
|
|
49
51
|
|
|
52
|
+
/* Download PDF saves the whole register, whichever sheet is open: the HTS lab
|
|
53
|
+
register's pages, then the referral and linkage register's, each an A4
|
|
54
|
+
landscape page. The file is made from copies, so the pages on screen are
|
|
55
|
+
left as they are. */
|
|
56
|
+
const sheetsRef = useRef<HTMLDivElement>(null);
|
|
57
|
+
const downloadPdf = async () => {
|
|
58
|
+
const pages = Array.from(sheetsRef.current?.querySelectorAll<HTMLElement>('[data-register-page]') ?? []);
|
|
59
|
+
const name = registerFileName('MOH-362', session?.sessionLocation?.display ?? '', period);
|
|
60
|
+
/* the register is styled by its own stylesheets in Arial and Georgia only,
|
|
61
|
+
and runs to many pages */
|
|
62
|
+
await downloadSheetsPdf(pages, name, { orientation: 'landscape', stylesheetsOnly: true, image: 'jpeg' });
|
|
63
|
+
};
|
|
64
|
+
|
|
50
65
|
/* the open sheet is kept in the URL */
|
|
51
66
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
52
67
|
const selectedSheet = searchParams.get('sheet') === 'linkage' ? 1 : 0;
|
|
@@ -71,26 +86,29 @@ const Moh362Register: React.FC = () => {
|
|
|
71
86
|
generatedPeriod={period}
|
|
72
87
|
defaultTab="custom"
|
|
73
88
|
backLink={{ label: 'All registers', to: '/?tab=registers' }}
|
|
89
|
+
onDownloadPdf={downloadPdf}
|
|
74
90
|
/>
|
|
75
91
|
{isLoading && <ReportSkeleton />}
|
|
76
92
|
{!isLoading && !isGenerated && (
|
|
77
93
|
<ReportPlaceholder reportName="MOH 362 register" isLocationChanged={rows !== null && hasLocationChanged} />
|
|
78
94
|
)}
|
|
79
95
|
{!isLoading && isGenerated && (
|
|
80
|
-
<
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
<div ref={sheetsRef}>
|
|
97
|
+
<Tabs selectedIndex={selectedSheet} onChange={selectSheet}>
|
|
98
|
+
<TabList aria-label="MOH 362 registers" className={styles.sheetTabs}>
|
|
99
|
+
<Tab>HTS Lab Register</Tab>
|
|
100
|
+
<Tab>Referral and Linkage Register</Tab>
|
|
101
|
+
</TabList>
|
|
102
|
+
<TabPanels>
|
|
103
|
+
<TabPanel className={styles.sheetPanel}>
|
|
104
|
+
<Moh362LabRegister rows={rows} />
|
|
105
|
+
</TabPanel>
|
|
106
|
+
<TabPanel className={styles.sheetPanel}>
|
|
107
|
+
<Moh362LinkageRegister rows={rows.filter(isLinked)} />
|
|
108
|
+
</TabPanel>
|
|
109
|
+
</TabPanels>
|
|
110
|
+
</Tabs>
|
|
111
|
+
</div>
|
|
94
112
|
)}
|
|
95
113
|
</>
|
|
96
114
|
);
|
|
@@ -19,25 +19,32 @@ export interface Moh362Row {
|
|
|
19
19
|
telephone?: string | null;
|
|
20
20
|
date_of_visit?: string | null;
|
|
21
21
|
hts_provider?: string | null;
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/** Married, Widowed, Single, Divorced or Separated */
|
|
23
|
+
marital_status?: string | null;
|
|
24
|
+
/** Gen Pop or KVP */
|
|
25
|
+
population_type?: string | null;
|
|
24
26
|
sub_population?: string | null;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
/** Facility or Community */
|
|
28
|
+
setting?: string | null;
|
|
29
|
+
/** Negative, Positive or Inconclusive */
|
|
30
|
+
hiv_test1?: string | null;
|
|
27
31
|
kit1_name?: string | null;
|
|
28
32
|
lot1?: string | null;
|
|
29
33
|
expiry1?: string | null;
|
|
30
|
-
hiv_test2?:
|
|
34
|
+
hiv_test2?: string | null;
|
|
31
35
|
kit2_name?: string | null;
|
|
32
36
|
lot2?: string | null;
|
|
33
37
|
expiry2?: string | null;
|
|
34
|
-
hiv_test3?:
|
|
38
|
+
hiv_test3?: string | null;
|
|
35
39
|
kit3_name?: string | null;
|
|
36
40
|
lot3?: string | null;
|
|
37
41
|
expiry3?: string | null;
|
|
42
|
+
/** Positive, Negative or Inconclusive */
|
|
38
43
|
final_result?: string | null;
|
|
44
|
+
/** Yes, No or NA */
|
|
39
45
|
discordant_couple?: string | null;
|
|
40
|
-
|
|
46
|
+
/** PrEP, PEP, VMMC, PMTCT, as a list, or NA */
|
|
47
|
+
referral_services?: string | null;
|
|
41
48
|
client_tested_as?: string | null;
|
|
42
49
|
referral_to?: string | null;
|
|
43
50
|
residence?: string | null;
|
|
@@ -58,24 +65,7 @@ export const clientName = (row: Moh362Row) =>
|
|
|
58
65
|
row.full_name || [row.first_name, row.middle_name, row.last_name].filter(Boolean).join(' ');
|
|
59
66
|
|
|
60
67
|
/** Tested positive and linked to care: started on ART */
|
|
61
|
-
export const isLinked = (row: Moh362Row) => row.final_result === '
|
|
62
|
-
|
|
63
|
-
/** An HIV test result (the concept the ETL returns) as the form's code: N, P, I */
|
|
64
|
-
export const testResult = (value: unknown) => {
|
|
65
|
-
switch (String(value ?? '')) {
|
|
66
|
-
case '664':
|
|
67
|
-
return 'N';
|
|
68
|
-
case '703':
|
|
69
|
-
return 'P';
|
|
70
|
-
case '1138':
|
|
71
|
-
return 'I';
|
|
72
|
-
default:
|
|
73
|
-
return text(value);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
/** The final result as the form's code: N, P, Ic (inconclusive), NA */
|
|
78
|
-
export const finalResult = (value: unknown) => (String(value ?? '') === 'I' ? 'Ic' : text(value));
|
|
68
|
+
export const isLinked = (row: Moh362Row) => row.final_result === 'Positive' && Boolean(row.art_start_date);
|
|
79
69
|
|
|
80
70
|
export const ageOf = (row: Moh362Row) => {
|
|
81
71
|
const age = Number(row.age);
|