@sasindi/radiologytestscheduling 0.0.0-stage → 1.0.2

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 (72) hide show
  1. package/LICENSE +354 -0
  2. package/README.md +166 -2
  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/206.css +1 -0
  8. package/dist/206.css.map +1 -0
  9. package/dist/206.js +39 -0
  10. package/dist/206.js.map +1 -0
  11. package/dist/237.js +1 -0
  12. package/dist/237.js.map +1 -0
  13. package/dist/29.js +1 -0
  14. package/dist/29.js.map +1 -0
  15. package/dist/362.js +1 -0
  16. package/dist/362.js.map +1 -0
  17. package/dist/410.js +1 -0
  18. package/dist/410.js.map +1 -0
  19. package/dist/480.js +1 -0
  20. package/dist/480.js.map +1 -0
  21. package/dist/517.js +1 -0
  22. package/dist/517.js.map +1 -0
  23. package/dist/538.js +1 -0
  24. package/dist/538.js.map +1 -0
  25. package/dist/540.css +1 -0
  26. package/dist/540.css.map +1 -0
  27. package/dist/559.js +1 -0
  28. package/dist/559.js.map +1 -0
  29. package/dist/570.js +60 -0
  30. package/dist/570.js.map +1 -0
  31. package/dist/578.js +1 -0
  32. package/dist/578.js.map +1 -0
  33. package/dist/647.js +1 -0
  34. package/dist/647.js.map +1 -0
  35. package/dist/652.js +1 -0
  36. package/dist/652.js.map +1 -0
  37. package/dist/668.js +1 -0
  38. package/dist/668.js.map +1 -0
  39. package/dist/749.js +1 -0
  40. package/dist/749.js.map +1 -0
  41. package/dist/809.js +22 -0
  42. package/dist/809.js.map +1 -0
  43. package/dist/813.js +1 -0
  44. package/dist/813.js.map +1 -0
  45. package/dist/898.js +1 -0
  46. package/dist/898.js.map +1 -0
  47. package/dist/974.css +1 -0
  48. package/dist/974.css.map +1 -0
  49. package/dist/974.js +1 -0
  50. package/dist/974.js.map +1 -0
  51. package/dist/976.js +1 -0
  52. package/dist/976.js.map +1 -0
  53. package/dist/977.js +1 -0
  54. package/dist/977.js.map +1 -0
  55. package/dist/98.js +1 -0
  56. package/dist/98.js.map +1 -0
  57. package/dist/openmrs-esm-radiologytestscheduling.js +3 -0
  58. package/dist/openmrs-esm-radiologytestscheduling.js.map +1 -0
  59. package/dist/routes.json +1 -0
  60. package/package.json +101 -4
  61. package/src/config-schema.ts +21 -0
  62. package/src/constants.ts +1 -0
  63. package/src/declarations.d.ts +23 -0
  64. package/src/index.ts +28 -0
  65. package/src/radiology-test-scheduling.component.tsx +711 -0
  66. package/src/radiology-test-scheduling.resource.ts +339 -0
  67. package/src/radiology-test-scheduling.scss +396 -0
  68. package/src/root.component.test.tsx +12 -0
  69. package/src/root.component.tsx +13 -0
  70. package/src/root.scss +5 -0
  71. package/src/routes.json +12 -0
  72. package/src/types.ts +100 -0
@@ -0,0 +1,711 @@
1
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Loading } from '@carbon/react';
4
+ import Swal from 'sweetalert2';
5
+ import {
6
+ fetchCurrentSessionAndInstitute,
7
+ fetchInstitutes,
8
+ fetchPendingSchedulingOrders,
9
+ fetchPatientDetails,
10
+ fetchPatientBasicInfo,
11
+ fetchLocationDisplay,
12
+ fetchUserDisplay,
13
+ fetchExamRooms,
14
+ saveScheduling,
15
+ SessionInfo,
16
+ } from './radiology-test-scheduling.resource';
17
+ import {
18
+ SchedulingOrderDTO,
19
+ PatientDemographics,
20
+ InstituteOption,
21
+ ExamRoom,
22
+ } from './types';
23
+ import styles from './radiology-test-scheduling.scss';
24
+
25
+ const DEFAULT_AVATAR =
26
+ 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="%23b0bec5"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>';
27
+
28
+ const RadiologyTestScheduling: React.FC = () => {
29
+ const { t } = useTranslation();
30
+
31
+ // Session & Institute states
32
+ const [sessionInfo, setSessionInfo] = useState<SessionInfo>({
33
+ userUuid: '',
34
+ personUuid: '',
35
+ locationUuid: '',
36
+ locationName: '',
37
+ });
38
+ const [institutes, setInstitutes] = useState<InstituteOption[]>([]);
39
+ const [selectedInstitute, setSelectedInstitute] = useState<string>('all');
40
+
41
+ // Pending Tests & Table states
42
+ const [pendingOrders, setPendingOrders] = useState<SchedulingOrderDTO[]>([]);
43
+ const [loadingOrders, setLoadingOrders] = useState<boolean>(true);
44
+ const [searchTerm, setSearchTerm] = useState<string>('');
45
+
46
+ // Selected Order & Patient state
47
+ const [selectedOrder, setSelectedOrder] = useState<SchedulingOrderDTO | null>(null);
48
+ const [patientDetails, setPatientDetails] = useState<PatientDemographics | null>(null);
49
+ const [loadingPatient, setLoadingPatient] = useState<boolean>(false);
50
+
51
+ // Scheduling Form inputs: Scheduled Date Time, Remarks, Modality, Exam Room
52
+ const [scheduledDateTime, setScheduledDateTime] = useState<string>('');
53
+ const [remarks, setRemarks] = useState<string>('');
54
+ const [selectedModality, setSelectedModality] = useState<string>('all');
55
+ const [examRooms, setExamRooms] = useState<ExamRoom[]>([]);
56
+ const [selectedExamRoom, setSelectedExamRoom] = useState<string>('all');
57
+ const [isSaving, setIsSaving] = useState<boolean>(false);
58
+
59
+ // Initialize Session and Institutes
60
+ useEffect(() => {
61
+ let isMounted = true;
62
+ (async () => {
63
+ try {
64
+ const [session, instList] = await Promise.all([
65
+ fetchCurrentSessionAndInstitute(),
66
+ fetchInstitutes(),
67
+ ]);
68
+ if (!isMounted) return;
69
+ setSessionInfo(session);
70
+ setInstitutes(instList);
71
+ if (session.locationUuid) {
72
+ setSelectedInstitute(session.locationUuid);
73
+ }
74
+ } catch (err) {
75
+ console.error('Initialization failed', err);
76
+ }
77
+ })();
78
+ return () => {
79
+ isMounted = false;
80
+ };
81
+ }, []);
82
+
83
+ // Load Pending Orders whenever selected institute changes
84
+ const loadPendingOrders = useCallback(async (instUuid: string) => {
85
+ setLoadingOrders(true);
86
+ try {
87
+ const orders = await fetchPendingSchedulingOrders(instUuid);
88
+
89
+ // Enhance with location, clinician, and patient basic demographics
90
+ const enhanced = await Promise.all(
91
+ orders.map(async (dto) => {
92
+ const loc = dto.radiology.location ? await fetchLocationDisplay(dto.radiology.location) : '';
93
+ const ordBy = dto.radiology.requesterReference
94
+ ? await fetchUserDisplay(dto.radiology.requesterReference)
95
+ : '';
96
+ const patientInfo = dto.radiology.patientPhn
97
+ ? await fetchPatientBasicInfo(dto.radiology.patientPhn)
98
+ : { name: '', gender: '', age: '' };
99
+
100
+ return {
101
+ ...dto,
102
+ locationName: loc,
103
+ orderedByName: ordBy,
104
+ patientName: patientInfo.name,
105
+ patientGender: patientInfo.gender,
106
+ patientAge: patientInfo.age,
107
+ };
108
+ }),
109
+ );
110
+ setPendingOrders(enhanced);
111
+ } catch (err) {
112
+ console.error('Failed to load pending scheduling orders', err);
113
+ Swal.fire({
114
+ icon: 'error',
115
+ title: 'Error Loading Orders',
116
+ text: 'Unable to fetch pending radiology test orders.',
117
+ confirmButtonColor: '#da1e28',
118
+ });
119
+ } finally {
120
+ setLoadingOrders(false);
121
+ }
122
+ }, []);
123
+
124
+ useEffect(() => {
125
+ loadPendingOrders(selectedInstitute);
126
+ }, [selectedInstitute, loadPendingOrders]);
127
+
128
+ // Load Exam Rooms when modality changes
129
+ useEffect(() => {
130
+ if (selectedModality === 'all' || !selectedModality) {
131
+ setExamRooms([]);
132
+ setSelectedExamRoom('all');
133
+ } else {
134
+ (async () => {
135
+ const rooms = await fetchExamRooms(selectedModality);
136
+ setExamRooms(rooms);
137
+ })();
138
+ }
139
+ }, [selectedModality]);
140
+
141
+ // Handle row selection
142
+ const handleSelectRow = async (dto: SchedulingOrderDTO) => {
143
+ setSelectedOrder(dto);
144
+ setScheduledDateTime('');
145
+ setRemarks('');
146
+ setLoadingPatient(true);
147
+
148
+ // Auto-populate Modality and Exam Room from selected test order if available
149
+ const modality = dto.radiology.categoryDisplay || 'all';
150
+ setSelectedModality(modality);
151
+
152
+ if (modality && modality !== 'all') {
153
+ try {
154
+ const rooms = await fetchExamRooms(modality);
155
+ setExamRooms(rooms);
156
+ const roomName = dto.radiology.supportingInfo;
157
+ if (roomName && roomName !== '--Select--') {
158
+ setSelectedExamRoom(roomName);
159
+ } else {
160
+ setSelectedExamRoom('all');
161
+ }
162
+ } catch (e) {
163
+ console.warn('Failed to load exam rooms on select', e);
164
+ }
165
+ } else {
166
+ setSelectedExamRoom('all');
167
+ setExamRooms([]);
168
+ }
169
+
170
+ try {
171
+ const details = await fetchPatientDetails(dto.radiology.patientPhn);
172
+ setPatientDetails(details);
173
+ } catch (e) {
174
+ console.error('Failed to load patient details', e);
175
+ } finally {
176
+ setLoadingPatient(false);
177
+ }
178
+ };
179
+
180
+ // Reset selection
181
+ const handleClear = () => {
182
+ setSelectedOrder(null);
183
+ setPatientDetails(null);
184
+ setScheduledDateTime('');
185
+ setRemarks('');
186
+ setSelectedModality('all');
187
+ setSelectedExamRoom('all');
188
+ setExamRooms([]);
189
+ };
190
+
191
+ // Save Schedule Handler
192
+ const handleSave = async () => {
193
+ if (!selectedOrder) {
194
+ Swal.fire({
195
+ icon: 'warning',
196
+ title: 'No Order Selected',
197
+ text: 'Please select a pending test order from the table to schedule.',
198
+ confirmButtonColor: '#0f62fe',
199
+ });
200
+ return;
201
+ }
202
+
203
+ if (!scheduledDateTime) {
204
+ Swal.fire({
205
+ icon: 'warning',
206
+ title: 'Date & Time Required',
207
+ text: 'Please choose the Scheduled Date and Time before saving.',
208
+ confirmButtonColor: '#0f62fe',
209
+ });
210
+ return;
211
+ }
212
+
213
+ // Format datetime string: "YYYY-MM-DD HH:mm:ss"
214
+ const formattedDateTime = scheduledDateTime.replace('T', ' ') + (scheduledDateTime.length === 16 ? ':00' : '');
215
+
216
+ const result = await Swal.fire({
217
+ title: 'Confirm Scheduling',
218
+ text: `Schedule ${selectedOrder.radiology.codeDisplay || 'test'} on ${formattedDateTime}?`,
219
+ icon: 'question',
220
+ showCancelButton: true,
221
+ confirmButtonColor: '#0f62fe',
222
+ cancelButtonColor: '#6c757d',
223
+ confirmButtonText: 'Yes, Schedule',
224
+ cancelButtonText: 'Cancel',
225
+ });
226
+
227
+ if (!result.isConfirmed) {
228
+ return;
229
+ }
230
+
231
+ setIsSaving(true);
232
+ try {
233
+ await saveScheduling(selectedOrder.type, {
234
+ id: selectedOrder.radiology.id,
235
+ scheduledDateTime: formattedDateTime,
236
+ remarks: remarks || '',
237
+ cby: sessionInfo.userUuid || '1',
238
+ });
239
+
240
+ await Swal.fire({
241
+ icon: 'success',
242
+ title: 'Saved Successfully!',
243
+ text: 'The radiology test has been scheduled.',
244
+ timer: 2000,
245
+ showConfirmButton: false,
246
+ });
247
+
248
+ handleClear();
249
+ loadPendingOrders(selectedInstitute);
250
+ } catch (err: any) {
251
+ console.error('Failed to save schedule', err);
252
+ Swal.fire({
253
+ icon: 'error',
254
+ title: 'Save Failed',
255
+ text: err?.message || 'Could not schedule radiology test. Please check server logs.',
256
+ confirmButtonColor: '#da1e28',
257
+ });
258
+ } finally {
259
+ setIsSaving(false);
260
+ }
261
+ };
262
+
263
+ // Filtered Orders for search input
264
+ const filteredOrders = useMemo(() => {
265
+ if (!searchTerm.trim()) return pendingOrders;
266
+ const term = searchTerm.toLowerCase();
267
+ return pendingOrders.filter((dto) => {
268
+ const phn = dto.radiology.patientPhn?.toLowerCase() || '';
269
+ const name = dto.patientName?.toLowerCase() || '';
270
+ const test = dto.radiology.codeDisplay?.toLowerCase() || '';
271
+ const section = dto.type?.toLowerCase() || '';
272
+ const priority = dto.radiology.priority?.toLowerCase() || '';
273
+ const location = dto.locationName?.toLowerCase() || '';
274
+ return (
275
+ phn.includes(term) ||
276
+ name.includes(term) ||
277
+ test.includes(term) ||
278
+ section.includes(term) ||
279
+ priority.includes(term) ||
280
+ location.includes(term)
281
+ );
282
+ });
283
+ }, [pendingOrders, searchTerm]);
284
+
285
+ return (
286
+ <div className={styles.bodyWrapper}>
287
+ {/* Main Unified Card: Heading inside the form container as required */}
288
+ <div className={styles.card}>
289
+ <div className={styles.cardHeader}>
290
+ <h2 className={styles.formHeading}>
291
+ {t('radiologyTestSchedulingHeading', 'Radiology Test Scheduling')}
292
+ </h2>
293
+ </div>
294
+
295
+ <div className={styles.serviceBox}>
296
+ {/* Top Filter Row: Institute Dropdown & Search Bar */}
297
+ <div className={styles.filterRow}>
298
+ <div className={styles.instituteSelectGroup}>
299
+ <h4 className={styles.instituteLabel}>{t('institute', 'Institute')}</h4>
300
+ <select
301
+ id="drpInstitute"
302
+ className={styles.formControl}
303
+ style={{ width: '260px' }}
304
+ value={selectedInstitute}
305
+ onChange={(e) => setSelectedInstitute(e.target.value)}
306
+ >
307
+ <option value="all">{t('allInstitutes', '-- All Institutes --')}</option>
308
+ {institutes.map((inst) => (
309
+ <option key={inst.uuid} value={inst.uuid}>
310
+ {inst.name}
311
+ </option>
312
+ ))}
313
+ </select>
314
+ </div>
315
+
316
+ <div className={styles.searchGroup}>
317
+ <input
318
+ type="text"
319
+ className={styles.formControl}
320
+ placeholder={t('searchPlaceholder', 'Search by PHN, Patient Name, Test Name, Section, or Location...')}
321
+ value={searchTerm}
322
+ onChange={(e) => setSearchTerm(e.target.value)}
323
+ />
324
+ </div>
325
+ </div>
326
+
327
+ {/* Table Container: Exact 17 columns as in team's module */}
328
+ <div className={styles.tableContainer}>
329
+ {loadingOrders ? (
330
+ <div style={{ padding: '2rem' }}>
331
+ <Loading description="Loading pending tests..." withOverlay={false} small />
332
+ </div>
333
+ ) : filteredOrders.length === 0 ? (
334
+ <div className={styles.emptyState}>
335
+ {t('noPendingOrders', 'No pending radiology test scheduling requests found.')}
336
+ </div>
337
+ ) : (
338
+ <table id="tblRadiologyTestScheduling" className={styles.table}>
339
+ <thead>
340
+ <tr>
341
+ <th>#</th>
342
+ <th>{t('institute', 'Institute')}</th>
343
+ <th>{t('section', 'Section')}</th>
344
+ <th>{t('phn', 'PHN')}</th>
345
+ <th>{t('name', 'Name')}</th>
346
+ <th>{t('gender', 'Gender')}</th>
347
+ <th>{t('age', 'Age')}</th>
348
+ <th>{t('admittedDate', 'Admitted Date')}</th>
349
+ <th>{t('orderedBy', 'Ordered By')}</th>
350
+ <th>{t('orderedDatetime', 'Ordered Datetime')}</th>
351
+ <th>{t('testName', 'Test Name')}</th>
352
+ <th>{t('region', 'Region')}</th>
353
+ <th>{t('examRoom', 'Exam Room')}</th>
354
+ <th>{t('comment', 'Comment')}</th>
355
+ <th>{t('priority', 'Priority')}</th>
356
+ <th>{t('consultant', 'Consultant')}</th>
357
+ <th style={{ textAlign: 'center' }}>{t('action', 'Action')}</th>
358
+ </tr>
359
+ </thead>
360
+ <tbody id="tbodyRadTestsScheduling">
361
+ {filteredOrders.map((dto, idx) => {
362
+ const isUrgent = dto.radiology.priority === 'Urgent';
363
+ const isSelected = selectedOrder?.radiology.id === dto.radiology.id;
364
+ const rowClass = `${isUrgent ? styles.urgentRow : ''} ${
365
+ isSelected ? styles.selectedRow : ''
366
+ }`.trim();
367
+
368
+ return (
369
+ <tr key={`${dto.type}-${dto.radiology.id}`} className={rowClass}>
370
+ <td>{idx + 1}</td>
371
+ <td>{dto.locationName || dto.radiology.location}</td>
372
+ <td>
373
+ <span className={`${styles.badge} ${styles.badgeType}`}>
374
+ {dto.type}
375
+ </span>
376
+ </td>
377
+ <td>
378
+ <strong>{dto.radiology.patientPhn}</strong>
379
+ </td>
380
+ <td>{dto.patientName || '-'}</td>
381
+ <td>{dto.patientGender || '-'}</td>
382
+ <td>{dto.patientAge !== undefined && dto.patientAge !== '' ? dto.patientAge : '-'}</td>
383
+ <td>
384
+ {dto.radiology.occurrenceDateTime
385
+ ? dto.radiology.occurrenceDateTime.split(' ')[0]
386
+ : '-'}
387
+ </td>
388
+ <td>{dto.orderedByName || dto.radiology.requesterReference || '-'}</td>
389
+ <td>{dto.radiology.occurrenceDateTime || '-'}</td>
390
+ <td>
391
+ <strong>{dto.radiology.codeDisplay || '-'}</strong>
392
+ </td>
393
+ <td>{dto.radiology.bodySiteText || '-'}</td>
394
+ <td>
395
+ {dto.radiology.supportingInfo === '--Select--'
396
+ ? '-'
397
+ : dto.radiology.supportingInfo || '-'}
398
+ </td>
399
+ <td>{dto.radiology.note || '-'}</td>
400
+ <td>
401
+ <span
402
+ className={`${styles.badge} ${
403
+ isUrgent ? styles.badgeUrgent : styles.badgeRoutine
404
+ }`}
405
+ >
406
+ {dto.radiology.priority || 'Routine'}
407
+ </span>
408
+ </td>
409
+ <td>
410
+ {dto.radiology.requesterDisplay === '--Select--'
411
+ ? '-'
412
+ : dto.radiology.requesterDisplay || '-'}
413
+ </td>
414
+ <td style={{ textAlign: 'center' }}>
415
+ <button
416
+ type="button"
417
+ className={`${styles.btnSelect} ${isSelected ? styles.btnSelected : ''}`}
418
+ onClick={() => handleSelectRow(dto)}
419
+ >
420
+ {isSelected ? t('selected', 'Selected') : t('select', 'Select')}
421
+ </button>
422
+ </td>
423
+ </tr>
424
+ );
425
+ })}
426
+ </tbody>
427
+ </table>
428
+ )}
429
+ </div>
430
+
431
+ {/* Demographics, Investigation Details & Scheduling Form Section */}
432
+ <div className={styles.demographicsSection}>
433
+ <div className={styles.row}>
434
+ {/* Patient Info (col10) */}
435
+ <div className={`${styles.col10} ${styles.formGroup}`}>
436
+ {loadingPatient ? (
437
+ <div style={{ padding: '1rem' }}>
438
+ <Loading description="Loading patient details..." withOverlay={false} small />
439
+ </div>
440
+ ) : (
441
+ <div className={styles.row}>
442
+ {/* Left Column */}
443
+ <div className={styles.col6}>
444
+ <div className={styles.fieldItem}>
445
+ <span className={styles.fieldIcon}>👉</span>
446
+ <span className={styles.fieldTitle}>{t('fullName', 'Full Name')}:</span>
447
+ <span id="txtFullName" className={styles.fieldVal}>
448
+ {patientDetails?.fullName || (selectedOrder?.patientName || '-')}
449
+ </span>
450
+ </div>
451
+ <div className={styles.fieldItem}>
452
+ <span className={styles.fieldIcon}>👉</span>
453
+ <span className={styles.fieldTitle}>{t('gender', 'Gender')}:</span>
454
+ <span id="txtGender" className={styles.fieldVal}>
455
+ {patientDetails?.gender || (selectedOrder?.patientGender || '-')}
456
+ </span>
457
+ </div>
458
+ <div className={styles.fieldItem}>
459
+ <span className={styles.fieldIcon}>👉</span>
460
+ <span className={styles.fieldTitle}>{t('nicNo', 'NIC No')}:</span>
461
+ <span id="txtNICNo" className={styles.fieldVal}>{patientDetails?.nicNo || '-'}</span>
462
+ </div>
463
+ <div className={styles.fieldItem}>
464
+ <span className={styles.fieldIcon}>👉</span>
465
+ <span className={styles.fieldTitle}>{t('guardianInfo', 'Guardian Info')}:</span>
466
+ <span id="txtGurdianInfo" className={styles.fieldVal}>{patientDetails?.guardianInfo || '-'}</span>
467
+ </div>
468
+ </div>
469
+
470
+ {/* Right Column */}
471
+ <div className={styles.col6}>
472
+ <div className={styles.fieldItem}>
473
+ <span className={styles.fieldIcon}>👉</span>
474
+ <span className={styles.fieldTitle}>{t('address', 'Address')}:</span>
475
+ <span id="txtAddress" className={styles.fieldVal}>{patientDetails?.address || '-'}</span>
476
+ </div>
477
+ <div className={styles.fieldItem}>
478
+ <span className={styles.fieldIcon}>👉</span>
479
+ <span className={styles.fieldTitle}>{t('age', 'Age')}:</span>
480
+ <span className={styles.fieldVal}>
481
+ Y: <span id="txtYears">{patientDetails?.years !== undefined && patientDetails?.years !== '' ? patientDetails.years : '-'}</span> |{' '}
482
+ M: <span id="txtMonths">{patientDetails?.months !== undefined && patientDetails?.months !== '' ? patientDetails.months : '-'}</span> |{' '}
483
+ D: <span id="txtDays">{patientDetails?.days !== undefined && patientDetails?.days !== '' ? patientDetails.days : '-'}</span>
484
+ </span>
485
+ </div>
486
+ <div className={styles.fieldItem}>
487
+ <span className={styles.fieldIcon}>👉</span>
488
+ <span className={styles.fieldTitle}>{t('dob', 'Date Of Birth')}:</span>
489
+ <span id="txtDateOfBirth" className={styles.fieldVal}>{patientDetails?.dateOfBirth || '-'}</span>
490
+ </div>
491
+ </div>
492
+ </div>
493
+ )}
494
+ </div>
495
+
496
+ {/* Patient Image (col2) */}
497
+ <div className={`${styles.col2} ${styles.uploadImgWrapper}`}>
498
+ <img
499
+ id="file_upload"
500
+ className={styles.uploadImg}
501
+ src={patientDetails?.profileImage || DEFAULT_AVATAR}
502
+ alt="Patient"
503
+ onError={(e) => {
504
+ (e.currentTarget as HTMLImageElement).src = DEFAULT_AVATAR;
505
+ }}
506
+ />
507
+ </div>
508
+
509
+ {/* Investigation Details Row (col12 with border-bottom) */}
510
+ <div className={`${styles.col12} ${styles.investigationRow}`}>
511
+ <div className={styles.row}>
512
+ <div className={styles.col3}>
513
+ <div className={styles.fieldItem}>
514
+ <span className={styles.fieldIcon}>👉</span>
515
+ <span className={styles.fieldTitle}>{t('testName', 'Test Name')}:</span>
516
+ <span id="lblTestName" className={styles.fieldVal}>
517
+ {selectedOrder?.radiology.codeDisplay || '-'}
518
+ </span>
519
+ </div>
520
+ </div>
521
+ <div className={styles.col3}>
522
+ <div className={styles.fieldItem}>
523
+ <span className={styles.fieldIcon}>👉</span>
524
+ <span className={styles.fieldTitle}>{t('region', 'Region')}:</span>
525
+ <span id="lblRegion" className={styles.fieldVal}>
526
+ {selectedOrder?.radiology.bodySiteText || '-'}
527
+ </span>
528
+ </div>
529
+ </div>
530
+ <div className={styles.col3}>
531
+ <div className={styles.fieldItem}>
532
+ <span className={styles.fieldIcon}>👉</span>
533
+ <span className={styles.fieldTitle}>{t('localization', 'Localization')}:</span>
534
+ <span id="lblLocalization" className={styles.fieldVal}>
535
+ {selectedOrder?.radiology.specimen || '-'}
536
+ </span>
537
+ </div>
538
+ </div>
539
+ <div className={styles.col3}>
540
+ <div className={styles.fieldItem}>
541
+ <span className={styles.fieldIcon}>👉</span>
542
+ <span className={styles.fieldTitle}>{t('position', 'Position')}:</span>
543
+ <span id="lblPosition" className={styles.fieldVal}>
544
+ {selectedOrder?.radiology.bodySiteDisplay || '-'}
545
+ </span>
546
+ </div>
547
+ </div>
548
+
549
+ <div className={styles.col3}>
550
+ <div className={styles.fieldItem}>
551
+ <span className={styles.fieldIcon}>👉</span>
552
+ <span className={styles.fieldTitle}>{t('comment', 'Comment')}:</span>
553
+ <span id="lblComment" className={styles.fieldVal}>
554
+ {selectedOrder?.radiology.note || '-'}
555
+ </span>
556
+ </div>
557
+ </div>
558
+ <div className={styles.col3}>
559
+ <div className={styles.fieldItem}>
560
+ <span className={styles.fieldIcon}>👉</span>
561
+ <span className={styles.fieldTitle}>{t('priority', 'Priority')}:</span>
562
+ <span id="lblPriority" className={styles.fieldVal}>
563
+ {selectedOrder?.radiology.priority || '-'}
564
+ </span>
565
+ </div>
566
+ </div>
567
+ <div className={styles.col3}>
568
+ <div className={styles.fieldItem}>
569
+ <span className={styles.fieldIcon}>👉</span>
570
+ <span className={styles.fieldTitle}>{t('consultant', 'Consultant')}:</span>
571
+ <span id="lblConsultant" className={styles.fieldVal}>
572
+ {selectedOrder?.radiology.requesterDisplay === '--Select--'
573
+ ? '-'
574
+ : selectedOrder?.radiology.requesterDisplay || '-'}
575
+ </span>
576
+ </div>
577
+ </div>
578
+ <div className={styles.col3}>
579
+ <div className={styles.fieldItem}>
580
+ <span className={styles.fieldIcon}>👉</span>
581
+ <span className={styles.fieldTitle}>{t('orderedDateTime', 'Ordered Date & Time')}:</span>
582
+ <span id="lblOdrDateTime" className={styles.fieldVal}>
583
+ {selectedOrder?.radiology.occurrenceDateTime || '-'}
584
+ </span>
585
+ </div>
586
+ </div>
587
+ </div>
588
+ </div>
589
+
590
+ {/* Scheduling Form Section: Scheduled Date Time, Remarks, Modality, Exam Room */}
591
+ <div className={`${styles.col12} ${styles.schedulingFormArea}`}>
592
+ <div className={styles.row}>
593
+ {/* Left Column: Scheduled Date Time & Remarks */}
594
+ <div className={styles.col6}>
595
+ <div className={`${styles.row} ${styles.formGroup}`}>
596
+ <div className={styles.col4}>
597
+ <label className={styles.label}>
598
+ {t('scheduledDateTime', 'Scheduled Date Time')}
599
+ </label>
600
+ </div>
601
+ <div className={styles.col8}>
602
+ <input
603
+ id="txtScheduledDateTime"
604
+ type="datetime-local"
605
+ className={styles.formControl}
606
+ value={scheduledDateTime}
607
+ onChange={(e) => setScheduledDateTime(e.target.value)}
608
+ />
609
+ </div>
610
+ </div>
611
+
612
+ <div className={`${styles.row} ${styles.formGroup}`}>
613
+ <div className={styles.col4}>
614
+ <label className={styles.label}>
615
+ {t('remarks', 'Remarks')}
616
+ </label>
617
+ </div>
618
+ <div className={styles.col8}>
619
+ <textarea
620
+ id="txtRemarks"
621
+ className={styles.formControl}
622
+ rows={2}
623
+ placeholder={t('enterRemarks', 'Remarks...')}
624
+ value={remarks}
625
+ onChange={(e) => setRemarks(e.target.value)}
626
+ />
627
+ </div>
628
+ </div>
629
+
630
+ {/* Action Buttons: Big blue Save button + Cancel */}
631
+ <div className={styles.buttonGroup}>
632
+ <button
633
+ id="btnSave"
634
+ type="button"
635
+ className={styles.btnPrimary}
636
+ onClick={handleSave}
637
+ disabled={isSaving}
638
+ >
639
+ {isSaving ? t('saving', 'Saving...') : t('save', 'Save')}
640
+ </button>
641
+ <button
642
+ id="btnClear"
643
+ type="button"
644
+ className={styles.btnCancel}
645
+ onClick={handleClear}
646
+ disabled={isSaving}
647
+ >
648
+ {t('cancel', 'Cancel')}
649
+ </button>
650
+ </div>
651
+ </div>
652
+
653
+ {/* Right Column: Modality & Exam Room */}
654
+ <div className={styles.col6}>
655
+ <div className={`${styles.row} ${styles.formGroup}`}>
656
+ <div className={styles.col4}>
657
+ <label className={styles.label}>
658
+ {t('modality', 'Modality')}
659
+ </label>
660
+ </div>
661
+ <div className={styles.col8}>
662
+ <select
663
+ id="drpSelectModality"
664
+ className={styles.formControl}
665
+ value={selectedModality}
666
+ onChange={(e) => setSelectedModality(e.target.value)}
667
+ >
668
+ <option value="all">--Select--</option>
669
+ <option value="Xray">Xray</option>
670
+ <option value="CT">CT</option>
671
+ <option value="MRI">MRI</option>
672
+ </select>
673
+ </div>
674
+ </div>
675
+
676
+ <div className={`${styles.row} ${styles.formGroup}`}>
677
+ <div className={styles.col4}>
678
+ <label className={styles.label}>
679
+ {t('examRoom', 'Exam Room')}
680
+ </label>
681
+ </div>
682
+ <div className={styles.col8}>
683
+ <select
684
+ id="drpSelectExamRoom"
685
+ className={styles.formControl}
686
+ value={selectedExamRoom}
687
+ onChange={(e) => setSelectedExamRoom(e.target.value)}
688
+ disabled={selectedModality === 'all'}
689
+ >
690
+ <option value="all">--Select--</option>
691
+ {examRooms.map((room, i) => (
692
+ <option key={`${room.name}-${i}`} value={room.name}>
693
+ {room.name}
694
+ </option>
695
+ ))}
696
+ </select>
697
+ </div>
698
+ </div>
699
+ </div>
700
+ </div>
701
+ </div>
702
+
703
+ </div>
704
+ </div>
705
+ </div>
706
+ </div>
707
+ </div>
708
+ );
709
+ };
710
+
711
+ export default RadiologyTestScheduling;