@sasindi/radiologytestscheduling 1.0.1 → 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.
@@ -7,10 +7,10 @@ import {
7
7
  fetchInstitutes,
8
8
  fetchPendingSchedulingOrders,
9
9
  fetchPatientDetails,
10
+ fetchPatientBasicInfo,
10
11
  fetchLocationDisplay,
11
12
  fetchUserDisplay,
12
13
  fetchExamRooms,
13
- fetchCalendarData,
14
14
  saveScheduling,
15
15
  SessionInfo,
16
16
  } from './radiology-test-scheduling.resource';
@@ -19,7 +19,6 @@ import {
19
19
  PatientDemographics,
20
20
  InstituteOption,
21
21
  ExamRoom,
22
- RadiologyOrder,
23
22
  } from './types';
24
23
  import styles from './radiology-test-scheduling.scss';
25
24
 
@@ -49,18 +48,13 @@ const RadiologyTestScheduling: React.FC = () => {
49
48
  const [patientDetails, setPatientDetails] = useState<PatientDemographics | null>(null);
50
49
  const [loadingPatient, setLoadingPatient] = useState<boolean>(false);
51
50
 
52
- // Scheduling inputs
51
+ // Scheduling Form inputs: Scheduled Date Time, Remarks, Modality, Exam Room
53
52
  const [scheduledDateTime, setScheduledDateTime] = useState<string>('');
54
53
  const [remarks, setRemarks] = useState<string>('');
55
- const [isSaving, setIsSaving] = useState<boolean>(false);
56
-
57
- // Calendar filter states
58
54
  const [selectedModality, setSelectedModality] = useState<string>('all');
59
55
  const [examRooms, setExamRooms] = useState<ExamRoom[]>([]);
60
56
  const [selectedExamRoom, setSelectedExamRoom] = useState<string>('all');
61
- const [calendarOrders, setCalendarOrders] = useState<RadiologyOrder[]>([]);
62
- const [calendarDate, setCalendarDate] = useState<Date>(new Date());
63
- const [loadingCalendar, setLoadingCalendar] = useState<boolean>(false);
57
+ const [isSaving, setIsSaving] = useState<boolean>(false);
64
58
 
65
59
  // Initialize Session and Institutes
66
60
  useEffect(() => {
@@ -91,17 +85,25 @@ const RadiologyTestScheduling: React.FC = () => {
91
85
  setLoadingOrders(true);
92
86
  try {
93
87
  const orders = await fetchPendingSchedulingOrders(instUuid);
94
- // Enhance with async location / user names if available
88
+
89
+ // Enhance with location, clinician, and patient basic demographics
95
90
  const enhanced = await Promise.all(
96
91
  orders.map(async (dto) => {
97
92
  const loc = dto.radiology.location ? await fetchLocationDisplay(dto.radiology.location) : '';
98
93
  const ordBy = dto.radiology.requesterReference
99
94
  ? await fetchUserDisplay(dto.radiology.requesterReference)
100
95
  : '';
96
+ const patientInfo = dto.radiology.patientPhn
97
+ ? await fetchPatientBasicInfo(dto.radiology.patientPhn)
98
+ : { name: '', gender: '', age: '' };
99
+
101
100
  return {
102
101
  ...dto,
103
102
  locationName: loc,
104
103
  orderedByName: ordBy,
104
+ patientName: patientInfo.name,
105
+ patientGender: patientInfo.gender,
106
+ patientAge: patientInfo.age,
105
107
  };
106
108
  }),
107
109
  );
@@ -112,6 +114,7 @@ const RadiologyTestScheduling: React.FC = () => {
112
114
  icon: 'error',
113
115
  title: 'Error Loading Orders',
114
116
  text: 'Unable to fetch pending radiology test orders.',
117
+ confirmButtonColor: '#da1e28',
115
118
  });
116
119
  } finally {
117
120
  setLoadingOrders(false);
@@ -124,34 +127,16 @@ const RadiologyTestScheduling: React.FC = () => {
124
127
 
125
128
  // Load Exam Rooms when modality changes
126
129
  useEffect(() => {
127
- (async () => {
128
- if (selectedModality === 'all' || !selectedModality) {
129
- setExamRooms([]);
130
- setSelectedExamRoom('all');
131
- } else {
130
+ if (selectedModality === 'all' || !selectedModality) {
131
+ setExamRooms([]);
132
+ setSelectedExamRoom('all');
133
+ } else {
134
+ (async () => {
132
135
  const rooms = await fetchExamRooms(selectedModality);
133
136
  setExamRooms(rooms);
134
- setSelectedExamRoom('all');
135
- }
136
- })();
137
- }, [selectedModality]);
138
-
139
- // Load Calendar Data
140
- const loadCalendar = useCallback(async () => {
141
- setLoadingCalendar(true);
142
- try {
143
- const data = await fetchCalendarData(selectedModality, selectedExamRoom, selectedInstitute);
144
- setCalendarOrders(data);
145
- } catch (err) {
146
- console.error('Failed to load calendar data', err);
147
- } finally {
148
- setLoadingCalendar(false);
137
+ })();
149
138
  }
150
- }, [selectedModality, selectedExamRoom, selectedInstitute]);
151
-
152
- useEffect(() => {
153
- loadCalendar();
154
- }, [loadCalendar]);
139
+ }, [selectedModality]);
155
140
 
156
141
  // Handle row selection
157
142
  const handleSelectRow = async (dto: SchedulingOrderDTO) => {
@@ -160,6 +145,28 @@ const RadiologyTestScheduling: React.FC = () => {
160
145
  setRemarks('');
161
146
  setLoadingPatient(true);
162
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
+
163
170
  try {
164
171
  const details = await fetchPatientDetails(dto.radiology.patientPhn);
165
172
  setPatientDetails(details);
@@ -176,6 +183,9 @@ const RadiologyTestScheduling: React.FC = () => {
176
183
  setPatientDetails(null);
177
184
  setScheduledDateTime('');
178
185
  setRemarks('');
186
+ setSelectedModality('all');
187
+ setSelectedExamRoom('all');
188
+ setExamRooms([]);
179
189
  };
180
190
 
181
191
  // Save Schedule Handler
@@ -201,7 +211,7 @@ const RadiologyTestScheduling: React.FC = () => {
201
211
  }
202
212
 
203
213
  // Format datetime string: "YYYY-MM-DD HH:mm:ss"
204
- const formattedDateTime = scheduledDateTime.replace('T', ' ') + ':00';
214
+ const formattedDateTime = scheduledDateTime.replace('T', ' ') + (scheduledDateTime.length === 16 ? ':00' : '');
205
215
 
206
216
  const result = await Swal.fire({
207
217
  title: 'Confirm Scheduling',
@@ -237,7 +247,6 @@ const RadiologyTestScheduling: React.FC = () => {
237
247
 
238
248
  handleClear();
239
249
  loadPendingOrders(selectedInstitute);
240
- loadCalendar();
241
250
  } catch (err: any) {
242
251
  console.error('Failed to save schedule', err);
243
252
  Swal.fire({
@@ -257,12 +266,14 @@ const RadiologyTestScheduling: React.FC = () => {
257
266
  const term = searchTerm.toLowerCase();
258
267
  return pendingOrders.filter((dto) => {
259
268
  const phn = dto.radiology.patientPhn?.toLowerCase() || '';
269
+ const name = dto.patientName?.toLowerCase() || '';
260
270
  const test = dto.radiology.codeDisplay?.toLowerCase() || '';
261
271
  const section = dto.type?.toLowerCase() || '';
262
272
  const priority = dto.radiology.priority?.toLowerCase() || '';
263
273
  const location = dto.locationName?.toLowerCase() || '';
264
274
  return (
265
275
  phn.includes(term) ||
276
+ name.includes(term) ||
266
277
  test.includes(term) ||
267
278
  section.includes(term) ||
268
279
  priority.includes(term) ||
@@ -271,109 +282,6 @@ const RadiologyTestScheduling: React.FC = () => {
271
282
  });
272
283
  }, [pendingOrders, searchTerm]);
273
284
 
274
- // Calendar grouping helper
275
- const calendarDays = useMemo(() => {
276
- const year = calendarDate.getFullYear();
277
- const month = calendarDate.getMonth();
278
- const firstDayIndex = new Date(year, month, 1).getDay();
279
- const totalDays = new Date(year, month + 1, 0).getDate();
280
- const prevMonthDays = new Date(year, month, 0).getDate();
281
-
282
- // Map scheduled tests by date string "YYYY-MM-DD"
283
- const dateMap = new Map<string, RadiologyOrder[]>();
284
- for (const ord of calendarOrders) {
285
- if (ord.scheduledDateTime) {
286
- const dStr = ord.scheduledDateTime.split(' ')[0];
287
- if (!dateMap.has(dStr)) {
288
- dateMap.set(dStr, []);
289
- }
290
- dateMap.get(dStr)!.push(ord);
291
- }
292
- }
293
-
294
- const cells = [];
295
-
296
- // Previous month filler days
297
- for (let i = firstDayIndex - 1; i >= 0; i--) {
298
- const dayNum = prevMonthDays - i;
299
- cells.push({
300
- dayNum,
301
- isCurrentMonth: false,
302
- dateStr: '',
303
- orders: [],
304
- });
305
- }
306
-
307
- // Current month days
308
- const todayStr = new Date().toISOString().split('T')[0];
309
- for (let day = 1; day <= totalDays; day++) {
310
- const monthStr = String(month + 1).padStart(2, '0');
311
- const dayStr = String(day).padStart(2, '0');
312
- const dateStr = `${year}-${monthStr}-${dayStr}`;
313
- cells.push({
314
- dayNum: day,
315
- isCurrentMonth: true,
316
- isToday: dateStr === todayStr,
317
- dateStr,
318
- orders: dateMap.get(dateStr) || [],
319
- });
320
- }
321
-
322
- // Next month filler days to complete grid (multiples of 7)
323
- const remaining = 7 - (cells.length % 7);
324
- if (remaining < 7) {
325
- for (let i = 1; i <= remaining; i++) {
326
- cells.push({
327
- dayNum: i,
328
- isCurrentMonth: false,
329
- dateStr: '',
330
- orders: [],
331
- });
332
- }
333
- }
334
-
335
- return cells;
336
- }, [calendarDate, calendarOrders]);
337
-
338
- // Handle clicking calendar event or day
339
- const handleCalendarEventClick = (dateStr: string, orders: RadiologyOrder[]) => {
340
- if (!orders || orders.length === 0) return;
341
-
342
- // Group orders by modality and exam room
343
- const breakdown: Record<string, Record<string, number>> = {};
344
- for (const ord of orders) {
345
- const mod = ord.categoryDisplay || 'Other';
346
- const room = ord.supportingInfo || 'Default Room';
347
- if (!breakdown[mod]) {
348
- breakdown[mod] = {};
349
- }
350
- breakdown[mod][room] = (breakdown[mod][room] || 0) + 1;
351
- }
352
-
353
- let html = `<div style="text-align: left; font-size: 0.92rem;">`;
354
- for (const [mod, rooms] of Object.entries(breakdown)) {
355
- html += `<div style="margin-bottom: 0.75rem;"><strong style="color: #009384;">${mod}</strong><ul style="margin: 0.25rem 0 0 1.25rem; padding: 0;">`;
356
- for (const [room, count] of Object.entries(rooms)) {
357
- html += `<li><strong>${room}:</strong> ${count} scheduled</li>`;
358
- }
359
- html += `</ul></div>`;
360
- }
361
- html += `</div>`;
362
-
363
- Swal.fire({
364
- title: `Scheduled Tests (${dateStr})`,
365
- html,
366
- icon: 'info',
367
- confirmButtonColor: '#0f62fe',
368
- confirmButtonText: 'Close',
369
- });
370
- };
371
-
372
- const monthNames = [
373
- 'January', 'February', 'March', 'April', 'May', 'June',
374
- 'July', 'August', 'September', 'October', 'November', 'December',
375
- ];
376
-
377
285
  return (
378
286
  <div className={styles.bodyWrapper}>
379
287
  {/* Main Unified Card: Heading inside the form container as required */}
@@ -384,15 +292,15 @@ const RadiologyTestScheduling: React.FC = () => {
384
292
  </h2>
385
293
  </div>
386
294
 
387
- {/* Institute and Search Filter Box */}
388
- <div className={styles.filterBox}>
389
- <div className={styles.row}>
390
- <div className={`${styles.col4} ${styles.formGroup}`}>
391
- <label className={styles.label}>
392
- <span>{t('institute', 'Institute')}</span>
393
- </label>
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>
394
300
  <select
301
+ id="drpInstitute"
395
302
  className={styles.formControl}
303
+ style={{ width: '260px' }}
396
304
  value={selectedInstitute}
397
305
  onChange={(e) => setSelectedInstitute(e.target.value)}
398
306
  >
@@ -405,466 +313,394 @@ const RadiologyTestScheduling: React.FC = () => {
405
313
  </select>
406
314
  </div>
407
315
 
408
- <div className={`${styles.col8} ${styles.formGroup}`}>
409
- <label className={styles.label}>
410
- <span>{t('searchPendingTests', 'Search Pending Tests')}</span>
411
- </label>
316
+ <div className={styles.searchGroup}>
412
317
  <input
413
318
  type="text"
414
319
  className={styles.formControl}
415
- placeholder={t('searchPlaceholder', 'Type PHN, Test Name, Section, or Location...')}
320
+ placeholder={t('searchPlaceholder', 'Search by PHN, Patient Name, Test Name, Section, or Location...')}
416
321
  value={searchTerm}
417
322
  onChange={(e) => setSearchTerm(e.target.value)}
418
323
  />
419
324
  </div>
420
325
  </div>
421
- </div>
422
326
 
423
- {/* Pending Tests Table */}
424
- <div className={styles.sectionHeader}>
425
- <span>{t('pendingSchedulingRequests', 'Pending Scheduling Requests')}</span>
426
- <span className={`${styles.badge} ${styles.badgeType}`} style={{ marginLeft: 'auto' }}>
427
- {filteredOrders.length} {t('requests', 'Requests')}
428
- </span>
429
- </div>
430
-
431
- <div className={styles.tableContainer}>
432
- {loadingOrders ? (
433
- <div style={{ padding: '2rem' }}>
434
- <Loading description="Loading pending tests..." withOverlay={false} small />
435
- </div>
436
- ) : filteredOrders.length === 0 ? (
437
- <div className={styles.emptyState}>
438
- {t('noPendingOrders', 'No pending radiology test scheduling requests found.')}
439
- </div>
440
- ) : (
441
- <table className={styles.table}>
442
- <thead>
443
- <tr>
444
- <th>#</th>
445
- <th>{t('institute', 'Institute')}</th>
446
- <th>{t('section', 'Section')}</th>
447
- <th>{t('phn', 'PHN')}</th>
448
- <th>{t('admittedDate', 'Admitted Date')}</th>
449
- <th>{t('orderedBy', 'Ordered By')}</th>
450
- <th>{t('orderedDatetime', 'Ordered Datetime')}</th>
451
- <th>{t('testName', 'Test Name')}</th>
452
- <th>{t('region', 'Region')}</th>
453
- <th>{t('examRoom', 'Exam Room')}</th>
454
- <th>{t('comment', 'Comment')}</th>
455
- <th>{t('priority', 'Priority')}</th>
456
- <th>{t('consultant', 'Consultant')}</th>
457
- <th style={{ textAlign: 'center' }}>{t('action', 'Action')}</th>
458
- </tr>
459
- </thead>
460
- <tbody>
461
- {filteredOrders.map((dto, idx) => {
462
- const isUrgent = dto.radiology.priority === 'Urgent';
463
- const isSelected = selectedOrder?.radiology.id === dto.radiology.id;
464
- const rowClass = `${isUrgent ? styles.urgentRow : ''} ${
465
- isSelected ? styles.selectedRow : ''
466
- }`.trim();
467
-
468
- return (
469
- <tr key={`${dto.type}-${dto.radiology.id}`} className={rowClass}>
470
- <td>{idx + 1}</td>
471
- <td>{dto.locationName || dto.radiology.location}</td>
472
- <td>
473
- <span className={`${styles.badge} ${styles.badgeType}`}>
474
- {dto.type}
475
- </span>
476
- </td>
477
- <td>
478
- <strong>{dto.radiology.patientPhn}</strong>
479
- </td>
480
- <td>
481
- {dto.radiology.occurrenceDateTime
482
- ? dto.radiology.occurrenceDateTime.split(' ')[0]
483
- : '-'}
484
- </td>
485
- <td>{dto.orderedByName || dto.radiology.requesterReference || '-'}</td>
486
- <td>{dto.radiology.occurrenceDateTime || '-'}</td>
487
- <td>
488
- <strong>{dto.radiology.codeDisplay || '-'}</strong>
489
- </td>
490
- <td>{dto.radiology.bodySiteText || '-'}</td>
491
- <td>
492
- {dto.radiology.supportingInfo === '--Select--'
493
- ? '-'
494
- : dto.radiology.supportingInfo || '-'}
495
- </td>
496
- <td>{dto.radiology.note || '-'}</td>
497
- <td>
498
- <span
499
- className={`${styles.badge} ${
500
- isUrgent ? styles.badgeUrgent : styles.badgeRoutine
501
- }`}
502
- >
503
- {dto.radiology.priority || 'Routine'}
504
- </span>
505
- </td>
506
- <td>
507
- {dto.radiology.requesterDisplay === '--Select--'
508
- ? '-'
509
- : dto.radiology.requesterDisplay || '-'}
510
- </td>
511
- <td style={{ textAlign: 'center' }}>
512
- <button
513
- type="button"
514
- className={styles.btnSelect}
515
- onClick={() => handleSelectRow(dto)}
516
- >
517
- {isSelected ? t('selected', 'Selected') : t('select', 'Select')}
518
- </button>
519
- </td>
520
- </tr>
521
- );
522
- })}
523
- </tbody>
524
- </table>
525
- )}
526
- </div>
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>
527
430
 
528
- {/* Details and Scheduling Form */}
529
- {selectedOrder && (
530
- <div className={styles.detailsPanel}>
431
+ {/* Demographics, Investigation Details & Scheduling Form Section */}
432
+ <div className={styles.demographicsSection}>
531
433
  <div className={styles.row}>
532
- {/* Patient Demographics */}
533
- <div className={`${styles.col6} ${styles.formGroup}`}>
534
- <div className={styles.sectionHeader}>
535
- <span>{t('patientDemographics', 'Patient Demographics')}</span>
536
- {selectedOrder.radiology.patientPhn && (
537
- <span style={{ fontSize: '0.85rem', color: '#6c757d', marginLeft: 'auto' }}>
538
- PHN: ({selectedOrder.radiology.patientPhn})
539
- </span>
540
- )}
541
- </div>
542
-
434
+ {/* Patient Info (col10) */}
435
+ <div className={`${styles.col10} ${styles.formGroup}`}>
543
436
  {loadingPatient ? (
544
- <Loading description="Loading patient demographics..." withOverlay={false} small />
437
+ <div style={{ padding: '1rem' }}>
438
+ <Loading description="Loading patient details..." withOverlay={false} small />
439
+ </div>
545
440
  ) : (
546
441
  <div className={styles.row}>
547
- <div className={styles.col8}>
548
- <div className={styles.infoField}>
549
- <span className={styles.infoLabel}>{t('fullName', 'Full Name')}:</span>
550
- <span className={styles.infoValue}>
551
- {patientDetails?.fullName || '-'}
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 || '-')}
552
449
  </span>
553
450
  </div>
554
- <div className={styles.infoField}>
555
- <span className={styles.infoLabel}>{t('gender', 'Gender')}:</span>
556
- <span className={styles.infoValue}>{patientDetails?.gender || '-'}</span>
557
- </div>
558
- <div className={styles.infoField}>
559
- <span className={styles.infoLabel}>{t('nicNo', 'NIC No')}:</span>
560
- <span className={styles.infoValue}>{patientDetails?.nicNo || '-'}</span>
561
- </div>
562
- <div className={styles.infoField}>
563
- <span className={styles.infoLabel}>{t('guardianInfo', 'Guardian Info')}:</span>
564
- <span className={styles.infoValue}>
565
- {patientDetails?.guardianInfo || '-'}
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 || '-')}
566
456
  </span>
567
457
  </div>
568
- <div className={styles.infoField}>
569
- <span className={styles.infoLabel}>{t('address', 'Address')}:</span>
570
- <span className={styles.infoValue}>{patientDetails?.address || '-'}</span>
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>
571
462
  </div>
572
- <div className={styles.infoField}>
573
- <span className={styles.infoLabel}>{t('age', 'Age')}:</span>
574
- <span className={styles.infoValue}>
575
- Y: {patientDetails?.years || 0} | M: {patientDetails?.months || 0} | D:{' '}
576
- {patientDetails?.days || 0}
577
- </span>
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>
578
467
  </div>
579
- <div className={styles.infoField}>
580
- <span className={styles.infoLabel}>{t('dob', 'Date Of Birth')}:</span>
581
- <span className={styles.infoValue}>
582
- {patientDetails?.dateOfBirth || '-'}
583
- </span>
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>
584
476
  </div>
585
- <div className={styles.infoField}>
586
- <span className={styles.infoLabel}>{t('telephone', 'Telephone')}:</span>
587
- <span className={styles.infoValue}>
588
- {[patientDetails?.telephoneHome, patientDetails?.telephoneMobile]
589
- .filter(Boolean)
590
- .join(', ') || '-'}
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>
591
484
  </span>
592
485
  </div>
593
- </div>
594
-
595
- <div className={`${styles.col4} ${styles.avatarContainer}`}>
596
- <img
597
- className={styles.avatarImg}
598
- src={patientDetails?.profileImage || DEFAULT_AVATAR}
599
- alt="Patient"
600
- onError={(e) => {
601
- (e.currentTarget as HTMLImageElement).src = DEFAULT_AVATAR;
602
- }}
603
- />
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>
604
491
  </div>
605
492
  </div>
606
493
  )}
607
494
  </div>
608
495
 
609
- {/* Investigation Details */}
610
- <div className={`${styles.col6} ${styles.formGroup}`}>
611
- <div className={styles.sectionHeader}>
612
- <span>{t('investigationDetails', 'Investigation Details')}</span>
613
- </div>
614
-
615
- <div className={styles.infoField}>
616
- <span className={styles.infoLabel}>{t('testName', 'Test Name')}:</span>
617
- <span className={styles.infoValue}>
618
- {selectedOrder.radiology.codeDisplay || '-'}
619
- </span>
620
- </div>
621
- <div className={styles.infoField}>
622
- <span className={styles.infoLabel}>{t('region', 'Region')}:</span>
623
- <span className={styles.infoValue}>
624
- {selectedOrder.radiology.bodySiteText || '-'}
625
- </span>
626
- </div>
627
- <div className={styles.infoField}>
628
- <span className={styles.infoLabel}>{t('localization', 'Localization')}:</span>
629
- <span className={styles.infoValue}>
630
- {selectedOrder.radiology.specimen || '-'}
631
- </span>
632
- </div>
633
- <div className={styles.infoField}>
634
- <span className={styles.infoLabel}>{t('position', 'Position')}:</span>
635
- <span className={styles.infoValue}>
636
- {selectedOrder.radiology.bodySiteDisplay || '-'}
637
- </span>
638
- </div>
639
- <div className={styles.infoField}>
640
- <span className={styles.infoLabel}>{t('comment', 'Comment')}:</span>
641
- <span className={styles.infoValue}>{selectedOrder.radiology.note || '-'}</span>
642
- </div>
643
- <div className={styles.infoField}>
644
- <span className={styles.infoLabel}>{t('priority', 'Priority')}:</span>
645
- <span
646
- className={`${styles.badge} ${
647
- selectedOrder.radiology.priority === 'Urgent'
648
- ? styles.badgeUrgent
649
- : styles.badgeRoutine
650
- }`}
651
- >
652
- {selectedOrder.radiology.priority || 'Routine'}
653
- </span>
654
- </div>
655
- <div className={styles.infoField}>
656
- <span className={styles.infoLabel}>{t('consultant', 'Consultant')}:</span>
657
- <span className={styles.infoValue}>
658
- {selectedOrder.radiology.requesterDisplay === '--Select--'
659
- ? '-'
660
- : selectedOrder.radiology.requesterDisplay || '-'}
661
- </span>
662
- </div>
663
- <div className={styles.infoField}>
664
- <span className={styles.infoLabel}>{t('orderedDateTime', 'Ordered Date & Time')}:</span>
665
- <span className={styles.infoValue}>
666
- {selectedOrder.radiology.occurrenceDateTime || '-'}
667
- </span>
668
- </div>
669
- </div>
670
- </div>
671
-
672
- {/* Scheduling Form Inputs */}
673
- <div className={styles.row} style={{ marginTop: '1.25rem' }}>
674
- <div className={`${styles.col6} ${styles.formGroup}`}>
675
- <label className={styles.label}>
676
- <span>{t('scheduledDateTime', 'Scheduled Date Time')} *</span>
677
- </label>
678
- <input
679
- type="datetime-local"
680
- className={styles.formControl}
681
- value={scheduledDateTime}
682
- onChange={(e) => setScheduledDateTime(e.target.value)}
683
- required
684
- />
685
- </div>
686
-
687
- <div className={`${styles.col6} ${styles.formGroup}`}>
688
- <label className={styles.label}>
689
- <span>{t('remarks', 'Remarks')}</span>
690
- </label>
691
- <textarea
692
- className={styles.formControl}
693
- rows={2}
694
- placeholder={t('enterRemarks', 'Add scheduling remarks or instructions...')}
695
- value={remarks}
696
- onChange={(e) => setRemarks(e.target.value)}
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
+ }}
697
506
  />
698
507
  </div>
699
- </div>
700
-
701
- {/* Action Buttons: Save button is prominent blue as requested */}
702
- <div className={styles.buttonGroup}>
703
- <button
704
- type="button"
705
- className={styles.btnPrimary}
706
- onClick={handleSave}
707
- disabled={isSaving}
708
- >
709
- {isSaving ? t('saving', 'Saving...') : t('saveSchedule', 'Save Schedule')}
710
- </button>
711
- <button
712
- type="button"
713
- className={styles.btnCancel}
714
- onClick={handleClear}
715
- disabled={isSaving}
716
- >
717
- {t('cancel', 'Cancel')}
718
- </button>
719
- </div>
720
- </div>
721
- )}
722
-
723
- {/* Calendar Overview Section */}
724
- <div style={{ marginTop: '2.5rem' }}>
725
- <div className={styles.sectionHeader}>
726
- <span>{t('scheduledCalendarOverview', 'Scheduled Calendar Overview')}</span>
727
- </div>
728
-
729
- <div className={styles.row} style={{ marginBottom: '1rem' }}>
730
- <div className={`${styles.col4} ${styles.formGroup}`}>
731
- <label className={styles.label}>
732
- <span>{t('modality', 'Modality')}</span>
733
- </label>
734
- <select
735
- className={styles.formControl}
736
- value={selectedModality}
737
- onChange={(e) => setSelectedModality(e.target.value)}
738
- >
739
- <option value="all">{t('allModalities', '-- All Modalities --')}</option>
740
- <option value="Xray">Xray</option>
741
- <option value="CT">CT</option>
742
- <option value="MRI">MRI</option>
743
- </select>
744
- </div>
745
508
 
746
- <div className={`${styles.col4} ${styles.formGroup}`}>
747
- <label className={styles.label}>
748
- <span>{t('examRoom', 'Exam Room')}</span>
749
- </label>
750
- <select
751
- className={styles.formControl}
752
- value={selectedExamRoom}
753
- onChange={(e) => setSelectedExamRoom(e.target.value)}
754
- disabled={selectedModality === 'all'}
755
- >
756
- <option value="all">{t('allExamRooms', '-- All Exam Rooms --')}</option>
757
- {examRooms.map((room, i) => (
758
- <option key={`${room.name}-${i}`} value={room.name}>
759
- {room.name}
760
- </option>
761
- ))}
762
- </select>
763
- </div>
764
- </div>
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>
765
548
 
766
- {/* Interactive Calendar Grid */}
767
- <div className={styles.calendarWrapper}>
768
- <div className={styles.calendarNav}>
769
- <button
770
- type="button"
771
- className={styles.calendarNavBtn}
772
- onClick={() =>
773
- setCalendarDate(
774
- new Date(calendarDate.getFullYear(), calendarDate.getMonth() - 1, 1),
775
- )
776
- }
777
- >
778
- &larr; {t('previous', 'Previous')}
779
- </button>
780
- <span className={styles.calendarMonthTitle}>
781
- {monthNames[calendarDate.getMonth()]} {calendarDate.getFullYear()}
782
- </span>
783
- <div style={{ display: 'flex', gap: '0.5rem' }}>
784
- <button
785
- type="button"
786
- className={styles.calendarNavBtn}
787
- onClick={() => setCalendarDate(new Date())}
788
- >
789
- {t('today', 'Today')}
790
- </button>
791
- <button
792
- type="button"
793
- className={styles.calendarNavBtn}
794
- onClick={() =>
795
- setCalendarDate(
796
- new Date(calendarDate.getFullYear(), calendarDate.getMonth() + 1, 1),
797
- )
798
- }
799
- >
800
- {t('next', 'Next')} &rarr;
801
- </button>
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>
802
588
  </div>
803
- </div>
804
589
 
805
- {loadingCalendar ? (
806
- <div style={{ padding: '2rem' }}>
807
- <Loading description="Loading calendar appointments..." withOverlay={false} small />
808
- </div>
809
- ) : (
810
- <div className={styles.calendarGrid}>
811
- {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map((d) => (
812
- <div key={d} className={styles.calendarDayHeader}>
813
- {d}
814
- </div>
815
- ))}
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>
816
611
 
817
- {calendarDays.map((cell, idx) => {
818
- const cellClass = `${styles.calendarCell} ${
819
- !cell.isCurrentMonth ? styles.calendarCellInactive : ''
820
- }`.trim();
821
-
822
- // Group cell orders by category
823
- const categoryCounts: Record<string, number> = {};
824
- for (const ord of cell.orders) {
825
- const cat = ord.categoryDisplay || 'Other';
826
- categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
827
- }
828
-
829
- return (
830
- <div
831
- key={idx}
832
- className={cellClass}
833
- onClick={() =>
834
- cell.orders.length > 0 &&
835
- handleCalendarEventClick(cell.dateStr, cell.orders)
836
- }
837
- style={{ cursor: cell.orders.length > 0 ? 'pointer' : 'default' }}
838
- >
839
- <span
840
- className={`${styles.calendarDayNumber} ${
841
- cell.isToday ? styles.calendarDayToday : ''
842
- }`}
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}
843
638
  >
844
- {cell.dayNum}
845
- </span>
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>
846
652
 
847
- {Object.entries(categoryCounts).map(([cat, count]) => {
848
- let badgeCls = styles.badgeOther;
849
- if (cat === 'Xray') badgeCls = styles.badgeXray;
850
- else if (cat === 'CT') badgeCls = styles.badgeCT;
851
- else if (cat === 'MRI') badgeCls = styles.badgeMRI;
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>
852
675
 
853
- return (
854
- <span
855
- key={cat}
856
- className={`${styles.calendarBadge} ${badgeCls}`}
857
- title={`${cat}: ${count} scheduled. Click to see details.`}
858
- >
859
- {cat}: {count}
860
- </span>
861
- );
862
- })}
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>
863
698
  </div>
864
- );
865
- })}
699
+ </div>
700
+ </div>
866
701
  </div>
867
- )}
702
+
703
+ </div>
868
704
  </div>
869
705
  </div>
870
706
  </div>