diff --git a/client/src/layouts/dashboard/config-navigation.jsx b/client/src/layouts/dashboard/config-navigation.jsx
index cf86a11..1c990b4 100644
--- a/client/src/layouts/dashboard/config-navigation.jsx
+++ b/client/src/layouts/dashboard/config-navigation.jsx
@@ -2,49 +2,56 @@ import SvgColor from 'src/components/svg-color';
// ----------------------------------------------------------------------
-const icon = (name) => ;
+const icon = (name) => (
+
+);
const navConfig = [
{
title: 'dashboard',
path: '/',
- icon: icon('ic_analytics')
+ icon: icon('ic_analytics'),
},
{
title: 'user',
path: '/user',
- icon: icon('ic_user')
+ icon: icon('ic_user'),
},
{
title: 'product',
path: '/products',
- icon: icon('ic_cart')
+ icon: icon('ic_cart'),
},
{
title: 'blog',
path: '/blog',
- icon: icon('ic_blog')
+ icon: icon('ic_blog'),
},
{
title: 'login',
path: '/login',
- icon: icon('ic_lock')
+ icon: icon('ic_lock'),
},
{
title: 'Register',
path: '/register',
- icon: icon('ic_lock')
+ icon: icon('ic_lock'),
},
{
title: 'Doctors',
path: '/doctors',
- icon: icon('ic_user')
+ icon: icon('ic_user'),
+ },
+ {
+ title: 'Patients',
+ path: '/patients',
+ icon: icon('ic_user'),
},
{
title: 'Not found',
path: '/404',
- icon: icon('ic_disabled')
- }
+ icon: icon('ic_disabled'),
+ },
];
export default navConfig;
diff --git a/client/src/pages/health-record.jsx b/client/src/pages/health-record.jsx
index 6631844..63cb859 100644
--- a/client/src/pages/health-record.jsx
+++ b/client/src/pages/health-record.jsx
@@ -1,17 +1,19 @@
import { Helmet } from 'react-helmet-async';
+import { useParams } from 'react-router-dom';
import { HealthRecordView } from 'src/sections/healthRecords';
// ----------------------------------------------------------------------
export default function HealthRecordPage() {
+ let { patientID } = useParams();
return (
<>
Health Records
-
+
>
);
}
diff --git a/client/src/pages/patients.jsx b/client/src/pages/patients.jsx
new file mode 100644
index 0000000..5ec6912
--- /dev/null
+++ b/client/src/pages/patients.jsx
@@ -0,0 +1,17 @@
+import { Helmet } from 'react-helmet-async';
+
+import { PatientView } from 'src/sections/patients/view';
+
+// ----------------------------------------------------------------------
+
+export default function PatientPage() {
+ return (
+ <>
+
+ Patients
+
+
+
+ >
+ );
+}
diff --git a/client/src/routes/sections.jsx b/client/src/routes/sections.jsx
index dbb8eb6..988dfb5 100644
--- a/client/src/routes/sections.jsx
+++ b/client/src/routes/sections.jsx
@@ -1,5 +1,5 @@
import { lazy, Suspense } from 'react';
-import { Outlet, Navigate, useRoutes } from 'react-router-dom';
+import { Outlet, Navigate, useRoutes, useParams } from 'react-router-dom';
import DashboardLayout from 'src/layouts/dashboard';
@@ -9,6 +9,7 @@ export const IndexPage = lazy(() => import('src/pages/app'));
export const BlogPage = lazy(() => import('src/pages/blog'));
export const UserPage = lazy(() => import('src/pages/user'));
export const DoctorsPage = lazy(() => import('src/pages/doctors'));
+export const PatientsPage = lazy(() => import('src/pages/patients'));
export const LoginPage = lazy(() => import('src/pages/login'));
export const RegisterPage = lazy(() => import('src/pages/register'));
export const ProductsPage = lazy(() => import('src/pages/products'));
@@ -34,6 +35,8 @@ export default function Router() {
{ path: 'products', element: },
{ path: 'blog', element: },
{ path: 'doctors', element: },
+ { path: 'patients', element: },
+ { path: 'health-record/:patientID', element: },
{ path: 'health-record', element: },
],
},
diff --git a/client/src/sections/healthRecords/health-record-view.jsx b/client/src/sections/healthRecords/health-record-view.jsx
index 1703e80..7bbb79f 100644
--- a/client/src/sections/healthRecords/health-record-view.jsx
+++ b/client/src/sections/healthRecords/health-record-view.jsx
@@ -5,18 +5,32 @@ import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
-import HealthRecordSummary from './health-record-summary';
+import { useUserContext } from 'src/contexts/userContext';
import { axiosInstance } from '../../utils/axiosInstance';
-export default function HealthRecordView() {
+import HealthRecordSummary from './health-record-summary';
+
+export default function HealthRecordView({ patientID }) {
+ console.log(patientID);
const [healthRecords, setHealthRecords] = useState([]);
const [newName, setNewName] = useState('');
const [newRecord, setNewRecord] = useState('');
+ // const {
+ // user: { role },
+ // } = useUserContext();
+ const user = localStorage.getItem('userRole');
+ console.log(user);
+
useEffect(() => {
const fetchHealthRecords = async () => {
try {
- const res = await axiosInstance.get(`/patients/6548e928d2f717cbd465119a/medicalhistory`);
+ let res;
+ if (user === 'Doctor') {
+ res = await axiosInstance.get(`/patients/${patientID}/medicalhistory`);
+ } else {
+ res = await axiosInstance.get(`/me/medicalhistory`);
+ }
setHealthRecords(res.data.result);
} catch (err) {
console.log(err);
@@ -27,7 +41,7 @@ export default function HealthRecordView() {
const handleSubmit = async () => {
try {
- await axiosInstance.post('/patients/6548e928d2f717cbd465119a/medicalhistory', {
+ await axiosInstance.post(`/patients/${patientID}/medicalhistory`, {
name: newName,
medicalRecord: newRecord,
});
@@ -55,29 +69,33 @@ export default function HealthRecordView() {
))}
-
- Add New Health Record
-
+ {user === 'Doctor' && (
+
+ Add New Health Record
+
+ )}
-
- setNewName(event.target.value)}
- sx={{ mb: 2 }}
- />
-
- setNewRecord(event.target.value)}
- sx={{ mb: 2 }}
- />
-
-
-
+ {user === 'Doctor' && (
+
+ setNewName(event.target.value)}
+ sx={{ mb: 2 }}
+ />
+
+ setNewRecord(event.target.value)}
+ sx={{ mb: 2 }}
+ />
+
+
+
+ )}
);
}
diff --git a/client/src/sections/login/login-view.jsx b/client/src/sections/login/login-view.jsx
index 0a7aa8c..9b7a420 100644
--- a/client/src/sections/login/login-view.jsx
+++ b/client/src/sections/login/login-view.jsx
@@ -36,7 +36,7 @@ export default function LoginView() {
const {
register,
handleSubmit,
- formState: { errors }
+ formState: { errors },
} = useForm();
const [loading, setLoading] = useState(false);
@@ -51,7 +51,9 @@ export default function LoginView() {
if (res.status == 200) {
const user = res.data.user;
- setUser({ name: user.name, role: user.role });
+ // setUser({ name: user.name, role: user.role });
+ localStorage.setItem('userRole', user.role);
+ localStorage.setItem('userName', user.name);
router.push(destination);
} else {
@@ -69,16 +71,16 @@ export default function LoginView() {
sx={{
...bgGradient({
color: alpha(theme.palette.background.default, 0.9),
- imgUrl: '/assets/background/overlay_4.jpg'
+ imgUrl: '/assets/background/overlay_4.jpg',
}),
- height: 1
+ height: 1,
}}
>
@@ -87,7 +89,7 @@ export default function LoginView() {
sx={{
p: 5,
width: 1,
- maxWidth: 420
+ maxWidth: 420,
}}
>
Sign in to Minimal
@@ -110,7 +112,7 @@ export default function LoginView() {
- )
+ ),
}}
/>
diff --git a/client/src/sections/patients/table-empty-rows.jsx b/client/src/sections/patients/table-empty-rows.jsx
new file mode 100644
index 0000000..ed44fe1
--- /dev/null
+++ b/client/src/sections/patients/table-empty-rows.jsx
@@ -0,0 +1,29 @@
+import PropTypes from 'prop-types';
+
+import TableRow from '@mui/material/TableRow';
+import TableCell from '@mui/material/TableCell';
+
+// ----------------------------------------------------------------------
+
+export default function TableEmptyRows({ emptyRows, height }) {
+ if (!emptyRows) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
+
+TableEmptyRows.propTypes = {
+ emptyRows: PropTypes.number,
+ height: PropTypes.number,
+};
diff --git a/client/src/sections/patients/table-no-data.jsx b/client/src/sections/patients/table-no-data.jsx
new file mode 100644
index 0000000..aedae4b
--- /dev/null
+++ b/client/src/sections/patients/table-no-data.jsx
@@ -0,0 +1,36 @@
+import PropTypes from 'prop-types';
+
+import Paper from '@mui/material/Paper';
+import TableRow from '@mui/material/TableRow';
+import TableCell from '@mui/material/TableCell';
+import Typography from '@mui/material/Typography';
+
+// ----------------------------------------------------------------------
+
+export default function TableNoData({ query }) {
+ return (
+
+
+
+
+ Not found
+
+
+
+ No results found for
+ "{query}".
+
Try checking for typos or using complete words.
+
+
+
+
+ );
+}
+
+TableNoData.propTypes = {
+ query: PropTypes.string,
+};
diff --git a/client/src/sections/patients/user-table-head.jsx b/client/src/sections/patients/user-table-head.jsx
new file mode 100644
index 0000000..cdb9a47
--- /dev/null
+++ b/client/src/sections/patients/user-table-head.jsx
@@ -0,0 +1,73 @@
+import PropTypes from 'prop-types';
+
+import Box from '@mui/material/Box';
+import TableRow from '@mui/material/TableRow';
+import Checkbox from '@mui/material/Checkbox';
+import TableHead from '@mui/material/TableHead';
+import TableCell from '@mui/material/TableCell';
+import TableSortLabel from '@mui/material/TableSortLabel';
+
+import { visuallyHidden } from './utils';
+
+// ----------------------------------------------------------------------
+
+export default function UserTableHead({
+ order,
+ orderBy,
+ rowCount,
+ headLabel,
+ numSelected,
+ onRequestSort,
+ onSelectAllClick,
+}) {
+ const onSort = (property) => (event) => {
+ onRequestSort(event, property);
+ };
+
+ return (
+
+
+
+ 0 && numSelected < rowCount}
+ checked={rowCount > 0 && numSelected === rowCount}
+ onChange={onSelectAllClick}
+ />
+
+
+ {headLabel.map((headCell) => (
+
+
+ {headCell.label}
+ {orderBy === headCell.id ? (
+
+ {order === 'desc' ? 'sorted descending' : 'sorted ascending'}
+
+ ) : null}
+
+
+ ))}
+
+
+ );
+}
+
+UserTableHead.propTypes = {
+ order: PropTypes.oneOf(['asc', 'desc']),
+ orderBy: PropTypes.string,
+ rowCount: PropTypes.number,
+ headLabel: PropTypes.array,
+ numSelected: PropTypes.number,
+ onRequestSort: PropTypes.func,
+ onSelectAllClick: PropTypes.func,
+};
diff --git a/client/src/sections/patients/user-table-row.jsx b/client/src/sections/patients/user-table-row.jsx
new file mode 100644
index 0000000..5ed0260
--- /dev/null
+++ b/client/src/sections/patients/user-table-row.jsx
@@ -0,0 +1,115 @@
+import { useState } from 'react';
+import PropTypes from 'prop-types';
+
+import Stack from '@mui/material/Stack';
+import Avatar from '@mui/material/Avatar';
+import Popover from '@mui/material/Popover';
+import TableRow from '@mui/material/TableRow';
+import Checkbox from '@mui/material/Checkbox';
+import MenuItem from '@mui/material/MenuItem';
+import TableCell from '@mui/material/TableCell';
+import Typography from '@mui/material/Typography';
+import IconButton from '@mui/material/IconButton';
+
+import Label from 'src/components/label';
+import Iconify from 'src/components/iconify';
+
+// ----------------------------------------------------------------------
+
+export default function UserTableRow({
+ id,
+ selected,
+ name,
+ avatarUrl,
+ phone,
+ email,
+ gender,
+ isVerified,
+ status,
+ handleClick,
+ viewProfile,
+ viewHealthRecords,
+}) {
+ const [open, setOpen] = useState(null);
+
+ const handleOpenMenu = (event) => {
+ setOpen(event.currentTarget);
+ };
+
+ const handleCloseMenu = () => {
+ setOpen(null);
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ {name}
+
+
+
+
+ {phone}
+
+ {email}
+
+ {gender}
+
+ {/* {isVerified ? 'Yes' : 'No'} */}
+
+ {/*
+
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+UserTableRow.propTypes = {
+ id: PropTypes.any,
+ avatarUrl: PropTypes.any,
+ phone: PropTypes.any,
+ handleClick: PropTypes.func,
+ viewProfile: PropTypes.func,
+ viewHealthRecords: PropTypes.func,
+ isVerified: PropTypes.any,
+ name: PropTypes.any,
+ email: PropTypes.any,
+ gender: PropTypes.any,
+ selected: PropTypes.any,
+ status: PropTypes.string,
+};
diff --git a/client/src/sections/patients/user-table-toolbar.jsx b/client/src/sections/patients/user-table-toolbar.jsx
new file mode 100644
index 0000000..169ce33
--- /dev/null
+++ b/client/src/sections/patients/user-table-toolbar.jsx
@@ -0,0 +1,69 @@
+import PropTypes from 'prop-types';
+
+import Tooltip from '@mui/material/Tooltip';
+import Toolbar from '@mui/material/Toolbar';
+import Typography from '@mui/material/Typography';
+import IconButton from '@mui/material/IconButton';
+import OutlinedInput from '@mui/material/OutlinedInput';
+import InputAdornment from '@mui/material/InputAdornment';
+
+import Iconify from 'src/components/iconify';
+
+// ----------------------------------------------------------------------
+
+export default function UserTableToolbar({ numSelected, filterName, onFilterName }) {
+ return (
+ theme.spacing(0, 1, 0, 3),
+ ...(numSelected > 0 && {
+ color: 'primary.main',
+ bgcolor: 'primary.lighter',
+ }),
+ }}
+ >
+ {numSelected > 0 ? (
+
+ {numSelected} selected
+
+ ) : (
+
+
+
+ }
+ />
+ )}
+
+ {numSelected > 0 ? (
+
+
+
+
+
+ ) : (
+
+
+
+
+
+ )}
+
+ );
+}
+
+UserTableToolbar.propTypes = {
+ numSelected: PropTypes.number,
+ filterName: PropTypes.string,
+ onFilterName: PropTypes.func,
+};
diff --git a/client/src/sections/patients/utils.js b/client/src/sections/patients/utils.js
new file mode 100644
index 0000000..18fe17c
--- /dev/null
+++ b/client/src/sections/patients/utils.js
@@ -0,0 +1,56 @@
+export const visuallyHidden = {
+ border: 0,
+ margin: -1,
+ padding: 0,
+ width: '1px',
+ height: '1px',
+ overflow: 'hidden',
+ position: 'absolute',
+ whiteSpace: 'nowrap',
+ clip: 'rect(0 0 0 0)',
+};
+
+export function emptyRows(page, rowsPerPage, arrayLength) {
+ return page ? Math.max(0, (1 + page) * rowsPerPage - arrayLength) : 0;
+}
+
+function descendingComparator(a, b, orderBy) {
+ if (a[orderBy] === null) {
+ return 1;
+ }
+ if (b[orderBy] === null) {
+ return -1;
+ }
+ if (b[orderBy] < a[orderBy]) {
+ return -1;
+ }
+ if (b[orderBy] > a[orderBy]) {
+ return 1;
+ }
+ return 0;
+}
+export function getComparator(order, orderBy) {
+ return order === 'desc'
+ ? (a, b) => descendingComparator(a, b, orderBy)
+ : (a, b) => -descendingComparator(a, b, orderBy);
+}
+
+export function applyFilter({ inputData, comparator, filterName }) {
+ const stabilizedThis = inputData.map((el, index) => [el, index]);
+
+ stabilizedThis.sort((a, b) => {
+ const order = comparator(a[0], b[0]);
+ if (order !== 0) return order;
+ return a[1] - b[1];
+ });
+
+ inputData = stabilizedThis.map((el) => el[0]);
+
+ if (filterName) {
+ inputData = inputData.filter(
+ (user) => user.name.toLowerCase().indexOf(filterName.toLowerCase()) !== -1
+ );
+ }
+
+ return inputData;
+}
diff --git a/client/src/sections/patients/view/index.js b/client/src/sections/patients/view/index.js
new file mode 100644
index 0000000..df2e262
--- /dev/null
+++ b/client/src/sections/patients/view/index.js
@@ -0,0 +1 @@
+export { default as PatientView } from './patient-view';
diff --git a/client/src/sections/patients/view/patient-view.jsx b/client/src/sections/patients/view/patient-view.jsx
new file mode 100644
index 0000000..9b4b002
--- /dev/null
+++ b/client/src/sections/patients/view/patient-view.jsx
@@ -0,0 +1,207 @@
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+import Card from '@mui/material/Card';
+import Stack from '@mui/material/Stack';
+import Table from '@mui/material/Table';
+import Button from '@mui/material/Button';
+import Container from '@mui/material/Container';
+import TableBody from '@mui/material/TableBody';
+import Typography from '@mui/material/Typography';
+import TableContainer from '@mui/material/TableContainer';
+import TablePagination from '@mui/material/TablePagination';
+
+import { axiosInstance } from '../../../utils/axiosInstance';
+
+import { users } from 'src/_mock/user';
+
+import Iconify from 'src/components/iconify';
+import Scrollbar from 'src/components/scrollbar';
+
+import TableNoData from '../table-no-data';
+import UserTableRow from '../user-table-row';
+import UserTableHead from '../user-table-head';
+import TableEmptyRows from '../table-empty-rows';
+import UserTableToolbar from '../user-table-toolbar';
+import { emptyRows, applyFilter, getComparator } from '../utils';
+
+// ----------------------------------------------------------------------
+
+export default function UserPage() {
+ const [patients, setPatients] = useState([]);
+
+ useEffect(() => {
+ const fetchMyPatients = async () => {
+ try {
+ const res = await axiosInstance.get(`/patients`);
+ setPatients(res.data.result);
+ } catch (err) {
+ console.log(err);
+ }
+ };
+ fetchMyPatients();
+ }, []);
+
+ //'/assets/images/avatars/avatar_1.jpg'
+ //console.log(users);
+
+ const [page, setPage] = useState(0);
+
+ const [order, setOrder] = useState('asc');
+
+ const [selected, setSelected] = useState([]);
+
+ const [orderBy, setOrderBy] = useState('name');
+
+ const [filterName, setFilterName] = useState('');
+
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+
+ const handleSort = (event, id) => {
+ const isAsc = orderBy === id && order === 'asc';
+ if (id !== '') {
+ setOrder(isAsc ? 'desc' : 'asc');
+ setOrderBy(id);
+ }
+ };
+
+ const handleSelectAllClick = (event) => {
+ if (event.target.checked) {
+ const newSelecteds = patients.map((n) => n.name);
+ setSelected(newSelecteds);
+ return;
+ }
+ setSelected([]);
+ };
+
+ const handleClick = (event, name) => {
+ const selectedIndex = selected.indexOf(name);
+ let newSelected = [];
+ if (selectedIndex === -1) {
+ newSelected = newSelected.concat(selected, name);
+ } else if (selectedIndex === 0) {
+ newSelected = newSelected.concat(selected.slice(1));
+ } else if (selectedIndex === selected.length - 1) {
+ newSelected = newSelected.concat(selected.slice(0, -1));
+ } else if (selectedIndex > 0) {
+ newSelected = newSelected.concat(
+ selected.slice(0, selectedIndex),
+ selected.slice(selectedIndex + 1)
+ );
+ }
+ setSelected(newSelected);
+ };
+
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setPage(0);
+ setRowsPerPage(parseInt(event.target.value, 10));
+ };
+
+ const handleFilterByName = (event) => {
+ setPage(0);
+ setFilterName(event.target.value);
+ };
+
+ const dataFiltered = applyFilter({
+ inputData: patients,
+ comparator: getComparator(order, orderBy),
+ filterName,
+ });
+
+ const navigate = useNavigate();
+
+ const handleViewHealthRecords = (patientID) => {
+ navigate(`/health-record/${patientID}`);
+ };
+
+ const notFound = !dataFiltered.length && !!filterName;
+
+ return (
+
+
+ Patients
+
+ }>
+ New User
+
+
+
+
+
+
+
+
+
+
+
+ {dataFiltered
+ .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
+ .map((row) => (
+ handleClick(event, row.name)}
+ viewProfile={() => console.log('view profile')}
+ viewHealthRecords={() => {
+ console.log('view health records' + row._id);
+ handleViewHealthRecords(row._id);
+ }}
+ />
+ ))}
+
+
+
+ {notFound && }
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/server/src/routes/user.route.ts b/server/src/routes/user.route.ts
index bb9f8d6..35a232e 100644
--- a/server/src/routes/user.route.ts
+++ b/server/src/routes/user.route.ts
@@ -9,7 +9,7 @@ const router = express.Router();
// Just for testing
router.use(isAuthenticated);
-router.use(isAuthorized('Admin'));
+// router.use(isAuthorized('Admin'));
router.delete('/:id', (req: Request, res: Response) => {
controller(res)(deleteUsers)({ _id: req.params.id });
diff --git a/server/src/services/doctor.service.ts b/server/src/services/doctor.service.ts
index 08b7366..25f973f 100644
--- a/server/src/services/doctor.service.ts
+++ b/server/src/services/doctor.service.ts
@@ -1,13 +1,13 @@
const { v4: uuidv4 } = require('uuid');
import { HttpError } from '../utils';
import StatusCodes from 'http-status-codes';
-import { User, Contract, Appointment, IPatient, IDoctor, Doctor, Request } from '../models';
+import { User, Contract, Appointment, IPatient, IDoctor, Doctor, Request, Patient } from '../models';
const getMyPatients = async (query: any) => {
- const appointments = await Appointment.find(query).distinct('patientID').select('patientID').populate('patientID');
- if (!appointments) return new HttpError(StatusCodes.NOT_FOUND, 'No patients with this doctor');
+ const patientIDs = await Appointment.find(query).distinct('patientID');
+ if (!patientIDs) return new HttpError(StatusCodes.NOT_FOUND, 'No patients with this doctor');
- const patients = appointments.map((appointment: any) => appointment.patientID);
+ const patients = await Patient.find({ _id: { $in: patientIDs } });
return {
status: StatusCodes.OK,