update jamespong14205,

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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