From 561a56fbfc37fc3b80b1d560d37954a9a90b509a Mon Sep 17 00:00:00 2001 From: ness-miewald <83977281+ness-miewald@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:01:28 -0700 Subject: [PATCH 1/5] Commited Frontend Frontend was cleaned up and added commits. --- App.jsx | 60 ++++++++++ Appointment.jsx | 176 +++++++++++++++++++++++++++++ ClickableBox.jsx | 47 ++++++++ Contact.jsx | 203 ++++++++++++++++++++++++++++++++++ Dashboard.jsx | 269 +++++++++++++++++++++++++++------------------ ForgotPassword.jsx | 127 +++++++++++++++++++++ GlucoseLogs.jsx | 148 +++++++++++++++++++++++++ Header.jsx | 61 ++++++++++ HelpInfo.jsx | 92 ++++++++++++++++ Login.jsx | 178 ++++++++++++++++++++++++++++++ MedicalForms.jsx | 55 +++++++++ Notifications.jsx | 78 +++++++++++++ Profile.jsx | 149 +++++++++++++++++++++++++ Registration.jsx | 130 ++++++++++++++++++++++ main.jsx | 36 ++++++ products.jsx | 86 +++++++++++++++ 16 files changed, 1788 insertions(+), 107 deletions(-) create mode 100644 App.jsx create mode 100644 Appointment.jsx create mode 100644 ClickableBox.jsx create mode 100644 Contact.jsx create mode 100644 ForgotPassword.jsx create mode 100644 GlucoseLogs.jsx create mode 100644 Header.jsx create mode 100644 HelpInfo.jsx create mode 100644 Login.jsx create mode 100644 MedicalForms.jsx create mode 100644 Notifications.jsx create mode 100644 Profile.jsx create mode 100644 Registration.jsx create mode 100644 main.jsx create mode 100644 products.jsx 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..543f190 --- /dev/null +++ b/Appointment.jsx @@ -0,0 +1,176 @@ +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 { Avatar, Button, CssBaseline, TextField, Container, Box, Typography, Paper } from '@mui/material'; // Importing specific components from Material-UI +import { ArrowBack as ArrowBackIcon} from '@mui/icons-material'; // Importing ArrowBackIcon from Material-UI icons + +// Functional component Appointment +const Appointment = () => { + // State variables to manage appointments, doctor name, date & time, and notes + const [appointments, setAppointments] = useState([]); // State variable to store appointments + const [doctor, setDoctor] = useState(''); // State variable to store doctor's name + const [dateTime, setDateTime] = useState(''); // State variable to store date & time + const [notes, setNotes] = useState(''); // State variable to store notes + + const token = localStorage.getItem('jwtToken'); // Retrieving JWT token from localStorage + const navigate = useNavigate(); // Initializing useNavigate hook for navigation + + // Fetch appointments from the backend on component mount + useEffect(() => { + fetchAppointments(); + }, []); + + // Function to format date + 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 appointments from the backend + const fetchAppointments = async () => { + 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(); + const { appointments } = responseData.data; + setAppointments(appointments); + } + }; + + // Function to record a new appointment + const recordAppointment = async () => { + const appointment = { doctor, dateTime, notes }; + 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(); // Fetch appointments after recording + } + }; + + // Function to delete an appointment + const deleteAppointment = async (appointmentId) => { + const response = await fetch('http://127.0.0.1:5000/dashboard/appointments', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ appointment_id: appointmentId }), + }); + if (response.ok) { + console.log('Appointment deleted successfully'); + fetchAppointments(); // Fetch appointments after deletion + } + }; + + // JSX to render the component + return ( + + + {/* Button to navigate back to previous page */} + + + + Appointment Scheduler + + {/* Form to schedule a new appointment */} + + setDateTime(e.target.value)} + /> + setDoctor(e.target.value)} + /> + setNotes(e.target.value)} + /> + {/* Button to schedule the appointment */} + + + + {/* Displaying scheduled appointments */} + {appointments && ( + + + Scheduled Appointments + + {appointments.map((appointment) => ( + + + {appointment.doctor_name} - {appointment.appointment_notes} at {formatDate(appointment.appointment_date)} + + {/* Button to delete the appointment */} + + + ))} + + )} + + ); +}; + +export default Appointment; // Exporting the component 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/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..eecc14e 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'; // Importing necessary modules from React +import { Link, Outlet, useLocation } from 'react-router-dom'; // Importing Link, Outlet, and useLocation from react-router-dom +import { styled } from '@mui/system'; // Importing styled from MUI system import { CssBaseline, Drawer as MuiDrawer, @@ -9,124 +10,178 @@ import { Divider, IconButton, Container, + ListItemButton, + ListItemIcon, + ListItemText, Typography -} from '@mui/material'; -import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; -import { mainListItems, secondaryListItems } from './ListItems'; +} from '@mui/material'; // Importing specific components from Material-UI +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'; // Importing specific icons from Material-UI icons + +import ClickableBox from './ClickableBox'; // Importing ClickableBox component from local file -const drawerWidth = 250; +const drawerWidth = 250; // Defining drawer width -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', +// Styling the Drawer component using styled from MUI system +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.leavingScreen, + 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), + }, }), - width: theme.spacing(7), - [theme.breakpoints.up('sm')]: { - width: theme.spacing(9), - }, - }), - }, -})); + }, + }), +); -export default function Dashboard() { - const [open, setOpen] = React.useState(true); +// Functional component Dashboard +export default function Dashboard({username}) { + const [open, setOpen] = React.useState(false); // State variable to manage drawer open/close + const location = useLocation(); // Getting current location using useLocation hook from react-router-dom + // Function to toggle drawer open/close 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 ( - - - - + + {/* Drawer component for navigation */} + + {/* Toolbar for drawer */} + + {/* IconButton to toggle drawer */} + + {open ? : } + + + + {/* List for navigation links */} + + + {/* List item for Profile */} + + + + + + Profile + + } + /> + + {/* List item for Glucose Logs */} + + + + + + Glucose Logs + + } + /> + + {/* List item for Appointments */} + + + + + + Appointments + + } + /> + + {/* List item for Notifications */} + + + + + + Notifications + + } + /> + + + + + - - - - - - - {mainListItems} - - {secondaryListItems} - - - - theme.palette.mode === 'light' - ? theme.palette.grey[100] - : theme.palette.grey[900], - flexGrow: 1, - height: '100vh', - overflow: 'auto', - }} - > - - - - Dashboard - - - handleClick('/glucose')} /> - handleClick('/?')} /> - handleClick('./ListItems')} /> - handleClick('/?')} /> - {/* Add more clickable boxes as needed */} - - - + {/* Container for main content */} + + {/* Typography for dashboard title */} + + {username}'s Dashboard + + {/* Conditionally rendering ClickableBox components based on location */} + {location.pathname === "/dashboard" && ( + + + + + + + + + )} + {/* Rendering nested routes */} + + + ); - } \ No newline at end of file +} 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..d3aace9 --- /dev/null +++ b/GlucoseLogs.jsx @@ -0,0 +1,148 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Button, CssBaseline, TextField, Container, Box, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from '@mui/material'; +import { ArrowBack as ArrowBackIcon} from '@mui/icons-material'; + +function GlucoseLogs() { + const [dateTime, setDateTime] = useState(''); + const [glucoseLevel, setGlucoseLevel] = useState(''); + const [logs, setLogs] = useState([]); + + const token = localStorage.getItem('jwtToken'); + const navigate = useNavigate(); + + useEffect(() => { + fetchLogs(); + }, []); + + 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); + }; + + const fetchLogs = async () => { + 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(); + const { logs } = responseData.data; + setLogs(logs); + } + } + + const recordLog = async () => { + const log = { date, time, glucoseLevel }; + const response = await fetch('/api/glucose', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify(log), + }); + if (response.ok) { + console.log('Log recorded successfully'); + loadLogs(); + } + }; + + const deleteLog = async () => { + const response = await fetch('http://127.0.0.1:5000/dashboard/glucose', { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + if (response.ok) { + console.log('Log deleted successfully'); + loadLogs(); + } + }; + + return ( + + + + + + Glucose Log Tracker + + + setDateTime(e.target.value)} + /> + setGlucoseLevel(e.target.value)} + /> + + + + {logs && ( + + + Previous Glucose Logs + + {logs.map((log) => ( + + + {formatDate(log.log_timestamp)}: {log.glucose_level} mg/dL + + + + ))} + + )} + + ); +} + +export default GlucoseLogs; 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/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/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 From 5cb4ca1db557169b2f08eb815f26672d4a5d8ccc Mon Sep 17 00:00:00 2001 From: ness-miewald <83977281+ness-miewald@users.noreply.github.com> Date: Sat, 4 May 2024 17:14:13 -0700 Subject: [PATCH 2/5] 2 frontend uploads The staff dashboard page and the staff communication page. --- Communication.jsx | 45 ++++++++++++ Staff_Dashboard.jsx | 170 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 Communication.jsx create mode 100644 Staff_Dashboard.jsx 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/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" && ( + + + + + + + + + )} + + + + + ); +} From cd36e7771c36fd56c2050da1cb21ae7b22bc0c33 Mon Sep 17 00:00:00 2001 From: ness-miewald <83977281+ness-miewald@users.noreply.github.com> Date: Mon, 6 May 2024 10:46:33 -0700 Subject: [PATCH 3/5] added to the backend added the route for the staff dashboard and communication page --- app.py | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 app.py 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 From 5c2b7cb311e5509cd59cee827f0f73eaae04027f Mon Sep 17 00:00:00 2001 From: ness-miewald <83977281+ness-miewald@users.noreply.github.com> Date: Mon, 6 May 2024 15:49:11 -0700 Subject: [PATCH 4/5] staff backend backend for the dashboard and communication --- admin_routes.py | 223 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 admin_routes.py 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 From fb388e2c92043ede0cadf9a2494988853e3ea573 Mon Sep 17 00:00:00 2001 From: ness-miewald <83977281+ness-miewald@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:53:35 -0700 Subject: [PATCH 5/5] UI I made changes to the UI --- Appointment.jsx | 397 ++++++++++++++++++++++++++-------------------- Dashboard.jsx | 299 ++++++++++++++++++----------------- GlucoseLogs.jsx | 409 ++++++++++++++++++++++++++++++++---------------- Products.jsx | 78 +++++++++ 4 files changed, 737 insertions(+), 446 deletions(-) create mode 100644 Products.jsx diff --git a/Appointment.jsx b/Appointment.jsx index 543f190..988fc66 100644 --- a/Appointment.jsx +++ b/Appointment.jsx @@ -1,176 +1,237 @@ -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 { Avatar, Button, CssBaseline, TextField, Container, Box, Typography, Paper } from '@mui/material'; // Importing specific components from Material-UI -import { ArrowBack as ArrowBackIcon} from '@mui/icons-material'; // Importing ArrowBackIcon from Material-UI icons +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'; -// Functional component Appointment -const Appointment = () => { - // State variables to manage appointments, doctor name, date & time, and notes - const [appointments, setAppointments] = useState([]); // State variable to store appointments - const [doctor, setDoctor] = useState(''); // State variable to store doctor's name - const [dateTime, setDateTime] = useState(''); // State variable to store date & time - const [notes, setNotes] = useState(''); // State variable to store notes - - const token = localStorage.getItem('jwtToken'); // Retrieving JWT token from localStorage - const navigate = useNavigate(); // Initializing useNavigate hook for navigation +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'); - // Fetch appointments from the backend on component mount - useEffect(() => { - fetchAppointments(); - }, []); + useEffect(() => { fetchAppointments(); }, []); - // Function to format date - 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); - }; + const formatDateTime = (dateTimeString) => { + const date = new Date(dateTimeString); + return date.toISOString().slice(0, 16); + }; - // Function to fetch appointments from the backend - const fetchAppointments = async () => { - 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(); - const { appointments } = responseData.data; - setAppointments(appointments); - } - }; + 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); + } + }; - // Function to record a new appointment - const recordAppointment = async () => { - const appointment = { doctor, dateTime, notes }; - 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(); // Fetch appointments after recording - } - }; + 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); + } + }; - // Function to delete an appointment - const deleteAppointment = async (appointmentId) => { - const response = await fetch('http://127.0.0.1:5000/dashboard/appointments', { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - }, - body: JSON.stringify({ appointment_id: appointmentId }), - }); - if (response.ok) { - console.log('Appointment deleted successfully'); - fetchAppointments(); // Fetch appointments after deletion - } - }; + 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); + } + }; - // JSX to render the component - return ( - - - {/* Button to navigate back to previous page */} - - - - Appointment Scheduler - - {/* Form to schedule a new appointment */} - - setDateTime(e.target.value)} - /> - setDoctor(e.target.value)} - /> - setNotes(e.target.value)} - /> - {/* Button to schedule the appointment */} - - - - {/* Displaying scheduled appointments */} - {appointments && ( - - - Scheduled Appointments - - {appointments.map((appointment) => ( - - - {appointment.doctor_name} - {appointment.appointment_notes} at {formatDate(appointment.appointment_date)} - - {/* Button to delete the appointment */} - - - ))} - - )} - - ); -}; + 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', + }); -export default Appointment; // Exporting the component + 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/Dashboard.jsx b/Dashboard.jsx index eecc14e..0e996ef 100644 --- a/Dashboard.jsx +++ b/Dashboard.jsx @@ -1,6 +1,6 @@ -import React from 'react'; // Importing necessary modules from React -import { Link, Outlet, useLocation } from 'react-router-dom'; // Importing Link, Outlet, and useLocation from react-router-dom -import { styled } from '@mui/system'; // Importing styled 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, @@ -13,39 +13,33 @@ import { ListItemButton, ListItemIcon, ListItemText, - Typography -} from '@mui/material'; // Importing specific components from Material-UI + 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'; // Importing specific icons from Material-UI icons + 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'; -import ClickableBox from './ClickableBox'; // Importing ClickableBox component from local file - -const drawerWidth = 250; // Defining drawer width - -// Styling the Drawer component using styled from MUI system const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })( ({ theme, open }) => ({ '& .MuiDrawer-paper': { position: 'relative', whiteSpace: 'nowrap', - width: drawerWidth, + width: 250, transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }), boxSizing: 'border-box', - background: theme.palette.primary.main, + background: '#1a73e8', // Updated color + color: 'white', // Updated text color ...(!open && { overflowX: 'hidden', transition: theme.transitions.create('width', { @@ -58,130 +52,153 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' }, }), }, - }), + }) ); -// Functional component Dashboard -export default function Dashboard({username}) { - const [open, setOpen] = React.useState(false); // State variable to manage drawer open/close - const location = useLocation(); // Getting current location using useLocation hook from react-router-dom +export default function Dashboard({ role, username }) { + const [open, setOpen] = React.useState(false); + const location = useLocation(); - // Function to toggle drawer open/close const toggleDrawer = () => { setOpen(!open); }; - + return ( - // Container component to contain the Dashboard content - - - {/* Drawer component for navigation */} - - {/* Toolbar for drawer */} - - {/* IconButton to toggle drawer */} - - {open ? : } - - - - {/* List for navigation links */} - - - {/* List item for Profile */} - - - - - - Profile - - } - /> - - {/* List item for Glucose Logs */} - - - - - - Glucose Logs - - } - /> - - {/* List item for Appointments */} - - - - - - Appointments - - } - /> - - {/* List item for Notifications */} - - - - - - Notifications - - } - /> - - - - - {/* Updated background color */} + + + - {/* Container for main content */} - - {/* Typography for dashboard title */} - - {username}'s Dashboard - - {/* Conditionally rendering ClickableBox components based on location */} - {location.pathname === "/dashboard" && ( - - - - - - - - - )} - {/* Rendering nested routes */} - - - - - ); + + {open ? : } + + + + + {role === 'admin' ? ( + <> + + + + + + Glucose Logs + + } + /> + + + + + + + Appointments + + } + /> + + + + + + + Notifications + + } + /> + + + ) : ( + <> + + + + + + Glucose Logs + + } + /> + + + + + + + Appointments + + } + /> + + + + + + + Notifications + + } + /> + + + )} + + + + + {location.pathname === "/dashboard" && ( + {/* Centered and gap updated */} + {role === 'admin' ? ( + <> + + + + + + ) : ( + <> + + + + + + +)} + +)} + + + + +); } diff --git a/GlucoseLogs.jsx b/GlucoseLogs.jsx index d3aace9..66c346e 100644 --- a/GlucoseLogs.jsx +++ b/GlucoseLogs.jsx @@ -1,148 +1,283 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Button, CssBaseline, TextField, Container, Box, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from '@mui/material'; -import { ArrowBack as ArrowBackIcon} from '@mui/icons-material'; +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'; -function GlucoseLogs() { - const [dateTime, setDateTime] = useState(''); - const [glucoseLevel, setGlucoseLevel] = useState(''); - const [logs, setLogs] = useState([]); +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'); - const token = localStorage.getItem('jwtToken'); - const navigate = useNavigate(); + useEffect(() => { + fetchLogs(); + }, []); - 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 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); - }; + 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 fetchLogs = async () => { - 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(); - const { logs } = responseData.data; - setLogs(logs); - } - } + 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 recordLog = async () => { - const log = { date, time, glucoseLevel }; - const response = await fetch('/api/glucose', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - }, - body: JSON.stringify(log), - }); - if (response.ok) { - console.log('Log recorded successfully'); - loadLogs(); - } - }; + 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 deleteLog = async () => { - const response = await fetch('http://127.0.0.1:5000/dashboard/glucose', { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${token}`, - }, - }); - if (response.ok) { - console.log('Log deleted successfully'); - loadLogs(); - } - }; + 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', + }); - return ( - - - - - - Glucose Log Tracker - - - setDateTime(e.target.value)} - /> - setGlucoseLevel(e.target.value)} - /> - - - - {logs && ( - - - Previous Glucose Logs - - {logs.map((log) => ( - - - {formatDate(log.log_timestamp)}: {log.glucose_level} mg/dL - - - - ))} - - )} - - ); -} + doc.save(fileName); + }; -export default GlucoseLogs; + 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/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