@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.
@@ -10,6 +10,7 @@ import {
10
10
  // In-memory cache for location and user displays to speed up table population
11
11
  const locationCache = new Map<string, string>();
12
12
  const userCache = new Map<string, string>();
13
+ const patientBasicCache = new Map<string, { name: string; gender: string; age: string | number }>();
13
14
 
14
15
  export interface SessionInfo {
15
16
  userUuid: string;
@@ -69,27 +70,18 @@ export async function fetchCurrentSessionAndInstitute(): Promise<SessionInfo> {
69
70
 
70
71
  /**
71
72
  * Fetch list of hospitals / institutes.
73
+ * Standard OpenMRS 3 query filtering for address14 === 'Hospital'.
72
74
  */
73
75
  export async function fetchInstitutes(): Promise<InstituteOption[]> {
74
76
  try {
75
- const res = await openmrsFetch(`${restBaseUrl}/patientregister/patientregister/getSelectedTypeData/Hospital`);
76
- const data = res.data?.data || res.data || [];
77
- if (Array.isArray(data) && data.length > 0) {
78
- return data.map((item: any) => ({
79
- uuid: item.uuid,
80
- name: item.name ? item.name.trim() : item.display || item.uuid,
81
- }));
82
- }
83
- } catch (e) {
84
- console.warn('Error fetching institutes from patientregister endpoint, falling back to location API', e);
85
- }
86
-
87
- try {
88
- const res = await openmrsFetch(`${restBaseUrl}/location?v=custom:(uuid,display)`);
77
+ const res = await openmrsFetch(`${restBaseUrl}/location?v=custom:(uuid,name,display,address14)&limit=100`);
89
78
  const results = res.data?.results || [];
90
- return results.map((item: any) => ({
79
+ const hospitals = results.filter((loc: any) => loc.address14 === 'Hospital');
80
+ const sourceList = hospitals.length > 0 ? hospitals : results;
81
+
82
+ return sourceList.map((item: any) => ({
91
83
  uuid: item.uuid,
92
- name: item.display || item.uuid,
84
+ name: (item.name || item.display || item.uuid).trim(),
93
85
  }));
94
86
  } catch (err) {
95
87
  console.error('Failed to load locations', err);
@@ -133,6 +125,32 @@ export async function fetchUserDisplay(userUuid: string): Promise<string> {
133
125
  }
134
126
  }
135
127
 
128
+ /**
129
+ * Fetch patient basic info (name, gender, age) for table display with caching.
130
+ */
131
+ export async function fetchPatientBasicInfo(phn: string): Promise<{ name: string; gender: string; age: string | number }> {
132
+ if (!phn) return { name: '', gender: '', age: '' };
133
+ if (patientBasicCache.has(phn)) {
134
+ return patientBasicCache.get(phn)!;
135
+ }
136
+ try {
137
+ const phnRes = await openmrsFetch(`${restBaseUrl}/phn/${encodeURIComponent(phn)}`);
138
+ const person = phnRes.data?.person_id?.person;
139
+ if (person) {
140
+ const info = {
141
+ name: person.display || '',
142
+ gender: person.gender || '',
143
+ age: person.age !== undefined && person.age !== null ? person.age : '',
144
+ };
145
+ patientBasicCache.set(phn, info);
146
+ return info;
147
+ }
148
+ } catch (err) {
149
+ console.warn('Error fetching patient basic info for PHN', phn, err);
150
+ }
151
+ return { name: '', gender: '', age: '' };
152
+ }
153
+
136
154
  /**
137
155
  * Fetch patient demographics & address by PHN.
138
156
  */
@@ -10,11 +10,11 @@
10
10
  --text-light: #6c757d;
11
11
  --border-color: #dee2e6;
12
12
  --danger-color: #da1e28;
13
- --danger-bg: #fff1f1;
13
+ --urgent-row-bg: #f5a4a4;
14
14
  --success-color: #198038;
15
15
  --info-color: #0f62fe;
16
16
  --warning-color: #f1c21b;
17
- --selected-bg: #e6f7f5;
17
+ --selected-bg: #d0e7ff;
18
18
  }
19
19
 
20
20
  .bodyWrapper {
@@ -29,7 +29,7 @@
29
29
  background: var(--card-bg);
30
30
  border-radius: 8px;
31
31
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
32
- padding: 1.75rem;
32
+ padding: 1.5rem;
33
33
  margin-bottom: 2rem;
34
34
  border: 1px solid var(--border-color);
35
35
  }
@@ -38,30 +38,24 @@
38
38
  display: flex;
39
39
  justify-content: space-between;
40
40
  align-items: center;
41
- margin-bottom: 1.5rem;
42
- padding-bottom: 0.85rem;
41
+ margin-bottom: 1.25rem;
42
+ padding-bottom: 0.75rem;
43
43
  border-bottom: 2px solid var(--primary-color);
44
44
  }
45
45
 
46
46
  .formHeading {
47
- font-size: 1.5rem;
47
+ font-size: 1.4rem;
48
48
  font-weight: 700;
49
49
  color: var(--primary-color);
50
50
  margin: 0;
51
51
  letter-spacing: -0.2px;
52
52
  }
53
53
 
54
- .sectionHeader {
55
- font-size: 1.15rem;
56
- font-weight: 600;
57
- color: var(--primary-color);
58
- margin-top: 0;
59
- margin-bottom: 1rem;
60
- padding-bottom: 0.4rem;
61
- border-bottom: 1px solid var(--border-color);
62
- display: flex;
63
- align-items: center;
64
- gap: 0.5rem;
54
+ .serviceBox {
55
+ background: #f5f6f9;
56
+ border: 1px solid var(--border-color);
57
+ border-radius: 6px;
58
+ padding: 1.25rem;
65
59
  }
66
60
 
67
61
  .row {
@@ -71,32 +65,31 @@
71
65
  margin-left: -0.75rem;
72
66
  }
73
67
 
74
- .col12 { flex: 0 0 100%; max-width: 100%; padding: 0 0.75rem; }
75
- .col8 { flex: 0 0 66.666667%; max-width: 66.666667%; padding: 0 0.75rem; }
76
- .col6 { flex: 0 0 50%; max-width: 50%; padding: 0 0.75rem; }
77
- .col4 { flex: 0 0 33.333333%; max-width: 33.333333%; padding: 0 0.75rem; }
78
- .col3 { flex: 0 0 25%; max-width: 25%; padding: 0 0.75rem; }
79
- .col2 { flex: 0 0 16.666667%; max-width: 16.666667%; padding: 0 0.75rem; }
68
+ .col12 { flex: 0 0 100%; max-width: 100%; padding: 0 0.75rem; box-sizing: border-box; }
69
+ .col10 { flex: 0 0 83.333333%; max-width: 83.333333%; padding: 0 0.75rem; box-sizing: border-box; }
70
+ .col8 { flex: 0 0 66.666667%; max-width: 66.666667%; padding: 0 0.75rem; box-sizing: border-box; }
71
+ .col6 { flex: 0 0 50%; max-width: 50%; padding: 0 0.75rem; box-sizing: border-box; }
72
+ .col4 { flex: 0 0 33.333333%; max-width: 33.333333%; padding: 0 0.75rem; box-sizing: border-box; }
73
+ .col3 { flex: 0 0 25%; max-width: 25%; padding: 0 0.75rem; box-sizing: border-box; }
74
+ .col2 { flex: 0 0 16.666667%; max-width: 16.666667%; padding: 0 0.75rem; box-sizing: border-box; }
80
75
 
81
76
  .formGroup {
82
- margin-bottom: 1.1rem;
77
+ margin-bottom: 1rem;
83
78
  }
84
79
 
85
80
  .label {
86
- display: flex;
87
- justify-content: space-between;
88
- align-items: center;
81
+ display: block;
89
82
  margin-bottom: 0.4rem;
90
83
  font-weight: 600;
91
- font-size: 0.875rem;
92
- color: var(--text-dark);
84
+ font-size: 0.88rem;
85
+ color: #004080;
93
86
  }
94
87
 
95
88
  .formControl {
96
89
  display: block;
97
90
  width: 100%;
98
- height: 2.5rem;
99
- padding: 0.45rem 0.8rem;
91
+ height: 2.4rem;
92
+ padding: 0.4rem 0.75rem;
100
93
  font-size: 0.9rem;
101
94
  font-weight: 400;
102
95
  line-height: 1.5;
@@ -118,26 +111,45 @@
118
111
 
119
112
  textarea.formControl {
120
113
  height: auto;
121
- min-height: 80px;
114
+ min-height: 70px;
122
115
  }
123
116
 
124
- /* Service filter box */
125
- .filterBox {
126
- background: #f8f9fa;
127
- border: 1px solid var(--border-color);
128
- border-radius: 6px;
129
- padding: 1.25rem;
130
- margin-bottom: 1.5rem;
117
+ /* Filter controls row */
118
+ .filterRow {
119
+ display: flex;
120
+ align-items: center;
121
+ flex-wrap: wrap;
122
+ gap: 1rem;
123
+ margin-bottom: 1.25rem;
124
+ }
125
+
126
+ .instituteSelectGroup {
127
+ display: flex;
128
+ align-items: center;
129
+ gap: 0.75rem;
130
+ }
131
+
132
+ .instituteLabel {
133
+ font-size: 1.1rem;
134
+ font-weight: 700;
135
+ color: #333;
136
+ margin: 0;
137
+ white-space: nowrap;
131
138
  }
132
139
 
133
- /* Table styling */
140
+ .searchGroup {
141
+ flex: 1;
142
+ min-width: 260px;
143
+ }
144
+
145
+ /* Table styling matching team's DataTable style */
134
146
  .tableContainer {
135
147
  width: 100%;
136
148
  overflow-x: auto;
137
- margin-top: 1rem;
138
- margin-bottom: 1.5rem;
149
+ margin-top: 0.5rem;
150
+ margin-bottom: 1.75rem;
139
151
  border: 1px solid var(--border-color);
140
- border-radius: 6px;
152
+ border-radius: 4px;
141
153
  background: #fff;
142
154
  }
143
155
 
@@ -145,16 +157,16 @@ textarea.formControl {
145
157
  width: 100%;
146
158
  border-collapse: collapse;
147
159
  white-space: nowrap;
148
- font-size: 0.875rem;
160
+ font-size: 0.85rem;
149
161
 
150
162
  thead th {
151
163
  background-color: #f1f3f5;
152
- color: #343a40;
164
+ color: #212529;
153
165
  font-weight: 600;
154
- padding: 0.65rem 0.85rem;
166
+ padding: 0.65rem 0.75rem;
155
167
  text-align: left;
156
168
  border-bottom: 2px solid var(--border-color);
157
- border-right: 1px solid var(--border-color);
169
+ border-right: 1px solid #dee2e6;
158
170
 
159
171
  &:last-child {
160
172
  border-right: none;
@@ -162,10 +174,10 @@ textarea.formControl {
162
174
  }
163
175
 
164
176
  tbody td {
165
- padding: 0.6rem 0.85rem;
166
- border-bottom: 1px solid var(--border-color);
177
+ padding: 0.55rem 0.75rem;
178
+ border-bottom: 1px solid #e9ecef;
167
179
  border-right: 1px solid #f1f3f5;
168
- color: #495057;
180
+ color: #333;
169
181
  vertical-align: middle;
170
182
 
171
183
  &:last-child {
@@ -182,119 +194,162 @@ textarea.formControl {
182
194
  }
183
195
  }
184
196
 
197
+ /* Urgent rows are highlighted in #f5a4a4 just like team's code */
185
198
  .urgentRow {
186
- background-color: #ffebee !important;
187
- color: #c62828 !important;
199
+ background-color: #f5a4a4 !important;
188
200
 
189
201
  &:hover {
190
- background-color: #ffcdd2 !important;
202
+ background-color: #f28b8b !important;
191
203
  }
192
204
  }
193
205
 
194
206
  .selectedRow {
195
207
  background-color: var(--selected-bg) !important;
196
- border-left: 4px solid var(--primary-color) !important;
197
- font-weight: 500;
208
+ font-weight: 600;
209
+ outline: 2px solid #0f62fe;
198
210
  }
199
211
 
200
212
  .badge {
201
213
  display: inline-block;
202
- padding: 0.25rem 0.5rem;
203
- font-size: 0.75rem;
204
- font-weight: 600;
214
+ padding: 0.2rem 0.5rem;
215
+ font-size: 0.72rem;
216
+ font-weight: 700;
205
217
  line-height: 1;
206
218
  text-align: center;
207
219
  white-space: nowrap;
208
220
  vertical-align: baseline;
209
- border-radius: 10rem;
221
+ border-radius: 4px;
210
222
  }
211
223
 
212
224
  .badgeUrgent {
213
225
  color: #fff;
214
- background-color: #da1e28;
226
+ background-color: #c62828;
215
227
  }
216
228
 
217
229
  .badgeRoutine {
218
230
  color: #212529;
219
- background-color: #e0e0e0;
231
+ background-color: #e9ecef;
220
232
  }
221
233
 
222
234
  .badgeType {
223
235
  color: #fff;
224
- background-color: #009384;
236
+ background-color: var(--primary-color);
225
237
  }
226
238
 
227
- /* Patient & Investigation details panel */
228
- .detailsPanel {
229
- background: #fafbfc;
230
- border: 1px solid var(--border-color);
231
- border-radius: 8px;
232
- padding: 1.25rem;
233
- margin-bottom: 1.5rem;
239
+ .btnSelect {
240
+ background-color: #ffc107;
241
+ color: #212529;
242
+ border: 1px solid #d39e00;
243
+ border-radius: 4px;
244
+ padding: 0.2rem 0.65rem;
245
+ font-size: 0.8rem;
246
+ font-weight: 600;
247
+ cursor: pointer;
248
+ transition: background-color 0.15s;
249
+
250
+ &:hover {
251
+ background-color: #e0a800;
252
+ }
253
+ }
254
+
255
+ .btnSelected {
256
+ background-color: #0f62fe !important;
257
+ color: #ffffff !important;
258
+ border-color: #0353e9 !important;
234
259
  }
235
260
 
236
- .infoField {
237
- margin-bottom: 0.6rem;
261
+ /* Demographics Panel */
262
+ .demographicsSection {
263
+ margin-top: 1rem;
264
+ }
265
+
266
+ .fieldItem {
267
+ margin-bottom: 0.55rem;
238
268
  font-size: 0.88rem;
239
269
  display: flex;
240
270
  align-items: baseline;
241
- gap: 0.4rem;
271
+ color: #333;
272
+ }
273
+
274
+ .fieldIcon {
275
+ color: #009384;
276
+ margin-right: 0.4rem;
277
+ font-weight: bold;
242
278
  }
243
279
 
244
- .infoLabel {
280
+ .fieldTitle {
245
281
  font-weight: 600;
246
- color: #495057;
247
- min-width: 130px;
282
+ color: #004080;
283
+ margin-right: 0.35rem;
284
+ white-space: nowrap;
248
285
  }
249
286
 
250
- .infoValue {
251
- color: #212529;
287
+ .fieldVal {
288
+ color: #111;
252
289
  font-weight: 500;
253
290
  word-break: break-word;
254
291
  }
255
292
 
256
- .avatarContainer {
293
+ /* Patient Image (upload-img matching team's style) */
294
+ .uploadImgWrapper {
257
295
  display: flex;
258
- flex-direction: column;
259
- align-items: center;
260
296
  justify-content: center;
297
+ align-items: center;
261
298
  padding: 0.5rem;
262
299
  }
263
300
 
264
- .avatarImg {
265
- width: 110px;
266
- height: 110px;
267
- border-radius: 8px;
301
+ .uploadImg {
302
+ border-radius: 16px;
303
+ display: inline-block;
304
+ height: 100px;
305
+ width: 100px;
268
306
  object-fit: cover;
269
- border: 2px solid var(--border-color);
270
- background-color: #eaeff2;
307
+ object-position: 50% 50%;
308
+ border: 3px solid #20e5bf;
309
+ margin-right: 15px;
310
+ vertical-align: middle;
311
+ background-color: #e9ecef;
312
+ }
313
+
314
+ /* Investigation Details Row */
315
+ .investigationRow {
316
+ border-bottom: 2px solid #808080;
317
+ padding-bottom: 0.75rem;
318
+ margin-top: 0.5rem;
319
+ margin-bottom: 1.25rem;
320
+ }
321
+
322
+ /* Scheduling Inputs & Action Buttons */
323
+ .schedulingFormArea {
324
+ margin-top: 0.5rem;
271
325
  }
272
326
 
273
- /* Action buttons */
274
327
  .buttonGroup {
275
328
  display: flex;
276
329
  gap: 1rem;
277
- margin-top: 1.5rem;
330
+ margin-top: 1rem;
278
331
  }
279
332
 
333
+ /* Big blue button as explicitly requested by user */
280
334
  .btnPrimary {
281
335
  background-color: #0f62fe;
282
336
  color: #ffffff;
283
337
  border: none;
284
338
  border-radius: 4px;
285
- padding: 0.7rem 1.6rem;
339
+ padding: 0.7rem 1.8rem;
286
340
  font-size: 1rem;
287
- font-weight: 600;
341
+ font-weight: 700;
288
342
  cursor: pointer;
289
343
  display: inline-flex;
290
344
  align-items: center;
291
345
  justify-content: center;
292
346
  gap: 0.5rem;
347
+ box-shadow: 0 2px 6px rgba(15, 98, 254, 0.25);
293
348
  transition: background-color 0.2s, box-shadow 0.2s;
294
349
 
295
350
  &:hover:not(:disabled) {
296
351
  background-color: #0353e9;
297
- box-shadow: 0 4px 8px rgba(15, 98, 254, 0.3);
352
+ box-shadow: 0 4px 10px rgba(15, 98, 254, 0.4);
298
353
  }
299
354
 
300
355
  &:disabled {
@@ -305,159 +360,21 @@ textarea.formControl {
305
360
  }
306
361
 
307
362
  .btnCancel {
308
- background-color: #e0e0e0;
309
- color: #393939;
363
+ background-color: #dc3545;
364
+ color: #ffffff;
310
365
  border: none;
311
366
  border-radius: 4px;
312
- padding: 0.7rem 1.4rem;
367
+ padding: 0.7rem 1.5rem;
313
368
  font-size: 0.95rem;
314
369
  font-weight: 600;
315
370
  cursor: pointer;
316
371
  transition: background-color 0.2s;
317
372
 
318
373
  &:hover {
319
- background-color: #d1d1d1;
374
+ background-color: #c82333;
320
375
  }
321
376
  }
322
377
 
323
- .btnSelect {
324
- background-color: #ffb800;
325
- color: #212529;
326
- border: none;
327
- border-radius: 4px;
328
- padding: 0.3rem 0.8rem;
329
- font-size: 0.8rem;
330
- font-weight: 600;
331
- cursor: pointer;
332
- transition: background-color 0.15s;
333
-
334
- &:hover {
335
- background-color: #e6a600;
336
- }
337
- }
338
-
339
- /* Calendar styling */
340
- .calendarWrapper {
341
- background: #ffffff;
342
- border: 1px solid var(--border-color);
343
- border-radius: 8px;
344
- padding: 1.25rem;
345
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
346
- }
347
-
348
- .calendarNav {
349
- display: flex;
350
- justify-content: space-between;
351
- align-items: center;
352
- margin-bottom: 1rem;
353
- }
354
-
355
- .calendarMonthTitle {
356
- font-size: 1.15rem;
357
- font-weight: 700;
358
- color: #343a40;
359
- }
360
-
361
- .calendarNavBtn {
362
- background: #f1f3f5;
363
- border: 1px solid #ced4da;
364
- border-radius: 4px;
365
- padding: 0.35rem 0.8rem;
366
- font-size: 0.85rem;
367
- font-weight: 600;
368
- cursor: pointer;
369
- transition: background 0.15s;
370
-
371
- &:hover {
372
- background: #e9ecef;
373
- }
374
- }
375
-
376
- .calendarGrid {
377
- display: grid;
378
- grid-template-columns: repeat(7, 1fr);
379
- gap: 2px;
380
- background-color: #dee2e6;
381
- border: 1px solid #dee2e6;
382
- border-radius: 4px;
383
- overflow: hidden;
384
- }
385
-
386
- .calendarDayHeader {
387
- background-color: #f8f9fa;
388
- font-weight: 600;
389
- font-size: 0.8rem;
390
- color: #495057;
391
- text-align: center;
392
- padding: 0.5rem 0.25rem;
393
- }
394
-
395
- .calendarCell {
396
- background-color: #fff;
397
- min-height: 80px;
398
- padding: 0.35rem;
399
- display: flex;
400
- flex-direction: column;
401
- transition: background-color 0.15s;
402
-
403
- &:hover {
404
- background-color: #fbfbfb;
405
- }
406
- }
407
-
408
- .calendarCellInactive {
409
- background-color: #f8f9fa;
410
- opacity: 0.5;
411
- }
412
-
413
- .calendarDayNumber {
414
- font-size: 0.78rem;
415
- font-weight: 600;
416
- color: #6c757d;
417
- margin-bottom: 0.25rem;
418
- }
419
-
420
- .calendarDayToday {
421
- color: #0f62fe;
422
- font-weight: 800;
423
- }
424
-
425
- .calendarBadge {
426
- margin-top: 2px;
427
- padding: 2px 5px;
428
- font-size: 0.72rem;
429
- border-radius: 3px;
430
- color: #fff;
431
- cursor: pointer;
432
- display: block;
433
- white-space: nowrap;
434
- overflow: hidden;
435
- text-overflow: ellipsis;
436
- font-weight: 600;
437
- transition: transform 0.1s;
438
-
439
- &:hover {
440
- transform: scale(1.02);
441
- }
442
- }
443
-
444
- .badgeXray {
445
- background-color: #da1e28;
446
- }
447
-
448
- .badgeCT {
449
- background-color: #0f62fe;
450
- }
451
-
452
- .badgeMRI {
453
- background-color: #198038;
454
- }
455
-
456
- .badgeOther {
457
- background-color: #6f42c1;
458
- }
459
-
460
- /* Empty state */
461
378
  .emptyState {
462
379
  text-align: center;
463
380
  padding: 2.5rem 1rem;
@@ -467,12 +384,13 @@ textarea.formControl {
467
384
  }
468
385
 
469
386
  @media (max-width: 992px) {
470
- .col6, .col8, .col4, .col3 {
387
+ .col10, .col8, .col6, .col4, .col3, .col2 {
471
388
  flex: 0 0 100%;
472
389
  max-width: 100%;
473
390
  }
474
391
 
475
- .calendarCell {
476
- min-height: 60px;
392
+ .uploadImgWrapper {
393
+ justify-content: flex-start;
394
+ margin-bottom: 1rem;
477
395
  }
478
396
  }