@digihmis/esm-outpatient-app 1.0.4 → 1.0.5

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.
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":">=2.24.0"},"extensions":[{"name":"outpatient-dashboard-header","slot":"outpatient-sidebar-slot","component":"outpatientDashboardHeader","order":0,"online":true,"offline":true},{"name":"outpatient-db-link","slot":"outpatient-dashboard-slot","component":"leftPanelLink","meta":{"name":"outpatient","slot":"outpatient-homepage-dashboard-slot","title":"Outpatient queue"},"order":1,"online":true,"offline":true},{"name":"outpatient-queue-dashboard","slot":"outpatient-homepage-dashboard-slot","component":"outpatientQueueDashboard"},{"name":"care-panel-link","slot":"outpatient-dashboard-slot","component":"carePanelLink","meta":{"name":"outpatient/patient/:patientUuid/file","slot":"outpatient-file-dashboard-slot","title":"Patient file","showOnlyOnRoute":"/file"},"order":2,"online":true,"offline":true},{"name":"care-panel-history","slot":"outpatient-file-dashboard-slot","component":"carePanel"}],"workspaces":[],"version":"1.0.4"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":">=2.24.0"},"extensions":[{"name":"outpatient-dashboard-header","slot":"outpatient-sidebar-slot","component":"outpatientDashboardHeader","order":0,"online":true,"offline":true},{"name":"outpatient-db-link","slot":"outpatient-dashboard-slot","component":"leftPanelLink","meta":{"name":"outpatient","slot":"outpatient-homepage-dashboard-slot","title":"Outpatient queue"},"order":1,"online":true,"offline":true},{"name":"outpatient-queue-dashboard","slot":"outpatient-homepage-dashboard-slot","component":"outpatientQueueDashboard"},{"name":"care-panel-link","slot":"outpatient-dashboard-slot","component":"carePanelLink","meta":{"name":"outpatient/patient/:patientUuid/file","slot":"outpatient-file-dashboard-slot","title":"Patient file","showOnlyOnRoute":"/patient/"},"order":2,"online":true,"offline":true},{"name":"care-panel-history","slot":"outpatient-file-dashboard-slot","component":"carePanel"}],"workspaces":[],"version":"1.0.5"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digihmis/esm-outpatient-app",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Outpatient microfrontend for the OpenMRS SPA",
5
5
  "browser": "dist/digihmis-esm-outpatient-app.js",
6
6
  "main": "src/index.ts",
@@ -210,6 +210,12 @@ export const configSchema: ConfigSchema = {
210
210
  _description: 'Procedure Concept SET UUID',
211
211
  _default: '165418AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
212
212
  },
213
+ radiologyConceptSetUuid: {
214
+ _type: Type.String,
215
+ _description:
216
+ 'Radiology/Imaging Concept SET UUID. Used on the patient file to split a patient’s studies into the Imaging tab (concepts in this set) vs the Procedures tab (everything else).',
217
+ _default: '164068AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
218
+ },
213
219
  visitAttributeTypes: {
214
220
  paymentMethods: {
215
221
  _type: Type.String,
@@ -332,6 +338,7 @@ export type ConfigObject = {
332
338
  };
333
339
  procedureOrderTypeUuid: string;
334
340
  procedureConceptSetUuid: string;
341
+ radiologyConceptSetUuid: string;
335
342
  visitAttributeTypes: {
336
343
  paymentMethods: string;
337
344
  insuranceScheme: string;
@@ -12,5 +12,7 @@ export const carePanelMeta = {
12
12
  slot: 'outpatient-file-dashboard-slot',
13
13
  title: 'Patient file',
14
14
  icon: UserIdentification,
15
- showOnlyOnRoute: '/file',
15
+ // Show the link throughout a patient's context (any /patient/<uuid>/... route), not only on the
16
+ // /file route itself — otherwise it vanishes the moment you're on any other patient sub-route.
17
+ showOnlyOnRoute: '/patient/',
16
18
  };
@@ -28,6 +28,7 @@ import {
28
28
  useClinicalEncounterFieldLabels,
29
29
  } from './clinical-encounters.resource';
30
30
  import styles from './patient-management.scss';
31
+ import { CardHeader } from '@openmrs/esm-patient-common-lib';
31
32
 
32
33
  interface ClinicalEncountersListProps {
33
34
  patientUuid: string;
@@ -62,7 +63,6 @@ function providerName(encounter: ClinicalEncounter): string | null {
62
63
  return encounter.encounterProviders?.[0]?.provider?.display ?? null;
63
64
  }
64
65
 
65
- /** The saved clinical-encounter records for a patient, shown on the Clinical encounter tab. */
66
66
  const ClinicalEncountersList: React.FC<ClinicalEncountersListProps> = ({ patientUuid, onStartConsultation }) => {
67
67
  const { t } = useTranslation();
68
68
  const { encounters, isLoading } = useClinicalEncounters(patientUuid);
@@ -117,17 +117,24 @@ const ClinicalEncountersList: React.FC<ClinicalEncountersListProps> = ({ patient
117
117
 
118
118
  return (
119
119
  <div className={styles.encounterList}>
120
- <div className={styles.encounterListHeader}>
121
- <span className={styles.encounterCount}>
122
- {t('clinicalEncountersCount', '{{count}} clinical encounter(s)', { count: encounters.length })}
123
- </span>
124
- <Button kind="ghost" size="sm" renderIcon={Add} onClick={onStartConsultation}>
125
- {t('startConsultation', 'Start consultation')}
126
- </Button>
120
+ <div className={styles.cardEncounter}>
121
+ <CardHeader title={t('clinicalEncounters', 'Clinical encounters ')}>
122
+ <Button kind="ghost" size="sm" renderIcon={Add} onClick={onStartConsultation}>
123
+ {t('startConsultation', 'Start consultation')}
124
+ </Button>
125
+ </CardHeader>
127
126
  </div>
128
127
 
129
128
  <DataTable rows={rows} headers={headers} size="sm" useZebraStyles>
130
- {({ rows, headers, getHeaderProps, getRowProps, getExpandedRowProps, getTableProps, getTableContainerProps }) => (
129
+ {({
130
+ rows,
131
+ headers,
132
+ getHeaderProps,
133
+ getRowProps,
134
+ getExpandedRowProps,
135
+ getTableProps,
136
+ getTableContainerProps,
137
+ }) => (
131
138
  <TableContainer {...getTableContainerProps()}>
132
139
  <Table {...getTableProps()} aria-label={t('clinicalEncounters', 'Clinical encounters')}>
133
140
  <TableHead>
@@ -83,8 +83,8 @@ export const BaseClinicalEncounterSchema = z.object({
83
83
  // Lymphadenopathy (when Lymphadenopathy is selected)
84
84
  siteLymphadenopathy: z.array(genericItemSchema).optional().default([]),
85
85
 
86
- // Section 3: Diagnosis
87
- primaryDiagnosis: z.array(genericItemSchema).optional().default([]),
86
+ // Section 3: Diagnosis — a primary diagnosis is mandatory before an encounter can be saved.
87
+ primaryDiagnosis: z.array(genericItemSchema).min(1, 'Please select at least one primary diagnosis'),
88
88
  secondaryDiagnosis: z.array(genericItemSchema).optional().default([]),
89
89
  diagnosisNotes: z.string().optional(),
90
90
 
@@ -536,6 +536,9 @@ const ClinicalEncounterWorkspace: React.FC<ClinicalEncounterWorkspaceProps> = ({
536
536
  if (hasLymphadenopathy) {
537
537
  fieldsToValidate.push('siteLymphadenopathy');
538
538
  }
539
+ } else if (currentSection === 3) {
540
+ // Diagnosis step: a primary diagnosis is mandatory before leaving this step.
541
+ fieldsToValidate.push('primaryDiagnosis');
539
542
  }
540
543
 
541
544
  const isValid = fieldsToValidate.length > 0 ? await trigger(fieldsToValidate) : true;
@@ -12,7 +12,15 @@ import {
12
12
  Tag,
13
13
  Tile,
14
14
  } from '@carbon/react';
15
- import { Calendar, ChartLineData, Medication, Microscope, Stethoscope, type CarbonIconType } from '@carbon/react/icons';
15
+ import {
16
+ Activity,
17
+ Calendar,
18
+ ChartLineData,
19
+ Medication,
20
+ Microscope,
21
+ Stethoscope,
22
+ type CarbonIconType,
23
+ } from '@carbon/react/icons';
16
24
  import {
17
25
  ExtensionSlot,
18
26
  formatDatetime,
@@ -23,6 +31,7 @@ import {
23
31
  } from '@openmrs/esm-framework';
24
32
  import PatientBanner from './patient-banner/patient-banner.component';
25
33
  import ClinicalEncountersList from './clinical-encounters.component';
34
+ import { LabResults, ImagingResults, ProcedureResults } from './results/investigation-results.component';
26
35
  import { getPatientUuidFromUrl } from './utils';
27
36
  import styles from './patient-management.scss';
28
37
 
@@ -82,6 +91,14 @@ const CarePanel: React.FC = () => {
82
91
  emptyTitle: t('noImagingTitle', 'No imaging results'),
83
92
  emptyBody: t('noImagingBody', 'Radiology and imaging results will appear here.'),
84
93
  },
94
+ {
95
+ key: 'procedures',
96
+ label: t('procedures', 'Procedures'),
97
+ icon: Activity,
98
+ slot: 'outpatient-procedures-slot',
99
+ emptyTitle: t('noProcedureTitle', 'No procedure reports'),
100
+ emptyBody: t('noProcedureBody', 'Procedure reports for this patient will appear here.'),
101
+ },
85
102
  {
86
103
  key: 'prescriptions',
87
104
  label: t('prescriptions', 'Prescriptions'),
@@ -102,33 +119,33 @@ const CarePanel: React.FC = () => {
102
119
 
103
120
  return (
104
121
  <div className={styles.container}>
105
- <Layer>
106
- <Tile className={styles.headerCard}>
107
- {isLoading ? (
108
- <SkeletonText heading width="50%" />
109
- ) : (
110
- patient && <PatientBanner patientUuid={patientUuid} patient={patient} hideActionsOverflow />
111
- )}
112
-
113
- </Tile>
114
- </Layer>
122
+ {isLoading ? (
123
+ <SkeletonText heading width="50%" />
124
+ ) : (
125
+ patient && <PatientBanner patientUuid={patientUuid} patient={patient} hideActionsOverflow />
126
+ )}
115
127
 
116
128
  <ExtensionSlot name="patient-vitals-header-slot" state={{ patientUuid }} className={styles.vitals} />
117
-
118
- <Layer className={styles.tabsCard}>
119
- <Tabs>
120
- <TabList aria-label={t('carePanel', 'Care panel')} contained>
121
- {tabs.map((tab) => (
122
- <Tab key={tab.key} renderIcon={tab.icon}>
123
- {tab.label}
124
- </Tab>
125
- ))}
126
- </TabList>
127
- <TabPanels>
128
- {tabs.map((tab) => (
129
+ <div className={styles.tabsContainer}>
130
+ <Tabs>
131
+ <TabList aria-label={t('carePanel', 'Care panel')} scrollDebounceWait={200}>
132
+ {tabs.map((tab) => (
133
+ <Tab key={tab.key} renderIcon={tab.icon}>
134
+ {tab.label}
135
+ </Tab>
136
+ ))}
137
+ </TabList>
138
+ <TabPanels>
139
+ {tabs.map((tab) => (
129
140
  <TabPanel key={tab.key} className={styles.tabPanel}>
130
141
  {tab.key === 'encounter' ? (
131
142
  <ClinicalEncountersList patientUuid={patientUuid} onStartConsultation={startConsultation} />
143
+ ) : tab.key === 'labs' ? (
144
+ <LabResults patientUuid={patientUuid} />
145
+ ) : tab.key === 'imaging' ? (
146
+ <ImagingResults patientUuid={patientUuid} />
147
+ ) : tab.key === 'procedures' ? (
148
+ <ProcedureResults patientUuid={patientUuid} />
132
149
  ) : (
133
150
  <>
134
151
  <ExtensionSlot name={tab.slot} state={{ patientUuid, visitUuid: activeVisit?.uuid }} />
@@ -137,9 +154,9 @@ const CarePanel: React.FC = () => {
137
154
  )}
138
155
  </TabPanel>
139
156
  ))}
140
- </TabPanels>
141
- </Tabs>
142
- </Layer>
157
+ </TabPanels>
158
+ </Tabs>
159
+ </div>
143
160
  </div>
144
161
  );
145
162
  };
@@ -2,16 +2,19 @@
2
2
  @use '@carbon/type';
3
3
 
4
4
  .container {
5
- padding: layout.$spacing-05;
6
- margin: 0 auto;
5
+ margin: 1rem auto;
6
+
7
7
  }
8
8
 
9
- /* ---- Header card: banner + visit status + primary action ---- */
10
9
  .headerCard {
11
10
  padding: layout.$spacing-05;
12
11
  margin-bottom: layout.$spacing-05;
13
12
  }
14
13
 
14
+ .cardEncounter {
15
+ padding-top: layout.$spacing-03;
16
+ }
17
+
15
18
  .actionBar {
16
19
  display: flex;
17
20
  align-items: center;
@@ -29,10 +32,8 @@
29
32
 
30
33
  .vitals {
31
34
  display: block;
32
- margin-bottom: layout.$spacing-05;
33
35
  }
34
36
 
35
- /* ---- Tabs card ---- */
36
37
  .tabsCard {
37
38
  border: 1px solid var(--cds-border-subtle);
38
39
  }
@@ -189,3 +190,30 @@
189
190
  text-align: right;
190
191
  width: 3rem;
191
192
  }
193
+
194
+
195
+ .tabsContainer {
196
+ padding-left: layout.$spacing-06;
197
+ padding-right: layout.$spacing-06;
198
+ background: var(--cds-field);
199
+
200
+ :global(.omrs-breakpoint-lt-tablet) & {
201
+ padding-left: layout.$spacing-03;
202
+ padding-right: layout.$spacing-03;
203
+ }
204
+ }
205
+
206
+ .tabsHeaderRow {
207
+ display: flex;
208
+ align-items: center;
209
+ justify-content: space-between;
210
+ margin-right: 0.51rem;
211
+
212
+ :global(.omrs-breakpoint-lt-desktop) & {
213
+ gap: layout.$spacing-03;
214
+ }
215
+
216
+ :global(.omrs-breakpoint-lt-tablet) & {
217
+ gap: layout.$spacing-03;
218
+ }
219
+ }
@@ -0,0 +1,210 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ InlineLoading,
5
+ Layer,
6
+ Table,
7
+ TableBody,
8
+ TableCell,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ Tag,
13
+ Tile,
14
+ } from '@carbon/react';
15
+ import { formatDatetime, parseDate, useConfig } from '@openmrs/esm-framework';
16
+ import { type ConfigObject } from '../../config-schema';
17
+ import {
18
+ formatLabValue,
19
+ useConceptSetMembers,
20
+ useLabResults,
21
+ useProcedureRecords,
22
+ type ProcedureRecord,
23
+ } from './results.resource';
24
+ import styles from './investigation-results.scss';
25
+
26
+ /** Map a report/procedure/order status to a Carbon tag colour. */
27
+ function statusColor(status?: string): string {
28
+ const s = (status ?? '').toLowerCase();
29
+ if (s.includes('approv') || s.includes('complet') || s.includes('verif')) {
30
+ return 'green';
31
+ }
32
+ if (s.includes('prelim') || s.includes('report')) {
33
+ return 'blue';
34
+ }
35
+ if (s.includes('progress') || s.includes('receiv')) {
36
+ return 'teal';
37
+ }
38
+ return 'gray';
39
+ }
40
+
41
+ function formatDate(value?: string): string {
42
+ if (!value) {
43
+ return '';
44
+ }
45
+ try {
46
+ return formatDatetime(parseDate(value), { mode: 'standard' });
47
+ } catch {
48
+ return value;
49
+ }
50
+ }
51
+
52
+ const Loading: React.FC = () => {
53
+ const { t } = useTranslation();
54
+ return (
55
+ <div className={styles.loading}>
56
+ <InlineLoading description={t('loadingResults', 'Loading results…')} />
57
+ </div>
58
+ );
59
+ };
60
+
61
+ const Empty: React.FC<{ message: string }> = ({ message }) => (
62
+ <Layer>
63
+ <Tile>
64
+ <p className={styles.empty}>{message}</p>
65
+ </Tile>
66
+ </Layer>
67
+ );
68
+
69
+ // ---- Lab results --------------------------------------------------------------
70
+
71
+ export const LabResults: React.FC<{ patientUuid: string }> = ({ patientUuid }) => {
72
+ const { t } = useTranslation();
73
+ const { labResults, isLoading, error } = useLabResults(patientUuid);
74
+
75
+ if (isLoading) {
76
+ return <Loading />;
77
+ }
78
+ if (error) {
79
+ return <p className={styles.error}>{t('labResultsError', 'Could not load lab results.')}</p>;
80
+ }
81
+ if (!labResults.length) {
82
+ return <Empty message={t('noLabResultsBody', 'Laboratory results for this patient will appear here.')} />;
83
+ }
84
+
85
+ return (
86
+ <div className={styles.list}>
87
+ {labResults.map((record) => (
88
+ <Layer key={record.orderUuid}>
89
+ <Tile className={styles.card}>
90
+ <div className={styles.cardHeader}>
91
+ <h4 className={styles.title}>{record.testName}</h4>
92
+ <span className={styles.date}>{formatDate(record.date)}</span>
93
+ </div>
94
+ {record.results.length ? (
95
+ <Table size="sm" className={styles.valuesTable}>
96
+ <TableHead>
97
+ <TableRow>
98
+ <TableHeader>{t('test', 'Test')}</TableHeader>
99
+ <TableHeader>{t('result', 'Result')}</TableHeader>
100
+ </TableRow>
101
+ </TableHead>
102
+ <TableBody>
103
+ {record.results.map((value, index) => (
104
+ <TableRow key={`${record.orderUuid}-${index}`}>
105
+ <TableCell>{value.concept?.display ?? '—'}</TableCell>
106
+ <TableCell>{formatLabValue(value.value)}</TableCell>
107
+ </TableRow>
108
+ ))}
109
+ </TableBody>
110
+ </Table>
111
+ ) : (
112
+ <p className={styles.empty}>{t('noValues', 'No recorded values.')}</p>
113
+ )}
114
+ </Tile>
115
+ </Layer>
116
+ ))}
117
+ </div>
118
+ );
119
+ };
120
+
121
+ // ---- Shared report list (imaging + procedure) ---------------------------------
122
+
123
+ const ReportCard: React.FC<{ record: ProcedureRecord }> = ({ record }) => {
124
+ const { t } = useTranslation();
125
+ const findings = record.procedureReport || record.preliminaryReport;
126
+ return (
127
+ <Layer>
128
+ <Tile className={styles.card}>
129
+ <div className={styles.cardHeader}>
130
+ <h4 className={styles.title}>{record.concept?.display ?? t('study', 'Study')}</h4>
131
+ <div className={styles.cardHeader}>
132
+ {record.status && <Tag type={statusColor(record.status)}>{record.status}</Tag>}
133
+ <span className={styles.date}>{formatDate(record.startDatetime)}</span>
134
+ </div>
135
+ </div>
136
+ {findings ? (
137
+ <div className={styles.section}>
138
+ <p className={styles.sectionLabel}>{t('findings', 'Findings')}</p>
139
+ <p className={styles.reportText}>{findings}</p>
140
+ </div>
141
+ ) : (
142
+ <p className={styles.empty}>{t('noReportYet', 'No report entered yet.')}</p>
143
+ )}
144
+ {record.impressions && (
145
+ <div className={styles.section}>
146
+ <p className={styles.sectionLabel}>{t('impression', 'Impression')}</p>
147
+ <p className={styles.reportText}>{record.impressions}</p>
148
+ </div>
149
+ )}
150
+ </Tile>
151
+ </Layer>
152
+ );
153
+ };
154
+
155
+ const ProcedureReportList: React.FC<{
156
+ patientUuid: string;
157
+ variant: 'imaging' | 'procedure';
158
+ emptyMessage: string;
159
+ }> = ({ patientUuid, variant, emptyMessage }) => {
160
+ const { t } = useTranslation();
161
+ const { radiologyConceptSetUuid } = useConfig<ConfigObject>();
162
+ const { procedures, isLoading, error } = useProcedureRecords(patientUuid);
163
+ const radiologyMembers = useConceptSetMembers(radiologyConceptSetUuid);
164
+
165
+ if (isLoading) {
166
+ return <Loading />;
167
+ }
168
+ if (error) {
169
+ return <p className={styles.error}>{t('studiesError', 'Could not load studies.')}</p>;
170
+ }
171
+
172
+ const filtered = procedures.filter((record) => {
173
+ const isImaging = record.concept ? radiologyMembers.has(record.concept.uuid) : false;
174
+ return variant === 'imaging' ? isImaging : !isImaging;
175
+ });
176
+
177
+ if (!filtered.length) {
178
+ return <Empty message={emptyMessage} />;
179
+ }
180
+
181
+ return (
182
+ <div className={styles.list}>
183
+ {filtered.map((record) => (
184
+ <ReportCard key={record.uuid} record={record} />
185
+ ))}
186
+ </div>
187
+ );
188
+ };
189
+
190
+ export const ImagingResults: React.FC<{ patientUuid: string }> = ({ patientUuid }) => {
191
+ const { t } = useTranslation();
192
+ return (
193
+ <ProcedureReportList
194
+ patientUuid={patientUuid}
195
+ variant="imaging"
196
+ emptyMessage={t('noImagingBody', 'Radiology and imaging results will appear here.')}
197
+ />
198
+ );
199
+ };
200
+
201
+ export const ProcedureResults: React.FC<{ patientUuid: string }> = ({ patientUuid }) => {
202
+ const { t } = useTranslation();
203
+ return (
204
+ <ProcedureReportList
205
+ patientUuid={patientUuid}
206
+ variant="procedure"
207
+ emptyMessage={t('noProcedureBody', 'Procedure reports for this patient will appear here.')}
208
+ />
209
+ );
210
+ };
@@ -0,0 +1,75 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+
5
+ .list {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: layout.$spacing-04;
9
+ padding: layout.$spacing-03 0;
10
+ }
11
+
12
+ .card {
13
+ padding: layout.$spacing-05;
14
+ }
15
+
16
+ .cardHeader {
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: space-between;
20
+ gap: layout.$spacing-03;
21
+ flex-wrap: wrap;
22
+ margin-bottom: layout.$spacing-03;
23
+ }
24
+
25
+ .title {
26
+ @include type.type-style('heading-compact-02');
27
+ margin: 0;
28
+ }
29
+
30
+ .date {
31
+ @include type.type-style('label-01');
32
+ color: colors.$gray-60;
33
+ }
34
+
35
+ .section {
36
+ margin-top: layout.$spacing-04;
37
+ }
38
+
39
+ .sectionLabel {
40
+ @include type.type-style('label-01');
41
+ color: colors.$gray-60;
42
+ text-transform: uppercase;
43
+ letter-spacing: 0.04em;
44
+ margin-bottom: layout.$spacing-02;
45
+ }
46
+
47
+ .reportText {
48
+ @include type.type-style('body-01');
49
+ white-space: pre-wrap;
50
+ margin: 0;
51
+ }
52
+
53
+ .valuesTable {
54
+ width: 100%;
55
+ }
56
+
57
+ .loading {
58
+ display: flex;
59
+ justify-content: center;
60
+ padding: layout.$spacing-06;
61
+ }
62
+
63
+ .empty {
64
+ @include type.type-style('body-01');
65
+ color: colors.$gray-60;
66
+ padding: layout.$spacing-05;
67
+ text-align: center;
68
+ }
69
+
70
+ .error {
71
+ @include type.type-style('body-01');
72
+ color: colors.$red-60;
73
+ padding: layout.$spacing-05;
74
+ text-align: center;
75
+ }