@openmrs/esm-openconceptlab-app 3.0.1-pre.10

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 (50) hide show
  1. package/.turbo/turbo-build.log +36 -0
  2. package/LICENSE +401 -0
  3. package/README.md +8 -0
  4. package/dist/316.js +2 -0
  5. package/dist/316.js.LICENSE.txt +19 -0
  6. package/dist/452.js +1 -0
  7. package/dist/574.js +1 -0
  8. package/dist/645.js +2 -0
  9. package/dist/645.js.LICENSE.txt +20 -0
  10. package/dist/709.js +1 -0
  11. package/dist/784.js +2 -0
  12. package/dist/784.js.LICENSE.txt +9 -0
  13. package/dist/858.js +2 -0
  14. package/dist/858.js.LICENSE.txt +1 -0
  15. package/dist/862.js +1 -0
  16. package/dist/main.js +1 -0
  17. package/dist/openmrs-esm-openconceptlab-app.js +1 -0
  18. package/dist/openmrs-esm-openconceptlab-app.js.buildmanifest.json +265 -0
  19. package/dist/openmrs-esm-openconceptlab-app.old +1 -0
  20. package/package.json +53 -0
  21. package/src/config-schema.ts +5 -0
  22. package/src/declarations.d.tsx +2 -0
  23. package/src/import/import.component.scss +27 -0
  24. package/src/import/import.component.tsx +190 -0
  25. package/src/import/import.resource.ts +40 -0
  26. package/src/import/import.test.tsx +103 -0
  27. package/src/index.ts +61 -0
  28. package/src/previous-imports/import-overview/import-items.component.scss +24 -0
  29. package/src/previous-imports/import-overview/import-items.component.tsx +124 -0
  30. package/src/previous-imports/import-overview/import-items.resource.ts +10 -0
  31. package/src/previous-imports/import-overview/import-items.test.tsx +93 -0
  32. package/src/previous-imports/import-overview/import-overview.component.scss +11 -0
  33. package/src/previous-imports/import-overview/import-overview.component.tsx +65 -0
  34. package/src/previous-imports/import-overview/import-overview.test.tsx +83 -0
  35. package/src/previous-imports/previous-imports.component.scss +39 -0
  36. package/src/previous-imports/previous-imports.component.tsx +136 -0
  37. package/src/previous-imports/previous-imports.resource.ts +17 -0
  38. package/src/previous-imports/previous-imports.test.tsx +77 -0
  39. package/src/root.component.scss +23 -0
  40. package/src/root.component.tsx +39 -0
  41. package/src/root.test.tsx +22 -0
  42. package/src/setup-tests.ts +1 -0
  43. package/src/subscription/subscription.component.scss +27 -0
  44. package/src/subscription/subscription.component.tsx +244 -0
  45. package/src/subscription/subscription.resource.ts +40 -0
  46. package/src/subscription/subscription.test.tsx +181 -0
  47. package/src/types.ts +34 -0
  48. package/translations/en.json +51 -0
  49. package/tsconfig.json +23 -0
  50. package/webpack.config.js +1 -0
@@ -0,0 +1,136 @@
1
+ import { formatDatetime, showNotification, usePagination } from '@openmrs/esm-framework';
2
+ import {
3
+ Column,
4
+ DataTable,
5
+ DataTableSkeleton,
6
+ Pagination,
7
+ PaginationSkeleton,
8
+ Grid,
9
+ SkeletonText,
10
+ Table,
11
+ TableBody,
12
+ TableCell,
13
+ TableExpandedRow,
14
+ TableExpandHeader,
15
+ TableExpandRow,
16
+ TableHead,
17
+ TableHeader,
18
+ TableRow,
19
+ } from '@carbon/react';
20
+ import React, { Fragment, useState } from 'react';
21
+ import { useTranslation } from 'react-i18next';
22
+ import { usePreviousImports } from './previous-imports.resource';
23
+ import styles from './previous-imports.component.scss';
24
+ import { Import } from '../types';
25
+ import ImportOverview from './import-overview/import-overview.component';
26
+
27
+ const PreviousImports: React.FC = () => {
28
+ const { t } = useTranslation();
29
+ const [pageSize, setPageSize] = useState(10);
30
+
31
+ const { data: prevImports, isLoading, isError } = usePreviousImports();
32
+ const { results, currentPage, goTo } = usePagination(prevImports, pageSize);
33
+
34
+ if (isLoading) {
35
+ return (
36
+ <Grid className={styles.grid}>
37
+ <Column sm={4} md={8} lg={10}>
38
+ <SkeletonText className={styles.productiveHeading03} />
39
+ <DataTableSkeleton showHeader={false} showToolbar={false} rowCount={10} columnCount={3} />
40
+ <PaginationSkeleton />
41
+ </Column>
42
+ </Grid>
43
+ );
44
+ }
45
+
46
+ if (isError) {
47
+ showNotification({
48
+ kind: 'error',
49
+ description: t('previousImportsFetchError', 'Error occured while fetching the imports'),
50
+ });
51
+ }
52
+
53
+ const headerData = [
54
+ {
55
+ header: t('dateAndTime', 'Date and Time'),
56
+ key: 'localDateStarted',
57
+ },
58
+ {
59
+ header: t('duration', 'Duration'),
60
+ key: 'importTime',
61
+ },
62
+ {
63
+ header: t('status', 'Status'),
64
+ key: 'status',
65
+ },
66
+ ];
67
+
68
+ const rowData = results?.map((prevImport) => {
69
+ return {
70
+ id: prevImport.uuid,
71
+ localDateStarted: formatDatetime(new Date(prevImport.localDateStarted)),
72
+ importTime: prevImport.importTime,
73
+ status: prevImport.status,
74
+ };
75
+ });
76
+
77
+ return (
78
+ !isLoading &&
79
+ !isError && (
80
+ <Grid className={styles.grid}>
81
+ <Column sm={4} md={8} lg={10}>
82
+ <h3 className={styles.productiveHeading03}>{t('previousImports', 'Previous Imports')}</h3>
83
+
84
+ <DataTable rows={rowData} headers={headerData} size="sm">
85
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => (
86
+ <Table {...getTableProps()} className={styles.tableBordered}>
87
+ <TableHead>
88
+ <TableRow>
89
+ <TableExpandHeader />
90
+ {headers.map((header, i) => (
91
+ <TableHeader key={i} {...getHeaderProps({ header })}>
92
+ {header.header}
93
+ </TableHeader>
94
+ ))}
95
+ </TableRow>
96
+ </TableHead>
97
+ <TableBody>
98
+ {rows.map((row) => (
99
+ <Fragment key={row.id}>
100
+ <TableExpandRow {...getRowProps({ row })} className={styles.tableRow}>
101
+ {row.cells.map((cell) => (
102
+ <TableCell key={cell.id}>{cell.value}</TableCell>
103
+ ))}
104
+ </TableExpandRow>
105
+ {row.isExpanded && (
106
+ <TableExpandedRow colSpan={headers.length + 1} className={styles.tableExpandedRow}>
107
+ <ImportOverview
108
+ selectedImportObject={prevImports.find((importItem: Import) => importItem.uuid === row.id)}
109
+ />
110
+ </TableExpandedRow>
111
+ )}
112
+ </Fragment>
113
+ ))}
114
+ </TableBody>
115
+ </Table>
116
+ )}
117
+ </DataTable>
118
+ <Pagination
119
+ className={styles.pagination}
120
+ size="sm"
121
+ page={currentPage}
122
+ pageSize={pageSize}
123
+ pageSizes={[10, 20, 50, 100]}
124
+ totalItems={prevImports.length}
125
+ onChange={({ page, pageSize }) => {
126
+ goTo(page);
127
+ setPageSize(pageSize);
128
+ }}
129
+ />
130
+ </Column>
131
+ </Grid>
132
+ )
133
+ );
134
+ };
135
+
136
+ export default PreviousImports;
@@ -0,0 +1,17 @@
1
+ import { openmrsFetch } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { Import } from '../types';
4
+
5
+ export function usePreviousImports() {
6
+ const { data, error, isValidating } = useSWR<{ data: { results: Import[] } }, Error>(
7
+ '/ws/rest/v1/openconceptlab/import?v=full',
8
+ openmrsFetch,
9
+ );
10
+
11
+ return {
12
+ data: data?.data?.results,
13
+ isLoading: !data && !error,
14
+ isError: error,
15
+ isValidating,
16
+ };
17
+ }
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import { screen, waitFor } from '@testing-library/react';
3
+ import { formatDatetime, openmrsFetch, usePagination } from '@openmrs/esm-framework';
4
+ import { renderWithSwr } from '../../../../tools/test-helpers';
5
+ import { mockPreviousImports } from '../../../../__mocks__/openconceptlab.mock';
6
+ import { Import } from '../types';
7
+ import PreviousImports from './previous-imports.component';
8
+
9
+ const mockOpenmrsFetch = openmrsFetch as jest.Mock;
10
+ const mockUsePagination = usePagination as jest.Mock;
11
+
12
+ jest.mock('@openmrs/esm-framework', () => {
13
+ const originalModule = jest.requireActual('@openmrs/esm-framework');
14
+
15
+ return {
16
+ ...originalModule,
17
+ usePagination: jest.fn(),
18
+ };
19
+ });
20
+
21
+ describe(`Previous Imports component`, () => {
22
+ afterEach(() => {
23
+ mockUsePagination.mockReset();
24
+ });
25
+
26
+ it(`renders without dying`, () => {
27
+ mockUsePagination.mockReturnValue({
28
+ currentPage: 1,
29
+ goTo: () => {},
30
+ results: [],
31
+ });
32
+ renderPreviousImportsComponent();
33
+ });
34
+
35
+ it(`renders the table`, async () => {
36
+ mockOpenmrsFetch.mockReturnValueOnce({ data: { results: [] } });
37
+ mockUsePagination.mockReturnValue({
38
+ currentPage: 1,
39
+ goTo: () => {},
40
+ results: [],
41
+ });
42
+ renderPreviousImportsComponent();
43
+ await waitForLoadingToFinish();
44
+
45
+ expect(screen.getByText('Previous Imports')).toBeVisible();
46
+ expect(screen.getByText('Date and Time')).toBeVisible();
47
+ expect(screen.getByText('Duration')).toBeVisible();
48
+ expect(screen.getByText('Status')).toBeVisible();
49
+ });
50
+
51
+ it(`renders the previous imports correctly`, async () => {
52
+ mockOpenmrsFetch.mockReturnValueOnce({ data: { results: mockPreviousImports } });
53
+ mockUsePagination.mockReturnValue({
54
+ currentPage: 1,
55
+ goTo: () => {},
56
+ results: mockPreviousImports,
57
+ });
58
+ renderPreviousImportsComponent();
59
+ await waitForLoadingToFinish();
60
+
61
+ mockPreviousImports.forEach((item: Import) => {
62
+ expect(screen.getByText(formatDatetime(item.localDateStarted))).toBeVisible();
63
+ expect(screen.getByText(item.importTime)).toBeVisible();
64
+ expect(screen.getByText(item.status)).toBeVisible();
65
+ });
66
+ });
67
+ });
68
+
69
+ function renderPreviousImportsComponent() {
70
+ renderWithSwr(<PreviousImports />);
71
+ }
72
+
73
+ function waitForLoadingToFinish() {
74
+ return waitFor(() => {
75
+ expect(screen.getByText('Previous Imports')).toBeVisible(), { timeout: 2000 };
76
+ });
77
+ }
@@ -0,0 +1,23 @@
1
+ @use "@carbon/styles/scss/spacing";
2
+ @use "@carbon/styles/scss/type";
3
+ @import "~@openmrs/esm-styleguide/src/vars";
4
+
5
+ .main {
6
+ background-color: $ui-02;
7
+ }
8
+
9
+ .moduleHeader {
10
+ @include type.type-style('productive-heading-03');
11
+ padding-left: spacing.$spacing-05;
12
+ padding-top: spacing.$spacing-05;
13
+ padding-bottom: spacing.$spacing-05;
14
+ }
15
+
16
+ .tabList {
17
+ padding: 0 spacing.$spacing-05;
18
+ }
19
+
20
+ .tabPanel {
21
+ height: 100%;
22
+ background-color: $ui-01;
23
+ }
@@ -0,0 +1,39 @@
1
+ import { Tab, Tabs, TabList, TabPanels, TabPanel } from '@carbon/react';
2
+ import React from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { SWRConfig } from 'swr';
5
+ import Subscription from './subscription/subscription.component';
6
+ import styles from './root.component.scss';
7
+ import Import from './import/import.component';
8
+ import PreviousImports from './previous-imports/previous-imports.component';
9
+
10
+ const Root: React.FC = () => {
11
+ const { t } = useTranslation();
12
+ return (
13
+ <SWRConfig>
14
+ <main className={`omrs-main-content ${styles.main}`}>
15
+ <h3 className={styles.moduleHeader}>{t('moduleTitle', 'OCL Subscription Module')}</h3>
16
+ <Tabs>
17
+ <TabList className={styles.tabList} contained={true}>
18
+ <Tab>{t('subscription', 'Subscription')} </Tab>
19
+ <Tab>{t('import', 'Import')} </Tab>
20
+ <Tab>{t('previousImports', 'Previous Imports')} </Tab>
21
+ </TabList>
22
+ <TabPanels>
23
+ <TabPanel className={styles.tabPanel}>
24
+ <Subscription />
25
+ </TabPanel>
26
+ <TabPanel className={styles.tabPanel}>
27
+ <Import />
28
+ </TabPanel>
29
+ <TabPanel className={styles.tabPanel}>
30
+ <PreviousImports />
31
+ </TabPanel>
32
+ </TabPanels>
33
+ </Tabs>
34
+ </main>
35
+ </SWRConfig>
36
+ );
37
+ };
38
+
39
+ export default Root;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { render, cleanup, screen } from '@testing-library/react';
3
+ import Root from './root.component';
4
+
5
+ describe(`Root component`, () => {
6
+ afterEach(cleanup);
7
+ it(`renders without dying`, () => {
8
+ renderOclSubscriptionModule();
9
+ });
10
+
11
+ it(`renders the title and tab containers`, () => {
12
+ renderOclSubscriptionModule();
13
+ expect(screen.getByText('OCL Subscription Module')).toBeInTheDocument();
14
+ expect(screen.getByRole('tab', { name: 'Subscription' })).toBeInTheDocument();
15
+ expect(screen.getByRole('tab', { name: 'Import' })).toBeInTheDocument();
16
+ expect(screen.getByRole('tab', { name: 'Previous Imports' })).toBeInTheDocument();
17
+ });
18
+ });
19
+
20
+ function renderOclSubscriptionModule() {
21
+ render(<Root />);
22
+ }
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/extend-expect';
@@ -0,0 +1,27 @@
1
+ @use "@carbon/styles/scss/spacing";
2
+ @use "@carbon/styles/scss/type";
3
+ @import "~@openmrs/esm-styleguide/src/vars";
4
+
5
+ .grid {
6
+ margin-left: 0;
7
+ margin-right: 0;
8
+ padding-left: 0;
9
+ padding-right: 0;
10
+ }
11
+
12
+ .productiveHeading03 {
13
+ @include type.type-style('productive-heading-03');
14
+ margin-bottom: spacing.$spacing-03;
15
+ }
16
+
17
+ .formGroup {
18
+ margin-bottom: spacing.$spacing-05;
19
+ }
20
+
21
+ .unsubscribeText {
22
+ padding-bottom: spacing.$spacing-05;
23
+ }
24
+
25
+ .unsubscribeForm {
26
+ margin-top: spacing.$spacing-05;
27
+ }
@@ -0,0 +1,244 @@
1
+ import { showNotification } from '@openmrs/esm-framework';
2
+ import {
3
+ Button,
4
+ ButtonSkeleton,
5
+ Checkbox,
6
+ CheckboxSkeleton,
7
+ Column,
8
+ Form,
9
+ FormGroup,
10
+ Grid,
11
+ Stack,
12
+ SkeletonText,
13
+ TextInput,
14
+ TextInputSkeleton,
15
+ } from '@carbon/react';
16
+ import React, { useCallback, useEffect, useState } from 'react';
17
+ import { useTranslation } from 'react-i18next';
18
+ import { deleteSubscription, updateSubscription, useSubscription } from './subscription.resource';
19
+ import styles from './subscription.component.scss';
20
+ import { useSWRConfig } from 'swr';
21
+
22
+ const Subscription: React.FC = () => {
23
+ const { t } = useTranslation();
24
+ const { mutate } = useSWRConfig();
25
+ const [subscriptionUrl, setSubscriptionUrl] = useState('');
26
+ const [token, setToken] = useState('');
27
+ const [isSubscribedToSnapshot, setIsSubscribedToSnapshot] = useState(false);
28
+ const [validationType, setValidationType] = useState<'NONE' | 'FULL'>('FULL');
29
+
30
+ const { data: subscription, isLoading, isError } = useSubscription();
31
+
32
+ useEffect(() => {
33
+ if (!isLoading && !isError) {
34
+ setSubscriptionUrl(subscription?.url || '');
35
+ setToken(subscription?.token || '');
36
+ setIsSubscribedToSnapshot(subscription?.subscribedToSnapshot || false);
37
+ setValidationType(subscription?.validationType || 'FULL');
38
+ }
39
+ }, [isLoading, isError, subscription]);
40
+
41
+ const handleChangeSubscriptionUrl = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
42
+ setSubscriptionUrl(event.target.value);
43
+ }, []);
44
+
45
+ const handleChangeToken = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
46
+ setToken(event.target.value);
47
+ }, []);
48
+
49
+ const handleChangeValidationType = useCallback((checked: boolean) => {
50
+ setValidationType(checked ? 'NONE' : 'FULL');
51
+ }, []);
52
+
53
+ const handleChangeSubscriptionType = useCallback((checked: boolean) => {
54
+ setIsSubscribedToSnapshot(checked);
55
+ }, []);
56
+
57
+ const handleSubmit = useCallback(
58
+ async (evt: React.FormEvent<HTMLFormElement>) => {
59
+ evt.preventDefault();
60
+ evt.stopPropagation();
61
+
62
+ const abortController = new AbortController();
63
+
64
+ const updatedSubscription = {
65
+ ...subscription,
66
+ url: subscriptionUrl,
67
+ token: token,
68
+ validationType: validationType,
69
+ subscribedToSnapshot: isSubscribedToSnapshot,
70
+ };
71
+ mutate('/ws/rest/v1/openconceptlab/subscription?v=full', updatedSubscription, false);
72
+
73
+ const response = await updateSubscription(updatedSubscription, abortController);
74
+ mutate('/ws/rest/v1/openconceptlab/subscription?v=full');
75
+
76
+ if (response.ok) {
77
+ showNotification({
78
+ kind: 'success',
79
+ description: t(
80
+ response.status === 201 ? 'subscriptionCreated' : 'subscriptionUpdated',
81
+ response.status === 201 ? 'Subscription created successfully' : 'Subscription updated successfully',
82
+ ),
83
+ });
84
+ } else {
85
+ showNotification({
86
+ title: t('errorSavingSubscription', 'Error occured while saving the subscription'),
87
+ kind: 'error',
88
+ critical: true,
89
+ description: JSON.stringify(response.data),
90
+ });
91
+ }
92
+
93
+ return () => abortController.abort();
94
+ },
95
+ [subscriptionUrl, token, validationType, isSubscribedToSnapshot, t, subscription, mutate],
96
+ );
97
+
98
+ const handleCancel = useCallback(() => {
99
+ setSubscriptionUrl(subscription?.url || '');
100
+ setToken(subscription?.token || '');
101
+ setIsSubscribedToSnapshot(subscription?.subscribedToSnapshot || false);
102
+ setValidationType(subscription?.validationType || 'FULL');
103
+
104
+ showNotification({
105
+ kind: 'info',
106
+ description: t('cancelledChanges', 'Cancelled changes successfully'),
107
+ });
108
+ }, [subscription, t]);
109
+
110
+ const handleUnsubscribe = useCallback(
111
+ async (evt: React.FormEvent<HTMLFormElement>) => {
112
+ evt.preventDefault();
113
+ evt.stopPropagation();
114
+ const abortController = new AbortController();
115
+
116
+ const response = await deleteSubscription(subscription, abortController);
117
+ mutate('/ws/rest/v1/openconceptlab/subscription?v=full');
118
+
119
+ if (response.status === 204) {
120
+ setSubscriptionUrl('');
121
+ setToken('');
122
+ setIsSubscribedToSnapshot(false);
123
+ setValidationType('FULL');
124
+ showNotification({
125
+ kind: 'success',
126
+ description: t('subscriptionDeleted', 'Successfully unsubscribed'),
127
+ });
128
+ } else {
129
+ showNotification({
130
+ title: t('errorDeletingSubscription', 'Error occured while deleting the subscription'),
131
+ kind: 'error',
132
+ critical: true,
133
+ description: JSON.stringify(response.data),
134
+ });
135
+ }
136
+
137
+ return () => abortController.abort();
138
+ },
139
+ [subscription, t, mutate],
140
+ );
141
+
142
+ if (isLoading) {
143
+ return (
144
+ <Grid className={styles.grid}>
145
+ <Column sm={4} md={8} lg={10}>
146
+ <Form>
147
+ <SkeletonText className={styles.productiveHeading03} />
148
+ <Stack gap={5}>
149
+ <TextInputSkeleton />
150
+ <TextInputSkeleton />
151
+ <FormGroup legendText={<SkeletonText width="75px" />} className={styles.formGroup}>
152
+ <CheckboxSkeleton />
153
+ <CheckboxSkeleton />
154
+ </FormGroup>
155
+ </Stack>
156
+ <ButtonSkeleton />
157
+ <ButtonSkeleton />
158
+ </Form>
159
+ <Form className={styles.unsubscribeForm}>
160
+ <SkeletonText className={styles.productiveHeading03} />
161
+ <SkeletonText className={styles.unsubscribeText} />
162
+ <ButtonSkeleton />
163
+ </Form>
164
+ </Column>
165
+ </Grid>
166
+ );
167
+ }
168
+
169
+ if (isError) {
170
+ showNotification({
171
+ kind: 'error',
172
+ description: t('subscriptionError', 'Error occured while fetching the subscription'),
173
+ });
174
+ }
175
+
176
+ return (
177
+ <Grid className={styles.grid}>
178
+ <Column sm={4} md={8} lg={10}>
179
+ <Form onSubmit={handleSubmit}>
180
+ <h3 className={styles.productiveHeading03}>{t('setupSubscription', 'Setup Subscription')}</h3>
181
+ <Stack gap={5}>
182
+ <TextInput
183
+ id="subscriptionUrl"
184
+ type="url"
185
+ labelText={t('subscriptionUrl', 'Subscription URL')}
186
+ placeholder="https://api.openconceptlab.org/orgs/organization-name/collections/dictionary-name"
187
+ value={subscriptionUrl}
188
+ onChange={handleChangeSubscriptionUrl}
189
+ light={true}
190
+ required
191
+ />
192
+ <TextInput
193
+ id="apiToken"
194
+ type="password"
195
+ placeholder="••••••••••••••••••••••••••••••••••••••••••••••••"
196
+ labelText={t('apiToken', 'Token')}
197
+ value={token}
198
+ onChange={handleChangeToken}
199
+ light={true}
200
+ required
201
+ />
202
+ <FormGroup legendText={t('advancedOptions', 'Advanced Options')} className={styles.formGroup}>
203
+ <Checkbox
204
+ checked={isSubscribedToSnapshot}
205
+ onChange={handleChangeSubscriptionType}
206
+ labelText={t('subscribeToSnapshotText', 'Subscribe to SNAPSHOT versions (not recommended)')}
207
+ id="isSubscribedToSnapshot"
208
+ />
209
+ <Checkbox
210
+ checked={validationType === 'NONE'}
211
+ onChange={handleChangeValidationType}
212
+ labelText={t(
213
+ 'disableValidationText',
214
+ 'Disable validation (should be used with care for well curated collections or sources)',
215
+ )}
216
+ id="isValidationDisabled"
217
+ />
218
+ </FormGroup>
219
+ </Stack>
220
+ <Button kind="secondary" onClick={handleCancel}>
221
+ {t('cancelButton', 'Cancel changes')}
222
+ </Button>
223
+ <Button kind="primary" type="submit">
224
+ {t('subscribeButton', 'Save changes')}
225
+ </Button>
226
+ </Form>
227
+ <Form onSubmit={handleUnsubscribe} className={styles.unsubscribeForm}>
228
+ <h3 className={styles.productiveHeading03}>{t('unsubscribe', 'Unsubscribe')}</h3>
229
+ <p className={styles.unsubscribeText}>
230
+ {t(
231
+ 'unsubscribeInfo',
232
+ 'If you unsubscribe, no concepts will be deleted nor changed. All information about subscription will be deleted from your system.',
233
+ )}
234
+ </p>
235
+ <Button kind="danger" type="submit" disabled={!subscription}>
236
+ {t('unsubscribeButton', 'Unsubscribe')}
237
+ </Button>
238
+ </Form>
239
+ </Column>
240
+ </Grid>
241
+ );
242
+ };
243
+
244
+ export default Subscription;
@@ -0,0 +1,40 @@
1
+ import { openmrsFetch } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { Subscription } from '../types';
4
+ import isNil from 'lodash-es/isNil';
5
+
6
+ export function useSubscription() {
7
+ const { data, error, isValidating } = useSWR<{ data: { results: Subscription[] } }, Error>(
8
+ '/ws/rest/v1/openconceptlab/subscription?v=full',
9
+ openmrsFetch,
10
+ );
11
+
12
+ return {
13
+ data: data?.data?.results[0],
14
+ isLoading: !data && !error,
15
+ isError: error,
16
+ isValidating,
17
+ };
18
+ }
19
+
20
+ export async function updateSubscription(subscription: Subscription, abortController?: AbortController) {
21
+ const url = isNil(subscription.uuid)
22
+ ? '/ws/rest/v1/openconceptlab/subscription'
23
+ : `/ws/rest/v1/openconceptlab/subscription/${subscription.uuid}`;
24
+ return openmrsFetch<Subscription>(url, {
25
+ method: 'POST',
26
+ body: subscription,
27
+ headers: {
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ signal: abortController?.signal,
31
+ });
32
+ }
33
+
34
+ export async function deleteSubscription(subscription: Subscription, abortController?: AbortController) {
35
+ const url = `/ws/rest/v1/openconceptlab/subscription/${subscription.uuid}`;
36
+ return openmrsFetch<Subscription>(url, {
37
+ method: 'DELETE',
38
+ signal: abortController?.signal,
39
+ });
40
+ }