@openmrs/esm-patient-lists-app 11.3.1-patch.9310 → 11.3.1-patch.9508
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/.turbo/turbo-build.log +17 -20
- package/dist/136.js +1 -1
- package/dist/136.js.map +1 -1
- package/dist/3125.js +1 -1
- package/dist/3125.js.map +1 -1
- package/dist/439.js +1 -0
- package/dist/5670.js +1 -1
- package/dist/5984.js +1 -1
- package/dist/5984.js.map +1 -1
- package/dist/6589.js +1 -0
- package/dist/8371.js +1 -0
- package/dist/8743.js +2 -0
- package/dist/8743.js.map +1 -0
- package/dist/8803.js +1 -0
- package/dist/8803.js.map +1 -0
- package/dist/9078.js +1 -1
- package/dist/9078.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-lists-app.js +1 -1
- package/dist/openmrs-esm-patient-lists-app.js.buildmanifest.json +135 -69
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/action-button/patient-lists-action-button.extension.tsx +8 -6
- package/src/index.ts +2 -0
- package/src/routes.json +16 -12
- package/src/workspaces/patient-list-details.workspace.test.tsx +19 -26
- package/src/workspaces/patient-list-details.workspace.tsx +32 -38
- package/src/workspaces/patient-lists.workspace.test.tsx +3 -22
- package/src/workspaces/patient-lists.workspace.tsx +81 -90
- package/translations/cs.json +21 -0
- package/translations/sq.json +21 -0
- package/translations/zh_TW.json +21 -0
- package/dist/2499.js +0 -2
- package/dist/2499.js.map +0 -1
- package/dist/4341.js +0 -1
- package/dist/4341.js.map +0 -1
- /package/dist/{2499.js.LICENSE.txt → 8743.js.LICENSE.txt} +0 -0
|
@@ -1,36 +1,29 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { screen, render } from '@testing-library/react';
|
|
3
3
|
import { openmrsFetch } from '@openmrs/esm-framework';
|
|
4
|
-
import PatientListDetailsWorkspace
|
|
5
|
-
import { type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib';
|
|
4
|
+
import PatientListDetailsWorkspace from './patient-list-details.workspace';
|
|
6
5
|
|
|
7
6
|
const mockOpenmrsFetch = openmrsFetch as jest.Mock;
|
|
8
7
|
|
|
9
|
-
const defaultProps
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
visitContext: null,
|
|
14
|
-
mutateVisitContext: null,
|
|
15
|
-
},
|
|
8
|
+
const defaultProps = {
|
|
9
|
+
patientUuid: '',
|
|
10
|
+
patient: undefined,
|
|
11
|
+
promptBeforeClosing: jest.fn(),
|
|
16
12
|
closeWorkspace: jest.fn(),
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
},
|
|
13
|
+
closeWorkspaceWithSavedChanges: jest.fn(),
|
|
14
|
+
list: {
|
|
15
|
+
attributes: [],
|
|
16
|
+
description:
|
|
17
|
+
'Cardiovascular Outcomes in Type 2 Diabetes (COTD Study): A Longitudinal Assessment of a New Diabetes Medication',
|
|
18
|
+
id: '4dcb7061-fcad-4542-b219-4c197c117050',
|
|
19
|
+
name: 'COTD Study',
|
|
20
|
+
size: 2,
|
|
21
|
+
startDate: '2023-11-14T23:45:51.000+0000',
|
|
22
|
+
type: 'My List',
|
|
28
23
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
windowName: '',
|
|
33
|
-
isRootWorkspace: false,
|
|
24
|
+
setTitle: jest.fn(),
|
|
25
|
+
visitContext: null,
|
|
26
|
+
mutateVisitContext: null,
|
|
34
27
|
};
|
|
35
28
|
|
|
36
29
|
const mockPatientListData = [
|
|
@@ -123,7 +116,7 @@ it('renders the patient list details workspace', async () => {
|
|
|
123
116
|
|
|
124
117
|
render(<PatientListDetailsWorkspace {...defaultProps} />);
|
|
125
118
|
|
|
126
|
-
await screen.findByRole('heading', { name: defaultProps.
|
|
119
|
+
await screen.findByRole('heading', { name: defaultProps.list.description });
|
|
127
120
|
|
|
128
121
|
expect(screen.getByRole('button', { name: /back to patient lists/i })).toBeInTheDocument();
|
|
129
122
|
expect(screen.getByText(/2 patients/i)).toBeInTheDocument();
|
|
@@ -1,56 +1,50 @@
|
|
|
1
1
|
import React, { type ComponentProps, useCallback } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Button } from '@carbon/react';
|
|
4
|
-
import { ArrowLeftIcon, formatDate,
|
|
5
|
-
import { type
|
|
4
|
+
import { ArrowLeftIcon, formatDate, launchWorkspace, parseDate } from '@openmrs/esm-framework';
|
|
5
|
+
import { type DefaultPatientWorkspaceProps } from '@openmrs/esm-patient-common-lib';
|
|
6
6
|
import { type MappedList, usePatientListMembers } from '../patient-lists.resource';
|
|
7
7
|
import PatientListDetailsTable from './patient-list-details-table.component';
|
|
8
8
|
import styles from './patient-list-details.scss';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface PatientListDetailsWorkspaceProps extends DefaultPatientWorkspaceProps {
|
|
11
11
|
list: MappedList;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
workspaceProps: { list },
|
|
16
|
-
closeWorkspace,
|
|
17
|
-
}) => {
|
|
14
|
+
function PatientListDetailsWorkspace({ list }: PatientListDetailsWorkspaceProps) {
|
|
18
15
|
const { t } = useTranslation();
|
|
19
16
|
const { listMembers, isLoading } = usePatientListMembers(list.id);
|
|
20
17
|
|
|
21
|
-
const closeListDetailsWorkspace = useCallback(() =>
|
|
18
|
+
const closeListDetailsWorkspace = useCallback(() => {
|
|
19
|
+
launchWorkspace('patient-lists');
|
|
20
|
+
}, []);
|
|
22
21
|
|
|
23
22
|
return (
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<section className={styles.tableContainer}>
|
|
49
|
-
<PatientListDetailsTable isLoading={isLoading} listMembers={listMembers} />
|
|
50
|
-
</section>
|
|
51
|
-
</main>
|
|
52
|
-
</Workspace2>
|
|
23
|
+
<main className={styles.container}>
|
|
24
|
+
<section className={styles.header}>
|
|
25
|
+
<h4 className={styles.description}>{list.description ?? '--'}</h4>
|
|
26
|
+
<p className={styles.details}>
|
|
27
|
+
{list.size} {t('patients', 'patients')} ·{' '}
|
|
28
|
+
<span className={styles.label}>{t('createdOn', 'Created on')}:</span>{' '}
|
|
29
|
+
{list.startDate ? formatDate(parseDate(list.startDate)) : null}
|
|
30
|
+
</p>
|
|
31
|
+
</section>
|
|
32
|
+
<div className={styles.backButton}>
|
|
33
|
+
<Button
|
|
34
|
+
kind="ghost"
|
|
35
|
+
renderIcon={(props: ComponentProps<typeof ArrowLeftIcon>) => <ArrowLeftIcon size={24} {...props} />}
|
|
36
|
+
iconDescription="Back to patient lists"
|
|
37
|
+
size="sm"
|
|
38
|
+
onClick={closeListDetailsWorkspace}
|
|
39
|
+
>
|
|
40
|
+
<span>{t('backToPatientLists', 'Back to patient lists')}</span>
|
|
41
|
+
</Button>
|
|
42
|
+
</div>
|
|
43
|
+
<section className={styles.tableContainer}>
|
|
44
|
+
<PatientListDetailsTable isLoading={isLoading} listMembers={listMembers} />
|
|
45
|
+
</section>
|
|
46
|
+
</main>
|
|
53
47
|
);
|
|
54
|
-
}
|
|
48
|
+
}
|
|
55
49
|
|
|
56
50
|
export default PatientListDetailsWorkspace;
|
|
@@ -23,7 +23,8 @@ it('renders an empty state if patient list data is unavailable', async () => {
|
|
|
23
23
|
mutate: jest.fn(),
|
|
24
24
|
isValidating: false,
|
|
25
25
|
});
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
render(<PatientListsWorkspace />);
|
|
27
28
|
|
|
28
29
|
expect(screen.getByTitle(/empty data illustration/i)).toBeInTheDocument();
|
|
29
30
|
expect(screen.getByText(/no patient lists to display/i)).toBeInTheDocument();
|
|
@@ -51,7 +52,7 @@ it('renders a tabular overview of the available patient lists', async () => {
|
|
|
51
52
|
isValidating: false,
|
|
52
53
|
});
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
render(<PatientListsWorkspace />);
|
|
55
56
|
|
|
56
57
|
await screen.findByRole('table');
|
|
57
58
|
|
|
@@ -74,23 +75,3 @@ it('renders a tabular overview of the available patient lists', async () => {
|
|
|
74
75
|
await user.type(searchbox, 'COTD');
|
|
75
76
|
expect(screen.getByRole('row', { name: /COTD Study My List 2/i })).toBeInTheDocument();
|
|
76
77
|
});
|
|
77
|
-
|
|
78
|
-
function renderPatientListWorkspace() {
|
|
79
|
-
render(
|
|
80
|
-
<PatientListsWorkspace
|
|
81
|
-
workspaceName={''}
|
|
82
|
-
launchChildWorkspace={jest.fn()}
|
|
83
|
-
closeWorkspace={jest.fn()}
|
|
84
|
-
workspaceProps={{}}
|
|
85
|
-
windowProps={{}}
|
|
86
|
-
groupProps={{
|
|
87
|
-
mutateVisitContext: jest.fn(),
|
|
88
|
-
patient: null,
|
|
89
|
-
patientUuid: null,
|
|
90
|
-
visitContext: null,
|
|
91
|
-
}}
|
|
92
|
-
windowName={''}
|
|
93
|
-
isRootWorkspace={false}
|
|
94
|
-
/>,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
@@ -18,24 +18,21 @@ import {
|
|
|
18
18
|
TableToolbarSearch,
|
|
19
19
|
Tile,
|
|
20
20
|
} from '@carbon/react';
|
|
21
|
-
import { EmptyDataIllustration
|
|
22
|
-
import { launchWorkspace, useLayoutType
|
|
23
|
-
import { usePatientLists } from '../patient-lists.resource';
|
|
21
|
+
import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
|
|
22
|
+
import { launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
|
|
23
|
+
import { usePatientLists, type MappedList } from '../patient-lists.resource';
|
|
24
24
|
import styles from './patient-lists.scss';
|
|
25
25
|
|
|
26
|
-
function PatientListsWorkspace(
|
|
26
|
+
function PatientListsWorkspace() {
|
|
27
27
|
const { t } = useTranslation();
|
|
28
28
|
const layout = useLayoutType();
|
|
29
29
|
const responsiveSize = layout === 'tablet' ? 'lg' : 'sm';
|
|
30
30
|
const [searchTerm, setSearchTerm] = useState('');
|
|
31
31
|
const { patientLists, isLoading } = usePatientLists();
|
|
32
32
|
|
|
33
|
-
const launchListDetailsWorkspace = useCallback(
|
|
34
|
-
(list
|
|
35
|
-
|
|
36
|
-
},
|
|
37
|
-
[launchChildWorkspace],
|
|
38
|
-
);
|
|
33
|
+
const launchListDetailsWorkspace = useCallback((list: MappedList) => {
|
|
34
|
+
launchWorkspace('patient-list-details', { list, workspaceTitle: list.name });
|
|
35
|
+
}, []);
|
|
39
36
|
|
|
40
37
|
const tableHeaders = [
|
|
41
38
|
{
|
|
@@ -69,96 +66,90 @@ function PatientListsWorkspace({ launchChildWorkspace }: PatientWorkspace2Defini
|
|
|
69
66
|
|
|
70
67
|
if (isLoading)
|
|
71
68
|
return (
|
|
72
|
-
<
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
</div>
|
|
76
|
-
</Workspace2>
|
|
69
|
+
<div className={styles.skeletonContainer}>
|
|
70
|
+
<DataTableSkeleton className={styles.dataTableSkeleton} rowCount={5} columnCount={5} zebra />
|
|
71
|
+
</div>
|
|
77
72
|
);
|
|
78
73
|
|
|
79
74
|
if (patientLists?.length > 0) {
|
|
80
75
|
return (
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const currentList = patientLists?.find((list) => list?.id === row.id);
|
|
76
|
+
<section className={styles.container}>
|
|
77
|
+
<DataTable headers={tableHeaders} rows={filteredLists || []} size={responsiveSize} useZebraStyles>
|
|
78
|
+
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
79
|
+
<>
|
|
80
|
+
<TableContainer className={styles.tableContainer}>
|
|
81
|
+
<div className={styles.toolbarWrapper}>
|
|
82
|
+
<TableToolbar className={styles.tableToolbar}>
|
|
83
|
+
<TableToolbarContent>
|
|
84
|
+
<TableToolbarSearch
|
|
85
|
+
className={styles.search}
|
|
86
|
+
expanded
|
|
87
|
+
onChange={handleSearchTermChange}
|
|
88
|
+
placeholder={t('searchThisList', 'Search this list')}
|
|
89
|
+
size="sm"
|
|
90
|
+
/>
|
|
91
|
+
</TableToolbarContent>
|
|
92
|
+
</TableToolbar>
|
|
93
|
+
</div>
|
|
94
|
+
{rows.length > 0 ? (
|
|
95
|
+
<Table aria-label="patient lists" className={styles.table} {...getTableProps()}>
|
|
96
|
+
<TableHead>
|
|
97
|
+
<TableRow>
|
|
98
|
+
{headers.map((header) => (
|
|
99
|
+
<TableHeader key={header.key} {...getHeaderProps({ header })}>
|
|
100
|
+
{header.header}
|
|
101
|
+
</TableHeader>
|
|
102
|
+
))}
|
|
103
|
+
</TableRow>
|
|
104
|
+
</TableHead>
|
|
105
|
+
<TableBody>
|
|
106
|
+
{rows.map((row) => {
|
|
107
|
+
const currentList = patientLists?.find((list) => list?.id === row.id);
|
|
114
108
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
) : null}
|
|
130
|
-
</TableContainer>
|
|
131
|
-
{filteredLists?.length === 0 ? (
|
|
132
|
-
<div className={styles.tileContainer}>
|
|
133
|
-
<Tile className={styles.tile}>
|
|
134
|
-
<div className={styles.tileContent}>
|
|
135
|
-
<p className={styles.content}>{t('noMatchingListsFound', 'No matching lists to display')}</p>
|
|
136
|
-
<p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
|
|
137
|
-
</div>
|
|
138
|
-
</Tile>
|
|
139
|
-
</div>
|
|
109
|
+
return (
|
|
110
|
+
<TableRow {...getRowProps({ row })} key={row.id}>
|
|
111
|
+
<TableCell>
|
|
112
|
+
<Link className={styles.link} onClick={() => launchListDetailsWorkspace(currentList)}>
|
|
113
|
+
{currentList?.name ?? ''}
|
|
114
|
+
</Link>
|
|
115
|
+
</TableCell>
|
|
116
|
+
<TableCell>{currentList?.type ?? ''}</TableCell>
|
|
117
|
+
<TableCell>{currentList?.size ?? ''}</TableCell>
|
|
118
|
+
</TableRow>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</TableBody>
|
|
122
|
+
</Table>
|
|
140
123
|
) : null}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
124
|
+
</TableContainer>
|
|
125
|
+
{filteredLists?.length === 0 ? (
|
|
126
|
+
<div className={styles.tileContainer}>
|
|
127
|
+
<Tile className={styles.tile}>
|
|
128
|
+
<div className={styles.tileContent}>
|
|
129
|
+
<p className={styles.content}>{t('noMatchingListsFound', 'No matching lists to display')}</p>
|
|
130
|
+
<p className={styles.helper}>{t('checkFilters', 'Check the filters above')}</p>
|
|
131
|
+
</div>
|
|
132
|
+
</Tile>
|
|
133
|
+
</div>
|
|
134
|
+
) : null}
|
|
135
|
+
</>
|
|
136
|
+
)}
|
|
137
|
+
</DataTable>
|
|
138
|
+
</section>
|
|
146
139
|
);
|
|
147
140
|
}
|
|
148
141
|
|
|
149
142
|
return (
|
|
150
|
-
<
|
|
151
|
-
<
|
|
152
|
-
<
|
|
153
|
-
<
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
</div>
|
|
161
|
-
</Workspace2>
|
|
143
|
+
<div className={styles.emptyStateContainer}>
|
|
144
|
+
<Layer>
|
|
145
|
+
<Tile className={styles.emptyStateTile}>
|
|
146
|
+
<div className={styles.tileContent}>
|
|
147
|
+
<EmptyDataIllustration />
|
|
148
|
+
<p className={styles.emptyStateContent}>{t('noPatientListsToDisplay', 'No patient lists to display')}</p>
|
|
149
|
+
</div>
|
|
150
|
+
</Tile>
|
|
151
|
+
</Layer>
|
|
152
|
+
</div>
|
|
162
153
|
);
|
|
163
154
|
}
|
|
164
155
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"backToPatientLists": "Back to patient lists",
|
|
3
|
+
"checkFilters": "Check the filters above",
|
|
4
|
+
"createdOn": "Created on",
|
|
5
|
+
"identifier": "Identifier",
|
|
6
|
+
"listName": "List name",
|
|
7
|
+
"listType": "List type",
|
|
8
|
+
"name": "Name",
|
|
9
|
+
"noMatchingListsFound": "No matching lists to display",
|
|
10
|
+
"noMatchingPatients": "No matching patients to display",
|
|
11
|
+
"noPatientListsToDisplay": "No patient lists to display",
|
|
12
|
+
"noPatientsInList": "There are no patients in this list",
|
|
13
|
+
"numberOfPatients": "No. of patients",
|
|
14
|
+
"patientListDetailWorkspaceTitle": "Patient List Details",
|
|
15
|
+
"patientLists": "Patient lists",
|
|
16
|
+
"patientListsWorkspaceTitle": "Patient Lists",
|
|
17
|
+
"patients": "patients",
|
|
18
|
+
"searchThisList": "Search this list",
|
|
19
|
+
"sex": "Sex",
|
|
20
|
+
"startDate": "Start Date"
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"backToPatientLists": "Back to patient lists",
|
|
3
|
+
"checkFilters": "Check the filters above",
|
|
4
|
+
"createdOn": "Created on",
|
|
5
|
+
"identifier": "Identifier",
|
|
6
|
+
"listName": "List name",
|
|
7
|
+
"listType": "List type",
|
|
8
|
+
"name": "Name",
|
|
9
|
+
"noMatchingListsFound": "No matching lists to display",
|
|
10
|
+
"noMatchingPatients": "No matching patients to display",
|
|
11
|
+
"noPatientListsToDisplay": "No patient lists to display",
|
|
12
|
+
"noPatientsInList": "There are no patients in this list",
|
|
13
|
+
"numberOfPatients": "No. of patients",
|
|
14
|
+
"patientListDetailWorkspaceTitle": "Patient List Details",
|
|
15
|
+
"patientLists": "Patient lists",
|
|
16
|
+
"patientListsWorkspaceTitle": "Patient Lists",
|
|
17
|
+
"patients": "patients",
|
|
18
|
+
"searchThisList": "Search this list",
|
|
19
|
+
"sex": "Sex",
|
|
20
|
+
"startDate": "Start Date"
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"backToPatientLists": "Back to patient lists",
|
|
3
|
+
"checkFilters": "Check the filters above",
|
|
4
|
+
"createdOn": "Created on",
|
|
5
|
+
"identifier": "Identifier",
|
|
6
|
+
"listName": "List name",
|
|
7
|
+
"listType": "List type",
|
|
8
|
+
"name": "Name",
|
|
9
|
+
"noMatchingListsFound": "No matching lists to display",
|
|
10
|
+
"noMatchingPatients": "No matching patients to display",
|
|
11
|
+
"noPatientListsToDisplay": "No patient lists to display",
|
|
12
|
+
"noPatientsInList": "There are no patients in this list",
|
|
13
|
+
"numberOfPatients": "No. of patients",
|
|
14
|
+
"patientListDetailWorkspaceTitle": "Patient List Details",
|
|
15
|
+
"patientLists": "Patient lists",
|
|
16
|
+
"patientListsWorkspaceTitle": "Patient Lists",
|
|
17
|
+
"patients": "patients",
|
|
18
|
+
"searchThisList": "Search this list",
|
|
19
|
+
"sex": "Sex",
|
|
20
|
+
"startDate": "Start Date"
|
|
21
|
+
}
|