@digihmis/esm-patient-registration-app 1.0.0

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 (146) hide show
  1. package/README.md +4 -0
  2. package/dist/117.js +1 -0
  3. package/dist/117.js.map +1 -0
  4. package/dist/131.js +1 -0
  5. package/dist/131.js.map +1 -0
  6. package/dist/132.js +1 -0
  7. package/dist/132.js.map +1 -0
  8. package/dist/152.js +1 -0
  9. package/dist/152.js.map +1 -0
  10. package/dist/209.js +1 -0
  11. package/dist/209.js.map +1 -0
  12. package/dist/247.js +1 -0
  13. package/dist/247.js.map +1 -0
  14. package/dist/28.js +1 -0
  15. package/dist/28.js.map +1 -0
  16. package/dist/317.js +1 -0
  17. package/dist/317.js.map +1 -0
  18. package/dist/349.js +1 -0
  19. package/dist/349.js.map +1 -0
  20. package/dist/371.js +1 -0
  21. package/dist/371.js.map +1 -0
  22. package/dist/378.js +1 -0
  23. package/dist/378.js.map +1 -0
  24. package/dist/442.js +1 -0
  25. package/dist/442.js.map +1 -0
  26. package/dist/45.js +1 -0
  27. package/dist/45.js.map +1 -0
  28. package/dist/459.js +12 -0
  29. package/dist/459.js.map +1 -0
  30. package/dist/466.js +1 -0
  31. package/dist/466.js.map +1 -0
  32. package/dist/508.js +1 -0
  33. package/dist/508.js.map +1 -0
  34. package/dist/61.js +1 -0
  35. package/dist/61.js.map +1 -0
  36. package/dist/640.js +1 -0
  37. package/dist/640.js.map +1 -0
  38. package/dist/689.js +1 -0
  39. package/dist/689.js.map +1 -0
  40. package/dist/709.js +1 -0
  41. package/dist/709.js.map +1 -0
  42. package/dist/712.js +1 -0
  43. package/dist/712.js.map +1 -0
  44. package/dist/720.js +1 -0
  45. package/dist/720.js.map +1 -0
  46. package/dist/742.js +1 -0
  47. package/dist/742.js.map +1 -0
  48. package/dist/771.js +1 -0
  49. package/dist/771.js.map +1 -0
  50. package/dist/806.js +1 -0
  51. package/dist/806.js.map +1 -0
  52. package/dist/912.js +1 -0
  53. package/dist/912.js.map +1 -0
  54. package/dist/913.js +1 -0
  55. package/dist/913.js.map +1 -0
  56. package/dist/940.js +1 -0
  57. package/dist/940.js.map +1 -0
  58. package/dist/957.js +15 -0
  59. package/dist/957.js.map +1 -0
  60. package/dist/digihmis-esm-patient-registration-app.js +6 -0
  61. package/dist/digihmis-esm-patient-registration-app.js.buildmanifest.json +827 -0
  62. package/dist/digihmis-esm-patient-registration-app.js.map +1 -0
  63. package/dist/main.js +17 -0
  64. package/dist/main.js.map +1 -0
  65. package/dist/routes.json +1 -0
  66. package/jest.config.js +26 -0
  67. package/package.json +51 -0
  68. package/rspack.config.js +1 -0
  69. package/src/active-visit-table/active-visit-table.component.tsx +240 -0
  70. package/src/active-visit-table/active-visits.resource.ts +338 -0
  71. package/src/active-visit-table/active-visits.scss +211 -0
  72. package/src/active-visit-table/empty-data-illustration.component.tsx +39 -0
  73. package/src/checkin-form/base-visit-type.component.tsx +102 -0
  74. package/src/checkin-form/base-visit-type.scss +75 -0
  75. package/src/checkin-form/checkin-form.scss +167 -0
  76. package/src/checkin-form/checkin-form.workspace.tsx +703 -0
  77. package/src/checkin-form/hooks/useDefaultFacilityLocation.ts +15 -0
  78. package/src/checkin-form/hooks/useDefaultVisitLocation.ts +24 -0
  79. package/src/checkin-form/hooks/useOfflineVisitType.ts +18 -0
  80. package/src/checkin-form/hooks/usePatientHistory.ts +40 -0
  81. package/src/checkin-form/hooks/useRecommendedVisitTypes.ts +34 -0
  82. package/src/checkin-form/hooks/useVisitAttributeType.ts +102 -0
  83. package/src/checkin-form/location-selector.component.tsx +86 -0
  84. package/src/checkin-form/recommended-visit-type.component.tsx +32 -0
  85. package/src/checkin-form/visit-attribute-type.component.tsx +256 -0
  86. package/src/checkin-form/visit-attribute-type.scss +5 -0
  87. package/src/checkin-form/visit-date-time.component.tsx +201 -0
  88. package/src/checkin-form/visit-form.resource.ts +308 -0
  89. package/src/components/custom-card/compact-patient-banner.component.tsx +70 -0
  90. package/src/components/custom-card/compact-patient-banner.scss +34 -0
  91. package/src/components/custom-card/custom-loader.component.tsx +22 -0
  92. package/src/components/custom-card/patient-search-context.ts +30 -0
  93. package/src/components/custom-input/autosuggest/autosuggest.component.tsx +213 -0
  94. package/src/components/custom-input/autosuggest/autosuggest.scss +158 -0
  95. package/src/components/custom-input/autosuggest/autosuggest.test.tsx +168 -0
  96. package/src/components/custom-patient-banner/custom-patient-banner.component.tsx +39 -0
  97. package/src/components/custom-patient-banner/custom-patient-banner.resource.ts +32 -0
  98. package/src/components/custom-patient-banner/custom-patient-banner.scss +26 -0
  99. package/src/config-schema.ts +511 -0
  100. package/src/constant.ts +6 -0
  101. package/src/constants/index.ts +1 -0
  102. package/src/dashboard.meta.ts +8 -0
  103. package/src/declarations.d.ts +6 -0
  104. package/src/empty-svg/empty-svg.component.tsx +565 -0
  105. package/src/icon-wrapper/icon-wrapper.extension.tsx +17 -0
  106. package/src/identifier-workspace/identifier-configure-form.workspace.tsx +278 -0
  107. package/src/identifier-workspace/identifier-configure.scss +106 -0
  108. package/src/identifier-workspace/identifier.resource.ts +90 -0
  109. package/src/index.ts +33 -0
  110. package/src/left-panel/left-panel.component.tsx +54 -0
  111. package/src/left-panel/left-panel.scss +13 -0
  112. package/src/page-header/page-header.component.tsx +35 -0
  113. package/src/page-header/page-header.scss +76 -0
  114. package/src/patient-tags/deceased-patient-tag.extension.tsx +41 -0
  115. package/src/patient-tags/deceased-patient-tag.scss +15 -0
  116. package/src/patient-tags/patient-tags.resource.ts +30 -0
  117. package/src/patient-tags/visit-tag.extension.tsx +43 -0
  118. package/src/patient-tags/visit-tag.scss +19 -0
  119. package/src/registration-form/form-manager.ts +449 -0
  120. package/src/registration-form/registration-form.component.tsx +1565 -0
  121. package/src/registration-form/registration-form.resource.ts +264 -0
  122. package/src/registration-form/registration-form.scss +282 -0
  123. package/src/routes.json +62 -0
  124. package/src/search-bar/search-bar.component.tsx +285 -0
  125. package/src/search-bar/search-bar.resource.ts +274 -0
  126. package/src/search-bar/search-bar.scss +427 -0
  127. package/src/search-dashboard/search-dashboard.component.tsx +17 -0
  128. package/src/search-dashboard/search-dashboard.scss +13 -0
  129. package/src/setup-tests.ts +1 -0
  130. package/src/type/index.ts +463 -0
  131. package/src/utils/fhir-mapper.ts +73 -0
  132. package/src/visit-summary/visit-summary.component.tsx +32 -0
  133. package/src/visit-summary/visit-summary.resource.ts +56 -0
  134. package/src/visit-summary/visit-summary.scss +74 -0
  135. package/translations/am.json +112 -0
  136. package/translations/ar.json +120 -0
  137. package/translations/en.json +112 -0
  138. package/translations/es.json +114 -0
  139. package/translations/fr.json +114 -0
  140. package/translations/he.json +114 -0
  141. package/translations/km.json +111 -0
  142. package/translations/pt.json +114 -0
  143. package/translations/sw.json +112 -0
  144. package/translations/vi.json +111 -0
  145. package/translations/zh.json +111 -0
  146. package/tsconfig.json +5 -0
@@ -0,0 +1,463 @@
1
+ import { type Visit, type OpenmrsResource, type Session } from '@openmrs/esm-framework';
2
+ import { type RegistrationConfig } from '../config-schema';
3
+ import { SavePatientTransactionManager } from '../registration-form/form-manager';
4
+
5
+ interface NameValue {
6
+ uuid: string;
7
+ preferred: boolean;
8
+ givenName: string;
9
+ middleName: string;
10
+ familyName: string;
11
+ }
12
+
13
+ export interface AttributeValue {
14
+ attributeType: string;
15
+ value: string;
16
+ }
17
+
18
+ /**
19
+ * Patient Identifier data as it is fetched and composed from the APIs.
20
+ */
21
+ export interface FetchedPatientIdentifierType {
22
+ name: string;
23
+ required: boolean;
24
+ uuid: string;
25
+ fieldName: string;
26
+ format: string;
27
+ formatDescription?: string;
28
+ isPrimary: boolean;
29
+ /** See: https://github.com/openmrs/openmrs-core/blob/e3fb1ac0a052aeff0f957a150731757dd319693b/api/src/main/java/org/openmrs/PatientIdentifierType.java#L41 */
30
+ uniquenessBehavior: undefined | null | 'UNIQUE' | 'NON_UNIQUE' | 'LOCATION';
31
+ }
32
+
33
+ export interface PatientIdentifierValue {
34
+ identifierUuid?: string;
35
+ identifierTypeUuid: string;
36
+ initialValue: string;
37
+ identifierValue: string;
38
+ identifierName: string;
39
+ selectedSource: IdentifierSource;
40
+ autoGeneration?: boolean;
41
+ preferred: boolean;
42
+ required: boolean;
43
+ }
44
+
45
+ /**
46
+ * Extends the `FetchedPatientIdentifierType` with aggregated data.
47
+ */
48
+ export interface PatientIdentifierType extends FetchedPatientIdentifierType {
49
+ identifierSources: Array<IdentifierSource>;
50
+ autoGenerationSource?: IdentifierSource;
51
+ checked?: boolean;
52
+ source?: IdentifierSource;
53
+ }
54
+
55
+ export interface IdentifierSource {
56
+ uuid: string;
57
+ name: string;
58
+ autoGenerationOption?: IdentifierSourceAutoGenerationOption;
59
+ }
60
+
61
+ export interface IdentifierSourceAutoGenerationOption {
62
+ manualEntryEnabled: boolean;
63
+ automaticGenerationEnabled: boolean;
64
+ }
65
+
66
+ export interface PatientIdentifier {
67
+ uuid?: string;
68
+ identifier: string;
69
+ identifierType?: string;
70
+ location?: string;
71
+ preferred?: boolean;
72
+ }
73
+
74
+ export interface PatientRegistration {
75
+ id?: number;
76
+ /**
77
+ * The preliminary patient in the FHIR format.
78
+ */
79
+ fhirPatient: fhir.Patient;
80
+ /**
81
+ * Internal data collected by patient-registration. Required for later syncing and editing.
82
+ * Not supposed to be used outside of this module.
83
+ */
84
+ _patientRegistrationData: {
85
+ isNewPatient: boolean;
86
+ formValues: FormValues;
87
+ patientUuidMap: PatientUuidMapType;
88
+ initialAddressFieldValues: Record<string, any>;
89
+ capturePhotoProps: CapturePhotoProps;
90
+ currentLocation: string;
91
+ initialIdentifierValues: FormValues['identifiers'];
92
+ currentUser: Session;
93
+ config: RegistrationConfig;
94
+ savePatientTransactionManager: SavePatientTransactionManager;
95
+ };
96
+ }
97
+
98
+ export type Relationship = {
99
+ relationshipType: string;
100
+ personA: string;
101
+ personB: string;
102
+ };
103
+
104
+ export type Patient = {
105
+ uuid: string;
106
+ identifiers: Array<PatientIdentifier>;
107
+ person: {
108
+ uuid: string;
109
+ names: Array<NameValue>;
110
+ gender: string;
111
+ birthdate: string;
112
+ birthdateEstimated: boolean;
113
+ attributes: Array<AttributeValue>;
114
+ addresses: Array<Record<string, string>>;
115
+ dead: boolean;
116
+ deathDate?: string;
117
+ causeOfDeath?: string;
118
+ };
119
+ };
120
+
121
+ export interface Encounter {
122
+ encounterDatetime?: Date;
123
+ patient: string;
124
+ encounterType: string;
125
+ location: string;
126
+ encounterProviders: Array<{
127
+ provider: string;
128
+ encounterRole: string;
129
+ }>;
130
+ form: string;
131
+ obs: Array<{
132
+ concept: string | OpenmrsResource;
133
+ value: string | number | OpenmrsResource;
134
+ }>;
135
+ }
136
+
137
+ export interface RelationshipValue {
138
+ relatedPersonName?: string;
139
+ relatedPersonUuid: string;
140
+ relation?: string;
141
+ relationshipType: string;
142
+ /**
143
+ * Defines the action to be taken on the existing relationship
144
+ * @kind ADD -> adds a new relationship
145
+ * @kind UPDATE -> updates an existing relationship
146
+ * @kind DELETE -> deletes an existing relationship
147
+ * @kind undefined -> no operation on the existing relationship
148
+ */
149
+ action?: 'ADD' | 'UPDATE' | 'DELETE';
150
+ /**
151
+ * Value kept for restoring initial relationshipType value
152
+ */
153
+ initialrelationshipTypeValue?: string;
154
+ uuid?: string;
155
+ }
156
+
157
+ export interface FormValues {
158
+ additionalFamilyName: string;
159
+ additionalGivenName: string;
160
+ additionalMiddleName: string;
161
+ addNameInLocalLanguage: boolean;
162
+ address: {
163
+ [addressField: string]: string;
164
+ };
165
+ attributes?: {
166
+ [attributeTypeUuid: string]: string;
167
+ };
168
+ birthdate: Date | string;
169
+ birthdateEstimated: boolean;
170
+ deathCause: string;
171
+ deathDate: string | Date;
172
+ deathTime: string;
173
+ deathTimeFormat: 'AM' | 'PM';
174
+ familyName: string;
175
+ gender: string;
176
+ givenName: string;
177
+ identifiers: {
178
+ [identifierFieldName: string]: PatientIdentifierValue;
179
+ };
180
+ isDead: boolean;
181
+ middleName: string;
182
+ monthsEstimated: number;
183
+ nonCodedCauseOfDeath: string;
184
+ obs?: {
185
+ [conceptUuid: string]: string;
186
+ };
187
+ patientUuid: string;
188
+ relationships: Array<RelationshipValue>;
189
+ telephoneNumber: string;
190
+ yearsEstimated: number;
191
+ observation?: ObsResponse;
192
+ concepts?: Array<ConceptAnswers>;
193
+ token?: string;
194
+ }
195
+
196
+ export interface PatientUuidMapType {
197
+ additionalNameUuid?: string;
198
+ preferredNameUuid?: string;
199
+ preferredAddressUuid?: string;
200
+ }
201
+
202
+ export interface CapturePhotoProps {
203
+ imageData: string;
204
+ dateTime: string;
205
+ }
206
+
207
+ export interface AddressValidationSchemaType {
208
+ name: string;
209
+ label: string;
210
+ regex: RegExp;
211
+ regexFormat: string;
212
+ }
213
+
214
+ export interface CodedPersonAttributeConfig {
215
+ personAttributeUuid: string;
216
+ conceptUuid: string;
217
+ }
218
+
219
+ export interface TextBasedPersonAttributeConfig {
220
+ personAttributeUuid: string;
221
+ validationRegex: string;
222
+ }
223
+
224
+ export interface PatientIdentifierResponse {
225
+ uuid: string;
226
+ identifier: string;
227
+ preferred: boolean;
228
+ identifierType: {
229
+ uuid: string;
230
+ required: boolean;
231
+ name: string;
232
+ };
233
+ }
234
+
235
+ export interface PersonAttributeTypeResponse {
236
+ uuid: string;
237
+ display: string;
238
+ name: string;
239
+ description: string;
240
+ format: string;
241
+ }
242
+
243
+ export interface PersonAttributeResponse {
244
+ display: string;
245
+ uuid: string;
246
+ value:
247
+ | string
248
+ | {
249
+ uuid: string;
250
+ display: string;
251
+ };
252
+ attributeType: {
253
+ display: string;
254
+ uuid: string;
255
+ format: 'org.openmrs.Concept' | string;
256
+ };
257
+ }
258
+
259
+ export interface ConceptResponse {
260
+ uuid: string;
261
+ display: string;
262
+ datatype: {
263
+ uuid: string;
264
+ display: string;
265
+ };
266
+ answers: Array<ConceptAnswers>;
267
+ setMembers: Array<ConceptAnswers>;
268
+ }
269
+
270
+ export interface ConceptAnswers {
271
+ display: string;
272
+ uuid: string;
273
+ }
274
+
275
+ export type AddressProperties =
276
+ | 'cityVillage'
277
+ | 'stateProvince'
278
+ | 'countyDistrict'
279
+ | 'postalCode'
280
+ | 'country'
281
+ | 'address1'
282
+ | 'address2'
283
+ | 'address3'
284
+ | 'address4'
285
+ | 'address5'
286
+ | 'address6'
287
+ | 'address7'
288
+ | 'address8'
289
+ | 'address9'
290
+ | 'address10'
291
+ | 'address11'
292
+ | 'address12'
293
+ | 'address13'
294
+ | 'address14'
295
+ | 'address15';
296
+
297
+ export type ExtensibleAddressProperties = { [p in AddressProperties]?: string } | null;
298
+
299
+ export interface AddressTemplate {
300
+ displayName: string | null;
301
+ codeName: string | null;
302
+ country: string | null;
303
+ lines: Array<
304
+ Array<{
305
+ isToken: 'IS_NOT_ADDR_TOKEN' | 'IS_ADDR_TOKEN';
306
+ displayText: string;
307
+ codeName?: AddressProperties;
308
+ displaySize?: string;
309
+ }>
310
+ > | null;
311
+ lineByLineFormat: Array<string> | null;
312
+ nameMappings: ExtensibleAddressProperties;
313
+ sizeMappings: ExtensibleAddressProperties;
314
+ elementDefaults: ExtensibleAddressProperties;
315
+ elementRegex: ExtensibleAddressProperties;
316
+ elementRegexFormats: ExtensibleAddressProperties;
317
+ requiredElements: Array<AddressProperties> | null;
318
+ }
319
+
320
+ // https://rest.openmrs.org/#address-template
321
+ export interface RestAddressTemplate {
322
+ uuid: string;
323
+ description: string;
324
+ property: string;
325
+ display: string;
326
+ value: string;
327
+ }
328
+
329
+ export interface ObsResponse {
330
+ results: Array<{ obs: Array<{ uuid: string; display: string; value: OpenmrsResource; concept: OpenmrsResource }> }>;
331
+ }
332
+
333
+ export interface SearchedPatient {
334
+ uuid: string;
335
+ identifiers: Array<Identifier>;
336
+ person: {
337
+ addresses: Array<Address>;
338
+ age: number;
339
+ birthdate: string;
340
+ gender: string;
341
+ dead: boolean;
342
+ deathDate: string | null;
343
+ personName: {
344
+ display: string;
345
+ givenName: string;
346
+ familyName: string;
347
+ middleName: string;
348
+ };
349
+ };
350
+ attributes: Array<{
351
+ value: OpenmrsResource | string;
352
+ attributeType: { uuid: string; display: string };
353
+ }>;
354
+ }
355
+
356
+ export interface Identifier {
357
+ display: string;
358
+ identifier: string;
359
+ identifierType: OpenmrsResource;
360
+ location: OpenmrsResource;
361
+ uuid: string;
362
+ preferred: boolean;
363
+ }
364
+
365
+ export interface Address {
366
+ preferred: boolean;
367
+ voided: boolean;
368
+ address1: string;
369
+ cityVillage: string;
370
+ country: string;
371
+ postalCode: string;
372
+ stateProvince: string;
373
+ }
374
+
375
+ export interface User {
376
+ uuid: string;
377
+ userProperties: {
378
+ [x: string]: string;
379
+ patientsVisited: string;
380
+ defaultLocation: string;
381
+ };
382
+ }
383
+
384
+ export interface PatientSearchResponse {
385
+ currentPage: number;
386
+ data?: Array<SearchedPatient>;
387
+ fetchError: Error;
388
+ hasMore: boolean;
389
+ isLoading: boolean;
390
+ isValidating: boolean;
391
+ setPage: (page: number | ((_page: number) => number)) => Promise<unknown[] | undefined>;
392
+ totalResults: number;
393
+ }
394
+
395
+ export interface Resources {
396
+ addressTemplate: AddressTemplate;
397
+ currentSession: Session;
398
+ relationshipTypes: any;
399
+ identifierTypes: Array<PatientIdentifierType>;
400
+ }
401
+
402
+ export interface OpenMRSPatient {
403
+ person: {
404
+ personName: {
405
+ display: string;
406
+ };
407
+ gender: string;
408
+ dead: boolean;
409
+ birthdate: string;
410
+ };
411
+ patientIdentifier: {
412
+ identifier: string;
413
+ identifierType: {
414
+ display: string;
415
+ };
416
+ };
417
+ }
418
+
419
+ export interface ActiveVisit {
420
+ age: string;
421
+ id: string;
422
+ idNumber: string;
423
+ gender: string;
424
+ location: string;
425
+ name: string;
426
+ patientUuid: string;
427
+ visitStartTime: string;
428
+ visitType: string;
429
+ visitUuid: string;
430
+ observations?: Record<string, Observation[]>;
431
+ [identifier: string]: string | Record<string, Observation[]>;
432
+ }
433
+ export interface Observation {
434
+ uuid: string;
435
+ concept: {
436
+ uuid: string;
437
+ display: string;
438
+ conceptClass: {
439
+ uuid: string;
440
+ display: string;
441
+ };
442
+ };
443
+ display: string;
444
+ groupMembers: null | Array<{
445
+ uuid: string;
446
+ concept: {
447
+ uuid: string;
448
+ display: string;
449
+ };
450
+ value: {
451
+ uuid: string;
452
+ display: string;
453
+ };
454
+ }>;
455
+ value: any;
456
+ obsDatetime: string;
457
+ }
458
+
459
+ export interface VisitResponse {
460
+ results: Array<Visit>;
461
+ links: Array<{ rel: 'prev' | 'next' }>;
462
+ totalCount: number;
463
+ }
@@ -0,0 +1,73 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { SearchedPatient } from '../type';
3
+
4
+ const getGender = (gender: string) => {
5
+ switch (gender) {
6
+ case 'M':
7
+ return 'male';
8
+ case 'F':
9
+ return 'female';
10
+ case 'O':
11
+ return 'other';
12
+ case 'U':
13
+ return 'unknown';
14
+ default:
15
+ return gender;
16
+ }
17
+ };
18
+
19
+ export function mapToFhirPatient(patient: SearchedPatient) {
20
+ const preferredAddress = patient?.person?.addresses?.find((address) => address.preferred);
21
+ const addressId = uuidv4();
22
+ const nameId = uuidv4();
23
+
24
+ return {
25
+ resourceType: 'Patient',
26
+ address: preferredAddress
27
+ ? [
28
+ {
29
+ id: addressId,
30
+ city: preferredAddress.cityVillage,
31
+ country: preferredAddress.country,
32
+ postalCode: preferredAddress.postalCode,
33
+ state: preferredAddress.stateProvince,
34
+ use: 'home',
35
+ },
36
+ ]
37
+ : [],
38
+ birthDate: patient?.person?.birthdate,
39
+ deceasedBoolean: patient?.person?.dead,
40
+ deceasedDateTime: patient?.person?.deathDate ?? undefined,
41
+ gender: getGender(patient?.person?.gender),
42
+ id: patient?.uuid,
43
+ identifier: patient?.identifiers?.map((identifier) => ({
44
+ id: identifier?.uuid,
45
+ type: {
46
+ coding: [
47
+ {
48
+ code: identifier.identifierType.uuid,
49
+ },
50
+ ],
51
+ text: identifier.identifierType.display,
52
+ },
53
+ use: 'official',
54
+ value: identifier.identifier,
55
+ })),
56
+ name: [
57
+ {
58
+ id: nameId,
59
+ given: [patient?.person?.personName.givenName, patient.person?.personName?.middleName],
60
+ family: patient?.person?.personName.familyName,
61
+ text: patient?.person?.personName.display,
62
+ },
63
+ ],
64
+ telecom:
65
+ patient.attributes
66
+ ?.filter((attribute) => attribute?.attributeType.display === 'Telephone Number')
67
+ ?.map((phone) => ({
68
+ system: 'phone',
69
+ value: phone.value.toString(),
70
+ use: 'mobile',
71
+ })) ?? [],
72
+ };
73
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { Tile } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import styles from './visit-summary.scss';
5
+ import useActiveVisits, { useAppointmentsData, useTotalVisits } from './visit-summary.resource';
6
+
7
+ const VisitSummaryTile: React.FC = () => {
8
+ const { count } = useActiveVisits();
9
+ const { data: visitsData } = useTotalVisits();
10
+ const { data: appointmentsData } = useAppointmentsData();
11
+
12
+ const { t } = useTranslation();
13
+
14
+ return (
15
+ <Tile className={styles.tileContainer}>
16
+ <div className={styles.displayDetails}>
17
+ <div className={styles.countLabel}>{t('activeVisits', 'Active Visits')}</div>
18
+ <div className={styles.displayData}>{count ?? 0}</div>
19
+ </div>
20
+ <div className={styles.displayDetails}>
21
+ <div className={styles.countLabel}>{t('totalVisits', 'Total Visits Today')}</div>
22
+ <div className={styles.displayData}>{visitsData?.length ?? 0}</div>
23
+ </div>
24
+ <div className={styles.displayDetails}>
25
+ <div className={styles.countLabel}>{t('scheduledForToday', 'Scheduled For Today')}</div>
26
+ <div className={styles.displayData}>{appointmentsData?.length ?? 0}</div>
27
+ </div>
28
+ </Tile>
29
+ );
30
+ };
31
+
32
+ export default VisitSummaryTile;
@@ -0,0 +1,56 @@
1
+ import useSWR from 'swr';
2
+ import { openmrsFetch, restBaseUrl, useSession, Visit } from '@openmrs/esm-framework';
3
+ import dayjs from 'dayjs';
4
+
5
+ export default function useActiveVisits() {
6
+ const session = useSession();
7
+ const sessionLocation = session?.sessionLocation?.uuid;
8
+
9
+ const customRepresentation = 'custom:(uuid,startDatetime,stopDatetime)';
10
+
11
+ const getUrl = () => {
12
+ let url = `${restBaseUrl}/visit?v=${customRepresentation}&`;
13
+ let urlSearchParams = new URLSearchParams();
14
+
15
+ urlSearchParams.append('includeParentLocations', 'true');
16
+ urlSearchParams.append('includeInactive', 'false');
17
+ urlSearchParams.append('totalCount', 'true');
18
+ urlSearchParams.append('location', `${sessionLocation}`);
19
+
20
+ return url + urlSearchParams.toString();
21
+ };
22
+
23
+ const { data, error, isLoading } = useSWR<{ data: { totalCount: number } }>(getUrl, openmrsFetch);
24
+
25
+ return {
26
+ count: data?.data?.totalCount,
27
+ error,
28
+ isLoading,
29
+ };
30
+ }
31
+
32
+ export const useTotalVisits = () => {
33
+ const customRepresentation = 'custom:(uuid,startDatetime,stopDatetime)';
34
+
35
+ const visitsUrl = `${restBaseUrl}/visit?includeInactive=true&v=${customRepresentation}&fromStartDate=${dayjs().format(
36
+ 'YYYY-MM-DD',
37
+ )}`;
38
+
39
+ const { data, error, isLoading } = useSWR<{ data: { results: Array<Visit> } }>(visitsUrl, openmrsFetch);
40
+
41
+ return {
42
+ data: data?.data?.results,
43
+ error,
44
+ isLoading,
45
+ };
46
+ };
47
+
48
+ export const useAppointmentsData = () => {
49
+ const appointmentDate = dayjs(new Date().setHours(0, 0, 0, 0)).toISOString();
50
+
51
+ const url = `${restBaseUrl}/appointment/all?forDate=${appointmentDate}`;
52
+
53
+ const { data, error, isLoading } = useSWR<{ data: Array<any> }>(url, openmrsFetch);
54
+
55
+ return { data: data?.data, error, isLoading };
56
+ };
@@ -0,0 +1,74 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+ @use '@openmrs/esm-styleguide/src/vars' as *;
5
+
6
+ .tileContainer {
7
+ border: 1px solid colors.$cool-gray-30;
8
+ margin-bottom: layout.$spacing-04;
9
+ display: grid;
10
+ grid-template-columns: 1fr 1fr 1fr;
11
+ gap: 0;
12
+ height: 80px;
13
+
14
+ :global(.omrs-breakpoint-lt-desktop) & {
15
+ padding: layout.$spacing-04;
16
+ }
17
+
18
+ :global(.omrs-breakpoint-lt-tablet) & {
19
+ padding: layout.$spacing-03;
20
+ grid-template-columns: 1fr;
21
+ grid-template-rows: auto auto auto;
22
+ gap: layout.$spacing-04;
23
+ }
24
+ }
25
+
26
+ .tileHeader {
27
+ color: colors.$gray-70;
28
+ margin-bottom: layout.$spacing-03;
29
+ text-transform: none;
30
+ letter-spacing: 0;
31
+
32
+ :global(.omrs-breakpoint-lt-tablet) & {
33
+ margin-bottom: layout.$spacing-02;
34
+ }
35
+ }
36
+
37
+ .displayDetails {
38
+ display: flex;
39
+ flex-direction: column;
40
+ align-items: flex-start;
41
+
42
+ :global(.omrs-breakpoint-lt-tablet) & {
43
+ padding: layout.$spacing-03 0;
44
+ border-right: none;
45
+ border-bottom: 1px solid colors.$gray-20;
46
+ padding-bottom: layout.$spacing-04;
47
+ margin-bottom: layout.$spacing-03;
48
+ }
49
+ }
50
+
51
+ .countLabel {
52
+ color: colors.$gray-70;
53
+ margin-bottom: layout.$spacing-02;
54
+ @include type.type-style('caption-01');
55
+ font-weight: bold;
56
+
57
+ :global(.omrs-breakpoint-lt-tablet) & {
58
+ @include type.type-style('caption-01');
59
+ font-weight: bold;
60
+ margin-bottom: layout.$spacing-01;
61
+ }
62
+ }
63
+
64
+ .displayData {
65
+ color: colors.$gray-100;
66
+ font-weight: bold;
67
+ @include type.type-style('caption-01');
68
+ font-size: 25px;
69
+
70
+ :global(.omrs-breakpoint-lt-tablet) & {
71
+ font-size: 2.625rem;
72
+ line-height: 2.75rem;
73
+ }
74
+ }