Skip to content

Commit d36c528

Browse files
authoredJul 16, 2021
Merge branch 'main' into MF-647
2 parents 74cf576 + a9b6d31 commit d36c528

34 files changed

+1734
-634
lines changed
 

‎.github/workflows/node.js.yml

+42
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,48 @@ jobs:
3131
path: |
3232
packages/**/dist
3333
34+
deploy_active_visits_app:
35+
runs-on: ubuntu-latest
36+
37+
env:
38+
DIR_NAME: "esm-active-visits-app"
39+
ESM_NAME: "@openmrs/esm-active-visits-app"
40+
JS_NAME: "openmrs-esm-active-visits-app.js"
41+
42+
needs: build
43+
44+
if: ${{ github.event_name == 'push' }}
45+
46+
steps:
47+
- name: Download Artifacts
48+
uses: actions/download-artifact@v2
49+
- name: Compute Timestamp
50+
run: echo "TIMESTAMP=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
51+
- name: Prepare Directory
52+
shell: bash
53+
run: |
54+
mkdir -p dist/${{ env.ESM_NAME }}/${{ env.TIMESTAMP }}_${{ github.sha }}
55+
mv packages/${{ env.DIR_NAME }}/dist/*.* dist/${{ env.ESM_NAME }}/${{ env.TIMESTAMP }}_${{ github.sha }}/
56+
- name: Publish to Digital Ocean
57+
uses: jakejarvis/s3-sync-action@master
58+
with:
59+
args: --acl public-read --follow-symlinks --cache-control "max-age=31536000"
60+
env:
61+
AWS_S3_BUCKET: ${{ secrets.DIGITAL_OCEAN_SPACES_BUCKET }}
62+
AWS_ACCESS_KEY_ID: ${{ secrets.DIGITAL_OCEAN_SPACES_KEY_ID }}
63+
AWS_SECRET_ACCESS_KEY: ${{ secrets.DIGITAL_OCEAN_SPACES_ACCESS_KEY }}
64+
AWS_S3_ENDPOINT: ${{ secrets.DIGITAL_OCEAN_SPACES_ENDPOINT }}
65+
SOURCE_DIR: "dist"
66+
- name: Update Importmap
67+
uses: fjogeleit/http-request-action@master
68+
with:
69+
url: http://${{ secrets.DEPLOYER_HOST }}/services?env=prod
70+
method: "PATCH"
71+
username: ${{ secrets.DEPLOYER_USERNAME }}
72+
password: ${{ secrets.DEPLOYER_PASSWORD }}
73+
data: '{ "service":"${{ env.ESM_NAME }}","url":"https://spa-modules.nyc3.digitaloceanspaces.com/${{ env.ESM_NAME }}/${{ env.TIMESTAMP }}_${{ github.sha }}/${{ env.JS_NAME }}" }'
74+
customHeaders: '{ "Accept": "application/json", "Content-Type": "application/json" }'
75+
3476
deploy_patient_search_app:
3577
runs-on: ubuntu-latest
3678

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"presets": [
3+
"@babel/preset-env",
4+
"@babel/preset-typescript",
5+
"@babel/preset-react"
6+
],
7+
"plugins": ["@babel/plugin-proposal-class-properties"]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "@openmrs/esm-active-visits-app",
3+
"version": "3.0.0",
4+
"description": "Active visits widget microfrontend for the OpenMRS SPA",
5+
"browser": "dist/openmrs-esm-active-visits-app.js",
6+
"main": "src/index.ts",
7+
"source": true,
8+
"license": "MPL-2.0",
9+
"homepage": "https://github.com/openmrs/openmrs-esm-patient-management#readme",
10+
"scripts": {
11+
"start": "openmrs develop",
12+
"serve": "webpack serve --mode=development",
13+
"debug": "npm run serve",
14+
"build": "webpack --mode production",
15+
"analyze": "webpack --mode=production --env.analyze=true",
16+
"lint": "eslint src --ext tsx",
17+
"typescript": "tsc",
18+
"extract-translations": "i18next 'src/**/*.component.tsx'"
19+
},
20+
"browserslist": [
21+
"extends browserslist-config-openmrs"
22+
],
23+
"keywords": [
24+
"openmrs"
25+
],
26+
"publishConfig": {
27+
"access": "public"
28+
},
29+
"repository": {
30+
"type": "git",
31+
"url": "git+https://github.com/openmrs/openmrs-esm-patient-management.git"
32+
},
33+
"bugs": {
34+
"url": "https://github.com/openmrs/openmrs-esm-patient-management/issues"
35+
},
36+
"dependencies": {
37+
"@carbon/charts-react": "^0.41.14",
38+
"@carbon/icons-react": "^10.18.0",
39+
"carbon-components-react": "^7.25.0",
40+
"d3": "^5.16.0",
41+
"lodash-es": "^4.17.15"
42+
},
43+
"peerDependencies": {
44+
"@openmrs/esm-framework": "3.x",
45+
"carbon-components": "10.x",
46+
"carbon-icons": "7.x",
47+
"dayjs": "1.x",
48+
"react": "16.x",
49+
"react-i18next": "11.x",
50+
"react-router-dom": "5.x",
51+
"rxjs": "6.x"
52+
},
53+
"devDependencies": {}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import React, { useMemo, useEffect, useState, useCallback } from 'react';
2+
import DataTable, {
3+
TableContainer,
4+
Table,
5+
TableHead,
6+
TableRow,
7+
TableHeader,
8+
TableBody,
9+
TableCell,
10+
TableToolbar,
11+
TableToolbarContent,
12+
} from 'carbon-components-react/es/components/DataTable';
13+
import DataTableSkeleton from 'carbon-components-react/es/components/DataTableSkeleton';
14+
import Pagination from 'carbon-components-react/es/components/Pagination';
15+
import Search from 'carbon-components-react/es/components/Search';
16+
import { useLayoutType, useConfig, usePagination, ConfigurableLink } from '@openmrs/esm-framework';
17+
import { ActiveVisitRow, fetchActiveVisits } from './active-visits.resource';
18+
import styles from './active-visits.scss';
19+
import { useTranslation } from 'react-i18next';
20+
import dayjs from 'dayjs';
21+
22+
const headerData = [
23+
{
24+
id: 0,
25+
header: 'Visit Time',
26+
key: 'visitStartTime',
27+
},
28+
{
29+
id: 1,
30+
header: 'ID Number',
31+
key: 'IDNumber',
32+
},
33+
{
34+
id: 2,
35+
header: 'Name',
36+
key: 'name',
37+
},
38+
{
39+
id: 3,
40+
header: 'Gender',
41+
key: 'gender',
42+
},
43+
{
44+
id: 4,
45+
header: 'Age',
46+
key: 'age',
47+
},
48+
{
49+
id: 5,
50+
header: 'Visit Type',
51+
key: 'visitType',
52+
},
53+
];
54+
55+
function formatDatetime(startDatetime) {
56+
const todayDate = dayjs();
57+
const today =
58+
dayjs(startDatetime).get('date') === todayDate.get('date') &&
59+
dayjs(startDatetime).get('month') === todayDate.get('month') &&
60+
dayjs(startDatetime).get('year') === todayDate.get('year');
61+
if (today) {
62+
return `Today - ${dayjs(startDatetime).format('HH:mm')}`;
63+
} else {
64+
return dayjs(startDatetime).format("DD MMM 'YY - HH:mm");
65+
}
66+
}
67+
68+
const ActiveVisitsTable = (props) => {
69+
const { t } = useTranslation();
70+
const layout = useLayoutType();
71+
const desktopView = layout === 'desktop';
72+
const config = useConfig();
73+
const [currentPageSize, setPageSize] = useState(config?.activeVisits?.pageSize ?? 10);
74+
const pageSizes = config?.activeVisits?.pageSizes ?? [10, 20, 50];
75+
const [loading, setLoading] = useState(true);
76+
const [activeVisits, setActiveVisits] = useState<ActiveVisitRow[]>([]);
77+
const [searchString, setSearchString] = useState('');
78+
79+
const searchResults = useMemo(() => {
80+
if (searchString && searchString.trim() !== '') {
81+
const search = searchString.toLowerCase();
82+
return activeVisits.filter((activeVisitRow) =>
83+
Object.keys(activeVisitRow).some((header) => {
84+
if (header === 'patientUuid') {
85+
return false;
86+
}
87+
return `${activeVisitRow[header]}`.toLowerCase().includes(search);
88+
}),
89+
);
90+
} else {
91+
return activeVisits;
92+
}
93+
}, [searchString, activeVisits]);
94+
const { goTo, currentPage, results } = usePagination(searchResults, currentPageSize);
95+
96+
useEffect(() => {
97+
const activeVisits = fetchActiveVisits().subscribe((data) => {
98+
const rowData = data.results.map((visit, ind) => ({
99+
id: `${ind}`,
100+
visitStartTime: formatDatetime(visit.startDatetime),
101+
IDNumber: visit?.patient?.identifiers[0]?.identifier,
102+
name: visit?.patient?.person?.display,
103+
gender: visit?.patient?.person?.gender,
104+
age: visit?.patient?.person?.age,
105+
visitType: visit?.visitType.display,
106+
patientUuid: visit?.patient?.uuid,
107+
}));
108+
setActiveVisits(rowData);
109+
setLoading(false);
110+
});
111+
return () => activeVisits.unsubscribe();
112+
}, []);
113+
114+
const handleSearch = useCallback((e) => setSearchString(e.target.value), []);
115+
116+
return !loading ? (
117+
<div className={styles.activeVisitsContainer}>
118+
<div className={styles.activeVisitsDetailHeaderContainer}>
119+
<h4 className={styles.productiveHeading02}>{t('activeVisits', 'Active Visits')}</h4>
120+
</div>
121+
<DataTable rows={results} headers={headerData} isSortable>
122+
{({ rows, headers, getHeaderProps, getTableProps, getBatchActionProps }) => (
123+
<TableContainer title="" className={styles.tableContainer}>
124+
<TableToolbar>
125+
<TableToolbarContent>
126+
<Search
127+
tabIndex={getBatchActionProps().shouldShowBatchActions ? -1 : 0}
128+
placeholder="Filter table"
129+
onChange={handleSearch}
130+
/>
131+
</TableToolbarContent>
132+
</TableToolbar>
133+
<Table {...getTableProps()} useZebraStyles>
134+
<TableHead>
135+
<TableRow style={{ height: desktopView ? '2rem' : '3rem' }}>
136+
{headers.map((header) => (
137+
<TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader>
138+
))}
139+
</TableRow>
140+
</TableHead>
141+
<TableBody>
142+
{rows.map((row, ind) => (
143+
<TableRow key={row.id} style={{ height: desktopView ? '2rem' : '3rem' }}>
144+
{row.cells.map((cell) => (
145+
<TableCell key={cell.id}>
146+
{cell.info.header === 'name' ? (
147+
<ConfigurableLink to={`\${openmrsSpaBase}/patient/${results[ind]?.patientUuid}/chart/`}>
148+
{cell.value}
149+
</ConfigurableLink>
150+
) : (
151+
cell.value
152+
)}
153+
</TableCell>
154+
))}
155+
</TableRow>
156+
))}
157+
</TableBody>
158+
</Table>
159+
{rows.length === 0 && (
160+
<p
161+
style={{ height: desktopView ? '2rem' : '3rem' }}
162+
className={`${styles.emptyRow} ${styles.bodyLong01}`}>
163+
{t('noVisitsFound', 'No visits found')}
164+
</p>
165+
)}
166+
<Pagination
167+
forwardText=""
168+
backwardText=""
169+
page={currentPage}
170+
pageSize={currentPageSize}
171+
pageSizes={pageSizes}
172+
totalItems={searchResults.length}
173+
className={styles.pagination}
174+
onChange={({ pageSize, page }) => {
175+
if (pageSize !== currentPageSize) {
176+
setPageSize(pageSize);
177+
}
178+
if (page !== currentPage) {
179+
goTo(page);
180+
}
181+
}}
182+
/>
183+
</TableContainer>
184+
)}
185+
</DataTable>
186+
</div>
187+
) : (
188+
<DataTableSkeleton />
189+
);
190+
};
191+
192+
export default ActiveVisitsTable;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { FetchResponse, openmrsFetch, openmrsObservableFetch, OpenmrsResource } from '@openmrs/esm-framework';
2+
import { take, map } from 'rxjs/operators';
3+
4+
export interface ActiveVisitRow {
5+
id: string;
6+
visitStartTime: string;
7+
IDNumber: string;
8+
name: string;
9+
gender: string;
10+
age: string;
11+
visitType: string;
12+
patientUuid: string;
13+
}
14+
15+
export function fetchActiveVisits() {
16+
const v =
17+
'custom:(uuid,patient:(uuid,identifiers:(identifier,uuid),person:(age,display,gender,uuid)),' +
18+
'visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,' +
19+
'stopDatetime)';
20+
return openmrsObservableFetch(`/ws/rest/v1/visit?includeInactive=false&v=${v}`, {
21+
headers: {
22+
contentType: 'application/json',
23+
},
24+
})
25+
.pipe(take(1))
26+
.pipe(map((response: FetchResponse<{ results: Array<any> }>) => response.data));
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@import "../root.scss";
2+
3+
.activeVisitsContainer {
4+
background-color: $ui-background;
5+
border: 1px solid #e0e0e0;
6+
width: 100%;
7+
margin: 0 auto;
8+
}
9+
10+
.activeVisitsDetailHeaderContainer {
11+
display: flex;
12+
justify-content: space-between;
13+
align-items: center;
14+
padding: $spacing-04 0 $spacing-04 $spacing-05;
15+
background-color: $ui-background;
16+
}
17+
18+
.productiveHeading02::after {
19+
content: "";
20+
display: block;
21+
width: 2rem;
22+
padding-top: 0.188rem;
23+
border-bottom: 0.375rem solid #007d79;
24+
}
25+
26+
.tableContainer section{
27+
position: relative;
28+
}
29+
30+
.tableContainer a {
31+
text-decoration: none;
32+
}
33+
34+
.pagination {
35+
overflow: hidden;
36+
}
37+
38+
.emptyRow {
39+
padding: 0 1rem;
40+
display: flex;
41+
align-items: center;
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Type } from '@openmrs/esm-framework';
2+
3+
export const configSchema = {
4+
activeVisits: {
5+
pageSize: {
6+
_type: Type.Number,
7+
_description: 'Count of active visits to be shown in a single page.',
8+
_default: 10,
9+
},
10+
pageSizes: {
11+
_type: Type.Array,
12+
_description: 'Customizable page sizes that user can choose',
13+
_default: [10, 20, 50],
14+
},
15+
},
16+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
declare module '*.css';
2+
declare module '*.scss';

0 commit comments

Comments
 (0)
Failed to load comments.