@openmrs/esm-billing-app 1.0.1-pre.14
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/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +57 -0
- package/.husky/pre-commit +7 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +14 -0
- package/.turbo.json +18 -0
- package/.yarn/plugins/@yarnpkg/plugin-outdated.cjs +35 -0
- package/LICENSE +401 -0
- package/README.md +7 -0
- package/__mocks__/bills.mock.ts +392 -0
- package/__mocks__/delivery-summary.mock.ts +87 -0
- package/__mocks__/encounter-observation.mock.ts +10649 -0
- package/__mocks__/encounter-observations.mock.ts +6187 -0
- package/__mocks__/hiv-summary.mock.ts +22 -0
- package/__mocks__/patient-summary.mock.ts +32 -0
- package/__mocks__/patient.mock.ts +59 -0
- package/__mocks__/program-summary.mock.ts +43 -0
- package/__mocks__/react-i18next.js +57 -0
- package/dist/294.js +2 -0
- package/dist/294.js.LICENSE.txt +9 -0
- package/dist/294.js.map +1 -0
- package/dist/319.js +1 -0
- package/dist/384.js +1 -0
- package/dist/384.js.map +1 -0
- package/dist/421.js +1 -0
- package/dist/421.js.map +1 -0
- package/dist/450.js +1 -0
- package/dist/450.js.map +1 -0
- package/dist/476.js +1 -0
- package/dist/476.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/757.js +1 -0
- package/dist/788.js +1 -0
- package/dist/800.js +2 -0
- package/dist/800.js.LICENSE.txt +3 -0
- package/dist/800.js.map +1 -0
- package/dist/807.js +1 -0
- package/dist/833.js +1 -0
- package/dist/935.js +2 -0
- package/dist/935.js.LICENSE.txt +19 -0
- package/dist/935.js.map +1 -0
- package/dist/96.js +2 -0
- package/dist/96.js.LICENSE.txt +47 -0
- package/dist/96.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +47 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-billing-app.js +1 -0
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +462 -0
- package/dist/openmrs-esm-billing-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/e2e/README.md +115 -0
- package/e2e/core/global-setup.ts +32 -0
- package/e2e/core/index.ts +1 -0
- package/e2e/core/test.ts +20 -0
- package/e2e/fixtures/api.ts +26 -0
- package/e2e/fixtures/index.ts +1 -0
- package/e2e/pages/home-page.ts +9 -0
- package/e2e/pages/index.ts +1 -0
- package/e2e/specs/sample-test.spec.ts +11 -0
- package/e2e/support/github/Dockerfile +34 -0
- package/e2e/support/github/docker-compose.yml +24 -0
- package/e2e/support/github/run-e2e-docker-env.sh +49 -0
- package/example.env +6 -0
- package/i18next-parser.config.js +89 -0
- package/jest.config.js +34 -0
- package/package.json +123 -0
- package/playwright.config.ts +32 -0
- package/prettier.config.js +8 -0
- package/src/bill-history/bill-history.component.tsx +187 -0
- package/src/bill-history/bill-history.scss +151 -0
- package/src/bill-history/bill-history.test.tsx +122 -0
- package/src/billable-services/bill-waiver/bill-selection.component.tsx +72 -0
- package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +108 -0
- package/src/billable-services/bill-waiver/bill-waiver-form.scss +34 -0
- package/src/billable-services/bill-waiver/bill-waiver.component.tsx +32 -0
- package/src/billable-services/bill-waiver/bill-waiver.scss +10 -0
- package/src/billable-services/bill-waiver/patient-bills.component.tsx +135 -0
- package/src/billable-services/bill-waiver/utils.ts +41 -0
- package/src/billable-services/billable-service.resource.ts +71 -0
- package/src/billable-services/billable-services-home.component.tsx +51 -0
- package/src/billable-services/billable-services.component.tsx +255 -0
- package/src/billable-services/billable-services.scss +218 -0
- package/src/billable-services/billable-services.test.tsx +16 -0
- package/src/billable-services/create-edit/add-billable-service.component.tsx +322 -0
- package/src/billable-services/create-edit/add-billable-service.scss +131 -0
- package/src/billable-services/create-edit/add-billable-service.test.tsx +152 -0
- package/src/billable-services/dashboard/dashboard.component.tsx +15 -0
- package/src/billable-services/dashboard/dashboard.scss +27 -0
- package/src/billable-services/dashboard/dashboard.test.tsx +11 -0
- package/src/billable-services/dashboard/service-metrics.component.tsx +42 -0
- package/src/billable-services-admin-card-link.component.test.tsx +21 -0
- package/src/billable-services-admin-card-link.component.tsx +25 -0
- package/src/billing-dashboard/billing-dashboard.component.tsx +20 -0
- package/src/billing-dashboard/billing-dashboard.scss +27 -0
- package/src/billing-dashboard/billing-dashboard.test.tsx +13 -0
- package/src/billing-form/billing-checkin-form.component.tsx +131 -0
- package/src/billing-form/billing-checkin-form.scss +13 -0
- package/src/billing-form/billing-checkin-form.test.tsx +134 -0
- package/src/billing-form/billing-form.component.tsx +25 -0
- package/src/billing-form/billing-form.resource.ts +31 -0
- package/src/billing-form/billing-form.scss +5 -0
- package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +173 -0
- package/src/billing-form/visit-attributes/visit-attributes-form.scss +22 -0
- package/src/billing-header/billing-header.component.tsx +43 -0
- package/src/billing-header/billing-header.scss +83 -0
- package/src/billing-header/billing-illustration.component.tsx +30 -0
- package/src/billing.resource.ts +120 -0
- package/src/bills-table/bills-table.component.tsx +280 -0
- package/src/bills-table/bills-table.scss +181 -0
- package/src/bills-table/bills-table.test.tsx +154 -0
- package/src/config-schema.ts +3 -0
- package/src/dashboard.meta.ts +6 -0
- package/src/declarations.d.ts +4 -0
- package/src/helpers/functions.ts +63 -0
- package/src/helpers/index.ts +1 -0
- package/src/index.ts +56 -0
- package/src/invoice/invoice-table.component.tsx +185 -0
- package/src/invoice/invoice-table.scss +91 -0
- package/src/invoice/invoice.component.tsx +138 -0
- package/src/invoice/invoice.scss +93 -0
- package/src/invoice/invoice.test.tsx +242 -0
- package/src/invoice/payments/invoice-breakdown/invoice-breakdown.component.tsx +17 -0
- package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +29 -0
- package/src/invoice/payments/payment-form/payment-form.component.tsx +105 -0
- package/src/invoice/payments/payment-form/payment-form.scss +54 -0
- package/src/invoice/payments/payment-history/payment-history.component.tsx +68 -0
- package/src/invoice/payments/payment.resource.ts +43 -0
- package/src/invoice/payments/payments.component.tsx +140 -0
- package/src/invoice/payments/payments.scss +46 -0
- package/src/invoice/payments/utils.ts +30 -0
- package/src/invoice/payments/visit-tags/visit-attribute.component.tsx +21 -0
- package/src/invoice/printable-invoice/print-receipt.component.tsx +28 -0
- package/src/invoice/printable-invoice/print-receipt.scss +14 -0
- package/src/invoice/printable-invoice/printable-footer.component.tsx +19 -0
- package/src/invoice/printable-invoice/printable-footer.scss +17 -0
- package/src/invoice/printable-invoice/printable-footer.test.tsx +30 -0
- package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +63 -0
- package/src/invoice/printable-invoice/printable-invoice-header.scss +61 -0
- package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +58 -0
- package/src/invoice/printable-invoice/printable-invoice.component.tsx +146 -0
- package/src/invoice/printable-invoice/printable-invoice.scss +50 -0
- package/src/left-panel-link.component.tsx +41 -0
- package/src/left-panel-link.test.tsx +38 -0
- package/src/metrics-cards/card.component.tsx +11 -0
- package/src/metrics-cards/card.scss +20 -0
- package/src/metrics-cards/metrics-cards.component.tsx +42 -0
- package/src/metrics-cards/metrics-cards.scss +12 -0
- package/src/metrics-cards/metrics-cards.test.tsx +41 -0
- package/src/metrics-cards/metrics.resource.ts +45 -0
- package/src/modal/require-payment-modal.component.tsx +81 -0
- package/src/modal/require-payment.scss +6 -0
- package/src/root.component.tsx +19 -0
- package/src/root.scss +30 -0
- package/src/routes.json +79 -0
- package/src/setup-tests.ts +13 -0
- package/src/types/index.ts +167 -0
- package/test-helpers.tsx +23 -0
- package/translations/am.json +107 -0
- package/translations/en.json +107 -0
- package/translations/es.json +107 -0
- package/translations/fr.json +107 -0
- package/translations/he.json +107 -0
- package/translations/km.json +107 -0
- package/tsconfig.json +16 -0
- package/webpack.config.js +1 -0
package/e2e/core/test.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { APIRequestContext, Page, test as base } from '@playwright/test';
|
|
2
|
+
import { api } from '../fixtures';
|
|
3
|
+
|
|
4
|
+
// This file sets up our custom test harness using the custom fixtures.
|
|
5
|
+
// See https://playwright.dev/docs/test-fixtures#creating-a-fixture for details.
|
|
6
|
+
// If a spec intends to use one of the custom fixtures, the special `test` function
|
|
7
|
+
// exported from this file must be used instead of the default `test` function
|
|
8
|
+
// provided by playwright.
|
|
9
|
+
|
|
10
|
+
export interface CustomTestFixtures {
|
|
11
|
+
loginAsAdmin: Page;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CustomWorkerFixtures {
|
|
15
|
+
api: APIRequestContext;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const test = base.extend<CustomTestFixtures, CustomWorkerFixtures>({
|
|
19
|
+
api: [api, { scope: 'worker' }],
|
|
20
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { APIRequestContext, PlaywrightWorkerArgs, WorkerFixture } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A fixture which initializes an [`APIRequestContext`](https://playwright.dev/docs/api/class-apirequestcontext)
|
|
5
|
+
* that is bound to the configured OpenMRS API server. The context is automatically authenticated
|
|
6
|
+
* using the configured admin account.
|
|
7
|
+
*
|
|
8
|
+
* Use the request context like this:
|
|
9
|
+
* ```ts
|
|
10
|
+
* test('your test', async ({ api }) => {
|
|
11
|
+
* const res = await api.get('patient/1234');
|
|
12
|
+
* await expect(res.ok()).toBeTruthy();
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const api: WorkerFixture<APIRequestContext, PlaywrightWorkerArgs> = async ({ playwright }, use) => {
|
|
17
|
+
const ctx = await playwright.request.newContext({
|
|
18
|
+
baseURL: `${process.env.E2E_BASE_URL}/ws/rest/v1/`,
|
|
19
|
+
httpCredentials: {
|
|
20
|
+
username: process.env.E2E_USER_ADMIN_USERNAME,
|
|
21
|
+
password: process.env.E2E_USER_ADMIN_PASSWORD,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await use(ctx);
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './api';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './home-page';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import test from '@playwright/test';
|
|
2
|
+
import { HomePage } from '../pages';
|
|
3
|
+
import { expect } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
// This test is a sample E2E test. You can delete it.
|
|
6
|
+
|
|
7
|
+
test('Sample test', async ({ page }) => {
|
|
8
|
+
const homePage = new HomePage(page);
|
|
9
|
+
await homePage.goto();
|
|
10
|
+
await expect(homePage.page.getByRole('link', { name: 'Home' })).toBeVisible();
|
|
11
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.3
|
|
2
|
+
FROM --platform=$BUILDPLATFORM node:18-alpine as dev
|
|
3
|
+
|
|
4
|
+
ARG APP_SHELL_VERSION=next
|
|
5
|
+
|
|
6
|
+
RUN mkdir -p /app
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
COPY . .
|
|
10
|
+
|
|
11
|
+
RUN npm_config_legacy_peer_deps=true npm install -g openmrs@${APP_SHELL_VERSION:-next}
|
|
12
|
+
ARG CACHE_BUST
|
|
13
|
+
RUN npm_config_legacy_peer_deps=true openmrs assemble --manifest --mode config --config spa-assemble-config.json --target ./spa
|
|
14
|
+
|
|
15
|
+
FROM --platform=$BUILDPLATFORM openmrs/openmrs-reference-application-3-frontend:nightly as frontend
|
|
16
|
+
FROM nginx:1.23-alpine
|
|
17
|
+
|
|
18
|
+
RUN apk update && \
|
|
19
|
+
apk upgrade && \
|
|
20
|
+
# add more utils for sponge to support our startup script
|
|
21
|
+
apk add --no-cache moreutils
|
|
22
|
+
|
|
23
|
+
# clear any default files installed by nginx
|
|
24
|
+
RUN rm -rf /usr/share/nginx/html/*
|
|
25
|
+
|
|
26
|
+
COPY --from=frontend /etc/nginx/nginx.conf /etc/nginx/nginx.conf
|
|
27
|
+
# this assumes that NOTHING in the framework is in a subdirectory
|
|
28
|
+
COPY --from=frontend /usr/share/nginx/html/* /usr/share/nginx/html/
|
|
29
|
+
COPY --from=frontend /usr/local/bin/startup.sh /usr/local/bin/startup.sh
|
|
30
|
+
RUN chmod +x /usr/local/bin/startup.sh
|
|
31
|
+
|
|
32
|
+
COPY --from=dev /app/spa/ /usr/share/nginx/html/
|
|
33
|
+
|
|
34
|
+
CMD ["/usr/local/bin/startup.sh"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# This docker compose file is used to create a backend environment for the e2e.yml workflow.
|
|
2
|
+
version: "3.7"
|
|
3
|
+
|
|
4
|
+
services:
|
|
5
|
+
gateway:
|
|
6
|
+
image: openmrs/openmrs-reference-application-3-gateway:${TAG:-nightly}
|
|
7
|
+
ports:
|
|
8
|
+
- "8080:80"
|
|
9
|
+
|
|
10
|
+
frontend:
|
|
11
|
+
build:
|
|
12
|
+
context: .
|
|
13
|
+
environment:
|
|
14
|
+
SPA_PATH: /openmrs/spa
|
|
15
|
+
API_URL: /openmrs
|
|
16
|
+
|
|
17
|
+
backend:
|
|
18
|
+
image: openmrs/openmrs-reference-application-3-backend:nightly-with-data
|
|
19
|
+
depends_on:
|
|
20
|
+
- db
|
|
21
|
+
|
|
22
|
+
# MariaDB
|
|
23
|
+
db:
|
|
24
|
+
image: openmrs/openmrs-reference-application-3-db:nightly-with-data
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash -eu
|
|
2
|
+
|
|
3
|
+
# get the dir containing the script
|
|
4
|
+
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
5
|
+
# create a temporary working directory
|
|
6
|
+
working_dir=$(mktemp -d "${TMPDIR:-/tmp/}openmrs-e2e-frontends.XXXXXXXXXX")
|
|
7
|
+
# get a list of all the apps in this workspace
|
|
8
|
+
apps=$(yarn workspaces list --json | jq -r 'if ((.location == ".") or (.location | test("form-engine-app")) or (.location | test("-app") | not)) then halt else .name end')
|
|
9
|
+
# this array will hold all of the packed app names
|
|
10
|
+
app_names=()
|
|
11
|
+
|
|
12
|
+
echo "Creating packed archives of apps..."
|
|
13
|
+
# for each app
|
|
14
|
+
for app in $apps
|
|
15
|
+
do
|
|
16
|
+
# @openmrs/esm-whatever -> _openmrs_esm_whatever
|
|
17
|
+
app_name=$(echo "$app" | tr '[:punct:]' '_');
|
|
18
|
+
# add to our array
|
|
19
|
+
app_names+=("$app_name.tgz");
|
|
20
|
+
# run yarn pack for our app and add it to the working directory
|
|
21
|
+
yarn workspace "$app" pack -o "$working_dir/$app_name.tgz" >/dev/null;
|
|
22
|
+
done;
|
|
23
|
+
echo "Created packed app archives"
|
|
24
|
+
|
|
25
|
+
echo "Creating dynamic spa-assemble-config.json..."
|
|
26
|
+
# dynamically assemble our list of frontend modules, prepending the login app and
|
|
27
|
+
# primary navigation apps; apps will all be in the /app directory of the Docker
|
|
28
|
+
# container
|
|
29
|
+
jq -n \
|
|
30
|
+
--arg apps "$apps" \
|
|
31
|
+
--arg app_names "$(echo ${app_names[@]})" \
|
|
32
|
+
'{"@openmrs/esm-primary-navigation-app": "next", "@openmrs/esm-home-app": "next"} + (
|
|
33
|
+
($apps | split("\n")) as $apps | ($app_names | split(" ") | map("/app/" + .)) as $app_files
|
|
34
|
+
| [$apps, $app_files]
|
|
35
|
+
| transpose
|
|
36
|
+
| map({"key": .[0], "value": .[1]})
|
|
37
|
+
| from_entries
|
|
38
|
+
)' | jq '{"frontendModules": .}' > "$working_dir/spa-assemble-config.json"
|
|
39
|
+
echo "Created dynamic spa-assemble-config.json"
|
|
40
|
+
|
|
41
|
+
echo "Copying Docker configuration..."
|
|
42
|
+
cp "$script_dir/Dockerfile" "$working_dir/Dockerfile"
|
|
43
|
+
cp "$script_dir/docker-compose.yml" "$working_dir/docker-compose.yml"
|
|
44
|
+
|
|
45
|
+
cd $working_dir
|
|
46
|
+
echo "Starting Docker containers..."
|
|
47
|
+
# CACHE_BUST to ensure the assemble step is always run
|
|
48
|
+
docker compose build --build-arg CACHE_BUST=$(date +%s) frontend
|
|
49
|
+
docker compose up -d
|
package/example.env
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# This is an example environment file for configuring dynamic values.
|
|
2
|
+
E2E_BASE_URL=http://localhost:8080/openmrs
|
|
3
|
+
E2E_USER_ADMIN_USERNAME=admin
|
|
4
|
+
E2E_USER_ADMIN_PASSWORD=Admin123
|
|
5
|
+
E2E_LOGIN_DEFAULT_LOCATION_UUID=44c3efb0-2583-4c80-a79e-1f756a03c0a1
|
|
6
|
+
# The above location UUID is for the "Outpatient Clinic" location in the reference application
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
contextSeparator: "_",
|
|
3
|
+
// Key separator used in your translation keys
|
|
4
|
+
|
|
5
|
+
createOldCatalogs: false,
|
|
6
|
+
// Save the \_old files
|
|
7
|
+
|
|
8
|
+
defaultNamespace: "translations",
|
|
9
|
+
// Default namespace used in your i18next config
|
|
10
|
+
|
|
11
|
+
defaultValue: "",
|
|
12
|
+
// Default value to give to empty keys
|
|
13
|
+
// You may also specify a function accepting the locale, namespace, and key as arguments
|
|
14
|
+
|
|
15
|
+
indentation: 2,
|
|
16
|
+
// Indentation of the catalog files
|
|
17
|
+
|
|
18
|
+
keepRemoved: false,
|
|
19
|
+
// Keep keys from the catalog that are no longer in code
|
|
20
|
+
|
|
21
|
+
keySeparator: ".",
|
|
22
|
+
// Key separator used in your translation keys
|
|
23
|
+
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
|
24
|
+
|
|
25
|
+
// see below for more details
|
|
26
|
+
lexers: {
|
|
27
|
+
hbs: ["HandlebarsLexer"],
|
|
28
|
+
handlebars: ["HandlebarsLexer"],
|
|
29
|
+
|
|
30
|
+
htm: ["HTMLLexer"],
|
|
31
|
+
html: ["HTMLLexer"],
|
|
32
|
+
|
|
33
|
+
mjs: ["JavascriptLexer"],
|
|
34
|
+
js: ["JavascriptLexer"], // if you're writing jsx inside .js files, change this to JsxLexer
|
|
35
|
+
ts: ["JavascriptLexer"],
|
|
36
|
+
jsx: ["JsxLexer"],
|
|
37
|
+
tsx: ["JsxLexer"],
|
|
38
|
+
|
|
39
|
+
default: ["JavascriptLexer"],
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
lineEnding: "auto",
|
|
43
|
+
// Control the line ending. See options at https://github.com/ryanve/eol
|
|
44
|
+
|
|
45
|
+
locales: ["en", "am", "es", "fr", "km", "he"],
|
|
46
|
+
// An array of the locales in your applications
|
|
47
|
+
|
|
48
|
+
namespaceSeparator: ":",
|
|
49
|
+
// Namespace separator used in your translation keys
|
|
50
|
+
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
|
51
|
+
|
|
52
|
+
output: "$NAMESPACE/$LOCALE.json",
|
|
53
|
+
// Supports $LOCALE and $NAMESPACE injection
|
|
54
|
+
// Supports JSON (.json) and YAML (.yml) file formats
|
|
55
|
+
// Where to write the locale files relative to process.cwd()
|
|
56
|
+
|
|
57
|
+
pluralSeparator: "_",
|
|
58
|
+
// Plural separator used in your translation keys
|
|
59
|
+
// If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
|
|
60
|
+
|
|
61
|
+
input: undefined,
|
|
62
|
+
// An array of globs that describe where to look for source files
|
|
63
|
+
// relative to the location of the configuration file
|
|
64
|
+
|
|
65
|
+
sort: true,
|
|
66
|
+
// Whether or not to sort the catalog
|
|
67
|
+
|
|
68
|
+
useKeysAsDefaultValue: false,
|
|
69
|
+
// Whether to use the keys as the default value; ex. "Hello": "Hello", "World": "World"
|
|
70
|
+
// This option takes precedence over the `defaultValue` and `skipDefaultValues` options
|
|
71
|
+
// You may also specify a function accepting the locale and namespace as arguments
|
|
72
|
+
|
|
73
|
+
verbose: false,
|
|
74
|
+
// Display info about the parsing including some stats
|
|
75
|
+
|
|
76
|
+
failOnWarnings: false,
|
|
77
|
+
// Exit with an exit code of 1 on warnings
|
|
78
|
+
|
|
79
|
+
customValueTemplate: null,
|
|
80
|
+
// If you wish to customize the value output the value as an object, you can set your own format.
|
|
81
|
+
// ${defaultValue} is the default value you set in your translation function.
|
|
82
|
+
// Any other custom property will be automatically extracted.
|
|
83
|
+
//
|
|
84
|
+
// Example:
|
|
85
|
+
// {
|
|
86
|
+
// message: "${defaultValue}",
|
|
87
|
+
// description: "${maxLength}", // t('my-key', {maxLength: 150})
|
|
88
|
+
// }
|
|
89
|
+
};
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @returns {Promise<import('jest').Config>}
|
|
3
|
+
*/
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
collectCoverageFrom: [
|
|
8
|
+
'**/src/**/*.component.tsx',
|
|
9
|
+
'!**/node_modules/**',
|
|
10
|
+
'!**/vendor/**',
|
|
11
|
+
'!**/src/**/*.test.*',
|
|
12
|
+
'!**/src/declarations.d.ts',
|
|
13
|
+
'!**/e2e/**',
|
|
14
|
+
],
|
|
15
|
+
transform: {
|
|
16
|
+
'^.+\\.(j|t)sx?$': '@swc/jest',
|
|
17
|
+
},
|
|
18
|
+
transformIgnorePatterns: ['/node_modules/(?!@openmrs)'],
|
|
19
|
+
moduleNameMapper: {
|
|
20
|
+
'\\.(s?css)$': 'identity-obj-proxy',
|
|
21
|
+
'@openmrs/esm-framework': '@openmrs/esm-framework/mock',
|
|
22
|
+
'^dexie$': require.resolve('dexie'),
|
|
23
|
+
'^lodash-es/(.*)$': 'lodash/$1',
|
|
24
|
+
'^react-i18next$': path.resolve(__dirname, '__mocks__', 'react-i18next.js'),
|
|
25
|
+
'^uuid$': path.resolve(__dirname, 'node_modules', 'uuid', 'dist', 'index.js'),
|
|
26
|
+
},
|
|
27
|
+
setupFilesAfterEnv: ['<rootDir>/src/setup-tests.ts'],
|
|
28
|
+
testPathIgnorePatterns: [path.resolve(__dirname, 'e2e')],
|
|
29
|
+
testEnvironment: 'jsdom',
|
|
30
|
+
testEnvironmentOptions: {
|
|
31
|
+
url: 'http://localhost/',
|
|
32
|
+
},
|
|
33
|
+
testTimeout: 20000,
|
|
34
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openmrs/esm-billing-app",
|
|
3
|
+
"version": "1.0.1-pre.14",
|
|
4
|
+
"description": "Billing frontend module for use in O3",
|
|
5
|
+
"browser": "dist/openmrs-esm-billing-app.js",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"source": true,
|
|
8
|
+
"license": "MPL-2.0",
|
|
9
|
+
"homepage": "https://github.com/openmrs/openmrs-esm-billing-app#readme",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"analyze": "webpack --mode=production --env.analyze=true",
|
|
12
|
+
"build": "webpack --mode production",
|
|
13
|
+
"ci:prepublish": "lerna publish from-package --no-git-reset --yes --dist-tag next",
|
|
14
|
+
"ci:publish": "lerna publish from-package --yes --no-git-reset --yes",
|
|
15
|
+
"coverage": "yarn test --coverage",
|
|
16
|
+
"debug": "npm run serve",
|
|
17
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts' i18next-parser.config.js",
|
|
18
|
+
"lint": "eslint src --ext ts,tsx --max-warnings=0",
|
|
19
|
+
"postinstall": "husky install",
|
|
20
|
+
"prettier": "prettier --config prettier.config.js --write \"src/**/*.{ts,tsx,css,scss}\" \"e2e/**/*.ts\"",
|
|
21
|
+
"release": "lerna version --no-git-tag-version",
|
|
22
|
+
"serve": "webpack serve --mode=development",
|
|
23
|
+
"start": "openmrs develop",
|
|
24
|
+
"test-e2e": "playwright test",
|
|
25
|
+
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests",
|
|
26
|
+
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js",
|
|
27
|
+
"typescript": "tsc",
|
|
28
|
+
"verify": "turbo lint && turbo typescript && turbo test --color --concurrency=5"
|
|
29
|
+
},
|
|
30
|
+
"browserslist": [
|
|
31
|
+
"extends browserslist-config-openmrs"
|
|
32
|
+
],
|
|
33
|
+
"keywords": [
|
|
34
|
+
"openmrs"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/openmrs/openmrs-esm-billing-app#readme"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/openmrs/openmrs-esm-billing-app/issues"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@carbon/react": "^1.48.1",
|
|
48
|
+
"@hookform/resolvers": "^3.3.4",
|
|
49
|
+
"classnames": "^2.5.1",
|
|
50
|
+
"fuzzy": "^0.1.3",
|
|
51
|
+
"lodash-es": "^4.17.21",
|
|
52
|
+
"react-hook-form": "^7.49.3",
|
|
53
|
+
"react-to-print": "^2.14.15",
|
|
54
|
+
"zod": "^3.22.4"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@openmrs/esm-framework": "*",
|
|
58
|
+
"@openmrs/esm-patient-common-lib": "6.x",
|
|
59
|
+
"react": "18.x",
|
|
60
|
+
"react-dom": "18.x",
|
|
61
|
+
"react-i18next": "11.x",
|
|
62
|
+
"rxjs": "6.x"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@openmrs/esm-framework": "next",
|
|
66
|
+
"@openmrs/esm-patient-common-lib": "next",
|
|
67
|
+
"@playwright/test": "^1.41.1",
|
|
68
|
+
"@swc/cli": "^0.3.2",
|
|
69
|
+
"@swc/core": "^1.3.106",
|
|
70
|
+
"@swc/jest": "^0.2.31",
|
|
71
|
+
"@testing-library/dom": "^9.3.4",
|
|
72
|
+
"@testing-library/jest-dom": "^6.3.0",
|
|
73
|
+
"@testing-library/react": "^14.1.2",
|
|
74
|
+
"@testing-library/user-event": "^14.5.2",
|
|
75
|
+
"@types/jest": "^29.5.11",
|
|
76
|
+
"@types/lodash-es": "^4.17.12",
|
|
77
|
+
"@types/react": "^18.2.48",
|
|
78
|
+
"@types/react-dom": "^18.2.18",
|
|
79
|
+
"@types/webpack-env": "^1.18.4",
|
|
80
|
+
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
|
81
|
+
"@typescript-eslint/parser": "^6.19.1",
|
|
82
|
+
"babel-preset-minify": "^0.5.2",
|
|
83
|
+
"concurrently": "^8.2.2",
|
|
84
|
+
"cross-env": "^7.0.3",
|
|
85
|
+
"css-loader": "^6.9.1",
|
|
86
|
+
"d3-selection": "^3.0.0",
|
|
87
|
+
"dayjs": "^1.11.10",
|
|
88
|
+
"dotenv": "^16.4.1",
|
|
89
|
+
"eslint": "^8.56.0",
|
|
90
|
+
"eslint-config-prettier": "^9.1.0",
|
|
91
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
92
|
+
"husky": "^9.0.6",
|
|
93
|
+
"i18next": "^23.7.20",
|
|
94
|
+
"i18next-parser": "^8.12.0",
|
|
95
|
+
"identity-obj-proxy": "^3.0.0",
|
|
96
|
+
"jest": "^29.7.0",
|
|
97
|
+
"jest-cli": "^29.7.0",
|
|
98
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
99
|
+
"lerna": "^8.0.2",
|
|
100
|
+
"lodash": "^4.17.21",
|
|
101
|
+
"openmrs": "next",
|
|
102
|
+
"pinst": "^3.0.0",
|
|
103
|
+
"prettier": "^3.2.4",
|
|
104
|
+
"pretty-quick": "^4.0.0",
|
|
105
|
+
"react": "^18.2.0",
|
|
106
|
+
"react-dom": "^18.2.0",
|
|
107
|
+
"react-i18next": "^14.0.1",
|
|
108
|
+
"react-router-dom": "^6.21.3",
|
|
109
|
+
"rxjs": "^6.6.7",
|
|
110
|
+
"sass": "^1.70.0",
|
|
111
|
+
"swc-loader": "^0.2.3",
|
|
112
|
+
"swr": "^2.2.4",
|
|
113
|
+
"turbo": "^1.11.3",
|
|
114
|
+
"typescript": "^4.9.5",
|
|
115
|
+
"webpack-cli": "^5.1.4",
|
|
116
|
+
"webpack-dev-server": "^4.15.1"
|
|
117
|
+
},
|
|
118
|
+
"lint-staged": {
|
|
119
|
+
"*.{js,jsx,ts,tsx}": "eslint --cache --fix"
|
|
120
|
+
},
|
|
121
|
+
"packageManager": "yarn@4.0.2",
|
|
122
|
+
"stableVersion": "1.0.0"
|
|
123
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { devices, PlaywrightTestConfig } from '@playwright/test';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
dotenv.config();
|
|
4
|
+
|
|
5
|
+
// See https://playwright.dev/docs/test-configuration.
|
|
6
|
+
const config: PlaywrightTestConfig = {
|
|
7
|
+
testDir: './e2e/specs',
|
|
8
|
+
timeout: 3 * 60 * 1000,
|
|
9
|
+
expect: {
|
|
10
|
+
timeout: 40 * 1000,
|
|
11
|
+
},
|
|
12
|
+
fullyParallel: true,
|
|
13
|
+
forbidOnly: !!process.env.CI,
|
|
14
|
+
retries: 0,
|
|
15
|
+
reporter: process.env.CI ? [['junit', { outputFile: 'results.xml' }], ['html']] : [['html']],
|
|
16
|
+
globalSetup: require.resolve('./e2e/core/global-setup'),
|
|
17
|
+
use: {
|
|
18
|
+
baseURL: `${process.env.E2E_BASE_URL}/spa/`,
|
|
19
|
+
storageState: 'e2e/storageState.json',
|
|
20
|
+
video: 'retain-on-failure',
|
|
21
|
+
},
|
|
22
|
+
projects: [
|
|
23
|
+
{
|
|
24
|
+
name: 'chromium',
|
|
25
|
+
use: {
|
|
26
|
+
...devices['Desktop Chrome'],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default config;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
DataTable,
|
|
6
|
+
DataTableSkeleton,
|
|
7
|
+
Layer,
|
|
8
|
+
Pagination,
|
|
9
|
+
Table,
|
|
10
|
+
TableBody,
|
|
11
|
+
TableCell,
|
|
12
|
+
TableContainer,
|
|
13
|
+
TableExpandedRow,
|
|
14
|
+
TableExpandHeader,
|
|
15
|
+
TableExpandRow,
|
|
16
|
+
TableHead,
|
|
17
|
+
TableHeader,
|
|
18
|
+
TableRow,
|
|
19
|
+
Tile,
|
|
20
|
+
} from '@carbon/react';
|
|
21
|
+
import { isDesktop, useLayoutType, usePagination } from '@openmrs/esm-framework';
|
|
22
|
+
import {
|
|
23
|
+
EmptyDataIllustration,
|
|
24
|
+
ErrorState,
|
|
25
|
+
launchPatientWorkspace,
|
|
26
|
+
usePaginationInfo,
|
|
27
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
28
|
+
import { useBills } from '../billing.resource';
|
|
29
|
+
import InvoiceTable from '../invoice/invoice-table.component';
|
|
30
|
+
import styles from './bill-history.scss';
|
|
31
|
+
|
|
32
|
+
interface BillHistoryProps {
|
|
33
|
+
patientUuid: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const BillHistory: React.FC<BillHistoryProps> = ({ patientUuid }) => {
|
|
37
|
+
const PAGE_SIZE = 10;
|
|
38
|
+
const { t } = useTranslation();
|
|
39
|
+
const { bills, isLoading, error } = useBills(patientUuid);
|
|
40
|
+
const layout = useLayoutType();
|
|
41
|
+
const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
|
|
42
|
+
const { paginated, goTo, results, currentPage } = usePagination(bills, PAGE_SIZE);
|
|
43
|
+
const { pageSizes } = usePaginationInfo(PAGE_SIZE, bills?.length, currentPage, results?.length);
|
|
44
|
+
|
|
45
|
+
const headerData = [
|
|
46
|
+
{
|
|
47
|
+
header: t('visitTime', 'Visit time'),
|
|
48
|
+
key: 'visitTime',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
header: t('identifier', 'Identifier'),
|
|
52
|
+
key: 'identifier',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
header: t('billedItems', 'Billed Items'),
|
|
56
|
+
key: 'billedItems',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
header: t('billTotal', 'Bill total'),
|
|
60
|
+
key: 'billTotal',
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const setBilledItems = (bill) =>
|
|
65
|
+
bill.lineItems.reduce((acc, item) => acc + (acc ? ' & ' : '') + (item.billableService || item.item || ''), '');
|
|
66
|
+
|
|
67
|
+
const rowData = results?.map((bill) => ({
|
|
68
|
+
id: bill.uuid,
|
|
69
|
+
uuid: bill.uuid,
|
|
70
|
+
billTotal: bill.totalAmount,
|
|
71
|
+
visitTime: bill.dateCreated,
|
|
72
|
+
identifier: bill.identifier,
|
|
73
|
+
billedItems: setBilledItems(bill),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
if (isLoading) {
|
|
77
|
+
return (
|
|
78
|
+
<div className={styles.loaderContainer}>
|
|
79
|
+
<DataTableSkeleton showHeader={false} showToolbar={false} zebra size={responsiveSize} />
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (error) {
|
|
85
|
+
return (
|
|
86
|
+
<div className={styles.errorContainer}>
|
|
87
|
+
<Layer>
|
|
88
|
+
<ErrorState error={error} headerTitle={t('billsList', 'Bill list')} />
|
|
89
|
+
</Layer>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (bills.length === 0) {
|
|
95
|
+
return (
|
|
96
|
+
<Layer className={styles.emptyStateContainer}>
|
|
97
|
+
<Tile className={styles.tile}>
|
|
98
|
+
<div className={styles.illo}>
|
|
99
|
+
<EmptyDataIllustration />
|
|
100
|
+
</div>
|
|
101
|
+
<p className={styles.content}>There are no bills to display.</p>
|
|
102
|
+
<Button onClick={() => launchPatientWorkspace('billing-form')} kind="ghost">
|
|
103
|
+
{t('launchBillForm', 'Launch bill form')}
|
|
104
|
+
</Button>
|
|
105
|
+
</Tile>
|
|
106
|
+
</Layer>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className={styles.billHistoryContainer}>
|
|
112
|
+
<DataTable isSortable rows={rowData} headers={headerData} size={responsiveSize} useZebraStyles>
|
|
113
|
+
{({
|
|
114
|
+
rows,
|
|
115
|
+
headers,
|
|
116
|
+
getExpandHeaderProps,
|
|
117
|
+
getTableProps,
|
|
118
|
+
getTableContainerProps,
|
|
119
|
+
getHeaderProps,
|
|
120
|
+
getRowProps,
|
|
121
|
+
}) => (
|
|
122
|
+
<TableContainer {...getTableContainerProps}>
|
|
123
|
+
<Table className={styles.table} {...getTableProps()} aria-label="Bill list">
|
|
124
|
+
<TableHead>
|
|
125
|
+
<TableRow>
|
|
126
|
+
<TableExpandHeader enableToggle {...getExpandHeaderProps()} />
|
|
127
|
+
{headers.map((header, i) => (
|
|
128
|
+
<TableHeader
|
|
129
|
+
key={i}
|
|
130
|
+
{...getHeaderProps({
|
|
131
|
+
header,
|
|
132
|
+
})}>
|
|
133
|
+
{header.header}
|
|
134
|
+
</TableHeader>
|
|
135
|
+
))}
|
|
136
|
+
</TableRow>
|
|
137
|
+
</TableHead>
|
|
138
|
+
<TableBody>
|
|
139
|
+
{rows.map((row, i) => {
|
|
140
|
+
const currentBill = bills?.find((bill) => bill.uuid === row.id);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<React.Fragment key={row.id}>
|
|
144
|
+
<TableExpandRow {...getRowProps({ row })}>
|
|
145
|
+
{row.cells.map((cell) => (
|
|
146
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
147
|
+
))}
|
|
148
|
+
</TableExpandRow>
|
|
149
|
+
{row.isExpanded ? (
|
|
150
|
+
<TableExpandedRow className={styles.expandedRow} colSpan={headers.length + 1}>
|
|
151
|
+
<div className={styles.container} key={i}>
|
|
152
|
+
<InvoiceTable bill={currentBill} isSelectable={false} />
|
|
153
|
+
</div>
|
|
154
|
+
</TableExpandedRow>
|
|
155
|
+
) : (
|
|
156
|
+
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
|
|
157
|
+
)}
|
|
158
|
+
</React.Fragment>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
</TableBody>
|
|
162
|
+
</Table>
|
|
163
|
+
</TableContainer>
|
|
164
|
+
)}
|
|
165
|
+
</DataTable>
|
|
166
|
+
{paginated && (
|
|
167
|
+
<Pagination
|
|
168
|
+
forwardText={t('nextPage', 'Next page')}
|
|
169
|
+
backwardText={t('previousPage', 'Previous page')}
|
|
170
|
+
page={currentPage}
|
|
171
|
+
pageSize={PAGE_SIZE}
|
|
172
|
+
pageSizes={pageSizes}
|
|
173
|
+
totalItems={bills.length}
|
|
174
|
+
className={styles.pagination}
|
|
175
|
+
size={responsiveSize}
|
|
176
|
+
onChange={({ page: newPage }) => {
|
|
177
|
+
if (newPage !== currentPage) {
|
|
178
|
+
goTo(newPage);
|
|
179
|
+
}
|
|
180
|
+
}}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export default BillHistory;
|