+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/Header.jsx b/Header.jsx
new file mode 100644
index 0000000..deb8649
--- /dev/null
+++ b/Header.jsx
@@ -0,0 +1,61 @@
+import React from "react"; // Importing necessary modules from React
+import { Link, useNavigate } from 'react-router-dom'; // Importing Link and useNavigate from react-router-dom
+import { AppBar, Toolbar, Button, IconButton } from '@mui/material'; // Importing necessary components from Material-UI
+import {
+ Help as HelpIcon,
+ ContactMail as ContactIcon,
+ AccountCircle as AccountCircleIcon,
+ Logout as LogoutIcon
+} from '@mui/icons-material'; // Importing necessary icons from Material-UI
+import TitleLogo from '../assets/images/svgs/title-removebg-preview.png'; // Importing company logo
+
+// Styles for buttons
+const buttonStyles = {
+ fontSize: '1.3rem',
+ padding: '15px',
+};
+
+// Functional component Header
+export default function Header({ isLoggedIn, setIsLoggedIn }) {
+ const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom
+
+ return (
+ // Header bar with app title and navigation buttons
+
+
+ {/* Company logo */}
+
+
+
+
+ {/* Help button */}
+
+ {/* Contact Us button */}
+
+ {/* Dashboard button */}
+
+ {/* Logout button (visible only when user is logged in) */}
+ {isLoggedIn && (
+
+ )}
+
+
+
+ );
+};
diff --git a/HelpInfo.jsx b/HelpInfo.jsx
new file mode 100644
index 0000000..8767600
--- /dev/null
+++ b/HelpInfo.jsx
@@ -0,0 +1,92 @@
+import React from "react"; // Importing necessary modules from React
+import { useNavigate } from "react-router-dom"; // Importing useNavigate hook from react-router-dom
+import {
+ Avatar,
+ Button,
+ CssBaseline,
+ Container,
+ Box,
+ Typography,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+} from "@mui/material"; // Importing necessary components from Material-UI
+import LiveHelpIcon from "@mui/icons-material/LiveHelp"; // Importing LiveHelpIcon from Material-UI
+import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; // Importing ExpandMoreIcon from Material-UI
+
+// Functional component HelpInfo
+export default function HelpInfo() {
+ const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom
+
+ // FAQ data array containing objects with questions and answers
+ const faqData = [
+ {
+ question: "How do I place an order?",
+ answer:
+ "TBI", // To Be Implemented
+ },
+ {
+ question: "How can I track my orders?",
+ answer:
+ "TBI", // To Be Implemented
+ },
+ {
+ question: "How can I update my profile information?",
+ answer:
+ "TBI", // To Be Implemented
+ },
+ ];
+
+ return (
+ // Container for the main content with a fixed width
+
+ {/* Normalizing CSS */}
+ {/* Main content wrapper */}
+
+ {/* Avatar representing help icon */}
+
+
+
+ {/* Title */}
+
+ Questions? How can we help?
+
+ {/* Accordion component for displaying FAQ */}
+
+ {faqData.map((faq, index) => (
+
+ {/* Accordion summary with question */}
+ } // Icon indicating expandable content
+ aria-controls={`panel${index}-content`}
+ id={`panel${index}-header`}
+ >
+ {faq.question}
+
+ {/* Accordion details with answer */}
+
+ {faq.answer}
+
+
+ ))}
+
+ {/* Button to navigate back */}
+
+
+
+ );
+};
diff --git a/Login.jsx b/Login.jsx
new file mode 100644
index 0000000..5058d5f
--- /dev/null
+++ b/Login.jsx
@@ -0,0 +1,178 @@
+import React, { useState } from 'react'; // Importing React and useState hook
+import { useNavigate } from 'react-router-dom'; // Importing useNavigate hook from react-router-dom
+import {
+ Avatar,
+ Button,
+ CssBaseline,
+ TextField,
+ Container,
+ Box,
+ Typography,
+} from '@mui/material'; // Importing necessary components from Material-UI
+import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; // Importing LockOutlinedIcon from Material-UI
+
+// Functional component Login
+export default function Login({ setIsLoggedIn, setUsername }) {
+ const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom
+
+ // State variables to store form data and error message
+ const [formData, setFormData] = useState({
+ email: '',
+ password: '',
+ });
+ const [errorMessage, setErrorMessage] = useState('');
+
+ // Function to handle input change and update form data accordingly
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData((prevFormData) => ({
+ ...prevFormData,
+ [name]: value,
+ }));
+ };
+
+ // Function to handle login form submission
+ const handleLogin = async (e) => {
+ e.preventDefault();
+ // Checking if the email and password match the predefined values
+ if (formData.email === 'user' && formData.password === 'password') {
+ setIsLoggedIn(true); // Setting isLoggedIn state to true
+ }
+ try {
+ // Making a POST request to the login endpoint with form data
+ const response = await fetch('http://127.0.0.1:5000/auth/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(formData),
+ });
+
+ if (response.ok) {
+ // If the response is successful, parse the data
+ const data = await response.json();
+ if (data.status === 'success') {
+ localStorage.setItem('jwtToken', data.access_token); // Storing JWT token in local storage
+ setUsername(data.username); // Setting username using setUsername function
+ setIsLoggedIn(true); // Setting isLoggedIn state to true
+ } else {
+ setErrorMessage(data.message); // Setting error message if login is unsuccessful
+ }
+ } else {
+ setErrorMessage('HTTP error: ' + response.status); // Setting error message for HTTP error
+ }
+ } catch (error) {
+ setErrorMessage('An error occurred: ' + error.message + '.'); // Setting error message for general error
+ }
+ };
+
+ return (
+ // Main container for login form
+
+ {/* Normalizing CSS */}
+ {/* Box for arranging components vertically */}
+
+ {/* Avatar representing lock icon */}
+
+
+
+ {/* Title */}
+
+ Welcome!
+
+ {/* Form for login */}
+
+ {/* Text field for email */}
+
+ {/* Text field for password */}
+
+ {/* Error message display */}
+ {errorMessage && (
+
+ {errorMessage}
+
+ )}
+ {/* Button for submitting login form */}
+
+ {/* Button for navigating to forgot password page */}
+
+
+ {/* Link to registration page */}
+
+ navigate('/registration')}
+ >
+ Don't have an account?
+
+
+
+
+ );
+}
diff --git a/MedicalForms.jsx b/MedicalForms.jsx
new file mode 100644
index 0000000..850f44e
--- /dev/null
+++ b/MedicalForms.jsx
@@ -0,0 +1,55 @@
+import React from "react"; // Importing React library
+import { useNavigate } from "react-router-dom"; // Importing useNavigate hook from react-router-dom
+import { Button, Grid, Paper, Typography, Link } from '@mui/material'; // Importing necessary components from Material-UI library
+import ArrowBackIcon from '@mui/icons-material/ArrowBack'; // Importing ArrowBackIcon from Material-UI
+
+// Importing PDF files from the assets folder
+import AIMMedicaidCommercialCMN from '../assets/PDF/AIM-Medicaid_Commericial-CMN-11_23.pdf';
+import AIMMedicareCMN from '../assets/PDF/AIM-Medicare-CMN-5_9_23.pdf';
+
+// Functional component MedicalForms
+const MedicalForms = () => {
+ const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom
+
+ // Array of documents with their names and paths
+ const documents = [
+ { name: 'AIM Plus Medicaid Commericial CMN', docPath: AIMMedicaidCommercialCMN },
+ { name: 'AIM Plus Medicare CMN', docPath: AIMMedicareCMN },
+ ];
+
+ return (
+
+ {/* Button to navigate back */}
+ } // Icon for back arrow
+ onClick={() => navigate('/')} // Click event to navigate back to home
+ sx={{ mb: 2, width: '150px', height: '40px' }} // Custom styles for button
+ >
+ Go Back
+
+ {/* Title for the page */}
+
+ Medical Forms
+
+ {/* Grid container to display documents */}
+
+ {documents.map((doc, index) => (
+
+ {/* Paper component to display each document */}
+
+ {doc.name} {/* Document name */}
+ {/* Link to open the PDF document in a new tab */}
+
+ Open PDF
+
+
+
+ ))}
+
+
+ );
+};
+
+export default MedicalForms; // Exporting MedicalForms component
diff --git a/Notifications.jsx b/Notifications.jsx
new file mode 100644
index 0000000..73f094c
--- /dev/null
+++ b/Notifications.jsx
@@ -0,0 +1,78 @@
+import React, { useState, useEffect } from 'react'; // Importing necessary modules from React
+import { useNavigate } from 'react-router-dom'; // Importing useNavigate hook from react-router-dom
+import { Typography, Paper, Box, Button } from '@mui/material'; // Importing necessary components from Material-UI
+import { ArrowBack as ArrowBackIcon } from '@mui/icons-material'; // Importing ArrowBackIcon from Material-UI
+
+// Functional component Notifications
+const Notifications = () => {
+ const [notifications, setNotifications] = useState([]); // State to store notifications
+ const token = localStorage.getItem('jwtToken'); // Retrieving JWT token from local storage
+ const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom
+
+ // useEffect hook to fetch notifications when the component mounts
+ useEffect(() => {
+ fetchNotifications();
+ }, []);
+
+ // Function to format date string
+ const formatDate = (dateString) => {
+ const date = new Date(dateString);
+ const options = { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' };
+ return date.toLocaleDateString('en-US', options);
+ };
+
+ // Function to fetch notifications from the backend
+ const fetchNotifications = async () => {
+ const response = await fetch('http://127.0.0.1:5000/dashboard/notifications', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`, // Sending JWT token in the request headers for authentication
+ }
+ });
+ if (response.ok) {
+ const responseData = await response.json();
+ const { notifications } = responseData.data;
+ setNotifications(notifications); // Updating notifications state with the fetched data
+ }
+ };
+
+ return (
+
+ {/* Button to navigate back */}
+ } // Icon for back arrow
+ onClick={() => navigate('/')} // Click event to navigate back to home
+ sx={{ mb: 2, width: '150px', height: '40px' }} // Custom styles for button
+ >
+ Go Back
+
+ {/* Title for the page */}
+
+ Notifications
+
+ {/* Displaying notifications in a Paper component */}
+ {notifications && (
+
+ {/* Mapping through notifications array and displaying each notification */}
+ {notifications.map((notification) => (
+
+ {/* Displaying formatted date and notification content */}
+
+ {formatDate(notification.status_timestamp)}: {notification.notification}
+
+ {/* Button to delete notification */}
+
+
+ ))}
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/Profile.jsx b/Profile.jsx
new file mode 100644
index 0000000..7bb1900
--- /dev/null
+++ b/Profile.jsx
@@ -0,0 +1,149 @@
+import React, { useState, useEffect } from 'react'; // Importing necessary modules from React
+import { TextField, Button, Typography, Box, CssBaseline, Paper, Container } from '@mui/material'; // Importing necessary components from Material-UI
+import { ArrowBack as ArrowBackIcon } from '@mui/icons-material'; // Importing ArrowBackIcon from Material-UI
+import { useNavigate } from 'react-router-dom'; // Importing useNavigate hook from react-router-dom
+
+// Functional component ProfilePage
+const ProfilePage = () => {
+ const [editing, setEditing] = useState(false); // State to manage editing mode
+ const [errorMessage, setErrorMessage] = useState(''); // State to manage error messages
+ const [profileInfo, setProfileInfo] = useState({ // State to store profile information
+ first_name: '',
+ last_name: '',
+ dob: '',
+ primary_phone: '',
+ secondary_phone: '',
+ address: '',
+ primary_insurance: '',
+ id_number: '',
+ contact_person: '',
+ doctor_name: '',
+ doctor_phone: '',
+ doctor_fax: '',
+ });
+ const token = localStorage.getItem('jwtToken'); // Getting JWT token from local storage
+ const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom
+
+ // useEffect hook to fetch profile information when the component mounts
+ useEffect(() => {
+ fetchProfile();
+ }, []);
+
+ // Function to handle editing mode
+ const handleEdit = () => {
+ setEditing(true);
+ };
+
+ // Function to save changes and exit editing mode
+ const handleSave = () => {
+ setEditing(false);
+ };
+
+ // Function to handle changes in input fields
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setProfileInfo({ ...profileInfo, [name]: value });
+ };
+
+ // Function to fetch profile information from the backend
+ const fetchProfile = async () => {
+ try {
+ const response = await fetch('http://127.0.0.1:5000/dashboard/profile', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`,
+ },
+ });
+
+ if (response.ok) {
+ const dataBody = await response.json();
+ if (dataBody.status === 'success') {
+ setProfileInfo(dataBody.data); // Updating profileInfo state with fetched data
+ } else {
+ setErrorMessage(data.message);
+ }
+ } else {
+ setErrorMessage('HTTP error: ' + response.status);
+ }
+ } catch (error) {
+ setErrorMessage('An error occurred: ' + error.message + '.');
+ }
+};
+
+ return (
+
+
+
+
+
+ {editing ? ( // Conditional rendering based on editing mode
+ <> {/* Fragment */}
+ }
+ onClick={handleSave}
+ sx={{ mb: 2, width: '150px', height: '40px' }}
+ >
+ Go Back
+
+
+
+
+ {/* Input fields for editing profile information */}
+ Edit Profile Information
+
+ {/* Other input fields */}
+
+
+ >
+ ) : (
+ <> {/* Fragment */}
+ }
+ onClick={() => navigate('/')}
+ sx={{ mb: 2, width: '150px', height: '40px' }}
+ >
+ Go Back
+
+ Profile Information
+ {/* Displaying profile information */}
+ First Name: {profileInfo.first_name}
+ {/* Other profile information */}
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default ProfilePage; // Exporting ProfilePage component
diff --git a/Registration.jsx b/Registration.jsx
new file mode 100644
index 0000000..0fde2d7
--- /dev/null
+++ b/Registration.jsx
@@ -0,0 +1,130 @@
+import React, { useState } from 'react'; // Importing necessary modules from React
+import { useNavigate } from 'react-router-dom'; // Importing useNavigate hook from react-router-dom
+import {
+ Avatar,
+ Button,
+ CssBaseline,
+ TextField,
+ Grid,
+ Box,
+ Typography,
+ Container,
+ Snackbar,
+} from '@mui/material'; // Importing necessary components from Material-UI
+import AccountBoxIcon from '@mui/icons-material/AccountBox'; // Importing AccountBoxIcon from Material-UI
+
+// Functional component Registration
+export default function Registration() {
+ const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom
+
+ const [showSnackbar, setShowSnackbar] = useState(false); // State to manage snackbar visibility
+ const [snackbarMessage, setSnackbarMessage] = useState(''); // State to manage snackbar message
+ const [formData, setFormData] = useState({ // State to store form data
+ firstName: '',
+ lastName: '',
+ username: '',
+ email: '',
+ password: '',
+ });
+
+ // Function to handle changes in input fields
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData((prevFormData) => ({
+ ...prevFormData,
+ [name]: value,
+ }));
+ };
+
+ // Function to handle user registration
+ const handleRegistration = async (e) => {
+ e.preventDefault();
+ try {
+ const response = await fetch('http://127.0.0.1:5000/auth/register', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(formData),
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ setSnackbarMessage(data.message); // Setting snackbar message with response data
+ setShowSnackbar(true); // Showing the snackbar
+ } else {
+ console.error('HTTP error:', response.status);
+ }
+ } catch (error) {
+ console.error('Error during submission:', error);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ Need an account?
+
+ {/* Registration form */}
+
+
+
+
+
+ {/* Other input fields */}
+
+
+ {/* Button to navigate to login page */}
+
+
+
+ {/* Snackbar for displaying messages */}
+ setShowSnackbar(false)}
+ message={
+
+ {snackbarMessage}
+
+ }
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
+ />
+
+ );
+}
diff --git a/Staff_Dashboard.jsx b/Staff_Dashboard.jsx
new file mode 100644
index 0000000..3464278
--- /dev/null
+++ b/Staff_Dashboard.jsx
@@ -0,0 +1,170 @@
+import React from 'react';
+import { Link, Outlet, useLocation } from 'react-router-dom';
+import { styled } from '@mui/system';
+import {
+ CssBaseline,
+ Drawer as MuiDrawer,
+ Box,
+ Toolbar,
+ List,
+ Divider,
+ IconButton,
+ Container,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ Typography
+} from '@mui/material';
+import {
+ AccountBox as AccountBoxIcon,
+ EditCalendar as EditCalendarIcon,
+ MedicalServices as MedicalServicesIcon,
+ Print as PrintIcon,
+ EditNote as EditNoteIcon,
+ PriorityHigh as PriorityHighIcon,
+ People as PeopleIcon,
+ Notifications as NotificationsIcon,
+ Assignment as AssignmentIcon,
+ ChevronLeft as ChevronLeftIcon,
+ ChevronRight as ChevronRightIcon
+} from '@mui/icons-material';
+
+import ClickableBox from './ClickableBox';
+
+const drawerWidth = 250;
+
+const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
+ ({ theme, open }) => ({
+ '& .MuiDrawer-paper': {
+ position: 'relative',
+ whiteSpace: 'nowrap',
+ width: drawerWidth,
+ transition: theme.transitions.create('width', {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.enteringScreen,
+ }),
+ boxSizing: 'border-box',
+ background: theme.palette.primary.main,
+ ...(!open && {
+ overflowX: 'hidden',
+ transition: theme.transitions.create('width', {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.leavingScreen,
+ }),
+ width: theme.spacing(7),
+ [theme.breakpoints.up('sm')]: {
+ width: theme.spacing(9),
+ },
+ }),
+ },
+ }),
+);
+
+export default function StaffDashboard({ username }) {
+ const [open, setOpen] = React.useState(false);
+ const location = useLocation();
+
+ const toggleDrawer = () => {
+ setOpen(!open);
+ };
+
+ return (
+
+
+
+
+
+ {open ? : }
+
+
+
+
+
+
+
+
+
+
+ Profile
+
+ }
+ />
+
+
+
+
+
+
+ Schedule
+
+ }
+ />
+
+
+
+
+
+
+ Patients
+
+ }
+ />
+
+
+
+
+
+
+ Notifications
+
+ }
+ />
+
+
+
+
+
+
+
+ {username}'s Dashboard
+
+ {location.pathname === "/staff-dashboard" && (
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/admin_routes.py b/admin_routes.py
new file mode 100644
index 0000000..faac270
--- /dev/null
+++ b/admin_routes.py
@@ -0,0 +1,223 @@
+# Admin routes
+# Endpoints for user management, app management, admin tasks
+from flask import Blueprint, jsonify, request
+from flask_cors import CORS
+from flask_jwt_extended import jwt_required, get_jwt_identity
+from utils.db_module import db
+from models.user_management_models import GlucoseLog, Appointment, Account
+from sqlalchemy.exc import SQLAlchemyError
+
+# Register admin_routes as a blueprint for importing into app.py + set up CORS
+admin_routes = Blueprint('admin_routes', __name__)
+CORS(admin_routes)
+
+# Respond 200 if Flask server is running
+@admin_routes.route('/debug', methods=['GET'])
+def debug():
+ return jsonify({'message': 'Debug check, server is running'}), 200
+
+# Serve the staff dashboard page
+@admin_routes.route('/staff-dashboard')
+def staff_dashboard():
+ return render_template('/staff_dashboard')
+
+# Serve the communication page
+@admin_routes.route('/communication')
+def communication_page():
+ return render_template('/communication')
+
+"""
+ /admin/glucose API endpoint:
+ GET:
+ - Expected fields: {user_id}
+ - Purpose: Retrieve all existing glucose logs
+ - Query database to retrieve glucose logs and return in message
+ - Response:
+ - 200: Glucose logs information retrieved
+ - 500: Database issues
+ POST:
+ - Expected fields: {user_id, glucose_level, creation_date}
+ - Purpose: Create a new glucose log entry for the user
+ - Response:
+ - 200: Glucose log information created
+ - 404: No existing glucose log
+ - 500: Database issues
+ DELETE:
+ - Expected fields: {user_id, glucose_log_id}
+ - Purpose: Remove glucose log from dashboard and database
+ - Response:
+ - 200: Glucose log deletion successful
+ - 404: No existing glucose log
+ - 500: Database issues
+ PUT:
+ - Expected fields: {user_id, glucose_level, creation_date}
+ - Purpose: Update glucose log for existing logs
+ - Response:
+ - 200: Glucose log update successful
+ - 404: No existing glucose log
+ - 500: Database issues
+"""
+@admin_routes.route('/admin/glucose', methods=['GET', 'POST', 'PUT', 'DELETE'])
+@jwt_required()
+def admin_glucose():
+ # Get JWT identity and verify if user is admin
+ token = get_jwt_identity()
+ account_id = token['account_id']
+ account = Account.query.get(account_id)
+
+ if not account or account.is_admin == 0:
+ return jsonify(message="Unauthorized access"), 401
+
+ try:
+ if request.method == 'GET':
+ # Retrieve logs for a specific user if `user_id` is provided
+ user_id = request.args.get('user_id')
+ if user_id:
+ glucose_logs = GlucoseLog.query.filter_by(user_id=user_id).all()
+ else:
+ # Retrieve all glucose logs
+ glucose_logs = GlucoseLog.query.all()
+
+ glucose_log_list = [{'id': log.id, 'user_id': log.user_id, 'glucose_level': log.glucose_level, 'creation_date': log.creation_date} for log in glucose_logs]
+ return jsonify(glucose_logs=glucose_log_list), 200
+
+ # Create new glucose log
+ elif request.method == 'POST':
+ data = request.json
+ new_glucose_log = GlucoseLog(
+ user_id=data['user_id'],
+ glucose_level=data['glucose_level'],
+ creation_date=data['creation_date']
+ )
+ db.session.add(new_glucose_log)
+ db.session.commit()
+ return jsonify(message='New glucose log created.'), 201
+
+ # Update existing glucose log
+ elif request.method == 'PUT':
+ log_id = request.args.get('log_id')
+ data = request.json
+ glucose_log = GlucoseLog.query.filter_by(id=log_id).first()
+ if not glucose_log:
+ return jsonify(message='Glucose log not found'), 404
+
+ glucose_log.glucose_level = data.get('glucose_level', glucose_log.glucose_level)
+ glucose_log.creation_date = data.get('creation_date', glucose_log.creation_date)
+ db.session.commit()
+ return jsonify(message='Glucose log updated.'), 200
+
+ # Delete a glucose log
+ elif request.method == 'DELETE':
+ log_id = request.args.get('log_id')
+ glucose_log = GlucoseLog.query.filter_by(id=log_id).first()
+ if not glucose_log:
+ return jsonify(message='Glucose log not found'), 404
+
+ db.session.delete(glucose_log)
+ db.session.commit()
+ return jsonify(message='Glucose log deleted.'), 200
+
+ # Rollback the session and handle the SQL error
+ except SQLAlchemyError as e:
+ return jsonify(message=str(e)), 500
+
+ return jsonify(message="Method not allowed"), 405
+
+
+"""
+ /admin/appointments API endpoint:
+ GET:
+ - Expected fields: {user_id}
+ - Purpose: Retrieve all existing appointments
+ - Query database to retrieve appointments and return in message
+ - Response:
+ - 200: appointments information retrieved
+ - 500: Database issues
+ POST:
+ - Expected fields: {user_id, appointments, creation_date}
+ - Purpose: Create a new appointment entry for the user
+ - Response:
+ - 200: Appointments information created
+ - 404: No existing appointment
+ - 500: Database issues
+ DELETE:
+ - Expected fields: {user_id, appointments_id}
+ - Purpose: Remove appointment from dashboard and database
+ - Response:
+ - 200: Appointment deletion successful
+ - 404: No existing appointment
+ - 500: Database issues
+ PUT:
+ - Expected fields: {user_id, appointments, creation_date}
+ - Purpose: Update appointment for existing appointments
+ - Response:
+ - 200: Appointment update successful
+ - 404: No existing appointment
+ - 500: Database issues
+"""
+@admin_routes.route('/admin/appointments', methods=['GET', 'POST', 'PUT', 'DELETE'])
+@jwt_required()
+def admin_appointments():
+ # Get JWT identity and verify if user is admin
+ token = get_jwt_identity()
+ account_id = token['account_id']
+ account = Account.query.get(account_id)
+
+ if not account or account.is_admin == 0:
+ return jsonify(message="Unauthorized access"), 401
+
+ try:
+ # Fetch all appointments
+ if request.method == 'GET':
+ user_id = request.args.get('user_id')
+ if user_id:
+ appointments = Appointment.query.filter_by(user_id=user_id).all()
+ else:
+ appointments = Appointment.query.all()
+ appointment_list = [{'id': appt.id, 'user_id': appt.user_id, 'date': appt.appointment_date, 'doctor_name': appt.doctor_name, 'notes': appt.appointment_notes} for appt in appointments]
+ return jsonify(appointments=appointment_list), 200
+
+ # Create new appointment
+ elif request.method == 'POST':
+ data = request.json
+ new_appointment = Appointment(
+ user_id=data['user_id'],
+ appointment_date=data['date'],
+ doctor_name=data['doctor_name'],
+ appointment_notes=data['notes']
+ )
+ db.session.add(new_appointment)
+ db.session.commit()
+ return jsonify(message='New appointment created.'), 201
+
+ # Update an existing appointment
+ elif request.method == 'PUT':
+ appointment_id = request.args.get('appointment_id')
+ data = request.json
+ appointment = Appointment.query.filter_by(id=appointment_id).first()
+ if not appointment:
+ return jsonify(message='Appointment not found'), 404
+
+ appointment.appointment_date = data.get('date', appointment.appointment_date)
+ appointment.doctor_name = data.get('doctor_name', appointment.doctor_name)
+ appointment.appointment_notes = data.get('notes', appointment.appointment_notes)
+ db.session.commit()
+ return jsonify(message='Appointment updated.'), 200
+
+ # Delete an appointment
+ elif request.method == 'DELETE':
+ appointment_id = request.args.get('appointment_id')
+ appointment = Appointment.query.filter_by(id=appointment_id).first()
+ if not appointment:
+ return jsonify(message='Appointment not found'), 404
+
+ db.session.delete(appointment)
+ db.session.commit()
+ return jsonify(message='Appointment deleted.'), 200
+
+ # Rollback the session and handle the SQL error
+ except SQLAlchemyError as e:
+ db.session.rollback()
+ return jsonify(message=str(e)), 500
+
+ return jsonify(message="Method not allowed"), 405
\ No newline at end of file
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..748ef86
--- /dev/null
+++ b/app.py
@@ -0,0 +1,139 @@
+from flask import Flask, request, jsonify
+from flask_cors import CORS
+from flask_jwt_extended import JWTManager
+
+from routes.dashboard_routes import dashboard_routes
+from routes.auth_routes import auth_routes
+from config import Config
+from models.db_module import db
+from datetime import datetime
+from flask_jwt_extended import jwt_required, get_jwt_identity
+
+import os
+import base64
+import sqlite3
+
+app = Flask(__name__)
+app.config.from_object(Config)
+jwt = JWTManager(app)
+CORS(app)
+
+db.init_app(app)
+
+app.register_blueprint(auth_routes, url_prefix='/auth')
+app.register_blueprint(dashboard_routes, url_prefix='/dashboard')
+app.register_blueprrint(staff_dashboard_routes, url_prefix='/staff_dashboard')
+
+
+# Product Page
+def get_productsDB_connection():
+ database_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'database', 'products.db')
+ conn = sqlite3.connect(database_path)
+ conn.row_factory = sqlite3.Row
+ return conn
+
+@app.route('/products', methods=['GET'])
+def products():
+ conn = get_productsDB_connection()
+ products = conn.execute('SELECT * FROM product').fetchall()
+ products_list = []
+ for product in products:
+ product_dict = dict(product)
+ if product_dict['image']:
+ product_dict['image'] = base64.b64encode(product_dict['image']).decode('utf-8')
+ products_list.append(product_dict)
+ conn.close()
+ return jsonify(products_list)
+
+
+# Glucose Log Page
+def get_glucose_log_db_connection():
+ database_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'database', 'glucose_log.db')
+ conn = sqlite3.connect(database_path)
+ conn.row_factory = sqlite3.Row
+ return conn
+
+@app.route('/glucose', methods=['POST'])
+@jwt_required()
+def add_glucose_log():
+ current_user_id = get_jwt_identity()
+ data = request.json
+
+ # Combind date and time into datetime
+ datetime_str = f"{data['date']} {data['time']}"
+ log_datetime = datetime.strptime(datetime_str, '%Y-%m-%d %H:%M')
+
+ conn = get_glucose_log_db_connection()
+ conn.execute('INSERT INTO glucose_logs (user_id, glucose_level, log_timestamp) VALUES (?, ?, ?)',
+ (current_user_id, data['glucose_level'], log_datetime))
+ conn.commit()
+ conn.close()
+ return jsonify({'message': 'Log added successfully'}), 201
+
+@app.route('/glucose/', methods=['DELETE'])
+@jwt_required()
+def delete_glucose_log(log_id):
+ current_user_id = get_jwt_identity()
+ conn = get_glucose_log_db_connection()
+ log_owner = conn.execute('SELECT user_id FROM glucose_logs WHERE log_id = ?', (log_id,)).fetchone()
+ if log_owner is None or log_owner['user_id'] != current_user_id:
+ return jsonify({'message': 'Unauthorized to delete this log'}), 403
+ conn.execute('DELETE FROM glucose_logs WHERE log_id = ?', (log_id,))
+ conn.commit()
+ conn.close()
+ return jsonify({'message': 'Log deleted successfully'}), 200
+
+@app.route('/glucose', methods=['GET'])
+@jwt_required()
+def get_glucose_logs():
+ current_user_id = get_jwt_identity()
+ conn = get_glucose_log_db_connection()
+ logs = conn.execute('SELECT * FROM glucose_logs WHERE user_id = ?', (current_user_id,)).fetchall()
+ conn.close()
+ return jsonify([dict(log) for log in logs])
+
+
+# Appointment Page
+def get_appointmentDB_connection():
+ database_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'database', 'user_management.db')
+ conn = sqlite3.connect(database_path)
+ conn.row_factory = sqlite3.Row
+ return conn
+
+@app.route('/appointments', methods=['POST'])
+def add_appointment():
+ data = request.json
+ conn = get_appointmentDB_connection()
+ conn.execute('INSERT INTO appointments (user_id, appointment_date, appointment_time, doctor_name, appointment_notes) VALUES (?, ?, ?, ?, ?)',
+ (data['user_id'], data['date'], data['time'], data['doctor_name'], data['notes']))
+ conn.commit()
+ conn.close()
+ return jsonify({'message': 'Appointment added successfully'}), 201
+
+@app.route('/appointments/', methods=['DELETE'])
+def delete_appointment(appointment_id):
+ conn = get_appointmentDB_connection()
+ conn.execute('DELETE FROM appointments WHERE appointment_id = ?', (appointment_id,))
+ conn.commit()
+ conn.close()
+ return jsonify({'message': 'Appointment deleted successfully'}), 200
+
+@app.route('/appointments', methods=['GET'])
+def get_appointments():
+ conn = get_appointmentDB_connection()
+ appointments = conn.execute('SELECT * FROM appointments').fetchall()
+ conn.close()
+ return jsonify([dict(appointment) for appointment in appointments])
+
+@app.route('/staff_dashboard', methods=['GET'])
+def serve_static(path):
+ return jsonify([dict(staff_dashboard) for dashboard in staff_dashboard])
+
+@app.route('/staff_communication', methods=['GET'])
+def serve_static(path):
+ return jsonify([dict(staff_communication) for dashboard in staff_communication])
+
+
+
+if __name__ == '__main__':
+ app.run(debug=True)
\ No newline at end of file
diff --git a/main.jsx b/main.jsx
new file mode 100644
index 0000000..b486644
--- /dev/null
+++ b/main.jsx
@@ -0,0 +1,36 @@
+import React from 'react'; // Importing React library
+import ReactDOM from 'react-dom/client'; // Importing ReactDOM for rendering
+import App from './App.jsx'; // Importing the main App component
+import '@fontsource/roboto/300.css'; // Importing Roboto font styles
+import '@fontsource/roboto/400.css';
+import '@fontsource/roboto/500.css';
+import '@fontsource/roboto/700.css';
+import { ThemeProvider, createTheme} from '@mui/material/styles'; // Importing ThemeProvider and createTheme from Material-UI
+
+// Creating a custom theme for the application
+const theme = createTheme({
+ components: {
+ MuiButton: { // Customizing the style of MuiButton component
+ styleOverrides: {
+ root: {
+ textTransform: 'none', // Disabling text transformation for MuiButton
+ },
+ },
+ },
+ },
+ palette: {
+ primary: {
+ main: '#0866ff' // Defining the main color for the primary palette
+ },
+ },
+});
+
+// Rendering the application inside the root element
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+ {/* Wrapping the entire application with ThemeProvider to provide the custom theme */}
+
+ {/* Rendering the main App component */}
+
+ ,
+);
diff --git a/products.jsx b/products.jsx
new file mode 100644
index 0000000..22c7d6a
--- /dev/null
+++ b/products.jsx
@@ -0,0 +1,86 @@
+import React, { useEffect, useState } from 'react'; // Importing necessary modules from React
+import { useNavigate } from "react-router-dom"; // Importing useNavigate hook from react-router-dom
+import { Button, CircularProgress, Grid } from '@mui/material'; // Importing necessary components from Material-UI
+import ArrowBackIcon from '@mui/icons-material/ArrowBack'; // Importing ArrowBackIcon from Material-UI
+
+// Functional component ShowProducts
+function ShowProducts() {
+ const [products, setProducts] = useState([]); // State to store products fetched from the backend
+ const [loading, setLoading] = useState(true); // State to manage loading state
+ const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom
+
+ // useEffect hook to fetch products when the component mounts
+ useEffect(() => {
+ // Fetching products from the backend
+ fetch('http://localhost:5000/products')
+ .then(response => response.json()) // Parsing response data as JSON
+ .then(data => {
+ setProducts(data); // Updating products state with fetched data
+ setLoading(false); // Setting loading state to false after data is fetched
+ })
+ .catch(error => {
+ console.error("There was an error!", error); // Logging error to the console
+ setLoading(false); // Setting loading state to false in case of error
+ });
+ }, []);
+
+ // Function to group products into pairs for grid layout
+ const groupProductsIntoPairs = (products) => {
+ return products.reduce((resultArray, item, index) => {
+ const chunkIndex = Math.floor(index / 2);
+
+ if (!resultArray[chunkIndex]) {
+ resultArray[chunkIndex] = [];
+ }
+
+ resultArray[chunkIndex].push(item);
+
+ return resultArray;
+ }, []);
+ };
+
+ return (
+
+ {/* Button to navigate back */}
+ } // Icon for back arrow
+ onClick={() => navigate('/')} // Click event to navigate back to home
+ sx={{ mb: 2, width: '150px', height: '40px' }} // Custom styles for button
+ >
+ Go Back
+
+ {/* Header section */}
+
+
Products
+ {/* Displaying loading spinner if data is being fetched */}
+ {loading ? (
+
+ ) : (
+ // Grid layout to display products
+
+ {/* Mapping through grouped product pairs and rendering them */}
+ {groupProductsIntoPairs(products).map((productPair, index) => (
+
+ {/* Mapping through product pair and rendering individual product cards */}
+ {productPair.map(product => (
+
+
{product.model_name}
+
{product.description}
+ {/* Rendering product image if available */}
+ {product.image && (
+
+ )}
+