Skip to content

advanced-computer-lab-2023/poly-medica-Clinic

Repository files navigation

Poly Medica

PolyMedica is a comprehensive healthcare solution that seamlessly integrates two virtual platforms: PolyMedica Clinics and PolyMedica Pharmacy. This innovative system is built on a microservices architecture, ensuring flexibility, scalability, and efficiency.

The System mainly composed of 6 services, the communication among them is done by either synchronous HTTP requests using Axios or asynchronous communication using Apache Kafka:

  • Clinic Service
  • Patient Service
  • Authentication Service
  • Communication Service
  • Payment Service
  • Pharmacy Service

Badges

Github Actions badge Git badge Express badge Jest Badge Node badge React badge Mongo badge Socket badge ES6 badge Redux badge ESLINT badge JWT badge Docker badge Kafka badge Stripe badge Swagger badge

Build Status

Authentication CI

Clinic CI

Frontend CI

Patient CI

Payment CI


Planned Features

Frontend Automated Testing with Jest for React MUI

To enhance code quality and ensure a stable frontend, we're working on implementing comprehensive automated tests using Jest for our React application built with Material-UI (MUI). These tests will cover unit testing, integration testing, and UI component testing to guarantee a seamless user experience.

AI Models Integration

We're excited to introduce AI models to augment our system's capabilities:

  • Doctor Assistance AI: This AI model will assist doctors by analyzing symptoms input by the patient, suggesting suitable medicines, and providing dosage recommendations. It will also create reminders for doctors regarding prescribed medications and their quantities.

  • Patient Assistance AI: Suggest replacements for medicines out of stock for the patient


Code Style Guide

JavaScript (Node.js and React)

  • Indentation: Use 2 spaces.
  • Naming Conventions: camelCase for variables/functions, PascalCase for React components.
  • ESLint: Utilize appropriate ESLint configurations for Node.js and React.

Express.js (Backend)

  • Routing: Follow RESTful conventions for organized routes.
  • Middleware: Use for route-specific logic.
  • Error Handling: Implement middleware for consistent error responses.

MongoDB (Database)

  • Naming Conventions: Maintain consistent naming for collections (singular nouns).
  • Schema Design: Ensure consistency across collections.
  • Indexes: Optimize with appropriate indexes for queries.

React with Material-UI (Frontend)

  • MUI Components: Leverage Material-UI components and adhere to their guidelines.
  • Folder Structure: Organize components by features/functions.
  • State Management: Use Redux/Context API for complex state (if needed).
  • Lifecycle Methods: Prefer hooks and functional components.

Git Workflow

  • Branching: Follow Gitflow (feature branches, develop, master).
  • Pull Requests: Require clear descriptions and peer reviews before merging.

Microservices Structure

Poly-Medica Clinic
├── clinic
│   ├── ...
│   └── (Clinic Service Code)
├── patient
│   ├── ...
│   └── (Patient Service Code)
├── authentication
│   ├── ...
│   └── (Authentication Service Code)
├── communication
│   ├── ...
│   └── (Communication Service Code)
├── payment
│   ├── ...
│   └── (Payment Service Code)
└── client
   ├── ...
   └── (Client Application Code)

Poly-Medica Pharmacy
├── pharmacy
│   ├── ...
│   └── (Pharmacy Service Code)
└── client
   ├── ...
   └── (Client Application Code)

Screenshots đź–µ

Login Page

login

Home Page

home

Appointments Page

appointments

Health Package Page

health-package

Apply Filter on doctors

apply-filter

Patient Adding family members

add-member

Doctor Receiving Notification

notification

Admin Viewing Requests

requests


Tech/Framework used


Features

The system serves different type of users (Patient, Doctor , Admin )

As a Guest I can
  • Sign up as a patient
  • submit a request to register as a doctor
  • upload and submit required documents upon registrationas a doctor
As a Patient I can
  • Add family members
  • Link another patient's account
  • Choose to pay for my appointment using my wallet or credit card
  • Enter credit card details and pay for an appointment
  • View registered family members
  • View uploaded health records
  • View, Reschedule and Filter appointments
  • Cancel an appointment for myself or for a family member
  • View, Download and Filter all prescriptions
  • choose the way to pay a prescription
  • Receive a notification
  • Request a follow-up to a previous appointment
  • Chat with a doctor
  • Start and End a video call with a doctor
As a Doctor I can
  • Update My personal information like email, affiliation and hourly rate
  • View and accept the employment contract
  • Add my available time slots for appointments
  • View uploaded health records
  • Add a new health records
  • View all prescriptions
  • View, Search and Filter a list of my patients
  • Select a patient from the list of patients
  • Receive a notification
  • View, Reschedule and Filter appointments
  • Cancel an appointment
  • Schedule a follow-up for a patient
  • Add and Delete medicine from a prescription
  • Add and Update dosage for each medicine added to the prescription
  • Download selected prescription
  • Update a patient's prescription before it is submitted to the pharmacy
  • Accept or Revoke a follow-up session request from a patient -Chat with a patient
  • Start and End a video call with a patient
As an Admin I can
  • Add another adminstrator
  • Remove a doctor or a patient or an admin from the system
  • View all of the information uploaded by a doctor to apply to join the platform
  • Accept or Reject the request of a doctor to join the platform
  • Add, Update and Delete health packages with different price ranges.
  • Accept a request for the registration of a doctor

Code Examples

Admin Details
import React from 'react';
import DoctorIcon from '../../../assets/images/icons/DoctorIcon.png';
import EmailIcon from '@mui/icons-material/Email';
import StarBorderIcon from '@mui/icons-material/StarBorder';
import {
	Dialog,
	DialogTitle,
	DialogContent,
	Typography,
	DialogActions,
	Button,
	useTheme,
} from '@mui/material';
import { styled } from '@mui/system';
import { useAdminContext } from 'hooks/useAdminContext';
import { commonStyles } from 'ui-component/CommonStyles';

const useStyles = styled(() => commonStyles);

const AdminDetails = () => {
	const classes = useStyles();
	const theme = useTheme();
	const title = ' ';

	const { setSelectedAdmin, setErrorMessage, selectedAdmin } = useAdminContext();

	const handleDialogClose = () => {
		setSelectedAdmin(null);
		setErrorMessage('');
	};

	return (
		<Dialog
			open={selectedAdmin}
			onClose={handleDialogClose}
			PaperProps={{ sx: { minWidth: theme.breakpoints.values.md > 800 ? 500 : 300 } }}
		>
			{selectedAdmin && (
				<>
					<DialogTitle align='center' variant='h2'>
						{selectedAdmin.userName}
					</DialogTitle>
					<DialogContent>
						<div className={classes.container}>
							<div>
								<img
									src={DoctorIcon}
									alt={`${title} ${selectedAdmin.userName}`}
									width='100'
									height='100'
								/>
								<Typography variant='h4' sx={{ marginTop: '1em' }}>
									{`${title} ${selectedAdmin.userName}`}
								</Typography>
							</div>
							<div className={classes.infoContainer}>
								<div className={classes.emailContainer}>
									<EmailIcon className={classes.iconMargin} />
									<Typography variant='body1'>{selectedAdmin.email}</Typography>
								</div>
								<div className={classes.emailContainer}>
									<StarBorderIcon className={classes.iconMargin} />
									<Typography variant='body1'>
										{selectedAdmin.mainAdmin ? 'Main Admin' : 'Sub Admin'}
									</Typography>
								</div>
							</div>
						</div>
					</DialogContent>
					<DialogActions>
						<Button onClick={handleDialogClose} color='primary'>
							Close
						</Button>
					</DialogActions>
				</>
			)}
		</Dialog>
	);
};

export default AdminDetails;
Search Context
 import React, { createContext, useContext, useState } from 'react';

const SearchContext = createContext();

export const useSearch = () => {
  return useContext(SearchContext);
};

export const SearchProvider = ({ children }) => {
  const [searchQuery, setSearchQuery] = useState('');

  const updateSearchQuery = (query) => {
    setSearchQuery(query);
  };

  return (
    <SearchContext.Provider value={{ searchQuery, updateSearchQuery }}>
      {children}
    </SearchContext.Provider>
  );
};
Get admins API
	app.get('/admins', async (req, res) => {
		try {
			const admins = await service.findAllAdmins();
			res.status(OK_STATUS_CODE).json({ admins });
		} catch (err) {
			res.status(ERROR_STATUS_CODE).json({ err: err.message });
		}
	});
Users Model
import mongoose from 'mongoose';
import { USER_ARR_ENUM } from '../../utils/Constants.js';

const userSchema = mongoose.Schema({
	userId:{
		type: mongoose.Schema.Types.ObjectId,
		required:true,
		unique: true,
	},
	email:{
		type:String,
		required:true,
		unique: true,
	},
	userName:{
		type:String,
		required:true,
		unique: true
	},
	password:{
		type:String,
		required:true
	},
	type:{
		type: String,
		enum: USER_ARR_ENUM,
		required:true
	},
});

userSchema.statics.signup = async function (
	userId,
	email,
	password,
	userName,
	type,
) {
	const userRecord = new this({
		userId: new mongoose.Types.ObjectId(userId),
		email,
		password,
		userName,
		type,
	});
	const result = await userRecord.save();
	return result;
};

const User = mongoose.model('User', userSchema);

export default User;
Notification
import PropTypes from 'prop-types';
 
import { useTheme } from '@mui/material/styles';
import { Box, Chip, Drawer, Stack, useMediaQuery } from '@mui/material';
 
import PerfectScrollbar from 'react-perfect-scrollbar';
import { BrowserView, MobileView } from 'react-device-detect';
 
import MenuList from './MenuList';
import LogoSection from './LogoSection';
import { drawerWidth } from 'store/constant'; code exa

const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
	const theme = useTheme();
	const matchUpMd = useMediaQuery(theme.breakpoints.up('md'));

	const drawer = (
		<>
			<Box sx={{ display: { xs: 'block', md: 'none' } }}>
				<Box sx={{ display: 'flex', p: 2, mx: 'auto' }}>
					<LogoSection />
				</Box>
			</Box>
			<BrowserView>
				<PerfectScrollbar
					component="div"
					style={{
						height: !matchUpMd ? 'calc(100vh - 56px)' : 'calc(100vh - 88px)',
						paddingLeft: '16px',
						paddingRight: '16px'
					}}
				>
					<MenuList />
					<Stack direction="row" justifyContent="center" sx={{ mb: 2 }}>
						<Chip label={process.env.REACT_APP_VERSION} disabled chipcolor="secondary" size="small" sx={{ cursor: 'pointer' }} />
					</Stack>
				</PerfectScrollbar>
			</BrowserView>
			<MobileView>
				<Box sx={{ px: 2 }}>
					<MenuList />
					<Stack direction="row" justifyContent="center" sx={{ mb: 2 }}>
						<Chip label={process.env.REACT_APP_VERSION} disabled chipcolor="secondary" size="small" sx={{ cursor: 'pointer' }} />
					</Stack>
				</Box>
			</MobileView>
		</>
	);

	const container = window !== undefined ? () => window.document.body : undefined;

	return (
		<Box component="nav" sx={{ flexShrink: { md: 0 }, width: matchUpMd ? drawerWidth : 'auto' }} aria-label="mailbox folders">
			<Drawer
				container={container}
				variant={matchUpMd ? 'persistent' : 'temporary'}
				anchor="left"
				open={drawerOpen}
				onClose={drawerToggle}
				sx={{
					'& .MuiDrawer-paper': {
						width: drawerWidth,
						background: theme.palette.background.default,
						color: theme.palette.text.primary,
						borderRight: 'none',
						[theme.breakpoints.up('md')]: {
							top: '88px'
						}
					}
				}}
				ModalProps={{ keepMounted: true }}
				color="inherit"
			>
				{drawer}
			</Drawer>
		</Box>
	);
};

Sidebar.propTypes = {
	drawerOpen: PropTypes.bool,
	drawerToggle: PropTypes.func,
	window: PropTypes.object
};

export default Sidebar;

Installation

> git clone https://github.com/advanced-computer-lab-2023/poly-medica-Clinic.git
> cd poly-medica-clinic
> chmod +x install-all.sh
> ./install-all.sh

API Documentation

The API documentation is created using Swagger. To access it, follow these steps:

  1. Ensure the service is running.
  2. Open your browser and navigate to localhost:SERVICE_PORT/api-docs.

swagger


Tests

The testing is done using jest. To run the tests, run the following command:

  • Make sure you are at the root directory of the project
> chmod +x run-tests.sh
>./run-tests.sh

image

Model tests

Faker.js is used to generate data to test different models

There are tests done for the following models : User , Admin , Patient , Doctor , Appointment , Health Package , Order , Prescription

How to use

To run the project

  • Make sure you are at the root directory of the project
  • The script will run all the services and the client except the pharmacy service, which could be found at this repository
> npm install -g concurrently
> chmod +x run-all.sh
> ./run-all.sh

All services and client will be running on the specified ports on your env files.

Environment Variables

To run this project, you will need to add the following environment variables to your .env file for all services

Authentication and Communication envs

MONGO_URI

MONGO_URI_TEST

JWT_SECRET

PORT

RESETEMAIL

RESETPASSWORD

Clinic and Patient envs

MONGO_URI

MONGO_URI_TEST

JWT_SECRET

PORT

Payment envs

SecretKey

PublicKey

PORT

Contributing

Contributions are always welcome!

Getting Started

  1. Fork the repository
  2. Clone the repository
  3. Install dependencies
  4. Create a new branch
  5. Make your changes
  6. Commit and push your changes
  7. Create a pull request
  8. Wait for your pull request to be reviewed and merged

Credits


License

The Project is open source following MIT License


Contributers: