Added the files.

This commit is contained in:
Batuhan Berk Başoğlu 2021-03-30 21:50:36 -04:00
commit 38ccdcbfe5
124 changed files with 32079 additions and 0 deletions

25
client/.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
firebase-debug.log*

16
client/README.md Normal file
View file

@ -0,0 +1,16 @@
# HMS client-side code
## Get Started
Get started developing...
```shell
# install deps
npm install
# run in development mode
npm run dev
# run tests
npm run test
```

View file

@ -0,0 +1,11 @@
#!/bin/bash
set -ev
#run only on master
if [[ $TRAVIS_PULL_REQUEST == "false" ]] && [[ $TRAVIS_BRANCH == "master" ]]; then
npm install -g firebase-tools
npm install -g selenium-webdriver
npm install codecov.io coveralls
cd functions
npm install
cd ..
fi

View file

@ -0,0 +1,88 @@
// To run this script use this command
// node bs.js yourBSUserName yourBSKey
var webdriver = require('selenium-webdriver')
var test = require('./bs_test.js')
// Input capabilities
var iPhone = {
browserName: 'iPhone',
device: 'iPhone 7',
realMobile: 'true',
os_version: '10.3',
'browserstack.user': process.argv[2],
'browserstack.key': process.argv[3],
}
var android = {
browserName: 'android',
device: 'Samsung Galaxy S8',
realMobile: 'true',
os_version: '7.0',
'browserstack.user': process.argv[2],
'browserstack.key': process.argv[3],
}
var desktopFF = {
browserName: 'Firefox',
browser_version: '59.0',
os: 'Windows',
os_version: '10',
resolution: '1024x768',
'browserstack.user': process.argv[2],
'browserstack.key': process.argv[3],
}
var desktopEdge = {
browserName: 'Edge',
browser_version: '16.0',
os: 'Windows',
os_version: '10',
resolution: '1024x768',
'browserstack.user': process.argv[2],
'browserstack.key': process.argv[3],
}
var desktopIE = {
browserName: 'Chrome',
browser_version: '69.0',
os: 'Windows',
os_version: '10',
resolution: '1024x768',
'browserstack.user': process.argv[2],
'browserstack.key': process.argv[3],
}
/*
var iPhoneDriver = new webdriver.Builder()
.usingServer('http://hub-cloud.browserstack.com/wd/hub')
.withCapabilities(iPhone)
.build()
var androidDriver = new webdriver.Builder()
.usingServer('http://hub-cloud.browserstack.com/wd/hub')
.withCapabilities(android)
.build()
*/
var desktopFFDriver = new webdriver.Builder()
.usingServer('http://hub-cloud.browserstack.com/wd/hub')
.withCapabilities(desktopFF)
.build()
var desktopEdgeDriver = new webdriver.Builder()
.usingServer('http://hub-cloud.browserstack.com/wd/hub')
.withCapabilities(desktopEdge)
.build()
var desktopIEDriver = new webdriver.Builder()
.usingServer('http://hub-cloud.browserstack.com/wd/hub')
.withCapabilities(desktopIE)
.build()
//test.runTest(iPhoneDriver)
//test.runTest(androidDriver)
test.runTest(desktopFFDriver)
test.runTest(desktopEdgeDriver)
test.runTest(desktopIEDriver)

View file

@ -0,0 +1,62 @@
var webdriver = require('selenium-webdriver')
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
}
module.exports.runTest = async driver => {
try {
await driver.get('https://www.react-most-wanted.com')
await sleep(2000)
var signInButton = driver.wait(webdriver.until.elementLocated(webdriver.By.name('signin')))
await signInButton.click()
await sleep(2000)
var passwordButton = driver.wait(
webdriver.until.elementLocated(
webdriver.By.className(
'firebaseui-idp-button mdl-button mdl-js-button mdl-button--raised firebaseui-idp-password firebaseui-id-idp-button'
)
)
)
await passwordButton.click()
await sleep(2000)
var emailInput = driver.wait(webdriver.until.elementLocated(webdriver.By.name('email')))
await emailInput.sendKeys('test@test.com')
await sleep(2000)
var nextButton = driver.wait(
webdriver.until.elementLocated(
webdriver.By.className(
'firebaseui-id-submit firebaseui-button mdl-button mdl-js-button mdl-button--raised mdl-button--colored'
)
)
)
await nextButton.click()
await sleep(2000)
var passwordInput = driver.wait(webdriver.until.elementLocated(webdriver.By.name('password')))
await passwordInput.sendKeys('123456')
await sleep(2000)
await passwordInput.sendKeys(webdriver.Key.ENTER)
await sleep(2000)
driver.quit()
} catch (e) {
console.log('Test Failed')
console.error(e)
process.exitCode = 1
process.abort()
driver.quit()
}
}

View file

@ -0,0 +1,7 @@
#!/bin/bash
set -ev
#run only on master
if [[ $TRAVIS_PULL_REQUEST == "false" ]] && [[ $TRAVIS_BRANCH == "master" ]]; then
npm run build
fi

View file

@ -0,0 +1,8 @@
#!/bin/bash
set -ev
#run only on master
if [[ $TRAVIS_PULL_REQUEST == "false" ]] && [[ $TRAVIS_BRANCH == "master" ]]; then
firebase use prod --token $FIREBASE_TOKEN
firebase deploy --non-interactive --token $FIREBASE_TOKEN
node ./continuous_deployment/bs.js $BSNAME $BSKEY
fi

6
client/jsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}

17632
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

57
client/package.json Normal file
View file

@ -0,0 +1,57 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@formatjs/intl-relativetimeformat": "7.x",
"@material-ui/core": "4.x",
"@material-ui/icons": "4.x",
"@material-ui/lab": "^4.0.0-alpha.56",
"base-shell": "1.x",
"crypto-js": "^4.0.0",
"github-markdown-css": "4.x",
"immutable": "^4.0.0-rc.12",
"intl": "1.x",
"intl-locales-supported": "1.x",
"material-ui-shell": "1.x",
"notistack": "1.x",
"react": "17.x",
"react-custom-scrollbars": "4.x",
"react-dom": "17.x",
"react-intl": "5.x",
"react-ios-pwa-prompt": "1.x",
"react-markdown": "5.x",
"react-redux": "^7.2.2",
"react-router-dom": "5.x",
"react-router-redux": "^4.0.8",
"react-scripts": "4.0.0",
"react-virtualized-auto-sizer": "1.x",
"react-window": "1.x",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0"
},
"scripts": {
"dev": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
client/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

43
client/public/index.html Normal file
View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>HealthGeeks HMS-PMS</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
client/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
client/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
client/public/robots.txt Normal file
View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

13
client/src/App.js Normal file
View file

@ -0,0 +1,13 @@
import React, { Component } from 'react'
import App from 'base-shell/lib'
import MUIConfig from 'material-ui-shell/lib'
import merge from 'base-shell/lib/utils/config'
import _config from './config'
const config = merge(MUIConfig, _config)
export default class Demo extends Component {
render() {
return <App config={config} />
}
}

View file

@ -0,0 +1,11 @@
/* eslint-disable import/no-anonymous-default-export */
export default {
API_URL: 'http://localhost:3001',
user: {
user: "/api/v1/staff",
},
auth: {
register: "/api/v1/staff/register",
login: "/api/v1/staff/login"
},
};

View file

@ -0,0 +1,37 @@
import { lazy } from 'react'
import locales from './locales'
import apiRoutes from './apiRoutes'
import routes from './routes'
import getMenuItems from './menuItems'
import themes from './themes'
import parseLanguages from 'base-shell/lib/utils/locale'
const config = {
auth: {
signInURL: '/',
},
apiRoutes,
routes,
locale: {
locales,
defaultLocale: parseLanguages( 'en'),
onError: (e) => {
//console.warn(e)
return
},
},
menu: {
getMenuItems,
},
theme: {
themes,
defaultThemeID: 'default',
defaultType: 'light',
},
pages: {
LandingPage: lazy(() => import('../pages/LandingPage/LandingPage')),
PageNotFound: lazy(() => import('../pages/PageNotFound/PageNotFound')),
},
}
export default config

View file

@ -0,0 +1 @@
export { default } from './config.js'

View file

@ -0,0 +1,43 @@
import { defineMessages } from 'react-intl'
const messages = defineMessages({
app_name: 'React Meist Gesucht',
sign_in: 'Anmelden',
sign_out: 'Abmelden',
sign_up: 'Anmeldung',
email: 'Email',
username: 'Nutzername',
password: 'Passwort',
about: 'Über',
home: 'Startseite',
page_not_found: 'Seite nicht gefunden',
settings: 'Einstellungen',
theme: 'Thema',
default: 'Standard',
red: 'Rot',
green: 'Grün',
language: 'Sprache',
en: 'Englisch',
de: 'Deutsch',
ru: 'Russisch',
menu: 'Menü',
menu_mini_mode: 'Mini-Menü',
offline: 'Offline',
demos:'Demos',
dialog_demo:'Demo Dialog',
dialog_title:'Dialog titel',
dialog_action:'JA, Löschen',
dialog_message:`Dialognachricht. Sie können hier so viel Text einfügen, wie Sie möchten.
Stellen Sie eine Frage oder zeigen Sie eine Warnung an, bevor Sie etwas löschen.
Sie können den Aktionstext auch auf "JA, Löschen" setzen und diese Aktion ausführen, indem Sie eine "handleAction" -Stütze übergeben.
Dies erhält einen "handleClose" -Rückruf, mit dem Sie den Dialog schließen können, wenn Ihre Aktion abgeschlossen ist.`,
toast_demo:'Demo Toast',
filter_demo:'Demo filter',
list_page_demo:'List Page Demo mit {count} Zeilen',
forgot_password:'Vergessen passwort',
password_reset:'Passwort zurücksetzen',
password_confirm:'Passwortbestätigung',
registration:'Registrierung'
})
export default messages

View file

@ -0,0 +1,43 @@
import { defineMessages } from 'react-intl'
const messages = defineMessages({
app_name: 'React Most Wanted',
sign_in: 'Sign in',
sign_out: 'Sign out',
sign_up: 'Sign up',
email: 'Email',
username: 'Username',
password: 'Password',
about: 'About',
home: 'Home',
page_not_found: 'Page not found',
settings: 'Settings',
theme: 'Theme',
default: 'Default',
red: 'Red',
green: 'Green',
language: 'Language',
en: 'English',
de: 'German',
ru: 'Russian',
menu: 'Menu',
menu_mini_mode: 'Mini menu',
offline: 'Offline',
demos:'Demos',
dialog_demo:'Demo dialog',
dialog_title:'Dialog title',
dialog_action:'YES, Delete',
dialog_message:`Dialog message. You can put as much text as you want here.
Ask a question or show a warning befor deleting something.
You can also set the action text to somerhing like "YES, Delete" and run that action by passing a "handleAction" prop.
This receives a "handleClose" callback with wich you can close the dialog when your action is done.`,
toast_demo:'Demo toast',
filter_demo:'Demo filter',
list_page_demo:'List Page demo with {count} rows',
forgot_password:'Forgot password',
password_reset:'Password reset',
password_confirm:'Password confirm',
registration:'Registration'
})
export default messages

View file

@ -0,0 +1,19 @@
const locales = [
{
locale: 'en',
messages: import('./en'),
//loadData: import(`@formatjs/intl-relativetimeformat/dist/locale-data/en`),
},
{
locale: 'ru',
messages: import('./ru'),
//loadData: import(`@formatjs/intl-relativetimeformat/dist/locale-data/ru`),
},
{
locale: 'de',
messages: import('./de'),
//loadData: import(`@formatjs/intl-relativetimeformat/dist/locale-data/de`),
},
]
export default locales

View file

@ -0,0 +1,43 @@
import { defineMessages } from 'react-intl'
const messages = defineMessages({
app_name: 'React Most Wanted',
sign_in: 'Вход',
sign_out: 'Выход',
sign_up: 'Зарегистрироваться',
email: 'Эл. адрес',
username: 'Имя пользователя',
password: 'Пароль',
about: 'О нас',
home: 'Главная',
page_not_found: 'Страница не найдена',
settings: 'Настройки',
theme: 'Тема',
default: 'По умолчанию',
red: 'Красная',
green: 'Зелёная',
language: 'Язык',
en: 'English',
de: 'Deutsch',
ru: 'Русский',
menu: 'Меню',
menu_mini_mode: 'Мини меню',
offline: 'Офлайн',
demos:'Демонстрации',
dialog_demo:'Демонстрация диалога',
dialog_title:'Заголовок диалога',
dialog_action:'Да, удалить',
dialog_message:`Диалоговое сообщение. Вы можете поместить сюда сколько угодно текста.
Задайте вопрос или покажите предупреждение перед удалением чего-либо.
Вы также можете задать для текста действия значение somerhing, например «Да, Удалить», и запустить это действие, передав опору «handleAction».
Он получает обратный вызов handleClose, с которым вы можете закрыть диалог, когда ваше действие будет выполнено.`,
toast_demo:'Демонстрация toast',
filter_demo:'Демонстрация фильтра',
list_page_demo:'Демонстрация страницы списка с {count} строками',
forgot_password:'Забыли пароль',
password_reset:'Сброс пароля',
password_confirm:'Подтвердить пароль',
registration:'Регистрация',
})
export default messages

View file

@ -0,0 +1,149 @@
import ChatBubble from '@material-ui/icons/ChatBubble'
import DashboardIcon from '@material-ui/icons/Dashboard'
import ExitToAppIcon from '@material-ui/icons/ExitToApp'
import FilterList from '@material-ui/icons/FilterList'
import GetApp from '@material-ui/icons/GetApp'
import InfoOutlined from '@material-ui/icons/InfoOutlined'
import LanguageIcon from '@material-ui/icons/Language'
import LockIcon from '@material-ui/icons/Lock'
import QuestionAnswer from '@material-ui/icons/QuestionAnswer'
import React from 'react'
import SettingsIcon from '@material-ui/icons/SettingsApplications'
import StyleIcon from '@material-ui/icons/Style'
import Tab from '@material-ui/icons/Tab'
import ViewList from '@material-ui/icons/ViewList'
import Web from '@material-ui/icons/Web'
import allLocales from './locales'
import allThemes from './themes'
import { store, Action } from "reducers";
const getMenuItems = (props) => {
const {
intl,
updateLocale,
locale,
menuContext,
themeContext,
a2HSContext,
auth: authData,
} = props
const { isAuthMenuOpen, setMiniMode } = menuContext
const { themeID, setThemeID } = themeContext
const { setAuth } = authData
const { isAppInstallable, isAppInstalled, deferredPrompt } = a2HSContext
const state = store.getState();
let isAuthorised = state.Auth.get('authenticated');
store.subscribe(_ => {
isAuthorised = store.getState().Auth.get('authenticated');
isAuthorised && setAuth({ isAuthenticated: isAuthorised })
})
setMiniMode(false);
const localeItems = allLocales.map((l) => {
return {
value: undefined,
visible: true,
primaryText: intl.formatMessage({ id: l.locale }),
onClick: () => {
updateLocale(l.locale)
},
leftIcon: <LanguageIcon />,
}
})
const themeItems = allThemes.map((t) => {
return {
value: undefined,
visible: true,
primaryText: intl.formatMessage({ id: t.id }),
onClick: () => {
setThemeID(t.id)
},
leftIcon: <StyleIcon style={{ color: t.color }} />,
}
})
if (isAuthMenuOpen || !isAuthorised) {
return [
{
value: '/',
onClick: isAuthorised
? () => {
setAuth({ isAuthenticated: false })
store.dispatch(Action.LogoutUser())
}
: () => {},
visible: true,
primaryText: isAuthorised
? intl.formatMessage({ id: 'sign_out' })
: intl.formatMessage({ id: 'sign_in' }),
leftIcon: isAuthorised ? <ExitToAppIcon /> : <LockIcon />,
},
]
}
return [
{
value: '/home',
visible: isAuthorised,
primaryText: intl.formatMessage({ id: 'home' }),
leftIcon: <DashboardIcon />,
},
{
value: '/staff_context',
visible: true,
primaryText: 'Staff Context',
leftIcon: <Web />,
},
{
value: '/patient_context',
visible: true,
primaryText: 'Patient Context',
leftIcon: <Web />,
},
{
value: '/division_context',
visible: true,
primaryText: 'Division Context',
leftIcon: <Web />,
},
{ divider: true },
{
primaryText: intl.formatMessage({ id: 'settings' }),
primaryTogglesNestedList: true,
leftIcon: <SettingsIcon />,
nestedItems: [
{
primaryText: intl.formatMessage({ id: 'theme' }),
secondaryText: intl.formatMessage({ id: themeID }),
primaryTogglesNestedList: true,
leftIcon: <StyleIcon />,
nestedItems: themeItems,
},
{
primaryText: intl.formatMessage({ id: 'language' }),
secondaryText: intl.formatMessage({ id: locale }),
primaryTogglesNestedList: true,
leftIcon: <LanguageIcon />,
nestedItems: localeItems,
}
],
},
{
value: null,
visible: isAppInstallable && !isAppInstalled,
onClick: () => {
deferredPrompt.prompt()
},
primaryText: intl.formatMessage({
id: 'install',
defaultMessage: 'Install',
}),
leftIcon: <GetApp />,
},
]
}
export default getMenuItems

View file

@ -0,0 +1,42 @@
/* eslint-disable react/jsx-key */
import React, { lazy } from 'react'
import AuthorizedRoute from 'base-shell/lib/components/AuthorizedRoute/AuthorizedRoute'
import UnauthorizedRoute from 'base-shell/lib/components/UnauthorizedRoute/UnauthorizedRoute'
import { Route } from 'react-router-dom'
const SignIn = lazy(() => import('../pages/SignIn/SignIn'))
const SignUp = lazy(() => import('../pages/SignUp/SignUp'))
const PasswordReset = lazy(() => import('../pages/PasswordReset/PasswordReset'))
const About = lazy(() => import('../pages/About'))
const Home = lazy(() => import('../pages/Home/Home'))
const StaffContext = lazy(() => import('../pages/StaffContext/wrapper'))
const PatientContext = lazy ( () => import('../pages/PatientContext'))
const DivisionContext = lazy(() => import('../pages/DivisionContext'))
const routes = [
<UnauthorizedRoute path="/signin" redirectTo="/" exact component={SignIn} />,
<UnauthorizedRoute path="/auth/staff/login" redirectTo="/home" exact component={SignIn} />,
<UnauthorizedRoute path="/auth/medical/login" redirectTo="/home" exact component={SignIn} />,
<UnauthorizedRoute path="/auth/nurse/login" redirectTo="/home" exact component={SignIn} />,
<UnauthorizedRoute path="/signup" redirectTo="/" exact component={SignUp} />,
<UnauthorizedRoute path="/auth/staff/signup" redirectTo="/home" exact component={SignUp} />,
<UnauthorizedRoute path="/auth/medical/signup" redirectTo="/home" exact component={SignUp} />,
<UnauthorizedRoute path="/auth/nurse/signup" redirectTo="/home" exact component={SignUp} />,
<UnauthorizedRoute
path="/password_reset"
redirectTo="/"
exact
component={PasswordReset}
/>,
<Route path="/about" exact component={About} />,
<AuthorizedRoute path="/home" exact component={Home} />,
<AuthorizedRoute path="/patient_context" exact component={PatientContext} />,
<AuthorizedRoute path="/staff_context" exact component={StaffContext} />,
<AuthorizedRoute path="/division_context" exact component={DivisionContext} />,
]
export default routes

View file

@ -0,0 +1,33 @@
import red from '@material-ui/core/colors/red'
import pink from '@material-ui/core/colors/pink'
import green from '@material-ui/core/colors/green'
const themes = [
{
id: 'default',
},
{
id: 'red',
color: red[500],
source: {
palette: {
primary: red,
secondary: pink,
error: red,
},
},
},
{
id: 'green',
color: green[500],
source: {
palette: {
primary: green,
secondary: red,
error: red,
},
},
},
]
export default themes

View file

@ -0,0 +1,64 @@
//TODO: Break up large constants file into smaller constants file
const PUBLIC_URL = process.env.PUBLIC_URL;
const PHOTO_NAMES = {
cloud: `${PUBLIC_URL}/img/icons/cloud.png`,
earth: `${PUBLIC_URL}/img/icons/earth.png`,
heart: `${PUBLIC_URL}/img/icons/heart.png`,
};
// Router's base (i.e. anything after the domain)
const ROUTER_BASE_NAME = "/";
// Various Server URLs
var SERVER_URL = "http://localhost:8081";
if (process && process.env) {
if (process.env.REACT_APP_SERVER_TYPE === "staging") {
SERVER_URL = "https://backend-staging.herokuapp.com";
}
if (process.env.REACT_APP_SERVER_TYPE === "prod") {
SERVER_URL = "https://backend-prod.herokuapp.com";
}
}
// Editor and Output constants
//View Mode
const CODE_AND_OUTPUT = 0;
const CODE_ONLY = 1;
const OUTPUT_ONLY = 2;
//UI
module.exports = {
// photo names
PHOTO_NAMES,
DEFAULT_PHOTO_NAME: "icecream",
// Router Base Name
ROUTER_BASE_NAME,
//Server Host Name
SERVER_URL,
//User value constants
MINIMUM_USERNAME_LENGTH: 6,
MINIMUM_PASSWORD_LENGTH: 6,
MINIMUM_DISPLAY_NAME_LENGTH: 1,
MAXIMUM_USERNAME_LENGTH: 32,
MAXIMUM_PASSWORD_LENGTH: 128,
MAXIMUM_DISPLAY_NAME_LENGTH: 25,
// UI constants
RING_LOADER_SIZE: 50,
OPEN_PANEL_LEFT: 0,
// editor constants:
CODE_AND_OUTPUT,
CODE_ONLY,
OUTPUT_ONLY,
// UI
//Firebase constants
EMAIL_DOMAIN_NAME: "@fake.com",
};

14
client/src/index.js Normal file
View file

@ -0,0 +1,14 @@
import React from "react";
import { render } from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import App from "./App";
import { store } from "reducers";
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);

6
client/src/lib/index.js Normal file
View file

@ -0,0 +1,6 @@
import * as storage from "../lib/storage";
export default {
...storage,
};

49
client/src/lib/storage.js Normal file
View file

@ -0,0 +1,49 @@
const storageAvailable = (type) => {
try {
const storage = window[type];
const x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return false;
}
};
class CookieStore {
static set(key, value) {
document.cookie = `${key}=${value}`;
}
static get(key) {
if (!document.cookie || document.cookie.length === 0) return undefined;
const cookies = {};
document.cookie.split(';').forEach((cookie) => {
cookies[cookie.split('=')[0].trim()] = cookie.split('=')[1].trim();
});
return cookies[key];
}
static remove(key) {
document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
}
}
class LocalStore {
static set(key, value) {
window.localStorage.setItem(key, value);
}
static get(key, value) {
return window.localStorage.getItem(key);
}
static remove(key) {
window.localStorage.removeItem(key);
}
}
export default (storageAvailable('localStorage') ? LocalStore : CookieStore);

View file

@ -0,0 +1,41 @@
import React, { useEffect, useState } from 'react'
import { useIntl } from 'react-intl'
import Page from 'material-ui-shell/lib/containers/Page'
import Scrollbar from 'material-ui-shell/lib/components/Scrollbar'
import ReactMarkdown from 'react-markdown'
import 'github-markdown-css'
export default function () {
const [source, setSource] = useState(null)
const intl = useIntl()
const loadData = async () => {
const data = await fetch(
'https://raw.githubusercontent.com/TarikHuber/react-most-wanted/master/README.md'
)
const text = await data.text()
setSource(text)
}
useEffect(() => {
loadData()
}, [])
return (
<Page
pageTitle={intl.formatMessage({ id: 'about', defaultMessage: 'About' })}
>
<Scrollbar>
<div style={{ backgroundColor: 'white', padding: 12 }}>
{source && (
<ReactMarkdown
className="markdown-body"
source={source}
escapeHtml
/>
)}
</div>
</Scrollbar>
</Page>
)
}

View file

@ -0,0 +1,93 @@
import React from 'react';
import Page from 'material-ui-shell/lib/containers/Page';
import Typography from '@material-ui/core/Typography';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
function createDivision(id, name, location, numOfBeds, chargeNurse, ext, status) {
return { id, name, location, numOfBeds, chargeNurse, ext, status };
}
const rows = [
createDivision(2421, 'Orthopedic', 'Bay 6', 6, "Ella", 2 , false),
createDivision(1343, 'Emergency', 'Ward 6', 1, 'Johnson', 1, true),
createDivision(5413, 'Psychiatric', 'Ward 11', 3, "Taggart", 5, true),
createDivision(8193, 'Cardiac', 'Ward 4', 5, "Vaquez", 8, false),
];
function DivisionView () {
const classes = useStyles();
React.useEffect ( () => {
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
};
fetch('/', options)
.then(res => res.json())
.then((data) => {
//we feed the data into the function to create division object
//const dataRows = data.map((division) => {
// return createDivision(division.id, division.name, division.location, division.numOfBeds, division.hargeNurse, division.ext, division.status);
//})
}).catch(e => {
console.error(e);
});
})
return (
<Page pageTitle='Division Context' >
<div style={{padding: '32px'}}>
<Typography variant="h4" style={{ marginBottom: "32px" }}> Division Vizualization</Typography>
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>ID </TableCell>
<TableCell> Name</TableCell>
<TableCell>Location</TableCell>
<TableCell>Num of Beds</TableCell>
<TableCell> Charge Nurse </TableCell>
<TableCell> Telephone Ext. </TableCell>
<TableCell> Status </TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.id}</TableCell>
<TableCell>{row.name}</TableCell>
<TableCell>{row.location}</TableCell>
<TableCell>{row.numOfBeds}</TableCell>
<TableCell>{row.chargeNurse}</TableCell>
<TableCell>{row.ext}</TableCell>
<TableCell>{row.status ? "Complete" : "Incomplete"}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
</Page>
)
}
export default DivisionView;

View file

@ -0,0 +1,3 @@
import DivisionView from './DivisionView';
export default DivisionView;

View file

@ -0,0 +1,74 @@
import HomeComponent from './HomeComponent';
import React from 'react';
import { connect } from 'react-redux';
import { Action } from 'reducers';
class Home extends React.Component {
saveChanges(newprofile) {
this.props.updateUser(newprofile);
}
componentWillMount() {
if (this.props.isAdmin) {
return this.props.redirectHome();
}
if (this.props.authenticated) {
this.props.fetchUser();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.isAdmin) {
return nextProps.redirectHome();
}
if (nextProps.updated) {
setTimeout(() => {
this.props.updateDone();
}, 1000);
}
}
render() {
return (
<HomeComponent
/>
);
}
}
const mapStateToProps = (state) => {
const profile = {
name: ''
};
if (state.User.get('fetchSuccess')) {
const User = state.User.get('profile');
profile.name = `${User.firstName} ${User.lastName}`;
}
return {
profile,
fetchSuccess: state.User.get('fetchSuccess'),
updated: state.User.get('updated'),
updateSuccess: state.User.get('updateSuccess'),
updateError: state.User.get('error'),
authenticated: state.Auth.get('authenticated'),
isAdmin: state.Auth.get('isAdmin'),
};
};
const mapDispatchToProps = dispatch => ({
fetchUser: () => {
dispatch(Action.FetchUser());
},
updateUser: (newprofile) => {
dispatch(Action.UpdateUser(newprofile));
},
updateDone: () => {
dispatch(Action.UserUpdateDone());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);

View file

@ -0,0 +1,21 @@
import Page from 'material-ui-shell/lib/containers/Page'
import React from 'react'
import Scrollbar from 'material-ui-shell/lib/components/Scrollbar/Scrollbar'
import { useIntl } from 'react-intl'
const HomePage = () => {
const intl = useIntl()
const user = "Bob";
return (
<Page pageTitle={'Welcome Bob'}>
<Scrollbar
style={{ height: '100%', width: '100%', display: 'flex', flex: 1 }}
>
Something here
</Scrollbar>
</Page>
)
}
export default HomePage

View file

@ -0,0 +1,77 @@
import React from "react";
import { Link } from "react-router-dom";
import { makeStyles } from "@material-ui/core/styles";
import logo from "../../pictures/logo.png";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
const useStyles = makeStyles((theme) => ({
link: {
margin: theme.spacing(2, 2, 1),
},
}));
const Landing = () => {
const classes = useStyles();
return (
<div
style={{
position: "absolute",
left: 0,
top: 0,
bottom: 0,
right: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
}}
>
<img src={logo} alt="logo" style={{ width: "25%" }} />
<Typography variant="h5" style={{ paddingBottom: "20px", textAlign: "center" }}>
Welcome to the HealthGeeks Patient-Management-System
</Typography>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
}}
>
<Link
className={classes.link}
to="/auth/staff/login"
style={{ textDecoration: "none" }}
>
<Button fullWidth variant="contained" color="primary">
Staff Member
</Button>
</Link>
<Link
className={classes.link}
to="/auth/medical/login"
style={{ textDecoration: "none" }}
>
<Button fullWidth variant="contained" color="primary">
Medical Staff
</Button>
</Link>
<Link
className={classes.link}
to="/auth/nurse/login"
style={{ textDecoration: "none" }}
>
<Button fullWidth variant="contained" color="primary">
Charge Nurse
</Button>
</Link>
</div>
</div>
);
};
export default Landing;

View file

@ -0,0 +1 @@
export { default } from './LandingPage'

View file

@ -0,0 +1,59 @@
import Button from '@material-ui/core/Button'
import Home from '@material-ui/icons/Home'
import Page from 'material-ui-shell/lib/containers/Page'
import Paper from '@material-ui/core/Paper'
import React from 'react'
import Typography from '@material-ui/core/Typography'
import { makeStyles } from '@material-ui/core/styles'
import { useIntl } from 'react-intl'
const useStyles = makeStyles((theme) => ({
icon: {
width: 192,
height: 192,
color: theme.palette.secondary.main,
},
container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: `100%`,
},
paper: {
backgroundColor: theme.palette.background.default,
margin: 0,
height: `calc(100vh - 64px)`,
},
button: {
marginTop: 20,
},
}))
const PageNotFound = () => {
const intl = useIntl()
const classes = useStyles()
return (
<Page pageTitle={intl.formatMessage({ id: 'page_not_found' })}>
<Paper className={classes.paper}>
<div className={classes.container}>
<Typography variant="h4">404</Typography>
<Typography variant="subtitle1">
{intl.formatMessage({ id: 'page_not_found' })}
</Typography>
<Button
color="secondary"
aria-label="home"
href="/"
className={classes.button}
>
<Home />
</Button>
</div>
</Paper>
</Page>
)
}
export default PageNotFound

View file

@ -0,0 +1,115 @@
import Button from '@material-ui/core/Button'
import Page from 'material-ui-shell/lib/containers/Page'
import Paper from '@material-ui/core/Paper'
import React, { useState } from 'react'
import TextField from '@material-ui/core/TextField'
import Typography from '@material-ui/core/Typography'
import { makeStyles } from '@material-ui/core/styles'
import { useHistory } from 'react-router-dom'
import { useIntl } from 'react-intl'
const useStyles = makeStyles((theme) => ({
paper: {
width: 'auto',
marginLeft: theme.spacing(3),
marginRight: theme.spacing(3),
[theme.breakpoints.up(620 + theme.spacing(6))]: {
width: 400,
marginLeft: 'auto',
marginRight: 'auto',
},
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: `${theme.spacing(2)}px ${theme.spacing(3)}px ${theme.spacing(
3
)}px`,
},
avatar: {
margin: theme.spacing(1),
width: 192,
height: 192,
color: theme.palette.secondary.main,
},
form: {
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: `100%`,
},
}))
const SignUp = () => {
const classes = useStyles()
const intl = useIntl()
const history = useHistory()
const [username, setUsername] = useState('')
function handleSubmit(event) {
event.preventDefault()
history.replace('/signin')
}
return (
<Page
pageTitle={intl.formatMessage({
id: 'password_reset',
defaultMessage: 'Password reset',
})}
onBackClick={() => {
history.goBack()
}}
>
<Paper className={classes.paper} elevation={6}>
<div className={classes.container}>
<Typography component="h1" variant="h5">
{intl.formatMessage({
id: 'password_reset',
defaultMessage: 'Password reset',
})}
</Typography>
<form className={classes.form} onSubmit={handleSubmit} noValidate>
<TextField
value={username}
onInput={(e) => setUsername(e.target.value)}
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label={intl.formatMessage({
id: 'email',
defaultMessage: 'E-Mail',
})}
name="email"
autoComplete="email"
autoFocus
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
{intl.formatMessage({
id: 'password_reset',
defaultMessage: 'Reset Password',
})}
</Button>
</form>
</div>
</Paper>
</Page>
)
}
export default SignUp

View file

@ -0,0 +1,5 @@
import RegisterPatientButton from './RegisterPatientButton';
import ConsultFileButton from './ConsultFileButton';
import RequestToAdmitButton from './RequestToAdmitButton'
export { RegisterPatientButton, ConsultFileButton, RequestToAdmitButton };

View file

@ -0,0 +1,28 @@
import React from 'react';
import { Button } from '@material-ui/core';
import { useQuestions } from 'material-ui-shell/lib/providers/Dialogs/Question';
import ConsultFileForm from './ConsultFileForm'
//we should have medication somewhere inside here
function ConsultFileButton(props){
const { openDialog, setProcessing } = useQuestions();
return (
<Button
variant="contained"
color="default"
onClick={() => {
openDialog({
title: 'Consult File',
message: <ConsultFileForm firstname= {props.firstname} lastname= {props.lastname} />,
action: null,
})
}}
>
Consult File
</Button>
)
}
export default ConsultFileButton;

View file

@ -0,0 +1,295 @@
import React from 'react';
import TextField from '@material-ui/core/TextField';
import MenuItem from '@material-ui/core/MenuItem';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(2),
width: '25ch',
},
},
selectEmpty: {
marginTop: theme.spacing(1),
width: '25ch',
},
}));
function ConsultFileForm (props){
const classes = useStyles();
const [gender, setGender] = React.useState('');
const [maritalStatus, setMaritalStatus] = React.useState('');
const [province, setProvince] = React.useState('');
const [state, setState] = React.useState({
firstname: '',
lastname: '',
telephone: '',
addressLineOne: '',
city: '',
provice: '',
postalCode: '',
dateOfBirth: '',
gender: '',
maritalStatus: '',
externalDoctor: '',
divisionID: '',
//admitted
//requested
});
React.useEffect( () => {
getData(); //we get data right after render
})
//GEt from backend
function getData(){
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
};
fetch('/ ', options)
.then(res => res.json())
.then((data) => {
//What happens after calling express goes here
setState( {
firstname: data.firstname,
lastname: data.lastname,
telephone: data.telephone,
addressLineOne: data.addressLineOne,
city: data.city,
provice: data.province,
postalCode: data.postalCode,
dateOfBirth: data.dateOfBirth,
gender: data.gender,
maritalStatus: data.maritalStatus,
externalDoctor: data.externalDoctor,
divisionID: data.divisionID,
})
}).catch(e => {
//console.error(e);
});
}
const handleChange = (e) => {
const value = e.target.value;
setState({
...state,
[e.target.id]: value
});
}
const handleGender = (e) => {
setGender(e.target.value)
}
const handleMaritalStatus = (e) => {
setMaritalStatus(e.target.value)
}
const handleProvinceChange = (e) => {
setProvince(e.target.value)
}
const [editable, setEditable] = React.useState(false);
const [buttonValue, setButtonValue] = React.useState('Edit');
function saveOrEdit(e){
if ( editable){
setButtonValue( 'Edit');
setEditable(false);
}else{
saveData(e);
setButtonValue('Save');
setEditable(true);
}
}
//Send Data to Backend
function saveData(e){
e.preventDefault();
var data = { // what to send goes here
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
body: JSON.stringify(data)
};
fetch('/ ', options)
.then(res => res.json())
.then((data) => {
//What happens after calling express goes here
}).catch(e => {
//console.error(e);
});
}
return(
<div>
<form className={classes.root} autoComplete="off">
<div>
<TextField
disabled = {!editable}
required
label="First Name"
value={state.firstname}
id="firstname"
onChange={handleChange}
/>
<TextField
disabled={!editable}
required
label="Last Name"
value={state.lastname}
id="lastname"
onChange={handleChange}
/>
</div>
<div>
<TextField
disabled={!editable}
required
label="Telephone Number"
value={state.telephone}
id="telephone"
onChange={handleChange}
/>
<TextField
required
disabled={!editable}
type="date"
label="Date of Birth"
value={state.dateOfBirth}
id="dateOfBirth"
onChange={handleChange}
InputLabelProps={{
shrink: true,
}}
/>
</div>
<div>
<TextField
disabled={!editable}
required
label="Address Line One"
value={state.addressLineOne}
id="addressLineOne"
onChange={handleChange}
/>
<TextField
disabled={!editable}
required
label="City"
value={state.city}
id="city"
onChange={handleChange}
/>
</div>
<div>
<TextField
disabled={!editable}
select
id="userProvince"
value={province}
onChange={handleProvinceChange}
label="Province"
>
<MenuItem value={'alberta'}>Alberta </MenuItem>
<MenuItem value={'britishcolumbia'}>British Columbia</MenuItem>
<MenuItem value={'newbrunswick'}>New Brunswick</MenuItem>
<MenuItem value={'newfoundlandandlabrador'}>Newfoundland and Labrador</MenuItem>
<MenuItem value={'novascotia'}>Nova Scotia</MenuItem>
<MenuItem value={'princeedwardisland'}>Prince Edward Island</MenuItem>
<MenuItem value={'ontario'}>Ontario</MenuItem>
<MenuItem value={'quebec'}>Quebec</MenuItem>
<MenuItem value={'saskatchewan'}>Saskatchewan</MenuItem>
</TextField>
<TextField
required
disabled={!editable}
label="Postal Code"
value={state.postalCode}
id="postalCode"
onChange={handleChange}
/>
</div>
<div>
<TextField
select
disabled={!editable}
required
label="Gender"
value={gender}
onChange={handleGender}
className={classes.selectEmpty}
>
<MenuItem value={'male'}> Male </MenuItem>
<MenuItem value={'female'}> Female </MenuItem>
<MenuItem value={'other'}> Other</MenuItem>
<MenuItem value={'none'}>Prefer Not to Identify</MenuItem>
</TextField>
<TextField
select
disabled={!editable}
required
label="Marital Status"
value={maritalStatus}
onChange={handleMaritalStatus}
className={classes.selectEmpty}
>
<MenuItem value={'married'}> Married </MenuItem>
<MenuItem value={'single'}> Single </MenuItem>
<MenuItem value={'commonlaw'}> Common Law</MenuItem>
</TextField>
</div>
<div>
<TextField
disabled={!editable}
required
label="External Doctor"
value={state.externalDoctor}
id="telephone"
onChange={handleChange}
/>
<TextField
disabled={!editable}
label="Division ID"
value={state.divisionID}
id="divisionid"
onChange={handleChange}
/>
</div>
<Button style={{ float: "right", marginRight: "2rem" }}
variant="contained"
color="primary"
onClick={(e) => saveOrEdit(e)}>
{buttonValue}
</Button>
</form>
</div>
)
}
export default ConsultFileForm;

View file

@ -0,0 +1,114 @@
import React from 'react';
import Page from 'material-ui-shell/lib/containers/Page';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { RegisterPatientButton, ConsultFileButton, RequestToAdmitButton } from './Buttons';
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
function createData(id, firstname, lastname, division, admitted) {
return { id, firstname, lastname, division, admitted };
}
const rows = [
createData(2421, 'James', 'Iger', 6, false),
createData( 1343, 'Bob', 'Andrews', 1, true),
createData(5413, 'John', 'Dunderson', 3, true),
createData(8193, 'Sam', 'Ellis', 5, false),
];
function PatientList() {
const classes = useStyles();
React.useEffect( () => {
getData();
})
function getData (){
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
};
fetch('/', options)
.then(res => res.json())
.then((data) => {
//we feed the data into the function to create patient object
const dataRows = data.map ( (patient) => {
return createData( patient.id, patient.firstname, patient.lastname, patient.division, patient.admitted);
})
}).catch(e => {
console.error(e);
});
}
return (
<Page pageTitle='Summary' >
<div style={{padding: '32px'}}>
<Typography variant="h3" style={{marginBottom: '16px'}}> Patient List </Typography>
<RegisterPatientButton />
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>ID </TableCell>
<TableCell>First Name</TableCell>
<TableCell>Last Name</TableCell>
<TableCell>Division</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.id}</TableCell>
<TableCell>{row.firstname}</TableCell>
<TableCell>{row.lastname}</TableCell>
<TableCell>{row.division}</TableCell>
<TableCell>
<ConsultFileButton
firstname = {row.firstname}
lastname = {row.lastname}
/>
</TableCell>
<TableCell >
<div hidden={row.admitted}>
<RequestToAdmitButton
firstname={row.firstname}
lastname={row.lastname}
/>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
</Page>
)
}
export default PatientList;

View file

@ -0,0 +1,29 @@
import React from 'react';
import { Button } from '@material-ui/core';
import { useQuestions } from 'material-ui-shell/lib/providers/Dialogs/Question';
import RegistrationForm from './RegistrationForm';
function RegisterPatientButton (props) {
const { openDialog, setProcessing } = useQuestions();
return (
<Button
variant="contained"
color="primary"
style={{marginBottom: '16px'}}
onClick={() => {
openDialog({
title: 'Register Patient',
message: <RegistrationForm/>,
action: null,
})
}}
>
Register Patient
</Button>
)
}
export default RegisterPatientButton;

View file

@ -0,0 +1,229 @@
import React from 'react';
import TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';
import MenuItem from '@material-ui/core/MenuItem';
import Button from '@material-ui/core/Button';
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(2),
width: '25ch',
},
},
selectEmpty: {
marginTop: theme.spacing(1),
width: '25ch',
},
}));
function RegistrationForm() {
const classes = useStyles();
const [gender, setGender] = React.useState('');
const [maritalStatus, setMaritalStatus] = React.useState('');
const [province, setProvince] = React.useState('alberta');
const [state, setState] = React.useState({
firstname: '',
lastname: '',
telephone: '',
addressLineOne: '',
city: '',
provice: '',
postalCode: '',
dateOfBirth: '',
gender : '',
maritalStatus: '',
externalDoctor: '',
divisionID: '',
//admitted
//requested
});
const handleChange = (e) => {
const value = e.target.value;
setState( {
...state,
[e.target.id] : value
});
}
const handleGender = (e) => {
setGender(e.target.value)
}
const handleMaritalStatus = (e) => {
setMaritalStatus(e.target.value)
}
const handleProvinceChange = (e) => {
setProvince(e.target.value)
}
//We call the API HERE
const register = (e) => {
e.preventDefault();
var data = { // what to send goes here
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
body: JSON.stringify(data)
};
fetch('/ ', options)
.then(res => res.json())
.then((data) => {
console.log(data)
//What happens after calling express goes here
}).catch(e => {
console.error(e);
});
}
return (
<div>
<form className={classes.root} autoComplete="off">
<div>
<TextField
required
label="First Name"
value = {state.firstname}
id = "firstname"
onChange = {handleChange}
/>
<TextField
required
label="Last Name"
value={state.lastname}
id="lastname"
onChange={handleChange}
/>
</div>
<div>
<TextField
required
label="Telephone Number"
value={state.telephone}
id="telephone"
onChange={handleChange}
/>
<TextField
required
type="date"
label="Date of Birth"
value={state.dateOfBirth}
id="dateOfBirth"
onChange={handleChange}
InputLabelProps={{
shrink: true,
}}
/>
</div>
<div>
<TextField
required
label="Address Line One"
value={state.addressLineOne}
id="addressLineOne"
onChange={handleChange}
/>
<TextField
required
label="City"
value={state.city}
id="city"
onChange={handleChange}
/>
<TextField
select
id="userProvince"
value={province}
onChange={handleProvinceChange}
label="Province"
>
<MenuItem value={'alberta'}>Alberta </MenuItem>
<MenuItem value={'britishcolumbia'}>British Columbia</MenuItem>
<MenuItem value={'newbrunswick'}>New Brunswick</MenuItem>
<MenuItem value={'newfoundlandandlabrador'}>Newfoundland and Labrador</MenuItem>
<MenuItem value={'novascotia'}>Nova Scotia</MenuItem>
<MenuItem value={'princeedwardisland'}>Prince Edward Island</MenuItem>
<MenuItem value={'ontario'}>Ontario</MenuItem>
<MenuItem value={'quebec'}>Quebec</MenuItem>
<MenuItem value={'saskatchewan'}>Saskatchewan</MenuItem>
</TextField>
<TextField
required
label="Postal Code"
value={state.postalCode}
id="postalCode"
onChange={handleChange}
/>
</div>
<div>
<TextField
select
required
label="Gender"
value={ gender}
onChange={ handleGender }
className={classes.selectEmpty}
>
<MenuItem value={'male'}> Male </MenuItem>
<MenuItem value={'female'}> Female </MenuItem>
<MenuItem value={'other'}> Other</MenuItem>
<MenuItem value={'none'}>Prefer Not to Identify</MenuItem>
</TextField>
<TextField
select
required
label="Marital Status"
value={maritalStatus}
onChange={handleMaritalStatus}
className={classes.selectEmpty}
>
<MenuItem value={'married'}> Married </MenuItem>
<MenuItem value={'single'}> Single </MenuItem>
<MenuItem value={'commonlaw'}> Common Law</MenuItem>
</TextField>
</div>
<div>
<TextField
required
label="External Doctor"
value={state.externalDoctor}
id="telephone"
onChange={handleChange}
/>
<TextField
label="Division ID"
value={state.divisionID}
id="divisionid"
onChange={handleChange}
/>
</div>
<Button style={{ float: "right", marginRight: "3rem" }}
variant="contained"
color="primary"
onClick = { (e) => register(e)}>
Register
</Button>
</form>
</div>
)
}
export default RegistrationForm;

View file

@ -0,0 +1,76 @@
import React from 'react';
import { Button } from '@material-ui/core';
import Snackbar from '@material-ui/core/Snackbar';
import MuiAlert from '@material-ui/lab/Alert';
import { makeStyles } from '@material-ui/core/styles';
function Alert(props) {
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
'& > * + *': {
marginTop: theme.spacing(2),
},
},
}));
function RequestToAdmitButton() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClick = (e) => {
//send to database
e.preventDefault();
var data = { // what to send goes here
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
body: JSON.stringify(data)
};
//create a request for admission here
fetch('/ ', options)
.then(res => res.json())
.catch(e => {
//console.error(e);
});
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
}
return (
<div className={classes.root}>
<Button
variant="contained"
color="default"
onClick={ handleClick } >
Request To Admit
</Button>
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success">
Successfully Requested Patient for Admission
</Alert>
</Snackbar>
</div>
)
}
export default RequestToAdmitButton;

View file

@ -0,0 +1,3 @@
import PatientList from './PatientList';
export default PatientList;

View file

@ -0,0 +1,46 @@
import React from 'react';
import { connect } from 'react-redux';
import { replace } from 'react-router-redux';
import { Action } from '../../reducers';
import SignInComponent from './SignInComponent';
class Login extends React.Component {
handleLogin(email, password) {
this.props.login(email, password);
}
componentWillMount() {
if (this.props.authenticated) {
this.props.redirectHome();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.authenticated) {
this.props.redirectHome();
}
}
render() {
return <SignInComponent onsubmit={this.handleLogin.bind(this)} error={this.props.error} />;
}
}
const mapStateToProps = state => ({
error: state.Auth.get('error'),
isAdmin: state.Auth.get('isAdmin'),
timestamp: state.Auth.get('timestamp'),
authenticated: state.Auth.get('authenticated'),
});
const mapDispatchToProps = dispatch => ({
login: (email, password) => {
dispatch(Action.LoginUser(email, password));
},
redirectHome: () => {
dispatch(replace('/home'));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);

View file

@ -0,0 +1,179 @@
import Button from "@material-ui/core/Button";
import Page from "material-ui-shell/lib/containers/Page";
import Paper from "@material-ui/core/Paper";
import React, { useState } from "react";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import { Link } from "react-router-dom";
import { makeStyles } from "@material-ui/core/styles";
import { useAuth } from "base-shell/lib/providers/Auth";
import { useHistory } from "react-router-dom";
import { useIntl } from "react-intl";
import { useMenu } from "material-ui-shell/lib/providers/Menu";
import { getDisplayUserType } from "../../utils/displayUserType";
const useStyles = makeStyles((theme) => ({
paper: {
width: "auto",
marginLeft: theme.spacing(3),
marginRight: theme.spacing(3),
[theme.breakpoints.up(620 + theme.spacing(6))]: {
width: 400,
marginLeft: "auto",
marginRight: "auto",
},
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: `${theme.spacing(2)}px ${theme.spacing(3)}px ${theme.spacing(
3
)}px`,
},
avatar: {
margin: theme.spacing(1),
width: 192,
height: 192,
color: theme.palette.secondary.main,
},
form: {
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
container: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: `100%`,
},
}));
const SignInComponent = (props) => {
const classes = useStyles();
const intl = useIntl();
const history = useHistory();
const userType = history.location.pathname.split("/")[2]; // pathname: /auth/staff/login
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [disableForm, setDisableForm] = useState(false);
const { setAuthMenuOpen } = useMenu();
const { setAuth } = useAuth();
const displayUserType = getDisplayUserType(userType);
function handleSubmit(event) {
event.preventDefault();
// authenticate({
// displayName: "User",
// email: email,
// });
const { onsubmit } = props;
if (!disableForm) {
setDisableForm(true);
onsubmit(email, password);
setAuth({ isAuthenticated: true });
setAuthMenuOpen(false);
}
}
const authenticate = (user) => {
setAuth({ isAuthenticated: true, ...user });
setAuthMenuOpen(false);
let _location = history.location;
let _route = "/home";
if (_location.state && _location.state.from) {
_route = _location.state.from.pathname;
history.push(_route);
} else {
history.push(_route);
}
};
return (
<Page pageTitle={intl.formatMessage({ id: "sign_in" })}>
<Paper className={classes.paper} elevation={6}>
<div className={classes.container}>
<Typography
component="h1"
variant="h5"
style={{ textTransform: "capitalize" }}
>
{`${displayUserType} ${intl.formatMessage({ id: "sign_in" })}`}
</Typography>
<form className={classes.form} onSubmit={handleSubmit} noValidate>
<TextField
disabled={disableForm}
value={email}
onInput={(e) => setEmail(e.target.value)}
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label={intl.formatMessage({
id: "email",
defaultMessage: "E-Mail",
})}
name="email"
autoComplete="email"
autoFocus
/>
<TextField
disabled={disableForm}
value={password}
onInput={(e) => setPassword(e.target.value)}
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label={intl.formatMessage({ id: "password" })}
type="password"
id="password"
autoComplete="current-password"
/>
{props.error ? (
<span>
<b>Error</b>: {props.error}
</span>
) : (
<span>&nbsp;</span>
)}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
{intl.formatMessage({ id: "sign_in" })}
</Button>
</form>
<div
style={{
display: "flex",
flexDirection: "row",
width: "100%",
justifyContent: "space-between",
}}
>
<Link to="/password_reset">
{intl.formatMessage({ id: "forgot_password" })}?
</Link>
<Link to={`/auth/${userType}/signup`}>
{intl.formatMessage({ id: "registration" })}
</Link>
</div>
</div>
</Paper>
</Page>
);
};
export default SignInComponent;

View file

@ -0,0 +1,53 @@
import React from 'react';
import { connect } from 'react-redux';
import SignUpComponent from './SignUpComponent';
import { Action } from 'reducers';
class SignUpContainer extends React.Component {
constructor(props) {
super(props);
this.createUser = this.createUser.bind(this);
}
createUser(profile) {
this.props.registerUser(profile);
}
componentWillReceiveProps(nextProps) {
if (nextProps.registered) {
setTimeout(() => {
this.props.registerDone();
}, 1000);
}
}
render() {
return (
<SignUpComponent
createUser={this.createUser}
created={this.props.registered}
createSuccess={this.props.registerSuccess}
createError={this.props.error}
/>
);
}
}
const mapStateToProps = state => ({
user: state.Registration.get('user'),
error: state.Registration.get('error'),
registered: state.Registration.get('registered'),
registerSuccess: state.Registration.get('registerSuccess'),
});
const mapDispatchToProps = dispatch => ({
registerUser: (newuser) => {
dispatch(Action.RegisterUser(newuser));
},
registerDone: () => {
dispatch(Action.registerDone());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(SignUpContainer);

View file

@ -0,0 +1,209 @@
import Button from "@material-ui/core/Button";
import Page from "material-ui-shell/lib/containers/Page";
import Paper from "@material-ui/core/Paper";
import React, { useState } from "react";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";
import { useAuth } from "base-shell/lib/providers/Auth";
import { useHistory } from "react-router-dom";
import { useIntl } from "react-intl";
import { useMenu } from "material-ui-shell/lib/providers/Menu";
import { getDisplayUserType } from "../../utils/displayUserType";
const useStyles = makeStyles((theme) => ({
paper: {
width: "auto",
marginLeft: theme.spacing(3),
marginRight: theme.spacing(3),
[theme.breakpoints.up(620 + theme.spacing(6))]: {
width: 400,
marginLeft: "auto",
marginRight: "auto",
},
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: `${theme.spacing(2)}px ${theme.spacing(3)}px ${theme.spacing(
3
)}px`,
},
avatar: {
margin: theme.spacing(1),
width: 192,
height: 192,
color: theme.palette.secondary.main,
},
form: {
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
container: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: `100%`,
},
}));
const SignUp = (props) => {
const classes = useStyles();
const intl = useIntl();
const history = useHistory();
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [userEmail, setUserEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [disableForm, setDisableForm] = useState(false);
const userType = history.location.pathname.split("/")[2]; // pathname ex: /auth/staff/login
const { setAuthMenuOpen } = useMenu();
const { setAuth } = useAuth();
const displayUserType = getDisplayUserType(userType);
function handleSubmit(event) {
event.preventDefault();
if (!disableForm) {
if (
firstName &&
lastName &&
userEmail &&
password &&
password.length >= 4
) {
setDisableForm(true);
props.createUser({
firstName,
lastName,
email:userEmail,
password,
confirmPassword,
});
}
}
setAuth({ isAuthenticated: true });
setAuthMenuOpen(false);
}
return (
<Page
pageTitle={intl.formatMessage({
id: "sign_up",
defaultMessage: " Sign up",
})}
onBackClick={() => {
history.goBack();
}}
>
<Paper className={classes.paper} elevation={6}>
<div className={classes.container}>
<Typography component="h1" variant="h5">
{`${displayUserType} ${intl.formatMessage({
id: "sign_up",
defaultMessage: "Sign up",
})}`}
</Typography>
<form className={classes.form} onSubmit={handleSubmit}>
<TextField
disabled={disableForm}
value={firstName}
onInput={(e) => setFirstName(e.target.value)}
variant="outlined"
margin="normal"
required
fullWidth
id="first-name"
label="First Name"
name="first-name"
autoComplete="first-name"
/>
<TextField
disabled={disableForm}
value={lastName}
onInput={(e) => setLastName(e.target.value)}
variant="outlined"
margin="normal"
required
fullWidth
id="last-name"
label="Last Name"
name="last-name"
autoComplete="last-name"
/>
<TextField
disabled={disableForm}
value={userEmail}
onInput={(e) => setUserEmail(e.target.value)}
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label={intl.formatMessage({
id: "email",
defaultMessage: "E-Mail",
})}
name="email"
autoComplete="email"
/>
<TextField
disabled={disableForm}
value={password}
onInput={(e) => setPassword(e.target.value)}
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label={intl.formatMessage({
id: "password",
defaultMessage: "Password",
})}
type="password"
id="password"
autoComplete="current-password"
/>
<TextField
disabled={disableForm}
value={confirmPassword}
onInput={(e) => setConfirmPassword(e.target.value)}
variant="outlined"
margin="normal"
required
fullWidth
name="password_confirm"
label={intl.formatMessage({
id: "password_confirm",
defaultMessage: "Confirm Password",
})}
type="password"
id="password_confirm"
autoComplete="current-password"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
{intl.formatMessage({ id: "sign_up", defaultMessage: "Sign up" })}
</Button>
</form>
</div>
</Paper>
</Page>
);
};
export default SignUp;

View file

@ -0,0 +1,100 @@
import React from 'react';
import Page from 'material-ui-shell/lib/containers/Page';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import AdmitButton from './AdmitButton';
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
function createData(id, firstname, lastname) {
return { id, firstname, lastname };
}
const rows = [
createData(2421, 'James', 'Iger' ),
createData(1343, 'Bob', 'Andrews'),
createData(5413, 'John', 'Dunderson'),
createData(8193, 'Sam', 'Ellis' ),
];
function AdmissionRequests() {
const classes = useStyles();
React.useEffect( () => {
getData();
})
function getData(){
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
};
fetch('/', options)
.then(res => res.json())
.then((data) => {
//we feed the data into the function to create patient object
const dataRows = data.map((patient) => {
return createData(patient.id, patient.firstname, patient.lastname );
})
}).catch(e => {
console.error(e);
});
}
return (
<div>
<Page pageTitle='Summary' >
<Typography variant="h4" style={{marginBottom: 16}}> Admission Requests </Typography>
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>ID </TableCell>
<TableCell>First Name</TableCell>
<TableCell>Last Name</TableCell>
<TableCell> Action </TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.id}</TableCell>
<TableCell>{row.firstname}</TableCell>
<TableCell>{row.lastname}</TableCell>
<TableCell >
<AdmitButton
firstname = {row.firstname}
lastname = {row.lastname}>
</AdmitButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Page>
</div>
)
}
export default AdmissionRequests;

View file

@ -0,0 +1,27 @@
import React from 'react';
import { useQuestions } from 'material-ui-shell/lib/providers/Dialogs/Question';
import { Button } from '@material-ui/core';
import AdmitForm from './AdmitForm';
function AdmitButton(props) {
const { openDialog, setProcessing } = useQuestions();
return (
<Button
variant="contained"
color="default"
onClick={() => {
openDialog({
title: 'Consult File',
message: <AdmitForm firstname={props.firstname} lastname={props.lastname}/> ,
action: null,
})
}}
>
Admit Patient
</Button>
)
}
export default AdmitButton;

View file

@ -0,0 +1,137 @@
import React from 'react';
import TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(2),
width: '25ch',
},
},
selectEmpty: {
marginTop: theme.spacing(1),
width: '25ch',
},
}));
function AdmitForm(props) {
const classes = useStyles();
const [state, setState] = React.useState({
firstname: props.firstname,
lastname: props.lastname,
division: '',
localdoctor: '',
roomnumber: '',
bednumber: '',
privateinsurancenumber: '',
});
const handleChange = (e) => {
const value = e.target.value;
setState({
...state,
[e.target.id]: value
});
}
//We call the API HERE
const admit = (e) => {
e.preventDefault();
var data = { // what to send goes here
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
body: JSON.stringify(data)
};
fetch('/ ', options)
.then(res => res.json())
.then((data) => {
console.log(data)
//What happens after calling express goes here
}).catch(e => {
console.error(e);
});
}
return (
<div>
<form className={classes.root} autoComplete="off">
<div>
<TextField
disabled
label="First Name"
value={state.firstname}
/>
<TextField
disabled
label="Last Name"
value={state.lastname}
/>
</div>
<div>
<TextField
required
label="Division"
value={state.division}
id="division"
onChange={handleChange}
/>
<TextField
required
label="Local Doctor"
value={state.localdoctor}
id="localdoctor"
onChange={handleChange}
/>
</div>
<div>
<TextField
required
label="Room Number"
value={state.roomnumber}
id="roomnumber"
onChange={handleChange}
/>
<TextField
required
label="Bed Number"
value={state.bednumber}
id="bednumber"
onChange={handleChange}
/>
</div>
<div>
<TextField
label="Private Insurance Number"
value={state.privateinsurancenumber}
id="privateinsurancenumber"
onChange={handleChange}
helperText= "Optional"
/>
</div>
<Button style={{ float: "right", marginRight: "3rem" }}
variant="contained"
color="primary"
onClick={(e) => admit(e)}>
Confirm Admission
</Button>
</form>
</div>
)
}
export default AdmitForm;

View file

@ -0,0 +1,113 @@
import React from 'react';
import TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';
import { Button } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(2),
width: '25ch',
},
},
selectEmpty: {
marginTop: theme.spacing(1),
width: '25ch',
},
}));
function DischargeForm() {
const classes = useStyles();
const [state, setState] = React.useState({
id: '',
firstname: '',
lastname: '',
division: '',
});
const handleChange = (e) => {
const value = e.target.value;
setState({
...state,
[e.target.id]: value
});
}
const discharge = (e) => {
e.preventDefault();
var data = { // what to send goes here
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
body: JSON.stringify(data)
};
fetch('/ ', options)
.then(res => res.json())
.then((data) => {
console.log(data)
//What happens after calling express goes here
}).catch(e => {
console.error(e);
});
}
return (
<div>
<form className={classes.root} autoComplete="off">
<div>
<TextField
required
label="ID"
value={state.id}
id="id"
onChange={handleChange}
/>
<TextField
required
label="Division"
value={state.division}
id="division"
onChange={handleChange}
/>
</div>
<div>
<TextField
required
label="First Name"
value={state.firstname}
id="firstname"
onChange={handleChange}
/>
<TextField
required
label="First Name"
value={state.firstname}
id="firstname"
onChange={handleChange}
/>
</div>
<Button style={{ float: "right", marginRight: "3rem" }}
variant="contained"
color="primary"
onClick={(e) => discharge(e)}
value = "Confirm Discharge">
Confirm Discharge
</Button>
</form>
</div>
)
}
export default DischargeForm;

View file

@ -0,0 +1,27 @@
import React from 'react';
import { useQuestions } from 'material-ui-shell/lib/providers/Dialogs/Question';
import { Button } from '@material-ui/core';
import DischargeForm from './DischargeForm';
function DischargePatient(props) {
const { openDialog, setProcessing } = useQuestions();
return (
<Button
variant="contained"
color="secondary"
onClick={() => {
openDialog({
title: 'Consult File',
message: <DischargeForm/>,
action: null,
})
}}
>
Discharge Patient
</Button>
)
}
export default DischargePatient;

View file

@ -0,0 +1,229 @@
import React from 'react';
import TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import { Typography } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(2),
width: '25ch',
},
},
selectEmpty: {
marginTop: theme.spacing(1),
width: '25ch',
},
}));
function PrescribeMedications(){
const classes = useStyles();
const [state, setState] = React.useState({
firstname: '',
lastname: '',
drugnumber: '',
drugname: '',
unitsbyday: '',
numberofadmin: '',
administrationtime1: '',
administrationunits1: '',
administrationtime2: '',
administrationunits2: '',
administrationtime3: '',
administrationunits3: '',
methodofadmin: '',
startdate: '',
finishdate: '',
})
const handleChange = (e) => {
const value = e.target.value;
setState({
...state,
[e.target.id]: value
});
}
const prescribe = (e) => {
e.preventDefault();
var data = { // what to send goes here
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
body: JSON.stringify(data)
};
fetch('/ ', options)
.then(res => res.json())
.then((data) => {
console.log(data)
//What happens after calling express goes here
}).catch(e => {
console.error(e);
});
}
return(
<div>
< Typography variant="h5" style={{padding: "1rem"}}> Prescribe Medications </Typography>
<form className={classes.root} autoComplete="off">
<div>
<TextField
required
label="First Name"
value={state.firstname}
id="firstname"
onChange={handleChange}
/>
<TextField
required
label="Last Name"
value={state.lastname}
id="lastname"
onChange={handleChange}
/>
</div>
<div>
<TextField
required
label="Drug Number"
value={state.drugnumber}
id="drugnumber"
onChange={handleChange}
/>
<TextField
required
label="Drug Name"
value={state.drugname}
id="drugname"
onChange={handleChange}
/>
</div>
<div>
<TextField
required
label="Units by Day"
value={state.unitsbyday}
id="unitsbyday"
onChange={handleChange}
/>
</div>
<Typography variant="h6" style={{ padding: "1rem" }}> Admin Information</Typography>
<div>
<TextField
required
label="Number of Administrations"
value={state.numberofadmin}
id="numberofadmin"
onChange={handleChange}
helperText="Per Day"
/>
</div>
<div>
<TextField
type="time"
value={state.adminstrationtime1}
id="adminstrationtime1"
onChange={handleChange}
helperText="Admnistration Time 1"
/>
<TextField
label="Admnistration Units 1"
value={state.administrationunits1}
id="administrationunits1"
onChange={handleChange}
/>
</div>
<div>
<TextField
type="time"
value={state.adminstrationtime2}
id="adminstrationtime1"
onChange={handleChange}
helperText="Admnistration Time 2"
/>
<TextField
label="Admnistration Units 1"
value={state.administrationunits2}
id="administrationunits2"
onChange={handleChange}
/>
</div>
<div>
<TextField
type="time"
value={state.adminstrationtime3}
id="adminstrationtime3"
onChange={handleChange}
helperText="Admnistration Time 3"
/>
<TextField
label="Admnistration Units 3"
value={state.administrationunits3}
id="administrationunits3"
onChange={handleChange}
/>
</div>
<div>
<TextField
required
label ="Method of Administration"
value={state.methodofadmin}
id="methodofadmin"
onChange={handleChange}
/>
</div>
<div>
<TextField
type="date"
value={state.startdate}
id="startdate"
onChange={handleChange}
helperText="Start Date"
/>
<TextField
type="date"
value={state.finishdate}
id="finishdate"
onChange={handleChange}
helperText="Finish Date"
/>
</div>
<Button style={{ float: "right", marginRight: "3rem" }}
variant="contained"
color="primary"
onClick={(e) => prescribe(e)}>
Prescibe Medication
</Button>
</form>
</div>
)
}
export default PrescribeMedications;

View file

@ -0,0 +1,36 @@
import React from 'react';
import Page from 'material-ui-shell/lib/containers/Page';
import Typography from '@material-ui/core/Typography';
import AdmissionRequests from './AdmissionRequests';
import PrescribeMedications from './PrescribeMedications';
import DischargePatient from './DischargePatient';
const StaffContext = (props) => {
//interchange between "CHARGE NURSE" and "MEDICAL" to see different actions
//get this using (props.accessType) or similar. Send props in wrapper
const [accessType, setAccessType] = React.useState('CHARGE NURSE') ;
if ( accessType === 'MEDICAL'){
return (
<Page pageTitle='Staff Context' >
<Typography variant="h4" style={{ padding: "1rem" }}> Available Actions for Doctor</Typography>
<PrescribeMedications/>
</Page>
)
} else if (accessType === "CHARGE NURSE"){
return (
<Page pageTitle='Staff Context' >
<div style={{padding: 64}}>
<Typography variant="h4" style={{marginBottom: 16}}> Available Actions for Charge Nurse</Typography>
<DischargePatient />
<AdmissionRequests/>
</div>
</Page>
)
}
}
export default StaffContext;

View file

@ -0,0 +1,3 @@
import StaffContext from "./StaffContext";
export default StaffContext;

View file

@ -0,0 +1,72 @@
import React from 'react';
import { connect } from 'react-redux';
import { Action } from 'reducers';
import StaffContext from './StaffContext';
class Home extends React.Component {
componentWillMount() {
if (this.props.isAdmin) {
return this.props.redirectHome();
}
if (this.props.authenticated) {
this.props.fetchUser();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.isAdmin) {
return nextProps.redirectHome();
}
if (nextProps.updated) {
setTimeout(() => {
this.props.updateDone();
}, 1000);
}
}
render() {
return (
<StaffContext accessType = { this.props.profile.role}
/>
);
}
}
const mapStateToProps = (state) => {
const profile = {
name: '',
role: '',
};
if (state.User.get('fetchSuccess')) {
const User = state.User.get('profile');
profile.name = `${User.firstName} ${User.lastName}`;
profile.role = User.accessType;
}
return {
profile,
fetchSuccess: state.User.get('fetchSuccess'),
updated: state.User.get('updated'),
updateSuccess: state.User.get('updateSuccess'),
updateError: state.User.get('error'),
authenticated: state.Auth.get('authenticated'),
isAdmin: state.Auth.get('isAdmin'),
};
};
const mapDispatchToProps = dispatch => ({
fetchUser: () => {
dispatch(Action.FetchUser());
},
updateUser: (newprofile) => {
dispatch(Action.UpdateUser(newprofile));
},
updateDone: () => {
dispatch(Action.UserUpdateDone());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

131
client/src/reducers/auth.js Normal file
View file

@ -0,0 +1,131 @@
import config from '../config/config'
import storage from '../lib/storage';
import Immutable from 'immutable';
import { replace } from 'react-router-redux';
/** ********************************************
** Constants **
******************************************** */
const AUTH_USER = Symbol();
const UNAUTH_USER = Symbol();
const AUTH_ERROR = Symbol();
const initState = () => {
const token = storage.get('token');
return Immutable.fromJS({
error: null,
timestamp: null,
authenticated: !!token,
isAdmin: !!token && tokenIsAdmin(token),
});
};
/** ********************************************
** Helper Functions **
******************************************** */
const tokenGetClaims = (token) => {
if (!token) {
return {};
}
const tokenArray = token.split('.');
if (tokenArray.length !== 3) {
return {};
}
return JSON.parse(window.atob(tokenArray[1].replace('-', '+').replace('_', '/')));
};
const tokenIsAdmin = token => !!tokenGetClaims(token).admin;
/** ********************************************
** Auth States **
******************************************** */
class State {
static Auth(error, token) {
return {
type: error ? AUTH_ERROR : AUTH_USER,
isAdmin: error ? undefined : tokenIsAdmin(token),
error: error || undefined,
};
}
static UnAuth(error) {
return {
type: UNAUTH_USER,
};
}
}
/** ********************************************
** Actions **
******************************************** */
const LoginUser = (email, password) => async (dispatch) => {
try {
const response = await fetch(config.apiRoutes.API_URL + config.apiRoutes.auth.login, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
const status = await response.status;
const data = await response.json();
if (!data) throw new Error('Empty response from server');
if (data.error) throw new Error(data.error.message);
storage.set('token', data.token);
dispatch(State.Auth(null, data.token));
} catch (err) {
dispatch(State.Auth(err.message));
}
};
const LogoutUser = error => async (dispatch) => {
console.log('here')
dispatch(State.UnAuth());
storage.remove('token');
storage.remove('auth');
dispatch(replace('/login'));
};
/** ********************************************
** Auth Reducer **
******************************************** */
const Auth = (state = initState(), action) => {
switch (action.type) {
case AUTH_USER:
return state.withMutations((val) => {
val.set('error', null);
val.set('timestamp', Date.now());
val.set('authenticated', true);
val.set('isAdmin', action.isAdmin);
});
case UNAUTH_USER:
return state.withMutations((val) => {
val.set('authenticated', false);
val.set('isAdmin', false);
});
case AUTH_ERROR:
return state.withMutations((val) => {
val.set('error', action.error);
val.set('timestamp', Date.now());
});
default:
return state;
}
};
export {
Auth, LoginUser, LogoutUser
};

View file

@ -0,0 +1,37 @@
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import { routerReducer } from "react-router-redux";
import thunk from "redux-thunk";
import { Auth, LoginUser, LogoutUser } from "./auth";
import { Registration, RegisterUser, registerDone } from "./registration";
import {
User,
FetchUser,
UpdateUser,
UserUpdateDone,
} from "./user";
const store = createStore(
combineReducers({
Auth,
Registration,
User,
router: routerReducer,
}),
compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
const Action = {
LoginUser,
LogoutUser,
RegisterUser,
registerDone,
FetchUser,
UpdateUser,
UserUpdateDone,
};
export { store, Action };

View file

@ -0,0 +1,96 @@
import config from '../config/config';
import Immutable from 'immutable';
import { Action } from 'reducers';
/** ********************************************
** Constants **
******************************************** */
const REGISTER_SUCCESS = Symbol();
const REGISTER_ERR = Symbol();
const REGISTER_DONE = Symbol();
const defaultState = Immutable.fromJS({
user: {},
registered: false,
registerSuccess: false,
error: null,
});
/** ********************************************
** Registration States **
******************************************** */
class State {
static Register(error, user) {
return {
type: error ? REGISTER_ERR : REGISTER_SUCCESS,
user: error ? undefined : user,
error: error || undefined,
};
}
}
/** ********************************************
** Actions **
******************************************** */
const RegisterUser = user => async (dispatch) => {
try {
const response = await fetch(config.apiRoutes.API_URL + config.apiRoutes.auth.register, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ user }),
});
const status = await response.status;
const data = await response.json();
if (!data) throw new Error('Empty response from server');
if (data.error) throw new Error(data.error.message);
dispatch(State.Register(null, data.user));
dispatch(Action.LoginUser(user.email, user.password));
} catch (err) {
dispatch(State.Register(err.message));
}
};
/** ********************************************
** Registration Reducer **
******************************************** */
const Registration = (state = defaultState, action) => {
switch (action.type) {
case REGISTER_SUCCESS:
return state.withMutations((val) => {
val.set('user', action.user);
val.set('error', null);
val.set('registered', true);
val.set('registerSuccess', true);
});
case REGISTER_ERR:
return state.withMutations((val) => {
val.set('error', action.error);
val.set('registered', true);
val.set('registerSuccess', false);
});
case REGISTER_DONE:
return state.withMutations((val) => {
val.set('registered', false);
});
default:
return state;
}
};
const registerDone = () => ({ type: REGISTER_DONE });
export {
Registration, RegisterUser, registerDone,
};

150
client/src/reducers/user.js Normal file
View file

@ -0,0 +1,150 @@
import Immutable from "immutable";
import storage from '../lib/storage';
import config from '../config/config';
import { LogoutUser } from "./auth";
/** ********************************************
** Constants **
******************************************** */
const FETCH_USER = Symbol();
const FETCH_USER_ERR = Symbol();
const UPDATE_USER_ERR = Symbol();
const UPDATE_USER_SUCCESS = Symbol();
const UPDATE_COMPLETED = Symbol();
const defaultState = Immutable.fromJS({
profile: {},
updated: false,
updateSuccess: false,
fetchSuccess: false,
error: null,
});
/** ********************************************
** User States **
******************************************** */
class State {
static FetchUser(error, user) {
return {
type: error ? FETCH_USER_ERR : FETCH_USER,
user: error ? undefined : user,
error: error || undefined,
};
}
static UpdateUser(error) {
return {
type: error ? UPDATE_USER_ERR : UPDATE_USER_SUCCESS,
error: error || undefined,
};
}
}
/** ********************************************
** Actions **
******************************************** */
const FetchUser = () => async (dispatch) => {
try {
const response = await fetch(config.apiRoutes.API_URL + config.apiRoutes.user.user, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${storage.get("token")}`,
},
});
const status = await response.status;
if (status === 401 || status === 403) {
return dispatch(LogoutUser());
}
const data = await response.json();
if (!data) throw new Error("Empty response from server");
if (data.error) throw new Error(data.error.message);
dispatch(State.FetchUser(null, data.user));
} catch (err) {
dispatch(State.FetchUser(err.message));
}
};
const UpdateUser = (user) => async (dispatch) => {
try {
const response = await fetch(config.apiRoutes.API_URL + config.apiRoutes.user.user, {
method: "PATCH",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${storage.get("token")}`,
},
body: JSON.stringify({ user }),
});
const status = await response.status;
if (status === 401 || status === 403) {
return dispatch(LogoutUser());
}
const data = await response.json();
if (!data) throw new Error("Empty response from server");
if (data.error) throw new Error(data.error.message);
dispatch(State.FetchUser(null, data.user));
dispatch(State.UpdateUser());
} catch (err) {
dispatch(State.UpdateUser(err.message));
}
};
/** ********************************************
** User Reducer **
******************************************** */
const User = (state = defaultState, action) => {
switch (action.type) {
case FETCH_USER:
return state.withMutations((val) => {
val.set("error", null);
val.set("profile", action.user);
val.set("fetchSuccess", true);
});
case FETCH_USER_ERR:
return state.withMutations((val) => {
val.set("error", action.error);
val.set("profile", {});
val.set("fetchSuccess", false);
});
case UPDATE_USER_ERR:
return state.withMutations((val) => {
val.set("error", action.error);
val.set("updated", true);
val.set("updateSuccess", false);
});
case UPDATE_USER_SUCCESS:
return state.withMutations((val) => {
val.set("error", null);
val.set("updated", true);
val.set("updateSuccess", true);
});
case UPDATE_COMPLETED:
return state.withMutations((val) => {
val.set("updated", false);
});
default:
return state;
}
};
const UserUpdateDone = () => ({ type: UPDATE_COMPLETED });
export { User, FetchUser, UpdateUser, UserUpdateDone };

View file

@ -0,0 +1,17 @@
export const getDisplayUserType = (userType) => {
let displayUserType;
switch (userType) {
case "staff":
displayUserType = "Staff Member";
break;
case "medical":
displayUserType = "Medical Staff";
break;
case "nurse":
displayUserType = "Charge Nurse";
break;
default:
displayUserType = "";
}
return displayUserType;
};