diff --git a/App.jsx b/App.jsx new file mode 100644 index 0000000..703cbdc --- /dev/null +++ b/App.jsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; // Importing React and useState hook +import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom'; // Importing necessary components from react-router-dom + +// Importing Components +import Header from './components/Header'; // Header component +import Login from './components/Login'; // Login component +import HelpInfo from './components/HelpInfo'; // HelpInfo component +import Contact from './components/Contact'; // Contact component +import Dashboard from './components/Dashboard'; // Dashboard component +import Registration from './components/Registration'; // Registration component +import ForgotPassword from './components/ForgotPassword'; // ForgotPassword component +import GlucoseLogs from './components/GlucoseLogs'; // GlucoseLogs component +import Profile from './components/Profile'; // Profile component +import Appointment from './components/Appointment'; // Appointment component +import Notifications from './components/Notifications'; // Notifications component +import MedicalForms from './components/MedicalForms'; // MedicalForms component +import Products from './components/products'; // Products component + +// Main functional component AimPlusMedicalSupplies +export default function AimPlusMedicalSupplies() { + const [isLoggedIn, setIsLoggedIn] = useState(false); // State to track user's login status + const [username, setUsername] = useState(''); // State to store the username + + return ( + +
+ {/* Header component */} +
+ + + {/* Route for the home page, redirects to dashboard if logged in, otherwise shows login component */} + : } /> + + {/* Route for dashboard and its child routes */} + : } > + {/* Child routes of dashboard */} + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* Route for help page */} + } /> + + {/* Route for forgot password page */} + } /> + + {/* Route for registration page */} + } /> + + {/* Route for contact page */} + } /> + +
+
+ ) +} diff --git a/Appointment.jsx b/Appointment.jsx new file mode 100644 index 0000000..988fc66 --- /dev/null +++ b/Appointment.jsx @@ -0,0 +1,237 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Button, CssBaseline, TextField, Container, Box, Typography, Paper, Grid } from '@mui/material'; +import { ArrowBack as ArrowBackIcon } from '@mui/icons-material'; +import jsPDF from 'jspdf'; +import 'jspdf-autotable'; + +export default function Appointment() { + const navigate = useNavigate(); + const [dateTime, setDateTime] = useState(''); + const [doctorName, setDoctorName] = useState(''); + const [notes, setNotes] = useState(''); + const [appointments, setAppointments] = useState([]); + const token = localStorage.getItem('jwtToken'); + + useEffect(() => { fetchAppointments(); }, []); + + const formatDateTime = (dateTimeString) => { + const date = new Date(dateTimeString); + return date.toISOString().slice(0, 16); + }; + + const fetchAppointments = async () => { + try { + const response = await fetch('http://127.0.0.1:5000/dashboard/appointments', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }); + if (response.ok) { + const responseData = await response.json(); + setAppointments(responseData.appointments); + } + } catch (error) { + console.error('Error fetching appointments:', error); + } + }; + + const recordAppointment = async () => { + const appointment = { date: dateTime, doctor_name: doctorName, notes: notes }; + try { + const response = await fetch('http://127.0.0.1:5000/dashboard/appointments', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify(appointment), + }); + if (response.ok) { + console.log('Appointment recorded successfully'); + fetchAppointments(); + } + } catch (error) { + console.error('Error recording appointment:', error); + } + }; + + const deleteAppointment = async (appointmentID) => { + try { + const response = await fetch(`http://127.0.0.1:5000/dashboard/appointments?appointment_id=${appointmentID}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + if (response.ok) { + console.log('Appointment deleted successfully'); + fetchAppointments(); + } + } catch (error) { + console.error('Error deleting appointment:', error); + } + }; + + const exportToPDF = () => { + const doc = new jsPDF(); + const tableColumn = ["Date & Time", "Doctor Name", "Notes"]; + let sortedAppointments = [...appointments]; + sortedAppointments.sort((a, b) => new Date(a.date) - new Date(b.date)); + const tableRows = []; + + sortedAppointments.forEach(appointment => { + const formattedDateTime = new Date(appointment.date).toLocaleString(); + const appointmentData = [ + formattedDateTime, + appointment.doctor_name, + appointment.notes + ]; + tableRows.push(appointmentData); + }); + + const currentDate = new Date(); + const formattedDate = currentDate.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '_'); + const fileName = `appointments_${formattedDate}.pdf`; + + const title = `Your Appointments\nGenerated: ${currentDate.toLocaleString()}`; + doc.text(title, 14, 20); + + doc.autoTable({ + head: [tableColumn], + body: tableRows, + startY: 35, + theme: 'grid', + styles: { + overflow: 'linebreak', + cellWidth: 'wrap', + cellPadding: 2, + }, + columnStyles: { + 0: { cellWidth: 'auto' }, + 1: { cellWidth: 'auto' }, + 2: { cellWidth: 'auto' }, + }, + margin: { top: 20 }, + tableWidth: 'auto', + tableHeight: 'auto', + }); + + doc.save(fileName); + }; + + return ( + + + + + + + + Appointment Scheduler + + setDateTime(e.target.value)} + /> + setDoctorName(e.target.value)} + /> + setNotes(e.target.value)} + /> + + + + + + + + Scheduled Appointments: + + {appointments + .sort((a, b) => new Date(a.date) - new Date(b.date)) + .map((appointment) => + + { + const appointmentDate = new Date(appointment.date); + const formattedDate = appointmentDate.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }); + const formattedTime = appointmentDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); + + return ( + + + + {formattedDate} at {formattedTime} + + + + + + Dr. {appointment.doctor_name} + + + {appointment.notes} + + + + ); + })} + + + + + ); +} \ No newline at end of file diff --git a/ClickableBox.jsx b/ClickableBox.jsx new file mode 100644 index 0000000..7cf4d70 --- /dev/null +++ b/ClickableBox.jsx @@ -0,0 +1,47 @@ +import React from "react"; // Importing necessary modules from React +import { Link } from "react-router-dom"; // Importing Link component from react-router-dom +import { Box, Typography } from "@mui/material"; // Importing Box and Typography components from Material-UI + +// Functional component ClickableBox +const ClickableBox = ({ title, path, icon: Icon }) => { + return ( + // Link component to navigate to the specified path when clicked + + {/* Box component representing the clickable box */} + + {/* Box component to center content vertically and horizontally */} + + {/* Rendering icon if provided */} + {Icon && } + {/* Rendering title */} + + {title} + + + + + ); +}; + +export default ClickableBox; // Exporting the component as the default export diff --git a/Communication.jsx b/Communication.jsx new file mode 100644 index 0000000..ddde1b2 --- /dev/null +++ b/Communication.jsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import { Box, TextField, Button, Typography } from '@mui/material'; + +export default function CommunicationPage() { + const [message, setMessage] = useState(''); + const [messages, setMessages] = useState([]); + + // Function to handle sending a message + const sendMessage = () => { + if (message.trim() !== '') { + // Add the new message to the list of messages + setMessages([...messages, message]); + // Clear the message input field + setMessage(''); + } + }; + + return ( + + + Communication Tools + + {/* Render the list of messages */} + + {messages.map((msg, index) => ( + + {msg} + + ))} + + {/* Input field for typing a new message */} + setMessage(e.target.value)} + sx={{ width: '100%', mb: 2 }} + /> + {/* Button to send the message */} + + + ); +} diff --git a/Contact.jsx b/Contact.jsx new file mode 100644 index 0000000..c04774f --- /dev/null +++ b/Contact.jsx @@ -0,0 +1,203 @@ +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, + Container, + Box, + Typography, + Link, + TextField, + Grid, +} from "@mui/material"; // Importing specific components from Material-UI +import LocationOnIcon from "@mui/icons-material/LocationOn"; // Importing LocationOnIcon from Material-UI icons +import PhoneIcon from "@mui/icons-material/Phone"; // Importing PhoneIcon from Material-UI icons +import EmailIcon from "@mui/icons-material/Email"; // Importing EmailIcon from Material-UI icons + +// Functional component Contact +export default function Contact() { + const navigate = useNavigate(); // Initializing useNavigate hook for navigation + + const [responseMessage, setResponseMessage] = useState(''); // State variable to manage response message + const [formData, setFormData] = useState({ // State variable to manage form data + name: '', + email: '', + message: '', + }); + + // Function to handle input change in the form fields + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData((prevFormData) => ({ + ...prevFormData, + [name]: value, + })); + }; + + // Function to handle form submission + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const response = await fetch('http://127.0.0.1:5000/services/contact', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + const data = await response.json(); + if (data.status === 'success') { + setResponseMessage(data.message); + setFormData({ + email: '', + name: '', + message: '', + }); + } else { + setResponseMessage(data.message); + } + } else { + setErrorMessage('HTTP error: ' + response.status); + } + } catch (error) { + setErrorMessage('An error occurred: ' + error.message + '.'); + } +}; + + return ( + // Container component to contain the contact form and information + + + {/* Grid component to layout the content */} + + {/* Grid item for displaying address and contact information */} + + + {/* Avatar and Typography components for displaying address */} + + + + + Our Address + + + 6521 AL Hwy 69 S, Suit N, +
+ Tuscaloosa, AL 35405 +
+ + {/* Avatar and Typography components for displaying phone number */} + + + + + Phone + + (866)-919-1246 + + {/* Avatar and Typography components for displaying email */} + + + + + Email + + info@aimplusmedicalsupplies.com + + http://www.aimplusmedicalsupplies.com + +
+
+ {/* Grid item for contact form */} + + + + Send us a Message + + {/* Form for sending a message */} +
+ + + + {/* Button to submit the form */} + + + {/* Button to navigate back */} + + {/* Displaying response message if any */} + {responseMessage && ( + + {responseMessage} + + )} +
+
+
+
+ ); +}; diff --git a/Dashboard.jsx b/Dashboard.jsx index 4fc41c6..0e996ef 100644 --- a/Dashboard.jsx +++ b/Dashboard.jsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; -import { styled, createTheme, ThemeProvider } from '@mui/system'; +import React from 'react'; +import { Link, Outlet, useLocation } from 'react-router-dom'; +import { styled } from '@mui/system'; import { CssBaseline, Drawer as MuiDrawer, @@ -9,79 +10,63 @@ import { Divider, IconButton, Container, - Typography + ListItemButton, + ListItemIcon, + ListItemText, + Typography, } from '@mui/material'; -import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; -import { mainListItems, secondaryListItems } from './ListItems'; - -const drawerWidth = 250; +import { + EditCalendar as EditCalendarIcon, + MedicalServices as MedicalServicesIcon, + Print as PrintIcon, + Notifications as NotificationsIcon, + People as PeopleIcon, + Assignment as AssignmentIcon, + ChevronLeft as ChevronLeftIcon, + ChevronRight as ChevronRightIcon, +} from '@mui/icons-material'; +import ClickableBox from './ClickableBox'; -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', - ...(!open && { - overflowX: 'hidden', +const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( + ({ theme, open }) => ({ + '& .MuiDrawer-paper': { + position: 'relative', + whiteSpace: 'nowrap', + width: 250, transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, + duration: theme.transitions.duration.enteringScreen, + }), + boxSizing: 'border-box', + background: '#1a73e8', // Updated color + color: 'white', // Updated text color + ...(!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), + }, }), - width: theme.spacing(7), - [theme.breakpoints.up('sm')]: { - width: theme.spacing(9), - }, - }), - }, -})); + }, + }) +); -export default function Dashboard() { - const [open, setOpen] = React.useState(true); +export default function Dashboard({ role, username }) { + const [open, setOpen] = React.useState(false); + const location = useLocation(); const toggleDrawer = () => { setOpen(!open); }; -const ClickableBox = ({ title, onClick }) => { - return ( - - - - {title} - - - - ); -}; - -const handleClick = (route) => { - // You can handle navigation to different routes here - console.log('Navigating to:', route); - }; - return ( - + {/* Updated background color */} - + { px: [1], }} > - - + + {open ? : } - {mainListItems} - - {secondaryListItems} + {role === 'admin' ? ( + <> + + + + + + Glucose Logs + + } + /> + + + + + + + Appointments + + } + /> + + + + + + + Notifications + + } + /> + + + ) : ( + <> + + + + + + Glucose Logs + + } + /> + + + + + + + Appointments + + } + /> + + + + + + + Notifications + + } + /> + + + )} - theme.palette.mode === 'light' - ? theme.palette.grey[100] - : theme.palette.grey[900], flexGrow: 1, height: '100vh', overflow: 'auto', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', // Updated for centering + padding: '20px', // Updated for spacing }} > - - - Dashboard - - - handleClick('/glucose')} /> - handleClick('/?')} /> - handleClick('./ListItems')} /> - handleClick('/?')} /> - {/* Add more clickable boxes as needed */} - - - - - ); - } \ No newline at end of file + {location.pathname === "/dashboard" && ( + {/* Centered and gap updated */} + {role === 'admin' ? ( + <> + + + + + + ) : ( + <> + + + + + + +)} + +)} + + + + +); +} diff --git a/ForgotPassword.jsx b/ForgotPassword.jsx new file mode 100644 index 0000000..c878c2d --- /dev/null +++ b/ForgotPassword.jsx @@ -0,0 +1,127 @@ +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, + Container, + Box, + Typography, + Snackbar, // Importing specific components from Material-UI +} from '@mui/material'; // Importing necessary components from Material-UI +import EmailIcon from '@mui/icons-material/Email'; // Importing EmailIcon from Material-UI icons + +// Functional component ForgotPassword +export default function ForgotPassword() { + const navigate = useNavigate(); // Getting navigation function using useNavigate hook from react-router-dom + + // State variables for response message and form data + const [responseMessage, setResponseMessage] = useState(''); + const [formData, setFormData] = useState({ + email: '', + }); + + // Function to handle input changes in the form + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData((prevFormData) => ({ + ...prevFormData, + [name]: value, + })); + }; + + // Function to handle form submission + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const response = await fetch('http://127.0.0.1:5000/auth/forgot-password', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + const data = await response.json(); + if (data.status === 'success') { + // Setting response message and resetting form data if successful + setResponseMessage(data.message); + setFormData({ + email:'', + }); + } else { + // Setting response message if unsuccessful + setResponseMessage(data.message); + } + } else { + console.error('HTTP error:', response.status) + } + } catch (error) { + console.error('Error during submission:', error); + } + }; + + return ( + // Main container for the forgot password form + + + + {/* Avatar for the email icon */} + + + + {/* Title for the forgot password form */} + + Forgot password? We can help! + + {/* Form for submitting email for password recovery */} + + + {/* Button to submit the form */} + + {/* Button to navigate back */} + + {/* Display response message */} + {responseMessage && ( + + {responseMessage} + + )} + + + + ); +} diff --git a/GlucoseLogs.jsx b/GlucoseLogs.jsx new file mode 100644 index 0000000..66c346e --- /dev/null +++ b/GlucoseLogs.jsx @@ -0,0 +1,283 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Button, CssBaseline, TextField, Container, Box, Typography, Paper, Grid, Table, TableHead, TableBody, TableRow, TableCell } from '@mui/material'; +import { ArrowBack as ArrowBackIcon } from '@mui/icons-material'; +import { VictoryChart, VictoryLine, VictoryTooltip } from 'victory'; +import jsPDF from 'jspdf'; +import 'jspdf-autotable'; + +export default function GlucoseLogs({ username }) { + const navigate = useNavigate(); + const [dateTime, setDateTime] = useState(''); + const [glucoseLevel, setGlucoseLevel] = useState(''); + const [logs, setLogs] = useState([]); + const token = localStorage.getItem('jwtToken'); + + useEffect(() => { + fetchLogs(); + }, []); + + const formatDateTime = (dateTimeString) => { + const date = new Date(dateTimeString); + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}`; + }; + + const fetchLogs = async () => { + try { + const response = await fetch('http://127.0.0.1:5000/dashboard/glucose', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }); + if (response.ok) { + const responseData = await response.json(); + setLogs(responseData.glucose_logs); + } + } catch (error) { + console.error('Error fetching logs:', error); + } + }; + + const recordLog = async () => { + const log = { glucose_level: glucoseLevel, creation_date: dateTime }; + try { + const response = await fetch('http://127.0.0.1:5000/dashboard/glucose', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify(log), + }); + if (response.ok) { + console.log('Log recorded successfully'); + fetchLogs(); + } + } catch (error) { + console.error('Error recording log:', error); + } + }; + + const deleteLog = async (logID) => { + try { + const response = await fetch(`http://127.0.0.1:5000/dashboard/glucose?glucose_log_id=${logID}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + if (response.ok) { + console.log('Log deleted successfully'); + fetchLogs(); + } + } catch (error) { + console.error('Error deleting log:', error); + } + }; + + const exportToPDF = () => { + const doc = new jsPDF(); + const tableColumn = ["Date & Time", "Glucose Level (mg/dL)"]; + let sortedLogs = [...logs]; + sortedLogs.sort((a, b) => new Date(a.creation_date) - new Date(b.creation_date)); + const tableRows = []; + + sortedLogs.forEach(log => { + const formattedDateTime = formatDateTime(log.creation_date); + const logData = [ + formattedDateTime, + log.glucose_level + ]; + tableRows.push(logData); + }); + + const currentDate = new Date(); + const formattedDate = currentDate.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '_'); + const fileName = `glucose_logs_${formattedDate}.pdf`; + + const title = `Your Glucose Logs\nGenerated: ${currentDate.toLocaleString()}`; + doc.text(title, 14, 20); + + doc.autoTable({ + head: [tableColumn], + body: tableRows, + startY: 35, + theme: 'grid', + styles: { + overflow: 'linebreak', + cellWidth: 'wrap', + cellPadding: 2, + }, + columnStyles: { + 0: { cellWidth: 'auto' }, + 1: { cellWidth: 'auto' }, + }, + margin: { top: 20 }, + tableWidth: 'auto', + tableHeight: 'auto', + }); + + doc.save(fileName); + }; + + const GlucoseChart = ({ logs }) => { + const formattedLogs = logs.map(log => ({ + x: new Date(log.creation_date), + y: log.glucose_level, + label: `Date: ${new Date(log.creation_date).toLocaleDateString()}, Glucose Level: ${log.glucose_level}` + })); + + return ( +
+

Your Glucose History

+ + } + data={formattedLogs} + x="x" + y="y" + labels={({ datum }) => datum.label} + style={{ + data: { stroke: "#007bff", strokeWidth: 5 } + }} + /> + +
+ ); + }; + + + return ( + + + + + + + + + Add New Logs + + + setDateTime(formatDateTime(e.target.value))} + /> + setGlucoseLevel(e.target.value)} + /> + + + + + + + + + Previous Glucose Logs: + + + + + + Date & Time + Glucose Level (mg/dL) + Delete + + + + {logs + .slice() + .sort((a, b) => new Date(b.creation_date) - new Date(a.creation_date)) + .map((log) => { + const logDate = new Date(log.creation_date); + const formattedDate = logDate.toLocaleDateString(undefined, { + year: 'numeric', + month: 'long', + day: 'numeric', + }); + const formattedTime = logDate.toLocaleTimeString(undefined, { + hour: '2-digit', + minute: '2-digit', + }); + + return ( + + {formattedDate} at {formattedTime} + {log.glucose_level} mg/dL + + + + + ); + })} + +
+
+
+
+
+
+ ); +} \ 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 */} + + 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 */} + + {/* 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 */} + + {/* 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 */} + + + ))} + + )} +
+ ); +}; + +export default Notifications; // Exporting Notifications component diff --git a/Products.jsx b/Products.jsx new file mode 100644 index 0000000..c249af0 --- /dev/null +++ b/Products.jsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from "react-router-dom"; +import { Button, CircularProgress, Grid } from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; + +export default function Products() { + const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await fetch('http://127.0.0.1:5000/dashboard/products') + if (!response.ok) { + throw new Error('Failed to fetch products'); + } + const data = await response.json(); + setProducts(data); + setLoading(false); + } catch (error) { + console.error("There was an error!", error); + setLoading(false); + } + }; + fetchData(); + }, []); + + 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 ( +
+ +
+

Products

+ {loading ? ( + + ) : ( + + {groupProductsIntoPairs(products).map((productPair, index) => ( + + {productPair.map(product => ( +
+

{product.model_name}

+

{product.description}

+ {product.image && ( + {product.model_name} + )} +
+ ))} +
+ ))} +
+ )} +
+
+ ); +} \ 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 */} + + + + + {/* Input fields for editing profile information */} + Edit Profile Information + + {/* Other input fields */} + + + + ) : ( + <> {/* Fragment */} + + 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 */} + + {/* 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 && ( + {product.model_name} + )} +
+ ))} +
+ ))} +
+ )} +
+
+ ); +} + +export default ShowProducts; // Exporting ShowProducts component