@openmrs/esm-sms-app 1.0.1-pre.22

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 (108) hide show
  1. package/.turbo/turbo-build.log +32 -0
  2. package/dist/123.js +2 -0
  3. package/dist/123.js.LICENSE.txt +9 -0
  4. package/dist/123.js.map +1 -0
  5. package/dist/144.js +2 -0
  6. package/dist/144.js.LICENSE.txt +19 -0
  7. package/dist/144.js.map +1 -0
  8. package/dist/160.js +1 -0
  9. package/dist/160.js.map +1 -0
  10. package/dist/173.js +1 -0
  11. package/dist/173.js.map +1 -0
  12. package/dist/197.js +1 -0
  13. package/dist/260.js +1 -0
  14. package/dist/260.js.map +1 -0
  15. package/dist/285.js +1 -0
  16. package/dist/285.js.map +1 -0
  17. package/dist/300.js +2 -0
  18. package/dist/300.js.LICENSE.txt +5 -0
  19. package/dist/300.js.map +1 -0
  20. package/dist/335.js +1 -0
  21. package/dist/356.js +1 -0
  22. package/dist/356.js.map +1 -0
  23. package/dist/372.js +1 -0
  24. package/dist/372.js.map +1 -0
  25. package/dist/378.js +1 -0
  26. package/dist/378.js.map +1 -0
  27. package/dist/41.js +2 -0
  28. package/dist/41.js.LICENSE.txt +9 -0
  29. package/dist/41.js.map +1 -0
  30. package/dist/449.js +1 -0
  31. package/dist/449.js.map +1 -0
  32. package/dist/465.js +1 -0
  33. package/dist/465.js.map +1 -0
  34. package/dist/470.js +2 -0
  35. package/dist/470.js.LICENSE.txt +9 -0
  36. package/dist/470.js.map +1 -0
  37. package/dist/495.js +1 -0
  38. package/dist/495.js.map +1 -0
  39. package/dist/55.js +1 -0
  40. package/dist/613.js +2 -0
  41. package/dist/613.js.LICENSE.txt +5 -0
  42. package/dist/613.js.map +1 -0
  43. package/dist/628.js +1 -0
  44. package/dist/628.js.map +1 -0
  45. package/dist/652.js +1 -0
  46. package/dist/733.js +2 -0
  47. package/dist/733.js.LICENSE.txt +30 -0
  48. package/dist/733.js.map +1 -0
  49. package/dist/830.js +1 -0
  50. package/dist/830.js.map +1 -0
  51. package/dist/831.js +2 -0
  52. package/dist/831.js.LICENSE.txt +5 -0
  53. package/dist/831.js.map +1 -0
  54. package/dist/876.js +2 -0
  55. package/dist/876.js.LICENSE.txt +9 -0
  56. package/dist/876.js.map +1 -0
  57. package/dist/919.js +1 -0
  58. package/dist/99.js +1 -0
  59. package/dist/main.js +1 -0
  60. package/dist/main.js.map +1 -0
  61. package/dist/openmrs-esm-sms-app.js +1 -0
  62. package/dist/openmrs-esm-sms-app.js.buildmanifest.json +807 -0
  63. package/dist/openmrs-esm-sms-app.js.map +1 -0
  64. package/dist/routes.json +1 -0
  65. package/jest.config.js +3 -0
  66. package/package.json +55 -0
  67. package/src/api/providers.resource.ts +45 -0
  68. package/src/config-schema.ts +34 -0
  69. package/src/declarations.d.ts +4 -0
  70. package/src/hooks/useLogs.ts +28 -0
  71. package/src/hooks/useProviderConfigTemplates.ts +16 -0
  72. package/src/hooks/useProviderConfigurations.ts +32 -0
  73. package/src/index.ts +35 -0
  74. package/src/providers/add-config-form/provider-config-form.scss +118 -0
  75. package/src/providers/add-config-form/provider-config-form.test.tsx +252 -0
  76. package/src/providers/add-config-form/provider-config-form.workspace.tsx +347 -0
  77. package/src/providers/empty-state/empty-data-illustration.component.tsx +39 -0
  78. package/src/providers/empty-state/empty-state.component.tsx +41 -0
  79. package/src/providers/empty-state/empty-state.scss +55 -0
  80. package/src/providers/modals/config-upload.modal.tsx +170 -0
  81. package/src/providers/modals/config-upload.scss +33 -0
  82. package/src/providers/modals/remove-config.modal.tsx +69 -0
  83. package/src/providers/providers-dashboard.component.tsx +63 -0
  84. package/src/providers/providers-dashboard.scss +69 -0
  85. package/src/providers/providers-dashboard.test.tsx +63 -0
  86. package/src/providers/providers-overview/providers-action-menu.component.tsx +102 -0
  87. package/src/providers/providers-overview/providers-action-menu.scss +11 -0
  88. package/src/providers/providers-overview/providers-overview.component.tsx +306 -0
  89. package/src/providers/providers-overview/providers-overview.scss +114 -0
  90. package/src/providers/sms-logs-table/sms-logs-table.component.tsx +217 -0
  91. package/src/providers/sms-logs-table/sms-logs-table.scss +63 -0
  92. package/src/providers/sms-logs-table/sms-logs-table.test.tsx +103 -0
  93. package/src/providers/test-form/provider-config-test-form.scss +105 -0
  94. package/src/providers/test-form/provider-config-test-form.test.tsx +122 -0
  95. package/src/providers/test-form/provider-config-test-form.workspace.tsx +233 -0
  96. package/src/providers/utils/index.ts +57 -0
  97. package/src/root.scss +39 -0
  98. package/src/routes.json +44 -0
  99. package/src/sms-admin-card-link.component.tsx +24 -0
  100. package/src/types/index.ts +68 -0
  101. package/translations/am.json +4 -0
  102. package/translations/en.json +47 -0
  103. package/translations/es.json +4 -0
  104. package/translations/fr.json +4 -0
  105. package/translations/he.json +4 -0
  106. package/translations/km.json +4 -0
  107. package/tsconfig.json +5 -0
  108. package/webpack.config.js +1 -0
@@ -0,0 +1,114 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @use '@carbon/styles/scss/type';
4
+ @import "~@openmrs/esm-styleguide/src/vars";
5
+
6
+ .widgetCard {
7
+ border: 1px solid $ui-03;
8
+ }
9
+
10
+ .divider {
11
+ width: 1px;
12
+ height: spacing.$spacing-05;
13
+ color: colors.$gray-20;
14
+ margin: 0 spacing.$spacing-05;
15
+ }
16
+
17
+ .rightMostFlexContainer {
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ }
22
+
23
+ .key {
24
+ @include type.type-style('body-compact-01');
25
+ color: $text-02;
26
+ margin-bottom: spacing.$spacing-03;
27
+ }
28
+
29
+ .tag {
30
+ :global(.cds--tag) {
31
+ min-height: unset;
32
+ }
33
+ }
34
+
35
+ .tableContainer {
36
+ padding: 0;
37
+
38
+ :global(.cds--data-table-header) {
39
+ padding: 0;
40
+ }
41
+
42
+ :global(.cds--table-toolbar) {
43
+ position: relative;
44
+ overflow: visible;
45
+ top: 0;
46
+ }
47
+
48
+ &:global(.cds--data-table-container) {
49
+ background: none !important;
50
+ }
51
+ }
52
+
53
+ .search {
54
+ max-width: 16rem;
55
+ }
56
+
57
+ .menuItem {
58
+ max-width: none;
59
+ }
60
+
61
+ .expandedRow {
62
+ padding-inline-start: 3.5rem;
63
+
64
+ > td {
65
+ padding: inherit !important;
66
+
67
+ > div {
68
+ max-height: max-content !important;
69
+ }
70
+ }
71
+
72
+ > div {
73
+ background-color: $ui-02;
74
+ }
75
+ }
76
+
77
+ .hiddenRow {
78
+ display: none;
79
+ }
80
+
81
+ .content {
82
+ @include type.type-style('heading-compact-02');
83
+ color: $text-02;
84
+ margin-bottom: spacing.$spacing-03;
85
+ }
86
+
87
+ .configInfo {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: spacing.$spacing-05;
91
+ padding: spacing.$spacing-05;
92
+ }
93
+
94
+ .gridTitle {
95
+ @include type.type-style('label-02');
96
+ color: colors.$gray-70;
97
+ }
98
+
99
+ .label {
100
+ @include type.type-style('label-01');
101
+ color: colors.$gray-70;
102
+ }
103
+
104
+ .labelBold {
105
+ @include type.type-style('label-01');
106
+ color: colors.$gray-70;
107
+ font-weight: bold;
108
+ margin-right: spacing.$spacing-02;
109
+ }
110
+
111
+ .labelContainer {
112
+ display: flex;
113
+ }
114
+
@@ -0,0 +1,217 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { CardHeader, ErrorState } from '@openmrs/esm-patient-common-lib';
5
+ import {
6
+ InlineLoading,
7
+ DataTable,
8
+ DataTableSkeleton,
9
+ Dropdown,
10
+ Layer,
11
+ Pagination,
12
+ TableContainer,
13
+ Table,
14
+ TableHead,
15
+ TableHeader,
16
+ TableRow,
17
+ TableBody,
18
+ TableCell,
19
+ Tile,
20
+ } from '@carbon/react';
21
+ import { useConfig, usePagination, useLayoutType, isDesktop as isDesktopLayout } from '@openmrs/esm-framework';
22
+ import { type ConfigObject } from '../../config-schema';
23
+ import { useLogsRecords } from '../../hooks/useLogs';
24
+ import { EmptyState } from '../empty-state/empty-state.component';
25
+ import styles from './sms-logs-table.scss';
26
+ import { Search } from '@carbon/react';
27
+ import { DatePicker } from '@carbon/react';
28
+
29
+ const SmslogsTable = () => {
30
+ const { t } = useTranslation();
31
+ const config = useConfig<ConfigObject>();
32
+ const layout = useLayoutType();
33
+ const isDesktop = isDesktopLayout(layout);
34
+ const isTablet = !isDesktop;
35
+ const headerTitle = t('smsLogs', 'SMS Logs');
36
+ const pageSizes = [10, 20, 30, 40, 50];
37
+ const [pageSize, setPageSize] = useState();
38
+ const [searchTerm, setSearchTerm] = useState('');
39
+ const [filter, setFilter] = useState('All');
40
+ const { smsLogs, isLoadingLogs, isValidatingLogs, mutateLogs, error } = useLogsRecords();
41
+
42
+ const phoneNumberFilters = useMemo(() => {
43
+ const phoneNumbers = smsLogs?.map((log) => log.phoneNumber);
44
+ return Array.from(new Set(phoneNumbers));
45
+ }, [smsLogs]);
46
+
47
+ /**** Comments below are used for translating keys in logs table, remove with caution *****/
48
+ // t('phoneNumber', 'Phone number')
49
+ // t('messageContent', 'Message')
50
+ // t('config', 'Configuration')
51
+ // t('timestamp', 'Timestamp')
52
+ // t('providerId', 'Provider')
53
+ // t('errorMessage', 'Error message')
54
+ // t('providerStatus', 'Provider status')
55
+ // t('openMrsId', 'Openmrs ID')
56
+ // t('deliveryStatus', 'Delivery status')
57
+ // t('smsDirection', 'SMS direction')
58
+ // t('modificationDate', 'Modification date')
59
+ // t('creationDate', 'Creation date')
60
+ // t('modifiedBy', 'Modified by')
61
+ // t('creator', 'Creator')
62
+
63
+ const headers = useMemo(
64
+ () =>
65
+ config?.smsLogsColumns?.map((column) => ({
66
+ key: column,
67
+ header: t(column),
68
+ isSortable: true,
69
+ sortFunc: (valueA, valueB) => valueA.display?.localeCompare(valueB.display),
70
+ })),
71
+ [config?.smsLogsColumns, t],
72
+ );
73
+
74
+ const filteredLogs = useMemo(() => {
75
+ if ((!filter || filter === 'All') && !searchTerm) {
76
+ return smsLogs;
77
+ }
78
+
79
+ let logs = smsLogs;
80
+ if (filter && filter !== 'All') {
81
+ logs = logs.filter((log) => log.phoneNumber === filter);
82
+ }
83
+
84
+ if (searchTerm) {
85
+ logs = logs.filter((log) => log.messageContent.toLowerCase().includes(searchTerm.toLowerCase()));
86
+ }
87
+
88
+ return logs;
89
+ }, [filter, searchTerm, smsLogs]);
90
+
91
+ const tableRows = useMemo(
92
+ () =>
93
+ filteredLogs.map((smsLog) => ({
94
+ ...smsLog,
95
+ timestamp: new Date(smsLog.timestamp).toLocaleString(),
96
+ })),
97
+ [filteredLogs],
98
+ );
99
+
100
+ const { results: paginatedLogs, paginated, goTo, currentPage } = usePagination(tableRows, pageSize);
101
+
102
+ if (isLoadingLogs) return <DataTableSkeleton role="progressbar" compact={isDesktop} zebra />;
103
+ if (error) return <ErrorState error={error} headerTitle={headerTitle} />;
104
+
105
+ if (smsLogs.length > 0) {
106
+ return (
107
+ <div className={styles.widgetCard}>
108
+ <CardHeader title={headerTitle}>
109
+ <span>{isValidatingLogs ? <InlineLoading /> : null}</span>
110
+ <div className={styles.rightMostFlexContainer}>
111
+ <div className={styles.filterContainer}>
112
+ <Dropdown
113
+ id="smsLogsFilter"
114
+ initialSelectedItem={'All'}
115
+ label=""
116
+ titleText={t('fitlerLogsByNumber', 'Fitler logs by phone number') + ':'}
117
+ type="inline"
118
+ items={['All', ...phoneNumberFilters]}
119
+ onChange={({ selectedItem }) => setFilter(selectedItem.value)}
120
+ size={isTablet ? 'lg' : 'sm'}
121
+ />
122
+ </div>
123
+ <div>
124
+ <DatePicker />
125
+ </div>
126
+ <div>
127
+ <Search
128
+ size={isTablet ? 'lg' : 'sm'}
129
+ placeholder="Search for a message"
130
+ labelText="Search"
131
+ closeButtonLabelText="Clear search input"
132
+ id="message-search"
133
+ onChange={(e) => setSearchTerm(e.target.value)}
134
+ />
135
+ </div>
136
+ </div>
137
+ </CardHeader>
138
+ <DataTable
139
+ aria-label="provider-configutations overview"
140
+ rows={paginatedLogs}
141
+ headers={headers}
142
+ isSortable
143
+ size={isTablet ? 'lg' : 'sm'}
144
+ useZebraStyles
145
+ overflowMenuOnHover={isDesktop}
146
+ >
147
+ {({ rows, headers, getHeaderProps, getTableProps }) => (
148
+ <>
149
+ <TableContainer>
150
+ <Table {...getTableProps()}>
151
+ <TableHead>
152
+ <TableRow>
153
+ {headers.map((header) => (
154
+ <TableHeader
155
+ className={classNames(styles.productiveHeading01, styles.text02)}
156
+ {...getHeaderProps({
157
+ header,
158
+ isSortable: header.isSortable,
159
+ })}
160
+ >
161
+ {header.header?.content ?? header.header}
162
+ </TableHeader>
163
+ ))}
164
+ <TableHeader />
165
+ </TableRow>
166
+ </TableHead>
167
+ <TableBody>
168
+ {rows.map((row) => (
169
+ <TableRow key={row.id}>
170
+ {row.cells.map((cell) => (
171
+ <TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
172
+ ))}
173
+ </TableRow>
174
+ ))}
175
+ </TableBody>
176
+ </Table>
177
+ </TableContainer>
178
+ {rows.length === 0 ? (
179
+ <div className={styles.tileContainer}>
180
+ <Tile className={styles.tile}>
181
+ <div className={styles.tileContent}>
182
+ <p className={styles.content}>{t('noSmsLogsToDisplay', 'No SMS Logs to display')}</p>
183
+ </div>
184
+ </Tile>
185
+ </div>
186
+ ) : null}
187
+ </>
188
+ )}
189
+ </DataTable>
190
+ {paginated && (
191
+ <Pagination
192
+ forwardText="Next page"
193
+ backwardText="Previous page"
194
+ page={currentPage}
195
+ pageSize={pageSize}
196
+ pageSizes={pageSizes}
197
+ totalItems={paginatedLogs?.length}
198
+ className={styles.pagination}
199
+ size={isDesktop ? 'sm' : 'lg'}
200
+ onChange={({ pageSize: newPageSize, page: newPage }) => {
201
+ if (newPageSize !== pageSize) {
202
+ setPageSize(newPageSize);
203
+ }
204
+ if (newPage !== currentPage) {
205
+ goTo(newPage);
206
+ }
207
+ }}
208
+ />
209
+ )}
210
+ </div>
211
+ );
212
+ }
213
+
214
+ return <EmptyState displayText={headerTitle} headerTitle={headerTitle} />;
215
+ };
216
+
217
+ export default SmslogsTable;
@@ -0,0 +1,63 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @use '@carbon/styles/scss/type';
4
+ @import "~@openmrs/esm-styleguide/src/vars";
5
+
6
+ .widgetCard {
7
+ border: 1px solid $ui-03;
8
+ }
9
+
10
+ .divider {
11
+ width: 1px;
12
+ height: spacing.$spacing-05;
13
+ color: colors.$gray-20;
14
+ margin: 0 spacing.$spacing-05;
15
+ }
16
+
17
+ .rightMostFlexContainer {
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ }
22
+
23
+ .filterContainer {
24
+ :global(.cds--dropdown__wrapper--inline) {
25
+ gap: 0;
26
+ }
27
+
28
+ :global(.cds--list-box__menu-icon) {
29
+ height: spacing.$spacing-05;
30
+ }
31
+
32
+ :global(.cds--dropdown--inline .cds--list-box__menu) {
33
+ min-width: spacing.$spacing-13;
34
+ }
35
+ }
36
+
37
+ .content {
38
+ @include type.type-style('heading-compact-02');
39
+ color: $text-02;
40
+ margin-bottom: spacing.$spacing-03;
41
+ }
42
+
43
+ .tileContainer {
44
+ background-color: $ui-02;
45
+ border-top: 1px solid $ui-03;
46
+ padding: spacing.$spacing-11 0;
47
+ }
48
+
49
+ .tile {
50
+ margin: auto;
51
+ width: fit-content;
52
+ }
53
+
54
+ .tileContent {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: center;
58
+ }
59
+
60
+ .helper {
61
+ @include type.type-style('body-compact-01');
62
+ color: $text-02;
63
+ }
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
+ import SmslogsTable from './sms-logs-table.component';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { useConfig, usePagination, useLayoutType } from '@openmrs/esm-framework';
6
+ import { useLogsRecords } from '../../hooks/useLogs';
7
+ import { renderWithSwr } from 'tools';
8
+
9
+ jest.mock('react-i18next', () => ({
10
+ useTranslation: jest.fn(),
11
+ }));
12
+
13
+ jest.mock('../../hooks/useLogs', () => ({
14
+ useLogsRecords: jest.fn(),
15
+ }));
16
+
17
+ describe('SmslogsTable', () => {
18
+ const mockUseTranslation = useTranslation as jest.Mock;
19
+ const mockUseConfig = useConfig as jest.Mock;
20
+ const mockUsePagination = usePagination as jest.Mock;
21
+ const mockUseLayoutType = useLayoutType as jest.Mock;
22
+ const mockUseLogsRecords = useLogsRecords as jest.Mock;
23
+
24
+ beforeEach(() => {
25
+ mockUseTranslation.mockReturnValue({ t: (key: string, value: string) => value });
26
+ mockUseConfig.mockReturnValue({
27
+ configurationPageSize: [10, 20, 30, 40, 50],
28
+ smsLogsColumns: ['phoneNumber', 'messageContent', 'config', 'timestamp', 'providerId'],
29
+ });
30
+ mockUseLayoutType.mockReturnValue('desktop');
31
+ mockUsePagination.mockReturnValue({
32
+ results: mockLogs,
33
+ paginated: true,
34
+ goTo: jest.fn(),
35
+ currentPage: 1,
36
+ });
37
+ });
38
+
39
+ it('displays loading skeleton when logs are loading', () => {
40
+ mockUseLogsRecords.mockReturnValueOnce({
41
+ smsLogs: [],
42
+ isLoadingLogs: true,
43
+ isValidatingLogs: false,
44
+ mutateLogs: jest.fn(),
45
+ error: null,
46
+ });
47
+ renderSmsLogsTable();
48
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
49
+ });
50
+
51
+ it('displays error state when there is an error', () => {
52
+ mockUseLogsRecords.mockReturnValueOnce({
53
+ smsLogs: [],
54
+ isLoadingLogs: false,
55
+ isValidatingLogs: false,
56
+ mutateLogs: jest.fn(),
57
+ error: new Error(),
58
+ });
59
+ renderSmsLogsTable();
60
+ expect(screen.getByText('Error undefined:')).toBeInTheDocument();
61
+ });
62
+ });
63
+
64
+ function renderSmsLogsTable() {
65
+ return renderWithSwr(<SmslogsTable />);
66
+ }
67
+
68
+ const mockLogs = [
69
+ {
70
+ id: 2,
71
+ errorMessage: null,
72
+ providerStatus: null,
73
+ openMrsId: '2f702be8b4ae459ba4d2d05b185c9ab7',
74
+ providerId: '5882ab57-50cd-4b52-b211-bca5c5b8a781',
75
+ deliveryStatus: 'DISPATCHED',
76
+ messageContent: 'Hello',
77
+ timestamp: '2024-08-02T06:25:18.000Z',
78
+ config: 'Vonage',
79
+ smsDirection: 'OUTBOUND',
80
+ phoneNumber: '256754544829',
81
+ modificationDate: '',
82
+ creationDate: '2024-08-02T06:25:18.000Z',
83
+ modifiedBy: null,
84
+ creator: 'daemon',
85
+ },
86
+ {
87
+ id: 1,
88
+ errorMessage: null,
89
+ providerStatus: null,
90
+ openMrsId: '2f702be8b4ae459ba4d2d05b185c9ab7',
91
+ providerId: null,
92
+ deliveryStatus: 'SCHEDULED',
93
+ messageContent: 'Hello',
94
+ timestamp: '2024-08-02T06:25:17.000Z',
95
+ config: 'Vonage',
96
+ smsDirection: 'OUTBOUND',
97
+ phoneNumber: '256754544829',
98
+ modificationDate: '',
99
+ creationDate: '2024-08-02T06:25:17.000Z',
100
+ modifiedBy: null,
101
+ creator: 'admin',
102
+ },
103
+ ];
@@ -0,0 +1,105 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+ @use '@carbon/styles/scss/spacing';
5
+
6
+ .heading {
7
+ @include type.type-style("heading-03");
8
+ margin-bottom: spacing.$spacing-03;
9
+ }
10
+
11
+ .columnLabel {
12
+ @include type.type-style("heading-compact-02");
13
+ color: colors.$gray-70;
14
+ }
15
+
16
+ .radioButtonGroup{
17
+ :global(.cds--radio-button-group--vertical) {
18
+ gap: spacing.$spacing-05;
19
+ }
20
+
21
+ }
22
+
23
+ .button {
24
+ height: spacing.$spacing-10;
25
+ display: flex;
26
+ align-content: flex-start;
27
+ align-items: baseline;
28
+ min-width: 50%;
29
+ }
30
+
31
+ .spinner {
32
+ min-height: spacing.$spacing-05;
33
+ }
34
+
35
+ .tablet {
36
+ padding: spacing.$spacing-06 spacing.$spacing-05;
37
+ background-color: white;
38
+ }
39
+
40
+ .desktop {
41
+ padding: 0;
42
+ }
43
+
44
+
45
+ .formContainer {
46
+ margin: spacing.$spacing-08 spacing.$spacing-05;
47
+ }
48
+
49
+ .form {
50
+ display: flex;
51
+ flex-direction: column;
52
+ justify-content: space-between;
53
+ height: var(--desktop-workspace-window-height);
54
+ }
55
+
56
+ .grid {
57
+ margin: 0;
58
+ padding: 0;
59
+ }
60
+
61
+ .row {
62
+ margin: spacing.$spacing-03 0 spacing.$spacing-08;
63
+ }
64
+
65
+ .errorMessage {
66
+ @include type.type-style("label-02");
67
+ color: colors.$red-60;
68
+ }
69
+
70
+ :global(.omrs-breakpoint-lt-desktop) {
71
+ .form {
72
+ height: var(--tablet-workspace-window-height);
73
+ }
74
+
75
+ .row {
76
+ display: flex;
77
+ gap: spacing.$spacing-10;
78
+
79
+ :global(.cds--col-sm-1) {
80
+ flex-basis: 30%;
81
+ min-width: 8rem;
82
+ text-align: left;
83
+ }
84
+
85
+ :global(.cds--col-sm-3) {
86
+ flex-basis: 70%;
87
+ }
88
+ }
89
+
90
+ .skeleton {
91
+ height: spacing.$spacing-08;
92
+ }
93
+ }
94
+
95
+ .errorNotification {
96
+ margin-top: 0.25rem;
97
+ }
98
+
99
+ :global(.omrs-breakpoint-gt-tablet) {
100
+ .row {
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: spacing.$spacing-05;
104
+ }
105
+ }