@kenyaemr/esm-ward-app 8.5.1-pre.37 → 8.5.1-pre.44

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 (55) hide show
  1. package/.turbo/turbo-build.log +12 -12
  2. package/dist/1160.js +1 -0
  3. package/dist/1160.js.map +1 -0
  4. package/dist/1917.js +1 -1
  5. package/dist/1917.js.map +1 -1
  6. package/dist/4224.js +2 -0
  7. package/dist/4224.js.map +1 -0
  8. package/dist/6009.js +1 -0
  9. package/dist/6009.js.map +1 -0
  10. package/dist/6012.js +1 -1
  11. package/dist/6012.js.map +1 -1
  12. package/dist/6871.js +1 -1
  13. package/dist/6871.js.map +1 -1
  14. package/dist/7059.js +2 -0
  15. package/dist/7059.js.map +1 -0
  16. package/dist/7661.js +1 -1
  17. package/dist/7661.js.map +1 -1
  18. package/dist/8205.js +1 -1
  19. package/dist/8205.js.map +1 -1
  20. package/dist/8308.js +1 -0
  21. package/dist/8308.js.map +1 -0
  22. package/dist/9113.js +1 -0
  23. package/dist/{9880.js.map → 9113.js.map} +1 -1
  24. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +162 -111
  25. package/dist/main.js +1 -1
  26. package/dist/main.js.map +1 -1
  27. package/dist/routes.json +1 -1
  28. package/package.json +2 -1
  29. package/src/config-schema.ts +28 -0
  30. package/src/discharge-printouts/discharge-printout.preview-modal.tsx +40 -0
  31. package/src/discharge-printouts/discharge-printout.resource.ts +335 -0
  32. package/src/discharge-printouts/discharge-printouts.scss +125 -0
  33. package/src/discharge-printouts/discharge-summary.tsx +170 -0
  34. package/src/discharge-printouts/field-input.tsx +21 -0
  35. package/src/discharge-printouts/gate-pass-printout.tsx +125 -0
  36. package/src/discharge-printouts/lab-results.tsx +96 -0
  37. package/src/hooks/useIpdDischargeEncounter.ts +10 -8
  38. package/src/index.ts +4 -0
  39. package/src/routes.json +4 -0
  40. package/src/ward-patients/admitted-patients.tsx +2 -2
  41. package/src/ward-patients/discharge-in-patients.tsx +22 -18
  42. package/src/ward-patients/discharge-patients.tsx +23 -3
  43. package/src/ward-patients/patient-cells.tsx +4 -10
  44. package/src/ward-workspace/admit-patient-form-workspace/patient-admission.resources.ts +19 -1
  45. package/src/ward-workspace/kenya-emr-patient-discharge/patient-discharge.resource.tsx +2 -1
  46. package/src/ward-workspace/patient-transfer-bed-swap/patient-admit-or-transfer-request-form.component.tsx +1 -1
  47. package/dist/1352.js +0 -2
  48. package/dist/1352.js.map +0 -1
  49. package/dist/3423.js +0 -1
  50. package/dist/3423.js.map +0 -1
  51. package/dist/4701.js +0 -2
  52. package/dist/4701.js.map +0 -1
  53. package/dist/9880.js +0 -1
  54. /package/dist/{1352.js.LICENSE.txt → 4224.js.LICENSE.txt} +0 -0
  55. /package/dist/{4701.js.LICENSE.txt → 7059.js.LICENSE.txt} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-ward-app",
3
- "version": "8.5.1-pre.37",
3
+ "version": "8.5.1-pre.44",
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",
@@ -42,6 +42,7 @@
42
42
  "classnames": "^2.3.2",
43
43
  "lodash-es": "^4.17.15",
44
44
  "react-hook-form": "^7.54.0",
45
+ "react-to-print": "^2.14.13",
45
46
  "zod": "3.24.1"
46
47
  },
47
48
  "peerDependencies": {
@@ -300,6 +300,11 @@ export const configSchema: ConfigSchema = {
300
300
  emmergencyDoctorPhoneNumber: '163152AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
301
301
  admissionDateTime: '1640AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
302
302
  chiefComplaint: '5219AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
303
+ complaint: '160531AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
304
+ physicalExamination: '162737AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
305
+ dischargeInstruction: '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
306
+ drugReaction: '162747AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
307
+ reactingDrug: '1193AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
303
308
  },
304
309
  },
305
310
  diagnosisConceptSourceUud: {
@@ -350,6 +355,21 @@ export const configSchema: ConfigSchema = {
350
355
  _type: Type.UUID,
351
356
  _default: '37ce491f-b2dd-4433-b203-efebb8ba1469',
352
357
  },
358
+ drugOrderEncounterType: {
359
+ _description: 'Drug Order encounter type Uuid',
360
+ _type: Type.UUID,
361
+ _default: '7df67b83-1b84-4fe2-b1b7-794b4e9bfcc3',
362
+ },
363
+ clinicalConsultationEncounterType: {
364
+ _description: 'Clinical consultation encounter type Uuid',
365
+ _type: Type.UUID,
366
+ _default: '465a92f2-baf8-42e9-9612-53064be868e8',
367
+ },
368
+ doctorsNoteEncounterType: {
369
+ _description: 'Doctors note encounter type Uuid',
370
+ _type: Type.UUID,
371
+ _default: '14b36860-5033-4765-b91b-ace856ab64c2',
372
+ },
353
373
  };
354
374
 
355
375
  export interface WardConfigObject {
@@ -382,11 +402,19 @@ export interface WardConfigObject {
382
402
  emmergencyDoctorPhoneNumber: string;
383
403
  admissionDateTime: string;
384
404
  chiefComplaint: string;
405
+ physicalExamination: string;
406
+ dischargeInstruction: string;
407
+ complaint: string;
408
+ drugReaction: string;
409
+ reactingDrug: string;
385
410
  };
386
411
  diagnosisConceptSourceUud: string;
387
412
  inpatientAdmissionEncounterProviderRole: string;
388
413
  mortuaryAdmissionLoctionTagUuid: string;
389
414
  dailyBedFeeBillableService: string;
415
+ drugOrderEncounterType: string;
416
+ clinicalConsultationEncounterType: string;
417
+ doctorsNoteEncounterType: string;
390
418
  }
391
419
 
392
420
  export interface PendingItemsElementConfig {
@@ -0,0 +1,40 @@
1
+ import { ModalHeader , ModalBody , ModalFooter , ButtonSet , Button } from '@carbon/react';
2
+ import React, { type FC, type ReactNode, useRef } from 'react';
3
+ import styles from './discharge-printouts.scss';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { useReactToPrint } from 'react-to-print';
6
+
7
+ type DischargePrintoutPreviewModalProps = {
8
+ printout: ReactNode;
9
+ onClose: () => void;
10
+ };
11
+ const DischargePrintoutPreviewModal: FC<DischargePrintoutPreviewModalProps> = ({ onClose, printout }) => {
12
+ const { t } = useTranslation();
13
+ const ref = useRef<HTMLDivElement>(null);
14
+
15
+ const handlePrint = useReactToPrint({
16
+ content: () => ref.current,
17
+ });
18
+ return (
19
+ <React.Fragment>
20
+ <ModalHeader className={styles.sectionHeader} closeModal={onClose}>
21
+ {t('printPreview', 'Print Preview')}
22
+ </ModalHeader>
23
+ <ModalBody>
24
+ <div ref={ref}>{printout}</div>
25
+ </ModalBody>
26
+ <ModalFooter>
27
+ <ButtonSet className={styles.buttonSet}>
28
+ <Button kind="secondary" onClick={onClose} className={styles.button}>
29
+ {t('cancel', 'Cancel')}
30
+ </Button>
31
+ <Button kind="primary" onClick={handlePrint} className={styles.button}>
32
+ {t('print', 'Print')}
33
+ </Button>
34
+ </ButtonSet>
35
+ </ModalFooter>
36
+ </React.Fragment>
37
+ );
38
+ };
39
+
40
+ export default DischargePrintoutPreviewModal;
@@ -0,0 +1,335 @@
1
+ import {
2
+ type Encounter,
3
+ type FetchResponse,
4
+ type Obs,
5
+ openmrsFetch,
6
+ type OpenmrsResource,
7
+ restBaseUrl,
8
+ useConfig,
9
+ type Visit,
10
+ } from '@openmrs/esm-framework';
11
+ import { type Order } from '@openmrs/esm-patient-common-lib';
12
+ import { useMemo } from 'react';
13
+ import useSWR from 'swr';
14
+ import { type WardConfigObject } from '../config-schema';
15
+ import { useEncounterDetails } from '../hooks/useIpdDischargeEncounter';
16
+ export const DATE_FORMART = 'DD/MM/YYYY';
17
+ export const TIME_FORMART = 'hh:mm A';
18
+
19
+ const labConceptRepresentation =
20
+ 'custom:(uuid,display,name,datatype,set,answers,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units,allowDecimal,' +
21
+ 'setMembers:(uuid,display,answers,datatype,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units,allowDecimal,set,setMembers:(uuid)))';
22
+
23
+ export const usePatientDiagnosis = (encounterUuid: string) => {
24
+ const customRepresentation =
25
+ 'custom:(uuid,display,visit:(uuid,encounters:(uuid,diagnoses:(uuid,display,certainty,diagnosis:(coded:(uuid,display))))))';
26
+ const url = `${restBaseUrl}/encounter/${encounterUuid}?v=${customRepresentation}`;
27
+
28
+ const { data, error, isLoading } = useSWR<FetchResponse<{ visit: Visit }>>(url, openmrsFetch);
29
+
30
+ const diagnoses = useMemo(() => {
31
+ return (
32
+ data?.data?.visit?.encounters?.flatMap(
33
+ (encounter) =>
34
+ encounter.diagnoses.map((diagnosis) => ({
35
+ id: diagnosis.diagnosis.coded.uuid,
36
+ text: diagnosis.display,
37
+ certainty: diagnosis.certainty,
38
+ })) || [],
39
+ ) || []
40
+ );
41
+ }, [data]);
42
+ const display = useMemo(() => {
43
+ if (diagnoses?.length)
44
+ return diagnoses
45
+ .map((d) => d.text)
46
+ .join(', ')
47
+ ?.toLowerCase();
48
+ return null;
49
+ }, [diagnoses]);
50
+
51
+ return {
52
+ error,
53
+ isLoading,
54
+ diagnoses: (diagnoses ?? []) as Array<{ id: string; text: string; certainty: string }>,
55
+ display,
56
+ };
57
+ };
58
+
59
+ export function usePatientOrders(dischargeEncounterUuId: string) {
60
+ const rep =
61
+ 'custom:(uuid,display,location:(display),encounterDatetime,visit:(uuid,display,encounters:(uuid,display,encounterType:(uuid,display),encounterDatetime,orders,obs)))';
62
+ const { encounter, error, isLoading } = useEncounterDetails(dischargeEncounterUuId, rep);
63
+ const {
64
+ drugOrderEncounterType,
65
+ clinicalConsultationEncounterType,
66
+ ipdDischargeEncounterTypeUuid,
67
+ doctorsNoteEncounterType,
68
+ conceptUuidForWardAdmission: concepts,
69
+ } = useConfig<WardConfigObject>();
70
+
71
+ const orderEncounters = useMemo(() => {
72
+ const encounters = (encounter?.visit?.encounters ?? []).filter(
73
+ (enc) => enc.encounterType.uuid === drugOrderEncounterType,
74
+ );
75
+ return encounters;
76
+ }, [encounter, drugOrderEncounterType]);
77
+
78
+ const clinicalConsultationEncounters = useMemo(() => {
79
+ const encounters = (encounter?.visit?.encounters ?? []).filter(
80
+ (enc) => enc.encounterType.uuid === clinicalConsultationEncounterType,
81
+ );
82
+ return encounters;
83
+ }, [encounter, clinicalConsultationEncounterType]);
84
+
85
+ const doctorsNoteEncounters = useMemo(() => {
86
+ const encounters = (encounter?.visit?.encounters ?? []).filter(
87
+ (enc) => enc.encounterType.uuid === doctorsNoteEncounterType,
88
+ );
89
+ return encounters;
90
+ }, [encounter, doctorsNoteEncounterType]);
91
+
92
+ const { drugorder, testorder } = useMemo<{ drugorder: Array<Order>; testorder: Array<Order> }>(
93
+ () =>
94
+ orderEncounters.reduce(
95
+ (prev, curr) => {
96
+ if (curr.orders?.length) {
97
+ prev['drugorder'].push(...curr.orders.filter((order) => order.type === 'drugorder'));
98
+ prev['testorder'].push(...curr.orders.filter((order) => order.type === 'testorder'));
99
+ }
100
+ return prev;
101
+ },
102
+ { drugorder: [], testorder: [] },
103
+ ),
104
+ [orderEncounters],
105
+ );
106
+
107
+ const complaints = useMemo(() => {
108
+ const obs = getComplaintsObs(doctorsNoteEncounters, concepts.complaint, concepts.chiefComplaint);
109
+ if (obs.length) return obs.map((o) => getObservationDisplayValue(o.value, null)).join(', ');
110
+ return null;
111
+ }, [doctorsNoteEncounters, concepts]);
112
+
113
+ const drugReactions = useMemo(() => {
114
+ const obs = getDrugReactions(clinicalConsultationEncounters, concepts.drugReaction, concepts.reactingDrug);
115
+ if (obs.length)
116
+ return obs
117
+ .map((o) => getObservationDisplayValue(o.value, null))
118
+ .join(', ')
119
+ .toLowerCase();
120
+ return null;
121
+ }, [clinicalConsultationEncounters, concepts]);
122
+
123
+ const ipdDischargeEncounter = useMemo<Encounter>(() => {
124
+ const encounters = (encounter?.visit?.encounters ?? []).find(
125
+ (enc) => enc.encounterType.uuid === ipdDischargeEncounterTypeUuid,
126
+ );
127
+ return encounters;
128
+ }, [encounter, ipdDischargeEncounterTypeUuid]);
129
+
130
+ const physicalExaminations = useMemo(() => {
131
+ const obs = doctorsNoteEncounters.reduce<Array<Obs>>((prev, cur) => {
132
+ if (cur.obs?.length) {
133
+ const obs = cur.obs.filter((o) => o.concept.uuid === concepts.physicalExamination);
134
+ prev.push(...obs);
135
+ }
136
+ return prev;
137
+ }, []);
138
+ return obs?.map((ob) => getObservationDisplayValue(ob.value, null)?.toLowerCase());
139
+ }, [doctorsNoteEncounters, concepts.physicalExamination]);
140
+
141
+ const dischargeinstructions = useMemo(() => {
142
+ const instructionObsValue = ipdDischargeEncounter?.obs?.find(
143
+ (o) => o.concept.uuid === concepts.dischargeInstruction,
144
+ )?.value;
145
+ return getObservationDisplayValue(instructionObsValue, null);
146
+ }, [ipdDischargeEncounter, concepts.dischargeInstruction]);
147
+
148
+ return {
149
+ isLoading,
150
+ error,
151
+ drugOrders: drugorder,
152
+ testOrders: testorder,
153
+ orderEncounters,
154
+ clinicalConsultationEncounters,
155
+ complaints,
156
+ drugReactions,
157
+ dischargeinstructions,
158
+ ipdDischargeEncounter,
159
+ physicalExaminations,
160
+ };
161
+ }
162
+
163
+ function getComplaintsObs(
164
+ encounters: Array<Encounter>,
165
+ complaintsConceptUuid: string,
166
+ chiefComplainConceptUuid: string,
167
+ ) {
168
+ return encounters.reduce<Array<Obs>>((prev, curr) => {
169
+ if (curr.obs.length) {
170
+ const complaintObs = curr.obs
171
+ .filter((o) => o.concept.uuid === complaintsConceptUuid && o.groupMembers)
172
+ .flatMap((o) => o.groupMembers)
173
+ .filter((o) => o.concept.uuid === chiefComplainConceptUuid);
174
+ prev.push(...complaintObs);
175
+ }
176
+ return prev;
177
+ }, []);
178
+ }
179
+
180
+ function getDrugReactions(encounters: Array<Encounter>, drugReactionsConceptUuid: string, drugConceptUuid: string) {
181
+ return encounters.reduce<Array<Obs>>((prev, curr) => {
182
+ if (curr.obs.length) {
183
+ const complaintObs = curr.obs
184
+ .filter((o) => o.concept.uuid === drugReactionsConceptUuid && o.groupMembers)
185
+ .flatMap((o) => o.groupMembers)
186
+ .filter((o) => o.concept.uuid === drugConceptUuid);
187
+ prev.push(...complaintObs);
188
+ }
189
+ return prev;
190
+ }, []);
191
+ }
192
+ type NullableNumber = number | null | undefined;
193
+ export type ObservationValue =
194
+ | OpenmrsResource // coded
195
+ | number // numeric
196
+ | string // text or misc
197
+ | boolean
198
+ | null;
199
+
200
+ export interface LabOrderConcept {
201
+ uuid: string;
202
+ display: string;
203
+ name?: ConceptName;
204
+ datatype: LabOrderConceptDatatype;
205
+ set: boolean;
206
+ version: string;
207
+ retired: boolean;
208
+ descriptions: Array<LabOrderConceptDescription>;
209
+ mappings?: Array<LabOrderConceptMapping>;
210
+ answers?: Array<OpenmrsResource>;
211
+ setMembers?: Array<LabOrderConcept>;
212
+ hiNormal?: NullableNumber;
213
+ hiAbsolute?: NullableNumber;
214
+ hiCritical?: NullableNumber;
215
+ lowNormal?: NullableNumber;
216
+ lowAbsolute?: NullableNumber;
217
+ lowCritical?: NullableNumber;
218
+ allowDecimal?: boolean | null;
219
+ units?: string;
220
+ }
221
+
222
+ export interface ConceptName {
223
+ display: string;
224
+ uuid: string;
225
+ name: string;
226
+ locale: string;
227
+ localePreferred: boolean;
228
+ conceptNameType: string;
229
+ }
230
+
231
+ export interface LabOrderConceptDatatype {
232
+ uuid: string;
233
+ display: string;
234
+ name: string;
235
+ description: string;
236
+ hl7Abbreviation: string;
237
+ retired: boolean;
238
+ resourceVersion: string;
239
+ }
240
+
241
+ export interface LabOrderConceptDescription {
242
+ display: string;
243
+ uuid: string;
244
+ description: string;
245
+ locale: string;
246
+ resourceVersion: string;
247
+ }
248
+
249
+ export interface LabOrderConceptMapping {
250
+ display: string;
251
+ uuid: string;
252
+ conceptReferenceTerm: OpenmrsResource;
253
+ conceptMapType: OpenmrsResource;
254
+ resourceVersion: string;
255
+ }
256
+ export function useOrderConceptByUuid(uuid: string) {
257
+ const apiUrl = `${restBaseUrl}/concept/${uuid}?v=${labConceptRepresentation}`;
258
+
259
+ const { data, error, isLoading, isValidating, mutate } = useSWR<LabOrderConcept, Error>(uuid, fetchAllSetMembers);
260
+ /**
261
+ * We are fetching 2 levels of set members at one go.
262
+ */
263
+
264
+ const results = useMemo(
265
+ () => ({
266
+ concept: data,
267
+ isLoading,
268
+ error,
269
+ isValidating,
270
+ mutate,
271
+ }),
272
+ [data, error, isLoading, isValidating, mutate],
273
+ );
274
+
275
+ return results;
276
+ }
277
+
278
+ /**
279
+ * This function fetches all the different levels of set members for a concept,
280
+ * while fetching 2 levels of set members at one go.
281
+ * @param conceptUuid - The UUID of the concept to fetch.
282
+ * @returns The concept with all its set members and their set members.
283
+ */
284
+ async function fetchAllSetMembers(conceptUuid: string): Promise<LabOrderConcept> {
285
+ const conceptResponse = await openmrsFetch<LabOrderConcept>(getUrlForConcept(conceptUuid));
286
+ let concept = conceptResponse.data;
287
+ const secondLevelSetMembers = concept.set
288
+ ? concept.setMembers
289
+ .map((member) => (member.set ? member.setMembers.map((lowerMember) => lowerMember.uuid) : []))
290
+ .flat()
291
+ : [];
292
+ if (secondLevelSetMembers.length > 0) {
293
+ const concepts = await Promise.all(secondLevelSetMembers.map((uuid) => fetchAllSetMembers(uuid)));
294
+ const uuidMap = concepts.reduce(
295
+ (acc, c) => {
296
+ acc[c.uuid] = c;
297
+ return acc;
298
+ },
299
+ {} as Record<string, LabOrderConcept>,
300
+ );
301
+ concept.setMembers = concept.setMembers.map((member) => {
302
+ if (member.set) {
303
+ member.setMembers = member.setMembers.map((lowerMember) => uuidMap[lowerMember.uuid]);
304
+ }
305
+ return member;
306
+ });
307
+ }
308
+
309
+ return concept;
310
+ }
311
+
312
+ function getUrlForConcept(conceptUuid: string) {
313
+ return `${restBaseUrl}/concept/${conceptUuid}?v=${labConceptRepresentation}`;
314
+ }
315
+
316
+ export const getObservationDisplayValue = (value: ObservationValue, defaultValue: string | null = '--'): string => {
317
+ if (value == null) return defaultValue;
318
+
319
+ switch (typeof value) {
320
+ case 'string':
321
+ return value.trim() || defaultValue;
322
+ case 'number':
323
+ return String(value);
324
+ case 'object':
325
+ if ('display' in value && typeof value.display === 'string') {
326
+ return value.display.trim() || defaultValue;
327
+ }
328
+ break;
329
+ }
330
+
331
+ return defaultValue;
332
+ };
333
+ export const getTreatmentDisplayText = (order: Order): string => {
334
+ return `${order.drug?.display} ${order.frequency.display} for ${order.duration} ${order.durationUnits.display}`;
335
+ };
@@ -0,0 +1,125 @@
1
+ @use '@carbon/type';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/colors';
4
+
5
+ .sectionHeader {
6
+ @include type.type-style('heading-02');
7
+ }
8
+
9
+ .button {
10
+ height: layout.$spacing-10;
11
+ display: flex;
12
+ align-content: flex-start;
13
+ align-items: baseline;
14
+ min-width: 20%;
15
+ }
16
+
17
+ .buttonSet {
18
+ padding: 0rem;
19
+ margin-top: layout.$spacing-05;
20
+ display: flex;
21
+ justify-content: space-between;
22
+ width: 100%;
23
+ margin-bottom: layout.$spacing-05;
24
+ }
25
+
26
+ .content {
27
+ padding: 6rem;
28
+ margin: 0 auto;
29
+ max-width: 800px;
30
+ font-size: 14px;
31
+ line-height: 1.5;
32
+ color: #000;
33
+ background-color: #fff;
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: layout.$spacing-06;
37
+
38
+ // Print-specific tweaks
39
+ @media print {
40
+ padding: 0.5in;
41
+ font-size: 12px;
42
+ line-height: 1.4;
43
+ color-adjust: exact; // prevents color fading during print
44
+ -webkit-print-color-adjust: exact;
45
+ }
46
+
47
+ // Optional: section separators
48
+ & + .content {
49
+ margin-top: 2rem;
50
+ border-top: 1px dashed #ccc;
51
+ padding-top: 1rem;
52
+ }
53
+ }
54
+
55
+ .header {
56
+ justify-content: center;
57
+ align-items: center;
58
+ text-align: center;
59
+ display: flex;
60
+ flex-direction: column;
61
+ font-weight: bold;
62
+ padding-top: layout.$spacing-05;
63
+ padding-bottom: layout.$spacing-05;
64
+ gap: layout.$spacing-03;
65
+ text-transform: uppercase;
66
+ }
67
+
68
+ .field {
69
+ display: flex;
70
+ align-items: baseline;
71
+ gap: 0;
72
+ width: 100%;
73
+ flex-grow: 1;
74
+
75
+ > .name {
76
+ text-transform: uppercase;
77
+ margin-right: 8px;
78
+ }
79
+
80
+ > .value {
81
+ text-transform: uppercase;
82
+ border-bottom: 2px dotted black;
83
+ flex: 1;
84
+ line-height: 1.4;
85
+ display: inline-block;
86
+ }
87
+ }
88
+
89
+ @mixin gridByColumnCount($columns: 3, $gap: 1rem) {
90
+ display: grid;
91
+ grid-template-columns: repeat($columns, 1fr);
92
+ gap: $gap;
93
+ align-items: baseline;
94
+
95
+ @media print {
96
+ padding: 0;
97
+ gap: 0.5rem;
98
+ border: none;
99
+ grid-template-columns: repeat($columns, 1fr);
100
+ }
101
+ }
102
+
103
+ .txtUpper {
104
+ text-transform: uppercase;
105
+ }
106
+
107
+ .txtTitle {
108
+ text-transform: capitalize;
109
+ }
110
+
111
+ .cols4 {
112
+ @include gridByColumnCount(4);
113
+ }
114
+
115
+ .cols2 {
116
+ @include gridByColumnCount(2);
117
+ }
118
+
119
+ .cols3 {
120
+ @include gridByColumnCount(3);
121
+ }
122
+
123
+ .cols7 {
124
+ @include gridByColumnCount(7);
125
+ }