An online web-based system designed to revolutionize healthcare interactions for clinics, doctors, pharmacists, and patients. This virtual clinic and pharmacy management system streamlines and automates every aspect of the healthcare journey, providing a seamless experience for all stakeholders.
This project was developed for the GUC's CSEN704 Advanced Computer Lab. The lab is a project-based course that aims to teach students:
-Scrum and Agile methodologies -Software development best practices -Software development tools and techniques -Software development process -Software Testing -Latest backend and frontend technologies
And since Web Development is a crucial part in our current era, this course gave us the perfect opportunity to explore Node.js, React, CSS and JavaScript all in one go. It also pushed us to challenge ourselves since our main goal from the start was to provide the user with the ultimate online experience.
- All user requirements are met and fulfilled, but more testing is needed to ensure the best service and UX.
- This project is currently a work-in-progress.
- Unit tests will be added in the next roll-out.
- Feedback and error handling are currently in development for a better UI/UX experience.
-
We adhere to the standardized JavaScript coding style conventions to improve overall readability and maintainability.
-
Semicolons are used after each statement.
-
All functions are declared above the code that uses them.
-
We follow the MVC (Model View Controller) architecture for features.
-
CamelCase is used for variable naming to maintain consistency.
-
Codes are formatted in VS Code using the Alt + Shift + F command.
1:Register Patient: 2:Patient Information: 3:Patient Dashboard: 4:Pharmacist Information: 5:Admin Dashboard: 6:Register Pharmacist: 7:Pharmacist Dashboard: 8:Login: 9:Admin:
-
In Back-end
-
NodeJS
-
MongoDB
-
Mongoose
-
Express
-
Bcrypt
-
Body-parser
-
Cors
-
Dotenv
-
Fs
-
Html-pdf
-
Https
-
Jsonwebtoken
-
Nodejs-nodemailer-outlook
-
Nodemon
-
Validator
-
-
In Front-end
-
ReactJS
-
Material UI
-
Axios
-
Bcrypt
-
Dateformat
-
File-saver
-
Jsonwebtoken
-
Http
-
Firebase
-
Jwt-decode
-
-
As a Pharmacist:
- I can view medicine
- Add Medicine
- Edit Medicine
- I can change password
- Chat With patients
- Chat With Doctors From Clinic
- View Sales Report
- I can View My Wallet Balance
-
As a Patient:
- I can view available medicines
- I can View My Cart
- I can View Past Orders
- I can Change My Password
- I can Change My Address
- I can track My orders
- Chat With Pharmacists
- I can View My Wallet Balance
- I can View My Current Address
-
As an Adminstrator:
- I can View Medicine
- I can Add Another Admin
- I can View All Pharmacists
- I can Remove Any Pharmacist
- I can View All Patients
- I can Remove Any patient
- I can Change My Password
- I can Approve/Reject Pharmacists
-
As a Guest:
- I can Login
- I can Regester as patient or Pharmacist
- I can Morget My Massword
const createCart = async (req, res) => {
try {
const { name, price, description, details, stock, sales, picture, use, amount, patientid } = req.body;
// Check if a cart item with the same name and patientid already exists
const existingCartItem = await CartModel.findOne({ name: name, patientid: patientid });
if (existingCartItem) {
// If the item exists, update the amount by incrementing it by 1
existingCartItem.amount += 1;
await existingCartItem.save();
res.status(200).json({ message: 'Cart item updated successfully', cartItem: existingCartItem });
} else {
// If the item doesn't exist, create a new cart item
const cartItem = await CartModel.create({
name,
price,
description,
details,
stock,
sales,
picture,
use,
amount,
patientid,
});
res.status(201).json({ message: 'Cart item created successfully', cartItem });
}
} catch (error) {
console.error(error);
// Handle different types of errors and return appropriate responses
if (error.name === 'ValidationError') {
// Handle validation errors
const validationErrors = Object.values(error.errors).map((err) => err.message);
return res.status(400).json({ error: validationErrors });
}
// For other errors, return a generic 500 status
return res.status(500).json({ error: 'Internal Server Error' });
}
};
const getPharmacist = async (req, res) => {
try{
const filter={};
const all=await PharmacistModel.find(filter);
res.status(200).json(all);
}catch(error){
res.status(400).json({error:error.message});
}
}
const createCart = async (req, res) => {
try {
const { name, price, description, details, stock, sales, picture, use, amount, patientid } = req.body;
// Check if a cart item with the same name and patientid already exists
const existingCartItem = await CartModel.findOne({ name: name, patientid: patientid });
if (existingCartItem) {
// If the item exists, update the amount by incrementing it by 1
existingCartItem.amount += 1;
await existingCartItem.save();
res.status(200).json({ message: 'Cart item updated successfully', cartItem: existingCartItem });
} else {
// If the item doesn't exist, create a new cart item
const cartItem = await CartModel.create({
name,
price,
description,
details,
stock,
sales,
picture,
use,
amount,
patientid,
});
res.status(201).json({ message: 'Cart item created successfully', cartItem });
}
} catch (error) {
console.error(error);
// Handle different types of errors and return appropriate responses
if (error.name === 'ValidationError') {
// Handle validation errors
const validationErrors = Object.values(error.errors).map((err) => err.message);
return res.status(400).json({ error: validationErrors });
}
// For other errors, return a generic 500 status
return res.status(500).json({ error: 'Internal Server Error' });
}
};
import axios from 'axios';
import { format, addYears } from 'date-fns';
import { Link as RouterLink, useParams, useNavigate } from 'react-router-dom';
import HealthPackagesList from '../HealthPackagesList';
import { AuthContext } from '../App';
import HealthPackage from '../HealthPackage';
import FollowUpRequestIcon from '@mui/icons-material/ForwardToInbox';
import React, { useEffect, useState, useContext } from 'react';
import { ExitToApp, Accessibility, AddCircleOutline,EditAttributes,Grid,LocalHospital, VideoCall,Notifications, PersonSearch } from '@mui/icons-material';
import CloseIcon from '@mui/icons-material/Close';
import DeleteIcon from '@mui/icons-material/Delete';
import { AppBar, Typography, Button, Toolbar, Container,
List, ListItem, ListItemText, IconButton, Divider, Paper,Drawer} from '@mui/material';
import ExitToAppIcon from '@mui/icons-material/ExitToApp';
import NotificationsIcon from '@mui/icons-material/Notifications';
import './Button.css'
import PasswordIcon from '@mui/icons-material/Password';
import UpdateIcon from '@mui/icons-material/Update';
import MoreTimeIcon from '@mui/icons-material/MoreTime';
import FilterAltIcon from '@mui/icons-material/FilterAlt';
import GroupsIcon from '@mui/icons-material/Groups';
import PersonIcon from '@mui/icons-material/Person';
import Person from '@mui/icons-material/Person';
function DoctorDashboard() {
const { setIsAuthenticated } = useContext(AuthContext);
const {doctorID} = useParams();
const{userID}=useParams();
const [balance, setBalance] = useState(0);
const UpdateDoctorInfo = `/doctor-dashboard/${doctorID}/UpdateDoctorInfo`;
const Doctor_Patient_Lists = `/doctor-dashboard/${doctorID}/Doctor_Patient_Lists`;
const SearchPatientsbyname = `/doctor-dashboard/${doctorID}/SearchPatientsbyname`;
const FilterAppointments = `/doctor-dashboard/${doctorID}/FilterAppointments`;
const pathToViewAppointments = `/doctor-dashboard/${doctorID}/viewAppointments`;
const navigate = useNavigate();
const ChangeDoctorPassword = `/doctor-dashboard/${doctorID}/ChangeDoctorPasswordForm`;
const AddAvailableAppointments = `/doctor-dashboard/${doctorID}/AddAvailableAppointments`;
const VideoCall1 = `/doctor-dashboard/${doctorID}/video`;
const ViewFollowUpRequests = `/doctor/${doctorID}/follow-up-requests`;
const ViewPharmacists = `/doctor-dashboard/${doctorID}/viewPharmacists`;
const [notifications, setNotifications] = useState([]);
const [drawerOpen, setDrawerOpen] = useState(false);
const [openNotifications, setOpenNotifications] = useState(false);
const toggleDrawer = (open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setDrawerOpen(open);
};
const dismissNotification = async (notificationId, index) => {
try {
const response = await axios.delete(`http://localhost:8000/doctor/dismissAllNotifications/${doctorID}`);
setNotifications((prevNotifications) => prevNotifications.filter((_, i) => i !== index));
} catch (error) {
console.error('Error dismissing a notification:', error);
}
};
const dismissAllNotifications = async () => {
try {
const response = await axios.delete(`http://localhost:8000/doctor/dismissAllNotifications/${doctorID}`);
if (response.status === 200) {
// This should clear out all notifications from the state and update the UI
setNotifications([]);
console.log('All notifications dismissed successfully');
} else {
// Handle any other HTTP status codes as needed
console.error('Received non-200 status code when dismissing all notifications:', response.status);
}
} catch (error) {
// Handle errors such as network issues, server not reachable, etc.
console.error('Error dismissing all notifications:', error);
}
};
const handleNotificationToggle = () => {
setOpenNotifications(!openNotifications);
};
useEffect(() => {
const fetchNotifications = async () => {
try {
const response = await axios.get(`http://localhost:8000/doctor/getNotifications/${doctorID}`);
setNotifications(response.data.notifications);
} catch (error) {
console.error('Error fetching notifications:', error);
}
};
fetchNotifications();
}, [doctorID]);
useEffect(() => {
axios.get(`http://localhost:8000/doctor/getWalletBalance/${doctorID}`)
.then((res) => {
setBalance(res.data);
})
.catch(error => console.error(error));
}, []);
const handleLogout = () => {
axios.get('http://localhost:8000/logout')
.then((response) => {
if (response.data.success) {
localStorage.removeItem('userSession');
setIsAuthenticated(false);
navigate('/');
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(`${key}: ${value}`);
}
} else {
console.error('Logout failed:', response.data.message);
}
})
.catch((error) => {
console.error('Logout failed:', error);
});
};const NotificationSidebar = () => (
<Drawer anchor="right" open={openNotifications} onClose={() => setOpenNotifications(false)}>
<Paper style={{ width: 300, padding: '16px' }} elevation={3}>
<Typography variant="h6" style={{ textAlign: 'center' }}>Notifications</Typography>
<IconButton style={{ position: 'absolute', top: 0, right: 0 }} onClick={() => setOpenNotifications(false)}>
<CloseIcon />
</IconButton>
<Divider />
<List>
{notifications.map((notification, index) => (
<ListItem key={notification._id} style={{ backgroundColor: '#f4f4f4', marginBottom: '10px', borderRadius: '4px' }}>
<ListItemText primary={notification.message} />
<IconButton onClick={() => dismissNotification(notification._id, index)}>
<DeleteIcon />
</IconButton>
</ListItem>
))}
<ListItem button onClick={dismissAllNotifications}>
<ListItemText primary="Dismiss All" />
<DeleteIcon />
</ListItem>
</List>
</Paper>
</Drawer>
);
return (
<div>
<AppBar position="static">
<Toolbar>
{/* Other items */}
<IconButton color="inherit" onClick={() => setOpenNotifications(true)}>
<NotificationsIcon />
</IconButton>
<Typography variant="h6" color="inherit" component="span" style={{ cursor: 'pointer' }} onClick={() => setOpenNotifications(true)}>
Notifications
</Typography>
{/* Logout button or other items */}
</Toolbar>
</AppBar>
<NotificationSidebar />
<Container
maxWidth="sm"
style={{
margin: '100px auto',
backgroundColor: '#f8f8f8',
padding: '0em',
borderRadius: '15px',
boxShadow: '0px 0px 10px rgba(0, 0, 0, 0.1)',
}}
>
<div className="container">
<div className="dashboard-buttons">
{/* Dashboard buttons */}
<a href={UpdateDoctorInfo} className="dashboard-button">
<UpdateIcon className="dashboard-button-icon" ></UpdateIcon>
<span className="dashboard-button-label">Update Info</span>
</a>
<a href={Doctor_Patient_Lists} className="dashboard-button">
<PersonSearch className="dashboard-button-icon" ></PersonSearch>
<span className="dashboard-button-label">My patients</span>
</a>
<a href={FilterAppointments} className="dashboard-button">
<FilterAltIcon className="dashboard-button-icon" ></FilterAltIcon>
<span className="dashboard-button-label">Appointment Filter</span>
</a>
<a href={pathToViewAppointments} className="dashboard-button">
<GroupsIcon className="dashboard-button-icon" ></GroupsIcon>
<span className="dashboard-button-label">View Appointments</span>
</a>
<a href={ChangeDoctorPassword} className="dashboard-button">
<PasswordIcon className="dashboard-button-icon" ></PasswordIcon>
<span className="dashboard-button-label">Change Password</span>
</a>
<a href={AddAvailableAppointments} className="dashboard-button">
<MoreTimeIcon className="dashboard-button-icon" ></MoreTimeIcon>
<span className="dashboard-button-label">Add Available Appointments</span>
</a>
<a href={VideoCall1} className="dashboard-button">
<VideoCall className="dashboard-button-icon" ></VideoCall>
<span className="dashboard-button-label">Video Call</span>
</a>
<a href={ViewFollowUpRequests} className="dashboard-button">
<FollowUpRequestIcon className="dashboard-button-icon" ></FollowUpRequestIcon>
<span className="dashboard-button-label">View FollowUp Requests</span>
</a>
<a href={ViewPharmacists} className="dashboard-button">
<PersonSearch className="dashboard-button-icon" ></PersonSearch>
<span className="dashboard-button-label">View Pharmacists</span>
</a>
<label>Wallet Balance:<br></br> {balance} EGP</label>
</div>
</div>
</Container>
<AppBar style={{ backgroundColor: '#0066ff' }}>
<Toolbar>
<Button variant="h6" style={{fontSize: '18px', marginRight: 755 }} startIcon={<LocalHospital />}>
Doctor Dashboard
</Button>
<Button variant="h6" color="inherit" component="span" startIcon={<Notifications />} style={{ cursor: 'pointer', marginRight:10, marginLeft:190}} onClick={() => setOpenNotifications(true)}>
Notifications
</Button>
<Button color="inherit" startIcon={<ExitToApp />} onClick={handleLogout}>
Logout
</Button>
</Toolbar>
</AppBar>
</div>
);
}
export default DoctorDashboard;
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate, Link as RouterLink } from 'react-router-dom';
import axios from 'axios';
import {
AppBar,
Typography,
Button,
Toolbar,
Container,
Grid
} from '@mui/material';
import { AddCircleOutline, Person, Medication,ShoppingCartCheckout,ManageAccounts ,AddShoppingCart , Chat } from '@mui/icons-material';
import './Button.css';
const image = require('./Background.png');
const PatientDashboard = () => {
const { userid, patientid } = useParams();
const Medicine = /patient-dashboard/${userid}/${patientid}/medicine;
const Cart = /patient-dashboard/${userid}/${patientid}/cart;
//const OrderTable = /patient-dashboard/${userid}/${patientid}/order;
const ChangeMyPassword = /patient-dashboard/${userid}/${patientid}/chnageMyPassword;
//const ViewAddress = /patient-dashboard/${userid}/viewAddress;
const orderhistory = /patient-dashboard/${userid}/${patientid}/orderHistory;
const trackOrders = /patient-dashboard/${userid}/${patientid}/OrderTable;
const viewPharmacists = /patient-dashboard/${userid}/${patientid}/viewPharmacists;
const addAddress = /patient-dashboard/${userid}/${patientid}/AddAddress;
const [balance, setBalance] = useState(0);
const [Address, setAdress] = useState(null);
const navigate = useNavigate();
const Login = /;
// Add this to the top of your Login component
useEffect(() => {
axios.get(http://localhost:7000/api/patient/getWalletBalance/${patientid})
.then((res) => {
setBalance(res.data.Wallet);
})
.catch((error) => {
console.error(error);
});
axios.get(http://localhost:7000/api/patient/currentadress/${patientid})
.then((res) => {
setAdress(res.data.Adress);
})
.catch((error) => {
console.error(error);
});
// Disable scrolling when the component mounts
document.body.style.overflow = 'hidden';
// Re-enable scrolling when the component is unmounted
return () => {
document.body.style.overflow = 'auto';
};
}, []);
const handleLogout = async (event) => {
event.preventDefault();
await axios.get('http://localhost:7000/api/logout');
sessionStorage.removeItem('jwt');
navigate('/');
};
return (
<div >
<AppBar style={{ backgroundColor: '#0066ff' }}>
<Toolbar>
<Button
color="inherit"
startIcon={<Person />}
style={{ fontSize: '18px', marginRight: 1000 }}
>
Patient Dashboard
</Button>
<Button
color= "inherit"
style={{ marginLeft: 130 }}
startIcon={<AddCircleOutline />}
onClick={handleLogout}
component={RouterLink}
to={Login}
onMouseOver={(e) => e.currentTarget.style.color = 'white'}
onMouseOut={(e) => e.currentTarget.style.color = ''}
>
Logout
</Button>
</Toolbar>
</AppBar>
<Container
maxWidth="sm"
style={{
margin: '150px auto',
backgroundColor: '#f8f8f8',
padding: '0em',
borderRadius: '15px',
boxShadow: '0px 0px 10px rgba(0, 0, 0, 0.1)',
}}
>
<div className="container">
<div className="dashboard-buttons">
{/* Dashboard buttons */}
<a href={Medicine} className="dashboard-button">
<Medication className="dashboard-button-icon" />
<span className="dashboard-button-label">View Medicine</span>
</a>
<a href={Cart} className="dashboard-button">
<AddShoppingCart className="dashboard-button-icon" />
<span className="dashboard-button-label">View Cart</span>
</a>
<a href={orderhistory} className="dashboard-button">
<ShoppingCartCheckout className="dashboard-button-icon" />
<span className="dashboard-button-label">Past Orders</span>
</a>
<a href={ChangeMyPassword} className="dashboard-button">
<ManageAccounts className="dashboard-button-icon" />
<span className="dashboard-button-label">Change Password</span>
</a>
<a href={addAddress} className="dashboard-button">
<ManageAccounts className="dashboard-button-icon" />
<span className="dashboard-button-label">Change Address</span>
</a>
<a href={trackOrders} className="dashboard-button">
<ManageAccounts className="dashboard-button-icon" />
<span className="dashboard-button-label">Track My Order(s)</span>
</a>
<a href={viewPharmacists} className="dashboard-button">
<Chat className="dashboard-button-icon" />
<span className="dashboard-button-label">Chat with Pharmacists</span>
</a>
<label>Wallet Balance:<br></br> {balance} EGP</label>
<label>Current Address: <br></br>{Address} </label>
</div>
</div>
</Container>
</div>
);
};
export default PatientDashboard;
```# Installation
* Open two separate terminals.
* In the first terminal, go to the Backend folder and type the command: `npm install`
```bash
cd backend --> npm install
```
* In the second terminal, go to the Frontend folder and type the command: `npm install`
```bash
cd frontend --> npm install
```
# API Reference 📋
1. Adminstrator route:
- <details><summary>Post /createAdmin</summary>
- Description: create new admin in the platform.
- URL: /api/adminstrator/createAdmin
- Parameters:
- Body: username, password, email
- Response: Models.Admin, Models.User
- Authorization: Required. Bearer token of the Admin.
</details>
- <details><summary>Put /changeMyPassword</summary>
- Description: change the password of the user.
- URL: /api/adminstrator/changeMyPassword/:userid
- Parameters: userid
- Body: oldPassword, newPassword
- Response: Models.Admin, Models.User, Models.Patient, Models.Pharmacist
- Authorization: Required. Bearer token of the Admin, Patient, and Pharmacist.
</details>
- <details><summary>Delete /removePharmacist</summary>
- Description: Admin remove Pharmacist.
- URL: /api/adminstrator/removePatient/:id
- Parameters: id
- Body: none
- Response: Models.User, Models.Pharmacist
- Authorization: Required. Bearer token of the Admin.
</details>
- <details><summary>Delete /removePatient</summary>
- Description: Admin remove Patient.
- URL: /api/adminstrator/removePatient/:id
- Parameters: id
- Body: none
- Response: Models.User, Models.Patient
- Authorization: Required. Bearer token of the Admin.
</details>
- <details><summary>Get /getPharmacist</summary>
- Description: Admin get all Pharmacist.
- URL: /api/adminstrator/Pharmacist
- Parameters: none
- Body: none
- Response: Models.Pharmacist
- Authorization: Required. Bearer token of the Admin.
</details>
- <details><summary>Get /getPatient</summary>
- Description: Admin get all Patient.
- URL: /api/adminstrator/Patient
- Parameters: none
- Body: none
- Response: Models.Patient
- Authorization: Required. Bearer token of the Admin.
</details>
- <details><summary>Get /getMedicine</summary>
- Description: Admin get all Medicine.
- URL: /api/adminstrator/Medicine
- Parameters: none
- Body: none
- Response: Models.Medicine
- Authorization: Required. Bearer token of the Admin.
</details>
- <details><summary>Put /updateMedicine</summary>
- Description: Admin Update Medicine.
- URL: /api/edit/medicine/:id
- Parameters: id
- Body: Name, Price, Description, Details, Stock, Sales, Picture, Use
- Response: Models.Medicine
- Authorization: Required. Bearer token of the Admin.
</details>
- <details><summary>Put /archiveMedicine</summary>
- Description: Admin archive Medicine.
- URL: /api/adminstrator/medicine/archive/:id
- Parameters: id
- Body: none
- Response: Models.Medicine
- Authorization: Required. Bearer token of the Admin.
</details>
- <details><summary>Put /unArchiveMedicine</summary>
- Description: Admin unarchive Medicine.
- URL: /api/adminstrator/medicine/unarchive/:id
- Parameters: id
- Body: none
- Response: Models.Medicine
- Authorization: Required. Bearer token of the Admin.
</details>
2. Patient route:
- <details><summary>Put /registerPatient</summary>
- Description: Patient unarchive Medicine.
- URL: /api/register-patient
- Parameters: id
- Body:
username,
name,
email,
password,
dateOfBirth,
gender,
mobileNumber,
emergencyContactFullName,
emergencyContactMobileNumber
- Response: Models.Patient, Models.User
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Get /getPatientWallet</summary>
- Description: get patient wallet.
- URL: /api/patient/getWalletBalance/:id
- Parameters: id
- Body: none
- Response: Models.Patient
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Get /getPatientCurrentAddress</summary>
- Description: get patient current adrress.
- URL: /api/patient/currentadress/:id
- Parameters: id
- Body: none
- Response: Models.Patient
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Post /createCart</summary>
- Description: Patient create Cart.
- URL: /api/patient/cart/add
- Parameters: none
- Body: name, price, description, details, stock, sales, picture, use, amount, patientid
- Response: Models.Cart
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Get /getCart</summary>
- Description: get patient cart.
- URL: /api/patient/Cart/:id
- Parameters: id
- Body: none
- Response: Models.Cart
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Delete /removeItemFromCart</summary>
- Description: Patient remove item from cart.
- URL: /api/patient/remove/cart/:cartid
- Parameters: cartid
- Body: none
- Response: Models.Cart
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Put /incrementCartAmount</summary>
- Description: Patient increment item from cart.
- URL: /api/patient/increment/cart/:id
- Parameters: id
- Body: none
- Response: Models.Cart
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Put /decrementCartAmount</summary>
- Description: Patient decrement item from cart.
- URL: /api/patient/decrement/cart/:id
- Parameters: id
- Body: none
- Response: Models.Cart
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Delete /removeOrder</summary>
- Description: Patient remove order.
- URL: /api/patient/order/remove/:id
- Parameters: id
- Body: none
- Response: Models.Order, Models.Patient, Models.Medicine
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Post /createOrder</summary>
- Description: Patient create order.
- URL: /api/patient/order/add
- Parameters: none
- Body: orderNumber, date, address, cartItems, price, patientid, status
- Response: Models.Order
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Post /getOrders</summary>
- Description: Patient get order.
- URL: /api/patient/order/:id
- Parameters: id
- Body: none
- Response: Models.Order
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Post /getCurrentOrders</summary>
- Description: Patient get current order.
- URL: /api/patient/order/current/:id
- Parameters: id
- Body: none
- Response: Models.Order
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Put /payOrderWithWallet</summary>
- Description: Patient pay with wallet.
- URL: /api/patient/order/pay/wallet/:id
- Parameters: id
- Body: none
- Response: Models.Order, Models.Patient
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Put /payOrderWithCreditCard</summary>
- Description: Patient pay with credit card.
- URL: /api/patient/order/pay/credit/:id
- Parameters: id
- Body: none
- Response: Models.Order, Models.Patient
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Get /getPatientMedicine</summary>
- Description: Patient get medicine.
- URL: /api/Patient/Medicine
- Parameters: id
- Body: none
- Response: Models.Medicine
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>post /checkoutOrder</summary>
- Description: Patient checkout order.
- URL: /api/patient/order/checkout/:id
- Parameters: id
- Body: none
- Response: Models.Cart, Models.Patient, Models.Medicine, Models.Order
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Get /getOrderbyId</summary>
- Description: Patient get order by id.
- URL: /api/patient/order/:id
- Parameters: id
- Body: none
- Response: Models.Order
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Get /getMedicineAlternatives</summary>
- Description: Patient get medicine alternatives.
- URL: api/patient/medicine/alternatives/:id
- Parameters: id
- Body: none
- Response: Models.Medicine
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Post /addPrescriptionMedicineToCart</summary>
- Description: Patient add prescription medicine to cart .
- URL: api/patient/medicine/alternatives/:id
- Parameters: none
- Body: name, price, description, details, stock, sales, picture, use, amount, patientid
- Response: Models.Cart
- Authorization: Required. Bearer token of the Patient.
</details>
3. Adress route:
- <details><summary>Post /addAddress</summary>
- Description: Patient add address
- URL: /api/address/addAddress
- Parameters: none
- Body: userData, newAddress
- Response: Models.Patient
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Get /getAddresses</summary>
- Description: Patient get address
- URL: /api/address/getAddresses
- Parameters: userData
- Body:
- Response: Models.Patient
- Authorization: Required. Bearer token of the Patient.
</details>
- <details><summary>Post /setCurrentAddress</summary>
- Description: Patient set current address
- URL: /api/address/getAddresses
- Parameters:
- Body: userData, currentAddress
- Response: Models.Patient
- Authorization: Required. Bearer token of the Patient.
</details>
4.Pharmacist route:
- <details><summary>Get /getUnapprovedPharmacists</summary>
- Description: get Unapproved Pharmacists.
- URL: /api/adminstrator/unapproved-pharmacists
- Parameters: none
- Body: none
- Response: Models.Pharmacist
- Authorization: Required. Bearer token of the Pharmacist.
</details>
- <details><summary>POST /registerPharmacist</summary>
- Description: Register a new pharmacist.
- URL: /api/register
- Parameters: none
- Body: Pharmacist details (username, password, name, email, dateOfBirth, hourlyRate, affiliation, educationalBackground, idDocument, medicalLicense, medicalDegree)
- Response: Models.Pharmacist
- Authorization: Not required.
</details>
- <details><summary>GET /getUnapprovedPharmacists</summary>
- Description: Get unapproved pharmacists.
- URL: /api/adminstrator/unapproved-pharmacists
- Parameters: none
- Body: none
- Response: Models.Pharmacist
- Authorization: Required. Bearer token of the administrator.
</details>
- <details><summary>PUT /approvePharmacist/:id</summary>
- Description: Approve a pharmacist.
- URL: /api/adminstrator/approve-pharmacist/:id
- Parameters: Pharmacist ID
- Body: none
- Response: Models.Pharmacist
- Authorization: Required. Bearer token of the administrator.
</details>
- <details><summary>PUT /rejectPharmacist/:id</summary>
- Description: Reject a pharmacist.
- URL: /api/adminstrator/reject-pharmacist/:id
- Parameters: Pharmacist ID
- Body: none
- Response: Models.Pharmacist
- Authorization: Required. Bearer token of the administrator.
</details>
- <details><summary>GET /viewPharmacistWallet/:id</summary>
- Description: View a pharmacist's wallet.
- URL: /api/pharmacist/wallet/:id
- Parameters: Pharmacist ID
- Body: none
- Response: Models.Pharmacist
- Authorization: Required. Bearer token of the pharmacist.
</details>
- <details><summary>PUT /updatePharmacistWallet/:id</summary>
- Description: Update a pharmacist's wallet.
- URL: /api/pharmacist/wallet/:id
- Parameters: Pharmacist ID
- Body: Hours worked
- Response: Models.Pharmacist
- Authorization: Required. Bearer token of the pharmacist.
</details>
- <details><summary>GET /filterSalesReport</summary>
- Description: Filter sales report.
- URL: /api/pharmacist/sales/filter
- Parameters: none
- Body: Date and medicine name
- Response: Models.SalesReport
- Authorization: Required. Bearer token of the pharmacist.
</details>
5.reset-password route:
- <details><summary>Post /reset-password</summary>
- Description: get Unapproved Pharmacists.
- URL: /reset-password/reset-password
- Parameters: none
- Body: email, password, resetToken
- Response: Models.Pharmacist, Models.User, Models.Patient, Models.Admin
- Authorization: none.
</details>
- <details><summary>Post /verify-otp</summary>
- Description: get Unapproved Pharmacists.
- URL: /reset-password/verify-otp
- Parameters: none
- Body: email, otp
- Response: none
- Authorization: none.
</details>
# Tests
Testing was done using `jest`. To run the tests, run the following command:
```bash
> npm run test
Also, tests can be done using Postman on any route.
To run backend
cd backend && node App.js
To run frontend
cd frontend && npm start
*The backend server and frontend will be running on the specified ports in your .env file.
Any contributions to our code is welcomed. You can always improve the frontend for a better UX.## Credit
- Our team "RADS" did a tremendous work in order to fullfill this project on time. Special thanks to our Scrum Master, Ahmed Hesham [@Ahmed Khaled] for being always there for support and for managing our team smoothly:
- Nour El Alfy
- Ziad Maged
- Yousif Sanad
- Mohammed Ali
- Fares negm
- Youssef osama
- Links that helped us alot:
- MERN Stack Crash Course Tutorial: https://www.youtube.com/playlist?list=PL4cUxeGkcC9iJ_KkrkBZWZRHVwnzLIoUE
- MERN stack authentication + profile: https://www.youtube.com/playlist?list=PLr_bWRQ_9ePVfQwf0LorPwTlOZSBoPGIu
- Search Bar in React Tutorial - Cool Search Filter Tutorial: https://www.youtube.com/watch?v=x7niho285qs&start=1337
- React: Add/Remove Input Fields Dynamically on button click: https://www.youtube.com/watch?v=XtS14dXwvwE
- MERN Auth Tutorial: https://www.youtube.com/playlist?list=PL4cUxeGkcC9g8OhpOZxNdhXggFz2lOuCT
- How to Connect Firebase With React Application: https://www.youtube.com/watch?v=ad6IavyAHsQ
MIT License
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.