diff --git a/README.md b/README.md index 24f4fd3..c68f548 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 0000000..a547bf3 --- /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 0000000..74872fd --- /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 0000000..2f2a382 --- /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 0000000..f3bd4ef --- /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 0000000..96e2c90 --- /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 0000000..7f69441 --- /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 0000000..6691ba1 Binary files /dev/null and b/frontend/public/admin.png differ diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /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 0000000..d1b03b7 --- /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 0000000..895151d --- /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 0000000..6c87de9 --- /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 0000000..ec2585e --- /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 0000000..ea9e363 --- /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 0000000..865c6b4 --- /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 0000000..5c5e30e --- /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 0000000..373a734 --- /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 0000000..98e2d5f --- /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 0000000..15b7df6 --- /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 0000000..11f02fe --- /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 0000000..f0a2350 --- /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 0000000..1ffef60 --- /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 0000000..0d3d714 --- /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 0000000..6549182 --- /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 0000000..310e7a8 --- /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 51dd2d4..8a51ac5 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 a2e2f8f..bfb47ef 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