From 30a98b0dd96878681225a7109032c8d92f90e0b0 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Thu, 7 Dec 2023 19:05:52 -0500 Subject: [PATCH 01/15] docker health check --- Dockerfile | 2 ++ Dockerfile.dev | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 68f8f448..03863e9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,4 +3,6 @@ WORKDIR /rems-admin COPY --chown=node:node . . RUN npm install EXPOSE 8090 +RUN apk add --no-cache curl +HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 CMD npm run start \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index 1e11a5a3..13421526 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -4,4 +4,6 @@ COPY --chown=node:node . . RUN npm install EXPOSE 8090 EXPOSE 8091 +RUN apk add --no-cache curl +HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 CMD ./dockerRunnerDev.sh \ No newline at end of file From 908cd4f947c295fb8ba0bf37b0ba875cf9b276f1 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Thu, 7 Dec 2023 19:11:51 -0500 Subject: [PATCH 02/15] add curl --- Dockerfile | 5 ++++- Dockerfile.dev | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03863e9f..5d8bfd5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,9 @@ WORKDIR /rems-admin COPY --chown=node:node . . RUN npm install EXPOSE 8090 -RUN apk add --no-cache curl +RUN apk update +RUN apk upgrade +RUN apk search curl +RUN apk add curl HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 CMD npm run start \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index 13421526..5888b044 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -4,6 +4,9 @@ COPY --chown=node:node . . RUN npm install EXPOSE 8090 EXPOSE 8091 -RUN apk add --no-cache curl +RUN apk update +RUN apk upgrade +RUN apk search curl +RUN apk add curl HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 CMD ./dockerRunnerDev.sh \ No newline at end of file From 94ec0607560403722ce5e516677b757f2ec54939 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Tue, 26 Dec 2023 14:32:14 -0500 Subject: [PATCH 03/15] commit submodule update --- src/rems-cds-hooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rems-cds-hooks b/src/rems-cds-hooks index da1a46d2..9d175cc3 160000 --- a/src/rems-cds-hooks +++ b/src/rems-cds-hooks @@ -1 +1 @@ -Subproject commit da1a46d22b1d5ded3afe1be73dde06d20d080b53 +Subproject commit 9d175cc38fd2f484e65da8564f951a36e170558a From 5e7352197ffad240378e3290c49528259bab0f3f Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Mon, 26 Aug 2024 14:41:27 -0400 Subject: [PATCH 04/15] health check updates --- Dockerfile | 6 +++++- Dockerfile.dev | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0ade6bf5..1f621597 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,9 @@ FROM node:18-alpine WORKDIR /rems-admin + +ARG PORT +ENV PORT=${PORT} + COPY --chown=node:node . . RUN npm install EXPOSE 8090 @@ -7,6 +11,6 @@ RUN apk update RUN apk upgrade RUN apk search curl RUN apk add curl -HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 +HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:${PORT} || exit 1 EXPOSE 8095 CMD npm run start \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index bdb02c0e..8b07501a 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,5 +1,9 @@ FROM node:18-alpine WORKDIR /rems-admin + +ARG PORT +ENV PORT=${PORT} + COPY --chown=node:node . . RUN npm install EXPOSE 8090 @@ -8,7 +12,7 @@ RUN apk update RUN apk upgrade RUN apk search curl RUN apk add curl -HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 +HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:${PORT} || exit 1 EXPOSE 8095 EXPOSE 8096 CMD ./dockerRunnerDev.sh \ No newline at end of file From 3d631eb77ba6a13d3e5f41b3211fd85452b18e34 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Mon, 26 Aug 2024 15:00:20 -0400 Subject: [PATCH 05/15] hardcode health check port --- Dockerfile | 5 ++++- Dockerfile.dev | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1f621597..f529a55a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ FROM node:18-alpine WORKDIR /rems-admin +# Install MITRE Certs +RUN curl -ksSL https://gitlab.mitre.org/mitre-scripts/mitre-pki/raw/master/os_scripts/install_certs.sh | sh + ARG PORT ENV PORT=${PORT} @@ -11,6 +14,6 @@ RUN apk update RUN apk upgrade RUN apk search curl RUN apk add curl -HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:${PORT} || exit 1 +HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 EXPOSE 8095 CMD npm run start \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index 8b07501a..079a2c5e 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,6 +1,10 @@ FROM node:18-alpine WORKDIR /rems-admin +# Install MITRE Certs +RUN curl -ksSL https://gitlab.mitre.org/mitre-scripts/mitre-pki/raw/master/os_scripts/install_certs.sh | sh + + ARG PORT ENV PORT=${PORT} @@ -12,7 +16,7 @@ RUN apk update RUN apk upgrade RUN apk search curl RUN apk add curl -HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:${PORT} || exit 1 +HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 EXPOSE 8095 EXPOSE 8096 CMD ./dockerRunnerDev.sh \ No newline at end of file From 599bd5f6c32ace30b33eff7096633393a4349371 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Tue, 27 Aug 2024 12:15:35 -0400 Subject: [PATCH 06/15] health check updates --- Dockerfile | 10 ++-------- Dockerfile.dev | 12 +++--------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index f529a55a..2a5eb75f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,13 @@ FROM node:18-alpine WORKDIR /rems-admin -# Install MITRE Certs -RUN curl -ksSL https://gitlab.mitre.org/mitre-scripts/mitre-pki/raw/master/os_scripts/install_certs.sh | sh - ARG PORT ENV PORT=${PORT} COPY --chown=node:node . . RUN npm install EXPOSE 8090 -RUN apk update -RUN apk upgrade -RUN apk search curl -RUN apk add curl -HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 + +HEALTHCHECK --interval=30s --start-period=15s --timeout=10m --retries=10 CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT} || exit 1 EXPOSE 8095 CMD npm run start \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index 079a2c5e..e3b76efe 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,10 +1,6 @@ FROM node:18-alpine WORKDIR /rems-admin -# Install MITRE Certs -RUN curl -ksSL https://gitlab.mitre.org/mitre-scripts/mitre-pki/raw/master/os_scripts/install_certs.sh | sh - - ARG PORT ENV PORT=${PORT} @@ -12,11 +8,9 @@ COPY --chown=node:node . . RUN npm install EXPOSE 8090 EXPOSE 8091 -RUN apk update -RUN apk upgrade -RUN apk search curl -RUN apk add curl -HEALTHCHECK --interval=60s --timeout=10m --retries=10 CMD curl --fail http://localhost:8090 || exit 1 + +HEALTHCHECK --interval=30s --start-period=15s --timeout=10m --retries=10 CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT} || exit 1 + EXPOSE 8095 EXPOSE 8096 CMD ./dockerRunnerDev.sh \ No newline at end of file From 468dec3bfe6b8678d18901df925044eba32096c2 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Tue, 27 Aug 2024 12:25:24 -0400 Subject: [PATCH 07/15] docker args update --- Dockerfile | 2 +- Dockerfile.dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2a5eb75f..594e2cfd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM node:18-alpine WORKDIR /rems-admin -ARG PORT +ARG PORT=8090 ENV PORT=${PORT} COPY --chown=node:node . . diff --git a/Dockerfile.dev b/Dockerfile.dev index e3b76efe..051e83bf 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,7 +1,7 @@ FROM node:18-alpine WORKDIR /rems-admin -ARG PORT +ARG PORT=8090 ENV PORT=${PORT} COPY --chown=node:node . . From a7725bf5016ff55870d1e33300b72fce41dd6120 Mon Sep 17 00:00:00 2001 From: Patrick LaRocque Date: Tue, 10 Sep 2024 16:08:28 -0400 Subject: [PATCH 08/15] make server name returned in card source configurable --- .env | 1 + README.md | 1 + src/config.ts | 3 ++- src/hooks/hookResources.ts | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.env b/.env index c80dcf6a..ab1c188c 100644 --- a/.env +++ b/.env @@ -10,3 +10,4 @@ SMART_ENDPOINT = http://localhost:4040/launch USE_HTTPS = false VSAC_API_KEY = changeMe WHITELIST = http://localhost, http://localhost:3005 +SERVER_NAME = MCODE REMS Administrator Prototype diff --git a/README.md b/README.md index 99ceda47..24f4fd36 100644 --- a/README.md +++ b/README.md @@ -98,3 +98,4 @@ Following are a list of modifiable paths: | USE_HTTPS | `false` | Change to true to enable HTTPS. Ensure that HTTPS_CERT_PATH and HTTPS_KEY_PATH are valid. | | VSAC_API_KEY | `changeMe` | Replace with VSAC API key for pulling down ValueSets. Request an API Key from the [VSAC website](https://vsac.nlm.nih.gov/) | | WHITELIST | `http://localhost, http://localhost:3005` | List of valid URLs for CORS. Should include any URLs the server accesses for resources. | +| SERVER_NAME | `CodeX REMS Administrator Prototype` | Name of the server that is returned in the card source. | diff --git a/src/config.ts b/src/config.ts index 74c8617a..69fa1a4f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,7 +12,8 @@ const whitelist = whitelistEnv && whitelistEnv.length === 1 ? whitelistEnv[0] : export default { server: { port: env.get('PORT').asInt(), - discoveryEndpoint: '/cds-services' + discoveryEndpoint: '/cds-services', + name: env.get('SERVER_NAME').required().asString() }, smart: { endpoint: env.get('SMART_ENDPOINT').required().asUrlString() diff --git a/src/hooks/hookResources.ts b/src/hooks/hookResources.ts index 4d435a46..1dd98dbf 100644 --- a/src/hooks/hookResources.ts +++ b/src/hooks/hookResources.ts @@ -244,7 +244,7 @@ export const validCodes: Coding[] = [ } ]; const source = { - label: 'MCODE REMS Administrator Prototype', + label: config.server.name, url: new URL('https://github.com/mcode/rems-admin') }; From 884fceea798c876290d9c0caadadab8e7251f4c0 Mon Sep 17 00:00:00 2001 From: Patrick LaRocque Date: Tue, 10 Sep 2024 16:11:30 -0400 Subject: [PATCH 09/15] update rems admin server name --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index ab1c188c..36730982 100644 --- a/.env +++ b/.env @@ -10,4 +10,4 @@ SMART_ENDPOINT = http://localhost:4040/launch USE_HTTPS = false VSAC_API_KEY = changeMe WHITELIST = http://localhost, http://localhost:3005 -SERVER_NAME = MCODE REMS Administrator Prototype +SERVER_NAME = CodeX REMS Administrator Prototype From a9c389ada76e017ff608cb636e99f64106c208f2 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Mon, 16 Sep 2024 13:50:01 -0400 Subject: [PATCH 10/15] linting fix --- src/lib/vsac_cache.ts | 5 ++++- src/rems-cds-hooks | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/vsac_cache.ts b/src/lib/vsac_cache.ts index 28ff8c49..2c08ccf3 100644 --- a/src/lib/vsac_cache.ts +++ b/src/lib/vsac_cache.ts @@ -60,7 +60,10 @@ class VsacCache { */ collectLibraryValueSets(library: Library) { // ensure only unique values - const result = fhirpath.evaluate(library, 'Library.dataRequirement.codeFilter.valueSet') as any[]; + const result = fhirpath.evaluate( + library, + 'Library.dataRequirement.codeFilter.valueSet' + ) as any[]; return new Set(result); } diff --git a/src/rems-cds-hooks b/src/rems-cds-hooks index 3ced784d..51dd2d4d 160000 --- a/src/rems-cds-hooks +++ b/src/rems-cds-hooks @@ -1 +1 @@ -Subproject commit 3ced784d3c13577c22c9012447f20e29b029632f +Subproject commit 51dd2d4d3dd46313187b4fb2c035c5e9b62d0fdd From d5844f4d56dea22f0850adddf0c61ff166419d12 Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Mon, 16 Sep 2024 19:24:34 -0400 Subject: [PATCH 11/15] update cds hooks --- src/rems-cds-hooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rems-cds-hooks b/src/rems-cds-hooks index 3ced784d..1dd79441 160000 --- a/src/rems-cds-hooks +++ b/src/rems-cds-hooks @@ -1 +1 @@ -Subproject commit 3ced784d3c13577c22c9012447f20e29b029632f +Subproject commit 1dd79441b51ec6c03659bc8e06b40b03d163ed8a From 6062309adc5a839e3c070cb011ea35183894decc Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Wed, 25 Sep 2024 13:19:43 -0400 Subject: [PATCH 12/15] update submodule --- src/rems-cds-hooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rems-cds-hooks b/src/rems-cds-hooks index 1dd79441..84d093f3 160000 --- a/src/rems-cds-hooks +++ b/src/rems-cds-hooks @@ -1 +1 @@ -Subproject commit 1dd79441b51ec6c03659bc8e06b40b03d163ed8a +Subproject commit 84d093f3e9d2ea7e22aa4eeae32dfa3907187c49 From 00583b33c86386b10a76d784bdca74541ad9a96a Mon Sep 17 00:00:00 2001 From: Sahil Malhotra Date: Wed, 25 Sep 2024 13:37:34 -0400 Subject: [PATCH 13/15] update submodule --- src/rems-cds-hooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rems-cds-hooks b/src/rems-cds-hooks index 84d093f3..877bc730 160000 --- a/src/rems-cds-hooks +++ b/src/rems-cds-hooks @@ -1 +1 @@ -Subproject commit 84d093f3e9d2ea7e22aa4eeae32dfa3907187c49 +Subproject commit 877bc7301a69de0b57bcdb90e4c255aebec6244b From 5fbd58d37ce6e2082328d19d8fef99817f1b88d7 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto <31778437+avirgulto@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:46:40 -0400 Subject: [PATCH 14/15] 732 rems admin UI (#157) * Initial react app for UI * Latest rems admin ui stuff * Updates to api call * Updated reset DB * Remove unused files * Fix eslint and prettier * remove extra ; and change header color * change start command to npm start * Update button colors throughout app * Update readme --------- Co-authored-by: Ariel Virgulto --- README.md | 18 ++ frontend/.gitignore | 24 +++ frontend/README.md | 50 +++++ frontend/config.json | 6 + frontend/eslint.config.js | 25 +++ frontend/index.html | 13 ++ frontend/package.json | 35 ++++ frontend/public/admin.png | Bin 0 -> 1392 bytes frontend/public/vite.svg | 1 + frontend/src/App.css | 81 ++++++++ frontend/src/App.tsx | 93 +++++++++ frontend/src/assets/react.svg | 1 + frontend/src/index.css | 13 ++ frontend/src/main.tsx | 10 + .../src/views/DataViews/CaseCollection.tsx | 180 ++++++++++++++++++ frontend/src/views/DataViews/Data.tsx | 67 +++++++ frontend/src/views/DataViews/Medications.tsx | 138 ++++++++++++++ .../src/views/DataViews/MetRequirements.tsx | 163 ++++++++++++++++ frontend/src/views/Login.tsx | 97 ++++++++++ frontend/src/vite-env.d.ts | 1 + frontend/tsconfig.app.json | 24 +++ frontend/tsconfig.json | 7 + frontend/tsconfig.node.json | 22 +++ frontend/vite.config.ts | 7 + src/lib/api_routes.ts | 83 ++++++++ src/rems-cds-hooks | 2 +- src/server.ts | 7 + 27 files changed, 1167 insertions(+), 1 deletion(-) create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/config.json create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/public/admin.png create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/views/DataViews/CaseCollection.tsx create mode 100644 frontend/src/views/DataViews/Data.tsx create mode 100644 frontend/src/views/DataViews/Medications.tsx create mode 100644 frontend/src/views/DataViews/MetRequirements.tsx create mode 100644 frontend/src/views/Login.tsx create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 src/lib/api_routes.ts diff --git a/README.md b/README.md index 24f4fd36..c68f5484 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,24 @@ Follow the mongodb setup instructions in the [REMS End to End Setup Guide](https If you would rather run with docker, follow the setup found in the [REMS Simple Setup Guide](https://github.com/mcode/rems-setup/blob/main/SimpleSetupGuide.md) (this will also setup the other REMS applications in docker as well). +## Starting the frontend + +Cd into the frontend repository + +### `cd frontend/` + +Next, install the required dependencies by running the following: + +### `npm install` + +Next, start the frontend with the following: + +### `npm start` + +Go to the UI running on http://localhost:5173/ (or whichever port it was run on) + +Still need to update docker to start the UI automatically. + ## Available Scripts In the project directory, you can run: diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 00000000..74872fd4 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/frontend/config.json b/frontend/config.json new file mode 100644 index 00000000..2f2a382b --- /dev/null +++ b/frontend/config.json @@ -0,0 +1,6 @@ +{ + "realm": "ClientFhirServer", + "client": "app-login", + "auth": "http://localhost:8180/", + "scopeId": "pims" +} diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 00000000..f3bd4ef9 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,25 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }] + } + } +); diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..96e2c905 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Rems Admin UI + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..7f694417 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "start": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/icons-material": "^6.1.0", + "@mui/material": "^6.1.0", + "axios": "^1.7.7", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.2" + }, + "devDependencies": { + "@eslint/js": "^9.9.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^5.4.1" + } +} diff --git a/frontend/public/admin.png b/frontend/public/admin.png new file mode 100644 index 0000000000000000000000000000000000000000..6691ba198c4638369e15c911c3301627d9d9406d GIT binary patch literal 1392 zcmeAS@N?(olHy`uVBq!ia0vp^WWn z(3qGz!O+_&QKU^ic;cFa3p|^aEK^cmH@!{ago#R#fcBbgudkW}?2hTp5U+P~@xCCy ztLvL9sQb5J(SnUqx0I((y1g@M=GAhGJ^cF5WzLl^IK5ob?I5d&x2Un?Y~D3%*0I{0 zFL`|Yv`F);II}~YE-_v|E}aYCe>CxR>4kYyBR}wluFqQkd!pp`#l>G9sdXp+RM4Snv*%GYNyWR4il3deG{xnI+l}SVxAuIP{2}63 z`~2Srj~<%#Z)fw~+Wc)>_GrACE~9Wtrm&NtyCon{Wzmw3lXL&Q51w5sXl8ihU`n%0 zrIwMRPM>4rnh!RrCC4O%!)8nfd0YK|U-IqMvWH6Kcl6zkv*BFs_|s|ATV8vI`>J!a z+tRY`UoX89`}FICOONIm7rc+U=FMfJd|}7tHQS9(XUQ~vmeYGBuhE&bbQSMEUSNQS zrABzB`T8sd^Q;1t2X9HWgMtW^QUp zqC!P(PF}H9g{=};g%ywu64qBz04piUwpEJo4N!2-FG^J~(=*UBP_pAvP*AWbN=dT{ za&d!d2l8x{GD=Dctn~HE%ggo3jrH=2()A53EiLs8jP#9+bb%^#i!1X=5-W7`ij^UT zz|3(;Elw`VEGWs$&r<-Io0ybeT4JlD1hNPYAnr`9$VAwbR}A$Q(1ZFQ8GS=N1AVyJ zK&>_)QLBK=iqxD)m&{yX@BrEalY!J2H+U;N{| z0T1J4gD=82ycf$!XBk>cB`-EK^KeQq(d3)dp>Ru5$7Quk&<98kVmtn%D2z~`d$gk*I|xy8Ak z4vS@Mh&Zg)?%k8p|IjF7LnnW9|HPPz2i=kZnr*xb)K!zNCK`7~7_O@1d2(aTH>Wk< z{8qndpPqMQ)!{4w&Zfg=ntfG0Eg=RPQ~HkGSkS>HBy-T?mB!W1*58Fv{yBzzYphbb zbsou1+{tn?MEjki*~07LJ95;+WcB_Y>EOR@<91a-*{oA=b>EB`2bZ>G$uV1&ZF$mdK II;Vst03i?*Gynhq literal 0 HcmV?d00001 diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 00000000..d1b03b7b --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,81 @@ +.App { + text-align: center; + background-color: #2F6A47; + padding: 20px; + margin-bottom: 20px; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #2F6A47; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +.navButtons { + list-style: none; + text-decoration: none; + margin-left: 5px; +} + +.white-btn { + color: white !important; + border-color: white !important; + margin-right: 5px !important; +} +.right-btn { + display: flex; + justify-content: flex-end; + margin: 10px 16px 10px 0; +} + +.App h1 { + color: white; + line-height: 1.3; + letter-spacing: 0.00938em; + font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; +} + +.logo { + grid-area: main; + text-align: left; + margin-right: auto; + display: inline-flex; + box-sizing: unset; +} + +.links { + grid-area: right; + margin-left: auto; + margin-top: 25px; +} + +.containerg { + display: grid; + grid-template-areas: ' main right'; +} + +.err-msg { + color: red; + font-style: italic; + text-align: center; +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 00000000..895151df --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,93 @@ +import './App.css'; +import { useState } from 'react'; +import { Button } from '@mui/material'; +import Box from '@mui/material/Box'; +import { Container } from '@mui/system'; +import DatasetIcon from '@mui/icons-material/Dataset'; +import Login from './views/Login'; +import Data from './views/DataViews/Data'; +import axios from 'axios'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +function App() { + const [token, setToken] = useState(null); + const [open, setOpen] = useState(false); + const [forceRefresh, setForceRefresh] = useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const resetDB = async () => { + setOpen(false); + await axios + .post('http://localhost:8090/etasu/reset') + .then(function (response: any) { + console.log(response); + setForceRefresh(true); + }) + .catch((error: any) => { + console.log('Error resetting the DB -- > ', error); + }); + }; + + return ( + +
+ +
+
+ +

Rems Admin

+
+ {token ? ( +
+ + +
+ ) : ( + + )} +
+
+ + {'Reset Rems Admin Database?'} + + + Resetting the rems admin database will delete any existing rems case information and + completed questionnaires. + + + + + + + +
+ {token ? : } +
+ ); +} + +export default App; diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 00000000..ec2585e8 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 00000000..ea9e3630 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + +); diff --git a/frontend/src/views/DataViews/CaseCollection.tsx b/frontend/src/views/DataViews/CaseCollection.tsx new file mode 100644 index 00000000..865c6b4d --- /dev/null +++ b/frontend/src/views/DataViews/CaseCollection.tsx @@ -0,0 +1,180 @@ +import axios from 'axios'; +import { useEffect, useState, SetStateAction } from 'react'; +import { + Button, + Card, + CardContent, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow +} from '@mui/material'; +import IconButton from '@mui/material/IconButton'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { Refresh } from '@mui/icons-material'; + +export type RemsCase = { + case_number?: string; + auth_number?: string; + patientFirstName?: string; + patientLastName?: string; + patientDOB?: string; + drugCode?: string; + drugName?: number; + status?: string; + dispenseStatus?: string; + metRequirements: + | { + metRequirementId: string; + requirementsDescription: string; + requirementName: string; + stakeholderId: string; + completed: boolean; + }[]; + _id: string; +}; +const CaseCollection = (props: { refresh: boolean }) => { + const [allData, setAllData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (props.refresh) { + getAllRemsCase(); + } + }, [props.refresh]); + + useEffect(() => { + getAllRemsCase(); + }, []); + + const getAllRemsCase = async () => { + const url = 'http://localhost:8090/api/all/remscase'; + await axios + .get(url) + .then(function (response: { data: SetStateAction }) { + setAllData(response.data); + setIsLoading(false); + }) + .catch((error: any) => { + setIsLoading(false); + console.log('Error -- > ', error); + }); + }; + + const deleteSingleRow = async (event: any, row: RemsCase) => { + const url = 'http://localhost:8090/api/remsCase/deleteOne'; + await axios + .post(url, { data: { params: row } }) + .then(function (response: { data: any; status: number }) { + if (response.status === 200) { + getAllRemsCase(); + } + }) + .catch((error: any) => { + setIsLoading(false); + console.log('Error -- > ', error); + }); + }; + + const formattedReqs = (row: RemsCase) => { + let reqNames: String[] = []; + row.metRequirements.forEach((req: any) => { + const completed = req.completed ? 'Completed' : 'Not completed'; + reqNames.push(`${req.requirementName}: ${completed}`); + }); + return reqNames.join(', '); + }; + + if (allData.length < 1 && !isLoading) { + return ( + +
+ +
+

No data

+
+ ); + } else { + return ( + +
+ +
+ + + + + + + + Case Number + Patient First Name + Patient Last Name + Drug Name + Drug Code + Patient DOB + Status + Dispense Status + Authorization Number + Met Requirements + Delete + + + + {allData.map(row => { + const metReq = formattedReqs(row); + return ( + + {row.case_number} + {row.patientFirstName} + {row.patientLastName} + {row.drugName} + {row.drugCode} + {row.patientDOB} + {row.status} + {row.dispenseStatus} + {row.auth_number} + {metReq} + + deleteSingleRow(event, row)} + > + + + + + ); + })} + +
+
+
+
+
+
+ ); + } +}; + +export default CaseCollection; diff --git a/frontend/src/views/DataViews/Data.tsx b/frontend/src/views/DataViews/Data.tsx new file mode 100644 index 00000000..5c5e30e2 --- /dev/null +++ b/frontend/src/views/DataViews/Data.tsx @@ -0,0 +1,67 @@ +import { SyntheticEvent, useState } from 'react'; +import { Box, Tab, Tabs } from '@mui/material'; +import { Container } from '@mui/system'; +import CaseCollection from './CaseCollection'; +import Medications from './Medications'; +import MetRequirements from './MetRequirements'; + +function a11yProps(index: number) { + return { + id: `simple-tab-${index}`, + 'aria-controls': `simple-tabpanel-${index}` + }; +} +const Data = (props: { refresh: boolean }) => { + const [tabIndex, setValue] = useState(0); + + const handleChange = (event: SyntheticEvent, newValue: number) => { + setValue(newValue); + }; + + return ( +
+ + + + + + + + + + + + + {tabIndex === 0 && ( + + + + )} + {tabIndex === 1 && ( + + + + )} + {tabIndex === 2 && ( + + + + )} + + + + +
+ ); +}; + +export default Data; diff --git a/frontend/src/views/DataViews/Medications.tsx b/frontend/src/views/DataViews/Medications.tsx new file mode 100644 index 00000000..373a734c --- /dev/null +++ b/frontend/src/views/DataViews/Medications.tsx @@ -0,0 +1,138 @@ +import axios from 'axios'; +import { useEffect, useState, SetStateAction } from 'react'; +import { + Box, + Button, + Card, + CardActions, + CardContent, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography +} from '@mui/material'; +import { Refresh } from '@mui/icons-material'; + +export type Medication = { + code?: string; + codeSystem?: string; + name?: string; + requirements: Array<{ + name: string; + description: string; + resourceId: string; + appContext: string; + requiredToDispense: boolean; + }>; + _id: string; +}; + +const Medications = (props: { refresh: boolean }) => { + const [allData, setAllData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (props.refresh) { + getAllMedications(); + } + }, [props.refresh]); + + useEffect(() => { + getAllMedications(); + }, []); + + const getAllMedications = async () => { + const url = 'http://localhost:8090/api/all/medications'; + await axios + .get(url) + .then(function (response: { data: SetStateAction }) { + setAllData(response.data); + setIsLoading(false); + }) + .catch((error: any) => { + setIsLoading(false); + console.log('Error -- > ', error); + }); + }; + + const formattedReqs = (row: Medication) => { + let reqNames: String[] = []; + row.requirements.forEach((req: any) => { + reqNames.push(req.name); + }); + return reqNames.join(', '); + }; + + if (allData.length < 1 && !isLoading) { + return ( + +
+ +
+

No data

+
+ ); + } else { + return ( + + +
+ +
+ + + + + + + Name + Code + Code System + Requirements + + + + {allData.map(row => { + const format = formattedReqs(row); + return ( + + {row.name} + {row.code} + {row.codeSystem} + {format} + + ); + })} + +
+
+
+
+
+
+ ); + } +}; + +export default Medications; diff --git a/frontend/src/views/DataViews/MetRequirements.tsx b/frontend/src/views/DataViews/MetRequirements.tsx new file mode 100644 index 00000000..98e2d5fa --- /dev/null +++ b/frontend/src/views/DataViews/MetRequirements.tsx @@ -0,0 +1,163 @@ +import axios from 'axios'; +import { useEffect, useState, SetStateAction } from 'react'; +import { + Box, + Button, + Card, + CardActions, + CardContent, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography +} from '@mui/material'; +import IconButton from '@mui/material/IconButton'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { Refresh } from '@mui/icons-material'; + +export type MetRequirements = { + drugName?: string; + requirementName?: string; + stakeholderId?: string; + completed?: boolean; + completedQuestionnaire?: { questionnaire: string }; + case_numbers?: []; + _id: string; +}; + +const MetRequirements = (props: { refresh: boolean }) => { + const [allData, setAllData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (props.refresh) { + getAllMetReqs(); + } + }, [props.refresh]); + + useEffect(() => { + getAllMetReqs(); + }, []); + + const getAllMetReqs = async () => { + const url = 'http://localhost:8090/api/all/metreqs'; + await axios + .get(url) + .then(function (response: { data: any }) { + setAllData(response.data); + setIsLoading(false); + }) + .catch((error: any) => { + setIsLoading(false); + console.log('Error -- > ', error); + }); + }; + + const deleteSingleRow = async (event: any, row: MetRequirements) => { + const url = 'http://localhost:8090/api/metreqs/deleteOne'; + await axios + .post(url, { data: { params: row } }) + .then(function (response: { data: any; status: number }) { + if (response.status === 200) { + getAllMetReqs(); + } + }) + .catch((error: any) => { + setIsLoading(false); + console.log('Error -- > ', error); + }); + }; + + const formattedQuestionnaire = (row: MetRequirements) => { + return row?.completedQuestionnaire?.questionnaire.split('http://localhost:8090/4_0_0/')[1]; + }; + const formattedCompleted = (row: MetRequirements) => { + return row?.completed === true ? 'Yes' : 'No'; + }; + + if (allData.length < 1 && !isLoading) { + return ( + +
+ +
+

No data

+
+ ); + } else { + return ( + + +
+ +
+ + + + + + + Drug Name + Requirement Name + Case Numbers + Completed + Completed Questionnaire + Delete + + + + {allData.map(row => { + const format = formattedQuestionnaire(row); + const complete = formattedCompleted(row); + return ( + + {row.drugName} + {row.requirementName} + {row.case_numbers?.join(', ')} + {complete} + {format} + + deleteSingleRow(event, row)} + > + + + + + ); + })} + +
+
+
+
+
+
+ ); + } +}; + +export default MetRequirements; diff --git a/frontend/src/views/Login.tsx b/frontend/src/views/Login.tsx new file mode 100644 index 00000000..15b7df6f --- /dev/null +++ b/frontend/src/views/Login.tsx @@ -0,0 +1,97 @@ +import { SetStateAction, useState } from 'react'; +import axios from 'axios'; +import { Avatar, Box, Button, Container, CssBaseline, TextField, Typography } from '@mui/material'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import config from '../../config.json'; + +const Login = props => { + const [showMessage, setShowMessage] = useState(false); + + const handleSubmit = (event: { + preventDefault: () => void; + currentTarget: HTMLFormElement | undefined; + }) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + const user = data.get('username')?.toString(); + const pass = data.get('password')?.toString(); + if (user && pass) { + const params = new URLSearchParams(); + params.append('username', user); + params.append('password', pass); + params.append('grant_type', 'password'); + params.append('client_id', config.client); + axios + .post(`${config.auth}/realms/${config.realm}/protocol/openid-connect/token`, params, { + withCredentials: true + }) + .then( + (result: { data: { scope: string; access_token: SetStateAction } }) => { + // do something with the token + const scope = result.data.scope; + if (scope) { + setShowMessage(true); + props.tokenCallback(result.data.access_token); + } else { + console.error('Unauthorized User'); + } + } + ) + .catch(err => { + if (err.response.status === 401) { + console.error('Unknown user'); + setShowMessage(true); + } else { + console.error(err); + } + }); + } + }; + + return ( + + + + + + + + Sign in + + + + + + {showMessage ?

Error signing in. Please try again.

: ''} +
+
+
+ ); +}; + +export default Login; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 00000000..f0a23505 --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 00000000..0d3d7144 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 00000000..65491824 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()] +}); diff --git a/src/lib/api_routes.ts b/src/lib/api_routes.ts new file mode 100644 index 00000000..310e7a89 --- /dev/null +++ b/src/lib/api_routes.ts @@ -0,0 +1,83 @@ +import { Router, Request, Response } from 'express'; +import { + remsCaseCollection, + medicationCollection, + metRequirementsCollection +} from '../fhir/models'; +const router = Router(); + +router.get('/all/remscase', async (req: Request, res: Response) => { + try { + console.log('Getting all rems case collection'); + res.send(await remsCaseCollection.find({})); + } catch (error) { + console.log('ERROR getting rems case collection --> ', error); + throw error; + } +}); + +router.post('/remsCase/deleteOne', async (req: Request, res: Response) => { + try { + // delete rems case collection + await remsCaseCollection.findByIdAndDelete({ _id: req.body.data.params?._id }); + // find and delete patient met requirements, either enrollment or status update forms + const allMatchedMetReqs = await metRequirementsCollection.find({ + case_numbers: { $in: req.body.data.params?.case_number } + }); + allMatchedMetReqs.forEach(async matchedReq => { + if (matchedReq.requirementName.includes('Patient')) { + await metRequirementsCollection.findOneAndDelete({ _id: matchedReq._id }); + } else { + // If not a patient form - remove case number from prescriber forms + await metRequirementsCollection.findOneAndUpdate( + { _id: matchedReq._id }, + { + case_numbers: matchedReq.case_numbers.filter( + num => num !== req.body.data.params?.case_number + ) + }, + { new: true } + ); + } + }); + res.send( + `Deleted REMS Case collection and patient forms with case number - ${req.body.data.params?.case_number}` + ); + } catch (error) { + console.log('ERROR deleting data --> ', error); + throw error; + } +}); + +router.get('/all/medications', async (req: Request, res: Response) => { + try { + console.log('Getting all medications'); + res.send(await medicationCollection.find({})); + } catch (error) { + console.log('ERROR getting all medications --> ', error); + throw error; + } +}); + +router.get('/all/metreqs', async (req: Request, res: Response) => { + try { + console.log('Getting all met requirements'); + res.send(await metRequirementsCollection.find({})); + } catch (error) { + console.log('ERROR getting met requirements --> ', error); + throw error; + } +}); + +router.post('/metreqs/deleteOne', async (req: Request, res: Response) => { + try { + // delete met requirements + await metRequirementsCollection.findByIdAndDelete({ _id: req.body.data.params?._id }); + res.send(`Deleted met requirement with name - ${req.body.data.params?.requirementName}`); + } catch (error) { + console.log('ERROR deleting met requirement --> ', error); + throw error; + } +}); + +export default router; diff --git a/src/rems-cds-hooks b/src/rems-cds-hooks index 51dd2d4d..8a51ac5c 160000 --- a/src/rems-cds-hooks +++ b/src/rems-cds-hooks @@ -1 +1 @@ -Subproject commit 51dd2d4d3dd46313187b4fb2c035c5e9b62d0fdd +Subproject commit 8a51ac5c6837549e43a30c4f886c0bfd2aeae50d diff --git a/src/server.ts b/src/server.ts index a2e2f8fa..bfb47ef6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,6 +10,7 @@ import encounterStartService from './hooks/rems.encounterstart'; import { Server } from '@projecttacoma/node-fhir-server-core'; import Etasu from './lib/etasu'; import Ncpdp from './ncpdp/script'; +import Api from './lib/api_routes'; import env from 'env-var'; import https from 'https'; import fs from 'fs'; @@ -29,6 +30,7 @@ const initialize = (config: any) => { .registerCdsHooks(config.server) .configureEtasuEndpoints() .configureNCPDPEndpoints() + .configureUIEndpoints() .setErrorRoutes(); }; @@ -135,6 +137,11 @@ class REMSServer extends Server { return this; } + configureUIEndpoints() { + this.app.use('/api/', Api); + return this; + } + /** * @method listen * @description Start listening on the configured port From fe9df9bdd162fb186aee5c15ae1bf2d78fdfcc42 Mon Sep 17 00:00:00 2001 From: Ariel Virgulto <31778437+avirgulto@users.noreply.github.com> Date: Fri, 27 Sep 2024 13:05:01 -0400 Subject: [PATCH 15/15] Add popup for met req column (#158) Co-authored-by: Ariel Virgulto --- .../src/views/DataViews/CaseCollection.tsx | 38 +++++++-- frontend/src/views/FormPopup.tsx | 82 +++++++++++++++++++ 2 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 frontend/src/views/FormPopup.tsx diff --git a/frontend/src/views/DataViews/CaseCollection.tsx b/frontend/src/views/DataViews/CaseCollection.tsx index 865c6b4d..96bdd422 100644 --- a/frontend/src/views/DataViews/CaseCollection.tsx +++ b/frontend/src/views/DataViews/CaseCollection.tsx @@ -15,6 +15,9 @@ import { import IconButton from '@mui/material/IconButton'; import DeleteIcon from '@mui/icons-material/Delete'; import { Refresh } from '@mui/icons-material'; +import CircleIcon from '@mui/icons-material/Circle'; +import Tooltip from '@mui/material/Tooltip'; +import FormPopup from '../FormPopup'; export type RemsCase = { case_number?: string; @@ -39,6 +42,8 @@ export type RemsCase = { const CaseCollection = (props: { refresh: boolean }) => { const [allData, setAllData] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [open, setOpen] = useState(false); + const [rowData, setRowData] = useState({}); useEffect(() => { if (props.refresh) { @@ -79,13 +84,35 @@ const CaseCollection = (props: { refresh: boolean }) => { }); }; + const openForms = (event: any, row: RemsCase) => { + setRowData(row); + setOpen(true); + } + + const handleClose = () => { + setOpen(false); + } + const formattedReqs = (row: RemsCase) => { - let reqNames: String[] = []; + let allCompleted = true; + let color = '#f0ad4e'; + let tooltip = 'Forms not completed'; row.metRequirements.forEach((req: any) => { - const completed = req.completed ? 'Completed' : 'Not completed'; - reqNames.push(`${req.requirementName}: ${completed}`); + if (!req.completed) { + allCompleted = false; + } }); - return reqNames.join(', '); + if (allCompleted) { + color = 'green'; + tooltip = 'Forms completed'; + } + return ( + + openForms(event, row)}> + + + + ) }; if (allData.length < 1 && !isLoading) { @@ -154,7 +181,7 @@ const CaseCollection = (props: { refresh: boolean }) => { {row.status} {row.dispenseStatus} {row.auth_number} - {metReq} + {metReq} { + ); } diff --git a/frontend/src/views/FormPopup.tsx b/frontend/src/views/FormPopup.tsx new file mode 100644 index 00000000..817b61c5 --- /dev/null +++ b/frontend/src/views/FormPopup.tsx @@ -0,0 +1,82 @@ +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import { + Box, + Button, + IconButton, + List, + ListItem, + ListItemIcon, + ListItemText + } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import CheckCircle from '@mui/icons-material/CheckCircle'; +import Close from '@mui/icons-material/Close'; + +const FormPopup = (props: { open: any; handleClose: any; data: any; }) => { + const { open, handleClose, data } = props; + + return ( + + + REMS Status + + ({ + position: 'absolute', + right: 8, + top: 8, + color: 'grey', + })} + > + + + + + + {data?.metRequirements?.map((metRequirements: any) => { + return ( + + + {metRequirements.completed ? ( + + ) : ( + + )} + + {metRequirements.completed ? ( + + ) : ( + + )} + + ); + })} + + + + + + + + ) +}; + +export default FormPopup; \ No newline at end of file