@openmrs/esm-patient-chart-app 11.3.1-patch.9310 → 11.3.1-patch.9508

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 (178) hide show
  1. package/.turbo/turbo-build.log +18 -21
  2. package/dist/1119.js +1 -1
  3. package/dist/1197.js +1 -1
  4. package/dist/2146.js +1 -1
  5. package/dist/2540.js +1 -0
  6. package/dist/2540.js.map +1 -0
  7. package/dist/2690.js +1 -1
  8. package/dist/276.js +1 -0
  9. package/dist/276.js.map +1 -0
  10. package/dist/2761.js.map +1 -1
  11. package/dist/3099.js +1 -1
  12. package/dist/{8278.js → 3119.js} +1 -1
  13. package/dist/{8278.js.map → 3119.js.map} +1 -1
  14. package/dist/3584.js +1 -1
  15. package/dist/3905.js +1 -0
  16. package/dist/3905.js.map +1 -0
  17. package/dist/4055.js +1 -1
  18. package/dist/4132.js +1 -1
  19. package/dist/4300.js +1 -1
  20. package/dist/4335.js +1 -1
  21. package/dist/439.js +1 -0
  22. package/dist/4618.js +1 -1
  23. package/dist/4652.js +1 -1
  24. package/dist/4944.js +1 -1
  25. package/dist/5048.js +1 -0
  26. package/dist/5048.js.map +1 -0
  27. package/dist/506.js +2 -0
  28. package/dist/506.js.map +1 -0
  29. package/dist/5173.js +1 -1
  30. package/dist/5241.js +1 -1
  31. package/dist/5442.js +1 -1
  32. package/dist/5661.js +1 -1
  33. package/dist/5670.js +1 -1
  34. package/dist/6022.js +1 -1
  35. package/dist/6411.js +1 -1
  36. package/dist/6411.js.map +1 -1
  37. package/dist/6468.js +1 -1
  38. package/dist/6568.js +1 -1
  39. package/dist/6568.js.map +1 -1
  40. package/dist/6589.js +1 -0
  41. package/dist/6679.js +1 -1
  42. package/dist/6840.js +1 -1
  43. package/dist/6859.js +1 -1
  44. package/dist/6924.js +1 -1
  45. package/dist/6924.js.map +1 -1
  46. package/dist/{9294.js → 6997.js} +1 -1
  47. package/dist/6997.js.map +1 -0
  48. package/dist/7097.js +1 -1
  49. package/dist/7159.js +1 -1
  50. package/dist/723.js +1 -1
  51. package/dist/7617.js +1 -1
  52. package/dist/7810.js +1 -0
  53. package/dist/7810.js.map +1 -0
  54. package/dist/7822.js +1 -1
  55. package/dist/7822.js.map +1 -1
  56. package/dist/795.js +1 -1
  57. package/dist/8163.js +1 -1
  58. package/dist/8260.js +1 -1
  59. package/dist/8260.js.map +1 -1
  60. package/dist/8349.js +1 -1
  61. package/dist/8371.js +1 -0
  62. package/dist/8454.js +1 -1
  63. package/dist/8454.js.map +1 -1
  64. package/dist/8618.js +1 -1
  65. package/dist/8709.js +1 -1
  66. package/dist/8709.js.map +1 -1
  67. package/dist/890.js +1 -1
  68. package/dist/9214.js +1 -1
  69. package/dist/9538.js +1 -1
  70. package/dist/9569.js +1 -1
  71. package/dist/986.js +1 -1
  72. package/dist/9879.js +1 -1
  73. package/dist/9895.js +1 -1
  74. package/dist/9900.js +1 -1
  75. package/dist/9913.js +1 -1
  76. package/dist/main.js +1 -1
  77. package/dist/main.js.map +1 -1
  78. package/dist/openmrs-esm-patient-chart-app.js +1 -1
  79. package/dist/openmrs-esm-patient-chart-app.js.buildmanifest.json +375 -285
  80. package/dist/openmrs-esm-patient-chart-app.js.map +1 -1
  81. package/dist/routes.json +1 -1
  82. package/package.json +2 -2
  83. package/src/actions-buttons/mark-patient-deceased.component.tsx +2 -2
  84. package/src/actions-buttons/start-visit.component.tsx +5 -10
  85. package/src/actions-buttons/start-visit.test.tsx +5 -9
  86. package/src/clinical-views/encounter-list/encounter-list-tabs.extension.tsx +2 -2
  87. package/src/clinical-views/utils/encounter-list-config-builder.ts +19 -6
  88. package/src/clinical-views/utils/helpers.ts +5 -4
  89. package/src/index.ts +18 -12
  90. package/src/mark-patient-deceased/mark-patient-deceased-form.test.tsx +9 -15
  91. package/src/mark-patient-deceased/mark-patient-deceased-form.workspace.tsx +138 -147
  92. package/src/patient-banner-tags/visit-attribute-tags.extension.tsx +21 -13
  93. package/src/patient-banner-tags/visit-attribute-tags.scss +8 -0
  94. package/src/patient-chart/chart-review/dashboard-view.component.tsx +2 -2
  95. package/src/patient-chart/chart-review/dashboard-view.scss +5 -0
  96. package/src/patient-chart/patient-chart.component.tsx +41 -50
  97. package/src/patient-chart/patient-chart.resources.ts +10 -52
  98. package/src/routes.json +7 -18
  99. package/src/visit/hooks/useDeleteVisit.tsx +1 -1
  100. package/src/visit/start-visit-button.component.tsx +2 -2
  101. package/src/visit/start-visit-button.test.tsx +2 -2
  102. package/src/visit/visit-action-items/edit-visit-details.component.tsx +8 -29
  103. package/src/visit/visit-form/base-visit-type.component.tsx +30 -21
  104. package/src/visit/visit-form/exported-visit-form.workspace.tsx +3 -0
  105. package/src/visit/visit-form/visit-form.test.tsx +18 -27
  106. package/src/visit/visit-form/visit-form.workspace.tsx +653 -35
  107. package/src/visit/visit-history-table/visit-actions-cell.component.tsx +2 -3
  108. package/src/visit/visit-history-table/visit-date-cell.component.tsx +0 -1
  109. package/src/visit/visit-history-table/visit-diagnoses-cell.component.tsx +0 -1
  110. package/src/visit/visit-history-table/visit-history-table.component.tsx +2 -3
  111. package/src/visit/visit-history-table/visit-type-cell.component.tsx +0 -1
  112. package/src/visit/visit-prompt/delete-visit-dialog.test.tsx +1 -1
  113. package/src/visit/visit-prompt/{end-visit-dialog.modal.tsx → end-visit-dialog.component.tsx} +1 -1
  114. package/src/visit/visit-prompt/end-visit-dialog.test.tsx +1 -1
  115. package/src/visit/visit-prompt/{start-visit-dialog.modal.tsx → start-visit-dialog.component.tsx} +4 -10
  116. package/src/visit/visit-prompt/start-visit-dialog.test.tsx +3 -3
  117. package/src/visit/visits-widget/current-visit-summary.extension.tsx +3 -3
  118. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.component.tsx +35 -12
  119. package/src/visit/visits-widget/visit-context/retrospective-data-date-time-picker/retrospective-date-time-picker.component.tsx +0 -1
  120. package/src/visit/visits-widget/visit-context/visit-context-switcher.modal.tsx +2 -2
  121. package/src/visit/visits-widget/visit-context/visit-context-switcher.test.tsx +20 -22
  122. package/src/visit/visits-widget/visit-detail-overview.component.tsx +2 -3
  123. package/src/visit/visits-widget/visit-detail-overview.test.tsx +4 -4
  124. package/translations/am.json +1 -2
  125. package/translations/ar.json +2 -3
  126. package/translations/ar_SY.json +1 -2
  127. package/translations/bn.json +1 -2
  128. package/translations/cs.json +196 -0
  129. package/translations/de.json +1 -2
  130. package/translations/en.json +1 -1
  131. package/translations/en_US.json +1 -2
  132. package/translations/es.json +2 -3
  133. package/translations/es_MX.json +1 -2
  134. package/translations/fr.json +7 -8
  135. package/translations/he.json +2 -3
  136. package/translations/hi.json +1 -2
  137. package/translations/hi_IN.json +1 -2
  138. package/translations/id.json +2 -3
  139. package/translations/it.json +2 -3
  140. package/translations/ka.json +2 -3
  141. package/translations/km.json +2 -3
  142. package/translations/ku.json +1 -2
  143. package/translations/ky.json +1 -2
  144. package/translations/lg.json +1 -2
  145. package/translations/ne.json +1 -2
  146. package/translations/pl.json +1 -2
  147. package/translations/pt.json +2 -3
  148. package/translations/pt_BR.json +2 -3
  149. package/translations/qu.json +1 -2
  150. package/translations/ro_RO.json +2 -3
  151. package/translations/ru_RU.json +1 -2
  152. package/translations/si.json +1 -2
  153. package/translations/sq.json +196 -0
  154. package/translations/sw.json +1 -2
  155. package/translations/sw_KE.json +1 -2
  156. package/translations/tr.json +1 -2
  157. package/translations/tr_TR.json +1 -2
  158. package/translations/uk.json +1 -2
  159. package/translations/uz.json +1 -2
  160. package/translations/uz@Latn.json +1 -2
  161. package/translations/uz_UZ.json +1 -2
  162. package/translations/vi.json +2 -3
  163. package/translations/zh.json +2 -3
  164. package/translations/zh_CN.json +2 -3
  165. package/translations/zh_TW.json +196 -0
  166. package/dist/1815.js +0 -2
  167. package/dist/1815.js.map +0 -1
  168. package/dist/3697.js +0 -1
  169. package/dist/3697.js.map +0 -1
  170. package/dist/5827.js +0 -1
  171. package/dist/5827.js.map +0 -1
  172. package/dist/7818.js +0 -1
  173. package/dist/7818.js.map +0 -1
  174. package/dist/9294.js.map +0 -1
  175. package/dist/9329.js +0 -1
  176. package/dist/9329.js.map +0 -1
  177. /package/dist/{1815.js.LICENSE.txt → 506.js.LICENSE.txt} +0 -0
  178. /package/src/visit/visit-prompt/{delete-visit-dialog.modal.tsx → delete-visit-dialog.component.tsx} +0 -0
@@ -20,7 +20,7 @@ import { Controller, useForm, type SubmitHandler } from 'react-hook-form';
20
20
  import { z } from 'zod';
21
21
  import { zodResolver } from '@hookform/resolvers/zod';
22
22
  import { WarningFilled } from '@carbon/react/icons';
23
- import { type PatientWorkspace2DefinitionProps, EmptyState } from '@openmrs/esm-patient-common-lib';
23
+ import { EmptyState, type DefaultPatientWorkspaceProps } from '@openmrs/esm-patient-common-lib';
24
24
  import {
25
25
  ExtensionSlot,
26
26
  useLayoutType,
@@ -28,16 +28,12 @@ import {
28
28
  ResponsiveWrapper,
29
29
  useConfig,
30
30
  OpenmrsDatePicker,
31
- Workspace2,
32
31
  } from '@openmrs/esm-framework';
33
32
  import { markPatientDeceased, useCausesOfDeath } from '../data.resource';
34
33
  import { type ChartConfig } from '../config-schema';
35
34
  import styles from './mark-patient-deceased-form.scss';
36
35
 
37
- const MarkPatientDeceasedForm: React.FC<PatientWorkspace2DefinitionProps<{}, {}>> = ({
38
- closeWorkspace,
39
- groupProps: { patientUuid },
40
- }) => {
36
+ const MarkPatientDeceasedForm: React.FC<DefaultPatientWorkspaceProps> = ({ closeWorkspace, patientUuid }) => {
41
37
  const { t } = useTranslation();
42
38
  const isTablet = useLayoutType() === 'tablet';
43
39
  const memoizedPatientUuid = useMemo(() => ({ patientUuid }), [patientUuid]);
@@ -82,7 +78,7 @@ const MarkPatientDeceasedForm: React.FC<PatientWorkspace2DefinitionProps<{}, {}>
82
78
 
83
79
  const {
84
80
  control,
85
- formState: { errors, isSubmitting, isDirty },
81
+ formState: { errors, isSubmitting },
86
82
  handleSubmit,
87
83
  watch,
88
84
  } = useForm<MarkPatientDeceasedFormSchema>({
@@ -121,156 +117,151 @@ const MarkPatientDeceasedForm: React.FC<PatientWorkspace2DefinitionProps<{}, {}>
121
117
  const onError = (errors) => console.error(errors);
122
118
 
123
119
  return (
124
- <Workspace2 title={t('markPatientDeceased', 'Mark patient deceased')} hasUnsavedChanges={isDirty}>
125
- <Form className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
126
- <div>
127
- {isTablet && (
128
- <Row className={styles.headerGridRow}>
129
- <ExtensionSlot className={styles.dataGridRow} name="visit-form-header-slot" state={memoizedPatientUuid} />
130
- </Row>
131
- )}
132
- <div className={styles.container}>
133
- <span className={styles.warningContainer}>
134
- <WarningFilled aria-label={t('warning', 'Warning')} className={styles.warningIcon} size={20} />
135
- <span className={styles.warningText}>
136
- {t(
137
- 'markDeceasedWarning',
138
- 'Marking the patient as deceased will end any active visits for this patient',
139
- )}
140
- </span>
120
+ <Form className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
121
+ <div>
122
+ {isTablet && (
123
+ <Row className={styles.headerGridRow}>
124
+ <ExtensionSlot className={styles.dataGridRow} name="visit-form-header-slot" state={memoizedPatientUuid} />
125
+ </Row>
126
+ )}
127
+ <div className={styles.container}>
128
+ <span className={styles.warningContainer}>
129
+ <WarningFilled aria-label={t('warning', 'Warning')} className={styles.warningIcon} size={20} />
130
+ <span className={styles.warningText}>
131
+ {t('markDeceasedWarning', 'Marking the patient as deceased will end any active visits for this patient')}
141
132
  </span>
142
- <section>
143
- <div className={styles.sectionTitle}>{t('dateOfDeath', 'Date of death')}</div>
133
+ </span>
134
+ <section>
135
+ <div className={styles.sectionTitle}>{t('dateOfDeath', 'Date of death')}</div>
136
+ {causesOfDeath?.length ? (
137
+ <ResponsiveWrapper>
138
+ <Controller
139
+ name="deathDate"
140
+ control={control}
141
+ render={({ field, fieldState }) => (
142
+ <OpenmrsDatePicker
143
+ {...field}
144
+ className={styles.datePicker}
145
+ id="deceasedDate"
146
+ data-testid="deceasedDate"
147
+ labelText={t('date', 'Date')}
148
+ maxDate={new Date()}
149
+ invalid={Boolean(fieldState?.error?.message)}
150
+ invalidText={fieldState?.error?.message}
151
+ />
152
+ )}
153
+ />
154
+ </ResponsiveWrapper>
155
+ ) : (
156
+ <DatePickerSkeleton />
157
+ )}
158
+ </section>
159
+ <section>
160
+ <div className={styles.sectionTitle}>{t('causeOfDeath', 'Cause of death')}</div>
161
+ <div
162
+ className={classNames(styles.conceptAnswerOverviewWrapper, {
163
+ [styles.conceptAnswerOverviewWrapperTablet]: isTablet,
164
+ [styles.conceptAnswerOverviewWrapperDesktop]: !isTablet,
165
+ [styles.errorOutline]: errors?.causeOfDeath?.message,
166
+ })}
167
+ >
168
+ {isLoadingCausesOfDeath ? <StructuredListSkeleton /> : null}
169
+
144
170
  {causesOfDeath?.length ? (
145
171
  <ResponsiveWrapper>
146
- <Controller
147
- name="deathDate"
148
- control={control}
149
- render={({ field, fieldState }) => (
150
- <OpenmrsDatePicker
151
- {...field}
152
- className={styles.datePicker}
153
- id="deceasedDate"
154
- data-testid="deceasedDate"
155
- labelText={t('date', 'Date')}
156
- maxDate={new Date()}
157
- invalid={Boolean(fieldState?.error?.message)}
158
- invalidText={fieldState?.error?.message}
159
- />
160
- )}
172
+ <Search
173
+ labelText={t('searchForCauseOfDeath', 'Search for a cause of death')}
174
+ onChange={handleSearchTermChange}
175
+ placeholder={t('searchForCauseOfDeath', 'Search for a cause of death')}
161
176
  />
162
177
  </ResponsiveWrapper>
163
- ) : (
164
- <DatePickerSkeleton />
165
- )}
166
- </section>
167
- <section>
168
- <div className={styles.sectionTitle}>{t('causeOfDeath', 'Cause of death')}</div>
169
- <div
170
- className={classNames(styles.conceptAnswerOverviewWrapper, {
171
- [styles.conceptAnswerOverviewWrapperTablet]: isTablet,
172
- [styles.conceptAnswerOverviewWrapperDesktop]: !isTablet,
173
- [styles.errorOutline]: errors?.causeOfDeath?.message,
174
- })}
175
- >
176
- {isLoadingCausesOfDeath ? <StructuredListSkeleton /> : null}
178
+ ) : null}
177
179
 
178
- {causesOfDeath?.length ? (
179
- <ResponsiveWrapper>
180
- <Search
181
- labelText={t('searchForCauseOfDeath', 'Search for a cause of death')}
182
- onChange={handleSearchTermChange}
183
- placeholder={t('searchForCauseOfDeath', 'Search for a cause of death')}
184
- />
185
- </ResponsiveWrapper>
186
- ) : null}
180
+ {causesOfDeath?.length && filteredCausesOfDeath.length > 0 ? (
181
+ <Controller
182
+ name="causeOfDeath"
183
+ control={control}
184
+ render={({ field: { onChange } }) => (
185
+ <RadioButtonGroup
186
+ className={styles.radioButtonGroup}
187
+ name={
188
+ causeOfDeathValue === freeTextFieldConceptUuid
189
+ ? 'freeTextFieldCauseOfDeath'
190
+ : 'codedCauseOfDeath'
191
+ }
192
+ orientation="vertical"
193
+ onChange={onChange}
194
+ >
195
+ {filteredCausesOfDeath.map(({ uuid, display, name }) => (
196
+ <RadioButton
197
+ className={styles.radioButton}
198
+ id={name}
199
+ key={uuid}
200
+ labelText={display}
201
+ value={uuid}
202
+ />
203
+ ))}
204
+ </RadioButtonGroup>
205
+ )}
206
+ />
207
+ ) : null}
187
208
 
188
- {causesOfDeath?.length && filteredCausesOfDeath.length > 0 ? (
189
- <Controller
190
- name="causeOfDeath"
191
- control={control}
192
- render={({ field: { onChange } }) => (
193
- <RadioButtonGroup
194
- className={styles.radioButtonGroup}
195
- name={
196
- causeOfDeathValue === freeTextFieldConceptUuid
197
- ? 'freeTextFieldCauseOfDeath'
198
- : 'codedCauseOfDeath'
199
- }
200
- orientation="vertical"
201
- onChange={onChange}
202
- >
203
- {filteredCausesOfDeath.map(({ uuid, display, name }) => (
204
- <RadioButton
205
- className={styles.radioButton}
206
- id={name}
207
- key={uuid}
208
- labelText={display}
209
- value={uuid}
210
- />
211
- ))}
212
- </RadioButtonGroup>
213
- )}
214
- />
215
- ) : null}
216
-
217
- {searchTerm && filteredCausesOfDeath.length === 0 && (
218
- <div className={styles.tileContainer}>
219
- <Tile className={styles.tile}>
220
- <div className={styles.tileContent}>
221
- <p className={styles.content}>
222
- {t('noMatchingCodedCausesOfDeath', 'No matching coded causes of death')}
223
- </p>
224
- <p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
225
- </div>
226
- </Tile>
227
- </div>
228
- )}
209
+ {searchTerm && filteredCausesOfDeath.length === 0 && (
210
+ <div className={styles.tileContainer}>
211
+ <Tile className={styles.tile}>
212
+ <div className={styles.tileContent}>
213
+ <p className={styles.content}>
214
+ {t('noMatchingCodedCausesOfDeath', 'No matching coded causes of death')}
215
+ </p>
216
+ <p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
217
+ </div>
218
+ </Tile>
219
+ </div>
220
+ )}
229
221
 
230
- {!isLoadingCausesOfDeath && !causesOfDeath?.length ? (
231
- <EmptyState
232
- displayText={t('causeOfDeath_lower', 'cause of death concepts configured in the system')}
233
- headerTitle={t('causeOfDeath', 'Cause of death')}
234
- />
235
- ) : null}
236
- </div>
237
- {errors?.causeOfDeath && <p className={styles.errorMessage}>{errors?.causeOfDeath?.message}</p>}
238
- </section>
239
- </div>
240
- {causeOfDeathValue === freeTextFieldConceptUuid && (
241
- <div className={styles.nonCodedCauseOfDeath}>
242
- <Controller
243
- name="nonCodedCauseOfDeath"
244
- control={control}
245
- render={({ field: { onChange, value } }) => (
246
- <TextInput
247
- id="freeTextCauseOfDeath"
248
- invalid={!!errors?.nonCodedCauseOfDeath}
249
- invalidText={errors?.nonCodedCauseOfDeath?.message}
250
- labelText={t('nonCodedCauseOfDeath', 'Non-coded cause of death')}
251
- onChange={onChange}
252
- placeholder={t('enterNonCodedCauseOfDeath', 'Enter non-coded cause of death')}
253
- value={value}
254
- />
255
- )}
256
- />
222
+ {!isLoadingCausesOfDeath && !causesOfDeath?.length ? (
223
+ <EmptyState
224
+ displayText={t('causeOfDeath_lower', 'cause of death concepts configured in the system')}
225
+ headerTitle={t('causeOfDeath', 'Cause of death')}
226
+ />
227
+ ) : null}
257
228
  </div>
258
- )}
229
+ {errors?.causeOfDeath && <p className={styles.errorMessage}>{errors?.causeOfDeath?.message}</p>}
230
+ </section>
259
231
  </div>
260
- <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
261
- <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
262
- {t('discard', 'Discard')}
263
- </Button>
264
- <Button className={styles.button} disabled={isSubmitting} kind="primary" type="submit">
265
- {isSubmitting ? (
266
- <InlineLoading description={t('saving', 'Saving') + '...'} role="progressbar" />
267
- ) : (
268
- t('saveAndClose', 'Save and close')
269
- )}
270
- </Button>
271
- </ButtonSet>
272
- </Form>
273
- </Workspace2>
232
+ {causeOfDeathValue === freeTextFieldConceptUuid && (
233
+ <div className={styles.nonCodedCauseOfDeath}>
234
+ <Controller
235
+ name="nonCodedCauseOfDeath"
236
+ control={control}
237
+ render={({ field: { onChange, value } }) => (
238
+ <TextInput
239
+ id="freeTextCauseOfDeath"
240
+ invalid={!!errors?.nonCodedCauseOfDeath}
241
+ invalidText={errors?.nonCodedCauseOfDeath?.message}
242
+ labelText={t('nonCodedCauseOfDeath', 'Non-coded cause of death')}
243
+ onChange={onChange}
244
+ placeholder={t('enterNonCodedCauseOfDeath', 'Enter non-coded cause of death')}
245
+ value={value}
246
+ />
247
+ )}
248
+ />
249
+ </div>
250
+ )}
251
+ </div>
252
+ <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
253
+ <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
254
+ {t('discard', 'Discard')}
255
+ </Button>
256
+ <Button className={styles.button} disabled={isSubmitting} kind="primary" type="submit">
257
+ {isSubmitting ? (
258
+ <InlineLoading description={t('saving', 'Saving') + '...'} role="progressbar" />
259
+ ) : (
260
+ t('saveAndClose', 'Save and close')
261
+ )}
262
+ </Button>
263
+ </ButtonSet>
264
+ </Form>
274
265
  );
275
266
  };
276
267
 
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { Tag } from '@carbon/react';
3
3
  import { formatDate, useConfig, useVisit } from '@openmrs/esm-framework';
4
4
  import { type ChartConfig } from '../config-schema';
5
+ import styles from './visit-attribute-tags.scss';
5
6
 
6
7
  interface VisitAttributeTagsProps {
7
8
  patientUuid: string;
@@ -32,22 +33,29 @@ const VisitAttributeTags: React.FC<VisitAttributeTagsProps> = ({ patientUuid })
32
33
  const { activeVisit } = useVisit(patientUuid);
33
34
  const { visitAttributeTypes } = useConfig<ChartConfig>();
34
35
 
35
- if (activeVisit == null) {
36
+ const displayableAttributes = activeVisit?.attributes
37
+ ?.filter(
38
+ (attribute) =>
39
+ visitAttributeTypes?.find(({ uuid }) => attribute?.attributeType?.uuid === uuid)?.displayInThePatientBanner,
40
+ )
41
+ .map((attribute) => ({
42
+ attribute,
43
+ value: getAttributeValue(attribute?.attributeType, attribute?.value),
44
+ }))
45
+ .filter(({ value }) => value != null && value !== '');
46
+
47
+ if (!displayableAttributes?.length) {
36
48
  return null;
37
49
  }
50
+
38
51
  return (
39
- <>
40
- {activeVisit?.attributes
41
- ?.filter(
42
- (attribute) =>
43
- visitAttributeTypes.find(({ uuid }) => attribute?.attributeType?.uuid === uuid)?.displayInThePatientBanner,
44
- )
45
- .map((attribute) => (
46
- <Tag key={attribute?.attributeType?.uuid} type="gray">
47
- {getAttributeValue(attribute?.attributeType, attribute?.value)}
48
- </Tag>
49
- ))}
50
- </>
52
+ <div className={styles.tagsContainer}>
53
+ {displayableAttributes.map(({ attribute, value }) => (
54
+ <Tag key={attribute?.attributeType?.uuid} type="gray">
55
+ {value}
56
+ </Tag>
57
+ ))}
58
+ </div>
51
59
  );
52
60
  };
53
61
 
@@ -0,0 +1,8 @@
1
+ @use '@carbon/layout';
2
+
3
+ .tagsContainer {
4
+ display: flex;
5
+ flex-wrap: wrap;
6
+ gap: layout.$spacing-02;
7
+ margin: 0 layout.$spacing-02;
8
+ }
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { useMatch } from 'react-router-dom';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { launchWorkspace2, Extension, ExtensionSlot, useExtensionSlotMeta } from '@openmrs/esm-framework';
5
+ import { launchWorkspace, Extension, ExtensionSlot, useExtensionSlotMeta } from '@openmrs/esm-framework';
6
6
  import { launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib';
7
7
  import { dashboardPath } from '../../constants';
8
8
  import styles from './dashboard-view.scss';
@@ -43,7 +43,7 @@ export function DashboardView({ dashboard, patientUuid, patient }: DashboardView
43
43
  basePath: view,
44
44
  patient,
45
45
  patientUuid,
46
- launchWorkspace2,
46
+ launchWorkspace,
47
47
  launchStartVisitPrompt,
48
48
  }),
49
49
  [patient, patientUuid, view],
@@ -12,6 +12,11 @@
12
12
  grid-auto-rows: auto;
13
13
  grid-gap: 1.3125rem;
14
14
  margin: 1.3125rem;
15
+ // Hide empty extension wrappers when components return null
16
+ // This prevents empty space in the grid when supplementary widgets have no data
17
+ > :has([data-extension-id='immunization-detailed-history-card']:empty) {
18
+ display: none;
19
+ }
15
20
  }
16
21
 
17
22
  // See https://zpl.io/lrlmdq0 for the Visits dashboard design
@@ -1,34 +1,21 @@
1
- import { ExtensionSlot, useWorkspaces, useLeftNav } from '@openmrs/esm-framework';
2
- import classNames from 'classnames';
3
1
  import React, { useMemo, useState } from 'react';
2
+ import classNames from 'classnames';
4
3
  import { useParams } from 'react-router-dom';
4
+ import { ExtensionSlot, WorkspaceContainer, useWorkspaces, useLeftNav } from '@openmrs/esm-framework';
5
5
  import { spaBasePath } from '../constants';
6
- import Loader from '../loader/loader.component';
6
+ import { usePatientChartPatientAndVisit } from './patient-chart.resources';
7
+ import { type LayoutMode } from './chart-review/dashboard-view.component';
7
8
  import ChartReview from '../patient-chart/chart-review/chart-review.component';
9
+ import Loader from '../loader/loader.component';
8
10
  import SideMenuPanel from '../side-nav/side-menu.component';
9
- import { type LayoutMode } from './chart-review/dashboard-view.component';
10
11
  import styles from './patient-chart.scss';
11
- import { usePatientChartPatientAndVisit } from './patient-chart.resources';
12
12
 
13
13
  const PatientChart: React.FC = () => {
14
14
  const { patientUuid, view: encodedView } = useParams();
15
-
16
- // specify key to ensure that WrapPatientChart instance is re-created
17
- // when we switch patient
18
- return <WrappedPatientChart key={patientUuid} patientUuid={patientUuid} encodedView={encodedView} />;
19
- };
20
-
21
- interface WrappedPatientChartProps {
22
- patientUuid: string;
23
- encodedView: string;
24
- }
25
-
26
- const WrappedPatientChart: React.FC<WrappedPatientChartProps> = ({ patientUuid, encodedView }) => {
27
- const view = decodeURIComponent(encodedView);
28
15
  const { workspaceWindowState, active } = useWorkspaces();
29
16
  const [layoutMode, setLayoutMode] = useState<LayoutMode>();
30
17
  const state = usePatientChartPatientAndVisit(patientUuid);
31
- const { isLoadingPatient, patient } = state;
18
+ const view = decodeURIComponent(encodedView);
32
19
 
33
20
  const leftNavBasePath = useMemo(() => spaBasePath.replace(':patientUuid', patientUuid), [patientUuid]);
34
21
 
@@ -38,39 +25,43 @@ const WrappedPatientChart: React.FC<WrappedPatientChartProps> = ({ patientUuid,
38
25
  <>
39
26
  <SideMenuPanel />
40
27
  <main className={classNames('omrs-main-content', styles.chartContainer)}>
41
- <>
42
- <div
43
- className={classNames(
44
- styles.innerChartContainer,
45
- workspaceWindowState === 'normal' && active ? styles.closeWorkspace : styles.activeWorkspace,
46
- )}
47
- >
48
- {isLoadingPatient ? (
49
- <Loader />
50
- ) : (
51
- <>
52
- <aside>
53
- <ExtensionSlot name="patient-header-slot" state={state} />
54
- <ExtensionSlot name="patient-highlights-bar-slot" state={state} />
55
- <ExtensionSlot name="patient-info-slot" state={state} />
56
- </aside>
57
- <div className={styles.grid}>
58
- <div
59
- className={classNames(styles.chartReview, { [styles.widthContained]: layoutMode == 'contained' })}
60
- >
61
- <ChartReview
62
- patient={patient}
63
- patientUuid={patientUuid}
64
- view={view}
65
- setDashboardLayoutMode={setLayoutMode}
66
- />
67
- </div>
28
+ <div
29
+ className={classNames(
30
+ styles.innerChartContainer,
31
+ workspaceWindowState === 'normal' && active ? styles.closeWorkspace : styles.activeWorkspace,
32
+ )}
33
+ >
34
+ {state.isLoadingPatient ? (
35
+ <Loader />
36
+ ) : (
37
+ <>
38
+ <aside>
39
+ <ExtensionSlot name="patient-header-slot" state={state} />
40
+ <ExtensionSlot name="patient-highlights-bar-slot" state={state} />
41
+ <ExtensionSlot name="patient-info-slot" state={state} />
42
+ </aside>
43
+ <div className={styles.grid}>
44
+ <div
45
+ className={classNames(styles.chartReview, { [styles.widthContained]: layoutMode === 'contained' })}
46
+ >
47
+ <ChartReview
48
+ patient={state.patient}
49
+ patientUuid={state.patientUuid}
50
+ view={view}
51
+ setDashboardLayoutMode={setLayoutMode}
52
+ />
68
53
  </div>
69
- </>
70
- )}
71
- </div>
72
- </>
54
+ </div>
55
+ </>
56
+ )}
57
+ </div>
73
58
  </main>
59
+ <WorkspaceContainer
60
+ actionMenuProps={state}
61
+ additionalWorkspaceProps={state}
62
+ contextKey={`patient/${patientUuid}`}
63
+ showSiderailAndBottomNav
64
+ />
74
65
  </>
75
66
  );
76
67
  };
@@ -1,15 +1,7 @@
1
- import { useEffect, useMemo, useRef } from 'react';
1
+ import { useEffect, useMemo } from 'react';
2
2
  import useSWR from 'swr';
3
- import {
4
- closeWorkspaceGroup2,
5
- launchWorkspaceGroup2,
6
- openmrsFetch,
7
- restBaseUrl,
8
- usePatient,
9
- useVisit,
10
- type Visit,
11
- } from '@openmrs/esm-framework';
12
- import { type PatientWorkspaceGroupProps, usePatientChartStore } from '@openmrs/esm-patient-common-lib';
3
+ import { openmrsFetch, restBaseUrl, usePatient, useVisit, type Visit } from '@openmrs/esm-framework';
4
+ import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
13
5
 
14
6
  const defaultVisitCustomRepresentation =
15
7
  'custom:(uuid,display,voided,indication,startDatetime,stopDatetime,' +
@@ -40,7 +32,7 @@ export function useVisitByUuid(visitUuid: string | null, representation: string
40
32
  * in‑app navigation. On a full page reload, visitContext is rehydrated by refetching
41
33
  * (via useVisit/useVisitByUuId) rather than restored from storage.
42
34
  * When we enter the chart, we want to update the visit context as follows:
43
- * does the the stored visitContext exist and belong to the patient?
35
+ * does the stored visitContext exist and belong to the patient?
44
36
  * 1. If so, the visitContext should be valid but possibly stale; fetch the visit again
45
37
  * and update the context
46
38
  * 2. If not, fetch the active visit of the patient, If it exists, set it as the
@@ -58,7 +50,7 @@ export function usePatientChartPatientAndVisit(patientUuid: string) {
58
50
  setVisitContext,
59
51
  } = usePatientChartStore(patientUuid);
60
52
 
61
- const isVisitContextValid = visitContext && visitContext.patient.uuid === patientUuid;
53
+ const isVisitContextValid = visitContext && visitContext.patient?.uuid === patientUuid;
62
54
  const {
63
55
  visit: newVisitContext,
64
56
  mutate: newMutateVisitContext,
@@ -70,39 +62,14 @@ export function usePatientChartPatientAndVisit(patientUuid: string) {
70
62
  mutate: mutateActiveVisit,
71
63
  } = useVisit(isVisitContextValid ? null : patientUuid);
72
64
 
73
- const isWorkspaceGroupLaunched = useRef(false);
74
-
75
65
  useEffect(() => {
76
- if (!isValidatingVisitContext && !isValidatingActiveVisit && patient) {
77
- let groupProps: PatientWorkspaceGroupProps = null;
66
+ if (!isValidatingVisitContext && !isValidatingActiveVisit && storePatientUuid) {
78
67
  if (activeVisit) {
79
- groupProps = {
80
- patientUuid: patient.id,
81
- patient,
82
- visitContext: activeVisit,
83
- mutateVisitContext: mutateActiveVisit,
84
- };
68
+ setVisitContext(activeVisit, mutateActiveVisit);
85
69
  } else if (newVisitContext) {
86
- groupProps = {
87
- patientUuid: patient.id,
88
- patient,
89
- visitContext: newVisitContext,
90
- mutateVisitContext: newMutateVisitContext,
91
- };
70
+ setVisitContext(newVisitContext, newMutateVisitContext);
92
71
  } else {
93
- groupProps = {
94
- patientUuid: patient.id,
95
- patient,
96
- visitContext: null,
97
- mutateVisitContext: null,
98
- };
99
- }
100
-
101
- setVisitContext(groupProps.visitContext, groupProps.mutateVisitContext);
102
-
103
- if (!isWorkspaceGroupLaunched.current) {
104
- launchWorkspaceGroup2('patient-chart', groupProps);
105
- isWorkspaceGroupLaunched.current = true;
72
+ setVisitContext(null, null);
106
73
  }
107
74
  }
108
75
  }, [
@@ -114,8 +81,6 @@ export function usePatientChartPatientAndVisit(patientUuid: string) {
114
81
  isValidatingActiveVisit,
115
82
  storePatientUuid,
116
83
  mutateActiveVisit,
117
- isWorkspaceGroupLaunched,
118
- patient,
119
84
  ]);
120
85
 
121
86
  useEffect(() => {
@@ -128,12 +93,6 @@ export function usePatientChartPatientAndVisit(patientUuid: string) {
128
93
  };
129
94
  }, [patient, setPatient, isLoadingPatient]);
130
95
 
131
- useEffect(() => {
132
- return () => {
133
- closeWorkspaceGroup2();
134
- };
135
- }, []);
136
-
137
96
  const state = useMemo(
138
97
  () => ({
139
98
  patientUuid,
@@ -141,9 +100,8 @@ export function usePatientChartPatientAndVisit(patientUuid: string) {
141
100
  visitContext,
142
101
  mutateVisitContext,
143
102
  isLoadingPatient,
144
- setPatient,
145
103
  }),
146
- [patient, patientUuid, visitContext, mutateVisitContext, isLoadingPatient, setPatient],
104
+ [patient, patientUuid, visitContext, mutateVisitContext, isLoadingPatient],
147
105
  );
148
106
 
149
107
  return state;