@sasindi/printlabtestresults 1.0.1
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/LICENSE +354 -0
- package/README.md +167 -0
- package/dist/117.js +1 -0
- package/dist/117.js.map +1 -0
- package/dist/127.js +43 -0
- package/dist/127.js.map +1 -0
- package/dist/145.js +22 -0
- package/dist/145.js.map +1 -0
- package/dist/197.js +1 -0
- package/dist/197.js.map +1 -0
- package/dist/237.js +1 -0
- package/dist/237.js.map +1 -0
- package/dist/340.js +1 -0
- package/dist/340.js.map +1 -0
- package/dist/362.js +1 -0
- package/dist/362.js.map +1 -0
- package/dist/480.js +1 -0
- package/dist/480.js.map +1 -0
- package/dist/517.js +1 -0
- package/dist/517.js.map +1 -0
- package/dist/529.js +1 -0
- package/dist/529.js.map +1 -0
- package/dist/538.js +1 -0
- package/dist/538.js.map +1 -0
- package/dist/578.js +1 -0
- package/dist/578.js.map +1 -0
- package/dist/652.js +1 -0
- package/dist/652.js.map +1 -0
- package/dist/668.js +1 -0
- package/dist/668.js.map +1 -0
- package/dist/7.js +1 -0
- package/dist/7.js.map +1 -0
- package/dist/749.js +1 -0
- package/dist/749.js.map +1 -0
- package/dist/898.js +1 -0
- package/dist/898.js.map +1 -0
- package/dist/964.js +1 -0
- package/dist/964.js.map +1 -0
- package/dist/976.js +1 -0
- package/dist/976.js.map +1 -0
- package/dist/977.js +1 -0
- package/dist/977.js.map +1 -0
- package/dist/98.js +1 -0
- package/dist/98.js.map +1 -0
- package/dist/997.js +1 -0
- package/dist/997.js.map +1 -0
- package/dist/main.js +6 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-printlabtestresults.js +6 -0
- package/dist/openmrs-esm-printlabtestresults.js.buildmanifest.json +771 -0
- package/dist/openmrs-esm-printlabtestresults.js.map +1 -0
- package/dist/routes.json +1 -0
- package/package.json +102 -0
- package/src/config-schema.ts +21 -0
- package/src/constants.ts +1 -0
- package/src/declarations.d.ts +23 -0
- package/src/index.ts +25 -0
- package/src/print-lab-test-results.component.tsx +608 -0
- package/src/print-lab-test-results.scss +299 -0
- package/src/root.component.test.tsx +12 -0
- package/src/root.component.tsx +13 -0
- package/src/root.scss +5 -0
- package/src/routes.json +12 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import styles from './print-lab-test-results.scss';
|
|
4
|
+
|
|
5
|
+
interface LabResultRow {
|
|
6
|
+
phnNo: string;
|
|
7
|
+
givenName: string;
|
|
8
|
+
testName: string;
|
|
9
|
+
gender: string;
|
|
10
|
+
age: string | number;
|
|
11
|
+
orderBy: string;
|
|
12
|
+
orderDateTime: string;
|
|
13
|
+
section: string;
|
|
14
|
+
barcodeNumber: string;
|
|
15
|
+
collectBy: string;
|
|
16
|
+
collectDate: string;
|
|
17
|
+
serial: number | string;
|
|
18
|
+
remark: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ParameterRow {
|
|
22
|
+
parameterName: string;
|
|
23
|
+
value: string;
|
|
24
|
+
units: string;
|
|
25
|
+
ranges: string;
|
|
26
|
+
flag?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const PrintLabTestResults: React.FC = () => {
|
|
30
|
+
const { t } = useTranslation();
|
|
31
|
+
|
|
32
|
+
// Location & Session state
|
|
33
|
+
const [locationUuid, setLocationUuid] = useState<string>('');
|
|
34
|
+
const [instituteName, setInstituteName] = useState<string>('Loading location...');
|
|
35
|
+
const [data, setData] = useState<LabResultRow[]>([]);
|
|
36
|
+
const [loading, setLoading] = useState<boolean>(true);
|
|
37
|
+
|
|
38
|
+
// Filter & Search state
|
|
39
|
+
const [sectionFilter, setSectionFilter] = useState<string>('ALL');
|
|
40
|
+
const [searchQuery, setSearchQuery] = useState<string>('');
|
|
41
|
+
|
|
42
|
+
// Modal state
|
|
43
|
+
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
|
44
|
+
const [selectedRow, setSelectedRow] = useState<LabResultRow | null>(null);
|
|
45
|
+
const [patientDetails, setPatientDetails] = useState<{
|
|
46
|
+
fullName: string;
|
|
47
|
+
section: string;
|
|
48
|
+
phn: string;
|
|
49
|
+
age: string;
|
|
50
|
+
gender: string;
|
|
51
|
+
testName: string;
|
|
52
|
+
remark: string;
|
|
53
|
+
date: string;
|
|
54
|
+
time: string;
|
|
55
|
+
} | null>(null);
|
|
56
|
+
|
|
57
|
+
const [parameters, setParameters] = useState<ParameterRow[]>([]);
|
|
58
|
+
const [loadingParams, setLoadingParams] = useState<boolean>(false);
|
|
59
|
+
|
|
60
|
+
// Pagination state
|
|
61
|
+
const [currentPage, setCurrentPage] = useState<number>(1);
|
|
62
|
+
const [pageSize, setPageSize] = useState<number>(25);
|
|
63
|
+
|
|
64
|
+
// Initialize Session and Location Context
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
async function initSession() {
|
|
67
|
+
try {
|
|
68
|
+
const sessionRes = await fetch('/openmrs/ws/rest/v1/session');
|
|
69
|
+
if (!sessionRes.ok) throw new Error('Session request failed');
|
|
70
|
+
const sessionData = await sessionRes.json();
|
|
71
|
+
const personUuid = sessionData.user?.person?.uuid;
|
|
72
|
+
|
|
73
|
+
let locUuid = '';
|
|
74
|
+
|
|
75
|
+
if (personUuid) {
|
|
76
|
+
const personRes = await fetch(`/openmrs/ws/rest/v1/person/${personUuid}`);
|
|
77
|
+
if (personRes.ok) {
|
|
78
|
+
const personData = await personRes.json();
|
|
79
|
+
if (personData.attributes) {
|
|
80
|
+
personData.attributes.forEach((attr: any) => {
|
|
81
|
+
if (attr.display) {
|
|
82
|
+
const [key, value] = attr.display.split('=');
|
|
83
|
+
if (key && value) {
|
|
84
|
+
const cleanKey = key.trim();
|
|
85
|
+
if (cleanKey === 'Institute Id' || cleanKey === 'Institute') {
|
|
86
|
+
locUuid = value.trim();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!locUuid && sessionData.sessionLocation?.uuid) {
|
|
96
|
+
locUuid = sessionData.sessionLocation.uuid;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (locUuid) {
|
|
100
|
+
setLocationUuid(locUuid);
|
|
101
|
+
const locRes = await fetch(`/openmrs/ws/rest/v1/location/${locUuid}`);
|
|
102
|
+
if (locRes.ok) {
|
|
103
|
+
const locData = await locRes.json();
|
|
104
|
+
setInstituteName(locData.display || 'OpenMRS Location');
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
setInstituteName('No Location Assigned');
|
|
108
|
+
setLoading(false);
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error('Error initializing location context:', err);
|
|
112
|
+
setInstituteName('Location Load Error');
|
|
113
|
+
setLoading(false);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
initSession();
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
// Fetch Table Data when locationUuid is set
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!locationUuid) return;
|
|
123
|
+
|
|
124
|
+
async function loadTableData() {
|
|
125
|
+
setLoading(true);
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(`/openmrs/ws/rest/v1/printlabreportresults/loaddetails/${locationUuid}`);
|
|
128
|
+
if (!response.ok) throw new Error('Failed to load details');
|
|
129
|
+
const rawData = await response.json();
|
|
130
|
+
|
|
131
|
+
// Map array response to LabResultRow objects
|
|
132
|
+
// Index mapping from legacy query:
|
|
133
|
+
// [0: phn, 1: givenname, 2: testname, 3: gender, 4: age, 5: orderby, 6: orderdatetime, 7: section, 8: barcodenumber, 9: labdocname, 10: collectioncdate, 11: id, 12: remark]
|
|
134
|
+
const mappedRows: LabResultRow[] = (rawData || []).map((row: any) => ({
|
|
135
|
+
phnNo: row[0] || '',
|
|
136
|
+
givenName: row[1] || '',
|
|
137
|
+
testName: row[2] || '',
|
|
138
|
+
gender: row[3] || '',
|
|
139
|
+
age: row[4] || '',
|
|
140
|
+
orderBy: row[5] || '',
|
|
141
|
+
orderDateTime: row[6] || '',
|
|
142
|
+
section: row[7] || '',
|
|
143
|
+
barcodeNumber: row[8] || '',
|
|
144
|
+
collectBy: row[9] || '',
|
|
145
|
+
collectDate: row[10] || '',
|
|
146
|
+
serial: row[11] || 0,
|
|
147
|
+
remark: row[12] || '',
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
setData(mappedRows);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.error('Error fetching print lab test results:', err);
|
|
153
|
+
setData([]);
|
|
154
|
+
} finally {
|
|
155
|
+
setLoading(false);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
loadTableData();
|
|
160
|
+
}, [locationUuid]);
|
|
161
|
+
|
|
162
|
+
// Filtered & Searched Data
|
|
163
|
+
const filteredData = useMemo(() => {
|
|
164
|
+
return data.filter((row) => {
|
|
165
|
+
// Section Filter
|
|
166
|
+
if (sectionFilter !== 'ALL' && row.section.toLowerCase() !== sectionFilter.toLowerCase()) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
// Search Filter
|
|
170
|
+
if (searchQuery.trim() !== '') {
|
|
171
|
+
const query = searchQuery.toLowerCase();
|
|
172
|
+
const matchBarcode = row.barcodeNumber.toLowerCase().includes(query);
|
|
173
|
+
const matchPhn = row.phnNo.toLowerCase().includes(query);
|
|
174
|
+
const matchName = row.givenName.toLowerCase().includes(query);
|
|
175
|
+
const matchTest = row.testName.toLowerCase().includes(query);
|
|
176
|
+
const matchOrderBy = row.orderBy.toLowerCase().includes(query);
|
|
177
|
+
const matchCollectBy = row.collectBy.toLowerCase().includes(query);
|
|
178
|
+
return matchBarcode || matchPhn || matchName || matchTest || matchOrderBy || matchCollectBy;
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
});
|
|
182
|
+
}, [data, sectionFilter, searchQuery]);
|
|
183
|
+
|
|
184
|
+
// Pagination slicing
|
|
185
|
+
const paginatedData = useMemo(() => {
|
|
186
|
+
const start = (currentPage - 1) * pageSize;
|
|
187
|
+
return filteredData.slice(start, start + pageSize);
|
|
188
|
+
}, [filteredData, currentPage, pageSize]);
|
|
189
|
+
|
|
190
|
+
const totalPages = Math.ceil(filteredData.length / pageSize) || 1;
|
|
191
|
+
|
|
192
|
+
// Open "View Results" Modal
|
|
193
|
+
const handleView = async (row: LabResultRow) => {
|
|
194
|
+
setSelectedRow(row);
|
|
195
|
+
|
|
196
|
+
const now = new Date();
|
|
197
|
+
const dateStr = now.toISOString().split('T')[0];
|
|
198
|
+
const timeStr = `${('0' + now.getHours()).slice(-2)}:${('0' + now.getMinutes()).slice(-2)}`;
|
|
199
|
+
|
|
200
|
+
setPatientDetails({
|
|
201
|
+
fullName: row.givenName,
|
|
202
|
+
section: row.section,
|
|
203
|
+
phn: row.phnNo,
|
|
204
|
+
age: String(row.age),
|
|
205
|
+
gender: row.gender,
|
|
206
|
+
testName: row.testName,
|
|
207
|
+
remark: row.remark,
|
|
208
|
+
date: dateStr,
|
|
209
|
+
time: timeStr,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
setIsModalOpen(true);
|
|
213
|
+
setLoadingParams(true);
|
|
214
|
+
setParameters([]);
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
let endpoint = '';
|
|
218
|
+
const testEnc = encodeURIComponent(row.testName);
|
|
219
|
+
if (row.section === 'OPD') {
|
|
220
|
+
endpoint = `/openmrs/ws/rest/v1/printlabreportresults/loadparameter/${testEnc}/${locationUuid}/${row.serial}`;
|
|
221
|
+
} else if (row.section === 'Clinic') {
|
|
222
|
+
endpoint = `/openmrs/ws/rest/v1/printlabreportresults/loadparameterclinic/${testEnc}/${locationUuid}/${row.serial}`;
|
|
223
|
+
} else if (row.section === 'Ward') {
|
|
224
|
+
endpoint = `/openmrs/ws/rest/v1/printlabreportresults/loadparameterward/${testEnc}/${locationUuid}/${row.serial}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (endpoint) {
|
|
228
|
+
const res = await fetch(endpoint);
|
|
229
|
+
if (res.ok) {
|
|
230
|
+
const paramData = await res.json();
|
|
231
|
+
// Array mapping: [0: parameterName, 1: value, 2: flag, 3: ranges, 4: units]
|
|
232
|
+
const mappedParams: ParameterRow[] = (paramData || []).map((p: any) => ({
|
|
233
|
+
parameterName: p[0] || '',
|
|
234
|
+
value: p[1] || '',
|
|
235
|
+
flag: p[2] || '',
|
|
236
|
+
ranges: p[3] || '',
|
|
237
|
+
units: p[4] || '',
|
|
238
|
+
}));
|
|
239
|
+
setParameters(mappedParams);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.error('Error loading parameters:', err);
|
|
244
|
+
} finally {
|
|
245
|
+
setLoadingParams(false);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Open PDF Report
|
|
250
|
+
const handlePrintReport = async (row: LabResultRow) => {
|
|
251
|
+
try {
|
|
252
|
+
let detailsEndpoint = '';
|
|
253
|
+
if (row.section === 'OPD') {
|
|
254
|
+
detailsEndpoint = `/openmrs/ws/rest/v1/printlabreportresults/labdetailsopd/${row.serial}`;
|
|
255
|
+
} else if (row.section === 'Clinic') {
|
|
256
|
+
detailsEndpoint = `/openmrs/ws/rest/v1/printlabreportresults/labdetailsclinic/${row.serial}`;
|
|
257
|
+
} else if (row.section === 'Ward') {
|
|
258
|
+
detailsEndpoint = `/openmrs/ws/rest/v1/printlabreportresults/labdetailsward/${row.serial}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (detailsEndpoint) {
|
|
262
|
+
const response = await fetch(detailsEndpoint);
|
|
263
|
+
if (response.ok) {
|
|
264
|
+
const labData = await response.json();
|
|
265
|
+
const encounterUuid = labData.encounteruuid || '';
|
|
266
|
+
const documentName = `${row.phnNo}@@@${row.testName}@@@${encounterUuid}`;
|
|
267
|
+
const filename = `${documentName}.pdf`;
|
|
268
|
+
const fileUrl = `${window.location.origin}/openmrs/owa/LabReportFileUploaded/${filename}`;
|
|
269
|
+
window.open(fileUrl, '_blank');
|
|
270
|
+
} else {
|
|
271
|
+
alert('Could not retrieve lab details for report PDF generation.');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
console.error('Error printing lab report:', err);
|
|
276
|
+
alert('Error fetching report PDF.');
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const getSectionBadgeClass = (section: string) => {
|
|
281
|
+
switch (section.toLowerCase()) {
|
|
282
|
+
case 'opd':
|
|
283
|
+
return styles.tagOpd;
|
|
284
|
+
case 'clinic':
|
|
285
|
+
return styles.tagClinic;
|
|
286
|
+
case 'ward':
|
|
287
|
+
return styles.tagWard;
|
|
288
|
+
default:
|
|
289
|
+
return styles.tagOpd;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<div className={styles.container}>
|
|
295
|
+
<div className={styles.card}>
|
|
296
|
+
<div className={styles.cardBody}>
|
|
297
|
+
|
|
298
|
+
{/* Header & Institute Name */}
|
|
299
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
|
|
300
|
+
<h2 className={styles.heading}>
|
|
301
|
+
{t('viewTestResultsHeading', 'View Test Results')}
|
|
302
|
+
</h2>
|
|
303
|
+
<div style={{ fontSize: '14px', fontWeight: 'bold', color: '#161616' }}>
|
|
304
|
+
<span>🏥 {instituteName}</span>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{/* Filters & Search Row */}
|
|
309
|
+
<div className={styles.filterRow}>
|
|
310
|
+
<div className={styles.filterGroup}>
|
|
311
|
+
<label htmlFor="sectionFilter" className={styles.labelCompact}>
|
|
312
|
+
Section:
|
|
313
|
+
</label>
|
|
314
|
+
<select
|
|
315
|
+
id="sectionFilter"
|
|
316
|
+
className={styles.selectCompact}
|
|
317
|
+
value={sectionFilter}
|
|
318
|
+
onChange={(e) => {
|
|
319
|
+
setSectionFilter(e.target.value);
|
|
320
|
+
setCurrentPage(1);
|
|
321
|
+
}}
|
|
322
|
+
>
|
|
323
|
+
<option value="ALL">All Sections</option>
|
|
324
|
+
<option value="OPD">OPD</option>
|
|
325
|
+
<option value="Clinic">Clinic</option>
|
|
326
|
+
<option value="Ward">Ward</option>
|
|
327
|
+
</select>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<div className={styles.filterGroup}>
|
|
331
|
+
<label htmlFor="searchInput" className={styles.labelCompact}>
|
|
332
|
+
Search:
|
|
333
|
+
</label>
|
|
334
|
+
<input
|
|
335
|
+
id="searchInput"
|
|
336
|
+
type="text"
|
|
337
|
+
placeholder="Search PHN, Barcode, Test Name, Patient..."
|
|
338
|
+
className={styles.searchInput}
|
|
339
|
+
value={searchQuery}
|
|
340
|
+
onChange={(e) => {
|
|
341
|
+
setSearchQuery(e.target.value);
|
|
342
|
+
setCurrentPage(1);
|
|
343
|
+
}}
|
|
344
|
+
/>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
{/* Table */}
|
|
349
|
+
<div className={styles.tableWrapper}>
|
|
350
|
+
<table className={styles.dataTable}>
|
|
351
|
+
<thead>
|
|
352
|
+
<tr>
|
|
353
|
+
<th>No</th>
|
|
354
|
+
<th>Section</th>
|
|
355
|
+
<th>Barcode No</th>
|
|
356
|
+
<th>PHN No</th>
|
|
357
|
+
<th>Patient Name</th>
|
|
358
|
+
<th>Gender</th>
|
|
359
|
+
<th>Age</th>
|
|
360
|
+
<th>Test Name</th>
|
|
361
|
+
<th>Order By</th>
|
|
362
|
+
<th>Order Date</th>
|
|
363
|
+
<th>Collect By</th>
|
|
364
|
+
<th>Collect Date</th>
|
|
365
|
+
<th>Actions</th>
|
|
366
|
+
</tr>
|
|
367
|
+
</thead>
|
|
368
|
+
<tbody>
|
|
369
|
+
{loading ? (
|
|
370
|
+
<tr>
|
|
371
|
+
<td colSpan={13} style={{ textAlign: 'center', padding: '24px' }}>
|
|
372
|
+
Loading test results...
|
|
373
|
+
</td>
|
|
374
|
+
</tr>
|
|
375
|
+
) : paginatedData.length === 0 ? (
|
|
376
|
+
<tr>
|
|
377
|
+
<td colSpan={13} style={{ textAlign: 'center', padding: '24px', color: '#6f6f6f' }}>
|
|
378
|
+
No approved test results found.
|
|
379
|
+
</td>
|
|
380
|
+
</tr>
|
|
381
|
+
) : (
|
|
382
|
+
paginatedData.map((row, idx) => (
|
|
383
|
+
<tr key={`${row.section}-${row.serial}-${idx}`}>
|
|
384
|
+
<td>{(currentPage - 1) * pageSize + idx + 1}</td>
|
|
385
|
+
<td>
|
|
386
|
+
<span className={getSectionBadgeClass(row.section)}>
|
|
387
|
+
{row.section}
|
|
388
|
+
</span>
|
|
389
|
+
</td>
|
|
390
|
+
<td style={{ fontWeight: '500' }}>{row.barcodeNumber}</td>
|
|
391
|
+
<td>{row.phnNo}</td>
|
|
392
|
+
<td>{row.givenName}</td>
|
|
393
|
+
<td>{row.gender}</td>
|
|
394
|
+
<td>{row.age}</td>
|
|
395
|
+
<td style={{ fontWeight: '600' }}>{row.testName}</td>
|
|
396
|
+
<td>{row.orderBy}</td>
|
|
397
|
+
<td>{row.orderDateTime}</td>
|
|
398
|
+
<td>{row.collectBy}</td>
|
|
399
|
+
<td>{row.collectDate}</td>
|
|
400
|
+
<td>
|
|
401
|
+
<div className={styles.actionBtnGroup}>
|
|
402
|
+
<button
|
|
403
|
+
type="button"
|
|
404
|
+
className={styles.btnPrimary}
|
|
405
|
+
onClick={() => handleView(row)}
|
|
406
|
+
>
|
|
407
|
+
View
|
|
408
|
+
</button>
|
|
409
|
+
<button
|
|
410
|
+
type="button"
|
|
411
|
+
className={styles.btnSuccess}
|
|
412
|
+
onClick={() => handlePrintReport(row)}
|
|
413
|
+
>
|
|
414
|
+
Print
|
|
415
|
+
</button>
|
|
416
|
+
</div>
|
|
417
|
+
</td>
|
|
418
|
+
</tr>
|
|
419
|
+
))
|
|
420
|
+
)}
|
|
421
|
+
</tbody>
|
|
422
|
+
</table>
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
{/* Pagination */}
|
|
426
|
+
{!loading && filteredData.length > 0 && (
|
|
427
|
+
<div className={styles.pagination}>
|
|
428
|
+
<div>
|
|
429
|
+
Showing {(currentPage - 1) * pageSize + 1} to{' '}
|
|
430
|
+
{Math.min(currentPage * pageSize, filteredData.length)} of {filteredData.length} records
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
434
|
+
<button
|
|
435
|
+
type="button"
|
|
436
|
+
className={styles.btnSecondary}
|
|
437
|
+
disabled={currentPage === 1}
|
|
438
|
+
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))}
|
|
439
|
+
>
|
|
440
|
+
Previous
|
|
441
|
+
</button>
|
|
442
|
+
<span>
|
|
443
|
+
Page {currentPage} of {totalPages}
|
|
444
|
+
</span>
|
|
445
|
+
<button
|
|
446
|
+
type="button"
|
|
447
|
+
className={styles.btnSecondary}
|
|
448
|
+
disabled={currentPage >= totalPages}
|
|
449
|
+
onClick={() => setCurrentPage((p) => Math.min(p + 1, totalPages))}
|
|
450
|
+
>
|
|
451
|
+
Next
|
|
452
|
+
</button>
|
|
453
|
+
|
|
454
|
+
<select
|
|
455
|
+
className={styles.selectCompact}
|
|
456
|
+
value={pageSize}
|
|
457
|
+
onChange={(e) => {
|
|
458
|
+
setPageSize(Number(e.target.value));
|
|
459
|
+
setCurrentPage(1);
|
|
460
|
+
}}
|
|
461
|
+
style={{ marginLeft: '12px' }}
|
|
462
|
+
>
|
|
463
|
+
<option value={10}>10 per page</option>
|
|
464
|
+
<option value={25}>25 per page</option>
|
|
465
|
+
<option value={50}>50 per page</option>
|
|
466
|
+
<option value={100}>100 per page</option>
|
|
467
|
+
</select>
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
)}
|
|
471
|
+
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
|
|
475
|
+
{/* View Results Modal */}
|
|
476
|
+
{isModalOpen && patientDetails && (
|
|
477
|
+
<div className={styles.modalOverlay}>
|
|
478
|
+
<div className={styles.modalContainer}>
|
|
479
|
+
<div className={styles.modalHeader}>
|
|
480
|
+
<h3>View Test Results</h3>
|
|
481
|
+
<button
|
|
482
|
+
type="button"
|
|
483
|
+
className={styles.btnClose}
|
|
484
|
+
onClick={() => setIsModalOpen(false)}
|
|
485
|
+
>
|
|
486
|
+
×
|
|
487
|
+
</button>
|
|
488
|
+
</div>
|
|
489
|
+
|
|
490
|
+
<div className={styles.modalBody}>
|
|
491
|
+
<div className={styles.gridForm}>
|
|
492
|
+
<div className={styles.formField}>
|
|
493
|
+
<label>Full Name</label>
|
|
494
|
+
<input type="text" value={patientDetails.fullName} readOnly />
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<div className={styles.formField}>
|
|
498
|
+
<label>Section</label>
|
|
499
|
+
<input type="text" value={patientDetails.section} readOnly />
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<div className={styles.formField}>
|
|
503
|
+
<label>PHN No</label>
|
|
504
|
+
<input type="text" value={patientDetails.phn} readOnly />
|
|
505
|
+
</div>
|
|
506
|
+
|
|
507
|
+
<div className={styles.formField}>
|
|
508
|
+
<label>Age</label>
|
|
509
|
+
<input type="text" value={patientDetails.age} readOnly />
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
<div className={styles.formField}>
|
|
513
|
+
<label>Gender</label>
|
|
514
|
+
<input type="text" value={patientDetails.gender} readOnly />
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<div className={styles.formField}>
|
|
518
|
+
<label>Test Name</label>
|
|
519
|
+
<input type="text" value={patientDetails.testName} readOnly />
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<div className={`${styles.formField} ${styles.formFieldFull}`}>
|
|
523
|
+
<label>Remark / Notes</label>
|
|
524
|
+
<textarea rows={2} value={patientDetails.remark} readOnly />
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
<div className={styles.formField}>
|
|
528
|
+
<label>Date</label>
|
|
529
|
+
<input type="text" value={patientDetails.date} readOnly />
|
|
530
|
+
</div>
|
|
531
|
+
|
|
532
|
+
<div className={styles.formField}>
|
|
533
|
+
<label>Time</label>
|
|
534
|
+
<input type="text" value={patientDetails.time} readOnly />
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
{/* Parameter Table */}
|
|
539
|
+
<h4 style={{ fontSize: '15px', fontWeight: '600', color: '#161616', marginTop: '16px', marginBottom: '8px' }}>
|
|
540
|
+
Test Parameters & Values
|
|
541
|
+
</h4>
|
|
542
|
+
|
|
543
|
+
<div className={styles.tableWrapper}>
|
|
544
|
+
<table className={styles.dataTable}>
|
|
545
|
+
<thead>
|
|
546
|
+
<tr>
|
|
547
|
+
<th>No</th>
|
|
548
|
+
<th>Parameter Name</th>
|
|
549
|
+
<th>Value</th>
|
|
550
|
+
<th>Units</th>
|
|
551
|
+
<th>Ranges</th>
|
|
552
|
+
</tr>
|
|
553
|
+
</thead>
|
|
554
|
+
<tbody>
|
|
555
|
+
{loadingParams ? (
|
|
556
|
+
<tr>
|
|
557
|
+
<td colSpan={5} style={{ textAlign: 'center', padding: '16px' }}>
|
|
558
|
+
Loading parameters...
|
|
559
|
+
</td>
|
|
560
|
+
</tr>
|
|
561
|
+
) : parameters.length === 0 ? (
|
|
562
|
+
<tr>
|
|
563
|
+
<td colSpan={5} style={{ textAlign: 'center', padding: '16px', color: '#6f6f6f' }}>
|
|
564
|
+
No parameter details recorded for this test.
|
|
565
|
+
</td>
|
|
566
|
+
</tr>
|
|
567
|
+
) : (
|
|
568
|
+
parameters.map((param, index) => (
|
|
569
|
+
<tr key={`param-${index}`}>
|
|
570
|
+
<td>{index + 1}</td>
|
|
571
|
+
<td style={{ fontWeight: '500' }}>{param.parameterName}</td>
|
|
572
|
+
<td>{param.value}</td>
|
|
573
|
+
<td>{param.units}</td>
|
|
574
|
+
<td>{param.ranges}</td>
|
|
575
|
+
</tr>
|
|
576
|
+
))
|
|
577
|
+
)}
|
|
578
|
+
</tbody>
|
|
579
|
+
</table>
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
|
|
583
|
+
<div className={styles.modalFooter}>
|
|
584
|
+
<button
|
|
585
|
+
type="button"
|
|
586
|
+
className={styles.btnSecondary}
|
|
587
|
+
onClick={() => setIsModalOpen(false)}
|
|
588
|
+
>
|
|
589
|
+
Close
|
|
590
|
+
</button>
|
|
591
|
+
{selectedRow && (
|
|
592
|
+
<button
|
|
593
|
+
type="button"
|
|
594
|
+
className={styles.btnSuccess}
|
|
595
|
+
onClick={() => handlePrintReport(selectedRow)}
|
|
596
|
+
>
|
|
597
|
+
Print Report PDF
|
|
598
|
+
</button>
|
|
599
|
+
)}
|
|
600
|
+
</div>
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
)}
|
|
604
|
+
</div>
|
|
605
|
+
);
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
export default PrintLabTestResults;
|