update jamespong14205,

This commit is contained in:
louiscklaw
2025-02-01 02:02:25 +08:00
parent 8bf2589af5
commit c3a16177eb
90 changed files with 9071 additions and 6 deletions

View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,73 @@
module.exports = {
env: {
browser: true,
es2021: true,
jest: true,
},
extends: ['plugin:react/recommended', 'airbnb', 'prettier'],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['react', 'prettier'],
rules: {
'react/react-in-jsx-scope': 'off',
'import/no-duplicates': 'error',
'import/no-unresolved': 'error',
'import/named': 'error',
'prettier/prettier': 'error',
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
'react/state-in-constructor': 'off',
'react/prop-types': 'off',
'react/no-access-state-in-setstate': 'error',
'react/no-danger': 'error',
'react/no-did-mount-set-state': 'error',
'react/no-did-update-set-state': 'error',
'react/no-will-update-set-state': 'error',
'react/no-redundant-should-component-update': 'error',
'react/no-this-in-sfc': 'error',
'react/no-typos': 'error',
'react/no-unused-state': 'error',
'react/jsx-no-bind': 'error',
'no-useless-call': 'error',
'no-useless-computed-key': 'error',
'no-useless-concat': 'error',
'no-useless-constructor': 'error',
'no-useless-rename': 'error',
'no-useless-return': 'error',
'react/jsx-props-no-spreading': 'off',
// overriding recommended rules
'no-constant-condition': ['error', { checkLoops: false }],
'no-console': ['error', { allow: ['log', 'warn', 'error'] }],
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-underscore-dangle': ['error'],
//
camelcase: 'off',
'no-alert': 'off',
},
settings: {
'import/resolver': {
node: {
paths: ['.'],
},
alias: {
map: [
['@/public', './public'],
['@/config', './config'],
// Add more here
],
extensions: ['.js', '.jsx'],
},
},
},
ignorePatterns: ['*_*', '*test*', '**/*debug*', '**/*copy*', '*helloworld*'],
};

39
task1/project/003_src/client/.gitignore vendored Normal file
View File

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

View File

@@ -0,0 +1,8 @@
command_exists () {
command -v "$1" >/dev/null 2>&1
}
# Workaround for Windows 10, Git Bash and Yarn
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi

View File

@@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
yarn lint-staged

View File

@@ -0,0 +1,18 @@
node_modules
.next
.DS_Store
static
.vercel
public/
.github/
.babelrc
README.md
# ignore deployment files
/.now
/.serverless
/.serverless_nextjs
/.vercel
.vercel
.now
.env

View File

@@ -0,0 +1,18 @@
module.exports = {
arrowParens: 'avoid',
bracketSpacing: true,
htmlWhitespaceSensitivity: 'css',
insertPragma: false,
bracketSameLine: false,
jsxSingleQuote: true,
printWidth: 120,
proseWrap: 'preserve',
quoteProps: 'as-needed',
requirePragma: false,
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'all',
useTabs: false,
plugins: [require.resolve('prettier-plugin-organize-imports')],
};

View File

@@ -0,0 +1,2 @@
{
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Othneil Drew
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,31 @@
### endpoints
```bash
http://localhost/api/patient_queue/list
http://localhost/api/patient_queue/non_urgent_case
http://localhost/api/patient_queue/semi_urgent_case
```
### start demo
```bash
# start docker desktop in host
# inside wsl
# 1. start docker
$ ./dc_up.sh
# 2. get into container
$ docker compose exec -it client bash
# 3. start demo
$ ./demo.sh
# 4. browse http://localhost after complete
```
### test ac
username: admin
password: nimda

View File

@@ -0,0 +1,66 @@
import Box from '@mui/material/Box';
const row = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
};
const labelColumn = {
fontWeight: 'bold',
width: '25%',
};
const valueColumn = {
width: '75%',
};
const cardTitle = {
fontWeight: 'bold',
fontSize: '1.2rem',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
};
export default function AdminQueueItemCard({ queue_data }) {
return (
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
margin: '1rem 0',
}}
>
<Box
sx={{
width: '90vw',
border: '1px solid gray',
borderRadius: '1rem',
display: 'flex',
flexDirection: 'column',
padding: '1rem',
}}
>
<Box sx={cardTitle}>{`non-urgent-case ${queue_data.id}`}</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', padding: '1rem 1rem' }}>
<Box sx={row}>
<Box sx={labelColumn}>Name</Box>
<Box sx={valueColumn}>{queue_data.name || ''}</Box>
</Box>
<Box sx={row}>
<Box sx={labelColumn}>HKID</Box>
<Box sx={valueColumn}>{queue_data.hkid || '-'}</Box>
</Box>
<Box sx={row}>
<Box sx={labelColumn}>Mobile</Box>
<Box sx={valueColumn}>{queue_data.mobile || '-'}</Box>
</Box>
</Box>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,15 @@
import { Box } from '@mui/material';
export default () => (
<Box
sx={{
width: '100%',
height: '90vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
loading...
</Box>
);

View File

@@ -0,0 +1,122 @@
import { Box, Button } from '@mui/material';
import { useRouter } from 'next/dist/client/router';
import { useState } from 'react';
const row = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
};
const labelColumn = {
fontWeight: 'bold',
width: '25%',
};
const valueColumn = {
width: '75%',
};
const cardTitle = {
fontWeight: 'bold',
fontSize: '1.2rem',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
};
export default ({ queue_data, show_edit_delete = true }) => {
const router = useRouter();
const [changing_page, setChangingPage] = useState(false);
function handleDeleteItemClick({ queue_type, id }) {
setChangingPage(true);
fetch(`/api/patient_queue/delete_queue_item?queue_type=${queue_type}&id=${id}`, {
method: 'GET',
})
.then(response => response.json())
.then(data => {
if (data.status === 'OK') {
console.log('item deleted');
alert('item deleted');
router.reload();
} else {
console.error(data.message);
}
})
.catch(err => {
console.error(err);
alert('error during removing queue');
});
}
function handleEditItemClick({ id }) {
setChangingPage(true);
router.push(`/NonUrgentCaseEdit/${id}`);
}
return (
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
margin: '1rem 0',
}}
>
<Box
sx={{
width: '90vw',
border: '1px solid gray',
borderRadius: '1rem',
display: 'flex',
flexDirection: 'column',
padding: '1rem',
}}
>
<Box sx={cardTitle}>{`non-urgent-case ${queue_data.id}`}</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', padding: '0.15rem 0.15rem' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', gap: '0.5rem', marginTop: '1rem' }}>
<Box sx={{ flexGrow: 1, fontSize: '0.8rem' }}>
<Box sx={row}>
<Box sx={labelColumn}>Name</Box>
<Box sx={valueColumn}>{queue_data.name || ''}</Box>
</Box>
<Box sx={row}>
<Box sx={labelColumn}>HKID</Box>
<Box sx={valueColumn}>{queue_data.hkid || '-'}</Box>
</Box>
<Box sx={row}>
<Box sx={labelColumn}>Mobile</Box>
<Box sx={valueColumn}>{queue_data.mobile || '-'}</Box>
</Box>
</Box>
<Box sx={{ display: show_edit_delete ? 'flex' : 'none', flexDirection: 'column', gap: '1rem' }}>
<Button
disabled={changing_page}
size='small'
variant='contained'
onClick={() => handleDeleteItemClick({ queue_type: 'non-urgent', id: queue_data.id })}
>
delete
</Button>
<Button
disabled={changing_page}
size='small'
variant='contained'
onClick={() => handleEditItemClick({ id: queue_data.id })}
>
edit
</Button>
{/* */}
</Box>
</Box>
</Box>
</Box>
</Box>
);
};

View File

@@ -0,0 +1,118 @@
import { Box, Button } from '@mui/material';
import { useRouter } from 'next/dist/client/router';
import { useState } from 'react';
const row = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
};
const labelColumn = {
fontWeight: 'bold',
width: '25%',
};
const valueColumn = {
width: '75%',
};
const cardTitle = {
fontWeight: 'bold',
fontSize: '1.2rem',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
};
export default ({ queue_data, show_edit_delete = true }) => {
const router = useRouter();
const [changing_page, setChangingPage] = useState(false);
function handleDeleteItemClick({ queue_type, id }) {
setChangingPage(true);
fetch(`/api/patient_queue/delete_queue_item?queue_type=${queue_type}&id=${id}`, {
method: 'GET',
})
.then(response => response.json())
.then(data => {
if (data.status === 'OK') {
console.log('item deleted');
router.reload();
} else {
console.error(data.message);
}
})
.catch(err => {
console.error(err);
});
}
function handleEditItemClick({ id }) {
setChangingPage(true);
router.push(`/SemiUrgentCaseEdit/${id}`);
}
return (
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
margin: '1rem 0',
}}
>
<Box
sx={{
width: '90vw',
border: '1px solid gray',
borderRadius: '1rem',
display: 'flex',
flexDirection: 'column',
padding: '1rem',
}}
>
<Box sx={cardTitle}>{`semi-urgent-case ${queue_data.id}`}</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', padding: '0.15rem 0.15rem' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', gap: '0.5rem', marginTop: '1rem' }}>
<Box sx={{ flexGrow: 1, fontSize: '0.8rem' }}>
<Box sx={row}>
<Box sx={labelColumn}>Name</Box>
<Box sx={valueColumn}>{queue_data.name || ''}</Box>
</Box>
<Box sx={row}>
<Box sx={labelColumn}>HKID</Box>
<Box sx={valueColumn}>{queue_data.hkid || '-'}</Box>
</Box>
<Box sx={row}>
<Box sx={labelColumn}>Mobile</Box>
<Box sx={valueColumn}>{queue_data.mobile || '-'}</Box>
</Box>
</Box>
<Box sx={{ display: show_edit_delete ? 'flex' : 'none', flexDirection: 'column', gap: '1rem' }}>
<Button
disabled={changing_page}
size='small'
variant='contained'
onClick={() => handleDeleteItemClick({ queue_type: 'semi-urgent', id: queue_data.id })}
>
delete
</Button>
<Button
disabled={changing_page}
size='small'
variant='contained'
onClick={() => handleEditItemClick({ id: queue_data.id })}
>
edit
</Button>
</Box>
</Box>
</Box>
</Box>
</Box>
);
};

View File

@@ -0,0 +1,15 @@
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
import { StylesProvider } from '@mui/styles';
import { ThemeProvider as StyledThemeProvider } from 'styled-components';
const MyThemeProvider = ({ theme, children }) => (
<StylesProvider injectFirst>
<CssBaseline />
<StyledThemeProvider theme={theme}>
<MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>
</StyledThemeProvider>
</StylesProvider>
);
export default MyThemeProvider;

View File

@@ -0,0 +1,37 @@
import { red } from '@mui/material/colors';
import { createTheme } from '@mui/material/styles';
// Create a theme instance.
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
error: {
main: red.A400,
},
},
components: {
MuiTypography: {
defaultProps: {
variantMapping: {
h1: 'h2',
h2: 'h2',
h3: 'h2',
h4: 'h2',
h5: 'h2',
h6: 'h2',
subtitle1: 'h2',
subtitle2: 'h2',
body1: 'span',
body2: 'span',
},
},
},
},
});
export default theme;

View File

@@ -0,0 +1,47 @@
const { DataTypes } = require('sequelize');
const bcrypt = require('bcrypt');
const { sequelize } = require('./model');
const Auth = sequelize.define(
'Auths',
{
uid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
username: { type: DataTypes.STRING, allowNull: false },
password: { type: DataTypes.STRING, allowNull: false },
session: { type: DataTypes.STRING, allowNull: false, defaultValue: '' },
role: { type: DataTypes.STRING, allowNull: false, defaultValue: 'customer' },
},
{ timestamps: false },
);
async function hashPassword(plainTextPassword) {
const saltRounds = 10;
return await bcrypt.hash(plainTextPassword, saltRounds);
}
(async () => {
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
// create table
await Auth.drop();
await sequelize.sync();
let user_row;
let password;
user_row = await Auth.create({
username: 'admin',
password: 'nimda',
role: 'admin',
});
user_row = await Auth.create({ username: 'pat1', password: await hashPassword('1tap') });
user_row = await Auth.create({ username: 'pat2', password: await hashPassword('2tap') });
user_row = await Auth.create({ username: 'pat3', password: await hashPassword('3tap') });
await sequelize.close();
} catch (error) {
console.error('Unable to connect to the database:', error);
}
})();

View File

@@ -0,0 +1,15 @@
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize('app_db', 'db_user', 'db_user_pass', {
host: 'mysql',
port: 3306,
dialect: 'mysql',
});
const sequelize_config = new Sequelize('app_db', 'db_user', 'db_user_pass', {
host: 'mysql',
port: 3306,
dialect: 'mysql',
});
module.exports = { sequelize, sequelize_config };

View File

@@ -0,0 +1,48 @@
const { Sequelize, DataTypes } = require('sequelize');
// const { sequelize } = require('./model'); // abonded
const sequelize_config = require('../utils/sequelize_config');
const Item = sequelize_config.define(
'NonUrgentQueue',
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
name: { type: DataTypes.STRING, allowNull: false },
hkid: { type: DataTypes.STRING, allowNull: false },
mobile: { type: DataTypes.STRING, allowNull: false },
age: { type: DataTypes.INTEGER, allowNull: false },
description: { type: DataTypes.STRING, allowNull: false },
//
bruisesScratchesMinorBurns: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
chestPain: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
headache: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
myMuiCheck: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
nauseaAndVomiting: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
runnyOrStuffyNose: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
soreThroat: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
},
{ timestamps: false },
);
(async () => {
try {
await sequelize_config.authenticate();
console.log('Connection has been established successfully.');
await Item.drop();
await sequelize_config.sync();
for (let i = 0; i < 3; i++) {
await Item.create({
name: `non-urgent-patient ${i}`,
hkid: `A123456(${i})`,
mobile: `9123456${i}`,
age: i + 10,
description: `non-urgent queue ${i}`,
});
}
await sequelize_config.close();
} catch (error) {
console.error('Unable to connect to the database:', error);
}
})();

View File

@@ -0,0 +1,33 @@
const { Sequelize, DataTypes } = require('sequelize');
const { sequelize } = require('./model');
const Item = sequelize.define(
'PatientQueue',
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
description: { type: DataTypes.STRING, allowNull: false },
},
{ timestamps: false },
);
(async () => {
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
await Item.drop();
await sequelize.sync();
for (let i = 0; i < 10; i++) {
await Item.create({
// get remainder of i divided by 3
// pid: (i % 25) + 1,
description: `test item ${i}`,
});
}
await sequelize.close();
} catch (error) {
console.error('Unable to connect to the database:', error);
}
})();

View File

@@ -0,0 +1,48 @@
const { Sequelize, DataTypes } = require('sequelize');
// const { sequelize } = require('./model'); //abonded
const sequelize_config = require('../utils/sequelize_config');
const Item = sequelize_config.define(
'SemiUrgentQueue',
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
name: { type: DataTypes.STRING, allowNull: false },
hkid: { type: DataTypes.STRING, allowNull: false },
mobile: { type: DataTypes.STRING, allowNull: false },
age: { type: DataTypes.INTEGER, allowNull: false },
description: { type: DataTypes.STRING, allowNull: false },
//
bruisesScratchesMinorBurns: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
chestPain: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
headache: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
myMuiCheck: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
nauseaAndVomiting: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
runnyOrStuffyNose: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
soreThroat: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
},
{ timestamps: false },
);
(async () => {
try {
await sequelize_config.authenticate();
console.log('Connection has been established successfully.');
await Item.drop();
await sequelize_config.sync();
for (let i = 5; i < 8; i++) {
await Item.create({
name: `semi-urgent-patient ${i}`,
hkid: `A123456(${i})`,
mobile: `9123456${i}`,
age: i + 20,
description: `semi-urgent queue ${i}`,
});
}
await sequelize_config.close();
} catch (error) {
console.error('Unable to connect to the database:', error);
}
})();

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -ex
rm -rf .next node_modules/*
yarn --dev
yarn build
yarn demo

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -ex
yarn --dev
yarn seed
yarn dev

View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "es6",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": ".",
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"paths": {
"@/config/*": ["./config/*"],
"@/public/*": ["./public"]
},
"plugins": []
},
"include": ["**/*.js", "**/*.jsx"],
"exclude": ["node_modules", "**/*copy*", "**/*.del"]
}

View File

@@ -0,0 +1,3 @@
module.exports = {
reactStrictMode: true,
};

View File

@@ -0,0 +1,69 @@
{
"name": "next-mui-boilerplate",
"private": true,
"description": "A JavaScript Nextjs boilerplate complete with material ui, eslint, airbnb react style guides and husky pre-commit hooks",
"keywords": [
"nextjs",
"mui",
"material-ui",
"airbnb-style-guides",
"husky",
"prettier",
"eslint"
],
"scripts": {
"build:w": "npx nodemon -w . --exec 'yarn build'",
"build": "yarn run clear && next build",
"clear": "rm -rf .next",
"demo": "yarn seed && yarn start",
"dev": "next dev -H 0.0.0.0",
"format": "prettier --ignore-path .prettierignore --write .",
"lint-fix:w": "npx nodemon -w . --exec 'yarn lint-fix'",
"lint-fix": "eslint --fix --ext .js,.jsx .",
"lint": "eslint **/*.js --report-unused-disable-directives",
"prepare_disabled": "husky install",
"seed": "cd db_seed && node ./auth.js && node ./patient_queue.js && node ./semi_urgent_case_queue.js && node ./non_urgent_case_queue.js",
"seed1": "cd db_seed && node ./auth.js && node ./patient_queue.js",
"start": "next start"
},
"lint-staged": {
"*js": [
"yarn lint --fix",
"yarn format"
]
},
"dependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/icons-material": "^6.1.3",
"@mui/lab": "^6.0.0-beta.12",
"@mui/material": "^5.1.0",
"@mui/styles": "^5.1.0",
"bcrypt": "^5.1.1",
"dotenv": "^16.4.5",
"formik": "^2.4.6",
"mysql2": "^3.11.3",
"next": "12.0.3",
"prettier-plugin-organize-imports": "^4.1.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"sequelize": "^6.37.4",
"styled-components": "^5.3.3",
"typescript": "^5.6.3",
"yup": "^1.4.0"
},
"devDependencies": {
"eslint": "<8.0.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-next": "12.0.2",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react-hooks": "^4.2.0",
"husky": "^7.0.0",
"lint-staged": "^11.2.6",
"prettier": "^2.4.1"
}
}

View File

@@ -0,0 +1,136 @@
import React, { useState } from 'react';
import { ChevronLeftOutlined } from '@mui/icons-material';
import MenuIcon from '@mui/icons-material/Menu';
import {
Box,
Button,
Drawer,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
} from '@mui/material';
import { useRouter } from 'next/dist/client/router';
import Head from 'next/head';
export default function Home() {
const [open, setOpen] = React.useState(false);
const router = useRouter();
const [changing_page, setChangingPage] = useState(false);
const toggleDrawer = newOpen => () => {
setOpen(newOpen);
};
const DrawerList = (
<Box sx={{ width: 250 }} role='presentation' onClick={toggleDrawer(false)}>
<List>
<ListItem key='dashboard' disablePadding>
<ListItemButton
onClick={() => {
setChangingPage(true);
router.push('/AdminLogin');
}}
>
<ListItemIcon>
<ChevronLeftOutlined />
</ListItemIcon>
<ListItemText primary='Logout' />
</ListItemButton>
</ListItem>
</List>
</Box>
);
return (
<>
<Head>
<title>admin dashboard</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box className='main'>
<Box
sx={{
//
display: 'flex',
flexDirection: 'flex-start',
alignItems: 'center',
gap: '1rem',
//
height: '3rem',
}}
>
<IconButton aria-label='menu' onClick={toggleDrawer(true)}>
<MenuIcon />
</IconButton>
<Box sx={{ fontWeight: 'bold', fontSize: '1.2rem' }}>Admin dashboard</Box>
</Box>
<Box
sx={{
height: '90vh',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Box sx={{}}>
<Box style={{ paddingTop: '3rem', width: '90vw' }}>
<Button
variant='contained'
disabled={changing_page}
onClick={() => {
setChangingPage(true);
router.push('/SemiUrgentCaseList');
}}
fullWidth
>
<Box sx={{ fontSize: '1.1rem', padding: '1rem' }}>Semi-urgent case</Box>
</Button>
</Box>
<Box style={{ paddingTop: '3rem', width: '90vw' }}>
<Button
variant='contained'
disabled={changing_page}
onClick={() => {
setChangingPage(true);
router.push('/NonUrgentCaseList');
}}
fullWidth
>
<Box style={{ fontSize: '1.1rem', padding: '1rem' }}>Non-urgent case</Box>
</Button>
</Box>
<Box style={{ paddingTop: '3rem', width: '90vw' }}>
<Button
variant='contained'
disabled={changing_page}
onClick={() => {
setChangingPage(true);
router.push('/SearchCase');
}}
fullWidth
>
<Box style={{ fontSize: '1.1rem', padding: '1rem' }}>Search case</Box>
</Button>
</Box>
</Box>
</Box>
</Box>
<Drawer open={open} onClose={toggleDrawer(false)}>
{DrawerList}
</Drawer>
</>
);
}

View File

@@ -0,0 +1,168 @@
import { Box, Button, TextField } from '@mui/material';
import Head from 'next/head';
import { ChevronLeftOutlined } from '@mui/icons-material';
import LoginIcon from '@mui/icons-material/Login';
import { useFormik } from 'formik';
import { useRouter } from 'next/dist/client/router';
import React from 'react';
import is_development_plant from 'utils/is_development_plant';
import * as yup from 'yup';
let default_init_values = {
username: '',
passwod: '',
};
if (is_development_plant) {
console.log('development plant');
default_init_values = {
username: 'admi',
password: 'nimda',
};
}
const validationSchema = yup.object({
username: yup
.string('Enter your username')
.min(5, 'Username should be of minimum 5 characters length')
.required('Username is required'),
password: yup
.string('Enter your password')
.min(5, 'Password should be of minimum 5 characters length')
.required('Password is required'),
});
export default function Home() {
const router = useRouter();
const [changing_page, setChangingPage] = React.useState(false);
const formik = useFormik({
initialValues: default_init_values,
validationSchema,
onSubmit: values => {
fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
})
.then(res => res.json())
.then(data => {
if (data.success) {
router.push('/AdminHome');
} else {
alert(data.message);
formik.resetForm();
}
})
.catch(err => {
console.error(err);
alert('server error');
});
},
});
return (
<>
<Head>
<title>admin login page</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box
sx={{
height: '90vh',
//
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: '1rem',
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
//
width: '100%',
padding: '0 2rem',
}}
>
<form onSubmit={formik.handleSubmit}>
<Box
sx={{
textAlign: 'center',
fontWeight: 'bold',
fontSize: '1.2rem',
marginTop: '2rem',
marginBottom: '2rem',
}}
>
Admin Login Page
</Box>
<TextField
fullWidth
variant='standard'
id='username'
name='username'
value={formik.values.username}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.username && Boolean(formik.errors.username)}
helperText={formik.touched.username && formik.errors.username}
label='Username'
inputProps={{ sx: { textAlign: 'center' } }}
/>
<TextField
fullWidth
variant='standard'
id='password'
name='password'
value={formik.values.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.password && Boolean(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password}
type='password'
label='Password'
inputProps={{ sx: { textAlign: 'center' } }}
/>
<Box
style={{
paddingTop: '3rem',
display: 'flex',
justifyContent: 'space-around',
}}
>
<Button
disabled={changing_page || formik.isSubmitting}
variant='outlined'
startIcon={<ChevronLeftOutlined />}
onClick={() => {
setChangingPage(true);
router.push('/');
}}
>
Back
</Button>
<Button
variant='contained'
type='submit'
startIcon={<LoginIcon />}
disabled={!(formik.isValid && formik.dirty && !formik.isSubmitting) || changing_page}
>
Login
</Button>
</Box>
</form>
</Box>
</Box>
</>
);
}

View File

@@ -0,0 +1,246 @@
import React, { useEffect, useState } from 'react';
import { Box, Button, TextField } from '@mui/material';
import Head from 'next/head';
import { ChevronLeftOutlined } from '@mui/icons-material';
import Loading from 'components/Loading';
import { useFormik } from 'formik';
import { useRouter } from 'next/dist/client/router';
import * as yup from 'yup';
const validationSchema = yup.object({
patient_name: yup
.string('Enter your name')
.min(2, 'Name should be of minimum 2 characters length')
.required('Name is required'),
patient_hkid: yup
.string('Enter your HKID')
.matches(/^[A-Z]{1,2}[0-9]{6}\([0-9]\)$/, 'HKID should be in format of XX123456(1-9)')
.required('HKID is required'),
patient_age: yup
.number('Enter your age')
.min(1, 'Age should be greater than 0')
.max(90, 'Age should be less than 90')
.required('Age is required'),
patient_mobile: yup
.string('Enter your mobile number')
.matches(/^[0-9]{8}$/, 'Mobile number should be 8 numbers')
.required('Mobile number is required'),
});
export default () => {
const router = useRouter();
const { id } = router.query;
const [loading, setLoading] = React.useState(true);
const [changing_page, setChangingPage] = useState(false);
const formik = useFormik({
enableReinitialize: true,
initialValues: {
patient_name: '',
patient_hkid: '',
patient_age: 0,
patient_mobile: '',
bruisesScratchesMinorBurns: false,
chestPain: false,
headache: false,
myMuiCheck: false,
nauseaAndVomiting: false,
runnyOrStuffyNose: false,
soreThroat: false,
},
validationSchema,
onSubmit: values => {
fetch('/api/patient_queue/update_queue_item', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, queue_type: 'non-urgent', values }),
})
.then(response => response.json())
.then(data => {
if (data.status) {
alert('saved !');
router.reload();
} else {
// alert(data.message);
alert('sorry there are something wrong, please try again');
}
})
.catch(err => {
console.error(err);
alert('server error');
});
},
});
useEffect(() => {
console.log({ t: `/api/patient_queue/read_queue_item?queue_type=non-urgent&id=${id}` });
if (id) {
fetch(`/api/patient_queue/read_queue_item?queue_type=non-urgent&id=${id}`, {
method: 'GET',
})
.then(response => response.json())
.then(data => {
if (data.status) {
console.log(data);
const {
name,
hkid,
mobile,
age,
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
} = data.queueItem;
formik.setValues({
patient_name: name,
patient_hkid: hkid,
patient_mobile: mobile,
patient_age: age,
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
});
} else {
alert(data.message);
}
setLoading(false);
})
.catch(err => {
console.error(err);
alert('server error');
})
.finally(() => {
setLoading(false);
});
}
}, [id]);
if (loading) {
return <Loading />;
}
return (
<>
<Head>
<title>dashboard - non-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '90vh',
}}
>
<form onSubmit={formik.handleSubmit}>
<Box sx={{ marginLeft: '2rem', marginRight: '2rem' }}>
<Box
sx={{
fontWeight: 'bold',
fontSize: '1.1rem',
marginTop: '1rem',
marginBottom: '1rem',
}}
>
Edit Registration
</Box>
<TextField
fullWidth
disabled={changing_page || formik.isSubmitting}
variant='standard'
id='patient_name'
name='patient_name'
label='Patient name'
value={formik.values.patient_name}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_name && Boolean(formik.errors.patient_name)}
helperText={formik.touched.patient_name && formik.errors.patient_name}
/>
<TextField
fullWidth
disabled={changing_page || formik.isSubmitting}
variant='standard'
id='patient_hkid'
name='patient_hkid'
label='HKID'
value={formik.values.patient_hkid}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_hkid && Boolean(formik.errors.patient_hkid)}
helperText={formik.touched.patient_hkid && formik.errors.patient_hkid}
/>
<TextField
fullWidth
disabled={changing_page || formik.isSubmitting}
variant='standard'
id='patient_age'
name='patient_age'
label='Age'
value={formik.values.patient_age}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_age && Boolean(formik.errors.patient_age)}
helperText={formik.touched.patient_age && formik.errors.patient_age}
/>
<TextField
fullWidth
disabled={changing_page || formik.isSubmitting}
variant='standard'
id='patient_mobile'
name='patient_mobile'
label='Mobile'
value={formik.values.patient_mobile}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_mobile && Boolean(formik.errors.patient_mobile)}
helperText={formik.touched.patient_mobile && formik.errors.patient_mobile}
/>
<Box sx={{ marginTop: '1rem', display: 'flex', justifyContent: 'space-around' }}>
<Button
disabled={changing_page}
onClick={() => {
setChangingPage(true);
router.push('/NonUrgentCaseList');
}}
variant='outline'
startIcon={<ChevronLeftOutlined />}
>
Back
</Button>
{/* */}
<Button
//
color='primary'
variant='contained'
type='submit'
disabled={!(formik.isValid && formik.dirty && !formik.isSubmitting) || changing_page}
>
Save
</Button>
</Box>
</Box>
</form>
</Box>
</>
);
};

View File

@@ -0,0 +1,40 @@
import { ChevronLeftOutlined } from '@mui/icons-material';
import { Box, Button } from '@mui/material';
import { useRouter } from 'next/dist/client/router';
import { useState } from 'react';
export default () => {
const [changing_page, setChangingPage] = useState(false);
const router = useRouter();
return (
<Box
sx={{
width: '100%',
height: '90vh',
//
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: '3rem',
//
fontWeight: 'bold',
fontSize: '1.2rem',
}}
>
<Box>Queue is empty</Box>
<Button
onClick={() => {
setChangingPage(true);
router.push('/AdminHome');
}}
startIcon={<ChevronLeftOutlined />}
variant='outlined'
disabled={changing_page}
>
Back
</Button>
</Box>
);
};

View File

@@ -0,0 +1,114 @@
import MenuIcon from '@mui/icons-material/Menu';
import React, { useEffect, useState } from 'react';
import { ChevronLeftOutlined } from '@mui/icons-material';
import { Box, Drawer, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import Loading from 'components/Loading';
import NonUrgentQueueItemCard from 'components/NonUrgentQueueItemCard';
import { useRouter } from 'next/dist/client/router';
import Head from 'next/head';
import QueueIsEmpty from './QueueIsEmpty';
export default () => {
const [open, setOpen] = React.useState(false);
const [p_queue, setPQueue] = React.useState([]);
const router = useRouter();
const [loading, setLoading] = React.useState(true);
const [changing_page, setChangingPage] = useState(false);
const toggleDrawer = newOpen => () => {
setOpen(newOpen);
};
useEffect(() => {
fetch('/api/patient_queue/non_urgent_case')
.then(response => response.json())
.then(data => {
setPQueue(data.patient_queues);
setLoading(false);
});
}, []);
const DrawerList = (
<Box sx={{ width: 250 }} role='presentation' onClick={toggleDrawer(false)}>
<List>
<ListItem key='dashboard' disablePadding>
<ListItemButton
disabled={changing_page}
onClick={() => {
setChangingPage(true);
router.push('/AdminHome');
}}
>
<ListItemIcon>
<ChevronLeftOutlined />
</ListItemIcon>
<ListItemText primary='back to dashboard' />
</ListItemButton>
</ListItem>
</List>
</Box>
);
if (loading) {
return <Loading />;
}
return (
<>
<Head>
<title>dashboard - non-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box className='main'>
<Box
sx={{
//
display: 'flex',
flexDirection: 'flex-start',
alignItems: 'center',
gap: '1rem',
//
height: '3rem',
}}
>
<IconButton aria-label='menu' onClick={toggleDrawer(true)}>
<MenuIcon />
</IconButton>
<Box sx={{ fontWeight: 'bold', fontSize: '1.2rem' }}>All Non-Urgent Cases</Box>
</Box>
{/* */}
<Box style={{ height: '95vh', overflowY: 'scroll' }}>
{p_queue.length === 0 ? (
<QueueIsEmpty />
) : (
<>
{p_queue.map(queue_data => (
<NonUrgentQueueItemCard key={queue_data} queue_data={queue_data} />
))}
<Box
style={{
width: '100%',
display: 'inline-flex',
justifyContent: 'center',
marginTop: '1rem',
marginBottom: '1rem',
}}
>
--end of list--
</Box>
</>
)}
</Box>
helloworld
</Box>
<Drawer open={open} onClose={toggleDrawer(false)}>
{DrawerList}
</Drawer>
</>
);
};

View File

@@ -0,0 +1,40 @@
import { Box, Button } from '@mui/material';
import { useRouter } from 'next/dist/client/router';
import { useState } from 'react';
export default () => {
const router = useRouter();
const [changing_page, setChangingPage] = useState(false);
return (
<>
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'row', justifyContent: 'center', marginTop: '2rem' }}>
<Box sx={{ fontWeight: 'bold', fontSize: '1.5rem' }}>Patient Landing</Box>
</Box>
<Box
sx={{
height: '80vh',
width: '100%',
display: 'inline-flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Box>
<Button
onClick={() => {
setChangingPage(true);
router.push('/PatientRegister');
}}
variant='contained'
disableElevation
disabled={changing_page}
>
Proceed to register
</Button>
</Box>
</Box>
</>
);
};

View File

@@ -0,0 +1,119 @@
import React, { useEffect, useState } from 'react';
import { Box, Button } from '@mui/material';
import Loading from 'components/Loading';
import { useRouter } from 'next/dist/client/router';
import Head from 'next/head';
export default () => {
const [loading, setLoading] = React.useState(true);
const router = useRouter();
const [changing_page, setChangingPage] = useState(false);
const [queue_type, setQueueType] = useState('');
const [queue_id, setQueueId] = useState('');
useEffect(() => {
let temp;
temp = localStorage.getItem('queue_type');
if (temp) setQueueType(temp);
temp = localStorage.getItem('queue_id');
if (temp) setQueueId(temp);
setLoading(false);
}, []);
if (loading) {
return <Loading />;
}
return (
<>
<Head>
<title>dashboard - non-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '90vh',
}}
>
<Box sx={{ fontWeight: 'bold', fontSize: '1.2rem' }}>Queue Number</Box>
<Box
style={{
marginTop: '1.2rem',
fontWeight: 'bold',
fontSize: '6rem',
backgroundColor: 'lightgray',
width: '75vw',
height: '75vw',
borderRadius: '1rem',
//
display: 'inline-flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
//
}}
>
{queue_id}
</Box>
<Box sx={{ marginTop: '1rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
{queue_type !== '' ? (
<>
<Box sx={{ fontWeight: 'bold' }}>Your waiting list category</Box>
<Box sx={{ fontWeight: 'bold', fontSize: '1.2rem' }}>{queue_type}</Box>
</>
) : (
<></>
)}
</Box>
<Box
sx={{
marginTop: '1.2rem',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
fontWeight: 'bold',
color: 'red',
}}
>
<Box sx={{}}>please screen capture </Box>
<Box sx={{}}>for saving your Queue Number</Box>
<Box sx={{ marginTop: '1rem' }}>
<Button
disabled={changing_page}
onClick={() => {
localStorage.removeItem('queue_id');
localStorage.removeItem('queue_name');
localStorage.removeItem('queue_hkid');
localStorage.removeItem('queue_mobile');
localStorage.removeItem('queue_description');
alert('Please be informed that the queue information cleared');
setChangingPage(true);
router.push('/');
}}
disableElevation
variant='contained'
>
Clear Queue Information
</Button>
</Box>
</Box>
</Box>
</>
);
};

View File

@@ -0,0 +1,318 @@
import React, { useEffect } from 'react';
import MailIcon from '@mui/icons-material/Mail';
import InboxIcon from '@mui/icons-material/MoveToInbox';
import {
Box,
Button,
Checkbox,
Divider,
Drawer,
FormControlLabel,
FormGroup,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
TextField,
} from '@mui/material';
import Head from 'next/head';
import { ChevronLeftOutlined } from '@mui/icons-material';
import Loading from 'components/Loading';
import { useFormik } from 'formik';
import { useRouter } from 'next/dist/client/router';
import * as yup from 'yup';
const validationSchema = yup.object({
patient_name: yup
.string('Enter your name')
.min(2, 'Name should be of minimum 2 characters length')
.required('Name is required'),
patient_hkid: yup
.string('Enter your HKID')
.matches(/^[A-Z]{1,2}[0-9]{6}\([0-9]\)$/, 'HKID should be in format of XX123456(1-9)')
.required('HKID is required'),
patient_age: yup
.number('Enter your age')
.min(1, 'Age should be greater than 0')
.max(90, 'Age should be less than 90')
.required('Age is required'),
patient_mobile: yup
.string('Enter your mobile number')
.matches(/^[0-9]{8}$/, 'Mobile number should be 8 numbers')
.required('Mobile number is required'),
});
export default () => {
const [open, setOpen] = React.useState(false);
const [loading, setLoading] = React.useState(true);
const [one_symptoms_selected, setOneSymptomsSelected] = React.useState(false);
const router = useRouter();
const toggleDrawer = newOpen => () => {
setOpen(newOpen);
};
const formik = useFormik({
initialValues: {
patient_name: '',
patient_hkid: '',
patient_age: 0,
patient_mobile: '',
bruisesScratchesMinorBurns: false,
chestPain: false,
headache: false,
myMuiCheck: false,
nauseaAndVomiting: false,
runnyOrStuffyNose: true,
soreThroat: false,
},
validationSchema,
onSubmit: values => {
// alert(JSON.stringify(values, null, 2));
fetch('/api/patient_queue/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ values }),
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.queue) {
alert(`registered successfully, will assign you to ${data.queue} queue`);
} else {
alert(`registered successfully`);
}
localStorage.setItem('queue_type', data.queue);
localStorage.setItem('queue_id', data.result.id);
localStorage.setItem('queue_name', data.result.name);
localStorage.setItem('queue_hkid', data.result.hkid);
localStorage.setItem('queue_mobile', data.result.mobile);
localStorage.setItem('queue_description', data.result.description);
router.push('/PatientQueueDisplay');
} else {
// alert(data.message);
alert('sorry there are something wrong, please try again');
}
})
.catch(err => {
console.error(err);
alert('server error');
});
},
});
useEffect(() => {
setOneSymptomsSelected(
!(
formik.values.bruisesScratchesMinorBurns ||
formik.values.chestPain ||
formik.values.headache ||
formik.values.myMuiCheck ||
formik.values.nauseaAndVomiting ||
formik.values.runnyOrStuffyNose ||
formik.values.soreThroat
),
);
}, [formik.values]);
const DrawerList = (
<Box sx={{ width: 250 }} role='presentation' onClick={toggleDrawer(false)}>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</Box>
);
useEffect(() => {
if (localStorage.getItem('queue_id')) {
router.push('/PatientQueueDisplay');
} else {
setLoading(false);
}
}, []);
if (loading) {
return <Loading />;
}
return (
<>
<Head>
<title>dashboard - non-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '90vh',
}}
>
<form onSubmit={formik.handleSubmit}>
<Box sx={{ marginLeft: '2rem', marginRight: '2rem' }}>
<Box
sx={{
fontWeight: 'bold',
fontSize: '1.1rem',
marginTop: '1rem',
marginBottom: '1rem',
}}
>
Electronic Diagnosis Registration
</Box>
<TextField
fullWidth
variant='standard'
id='patient_name'
name='patient_name'
label='Patient name'
value={formik.values.patient_name}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_name && Boolean(formik.errors.patient_name)}
helperText={formik.touched.patient_name && formik.errors.patient_name}
/>
<TextField
fullWidth
variant='standard'
id='patient_hkid'
name='patient_hkid'
label='HKID'
value={formik.values.patient_hkid}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_hkid && Boolean(formik.errors.patient_hkid)}
helperText={formik.touched.patient_hkid && formik.errors.patient_hkid}
/>
<TextField
fullWidth
variant='standard'
id='patient_age'
name='patient_age'
label='Age'
value={formik.values.patient_age}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_age && Boolean(formik.errors.patient_age)}
helperText={formik.touched.patient_age && formik.errors.patient_age}
/>
<TextField
fullWidth
variant='standard'
id='patient_mobile'
name='patient_mobile'
label='Mobile'
value={formik.values.patient_mobile}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_mobile && Boolean(formik.errors.patient_mobile)}
helperText={formik.touched.patient_mobile && formik.errors.patient_mobile}
/>
<Box sx={{ marginTop: '2rem' }}>
<FormGroup>
<Box sx={{ marginTop: '1rem', marginBottom: '1rem' }}>Reason for seeking medical advice</Box>
{formik.dirty && one_symptoms_selected ? (
<Box sx={{ color: 'red', fontSize: '0.8rem', fontWeight: 'bold' }}>
Please select at least one symptom
</Box>
) : (
<></>
)}
<FormControlLabel
control={<Checkbox />}
label='Runny or stuffy nose'
checked={formik.values.runnyOrStuffyNose}
onChange={() => formik.setFieldValue('runnyOrStuffyNose', !formik.values.runnyOrStuffyNose)}
/>
<FormControlLabel
control={<Checkbox />}
label='sore throat'
checked={formik.values.soreThroat}
onChange={() => formik.setFieldValue('soreThroat', !formik.values.soreThroat)}
/>
<FormControlLabel
control={<Checkbox />}
label='nausea and vomiting'
checked={formik.values.nauseaAndVomiting}
onChange={() => formik.setFieldValue('nauseaAndVomiting', !formik.values.nauseaAndVomiting)}
/>
<FormControlLabel
control={<Checkbox />}
label='Headache (Semi-urgent)'
checked={formik.values.headache}
onChange={() => formik.setFieldValue('headache', !formik.values.headache)}
/>
<FormControlLabel
control={<Checkbox />}
label='chest pain (Semi-urgent)'
checked={formik.values.chestPain}
onChange={() => formik.setFieldValue('chestPain', !formik.values.chestPain)}
/>
<FormControlLabel
control={<Checkbox />}
label='Bruises, scratches, minor burns (Semi-urgent)'
checked={formik.values.bruisesScratchesMinorBurns}
onChange={() =>
formik.setFieldValue('bruisesScratchesMinorBurns', !formik.values.bruisesScratchesMinorBurns)
}
/>
</FormGroup>
</Box>
<Box sx={{ marginTop: '1rem', display: 'flex', justifyContent: 'space-around' }}>
<Button
disabled={formik.isSubmitting}
onClick={() => router.push('/')}
variant='outline'
startIcon={<ChevronLeftOutlined />}
>
Cancel
</Button>
{/* */}
<Button
color='primary'
variant='contained'
type='submit'
disabled={!(formik.isValid && formik.dirty && !formik.isSubmitting)}
>
Submit
</Button>
</Box>
</Box>
</form>
</Box>
<Drawer open={open} onClose={toggleDrawer(false)}>
{DrawerList}
</Drawer>
</>
);
};

View File

@@ -0,0 +1,21 @@
import { Box } from '@mui/material';
export default () => (
<Box
sx={{
width: '100%',
height: '30vh',
//
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: '3rem',
//
fontWeight: 'bold',
fontSize: '1.2rem',
}}
>
<Box>No result found</Box>
</Box>
);

View File

@@ -0,0 +1,21 @@
import { Box } from '@mui/material';
export default () => (
<Box
sx={{
width: '100%',
height: '30vh',
//
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: '3rem',
//
fontWeight: 'bold',
fontSize: '1.2rem',
}}
>
<Box>Press Search to start</Box>
</Box>
);

View File

@@ -0,0 +1,256 @@
import MenuIcon from '@mui/icons-material/Menu';
import React, { useState } from 'react';
import { ChevronLeftOutlined } from '@mui/icons-material';
import {
Box,
Button,
Checkbox,
Drawer,
FormControlLabel,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
TextField,
} from '@mui/material';
import NonUrgentQueueItemCard from 'components/NonUrgentQueueItemCard';
import SemiUrgentQueueItemCard from 'components/SemiUrgentQueueItemCard';
import { useFormik } from 'formik';
import { useRouter } from 'next/dist/client/router';
import Head from 'next/head';
import * as yup from 'yup';
import NoResultFound from './NoResultFound';
import PressSearchToStart from './PressSearchToStart';
const default_init_values = {
semi_urgent_case: true,
non_urgent_case: true,
hkid: '',
mobile: '',
};
const validationSchema = yup.object({});
const search_input_row_sx = {
width: '100%',
padding: '1rem',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
gap: '0.25rem',
fontWeight: 'bold',
};
const row_button_sx = {
width: '100%',
padding: '1rem',
display: 'flex',
justifyContent: 'space-around',
gap: '0.5rem',
};
export default () => {
const [open, setOpen] = React.useState(false);
const [p_queue, setPQueue] = React.useState([]);
const router = useRouter();
const [not_search_yet, setNotSearchYet] = React.useState(true);
const [changing_page, setChangingPage] = useState(false);
const toggleDrawer = newOpen => () => {
setOpen(newOpen);
};
const formik = useFormik({
initialValues: default_init_values,
validationSchema,
onSubmit: values => {
setNotSearchYet(false);
fetch('/api/patient_queue/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
})
.then(res => res.json())
.then(data => {
setPQueue(data.patient_queues);
formik.setSubmitting(false);
})
.catch(err => {
console.error(err);
alert('server error');
});
},
});
const DrawerList = (
<Box sx={{ width: 250 }} role='presentation' onClick={toggleDrawer(false)}>
<List>
<ListItem key='dashboard' disablePadding>
<ListItemButton
disabled={changing_page}
onClick={() => {
setChangingPage(true);
router.push('/AdminHome');
}}
>
<ListItemIcon>
<ChevronLeftOutlined />
</ListItemIcon>
<ListItemText primary='back to dashboard' />
</ListItemButton>
</ListItem>
</List>
</Box>
);
return (
<>
<Head>
<title>dashboard - non-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box className='main'>
<Box
sx={{
//
display: 'flex',
flexDirection: 'flex-start',
alignItems: 'center',
gap: '1rem',
//
height: '3rem',
}}
>
<IconButton aria-label='menu' onClick={toggleDrawer(true)}>
<MenuIcon />
</IconButton>
<Box sx={{ fontWeight: 'bold', fontSize: '1.2rem' }}>Search case</Box>
</Box>
<Box>
<form onSubmit={formik.handleSubmit}>
<Box sx={search_input_row_sx}>
<Box>Search by case type:</Box>
<FormControlLabel
control={<Checkbox sx={{ padding: '0.25rem 1rem' }} size='small' />}
label='semi-urgent case'
checked={formik.values.semi_urgent_case}
onChange={() => formik.setFieldValue('semi_urgent_case', !formik.values.semi_urgent_case)}
/>
<FormControlLabel
control={<Checkbox sx={{ padding: '0.25rem 1rem' }} size='small' />}
label='non-urgent case'
checked={formik.values.non_urgent_case}
onChange={() => formik.setFieldValue('non_urgent_case', !formik.values.non_urgent_case)}
/>
</Box>
<Box sx={search_input_row_sx}>
<TextField
fullWidth
variant='standard'
id='hkid'
name='hkid'
label='Search by HKID: Please input HKID'
size='small'
value={formik.values.hkid}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.hkid && Boolean(formik.errors.hkid)}
helperText={formik.touched.hkid && formik.errors.hkid}
/>
</Box>
<Box sx={search_input_row_sx}>
<TextField
fullWidth
variant='standard'
id='mobile'
name='mobile'
label='Search by Mobile: Please input no.'
size='small'
value={formik.values.mobile}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.mobile && Boolean(formik.errors.mobile)}
helperText={formik.touched.mobile && formik.errors.mobile}
/>
</Box>
<Box sx={row_button_sx}>
<Button
disabled={!formik.dirty || formik.isSubmitting}
fullWidth
variant='outlined'
onClick={() => {
setNotSearchYet(true);
setPQueue([]);
formik.resetForm();
}}
>
Reset
</Button>
<Button
// disabled={!(formik.isValid && formik.dirty && !formik.isSubmitting) || changing_page}
fullWidth
variant='contained'
color='primary'
type='submit'
disabled={!(formik.isValid && formik.dirty && !formik.isSubmitting)}
>
Search
</Button>
</Box>
</form>
</Box>
<Box style={{ overflowY: 'scroll' }}>
{not_search_yet ? (
<PressSearchToStart />
) : (
<>
{p_queue.length === 0 ? (
<NoResultFound />
) : (
<>
{p_queue.map(queue_data => {
if (queue_data.queue_type === 'non-urgent')
return (
<NonUrgentQueueItemCard key={queue_data} queue_data={queue_data} show_edit_delete={false} />
);
if (queue_data.queue_type === 'semi-urgent')
return (
<SemiUrgentQueueItemCard key={queue_data} queue_data={queue_data} show_edit_delete={false} />
);
return <></>;
})}
<Box
style={{
width: '100%',
display: 'inline-flex',
justifyContent: 'center',
marginTop: '1rem',
marginBottom: '1rem',
}}
>
--end of list--
</Box>
</>
)}
</>
)}
</Box>
</Box>
<Drawer open={open} onClose={toggleDrawer(false)}>
{DrawerList}
</Drawer>
</>
);
};

View File

@@ -0,0 +1,246 @@
import React, { useEffect } from 'react';
import { Box, Button, TextField } from '@mui/material';
import Head from 'next/head';
import { ChevronLeftOutlined } from '@mui/icons-material';
import Loading from 'components/Loading';
import { useFormik } from 'formik';
import { useRouter } from 'next/dist/client/router';
import * as yup from 'yup';
const validationSchema = yup.object({
patient_name: yup
.string('Enter your name')
.min(2, 'Name should be of minimum 2 characters length')
.required('Name is required'),
patient_hkid: yup
.string('Enter your HKID')
.matches(/^[A-Z]{1,2}[0-9]{6}\([0-9]\)$/, 'HKID should be in format of XX123456(1-9)')
.required('HKID is required'),
patient_age: yup
.number('Enter your age')
.min(1, 'Age should be greater than 0')
.max(90, 'Age should be less than 90')
.required('Age is required'),
patient_mobile: yup
.string('Enter your mobile number')
.matches(/^[0-9]{8}$/, 'Mobile number should be 8 numbers')
.required('Mobile number is required'),
});
export default () => {
const router = useRouter();
const { id } = router.query;
const [changing_page, setChangingPage] = React.useState(false);
const [loading, setLoading] = React.useState(true);
const formik = useFormik({
enableReinitialize: true,
initialValues: {
patient_name: '',
patient_hkid: '',
patient_age: 0,
patient_mobile: '',
//
bruisesScratchesMinorBurns: false,
chestPain: false,
headache: false,
myMuiCheck: false,
nauseaAndVomiting: false,
runnyOrStuffyNose: false,
soreThroat: false,
},
validationSchema,
onSubmit: values => {
fetch('/api/patient_queue/update_queue_item', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, queue_type: 'semi-urgent', values }),
})
.then(response => response.json())
.then(data => {
if (data.status) {
alert('saved !');
router.reload();
} else {
// alert(data.message);
alert('sorry there are something wrong, please try again');
}
})
.catch(err => {
console.error(err);
alert('server error');
});
},
});
useEffect(() => {
console.log({ t: `/api/patient_queue/read_queue_item?queue_type=semi-urgent&id=${id}` });
if (id) {
fetch(`/api/patient_queue/read_queue_item?queue_type=semi-urgent&id=${id}`, {
method: 'GET',
})
.then(response => response.json())
.then(data => {
if (data.status) {
console.log(data);
const {
name,
hkid,
mobile,
age,
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
} = data.queueItem;
formik.setValues({
patient_name: name,
patient_hkid: hkid,
patient_mobile: mobile,
patient_age: age,
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
});
} else {
alert(data.message);
}
setLoading(false);
})
.catch(err => {
console.error(err);
alert('server error');
})
.finally(() => {
setLoading(false);
});
}
}, [id]);
if (loading) {
return <Loading />;
}
return (
<>
<Head>
<title>dashboard - non-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '90vh',
}}
>
<form onSubmit={formik.handleSubmit}>
<Box sx={{ marginLeft: '2rem', marginRight: '2rem' }}>
<Box
sx={{
fontWeight: 'bold',
fontSize: '1.1rem',
marginTop: '1rem',
marginBottom: '1rem',
}}
>
Edit Registration
</Box>
<TextField
fullWidth
disabled={changing_page || formik.isSubmitting}
variant='standard'
id='patient_name'
name='patient_name'
label='Patient name'
value={formik.values.patient_name}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_name && Boolean(formik.errors.patient_name)}
helperText={formik.touched.patient_name && formik.errors.patient_name}
/>
<TextField
fullWidth
disabled={changing_page || formik.isSubmitting}
variant='standard'
id='patient_hkid'
name='patient_hkid'
label='HKID'
value={formik.values.patient_hkid}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_hkid && Boolean(formik.errors.patient_hkid)}
helperText={formik.touched.patient_hkid && formik.errors.patient_hkid}
/>
<TextField
fullWidth
disabled={changing_page || formik.isSubmitting}
variant='standard'
id='patient_age'
name='patient_age'
label='Age'
value={formik.values.patient_age}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_age && Boolean(formik.errors.patient_age)}
helperText={formik.touched.patient_age && formik.errors.patient_age}
/>
<TextField
fullWidth
disabled={changing_page || formik.isSubmitting}
variant='standard'
id='patient_mobile'
name='patient_mobile'
label='Mobile'
value={formik.values.patient_mobile}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.patient_mobile && Boolean(formik.errors.patient_mobile)}
helperText={formik.touched.patient_mobile && formik.errors.patient_mobile}
/>
<Box sx={{ marginTop: '1rem', display: 'flex', justifyContent: 'space-around' }}>
<Button
disabled={changing_page || formik.isSubmitting}
onClick={() => {
setChangingPage(true);
router.push('/SemiUrgentCaseList');
}}
variant='outline'
startIcon={<ChevronLeftOutlined />}
>
Back
</Button>
{/* */}
<Button
color='primary'
variant='contained'
type='submit'
disabled={!(formik.isValid && formik.dirty && !formik.isSubmitting) || changing_page}
>
Save
</Button>
</Box>
</Box>
</form>
</Box>
</>
);
};

View File

@@ -0,0 +1,40 @@
import { ChevronLeftOutlined } from '@mui/icons-material';
import { Box, Button } from '@mui/material';
import { useRouter } from 'next/dist/client/router';
import { useState } from 'react';
export default () => {
const [changing_page, setChangingPage] = useState(false);
const router = useRouter();
return (
<Box
sx={{
width: '100%',
height: '90vh',
//
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: '3rem',
//
fontWeight: 'bold',
fontSize: '1.2rem',
}}
>
<Box>Queue is empty</Box>
<Button
onClick={() => {
setChangingPage(true);
router.push('/AdminHome');
}}
startIcon={<ChevronLeftOutlined />}
variant='outlined'
disabled={changing_page}
>
Back
</Button>
</Box>
);
};

View File

@@ -0,0 +1,112 @@
import React, { useEffect, useState } from 'react';
import { ChevronLeftOutlined } from '@mui/icons-material';
import MenuIcon from '@mui/icons-material/Menu';
import { Box, Drawer, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import Loading from 'components/Loading';
import SemiUrgentQueueItemCard from 'components/SemiUrgentQueueItemCard';
import { useRouter } from 'next/dist/client/router';
import Head from 'next/head';
import QueueIsEmpty from './QueueIsEmpty';
export default () => {
const [open, setOpen] = React.useState(false);
const [p_queue, setPQueue] = React.useState([]);
const router = useRouter();
const [loading, setLoading] = React.useState(true);
const [changing_page, setChangingPage] = useState(false);
const toggleDrawer = newOpen => () => {
setOpen(newOpen);
};
useEffect(() => {
fetch('/api/patient_queue/semi_urgent_case')
.then(response => response.json())
.then(data => {
setPQueue(data.patient_queues);
setLoading(false);
});
}, []);
const DrawerList = (
<Box sx={{ width: 250 }} role='presentation' onClick={toggleDrawer(false)}>
<List>
<ListItem key='dashboard' disablePadding>
<ListItemButton
disabled={changing_page}
onClick={() => {
setChangingPage(true);
router.push('/AdminHome');
}}
>
<ListItemIcon>
<ChevronLeftOutlined />
</ListItemIcon>
<ListItemText primary='back to dashboard' />
</ListItemButton>
</ListItem>
</List>
</Box>
);
if (loading) {
return <Loading />;
}
return (
<>
<Head>
<title>dashboard - semi-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box className='main'>
<Box
sx={{
//
display: 'flex',
flexDirection: 'flex-start',
alignItems: 'center',
gap: '1rem',
//
height: '3rem',
}}
>
<IconButton aria-label='menu' onClick={toggleDrawer(true)}>
<MenuIcon />
</IconButton>
<Box sx={{ fontWeight: 'bold', fontSize: '1.2rem' }}>All Semi-Urgent Cases</Box>
</Box>
<Box style={{ height: '95vh', overflowY: 'scroll' }}>
{p_queue.length === 0 ? (
<QueueIsEmpty />
) : (
<>
{p_queue.map(queue_data => (
<SemiUrgentQueueItemCard key={queue_data} queue_data={queue_data} />
))}
<Box
style={{
width: '100%',
display: 'inline-flex',
justifyContent: 'center',
marginTop: '1rem',
marginBottom: '1rem',
}}
>
--end of list--
</Box>
</>
)}
</Box>
</Box>
<Drawer open={open} onClose={toggleDrawer(false)}>
{DrawerList}
</Drawer>
</>
);
};

View File

@@ -0,0 +1,18 @@
import ThemeProvider from '@/config/StyledMaterialThemeProvider';
import theme from '@/config/theme';
import Head from 'next/head';
function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<meta name='viewport' content='minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no' />
</Head>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</>
);
}
export default MyApp;

View File

@@ -0,0 +1,98 @@
import { ServerStyleSheets } from '@mui/styles';
import Document, { Head, Html, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
import theme from '@/config/theme';
export default class MyDocument extends Document {
componentDidMount() {}
render() {
return (
<Html lang='en'>
<Head>
<meta charSet='utf-8' />
{/* PWA primary color */}
<meta name='theme-color' content={theme.palette.primary.main} />
</Head>
<style jsx global>
{`
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fica Sans,
Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
`}
</style>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async ctx => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(sheets.collect(<App {...props} />)),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: (
<>
{initialProps.styles}
{sheets.getStyleElement()}
{sheet.getStyleElement()}
{/* {flush() || null} */}
</>
),
};
} finally {
sheet.seal();
}
};

View File

@@ -0,0 +1,21 @@
async function helloworld(req) {
try {
return { status: 'OK' };
} catch (error) {
console.error(error);
return { status: 'ERROR' };
}
}
async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default handler;

View File

@@ -0,0 +1,33 @@
import Auth from './model';
async function login(req) {
try {
console.log({ test: req.body });
const { username: incoming_username, password: incoming_password } = req.body;
const users = await Auth.findAll();
for (let i = 0; i < users.length; i += 1) {
const user = users[i];
console.log(users);
if (user.username === incoming_username && user.password === incoming_password) {
return { success: 'login success' };
}
}
return { message: 'login failed' };
} catch (error) {
console.error(error);
return { message: 'login failed' };
}
}
async function handler(req, res) {
try {
const result = await login(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default handler;

View File

@@ -0,0 +1,17 @@
import sequelize_config from 'utils/sequelize_config';
const { DataTypes } = require('sequelize');
const Auth = sequelize_config.define(
'Auths',
{
uid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
username: { type: DataTypes.STRING, allowNull: false },
password: { type: DataTypes.STRING, allowNull: false },
session: { type: DataTypes.STRING, allowNull: false, defaultValue: '' },
role: { type: DataTypes.STRING, allowNull: false },
},
{ timestamps: false },
);
export default Auth;

View File

@@ -0,0 +1,20 @@
async function helloworld(req) {
try {
return { status: 'OK' };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
let result = await helloworld(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(200).send({ status: 'error', message: 'helloworld error' });
}
}
export default handler;

View File

@@ -0,0 +1,39 @@
import { NonUrgentQueue, SemiUrgentQueue } from './model';
async function deleteQueueById(req, res) {
try {
const { id } = req.query;
const { queue_type } = req.query;
if (!id || !queue_type) {
return res.status(400).send({ status: 'error', message: 'id and queue_type are required' });
}
switch (queue_type) {
case 'semi-urgent':
await SemiUrgentQueue.destroy({ where: { id } });
return { status: 'OK' };
case 'non-urgent':
await NonUrgentQueue.destroy({ where: { id } });
return { status: 'OK' };
default:
return res.status(400).send({ status: 'error', message: 'invalid queue_type' });
}
} catch (error) {
console.error(error);
return { status: 'error' };
}
}
async function handler(req, res) {
try {
const result = await deleteQueueById(req, res);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'list error' });
}
}
export default handler;

View File

@@ -0,0 +1,25 @@
import { PatientQueue } from './model';
async function list() {
try {
const patient_queues = await PatientQueue.findAll();
return { status: 'OK', patient_queues };
} catch (error) {
console.error(error);
return { status: 'ERROR' };
}
}
async function handler(req, res) {
try {
const result = await list(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'list error' });
}
}
export default handler;

View File

@@ -0,0 +1,55 @@
const { DataTypes } = require('sequelize');
const sequelize_config = require('../../../utils/sequelize_config');
const PatientQueue = sequelize_config.define(
'PatientQueue',
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
description: { type: DataTypes.STRING, allowNull: false },
},
{ timestamps: false },
);
const SemiUrgentQueue = sequelize_config.define(
'SemiUrgentQueue',
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
name: { type: DataTypes.STRING, allowNull: false },
hkid: { type: DataTypes.STRING, allowNull: false },
mobile: { type: DataTypes.STRING, allowNull: false },
age: { type: DataTypes.INTEGER, allowNull: false },
description: { type: DataTypes.STRING, allowNull: false },
//
bruisesScratchesMinorBurns: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
chestPain: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
headache: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
myMuiCheck: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
nauseaAndVomiting: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
runnyOrStuffyNose: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
soreThroat: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
},
{ timestamps: false },
);
const NonUrgentQueue = sequelize_config.define(
'NonUrgentQueue',
{
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, unique: true },
name: { type: DataTypes.STRING, allowNull: false },
hkid: { type: DataTypes.STRING, allowNull: false },
mobile: { type: DataTypes.STRING, allowNull: false },
age: { type: DataTypes.INTEGER, allowNull: false },
description: { type: DataTypes.STRING, allowNull: false },
//
bruisesScratchesMinorBurns: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
chestPain: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
headache: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
myMuiCheck: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
nauseaAndVomiting: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
runnyOrStuffyNose: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
soreThroat: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
},
{ timestamps: false },
);
export { NonUrgentQueue, PatientQueue, SemiUrgentQueue };

View File

@@ -0,0 +1,24 @@
import { NonUrgentQueue } from './model';
async function list() {
try {
const patient_queues = await NonUrgentQueue.findAll();
return { status: 'OK', patient_queues };
} catch (error) {
console.error(error);
}
}
async function handler(req, res) {
try {
const result = await list(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'list error' });
}
}
export default handler;

View File

@@ -0,0 +1,46 @@
import { NonUrgentQueue, SemiUrgentQueue } from './model';
async function readQueueById(req, res) {
try {
const { id } = req.query;
const { queue_type } = req.query;
if (!id || !queue_type) {
return res.status(400).send({ status: 'error', message: 'id and queue_type are required' });
}
let queueItem;
switch (queue_type) {
case 'semi-urgent':
queueItem = await SemiUrgentQueue.findOne({ where: { id } });
break;
case 'non-urgent':
queueItem = await NonUrgentQueue.findOne({ where: { id } });
break;
default:
return res.status(400).send({ status: 'error', message: 'invalid queue_type' });
}
if (!queueItem) {
return res.status(404).send({ status: 'error', message: 'No queue item found' });
}
return { status: 'OK', queueItem };
} catch (error) {
console.error(error);
return { status: 'error' };
}
}
async function handler(req, res) {
try {
const result = await readQueueById(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'list error' });
}
}
export default handler;

View File

@@ -0,0 +1,77 @@
import { NonUrgentQueue, SemiUrgentQueue } from './model';
async function list(req) {
let output = {};
const {
patient_name: name,
patient_hkid: hkid,
patient_age: age,
patient_mobile: mobile,
//
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
} = req.body.values;
try {
if (headache || chestPain || bruisesScratchesMinorBurns) {
console.log('headache || chestPain || bruisesScratchesMinorBurns, classified to a SemiUrgentQueue');
const result = await SemiUrgentQueue.create({
name,
hkid,
mobile,
age,
description: '',
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
});
output = { success: 'OK', queue: 'semi-urgent', result };
} else {
console.log('headache == false, classified to a NonUrgentQueue');
const result = await NonUrgentQueue.create({
name,
hkid,
mobile,
age,
description: '',
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
});
output = { success: 'OK', queue: 'non-urgent', result };
}
} catch (error) {
output = { message: 'error' };
}
return output;
}
async function handler(req, res) {
try {
const result = await list(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'list error' });
}
}
export default handler;

View File

@@ -0,0 +1,51 @@
import { Op, Sequelize } from 'sequelize';
import { NonUrgentQueue, SemiUrgentQueue } from './model';
async function search_queue(req) {
try {
let n_u_result = [];
let s_u_result = [];
const { semi_urgent_case, non_urgent_case } = req.body;
const criteria = ['hkid', 'mobile']
.filter(key => req.body[key] !== '')
.map(key => ({
[key]: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col(key)), 'LIKE', `%${req.body[key].toLowerCase()}%`),
}));
if (non_urgent_case) {
n_u_result = await NonUrgentQueue.findAll({ where: { [Op.or]: criteria } });
// if (n_u_result === undefined) n_u_result = [];
n_u_result.forEach(q => {
q.dataValues.queue_type = 'non-urgent';
});
}
if (semi_urgent_case) {
s_u_result = await SemiUrgentQueue.findAll({ where: { [Op.or]: criteria } });
// if (s_u_result === undefined) s_u_result = [];
s_u_result.forEach(q => {
q.dataValues.queue_type = 'semi-urgent';
});
}
console.log({ s_u_result });
return { status: 'OK', patient_queues: [...n_u_result, ...s_u_result] };
} catch (error) {
console.error(error);
return { status: 'ERROR' };
}
}
async function handler(req, res) {
try {
const result = await search_queue(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'list error' });
}
}
export default handler;

View File

@@ -0,0 +1,25 @@
import { SemiUrgentQueue } from './model';
async function list() {
try {
const patient_queues = await SemiUrgentQueue.findAll();
return { status: 'OK', patient_queues };
} catch (error) {
console.error(error);
return { status: 'ERROR' };
}
}
async function handler(req, res) {
try {
const result = await list(req);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'list error' });
}
}
export default handler;

View File

@@ -0,0 +1,15 @@
fetch("http://localhost/api/patient_queue/read_queue_item?queue_type=non-urgent&id=4", {
"headers": {
"sec-ch-ua": "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"",
"sec-ch-ua-mobile": "?1",
"sec-ch-ua-platform": "\"Android\"",
"Referer": "http://localhost/NonUrgentCaseEdit/1",
"Referrer-Policy": "strict-origin-when-cross-origin"
},
"body": null,
"method": "GET"
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

View File

@@ -0,0 +1,23 @@
fetch("http://localhost/api/patient_queue/register", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9,zh-TW;q=0.8,zh-CN;q=0.7,zh;q=0.6",
"cache-control": "no-cache",
"content-type": "application/json",
"pragma": "no-cache",
"sec-ch-ua": "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"",
"sec-ch-ua-mobile": "?1",
"sec-ch-ua-platform": "\"Android\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"cookie": "pma_lang=en; phpMyAdmin=51ff7da1fa656cafa38dce7a6be8a79d",
"Referer": "http://localhost/PatientRegister",
"Referrer-Policy": "strict-origin-when-cross-origin"
},
"body": "{\"values\":{\"patient_name\":\"p1\",\"patient_hkid\":\"A123456(7)\",\"patient_age\":\"32\",\"patient_mobile\":\"91234567\",\"bruisesScratchesMinorBurns\":true,\"chestPain\":false,\"headache\":false,\"myMuiCheck\":false,\"nauseaAndVomiting\":false,\"runnyOrStuffyNose\":true,\"soreThroat\":false}}",
"method": "POST"
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

View File

@@ -0,0 +1,23 @@
fetch('http://localhost/api/patient_queue/search', {
headers: {
accept: '*/*',
'accept-language': 'en-US,en;q=0.9,zh-TW;q=0.8,zh-CN;q=0.7,zh;q=0.6',
'cache-control': 'no-cache',
'content-type': 'application/json',
pragma: 'no-cache',
'sec-ch-ua': '"Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
'sec-ch-ua-mobile': '?1',
'sec-ch-ua-platform': '"Android"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
cookie: 'pma_lang=en',
Referer: 'http://localhost/SearchCase',
'Referrer-Policy': 'strict-origin-when-cross-origin',
},
body: '{"semi_urgent_case":true,"non_urgent_case":false,"hkid":"","mobile":"91234567"}',
method: 'POST',
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

View File

@@ -0,0 +1,21 @@
fetch("http://localhost/api/patient_queue/update_queue_item", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9,zh-TW;q=0.8,zh-CN;q=0.7,zh;q=0.6",
"content-type": "application/json",
"sec-ch-ua": "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"",
"sec-ch-ua-mobile": "?1",
"sec-ch-ua-platform": "\"Android\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"cookie": "pma_lang=en; phpMyAdmin=51ff7da1fa656cafa38dce7a6be8a79d",
"Referer": "http://localhost/NonUrgentCaseEdit/4",
"Referrer-Policy": "strict-origin-when-cross-origin"
},
"body": "{\"id\":\"4\",\"queue_type\":\"non-urgent\",\"values\":{\"patient_name\":\"non-urgent-patient 3 update\",\"patient_hkid\":\"A123456(3)\",\"patient_mobile\":\"91234563\",\"patient_age\":13}}",
"method": "POST"
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

View File

@@ -0,0 +1,89 @@
import { NonUrgentQueue, SemiUrgentQueue } from './model';
async function updateQueueById(req, res) {
try {
const { id, queue_type } = req.body;
console.log({ t: req.body });
if (!id || !queue_type) {
return res.status(400).send({ status: 'error', message: 'id and queue_type are required' });
}
const {
patient_name: name,
patient_hkid: hkid,
patient_age: age,
patient_mobile: mobile,
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
} = req.body.values;
switch (queue_type) {
case 'non-urgent':
await NonUrgentQueue.update(
{
name,
hkid,
age,
mobile,
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
},
{ where: { id } },
);
return { status: 'OK' };
case 'semi-urgent':
await SemiUrgentQueue.update(
{
name,
hkid,
age,
mobile,
bruisesScratchesMinorBurns,
chestPain,
headache,
myMuiCheck,
nauseaAndVomiting,
runnyOrStuffyNose,
soreThroat,
},
{ where: { id } },
);
return { status: 'OK' };
default:
return res.status(400).send({ status: 'error', message: 'invalid queue_type' });
}
} catch (error) {
console.error(error);
return { status: 'error' };
}
}
async function handler(req, res) {
if (req.method === 'POST') {
try {
const result = await updateQueueById(req, res);
return res.status(200).send(result);
} catch (err) {
console.log(err);
return res.status(200).send({ status: 'error', message: 'list error' });
}
} else {
return res.status(405).send({ status: 'error', message: 'method not allowed' });
}
}
export default handler;

View File

@@ -0,0 +1,104 @@
import React from 'react';
import MailIcon from '@mui/icons-material/Mail';
import InboxIcon from '@mui/icons-material/MoveToInbox';
import { Box, Divider, Drawer, Link, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import Head from 'next/head';
import { useFormik } from 'formik';
import * as yup from 'yup';
const validationSchema = yup.object({
// email: yup.string('Enter your email').email('Enter a valid email').required('Email is required'),
// password: yup
// .string('Enter your password')
// .min(8, 'Password should be of minimum 8 characters length')
// .required('Password is required'),
});
export default function Home() {
const [open, setOpen] = React.useState(false);
const toggleDrawer = newOpen => () => {
setOpen(newOpen);
};
const formik = useFormik({
initialValues: {
email: 'foobar@example.com',
password: 'foobar',
patient_name: 'default patient name',
patient_hkid: 'A213456(7)',
patient_age: 37,
patient_mobile: '91234567',
bruisesScratchesMinorBurns: false,
chestPain: false,
headache: false,
myMuiCheck: false,
nauseaAndVomiting: false,
runnyOrStuffyNose: false,
soreThroat: false,
},
validationSchema: validationSchema,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
const DrawerList = (
<Box sx={{ width: 250 }} role='presentation' onClick={toggleDrawer(false)}>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</Box>
);
return (
<>
<Head>
<title>dashboard - non-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '90vh',
}}
>
<Link href='/PatientRegister'>PatientRegister</Link>
<Link href='/PatientQueueDisplay'>PatientQueueDisplay</Link>
<Link href='/AdminLogin'>AdminLogin</Link>
<Link href='/AdminHome'>AdminHome</Link>
<Link href='/SemiUrgentCaseList'>SemiUrgentCaseList</Link>
<Link href='/NonUrgentCaseList'>NonUrgentCaseList</Link>
</Box>
<Drawer open={open} onClose={toggleDrawer(false)}>
{DrawerList}
</Drawer>
</>
);
}

View File

@@ -0,0 +1,5 @@
export default () => `
padding:0;
margin:0;
`;

View File

@@ -0,0 +1,55 @@
import { Box, Button } from '@mui/material';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { useState } from 'react';
export default () => {
const [changing_page, setChangingPage] = useState(false);
const router = useRouter();
return (
<>
<Head>
<title>dashboard - non-urgent case</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '90vh',
}}
>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<Box sx={{ fontWeight: 'bold', fontSize: '1.2rem' }}>Demo Patient Queue system</Box>
<Button
onClick={() => {
setChangingPage(true);
router.push('/PatientLanding');
}}
variant='contained'
disableElevation
disabled={changing_page}
>
I am patient
</Button>
<Button
onClick={() => {
setChangingPage(true);
router.push('/AdminLogin');
}}
variant='contained'
disableElevation
disabled={changing_page}
>
I am admin
</Button>
</Box>
</Box>
</>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
export default process.env.NODE_ENV === 'development';

View File

@@ -0,0 +1,38 @@
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize('app_db', 'db_user', 'db_user_pass', {
host: 'mysql',
port: 3306,
dialect: 'mysql',
});
const User = sequelize.define(
'User',
{
firstName: { type: Sequelize.STRING, allowNull: false },
lastName: { type: Sequelize.STRING, allowNull: false },
email: { type: Sequelize.STRING, allowNull: false },
role: { type: Sequelize.STRING, allowNull: false },
},
{ timestamps: false },
);
(async () => {
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
// create table
await sequelize.sync();
const user = await User.create({ firstName: 'John', lastName: 'Doe' });
console.log('Created user: ', user);
const users = await User.findAll();
console.log('Found all users: ', users);
await sequelize.close();
} catch (error) {
console.error('Unable to connect to the database:', error);
}
})();

View File

@@ -0,0 +1,9 @@
const { Sequelize } = require('sequelize');
const sequelize_config = new Sequelize('app_db', 'db_user', 'db_user_pass', {
host: 'mysql',
port: 3306,
dialect: 'mysql',
});
module.exports = sequelize_config;

File diff suppressed because it is too large Load Diff