@kenyaemr/esm-ward-app 8.5.4-pre.1 → 8.5.4-pre.10

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 (233) hide show
  1. package/README.md +31 -0
  2. package/package.json +1 -1
  3. package/src/config-schema.ts +21 -1
  4. package/src/in-patient/labourCareGuide/alert-rules.ts +97 -4
  5. package/src/in-patient/labourCareGuide/components/lcg-correction-audit-banner.component.tsx +42 -0
  6. package/src/in-patient/labourCareGuide/constants/admission-form.constants.ts +22 -0
  7. package/src/in-patient/labourCareGuide/context/lcg-section-state.context.tsx +252 -0
  8. package/src/in-patient/labourCareGuide/forms/admission-details-form.component.tsx +28 -14
  9. package/src/in-patient/labourCareGuide/forms/baby-form.component.tsx +55 -179
  10. package/src/in-patient/labourCareGuide/forms/labour-care-guide-forms.component.tsx +69 -31
  11. package/src/in-patient/labourCareGuide/forms/labour-progress-form.component.tsx +163 -216
  12. package/src/in-patient/labourCareGuide/forms/lcg-session-correction-wizard.component.tsx +214 -0
  13. package/src/in-patient/labourCareGuide/forms/medication-form.component.tsx +598 -220
  14. package/src/in-patient/labourCareGuide/forms/shared-decision-assessment-field.component.tsx +109 -0
  15. package/src/in-patient/labourCareGuide/forms/shared-decision-making-form.component.tsx +186 -187
  16. package/src/in-patient/labourCareGuide/forms/supportive-care-form.component.tsx +33 -90
  17. package/src/in-patient/labourCareGuide/forms/time-form.component.tsx +12 -4
  18. package/src/in-patient/labourCareGuide/forms/time-picker-dropdown.component.tsx +168 -53
  19. package/src/in-patient/labourCareGuide/forms/woman-form.component.tsx +51 -79
  20. package/src/in-patient/labourCareGuide/global-cds-alerts/global-cds-alert-context.tsx +2 -2
  21. package/src/in-patient/labourCareGuide/graphs/labour-progress.component.tsx +188 -86
  22. package/src/in-patient/labourCareGuide/graphs/medication.component.tsx +15 -23
  23. package/src/in-patient/labourCareGuide/graphs/shared-decision-making.component.tsx +157 -44
  24. package/src/in-patient/labourCareGuide/graphs/supportive-care.component.tsx +4 -1
  25. package/src/in-patient/labourCareGuide/graphs/woman.component.tsx +3 -39
  26. package/src/in-patient/labourCareGuide/hooks/use-lcg-data.ts +473 -229
  27. package/src/in-patient/labourCareGuide/hooks/use-lcg-debounced-value.ts +17 -0
  28. package/src/in-patient/labourCareGuide/hooks/use-lcg-form-clock.ts +36 -0
  29. package/src/in-patient/labourCareGuide/hooks/use-lcg-section-autosave.ts +33 -0
  30. package/src/in-patient/labourCareGuide/labour-care-guide.component.tsx +311 -53
  31. package/src/in-patient/labourCareGuide/labour-care-guide.scss +47 -0
  32. package/src/in-patient/labourCareGuide/labour-care-guide.utils.ts +146 -7
  33. package/src/in-patient/labourCareGuide/lcg-alert-lifecycle.ts +161 -0
  34. package/src/in-patient/labourCareGuide/partography-data-form.scss +119 -3
  35. package/src/in-patient/labourCareGuide/partography.resource.ts +15 -27
  36. package/src/in-patient/labourCareGuide/resources/admission-details.resource.ts +26 -25
  37. package/src/in-patient/labourCareGuide/resources/drug-stock.resource.ts +98 -0
  38. package/src/in-patient/labourCareGuide/resources/labour-progress.resource.ts +34 -2
  39. package/src/in-patient/labourCareGuide/resources/lcg-session-correction.resource.ts +1027 -0
  40. package/src/in-patient/labourCareGuide/resources/medication.resource.ts +241 -37
  41. package/src/in-patient/labourCareGuide/resources/shared-decision-making.resource.ts +37 -3
  42. package/src/in-patient/labourCareGuide/resources/supportive-care.resource.ts +53 -5
  43. package/src/in-patient/labourCareGuide/storage/lcg-local-storage.ts +374 -0
  44. package/src/in-patient/labourCareGuide/time-slot-utils.ts +180 -26
  45. package/src/in-patient/labourCareGuide/types/concept-uuids.constants.ts +21 -0
  46. package/src/in-patient/labourCareGuide/types/index.ts +31 -7
  47. package/src/index.ts +17 -0
  48. package/src/routes.json +20 -0
  49. package/src/ward-patients/ward-patients-slots.ts +21 -0
  50. package/src/ward-patients/ward-patients-table.test.tsx +58 -0
  51. package/src/ward-patients/ward-patients-table.tsx +25 -8
  52. package/translations/en.json +56 -15
  53. package/translations/fr.json +57 -15
  54. package/.turbo/turbo-build.log +0 -8
  55. package/dist/1084.js +0 -1
  56. package/dist/1084.js.map +0 -1
  57. package/dist/1189.js +0 -1
  58. package/dist/1189.js.map +0 -1
  59. package/dist/12.js +0 -17
  60. package/dist/12.js.map +0 -1
  61. package/dist/126.js +0 -1
  62. package/dist/1308.js +0 -1
  63. package/dist/1308.js.map +0 -1
  64. package/dist/1374.js +0 -1
  65. package/dist/1374.js.map +0 -1
  66. package/dist/1387.js +0 -1
  67. package/dist/1387.js.map +0 -1
  68. package/dist/15.js +0 -1
  69. package/dist/1564.js +0 -1
  70. package/dist/1567.js +0 -1
  71. package/dist/1567.js.map +0 -1
  72. package/dist/1739.js +0 -1
  73. package/dist/1739.js.map +0 -1
  74. package/dist/1845.js +0 -1
  75. package/dist/1972.js +0 -1
  76. package/dist/1972.js.map +0 -1
  77. package/dist/2016.js +0 -1
  78. package/dist/2016.js.map +0 -1
  79. package/dist/2117.js +0 -1
  80. package/dist/2117.js.map +0 -1
  81. package/dist/215.js +0 -1
  82. package/dist/2178.js +0 -1
  83. package/dist/239.js +0 -1
  84. package/dist/239.js.map +0 -1
  85. package/dist/2486.js +0 -1
  86. package/dist/2486.js.map +0 -1
  87. package/dist/2566.js +0 -1
  88. package/dist/2710.js +0 -1
  89. package/dist/2710.js.map +0 -1
  90. package/dist/2759.js +0 -1
  91. package/dist/2823.js +0 -15
  92. package/dist/2823.js.map +0 -1
  93. package/dist/3089.js +0 -1
  94. package/dist/3089.js.map +0 -1
  95. package/dist/3230.js +0 -1
  96. package/dist/3261.js +0 -1
  97. package/dist/3261.js.map +0 -1
  98. package/dist/3321.js +0 -1
  99. package/dist/3321.js.map +0 -1
  100. package/dist/3350.js +0 -1
  101. package/dist/3399.js +0 -1
  102. package/dist/3399.js.map +0 -1
  103. package/dist/3441.js +0 -1
  104. package/dist/3547.js +0 -1
  105. package/dist/3547.js.map +0 -1
  106. package/dist/3548.js +0 -1
  107. package/dist/3548.js.map +0 -1
  108. package/dist/3565.js +0 -1
  109. package/dist/3571.js +0 -1
  110. package/dist/3571.js.map +0 -1
  111. package/dist/364.js +0 -1
  112. package/dist/364.js.map +0 -1
  113. package/dist/3746.js +0 -1
  114. package/dist/3925.js +0 -1
  115. package/dist/3946.js +0 -1
  116. package/dist/401.js +0 -1
  117. package/dist/401.js.map +0 -1
  118. package/dist/4032.js +0 -1
  119. package/dist/4032.js.map +0 -1
  120. package/dist/4206.js +0 -1
  121. package/dist/4206.js.map +0 -1
  122. package/dist/4480.js +0 -1
  123. package/dist/4480.js.map +0 -1
  124. package/dist/4635.js +0 -1
  125. package/dist/4635.js.map +0 -1
  126. package/dist/4684.js +0 -1
  127. package/dist/4684.js.map +0 -1
  128. package/dist/4735.js +0 -1
  129. package/dist/4735.js.map +0 -1
  130. package/dist/4744.js +0 -1
  131. package/dist/4744.js.map +0 -1
  132. package/dist/4894.js +0 -1
  133. package/dist/4970.js +0 -1
  134. package/dist/4970.js.map +0 -1
  135. package/dist/5069.js +0 -1
  136. package/dist/5069.js.map +0 -1
  137. package/dist/5090.js +0 -1
  138. package/dist/5090.js.map +0 -1
  139. package/dist/5130.js +0 -1
  140. package/dist/5187.js +0 -1
  141. package/dist/5411.js +0 -1
  142. package/dist/5411.js.map +0 -1
  143. package/dist/5441.js +0 -1
  144. package/dist/5441.js.map +0 -1
  145. package/dist/5491.js +0 -1
  146. package/dist/5491.js.map +0 -1
  147. package/dist/5496.js +0 -1
  148. package/dist/5496.js.map +0 -1
  149. package/dist/5595.js +0 -1
  150. package/dist/5659.js +0 -1
  151. package/dist/5659.js.map +0 -1
  152. package/dist/5706.js +0 -1
  153. package/dist/5706.js.map +0 -1
  154. package/dist/5961.js +0 -1
  155. package/dist/6061.js +0 -1
  156. package/dist/6061.js.map +0 -1
  157. package/dist/6133.js +0 -1
  158. package/dist/6180.js +0 -1
  159. package/dist/6180.js.map +0 -1
  160. package/dist/6296.js +0 -1
  161. package/dist/6296.js.map +0 -1
  162. package/dist/6322.js +0 -1
  163. package/dist/6322.js.map +0 -1
  164. package/dist/6381.js +0 -1
  165. package/dist/6381.js.map +0 -1
  166. package/dist/6455.js +0 -1
  167. package/dist/6455.js.map +0 -1
  168. package/dist/6456.js +0 -1
  169. package/dist/6466.js +0 -1
  170. package/dist/6613.js +0 -1
  171. package/dist/6783.js +0 -1
  172. package/dist/6925.js +0 -1
  173. package/dist/6925.js.map +0 -1
  174. package/dist/7091.js +0 -1
  175. package/dist/7091.js.map +0 -1
  176. package/dist/7263.js +0 -1
  177. package/dist/7263.js.map +0 -1
  178. package/dist/7348.js +0 -1
  179. package/dist/7431.js +0 -1
  180. package/dist/7431.js.map +0 -1
  181. package/dist/7543.js +0 -1
  182. package/dist/7607.js +0 -1
  183. package/dist/762.js +0 -1
  184. package/dist/762.js.map +0 -1
  185. package/dist/772.js +0 -1
  186. package/dist/775.js +0 -1
  187. package/dist/775.js.map +0 -1
  188. package/dist/8171.js +0 -1
  189. package/dist/8171.js.map +0 -1
  190. package/dist/8488.js +0 -1
  191. package/dist/8488.js.map +0 -1
  192. package/dist/8727.js +0 -1
  193. package/dist/8736.js +0 -1
  194. package/dist/8736.js.map +0 -1
  195. package/dist/8847.js +0 -1
  196. package/dist/9009.js +0 -1
  197. package/dist/9009.js.map +0 -1
  198. package/dist/9015.js +0 -1
  199. package/dist/906.js +0 -1
  200. package/dist/9065.js +0 -1
  201. package/dist/9182.js +0 -1
  202. package/dist/9314.js +0 -1
  203. package/dist/9314.js.map +0 -1
  204. package/dist/9339.js +0 -1
  205. package/dist/9361.js +0 -1
  206. package/dist/9361.js.map +0 -1
  207. package/dist/9453.js +0 -1
  208. package/dist/9465.js +0 -1
  209. package/dist/9465.js.map +0 -1
  210. package/dist/9535.js +0 -22
  211. package/dist/9535.js.map +0 -1
  212. package/dist/9610.js +0 -1
  213. package/dist/9610.js.map +0 -1
  214. package/dist/9668.js +0 -1
  215. package/dist/9668.js.map +0 -1
  216. package/dist/9727.js +0 -1
  217. package/dist/9727.js.map +0 -1
  218. package/dist/9734.js +0 -1
  219. package/dist/9734.js.map +0 -1
  220. package/dist/9833.js +0 -1
  221. package/dist/9833.js.map +0 -1
  222. package/dist/9848.js +0 -1
  223. package/dist/9848.js.map +0 -1
  224. package/dist/9876.js +0 -1
  225. package/dist/9876.js.map +0 -1
  226. package/dist/9920.js +0 -1
  227. package/dist/9938.js +0 -1
  228. package/dist/kenyaemr-esm-ward-app.js +0 -5
  229. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +0 -2723
  230. package/dist/kenyaemr-esm-ward-app.js.map +0 -1
  231. package/dist/main.js +0 -5
  232. package/dist/main.js.map +0 -1
  233. package/dist/routes.json +0 -1
package/README.md CHANGED
@@ -22,6 +22,37 @@ Please see the [EMR API README](https://github.com/openmrs/openmrs-module-emrapi
22
22
 
23
23
  See the [config-schema](./src/config-schema.ts) for details on the configuration options available to the ward app.
24
24
 
25
+ ## Ward Patient Table Tab Extension Slots
26
+
27
+ The default ward table view renders four fixed tabs (Awaiting Admission, Admitted, Discharge In, Discharged). Each tab panel is backed by an extension slot so implementers can replace the default table UI without forking the ward app.
28
+
29
+ | Tab | Slot name | Default extension name |
30
+ |-----|-----------|------------------------|
31
+ | Awaiting Admission | `ward-patients-awaiting-admission-slot` | `ward-patients-awaiting-admission` |
32
+ | Admitted | `ward-patients-admitted-slot` | `ward-patients-admitted` |
33
+ | Discharge In | `ward-patients-discharge-in-slot` | `ward-patients-discharge-in` |
34
+ | Discharged | `ward-patients-discharged-slot` | `ward-patients-discharged` |
35
+
36
+ ### Custom component requirements
37
+
38
+ - Render under `DefaultWardView` so `useAppContext('ward-view-context')` provides ward patient data.
39
+ - Reuse types from [`src/types/index.ts`](./src/types/index.ts): `WardViewContext`, `WardPatient`, etc.
40
+ - Optional shared UI helpers: `HyperLinkPatientCell`, `EmptyState`, `ErrorState`, `getOpenmrsId`.
41
+
42
+ ### Replacing a default table
43
+
44
+ In your module's `routes.json`, register the replacement extension:
45
+
46
+ ```json
47
+ {
48
+ "name": "my-custom-awaiting-admission",
49
+ "component": "myAwaitingAdmissionTable",
50
+ "slot": "ward-patients-awaiting-admission-slot"
51
+ }
52
+ ```
53
+
54
+ The default table components are exported from this module (`wardPatientsAwaitingAdmissionTable`, `wardPatientsAdmittedTable`, `wardPatientsDischargeInTable`, `wardPatientsDischargedTable`) if you need to wrap or extend them.
55
+
25
56
  ## Bed Management
26
57
 
27
58
  The ward app is designed to support patient bed assignments and to be used in conjunction with the bedmanagement module and the bed-management-app ESM. However, bed management is entirely optional. Patients will appear on a ward if they:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-ward-app",
3
- "version": "8.5.4-pre.1",
3
+ "version": "8.5.4-pre.10",
4
4
  "description": "Ward and bed management module for O3",
5
5
  "browser": "dist/kenyaemr-esm-ward-app.js",
6
6
  "main": "src/index.ts",
@@ -45,6 +45,7 @@ export const labourCareGuideConceptUuids = {
45
45
  UUID_URINE_ONE_PLUS: '1362AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
46
46
  UUID_URINE_TWO_PLUS: '1363AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
47
47
  UUID_URINE_THREE_PLUS: '1364AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
48
+ UUID_URINE_NOT_DONE: '1118AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
48
49
  UUID_URINE_TEST_TIME_SLOT: 'bb3724c9-fbcc-49c5-9702-6cde0be325ca',
49
50
  UUID_PARTOGRAPHY_ENCOUNTER: '022d62af-e2a5-4282-953b-52dd5cba3296',
50
51
  UUID_MEDICATION_OXYTOCIN_ADMINISTERED: '82580974-ff46-41c5-b218-4bea7f53bf7e',
@@ -83,6 +84,19 @@ export const labourCareGuideConceptUuids = {
83
84
  UUID_URGE_TO_PUSH: '9ad5b56d-597c-4a40-ade6-387802daa44a',
84
85
  UUID_EMERGENCY_OVERRIDE: '1670AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
85
86
  UUID_EMERGENCY_REASON: '163761AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
87
+ UUID_SHARED_DECISION_TERMINATE_ONGOING_PROGRESS: '737fa73a-509f-40d8-9e84-91dc71db7f4c',
88
+ UUID_LCG_TERMINATION_REASON_TYPE: '6098AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
89
+ UUID_LCG_TERMINATION_REASON_WRONG_PATIENT: '164144AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
90
+ UUID_LCG_TERMINATION_REASON_OTHER: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
91
+ UUID_LCG_CORRECTION_ENCOUNTER_TYPE: 'b2c4d5e6-7f8a-4e9b-8c1d-2e3f8e4a3b8f',
92
+ UUID_LCG_CORRECTION_TYPE: '4b3eb27b-43c0-4862-bd83-be06beb3d699',
93
+ UUID_LCG_CORRECTION_TYPE_WRONG_PATIENT_TRANSFER: '1286AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
94
+ UUID_LCG_CORRECTION_SOURCE_PATIENT_UUID: '162720AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
95
+ UUID_LCG_CORRECTION_SOURCE_TERMINATE_ENCOUNTER_UUID: '1671AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
96
+ UUID_LCG_CORRECTION_TERMINATION_REASON: '162704AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
97
+ UUID_LCG_CORRECTION_SOURCE_PATIENT_DISPLAY: '850a960b-e8b5-4775-ba74-aaddb2abbf51',
98
+ UUID_LCG_CORRECTION_TERMINATED_AT: '163286AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
99
+ UUID_LCG_CORRECTION_TRANSFER_NOTES: '1056dc03-ce4d-45cb-a1eb-87707162e6ee',
86
100
  FETAL_HEART_RATE_CONCEPT: '1440AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
87
101
  FETAL_HEART_RATE_TIME_CONCEPT: 'bb3724c9-fbcc-49c5-9702-6cde0be325ca',
88
102
  FETAL_HEART_RATE_HOUR_CONCEPT: '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
@@ -98,7 +112,7 @@ export const labourCareGuideConceptUuids = {
98
112
  CONTRACTION_LEVEL_MODERATE_CONCEPT: '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
99
113
  CONTRACTION_LEVEL_STRONG_CONCEPT: '166788AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
100
114
  UTERINE_CONTRACTION_FREQUENCY_CONCEPT: 'd266dee4-bbe8-4736-b94d-c33300f55bca',
101
- UTERINE_CONTRACTION_DURATION_CONCEPT: 'd266dee4-bbe8-4736-b94d-c33300f55bca',
115
+ UTERINE_CONTRACTION_DURATION_CONCEPT: '159368AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
102
116
  OXYTOCIN_TIME_CONCEPT: 'bb3724c9-fbcc-49c5-9702-6cde0be325ca',
103
117
  OXYTOCIN_DROPS_PER_MINUTE_CONCEPT: '1d109b10-7b30-4bfa-8a7c-6ecb73357fc2',
104
118
  OXYTOCIN_DOSE_CONCEPT: '81369AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
@@ -920,6 +934,11 @@ export const configSchema: ConfigSchema = {
920
934
  _description: 'Labour care guide partography module configuration',
921
935
  _default: labourCareGuidePartographyConfig,
922
936
  },
937
+ lcgSessionCorrectionPrivilege: {
938
+ _type: Type.String,
939
+ _description: 'OpenMRS privilege required to open the LCG wrong-patient correction wizard',
940
+ _default: 'Correct LCG Wrong Patient',
941
+ },
923
942
  } as const;
924
943
 
925
944
  export interface WardConfigObject {
@@ -1030,6 +1049,7 @@ export interface WardConfigObject {
1030
1049
  treatmentChartNameConceptUuid: string;
1031
1050
  treatmentChartTimeConceptUuid: string;
1032
1051
  partography: LabourCareGuidePartographyConfig;
1052
+ lcgSessionCorrectionPrivilege: string;
1033
1053
  }
1034
1054
  export interface PendingItemsElementConfig {
1035
1055
  id: string;
@@ -29,13 +29,15 @@ export const isSupportiveAlertValue = (rowKey: SupportiveAlertKey, rawValue: str
29
29
  return false;
30
30
  }
31
31
 
32
+ const isNoValue = value === 'N' || value === 'NO' || value.includes('1066');
33
+
32
34
  switch (rowKey) {
33
35
  case 'posture':
34
- return value === 'SP';
36
+ return value === 'SP' || value.includes('SUPINE');
35
37
  case 'companion':
36
38
  case 'painRelief':
37
39
  case 'oralFluid':
38
- return value === 'N';
40
+ return isNoValue;
39
41
  default:
40
42
  return false;
41
43
  }
@@ -190,9 +192,16 @@ export const isBabyAlertValue = (rowKey: BabyAlertKey, rawValue: string) => {
190
192
  return Number.isFinite(numericValue) && (numericValue < 110 || numericValue >= 160);
191
193
  }
192
194
  case 'fhrDeceleration':
193
- return value === 'L';
195
+ return value === 'L' || value.includes('LATE');
194
196
  case 'amnioticFluid':
195
- return value === 'M+++' || value === 'B';
197
+ return (
198
+ value === 'B' ||
199
+ value === 'M' ||
200
+ value === 'M+' ||
201
+ value === 'M++' ||
202
+ value === 'M+++' ||
203
+ value.startsWith('M+')
204
+ );
196
205
  case 'fetalPosition':
197
206
  return value === 'P' || value === 'T';
198
207
  case 'caput':
@@ -464,3 +473,87 @@ export const getCervixStallTokenGuidance = (t: TFunction, token: string): string
464
473
  );
465
474
  return t('cervixStallTokenGuidancePlaceholder', 'Labour progress alert: review and act.');
466
475
  };
476
+
477
+ // ─── Shared Decision Making assessment prefill ───────────────────────────────
478
+
479
+ export type SharedDecisionAlertItem = {
480
+ id?: string;
481
+ section: string;
482
+ code: string;
483
+ guidance: string;
484
+ timestampCreatedMs?: number;
485
+ };
486
+
487
+ export type SharedDecisionAssessmentLine = {
488
+ label: string;
489
+ comment: string;
490
+ };
491
+
492
+ export const formatAlertItemAsAssessmentLabel = (item: SharedDecisionAlertItem): string => {
493
+ const code = String(item.code ?? '').trim();
494
+ if (!code) {
495
+ return '';
496
+ }
497
+
498
+ const parenMatch = code.match(/^(.+?)\s+\((.+)\)$/);
499
+ if (parenMatch) {
500
+ return `${parenMatch[2].trim()} ${parenMatch[1].trim()}`;
501
+ }
502
+
503
+ const guidancePrefix = item.guidance.match(/^([^:\n]+):/);
504
+ if (guidancePrefix?.[1]) {
505
+ return guidancePrefix[1].trim();
506
+ }
507
+
508
+ return code;
509
+ };
510
+
511
+ export const buildAssessmentPrefillFromAlerts = (items: SharedDecisionAlertItem[]): string => {
512
+ const seen = new Set<string>();
513
+ const lines: string[] = [];
514
+
515
+ for (const item of items) {
516
+ if (!item?.code) {
517
+ continue;
518
+ }
519
+
520
+ const label = formatAlertItemAsAssessmentLabel(item);
521
+ const dedupeKey = label.toLowerCase();
522
+ if (!label || seen.has(dedupeKey)) {
523
+ continue;
524
+ }
525
+
526
+ seen.add(dedupeKey);
527
+ lines.push(`${label}: `);
528
+ }
529
+
530
+ return lines.join('\n');
531
+ };
532
+
533
+ export const parseAssessmentValue = (value: string): SharedDecisionAssessmentLine[] => {
534
+ if (!value.trim()) {
535
+ return [];
536
+ }
537
+
538
+ return value
539
+ .split('\n')
540
+ .map((line) => {
541
+ const colonIndex = line.indexOf(':');
542
+ if (colonIndex === -1) {
543
+ return { label: line.trim(), comment: '' };
544
+ }
545
+
546
+ return {
547
+ label: line.slice(0, colonIndex).trim(),
548
+ comment: line.slice(colonIndex + 1).replace(/^\s/, ''),
549
+ };
550
+ })
551
+ .filter((line) => line.label);
552
+ };
553
+
554
+ export const serializeAssessmentLines = (lines: SharedDecisionAssessmentLine[]): string => {
555
+ return lines
556
+ .map(({ label, comment }) => (label ? `${label}: ${comment}` : ''))
557
+ .filter(Boolean)
558
+ .join('\n');
559
+ };
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { InlineNotification } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import {
5
+ formatCorrectionAuditSubtitle,
6
+ type LcgCorrectionAuditRecord,
7
+ } from '../resources/lcg-session-correction.resource';
8
+ import styles from '../labour-care-guide.scss';
9
+
10
+ type LcgCorrectionAuditBannerProps = {
11
+ auditRecord?: LcgCorrectionAuditRecord | null;
12
+ variant?: 'chart' | 'sdm' | 'form';
13
+ };
14
+
15
+ export const LcgCorrectionAuditBanner: React.FC<LcgCorrectionAuditBannerProps> = ({ auditRecord, variant = 'sdm' }) => {
16
+ const { t } = useTranslation();
17
+
18
+ if (!auditRecord) {
19
+ return null;
20
+ }
21
+
22
+ const subtitle = formatCorrectionAuditSubtitle(auditRecord);
23
+ const title =
24
+ variant === 'chart'
25
+ ? t('lcgAuditChartTitle', 'Session corrected — data transferred from wrong chart')
26
+ : t('lcgAuditSdmTitle', 'Shared Decision Making — transferred session audit');
27
+
28
+ const bannerClassName =
29
+ variant === 'form'
30
+ ? styles.correctionAuditBannerForm
31
+ : variant === 'chart'
32
+ ? styles.correctionAuditBannerChart
33
+ : styles.correctionAuditBanner;
34
+
35
+ return (
36
+ <div className={bannerClassName}>
37
+ <InlineNotification kind="warning" lowContrast hideCloseButton title={title} subtitle={subtitle} />
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default LcgCorrectionAuditBanner;
@@ -0,0 +1,22 @@
1
+ export const LCG_LABOUR_ONSET_CODE = {
2
+ spontaneous: 'spontaneous',
3
+ induction: 'induction',
4
+ preLabourCsection: 'preLabourCsection',
5
+ } as const;
6
+
7
+ export type LcgLabourOnsetCode = (typeof LCG_LABOUR_ONSET_CODE)[keyof typeof LCG_LABOUR_ONSET_CODE];
8
+
9
+ export const LCG_ADMISSION_RISK_FACTOR_CODE = {
10
+ chronicHypertension: 'chronicHypertension',
11
+ preEclampsia: 'preEclampsia',
12
+ advancedAge: 'advancedAge',
13
+ adolescentPregnancy: 'adolescentPregnancy',
14
+ pretermLabour: 'pretermLabour',
15
+ multipleGestation: 'multipleGestation',
16
+ colonization: 'colonization',
17
+ other: 'other',
18
+ none: 'none',
19
+ } as const;
20
+
21
+ export type LcgAdmissionRiskFactorCode =
22
+ (typeof LCG_ADMISSION_RISK_FACTOR_CODE)[keyof typeof LCG_ADMISSION_RISK_FACTOR_CODE];
@@ -0,0 +1,252 @@
1
+ import React, { startTransition } from 'react';
2
+ import type { BabyEntry } from '../graphs/baby.component';
3
+ import type { LabourProgressEntry } from '../graphs/labour-progress.component';
4
+ import type { WomanLocalEntry } from '../graphs/woman.component';
5
+ import type { MedicationEntry } from '../resources/medication.resource';
6
+ import type { SupportiveCareEntry } from '../resources/supportive-care.resource';
7
+ import type { SharedDecisionMakingEntry } from '../resources/shared-decision-making.resource';
8
+ import { readLcgSectionEntries } from '../storage/lcg-local-storage';
9
+ import { useLcgSectionAutosave } from '../hooks/use-lcg-section-autosave';
10
+
11
+ export type LcgSectionKey =
12
+ | 'supportive-care'
13
+ | 'baby'
14
+ | 'woman'
15
+ | 'labour-progress'
16
+ | 'medication'
17
+ | 'shared-decision-making';
18
+
19
+ type SectionEntryMap = {
20
+ 'supportive-care': SupportiveCareEntry;
21
+ baby: BabyEntry;
22
+ woman: WomanLocalEntry;
23
+ 'labour-progress': LabourProgressEntry;
24
+ medication: MedicationEntry;
25
+ 'shared-decision-making': SharedDecisionMakingEntry;
26
+ };
27
+
28
+ type SectionContextValue<T> = {
29
+ entries: T[];
30
+ setEntries: React.Dispatch<React.SetStateAction<T[]>>;
31
+ };
32
+
33
+ function useTransitionBackedEntries<T>(initializer: () => T[]) {
34
+ const [entries, setEntriesState] = React.useState(initializer);
35
+ const setEntries = React.useCallback((updater: React.SetStateAction<T[]>) => {
36
+ startTransition(() => {
37
+ setEntriesState(updater);
38
+ });
39
+ }, []);
40
+
41
+ return [entries, setEntries] as const;
42
+ }
43
+
44
+ const SupportiveCareSectionContext = React.createContext<SectionContextValue<SupportiveCareEntry> | null>(null);
45
+ const BabySectionContext = React.createContext<SectionContextValue<BabyEntry> | null>(null);
46
+ const WomanSectionContext = React.createContext<SectionContextValue<WomanLocalEntry> | null>(null);
47
+ const LabourProgressSectionContext = React.createContext<SectionContextValue<LabourProgressEntry> | null>(null);
48
+ const MedicationSectionContext = React.createContext<SectionContextValue<MedicationEntry> | null>(null);
49
+ const SharedDecisionSectionContext = React.createContext<SectionContextValue<SharedDecisionMakingEntry> | null>(null);
50
+
51
+ const missingProviderError = 'useLcgSectionState must be used within LcgSectionStateProvider';
52
+
53
+ function hydrateSectionEntries<T>(patientUuid: string, section: LcgSectionKey): T[] {
54
+ if (!patientUuid) {
55
+ return [];
56
+ }
57
+
58
+ return readLcgSectionEntries<T>(patientUuid, section);
59
+ }
60
+
61
+ function SectionAutosaveBridge<T>({
62
+ patientUuid,
63
+ section,
64
+ entries,
65
+ }: {
66
+ patientUuid: string;
67
+ section: LcgSectionKey;
68
+ entries: T[];
69
+ }) {
70
+ useLcgSectionAutosave(patientUuid, section, entries);
71
+ return null;
72
+ }
73
+
74
+ export type LcgSectionStateProviderProps = {
75
+ patientUuid: string;
76
+ children: React.ReactNode;
77
+ };
78
+
79
+ export const LcgSectionStateProvider: React.FC<LcgSectionStateProviderProps> = ({ patientUuid, children }) => {
80
+ const [supportiveCareEntries, setSupportiveCareEntries] = useTransitionBackedEntries<SupportiveCareEntry>(() =>
81
+ hydrateSectionEntries<SupportiveCareEntry>(patientUuid, 'supportive-care'),
82
+ );
83
+ const [babyEntries, setBabyEntries] = useTransitionBackedEntries<BabyEntry>(() =>
84
+ hydrateSectionEntries<BabyEntry>(patientUuid, 'baby'),
85
+ );
86
+ const [womanEntries, setWomanEntries] = useTransitionBackedEntries<WomanLocalEntry>(() =>
87
+ hydrateSectionEntries<WomanLocalEntry>(patientUuid, 'woman'),
88
+ );
89
+ const [labourProgressEntries, setLabourProgressEntries] = useTransitionBackedEntries<LabourProgressEntry>(() =>
90
+ hydrateSectionEntries<LabourProgressEntry>(patientUuid, 'labour-progress'),
91
+ );
92
+ const [medicationEntries, setMedicationEntries] = useTransitionBackedEntries<MedicationEntry>(() =>
93
+ hydrateSectionEntries<MedicationEntry>(patientUuid, 'medication'),
94
+ );
95
+ const [sharedDecisionEntries, setSharedDecisionEntries] = useTransitionBackedEntries<SharedDecisionMakingEntry>(() =>
96
+ hydrateSectionEntries<SharedDecisionMakingEntry>(patientUuid, 'shared-decision-making'),
97
+ );
98
+
99
+ React.useEffect(() => {
100
+ startTransition(() => {
101
+ setSupportiveCareEntries(hydrateSectionEntries<SupportiveCareEntry>(patientUuid, 'supportive-care'));
102
+ setBabyEntries(hydrateSectionEntries<BabyEntry>(patientUuid, 'baby'));
103
+ setWomanEntries(hydrateSectionEntries<WomanLocalEntry>(patientUuid, 'woman'));
104
+ setLabourProgressEntries(hydrateSectionEntries<LabourProgressEntry>(patientUuid, 'labour-progress'));
105
+ setMedicationEntries(hydrateSectionEntries<MedicationEntry>(patientUuid, 'medication'));
106
+ setSharedDecisionEntries(hydrateSectionEntries<SharedDecisionMakingEntry>(patientUuid, 'shared-decision-making'));
107
+ });
108
+ }, [
109
+ patientUuid,
110
+ setBabyEntries,
111
+ setLabourProgressEntries,
112
+ setMedicationEntries,
113
+ setSharedDecisionEntries,
114
+ setSupportiveCareEntries,
115
+ setWomanEntries,
116
+ ]);
117
+
118
+ const supportiveCareValue = React.useMemo(
119
+ () => ({ entries: supportiveCareEntries, setEntries: setSupportiveCareEntries }),
120
+ [supportiveCareEntries, setSupportiveCareEntries],
121
+ );
122
+ const babyValue = React.useMemo(
123
+ () => ({ entries: babyEntries, setEntries: setBabyEntries }),
124
+ [babyEntries, setBabyEntries],
125
+ );
126
+ const womanValue = React.useMemo(
127
+ () => ({ entries: womanEntries, setEntries: setWomanEntries }),
128
+ [womanEntries, setWomanEntries],
129
+ );
130
+ const labourProgressValue = React.useMemo(
131
+ () => ({ entries: labourProgressEntries, setEntries: setLabourProgressEntries }),
132
+ [labourProgressEntries, setLabourProgressEntries],
133
+ );
134
+ const medicationValue = React.useMemo(
135
+ () => ({ entries: medicationEntries, setEntries: setMedicationEntries }),
136
+ [medicationEntries, setMedicationEntries],
137
+ );
138
+ const sharedDecisionValue = React.useMemo(
139
+ () => ({ entries: sharedDecisionEntries, setEntries: setSharedDecisionEntries }),
140
+ [sharedDecisionEntries, setSharedDecisionEntries],
141
+ );
142
+
143
+ return (
144
+ <SupportiveCareSectionContext.Provider value={supportiveCareValue}>
145
+ <BabySectionContext.Provider value={babyValue}>
146
+ <WomanSectionContext.Provider value={womanValue}>
147
+ <LabourProgressSectionContext.Provider value={labourProgressValue}>
148
+ <MedicationSectionContext.Provider value={medicationValue}>
149
+ <SharedDecisionSectionContext.Provider value={sharedDecisionValue}>
150
+ <SectionAutosaveBridge
151
+ patientUuid={patientUuid}
152
+ section="supportive-care"
153
+ entries={supportiveCareEntries}
154
+ />
155
+ <SectionAutosaveBridge patientUuid={patientUuid} section="baby" entries={babyEntries} />
156
+ <SectionAutosaveBridge patientUuid={patientUuid} section="woman" entries={womanEntries} />
157
+ <SectionAutosaveBridge
158
+ patientUuid={patientUuid}
159
+ section="labour-progress"
160
+ entries={labourProgressEntries}
161
+ />
162
+ <SectionAutosaveBridge patientUuid={patientUuid} section="medication" entries={medicationEntries} />
163
+ <SectionAutosaveBridge
164
+ patientUuid={patientUuid}
165
+ section="shared-decision-making"
166
+ entries={sharedDecisionEntries}
167
+ />
168
+ {children}
169
+ </SharedDecisionSectionContext.Provider>
170
+ </MedicationSectionContext.Provider>
171
+ </LabourProgressSectionContext.Provider>
172
+ </WomanSectionContext.Provider>
173
+ </BabySectionContext.Provider>
174
+ </SupportiveCareSectionContext.Provider>
175
+ );
176
+ };
177
+
178
+ export function useLcgSectionState<K extends LcgSectionKey>(section: K): SectionContextValue<SectionEntryMap[K]> {
179
+ const supportiveCareContext = React.useContext(SupportiveCareSectionContext);
180
+ const babyContext = React.useContext(BabySectionContext);
181
+ const womanContext = React.useContext(WomanSectionContext);
182
+ const labourProgressContext = React.useContext(LabourProgressSectionContext);
183
+ const medicationContext = React.useContext(MedicationSectionContext);
184
+ const sharedDecisionContext = React.useContext(SharedDecisionSectionContext);
185
+
186
+ switch (section) {
187
+ case 'supportive-care': {
188
+ if (!supportiveCareContext) {
189
+ throw new Error(missingProviderError);
190
+ }
191
+ return supportiveCareContext as SectionContextValue<SectionEntryMap[K]>;
192
+ }
193
+ case 'baby': {
194
+ if (!babyContext) {
195
+ throw new Error(missingProviderError);
196
+ }
197
+ return babyContext as SectionContextValue<SectionEntryMap[K]>;
198
+ }
199
+ case 'woman': {
200
+ if (!womanContext) {
201
+ throw new Error(missingProviderError);
202
+ }
203
+ return womanContext as SectionContextValue<SectionEntryMap[K]>;
204
+ }
205
+ case 'labour-progress': {
206
+ if (!labourProgressContext) {
207
+ throw new Error(missingProviderError);
208
+ }
209
+ return labourProgressContext as SectionContextValue<SectionEntryMap[K]>;
210
+ }
211
+ case 'medication': {
212
+ if (!medicationContext) {
213
+ throw new Error(missingProviderError);
214
+ }
215
+ return medicationContext as SectionContextValue<SectionEntryMap[K]>;
216
+ }
217
+ case 'shared-decision-making': {
218
+ if (!sharedDecisionContext) {
219
+ throw new Error(missingProviderError);
220
+ }
221
+ return sharedDecisionContext as SectionContextValue<SectionEntryMap[K]>;
222
+ }
223
+ default: {
224
+ const exhaustiveCheck: never = section;
225
+ throw new Error(`Unsupported LCG section: ${String(exhaustiveCheck)}`);
226
+ }
227
+ }
228
+ }
229
+
230
+ export function useLcgAllSectionEntries() {
231
+ const supportiveCare = useLcgSectionState('supportive-care');
232
+ const baby = useLcgSectionState('baby');
233
+ const woman = useLcgSectionState('woman');
234
+ const labourProgress = useLcgSectionState('labour-progress');
235
+ const medication = useLcgSectionState('medication');
236
+ const sharedDecision = useLcgSectionState('shared-decision-making');
237
+
238
+ return {
239
+ localSupportiveCareEntries: supportiveCare.entries,
240
+ setLocalSupportiveCareEntries: supportiveCare.setEntries,
241
+ localBabyEntries: baby.entries,
242
+ setLocalBabyEntries: baby.setEntries,
243
+ localWomanEntries: woman.entries,
244
+ setLocalWomanEntries: woman.setEntries,
245
+ localLabourProgressEntries: labourProgress.entries,
246
+ setLocalLabourProgressEntries: labourProgress.setEntries,
247
+ localMedicationEntries: medication.entries,
248
+ setLocalMedicationEntries: medication.setEntries,
249
+ localSharedDecisionMakingEntries: sharedDecision.entries,
250
+ setLocalSharedDecisionMakingEntries: sharedDecision.setEntries,
251
+ };
252
+ }
@@ -16,6 +16,7 @@ import {
16
16
  import styles from '../partography-data-form.scss';
17
17
  import { saveAdmissionDetails } from '../resources/admission-details.resource';
18
18
  import { AdmissionDetailsTimePickerDropdown } from './form-scoped-time-pickers.component';
19
+ import { LCG_ADMISSION_RISK_FACTOR_CODE, LCG_LABOUR_ONSET_CODE } from '../constants/admission-form.constants';
19
20
 
20
21
  export type AdmissionDetailsFormData = {
21
22
  labourOnset: string;
@@ -89,22 +90,30 @@ const AdmissionDetailsForm: React.FC<AdmissionDetailsFormProps> = ({ isOpen, onC
89
90
 
90
91
  const rupturedMembranesValue = watch('rupturedMembranes');
91
92
  const riskFactorsValue = watch('riskFactors');
92
- const hasOtherRiskFactor = (riskFactorsValue || []).includes('other');
93
+ const hasOtherRiskFactor = (riskFactorsValue || []).includes(LCG_ADMISSION_RISK_FACTOR_CODE.other);
93
94
  const patientAge = React.useMemo(() => resolvePatientAge(patient), [patient]);
94
95
  const canSelectAdolescentPregnancy = Number.isFinite(patientAge) && patientAge >= 10 && patientAge <= 19;
95
96
 
96
97
  const riskFactorOptions = React.useMemo(
97
98
  () =>
98
99
  [
99
- { id: 'chronic_hypertension', text: t('chronicHypertension', 'Chronic hypertension') },
100
- { id: 'pre_eclampsia', text: t('preEclampsia', 'Pre-eclampsia') },
101
- { id: 'advanced_age', text: t('advancedAge', "Woman's advanced age") },
102
- { id: 'adolescent_pregnancy', text: t('adolescentPregnancy', 'Adolescent pregnancy') },
103
- { id: 'multiple_gestation', text: t('multipleGestation', 'Multiple gestation') },
104
- { id: 'colonization', text: t('colonization', 'Colonization') },
105
- { id: 'other', text: t('other', 'Other') },
106
- { id: 'none', text: t('none', 'None') },
107
- ].filter((option) => option.id !== 'adolescent_pregnancy' || canSelectAdolescentPregnancy),
100
+ {
101
+ id: LCG_ADMISSION_RISK_FACTOR_CODE.chronicHypertension,
102
+ text: t('chronicHypertension', 'Chronic hypertension'),
103
+ },
104
+ { id: LCG_ADMISSION_RISK_FACTOR_CODE.preEclampsia, text: t('preEclampsia', 'Pre-eclampsia') },
105
+ { id: LCG_ADMISSION_RISK_FACTOR_CODE.advancedAge, text: t('advancedAge', "Woman's advanced age") },
106
+ {
107
+ id: LCG_ADMISSION_RISK_FACTOR_CODE.adolescentPregnancy,
108
+ text: t('adolescentPregnancy', 'Adolescent pregnancy'),
109
+ },
110
+ { id: LCG_ADMISSION_RISK_FACTOR_CODE.multipleGestation, text: t('multipleGestation', 'Multiple gestation') },
111
+ { id: LCG_ADMISSION_RISK_FACTOR_CODE.colonization, text: t('colonization', 'Colonization') },
112
+ { id: LCG_ADMISSION_RISK_FACTOR_CODE.other, text: t('other', 'Other') },
113
+ { id: LCG_ADMISSION_RISK_FACTOR_CODE.none, text: t('none', 'None') },
114
+ ].filter(
115
+ (option) => option.id !== LCG_ADMISSION_RISK_FACTOR_CODE.adolescentPregnancy || canSelectAdolescentPregnancy,
116
+ ),
108
117
  [canSelectAdolescentPregnancy, t],
109
118
  );
110
119
 
@@ -131,7 +140,9 @@ const AdmissionDetailsForm: React.FC<AdmissionDetailsFormProps> = ({ isOpen, onC
131
140
  ...data,
132
141
  riskFactors: canSelectAdolescentPregnancy
133
142
  ? data.riskFactors
134
- : (data.riskFactors || []).filter((riskFactor) => riskFactor !== 'adolescent_pregnancy'),
143
+ : (data.riskFactors || []).filter(
144
+ (riskFactor) => riskFactor !== LCG_ADMISSION_RISK_FACTOR_CODE.adolescentPregnancy,
145
+ ),
135
146
  };
136
147
  const result = await saveAdmissionDetails(patientUuid, payload, t);
137
148
  if (!result.success) {
@@ -191,9 +202,12 @@ const AdmissionDetailsForm: React.FC<AdmissionDetailsFormProps> = ({ isOpen, onC
191
202
  invalid={!!fieldState.error}
192
203
  invalidText={fieldState.error?.message}>
193
204
  <SelectItem value="" text={t('selectOption', 'Select option')} />
194
- <SelectItem value="spontaneous" text={t('spontaneous', 'Spontaneous')} />
195
- <SelectItem value="induction" text={t('induction', 'Induction')} />
196
- <SelectItem value="pre_labour_csection" text={t('preLabourCsection', 'Pre-labour C-section')} />
205
+ <SelectItem value={LCG_LABOUR_ONSET_CODE.spontaneous} text={t('spontaneous', 'Spontaneous')} />
206
+ <SelectItem value={LCG_LABOUR_ONSET_CODE.induction} text={t('induction', 'Induction')} />
207
+ <SelectItem
208
+ value={LCG_LABOUR_ONSET_CODE.preLabourCsection}
209
+ text={t('preLabourCsection', 'Pre-labour C-section')}
210
+ />
197
211
  </Select>
198
212
  )}
199
213
  />