@sasindi/radiologytestscheduling 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +875 -0
  66. package/src/radiology-test-scheduling.resource.ts +321 -0
  67. package/src/radiology-test-scheduling.scss +478 -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,478 @@
1
+ /* Radiology Test Scheduling Unified Design System - OpenMRS 3 */
2
+
3
+ :root {
4
+ --primary-color: #009384;
5
+ --primary-hover: #007a6d;
6
+ --secondary-color: #00bfa5;
7
+ --bg-color: #f4f7f6;
8
+ --card-bg: #ffffff;
9
+ --text-dark: #212529;
10
+ --text-light: #6c757d;
11
+ --border-color: #dee2e6;
12
+ --danger-color: #da1e28;
13
+ --danger-bg: #fff1f1;
14
+ --success-color: #198038;
15
+ --info-color: #0f62fe;
16
+ --warning-color: #f1c21b;
17
+ --selected-bg: #e6f7f5;
18
+ }
19
+
20
+ .bodyWrapper {
21
+ background-color: var(--bg-color);
22
+ padding: 1.5rem;
23
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
24
+ color: var(--text-dark);
25
+ min-height: 100vh;
26
+ }
27
+
28
+ .card {
29
+ background: var(--card-bg);
30
+ border-radius: 8px;
31
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
32
+ padding: 1.75rem;
33
+ margin-bottom: 2rem;
34
+ border: 1px solid var(--border-color);
35
+ }
36
+
37
+ .cardHeader {
38
+ display: flex;
39
+ justify-content: space-between;
40
+ align-items: center;
41
+ margin-bottom: 1.5rem;
42
+ padding-bottom: 0.85rem;
43
+ border-bottom: 2px solid var(--primary-color);
44
+ }
45
+
46
+ .formHeading {
47
+ font-size: 1.5rem;
48
+ font-weight: 700;
49
+ color: var(--primary-color);
50
+ margin: 0;
51
+ letter-spacing: -0.2px;
52
+ }
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;
65
+ }
66
+
67
+ .row {
68
+ display: flex;
69
+ flex-wrap: wrap;
70
+ margin-right: -0.75rem;
71
+ margin-left: -0.75rem;
72
+ }
73
+
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; }
80
+
81
+ .formGroup {
82
+ margin-bottom: 1.1rem;
83
+ }
84
+
85
+ .label {
86
+ display: flex;
87
+ justify-content: space-between;
88
+ align-items: center;
89
+ margin-bottom: 0.4rem;
90
+ font-weight: 600;
91
+ font-size: 0.875rem;
92
+ color: var(--text-dark);
93
+ }
94
+
95
+ .formControl {
96
+ display: block;
97
+ width: 100%;
98
+ height: 2.5rem;
99
+ padding: 0.45rem 0.8rem;
100
+ font-size: 0.9rem;
101
+ font-weight: 400;
102
+ line-height: 1.5;
103
+ color: #495057;
104
+ background-color: #fff;
105
+ border: 1px solid #ced4da;
106
+ border-radius: 4px;
107
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
108
+ box-sizing: border-box;
109
+
110
+ &:focus {
111
+ color: #495057;
112
+ background-color: #fff;
113
+ border-color: var(--primary-color);
114
+ outline: 0;
115
+ box-shadow: 0 0 0 0.2rem rgba(0, 147, 132, 0.2);
116
+ }
117
+ }
118
+
119
+ textarea.formControl {
120
+ height: auto;
121
+ min-height: 80px;
122
+ }
123
+
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;
131
+ }
132
+
133
+ /* Table styling */
134
+ .tableContainer {
135
+ width: 100%;
136
+ overflow-x: auto;
137
+ margin-top: 1rem;
138
+ margin-bottom: 1.5rem;
139
+ border: 1px solid var(--border-color);
140
+ border-radius: 6px;
141
+ background: #fff;
142
+ }
143
+
144
+ .table {
145
+ width: 100%;
146
+ border-collapse: collapse;
147
+ white-space: nowrap;
148
+ font-size: 0.875rem;
149
+
150
+ thead th {
151
+ background-color: #f1f3f5;
152
+ color: #343a40;
153
+ font-weight: 600;
154
+ padding: 0.65rem 0.85rem;
155
+ text-align: left;
156
+ border-bottom: 2px solid var(--border-color);
157
+ border-right: 1px solid var(--border-color);
158
+
159
+ &:last-child {
160
+ border-right: none;
161
+ }
162
+ }
163
+
164
+ tbody td {
165
+ padding: 0.6rem 0.85rem;
166
+ border-bottom: 1px solid var(--border-color);
167
+ border-right: 1px solid #f1f3f5;
168
+ color: #495057;
169
+ vertical-align: middle;
170
+
171
+ &:last-child {
172
+ border-right: none;
173
+ }
174
+ }
175
+
176
+ tbody tr {
177
+ transition: background-color 0.15s ease;
178
+
179
+ &:hover {
180
+ background-color: #f8f9fa;
181
+ }
182
+ }
183
+ }
184
+
185
+ .urgentRow {
186
+ background-color: #ffebee !important;
187
+ color: #c62828 !important;
188
+
189
+ &:hover {
190
+ background-color: #ffcdd2 !important;
191
+ }
192
+ }
193
+
194
+ .selectedRow {
195
+ background-color: var(--selected-bg) !important;
196
+ border-left: 4px solid var(--primary-color) !important;
197
+ font-weight: 500;
198
+ }
199
+
200
+ .badge {
201
+ display: inline-block;
202
+ padding: 0.25rem 0.5rem;
203
+ font-size: 0.75rem;
204
+ font-weight: 600;
205
+ line-height: 1;
206
+ text-align: center;
207
+ white-space: nowrap;
208
+ vertical-align: baseline;
209
+ border-radius: 10rem;
210
+ }
211
+
212
+ .badgeUrgent {
213
+ color: #fff;
214
+ background-color: #da1e28;
215
+ }
216
+
217
+ .badgeRoutine {
218
+ color: #212529;
219
+ background-color: #e0e0e0;
220
+ }
221
+
222
+ .badgeType {
223
+ color: #fff;
224
+ background-color: #009384;
225
+ }
226
+
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;
234
+ }
235
+
236
+ .infoField {
237
+ margin-bottom: 0.6rem;
238
+ font-size: 0.88rem;
239
+ display: flex;
240
+ align-items: baseline;
241
+ gap: 0.4rem;
242
+ }
243
+
244
+ .infoLabel {
245
+ font-weight: 600;
246
+ color: #495057;
247
+ min-width: 130px;
248
+ }
249
+
250
+ .infoValue {
251
+ color: #212529;
252
+ font-weight: 500;
253
+ word-break: break-word;
254
+ }
255
+
256
+ .avatarContainer {
257
+ display: flex;
258
+ flex-direction: column;
259
+ align-items: center;
260
+ justify-content: center;
261
+ padding: 0.5rem;
262
+ }
263
+
264
+ .avatarImg {
265
+ width: 110px;
266
+ height: 110px;
267
+ border-radius: 8px;
268
+ object-fit: cover;
269
+ border: 2px solid var(--border-color);
270
+ background-color: #eaeff2;
271
+ }
272
+
273
+ /* Action buttons */
274
+ .buttonGroup {
275
+ display: flex;
276
+ gap: 1rem;
277
+ margin-top: 1.5rem;
278
+ }
279
+
280
+ .btnPrimary {
281
+ background-color: #0f62fe;
282
+ color: #ffffff;
283
+ border: none;
284
+ border-radius: 4px;
285
+ padding: 0.7rem 1.6rem;
286
+ font-size: 1rem;
287
+ font-weight: 600;
288
+ cursor: pointer;
289
+ display: inline-flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ gap: 0.5rem;
293
+ transition: background-color 0.2s, box-shadow 0.2s;
294
+
295
+ &:hover:not(:disabled) {
296
+ background-color: #0353e9;
297
+ box-shadow: 0 4px 8px rgba(15, 98, 254, 0.3);
298
+ }
299
+
300
+ &:disabled {
301
+ background-color: #8d8d8d;
302
+ cursor: not-allowed;
303
+ opacity: 0.7;
304
+ }
305
+ }
306
+
307
+ .btnCancel {
308
+ background-color: #e0e0e0;
309
+ color: #393939;
310
+ border: none;
311
+ border-radius: 4px;
312
+ padding: 0.7rem 1.4rem;
313
+ font-size: 0.95rem;
314
+ font-weight: 600;
315
+ cursor: pointer;
316
+ transition: background-color 0.2s;
317
+
318
+ &:hover {
319
+ background-color: #d1d1d1;
320
+ }
321
+ }
322
+
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
+ .emptyState {
462
+ text-align: center;
463
+ padding: 2.5rem 1rem;
464
+ color: var(--text-light);
465
+ font-style: italic;
466
+ font-size: 0.95rem;
467
+ }
468
+
469
+ @media (max-width: 992px) {
470
+ .col6, .col8, .col4, .col3 {
471
+ flex: 0 0 100%;
472
+ max-width: 100%;
473
+ }
474
+
475
+ .calendarCell {
476
+ min-height: 60px;
477
+ }
478
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { render, screen } from '@testing-library/react';
4
+ import Root from './root.component';
5
+
6
+ describe('Root', () => {
7
+ it('mounts the route shell without crashing', () => {
8
+ // No route matches the default jsdom URL, so the shell mounts but renders
9
+ // no route content; this asserts the wiring (BrowserRouter + Routes) works.
10
+ expect(() => render(<Root />)).not.toThrow();
11
+ });
12
+ });
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { BrowserRouter, Routes, Route } from 'react-router-dom';
3
+ import RadiologyTestScheduling from './radiology-test-scheduling.component';
4
+
5
+ const Root: React.FC = () => (
6
+ <BrowserRouter basename={window.getOpenmrsSpaBase()}>
7
+ <Routes>
8
+ <Route path="radiologytestscheduling" element={<RadiologyTestScheduling />} />
9
+ </Routes>
10
+ </BrowserRouter>
11
+ );
12
+
13
+ export default Root;
package/src/root.scss ADDED
@@ -0,0 +1,5 @@
1
+ @use '@carbon/layout';
2
+
3
+ .container {
4
+ padding: layout.$spacing-05;
5
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json.openmrs.org/routes.schema.json",
3
+ "pages": [
4
+ {
5
+ "route": "radiologytestscheduling",
6
+ "component": "root",
7
+ "online": true
8
+ }
9
+ ],
10
+ "extensions": [
11
+ ]
12
+ }
package/src/types.ts ADDED
@@ -0,0 +1,100 @@
1
+ export interface RadiologyOrder {
2
+ id: number;
3
+ patientId?: string;
4
+ patientPhn: string;
5
+ patientSystem?: string;
6
+ encounter?: string;
7
+ location: string;
8
+ section?: string;
9
+ schedulingreq?: string;
10
+ approvalreq?: string;
11
+ identifier?: string;
12
+ basedOn?: string;
13
+ replaces?: string;
14
+ requisition?: string;
15
+ status?: string;
16
+ intent?: string;
17
+ priority?: string;
18
+ doNotPerform?: string;
19
+ categoryCode?: string;
20
+ categoryDisplay?: string; // 'Xray' | 'CT' | 'MRI' | etc.
21
+ codeCode?: string;
22
+ codeDisplay?: string; // Test Name
23
+ quantity?: string;
24
+ quantityRatio?: string;
25
+ quantityRange?: string;
26
+ focusCode?: string;
27
+ focusDisplay?: string;
28
+ occurrenceDateTime?: string;
29
+ occurrencePeriod?: string;
30
+ occurrenceTiming?: string;
31
+ asNeededBoolean?: string;
32
+ authoredOn?: string;
33
+ requesterReference?: string;
34
+ requesterDisplay?: string;
35
+ performerTypeCode?: string;
36
+ performerTypeDisplay?: string;
37
+ performerReference?: string;
38
+ performerDisplay?: string;
39
+ reason?: string;
40
+ supportingInfo?: string; // Exam Room
41
+ specimen?: string; // Localization
42
+ bodySiteCode?: string;
43
+ bodySiteDisplay?: string; // Position
44
+ bodySiteText?: string; // Region
45
+ bodyStructureId?: string;
46
+ bodyStructureIdentifier?: string;
47
+ bodyStructureCode?: string;
48
+ bodyStructureDisplay?: string;
49
+ bodyStructureText?: string;
50
+ bodyStructureDescription?: string;
51
+ note?: string; // Comment
52
+ scheduledDateTime?: string;
53
+ remarks?: string;
54
+ noty?: string;
55
+ cby?: string;
56
+ cdate?: string;
57
+ }
58
+
59
+ export interface SchedulingOrderDTO {
60
+ radiology: RadiologyOrder;
61
+ type: 'OPD' | 'Clinic' | 'Ward';
62
+ patientName?: string;
63
+ patientGender?: string;
64
+ patientAge?: string | number;
65
+ locationName?: string;
66
+ orderedByName?: string;
67
+ }
68
+
69
+ export interface PatientDemographics {
70
+ fullName: string;
71
+ gender: string;
72
+ nicNo: string;
73
+ guardianInfo: string;
74
+ address: string;
75
+ years: string | number;
76
+ months: string | number;
77
+ days: string | number;
78
+ dateOfBirth: string;
79
+ profileImage: string;
80
+ telephoneHome: string;
81
+ telephoneMobile: string;
82
+ }
83
+
84
+ export interface ExamRoom {
85
+ id?: number | string;
86
+ name: string;
87
+ }
88
+
89
+ export interface InstituteOption {
90
+ uuid: string;
91
+ name: string;
92
+ }
93
+
94
+ export interface CalendarEvent {
95
+ title: string;
96
+ date: string; // YYYY-MM-DD
97
+ category: string;
98
+ count: number;
99
+ breakdown: Record<string, number>;
100
+ }