@sasindi/samplecollection 1.0.0

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.
Files changed (64) hide show
  1. package/LICENSE +354 -0
  2. package/README.md +123 -0
  3. package/dist/117.js +1 -0
  4. package/dist/117.js.map +1 -0
  5. package/dist/197.js +1 -0
  6. package/dist/197.js.map +1 -0
  7. package/dist/237.js +1 -0
  8. package/dist/237.js.map +1 -0
  9. package/dist/30.js +1 -0
  10. package/dist/30.js.map +1 -0
  11. package/dist/362.js +1 -0
  12. package/dist/362.js.map +1 -0
  13. package/dist/410.js +1 -0
  14. package/dist/410.js.map +1 -0
  15. package/dist/435.js +1 -0
  16. package/dist/435.js.map +1 -0
  17. package/dist/480.js +1 -0
  18. package/dist/480.js.map +1 -0
  19. package/dist/517.js +1 -0
  20. package/dist/517.js.map +1 -0
  21. package/dist/529.js +1 -0
  22. package/dist/529.js.map +1 -0
  23. package/dist/538.js +1 -0
  24. package/dist/538.js.map +1 -0
  25. package/dist/578.js +1 -0
  26. package/dist/578.js.map +1 -0
  27. package/dist/584.js +22 -0
  28. package/dist/584.js.map +1 -0
  29. package/dist/652.js +1 -0
  30. package/dist/652.js.map +1 -0
  31. package/dist/668.js +1 -0
  32. package/dist/668.js.map +1 -0
  33. package/dist/749.js +1 -0
  34. package/dist/749.js.map +1 -0
  35. package/dist/898.js +1 -0
  36. package/dist/898.js.map +1 -0
  37. package/dist/964.js +1 -0
  38. package/dist/964.js.map +1 -0
  39. package/dist/976.js +1 -0
  40. package/dist/976.js.map +1 -0
  41. package/dist/98.js +1 -0
  42. package/dist/98.js.map +1 -0
  43. package/dist/981.js +43 -0
  44. package/dist/981.js.map +1 -0
  45. package/dist/997.js +1 -0
  46. package/dist/997.js.map +1 -0
  47. package/dist/main.js +6 -0
  48. package/dist/main.js.map +1 -0
  49. package/dist/openmrs-esm-samplecollection.js +6 -0
  50. package/dist/openmrs-esm-samplecollection.js.buildmanifest.json +748 -0
  51. package/dist/openmrs-esm-samplecollection.js.map +1 -0
  52. package/dist/routes.json +1 -0
  53. package/package.json +111 -0
  54. package/src/config-schema.ts +21 -0
  55. package/src/constants.ts +1 -0
  56. package/src/declarations.d.ts +23 -0
  57. package/src/index.ts +28 -0
  58. package/src/root.component.test.tsx +12 -0
  59. package/src/root.component.tsx +15 -0
  60. package/src/root.scss +5 -0
  61. package/src/routes.json +14 -0
  62. package/src/samplecollection.component.tsx +280 -0
  63. package/src/samplecollection.scss +163 -0
  64. package/src/samplecollectionroom.component.tsx +640 -0
@@ -0,0 +1,640 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useTranslation } from 'react-i18next';
4
+ import {
5
+ Tile,
6
+ TextInput,
7
+ Select,
8
+ SelectItem,
9
+ TextArea,
10
+ Button,
11
+ Table,
12
+ TableHead,
13
+ TableRow,
14
+ TableHeader,
15
+ TableBody,
16
+ TableCell,
17
+ Checkbox,
18
+ Loading,
19
+ InlineNotification
20
+ } from '@carbon/react';
21
+ import styles from './samplecollection.scss';
22
+
23
+ const UserAvatar = () => (
24
+ <svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ borderRadius: '50%', border: '3px solid #009384' }}>
25
+ <circle cx="50" cy="50" r="50" fill="#E0F2F1"/>
26
+ <circle cx="50" cy="40" r="18" fill="#009384"/>
27
+ <path d="M18 78C18 62.536 32.3269 50 50 50C67.6731 50 82 62.536 82 78" stroke="#009384" strokeWidth="8" strokeLinecap="round"/>
28
+ </svg>
29
+ );
30
+
31
+ const SamplecollectionRoom: React.FC = () => {
32
+ const { t } = useTranslation();
33
+ const navigate = useNavigate();
34
+
35
+ // Route state read from localstorage
36
+ const phnNo = localStorage.getItem('phnNo') || '';
37
+ const initialSection = localStorage.getItem('section') || '';
38
+
39
+ // Patient Info States
40
+ const [patientName, setPatientName] = useState<string>('');
41
+ const [patientGender, setPatientGender] = useState<string>('');
42
+ const [patientAge, setPatientAge] = useState<string>('');
43
+ const [profileImgName, setProfileImgName] = useState<string>('');
44
+
45
+ // Context & IDs
46
+ const [userUuid, setUserUuid] = useState<string>('');
47
+ const [locationUuid, setLocationUuid] = useState<string>('');
48
+ const [locationName, setLocationName] = useState<string>('');
49
+ const [poi, setPoi] = useState<string>('');
50
+
51
+ // Table Data
52
+ const [testList, setTestList] = useState<any[]>([]);
53
+ const [loading, setLoading] = useState<boolean>(true);
54
+ const [errorMsg, setErrorMsg] = useState<string>('');
55
+ const [successMsg, setSuccessMsg] = useState<string>('');
56
+
57
+ // Form selections state
58
+ const [selectedIds, setSelectedIds] = useState<string[]>([]);
59
+ const [selectedUrgents, setSelectedUrgents] = useState<string[]>([]);
60
+ const [selectedTestnames, setSelectedTestnames] = useState<string[]>([]);
61
+
62
+ const [displayTestNames, setDisplayTestNames] = useState<string>('');
63
+ const [displayOrderBy, setDisplayOrderBy] = useState<string>('');
64
+ const [displayOrderDate, setDisplayOrderDate] = useState<string>('');
65
+ const [displayOrderTime, setDisplayOrderTime] = useState<string>('');
66
+
67
+ // Save fields state
68
+ const [saveDate, setSaveDate] = useState<string>('');
69
+ const [saveTime, setSaveTime] = useState<string>('');
70
+ const [saveStatus, setSaveStatus] = useState<string>('Done');
71
+ const [saveRemark, setSaveRemark] = useState<string>('');
72
+ const [saving, setSaving] = useState<boolean>(false);
73
+
74
+ // Pad number helper
75
+ const padNumber = (number: number, length: number) => {
76
+ return number.toString().padStart(length, '0');
77
+ };
78
+
79
+ // Get current date string formatted as YYMMDDHHmmss
80
+ const getCurrentDateFormatted = () => {
81
+ const now = new Date();
82
+ const year = now.getFullYear().toString().substr(-2);
83
+ const month = padNumber(now.getMonth() + 1, 2);
84
+ const day = padNumber(now.getDate(), 2);
85
+ const hours = padNumber(now.getHours(), 2);
86
+ const minutes = padNumber(now.getMinutes(), 2);
87
+ const seconds = padNumber(now.getSeconds(), 2);
88
+ return year + month + day + hours + minutes + seconds;
89
+ };
90
+
91
+ // Age calculation helper
92
+ const calculateAge = (birthdateString: string) => {
93
+ if (!birthdateString) return '';
94
+ const now = new Date();
95
+ const dob = new Date(birthdateString);
96
+ let age = now.getFullYear() - dob.getFullYear();
97
+ const monthDiff = now.getMonth() - dob.getMonth();
98
+ if (monthDiff < 0 || (monthDiff === 0 && now.getDate() < dob.getDate())) {
99
+ age--;
100
+ }
101
+ return age.toString();
102
+ };
103
+
104
+ // Set default values for Date and Time
105
+ useEffect(() => {
106
+ const today = new Date();
107
+ const formattedDate = `${today.getFullYear()}-${padNumber(today.getMonth() + 1, 2)}-${padNumber(today.getDate(), 2)}`;
108
+ const formattedTime = `${padNumber(today.getHours(), 2)}:${padNumber(today.getMinutes(), 2)}`;
109
+
110
+ setSaveDate(formattedDate);
111
+ setSaveTime(formattedTime);
112
+ }, []);
113
+
114
+ // Fetch session, location info and patient profile info
115
+ useEffect(() => {
116
+ if (!phnNo) {
117
+ setErrorMsg('No patient PHN provided');
118
+ setLoading(false);
119
+ return;
120
+ }
121
+
122
+ async function initContextAndPatient() {
123
+ try {
124
+ // Load session
125
+ const sessionRes = await fetch('/openmrs/ws/rest/v1/session');
126
+ if (!sessionRes.ok) throw new Error('Session request failed');
127
+ const sessionData = await sessionRes.json();
128
+ setUserUuid(sessionData.user.uuid);
129
+ const personUuid = sessionData.user.person.uuid;
130
+
131
+ // Load user person attributes to get clinic location
132
+ const personAttrRes = await fetch(`/openmrs/ws/rest/v1/person/${personUuid}`);
133
+ if (!personAttrRes.ok) throw new Error('User person details failed');
134
+ const personAttrData = await personAttrRes.json();
135
+
136
+ let locUuid = '';
137
+ if (personAttrData.attributes) {
138
+ personAttrData.attributes.forEach((attr: any) => {
139
+ if (attr.display) {
140
+ const [key, value] = attr.display.split('=');
141
+ if (key && value && key.trim() === 'Institute Id') {
142
+ locUuid = value.trim();
143
+ }
144
+ }
145
+ });
146
+ }
147
+
148
+ if (locUuid) {
149
+ setLocationUuid(locUuid);
150
+
151
+ // Get Location Details (for Name & POI)
152
+ const locRes = await fetch(`/openmrs/ws/rest/v1/location/${locUuid}`);
153
+ if (locRes.ok) {
154
+ const locData = await locRes.json();
155
+ setLocationName(locData.display || '');
156
+
157
+ if (locData.attributes) {
158
+ locData.attributes.forEach((attr: any) => {
159
+ if (attr.display && attr.display.includes('POI')) {
160
+ const val = attr.display.split(':')[1];
161
+ if (val) setPoi(val.split(',')[0].trim());
162
+ }
163
+ });
164
+ }
165
+ }
166
+ }
167
+
168
+ // Fetch patient profile details
169
+ const patientRes = await fetch(`/openmrs/ws/rest/v1/patientexamination/phn/${phnNo}`);
170
+ if (!patientRes.ok) throw new Error('Patient not found');
171
+ const patientData = await patientRes.json();
172
+
173
+ setPatientName(patientData.person_id?.person?.display || '');
174
+ setPatientGender(patientData.person_id?.person?.gender || '');
175
+ if (patientData.person_id?.person?.birthdate) {
176
+ setPatientAge(calculateAge(patientData.person_id.person.birthdate));
177
+ }
178
+
179
+ // Check for ProfileImage attribute
180
+ if (patientData.person_id?.person?.attributes) {
181
+ patientData.person_id.person.attributes.forEach((attr: any) => {
182
+ if (attr.display && attr.display.split('=')[0].trim() === 'ProfileImage') {
183
+ setProfileImgName(attr.display.split('=')[1].trim());
184
+ }
185
+ });
186
+ }
187
+
188
+ // Fetch active test details for checkoff
189
+ if (locUuid) {
190
+ const loadViewRes = await fetch(`/openmrs/ws/rest/v1/samplecollection/loadview/${phnNo}/${initialSection}/${locUuid}`);
191
+ if (loadViewRes.ok) {
192
+ const loadViewData = await loadViewRes.json();
193
+ setTestList(loadViewData || []);
194
+ }
195
+ }
196
+ } catch (err: any) {
197
+ console.error('Error loading page context details:', err);
198
+ setErrorMsg(err.message || 'Error occurred during data load');
199
+ } finally {
200
+ setLoading(false);
201
+ }
202
+ }
203
+
204
+ initContextAndPatient();
205
+ }, [phnNo, initialSection]);
206
+
207
+ // Handle row checkoff selections
208
+ const handleCheckboxChange = (checked: boolean, id: string, testName: string, urgent: string, row: any) => {
209
+ let nextIds = [...selectedIds];
210
+ let nextTestnames = [...selectedTestnames];
211
+ let nextUrgents = [...selectedUrgents];
212
+
213
+ if (checked) {
214
+ nextIds.push(id);
215
+ nextTestnames.push(testName);
216
+ nextUrgents.push(urgent);
217
+ } else {
218
+ nextIds = nextIds.filter((item) => item !== id);
219
+ nextTestnames = nextTestnames.filter((item) => item !== testName);
220
+ nextUrgents = nextUrgents.filter((item) => item !== urgent);
221
+ }
222
+
223
+ setSelectedIds(nextIds);
224
+ setSelectedTestnames(nextTestnames);
225
+ setSelectedUrgents(nextUrgents);
226
+
227
+ // Update form view fields
228
+ setDisplayTestNames(nextTestnames.join(', '));
229
+
230
+ if (nextIds.length === 0) {
231
+ setDisplayOrderBy('');
232
+ setDisplayOrderDate('');
233
+ setDisplayOrderTime('');
234
+ } else {
235
+ // Set to the values of the last checked row
236
+ const orderby = row[5] || '';
237
+ const timestamp = row[6] || '';
238
+ const [date, time] = timestamp.split(' ');
239
+ setDisplayOrderBy(orderby);
240
+ setDisplayOrderDate(date || '');
241
+ setDisplayOrderTime(time || '');
242
+ }
243
+ };
244
+
245
+ const handleSave = async () => {
246
+ if (saveStatus === '0') {
247
+ setErrorMsg('Please select a valid Collection Status');
248
+ return;
249
+ }
250
+ if (selectedIds.length === 0) {
251
+ setErrorMsg('Please select at least one test to process');
252
+ return;
253
+ }
254
+ if (!saveDate || !saveTime) {
255
+ setErrorMsg('Please specify both Date and Time of collection');
256
+ return;
257
+ }
258
+ if (!userUuid) {
259
+ setErrorMsg('Invalid session. Please login again.');
260
+ return;
261
+ }
262
+
263
+ setSaving(true);
264
+ setErrorMsg('');
265
+ setSuccessMsg('');
266
+
267
+ try {
268
+ if (saveStatus === 'Not Done') {
269
+ // Ported JSP behavior: Just show success and redirect/reload
270
+ setSuccessMsg('Status saved successfully as Not Done!');
271
+ setTimeout(() => {
272
+ navigate('/samplecollection');
273
+ }, 1500);
274
+ return;
275
+ }
276
+
277
+ // Generate Barcode
278
+ const currentDate = getCurrentDateFormatted();
279
+ let hasUrgent = selectedUrgents.some((u) => u === 'true');
280
+ const generatedBarcode = hasUrgent ? `*${currentDate}${poi}` : `${currentDate}${poi}`;
281
+
282
+ // Progress string
283
+ const progress = saveStatus === 'Done' ? 'samplecollected' : 'samplenotcollected';
284
+
285
+ // Save Endpoint depending on section
286
+ const sectionType = initialSection === 'OPD' ? 'OPD' : initialSection === 'Clinic' ? 'Clinic' : 'Ward';
287
+ const saveEndpoint = {
288
+ OPD: '/openmrs/ws/rest/v1/samplecollection/labtestssave',
289
+ Clinic: '/openmrs/ws/rest/v1/samplecollection/labtestssaveclinic',
290
+ Ward: '/openmrs/ws/rest/v1/samplecollection/labtestssaveward',
291
+ }[sectionType];
292
+
293
+ // Send requests sequentially/parallelly for each selected ID
294
+ for (const id of selectedIds) {
295
+ const payload = {
296
+ id: id,
297
+ collectionstatus: saveStatus,
298
+ remark: saveRemark,
299
+ barcodenumber: generatedBarcode,
300
+ collectiondate: saveDate,
301
+ collectiontime: saveTime,
302
+ collectioncby: userUuid,
303
+ status: 'processing',
304
+ progress: progress,
305
+ };
306
+
307
+ const response = await fetch(saveEndpoint, {
308
+ method: 'POST',
309
+ body: JSON.stringify(payload),
310
+ headers: { 'Content-Type': 'application/json' },
311
+ });
312
+
313
+ if (!response.ok) {
314
+ throw new Error(`Failed to save test record for ID: ${id}`);
315
+ }
316
+ }
317
+
318
+ setSuccessMsg('Sample status updated successfully! Preparing barcode print...');
319
+
320
+ // Fetch shortcodes for print labels
321
+ const shortcodes: string[] = [];
322
+ for (const test of selectedTestnames) {
323
+ const res = await fetch(`/openmrs/ws/rest/v1/samplecollection/getshortcode/${encodeURIComponent(test)}`);
324
+ if (res.ok) {
325
+ const shortcodeData = await res.json();
326
+ if (shortcodeData?.shortcode) {
327
+ shortcodes.push(shortcodeData.shortcode);
328
+ }
329
+ }
330
+ }
331
+
332
+ const combinedShortcodes = shortcodes.join(', ');
333
+
334
+ // Launch Barcode Print popup matching the team's dimensions
335
+ const printWindow = window.open('', '_blank');
336
+ if (printWindow) {
337
+ printWindow.document.write(`
338
+ <!DOCTYPE html>
339
+ <html>
340
+ <head>
341
+ <title>Barcode Print</title>
342
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jsbarcode/3.11.5/JsBarcode.all.min.js"></script>
343
+ <style>
344
+ body {
345
+ margin: 0;
346
+ padding: 0;
347
+ text-align: center;
348
+ }
349
+ .container {
350
+ width: 1.96in; /* 5cm */
351
+ height: 0.98in; /* 2.5cm */
352
+ margin: 0 auto;
353
+ position: relative;
354
+ }
355
+ svg {
356
+ width: 100%;
357
+ height: 80%;
358
+ }
359
+ .shortcode {
360
+ font-family: Arial, sans-serif;
361
+ font-size: 10px;
362
+ margin-top: -14px;
363
+ }
364
+ @media print {
365
+ body { margin: 0; padding: 0; }
366
+ }
367
+ </style>
368
+ </head>
369
+ <body>
370
+ <div class="container">
371
+ <svg id="barcode"></svg>
372
+ <div class="shortcode">${patientName} (${patientAge}Y)</div>
373
+ <div style="font-size: 10px; font-family: Arial, sans-serif;">${initialSection}/${combinedShortcodes}</div>
374
+ </div>
375
+ <script>
376
+ document.addEventListener("DOMContentLoaded", function() {
377
+ JsBarcode("#barcode", "${generatedBarcode}", {
378
+ width: 2,
379
+ height: 50,
380
+ displayValue: true,
381
+ fontSize: 14,
382
+ margin: 5
383
+ });
384
+ setTimeout(function() {
385
+ window.print();
386
+ window.close();
387
+ }, 800);
388
+ });
389
+ </script>
390
+ </body>
391
+ </html>
392
+ `);
393
+ printWindow.document.close();
394
+ }
395
+
396
+ // Clear selections and reload table data
397
+ setSelectedIds([]);
398
+ setSelectedTestnames([]);
399
+ setSelectedUrgents([]);
400
+ setDisplayTestNames('');
401
+ setDisplayOrderBy('');
402
+ setDisplayOrderDate('');
403
+ setDisplayOrderTime('');
404
+
405
+ // Refetch tests
406
+ const loadViewRes = await fetch(`/openmrs/ws/rest/v1/samplecollection/loadview/${phnNo}/${initialSection}/${locationUuid}`);
407
+ if (loadViewRes.ok) {
408
+ const loadViewData = await loadViewRes.json();
409
+ setTestList(loadViewData || []);
410
+ }
411
+ } catch (err: any) {
412
+ console.error('Error during save process:', err);
413
+ setErrorMsg(err.message || 'Error occurred saving collection data');
414
+ } finally {
415
+ setSaving(false);
416
+ }
417
+ };
418
+
419
+ if (loading) return <Loading description="Loading page details..." withOverlay={true} />;
420
+
421
+ return (
422
+ <div className={styles.container}>
423
+ <Tile className={styles.tile}>
424
+ {/* Header */}
425
+ <div className={styles.headerRow}>
426
+ <h1 className={styles.heading}>
427
+ {t('samplecollectionRoomHeading', 'Sample Collection Room')}
428
+ </h1>
429
+ <div className={styles.locationBadge}>
430
+ <span className="icon-hospital" />
431
+ <span>{locationName}</span>
432
+ </div>
433
+ </div>
434
+
435
+ {/* Notifications */}
436
+ {errorMsg && (
437
+ <InlineNotification
438
+ kind="error"
439
+ title="Error"
440
+ subtitle={errorMsg}
441
+ onClose={() => setErrorMsg('')}
442
+ style={{ marginBottom: '1rem' }}
443
+ />
444
+ )}
445
+ {successMsg && (
446
+ <InlineNotification
447
+ kind="success"
448
+ title="Success"
449
+ subtitle={successMsg}
450
+ onClose={() => setSuccessMsg('')}
451
+ style={{ marginBottom: '1rem' }}
452
+ />
453
+ )}
454
+
455
+ {/* Patient Profile Card */}
456
+ <div className={styles.patientCard}>
457
+ <div className={styles.patientGrid}>
458
+ <div className={styles.patientField}>
459
+ <span className={styles.fieldLabel}>Full Name</span>
460
+ <span className={styles.fieldValue}>{patientName || 'N/A'}</span>
461
+ </div>
462
+ <div className={styles.patientField}>
463
+ <span className={styles.fieldLabel}>Section</span>
464
+ <span className={styles.fieldValue}>{initialSection || 'N/A'}</span>
465
+ </div>
466
+ <div className={styles.patientField}>
467
+ <span className={styles.fieldLabel}>PHN No</span>
468
+ <span className={styles.fieldValue}>{phnNo || 'N/A'}</span>
469
+ </div>
470
+ <div className={styles.patientField}>
471
+ <span className={styles.fieldLabel}>Age</span>
472
+ <span className={styles.fieldValue}>{patientAge ? `${patientAge} years` : 'N/A'}</span>
473
+ </div>
474
+ <div className={styles.patientField}>
475
+ <span className={styles.fieldLabel}>Gender</span>
476
+ <span className={styles.fieldValue}>{patientGender || 'N/A'}</span>
477
+ </div>
478
+ </div>
479
+ <div className={styles.avatarContainer}>
480
+ {profileImgName ? (
481
+ <img
482
+ src={`${window.location.origin}/openmrs/owa/PatientImagesUploaded/${profileImgName}`}
483
+ alt="Patient profile"
484
+ className={styles.avatarImg}
485
+ onError={(e) => {
486
+ // Fallback if image not found
487
+ (e.target as HTMLElement).style.display = 'none';
488
+ }}
489
+ />
490
+ ) : (
491
+ <UserAvatar />
492
+ )}
493
+ </div>
494
+ </div>
495
+
496
+ {/* Tests List Table */}
497
+ <div style={{ maxHeight: '250px', overflowY: 'auto', marginBottom: '2rem' }}>
498
+ <Table>
499
+ <TableHead>
500
+ <TableRow>
501
+ <TableHeader>No</TableHeader>
502
+ <TableHeader>Test Name</TableHeader>
503
+ <TableHeader>Order By</TableHeader>
504
+ <TableHeader>Order Date/Time</TableHeader>
505
+ <TableHeader className={styles.checkboxCell} />
506
+ </TableRow>
507
+ </TableHead>
508
+ <TableBody>
509
+ {testList.length === 0 ? (
510
+ <TableRow>
511
+ <TableCell colSpan={5} style={{ textAlign: 'center' }}>
512
+ No active tests pending collection.
513
+ </TableCell>
514
+ </TableRow>
515
+ ) : (
516
+ testList.map((row, index) => {
517
+ const id = row[8];
518
+ const testName = row[2];
519
+ const orderBy = row[5];
520
+ const orderDateTime = row[6];
521
+ const urgent = row[9]; // 'true' or 'false'
522
+ const isChecked = selectedIds.includes(String(id));
523
+
524
+ return (
525
+ <TableRow key={id || index}>
526
+ <TableCell>{index + 1}</TableCell>
527
+ <TableCell>{testName}</TableCell>
528
+ <TableCell>{orderBy}</TableCell>
529
+ <TableCell>{orderDateTime}</TableCell>
530
+ <TableCell className={styles.checkboxCell}>
531
+ <Checkbox
532
+ id={`chk-${id}`}
533
+ labelText=""
534
+ checked={isChecked}
535
+ onChange={(e, { checked }) =>
536
+ handleCheckboxChange(checked, String(id), testName, String(urgent), row)
537
+ }
538
+ />
539
+ </TableCell>
540
+ </TableRow>
541
+ );
542
+ })
543
+ )}
544
+ </TableBody>
545
+ </Table>
546
+ </div>
547
+
548
+ {/* Detail Panel */}
549
+ <div className={styles.roomFormSection}>
550
+ <div className={styles.formGrid}>
551
+ <TextInput
552
+ id="txttestname"
553
+ labelText="Test Name"
554
+ value={displayTestNames}
555
+ readOnly
556
+ />
557
+ <TextInput
558
+ id="txtorderby"
559
+ labelText="Order By"
560
+ value={displayOrderBy}
561
+ readOnly
562
+ />
563
+ <TextInput
564
+ id="txtorderdate"
565
+ labelText="Order Date"
566
+ value={displayOrderDate}
567
+ readOnly
568
+ />
569
+ <TextInput
570
+ id="txtordertime"
571
+ labelText="Order Time"
572
+ value={displayOrderTime}
573
+ readOnly
574
+ />
575
+ </div>
576
+
577
+ <hr style={{ border: 'none', height: '1px', backgroundColor: '#e0e0e0', margin: '20px 0' }} />
578
+
579
+ {/* User inputs */}
580
+ <div className={styles.formGrid}>
581
+ <TextInput
582
+ id="txtdate"
583
+ labelText="Date"
584
+ type="date"
585
+ value={saveDate}
586
+ onChange={(e) => setSaveDate(e.target.value)}
587
+ />
588
+ <TextInput
589
+ id="txttime"
590
+ labelText="Time"
591
+ type="time"
592
+ value={saveTime}
593
+ onChange={(e) => setSaveTime(e.target.value)}
594
+ />
595
+ <Select
596
+ id="drpstatus"
597
+ labelText="Collection Status"
598
+ value={saveStatus}
599
+ onChange={(e) => setSaveStatus(e.target.value)}
600
+ >
601
+ <SelectItem value="0" text="--Select--" />
602
+ <SelectItem value="Done" text="Done" />
603
+ <SelectItem value="Not Done" text="Not Done" />
604
+ </Select>
605
+ </div>
606
+
607
+ <div style={{ marginBottom: '2rem' }}>
608
+ <TextArea
609
+ id="txtnote"
610
+ labelText="Remark"
611
+ rows={3}
612
+ value={saveRemark}
613
+ onChange={(e) => setSaveRemark(e.target.value)}
614
+ />
615
+ </div>
616
+
617
+ {/* Action buttons */}
618
+ <div className={styles.actionsContainer}>
619
+ <Button
620
+ kind="danger"
621
+ onClick={() => navigate('/samplecollection')}
622
+ disabled={saving}
623
+ >
624
+ Back
625
+ </Button>
626
+ <Button
627
+ kind="primary"
628
+ onClick={handleSave}
629
+ disabled={saving || testList.length === 0}
630
+ >
631
+ {saving ? 'Saving...' : 'Save'}
632
+ </Button>
633
+ </div>
634
+ </div>
635
+ </Tile>
636
+ </div>
637
+ );
638
+ };
639
+
640
+ export default SamplecollectionRoom;