update,
This commit is contained in:
2
jamespong14205/.gitignore
vendored
Normal file
2
jamespong14205/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
**/*draft*
|
||||
**/*copy*
|
11
jamespong14205/gitUpdate.sh
Executable file
11
jamespong14205/gitUpdate.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
git add .
|
||||
|
||||
git commit -m"update jamespong14205,"
|
||||
|
||||
git push
|
||||
|
||||
echo "done"
|
20
jamespong14205/meta.md
Normal file
20
jamespong14205/meta.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
tags: js, app, db, nextjs, login, registration
|
||||
---
|
||||
|
||||
# apps
|
||||
|
||||
### ver 1:
|
||||
|
||||
https://youtu.be/KOTr7nnr6uk
|
||||
|
||||
balance:
|
||||
|
||||
| item | amount |
|
||||
| ------------------- | ---------- |
|
||||
| quote | + HKD 2000 |
|
||||
| deposit received | - HKD 500 |
|
||||
| outstanding | HKD 1500 |
|
||||
| | |
|
||||
| vo (search page) | + HKD 350 |
|
||||
| current outstanding | HKD 1850 |
|
1
jamespong14205/task1/.gitignore
vendored
Normal file
1
jamespong14205/task1/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
**/volumes/*
|
33
jamespong14205/task1/README.md
Normal file
33
jamespong14205/task1/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# README
|
||||
|
||||
### known background requirement
|
||||
|
||||
1. align `.wslconfig` for below parameters
|
||||
|
||||
```
|
||||
[wsl2]
|
||||
memory=4G
|
||||
processors=4
|
||||
swap=16G
|
||||
```
|
||||
|
||||
### start development/demo server
|
||||
|
||||
```bash
|
||||
# backup
|
||||
$ node ./backup.js
|
||||
|
||||
# 1. start docker
|
||||
$ ./dc_up.sh
|
||||
|
||||
# 2.
|
||||
$ docker compose exec -it client bash
|
||||
|
||||
# 3. into client, spin up dev server
|
||||
$ yarn
|
||||
$ yarn demo
|
||||
|
||||
# 4. brows localhost -> http://localhost
|
||||
|
||||
|
||||
```
|
59
jamespong14205/task1/backup.js
Normal file
59
jamespong14205/task1/backup.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const execSync = require('child_process').execSync;
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Function to get all subdirectories of a given directory
|
||||
function getDirectories(srcPath, excludeDirs) {
|
||||
return fs
|
||||
.readdirSync(srcPath)
|
||||
.filter(file => !excludeDirs.includes(file) && fs.lstatSync(path.join(srcPath, file)).isDirectory())
|
||||
.map(name => path.join(srcPath, name));
|
||||
}
|
||||
|
||||
// Get current working directory
|
||||
const cwd = process.cwd();
|
||||
|
||||
// Path to app-head directory
|
||||
const appHeadDir = path.join(cwd, 'project');
|
||||
|
||||
// Check if app-head exists
|
||||
if (!fs.existsSync(appHeadDir)) {
|
||||
console.error(`Error: ${appHeadDir} does not exist.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Execute reset.bat scripts
|
||||
try {
|
||||
// execSync(`cmd /c "cd ${appHeadDir} && scripts\\reset.bat"`, { stdio: 'inherit' });
|
||||
} catch (err) {
|
||||
console.error(`Error executing reset.bat script: ${err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Define excluded directories
|
||||
const excludedDirs = ['.next', 'node_modules', '.git', 'volumes'];
|
||||
|
||||
// Copy app-head directory and its contents to a new directory with an increasing number suffix
|
||||
let maxNum = 0;
|
||||
const directories = getDirectories(cwd, excludedDirs);
|
||||
for (const dir of directories) {
|
||||
const match = dir.match(/^.+draft(\d+)$/);
|
||||
if (match) {
|
||||
const num = parseInt(match[1], 10);
|
||||
if (num > maxNum) {
|
||||
maxNum = num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var zerofilled = ('0000' + (maxNum + 1)).slice(-4);
|
||||
const targetDir = path.join(cwd, `draft${zerofilled}`);
|
||||
fs.mkdirSync(targetDir);
|
||||
|
||||
// Copy app-head directory and its contents to targetDir, excluding specified directories
|
||||
fs.cpSync(appHeadDir, targetDir, {
|
||||
filter: src => !excludedDirs.includes(path.basename(src)),
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
console.log(`Successfully copied ${appHeadDir} to ${targetDir}.`);
|
21
jamespong14205/task1/clear_node_modules.js
Normal file
21
jamespong14205/task1/clear_node_modules.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function removeNodeModules(dir) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === 'node_modules') {
|
||||
fs.rmSync(fullPath, { recursive: true });
|
||||
} else {
|
||||
removeNodeModules(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeNodeModules('.');
|
||||
console.log("All 'node_modules' directories have been removed.");
|
BIN
jamespong14205/task1/project/001_from_user/01.docx
Normal file
BIN
jamespong14205/task1/project/001_from_user/01.docx
Normal file
Binary file not shown.
5
jamespong14205/task1/project/001_from_user/notes.md
Normal file
5
jamespong14205/task1/project/001_from_user/notes.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Q: 同埋遲啲係咪會做埋 Admin 可以 modify 啲 case?🫡 delete ,modify 同 save 就 OK
|
||||
A: 你比一比 D 掣個擺位我我放番佢地出黎 : )
|
||||
|
||||
Q: 如果佢 Admin login 嗰度入錯咗 Account 係咪都會 show?
|
||||
A: 係 : )
|
29
jamespong14205/task1/project/002_notes/README.md
Normal file
29
jamespong14205/task1/project/002_notes/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# README
|
||||
|
||||
### known background requirement
|
||||
|
||||
1. align `.wslconfig` for below parameters
|
||||
|
||||
```
|
||||
[wsl2]
|
||||
memory=4G
|
||||
processors=4
|
||||
swap=16G
|
||||
```
|
||||
|
||||
### start development/demo server
|
||||
|
||||
```bash
|
||||
# 1. start docker
|
||||
$ ./dc_up.sh
|
||||
|
||||
# 2.
|
||||
$ docker compose exec -it client bash
|
||||
|
||||
# 3. into client, spin up dev server
|
||||
$ yarn demo
|
||||
|
||||
# 4. brows localhost -> http://localhost
|
||||
|
||||
|
||||
```
|
BIN
jamespong14205/task1/project/002_notes/design/admin_case_list.png
(Stored with Git LFS)
Normal file
BIN
jamespong14205/task1/project/002_notes/design/admin_case_list.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
jamespong14205/task1/project/002_notes/design/admin_edit.jpg
(Stored with Git LFS)
Normal file
BIN
jamespong14205/task1/project/002_notes/design/admin_edit.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
jamespong14205/task1/project/002_notes/design/admin_home.png
(Stored with Git LFS)
Normal file
BIN
jamespong14205/task1/project/002_notes/design/admin_home.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
jamespong14205/task1/project/002_notes/design/admin_login.png
(Stored with Git LFS)
Normal file
BIN
jamespong14205/task1/project/002_notes/design/admin_login.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
jamespong14205/task1/project/002_notes/design/client_queue.png
(Stored with Git LFS)
Normal file
BIN
jamespong14205/task1/project/002_notes/design/client_queue.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
jamespong14205/task1/project/002_notes/design/client_register.png
(Stored with Git LFS)
Normal file
BIN
jamespong14205/task1/project/002_notes/design/client_register.png
(Stored with Git LFS)
Normal file
Binary file not shown.
39
jamespong14205/task1/project/002_notes/design/index.md
Normal file
39
jamespong14205/task1/project/002_notes/design/index.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Digest
|
||||
|
||||
### stack
|
||||
|
||||
- app
|
||||
- db
|
||||
|
||||
### Registration form page (1page + 2 logic)
|
||||
|
||||

|
||||
|
||||
(Please add required fields checking for user didn't input data)
|
||||
|
||||
- If user select more than three option will pass to semi-urgent waiting list,
|
||||
- If user select advice include (nausea and vomiting) also pass to semi-urgent waiting list,
|
||||
- otherwise > non - urgent waiting list)
|
||||
|
||||
### Queue page (1 page + 2 logic)
|
||||
|
||||

|
||||
|
||||
- DIsplay user waiting number
|
||||
- waiting list type (semi-urgent /non-urgent)
|
||||
|
||||
### admin login page (1 page + 1 logic)
|
||||
|
||||

|
||||
|
||||
### Administrator page (1 page + 2 logic)
|
||||
|
||||

|
||||
|
||||
### case list view (2 pages + 2 logic)
|
||||
|
||||

|
||||
|
||||
- case can be modify
|
||||
- save
|
||||
- deleted
|
BIN
jamespong14205/task1/project/002_notes/design/search.jpg
(Stored with Git LFS)
Normal file
BIN
jamespong14205/task1/project/002_notes/design/search.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
node_modules
|
73
jamespong14205/task1/project/003_src/client/.eslintrc.js
Normal file
73
jamespong14205/task1/project/003_src/client/.eslintrc.js
Normal 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
jamespong14205/task1/project/003_src/client/.gitignore
vendored
Normal file
39
jamespong14205/task1/project/003_src/client/.gitignore
vendored
Normal 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
|
@@ -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
|
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
. "$(dirname "$0")/common.sh"
|
||||
|
||||
yarn lint-staged
|
18
jamespong14205/task1/project/003_src/client/.prettierignore
Normal file
18
jamespong14205/task1/project/003_src/client/.prettierignore
Normal 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
|
18
jamespong14205/task1/project/003_src/client/.prettierrc.js
Normal file
18
jamespong14205/task1/project/003_src/client/.prettierrc.js
Normal 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')],
|
||||
};
|
21
jamespong14205/task1/project/003_src/client/LICENCE.txt
Normal file
21
jamespong14205/task1/project/003_src/client/LICENCE.txt
Normal 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.
|
31
jamespong14205/task1/project/003_src/client/README.md
Normal file
31
jamespong14205/task1/project/003_src/client/README.md
Normal 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
|
@@ -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>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
);
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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;
|
37
jamespong14205/task1/project/003_src/client/config/theme.js
Normal file
37
jamespong14205/task1/project/003_src/client/config/theme.js
Normal 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;
|
47
jamespong14205/task1/project/003_src/client/db_seed/auth.js
Normal file
47
jamespong14205/task1/project/003_src/client/db_seed/auth.js
Normal 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);
|
||||
}
|
||||
})();
|
15
jamespong14205/task1/project/003_src/client/db_seed/model.js
Normal file
15
jamespong14205/task1/project/003_src/client/db_seed/model.js
Normal 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 };
|
@@ -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);
|
||||
}
|
||||
})();
|
@@ -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);
|
||||
}
|
||||
})();
|
@@ -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);
|
||||
}
|
||||
})();
|
11
jamespong14205/task1/project/003_src/client/demo.sh
Executable file
11
jamespong14205/task1/project/003_src/client/demo.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
rm -rf .next node_modules/*
|
||||
|
||||
yarn --dev
|
||||
|
||||
yarn build
|
||||
|
||||
yarn demo
|
9
jamespong14205/task1/project/003_src/client/entry.sh
Executable file
9
jamespong14205/task1/project/003_src/client/entry.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
yarn --dev
|
||||
|
||||
yarn seed
|
||||
|
||||
yarn dev
|
24
jamespong14205/task1/project/003_src/client/jsconfig.json
Normal file
24
jamespong14205/task1/project/003_src/client/jsconfig.json
Normal 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"]
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
reactStrictMode: true,
|
||||
};
|
69
jamespong14205/task1/project/003_src/client/package.json
Normal file
69
jamespong14205/task1/project/003_src/client/package.json
Normal 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"
|
||||
}
|
||||
}
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
);
|
@@ -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>
|
||||
);
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
18
jamespong14205/task1/project/003_src/client/pages/_app.js
Normal file
18
jamespong14205/task1/project/003_src/client/pages/_app.js
Normal 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;
|
@@ -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();
|
||||
}
|
||||
};
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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 };
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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));
|
||||
|
@@ -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));
|
@@ -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));
|
@@ -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));
|
@@ -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;
|
104
jamespong14205/task1/project/003_src/client/pages/debug/index.js
Normal file
104
jamespong14205/task1/project/003_src/client/pages/debug/index.js
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
export default () => `
|
||||
padding:0;
|
||||
margin:0;
|
||||
|
||||
`;
|
55
jamespong14205/task1/project/003_src/client/pages/index.js
Normal file
55
jamespong14205/task1/project/003_src/client/pages/index.js
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
BIN
jamespong14205/task1/project/003_src/client/public/favicon.ico
Normal file
BIN
jamespong14205/task1/project/003_src/client/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
@@ -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 |
@@ -0,0 +1 @@
|
||||
export default process.env.NODE_ENV === 'development';
|
38
jamespong14205/task1/project/003_src/client/utils/seed.js
Normal file
38
jamespong14205/task1/project/003_src/client/utils/seed.js
Normal 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);
|
||||
}
|
||||
})();
|
@@ -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;
|
5050
jamespong14205/task1/project/003_src/client/yarn.lock
Normal file
5050
jamespong14205/task1/project/003_src/client/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
13
jamespong14205/task1/project/003_src/dc_up.sh
Executable file
13
jamespong14205/task1/project/003_src/dc_up.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
docker compose pull
|
||||
docker compose build
|
||||
|
||||
docker compose kill
|
||||
docker compose down --volumes
|
||||
sudo rm -rf ./volumes/mysql
|
||||
|
||||
docker compose up -d
|
||||
|
50
jamespong14205/task1/project/003_src/docker-compose.yml
Normal file
50
jamespong14205/task1/project/003_src/docker-compose.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
# TODO: rename me
|
||||
name: jamespong14205
|
||||
|
||||
volumes:
|
||||
client_node_modules:
|
||||
|
||||
services:
|
||||
client:
|
||||
image: node:20-buster-slim
|
||||
restart: always
|
||||
volumes:
|
||||
- ./client:/usr/bin/app
|
||||
- client_node_modules:/usr/bin/app/node_modules
|
||||
command: sleep infinity
|
||||
working_dir: /usr/bin/app
|
||||
|
||||
ports:
|
||||
- 80:3000
|
||||
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
# container_name: db
|
||||
restart: always
|
||||
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: my_secret_password
|
||||
MYSQL_DATABASE: app_db
|
||||
MYSQL_USER: db_user
|
||||
MYSQL_PASSWORD: db_user_pass
|
||||
MYSQL_ROOT_HOST: "%"
|
||||
ports:
|
||||
- "6033:3306"
|
||||
volumes:
|
||||
- ./volumes/mysql:/var/lib/mysql
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin
|
||||
# container_name: pma
|
||||
restart: always
|
||||
links:
|
||||
- mysql
|
||||
environment:
|
||||
PMA_PORT: 3306
|
||||
PMA_ARBITRARY: 1
|
||||
#
|
||||
PMA_HOST: mysql
|
||||
PMA_USER: db_user
|
||||
PMA_PASSWORD: db_user_pass
|
||||
ports:
|
||||
- 8080:80
|
6
jamespong14205/task1/project/003_src/setup.sh
Executable file
6
jamespong14205/task1/project/003_src/setup.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
npx create-next-app@latest helloworld-js \
|
||||
--no-eslint --use-yarn --js --no-tailwind
|
11
jamespong14205/task1/quotation.md
Normal file
11
jamespong14205/task1/quotation.md
Normal file
@@ -0,0 +1,11 @@
|
||||
(1 page + 1 logic)
|
||||
(1 page + 2 logic)
|
||||
(2 pages + 2 logic)
|
||||
(1 page + 2 logic)
|
||||
(1 page + 2 logic)
|
||||
|
||||
6 page + 10 logic
|
||||
|
||||
HKD 2000
|
||||
|
||||
stack: ionic + node + windows
|
Reference in New Issue
Block a user