diff --git a/apps/Frontend/src/App.tsx b/apps/Frontend/src/App.tsx index e4574da..5c5d4fe 100644 --- a/apps/Frontend/src/App.tsx +++ b/apps/Frontend/src/App.tsx @@ -1,5 +1,5 @@ import { Switch, Route } from "wouter"; -import React, { Suspense, lazy } from "react"; +import React, { lazy } from "react"; import { Provider } from "react-redux"; import { store } from "./redux/store"; import { queryClient } from "./lib/queryClient"; @@ -9,7 +9,6 @@ import { TooltipProvider } from "./components/ui/tooltip"; import { ProtectedRoute } from "./lib/protected-route"; import { AuthProvider } from "./hooks/use-auth"; import Dashboard from "./pages/dashboard"; -import LoadingScreen from "./components/ui/LoadingScreen"; const AuthPage = lazy(() => import("./pages/auth-page")); const AppointmentsPage = lazy(() => import("./pages/appointments-page")); @@ -24,6 +23,7 @@ const DocumentPage = lazy(() => import("./pages/documents-page")); const DatabaseManagementPage = lazy( () => import("./pages/database-management-page") ); +const ReportsPage = lazy(() => import("./pages/reports-page")); const NotFound = lazy(() => import("./pages/not-found")); function Router() { @@ -47,6 +47,7 @@ function Router() { path="/database-management" component={() => } /> + } /> } /> } /> @@ -60,9 +61,7 @@ function App() { - }> - - + diff --git a/apps/Frontend/src/components/layout/app-layout.tsx b/apps/Frontend/src/components/layout/app-layout.tsx new file mode 100644 index 0000000..a541f3a --- /dev/null +++ b/apps/Frontend/src/components/layout/app-layout.tsx @@ -0,0 +1,25 @@ +import { SidebarProvider } from "@/components/ui/sidebar"; +import { Sidebar } from "@/components/layout/sidebar"; +import { TopAppBar } from "@/components/layout/top-app-bar"; + +export default function AppLayout({ children }: { children: React.ReactNode }) { + return ( + +
+ {/* Fixed top bar */} + + + {/* Main content area */} +
+ {/* Sidebar (collapsible on mobile) */} + + + {/* Page content */} +
+ {children} +
+
+
+
+ ); +} diff --git a/apps/Frontend/src/components/layout/sidebar.tsx b/apps/Frontend/src/components/layout/sidebar.tsx index ff3de0c..13d2475 100644 --- a/apps/Frontend/src/components/layout/sidebar.tsx +++ b/apps/Frontend/src/components/layout/sidebar.tsx @@ -9,96 +9,115 @@ import { CreditCard, FolderOpen, Database, + FileText, } from "lucide-react"; - import { cn } from "@/lib/utils"; +import { useEffect, useMemo, useState } from "react"; +import { useSidebar } from "@/components/ui/sidebar"; -interface SidebarProps { - isMobileOpen: boolean; - setIsMobileOpen: (open: boolean) => void; -} +const WIDTH_ANIM_MS = 100; -export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) { +export function Sidebar() { const [location] = useLocation(); + const { state, openMobile, setOpenMobile } = useSidebar(); // "expanded" | "collapsed" - const navItems = [ - { - name: "Dashboard", - path: "/", - icon: , - }, - { - name: "Appointments", - path: "/appointments", - icon: , - }, - { - name: "Patients", - path: "/patients", - icon: , - }, - { - name: "Insurance Eligibility", - path: "/insurance-eligibility", - icon: , - }, - { - name: "Claims", - path: "/claims", - icon: , - }, - { - name: "Payments", - path: "/payments", - icon: , - }, - { - name: "Documents", - path: "/documents", - icon: , - }, - { - name: "Backup Database", - path: "/database-management", - icon: , - }, - { - name: "Settings", - path: "/settings", - icon: , - }, - ]; + // Delay label visibility until the width animation completes + const [showLabels, setShowLabels] = useState(state !== "collapsed"); + useEffect(() => { + let timer: number | undefined; + + if (state === "expanded") { + timer = window.setTimeout(() => setShowLabels(true), WIDTH_ANIM_MS); + } else { + setShowLabels(false); + } + + return () => { + if (timer !== undefined) { + window.clearTimeout(timer); + } + }; + }, [state]); + + const navItems = useMemo( + () => [ + { + name: "Dashboard", + path: "/", + icon: , + }, + { + name: "Appointments", + path: "/appointments", + icon: , + }, + { + name: "Patients", + path: "/patients", + icon: , + }, + { + name: "Insurance Eligibility", + path: "/insurance-eligibility", + icon: , + }, + { + name: "Claims", + path: "/claims", + icon: , + }, + { + name: "Payments", + path: "/payments", + icon: , + }, + { + name: "Documents", + path: "/documents", + icon: , + }, + { + name: "Reports", + path: "/reports", + icon: , + }, + { + name: "Backup Database", + path: "/database-management", + icon: , + }, + { + name: "Settings", + path: "/settings", + icon: , + }, + ], + [] + ); return (
-
- - - - - -

DentalConnect

-
-
-
- ) + ); } -) -Sidebar.displayName = "Sidebar" +); +Sidebar.displayName = "Sidebar"; const SidebarTrigger = React.forwardRef< React.ElementRef, React.ComponentProps >(({ className, onClick, ...props }, ref) => { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return ( - ) -}) -SidebarTrigger.displayName = "SidebarTrigger" + ); +}); +SidebarTrigger.displayName = "SidebarTrigger"; const SidebarRail = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> >(({ className, ...props }, ref) => { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return ( + -
- + {/* Context Menu */} + + { + const fullAppointment = appointments.find( + (a) => a.id === props.appointmentId + ); + if (fullAppointment) { + handleEditAppointment(fullAppointment); + } + }} + > + + + Edit Appointment + + + + handleDeleteAppointment(props.appointmentId) + } + > + + + Delete Appointment + + + -
-
-
-
-

- Appointment Schedule -

-

- View and manage the dental practice schedule -

+ {/* Main Content - Split into Schedule and Calendar */} +
+ {/* Left side - Schedule Grid */} +
+
+
+
+ +

{formattedDate}

+ +
-
- {/* Context Menu */} - - { - const fullAppointment = appointments.find( - (a) => a.id === props.appointmentId - ); - if (fullAppointment) { - handleEditAppointment(fullAppointment); - } - }} - > - - - Edit Appointment - - - - handleDeleteAppointment(props.appointmentId) - } - > - - - Delete Appointment - - - + {/* Schedule Grid with Drag and Drop */} + +
+ + + + + {staffMembers.map((staff) => ( + + ))} + + + + {timeSlots.map((timeSlot) => ( + + + {staffMembers.map((staff) => ( + + ))} + + ))} + +
Time + {staff.name} +
+ {staff.role} +
+
+ {timeSlot.displayTime} +
+
+
+
- {/* Main Content - Split into Schedule and Calendar */} -
- {/* Left side - Schedule Grid */} -
-
-
-
- -

{formattedDate}

- -
+ {/* Right side - Calendar and Stats */} +
+ {/* Calendar Card */} + + + Calendar + + Select a date to view or schedule appointments + + + + { + if (date) setSelectedDate(date); + }} + /> + + + + {/* Statistics Card */} + + + + Appointments + + + + Statistics for {formattedDate} + + + +
+
+ + Total appointments: + + + {selectedDateAppointments.length} + +
+
+ With doctors: + + { + processedAppointments.filter( + (apt) => + staffMembers.find( + (s) => Number(s.id) === apt.staffId + )?.role === "doctor" + ).length + } + +
+
+ + With hygienists: + + + { + processedAppointments.filter( + (apt) => + staffMembers.find( + (s) => Number(s.id) === apt.staffId + )?.role === "hygienist" + ).length + } +
- - {/* Schedule Grid with Drag and Drop */} - -
- - - - - {staffMembers.map((staff) => ( - - ))} - - - - {timeSlots.map((timeSlot) => ( - - - {staffMembers.map((staff) => ( - - ))} - - ))} - -
- Time - - {staff.name} -
- {staff.role} -
-
- {timeSlot.displayTime} -
-
-
-
- - {/* Right side - Calendar and Stats */} -
- {/* Calendar Card */} - - - Calendar - - Select a date to view or schedule appointments - - - - { - if (date) setSelectedDate(date); - }} - /> - - - - {/* Statistics Card */} - - - - Appointments - - - - Statistics for {formattedDate} - - - -
-
- - Total appointments: - - - {selectedDateAppointments.length} - -
-
- - With doctors: - - - { - processedAppointments.filter( - (apt) => - staffMembers.find( - (s) => Number(s.id) === apt.staffId - )?.role === "doctor" - ).length - } - -
-
- - With hygienists: - - - { - processedAppointments.filter( - (apt) => - staffMembers.find( - (s) => Number(s.id) === apt.staffId - )?.role === "hygienist" - ).length - } - -
-
-
-
-
-
+ +
-
+
{/* Add/Edit Appointment Modal */} diff --git a/apps/Frontend/src/pages/claims-page.tsx b/apps/Frontend/src/pages/claims-page.tsx index 2ae05bf..13e6379 100644 --- a/apps/Frontend/src/pages/claims-page.tsx +++ b/apps/Frontend/src/pages/claims-page.tsx @@ -1,7 +1,5 @@ import { useState, useEffect, useMemo } from "react"; import { useMutation } from "@tanstack/react-query"; -import { TopAppBar } from "@/components/layout/top-app-bar"; -import { Sidebar } from "@/components/layout/sidebar"; import { Card, CardHeader, @@ -410,58 +408,47 @@ export default function ClaimsPage() { }; return ( -
- + dispatch(clearTaskStatus())} /> -
- - - dispatch(clearTaskStatus())} - /> - -
-
-
-
-

- Insurance Claims -

-

- Manage and submit insurance claims for patients -

-
-
+
+
+
+

+ Insurance Claims +

+

+ Manage and submit insurance claims for patients +

- - {/* Recent Claims by Patients also handles new claims */} - - - {/* Recent Claims Section */} - - - Recently Submitted Claims - - View and manage all recent claims information - - - - - - -
+
+ {/* Recent Claims by Patients also handles new claims */} + + + {/* Recent Claims Section */} + + + Recently Submitted Claims + + View and manage all recent claims information + + + + + + + {/* Claim Form Modal */} {isClaimFormOpen && selectedPatientId !== null && ( - +
+ {/* Quick Stats */} +
+ + + + +
-
- + {/* Today's Appointments Section */} +
+
+

+ Today's Appointments +

+ +
-
- {/* Quick Stats */} -
- - - - -
- - {/* Today's Appointments Section */} -
-
-

- Today's Appointments -

- -
- - - - {todaysAppointments.length > 0 ? ( -
- {todaysAppointments.map((appointment) => { - const patient = patients.find( - (p) => p.id === appointment.patientId - ); - return ( -
-
-
- -
-
-

- {patient - ? `${patient.firstName} ${patient.lastName}` - : "Unknown Patient"} -

-
- - - {`${format( - parse( - `${format(new Date(appointment.date), "yyyy-MM-dd")} ${appointment.startTime}`, - "yyyy-MM-dd HH:mm", - new Date() - ), - "hh:mm a" - )} - ${format( - parse( - `${format(new Date(appointment.date), "yyyy-MM-dd")} ${appointment.endTime}`, - "yyyy-MM-dd HH:mm", - new Date() - ), - "hh:mm a" - )}`} - - - - - {appointment.type.charAt(0).toUpperCase() + - appointment.type.slice(1)} - -
-
+ + + {todaysAppointments.length > 0 ? ( +
+ {todaysAppointments.map((appointment) => { + const patient = patients.find( + (p) => p.id === appointment.patientId + ); + return ( +
+
+
+ +
+
+

+ {patient + ? `${patient.firstName} ${patient.lastName}` + : "Unknown Patient"} +

+
+ + + {`${format( + parse( + `${format(new Date(appointment.date), "yyyy-MM-dd")} ${appointment.startTime}`, + "yyyy-MM-dd HH:mm", + new Date() + ), + "hh:mm a" + )} - ${format( + parse( + `${format(new Date(appointment.date), "yyyy-MM-dd")} ${appointment.endTime}`, + "yyyy-MM-dd HH:mm", + new Date() + ), + "hh:mm a" + )}`} + + + + + {appointment.type.charAt(0).toUpperCase() + + appointment.type.slice(1)} +
-
- +
+
+ - {appointment.status - ? appointment.status.charAt(0).toUpperCase() + - appointment.status.slice(1) - : "Scheduled"} - - - View All - -
-
- ); - })} -
- ) : ( -
- -

- No appointments today -

-

- You don't have any appointments scheduled for today. -

- -
- )} - - -
+ > + {appointment.status + ? appointment.status.charAt(0).toUpperCase() + + appointment.status.slice(1) + : "Scheduled"} + + + View All + +
+
+ ); + })} +
+ ) : ( +
+ +

+ No appointments today +

+

+ You don't have any appointments scheduled for today. +

+ +
+ )} + + +
- {/* Analytics Dashboard Section */} -
- - -
+ {/* Analytics Dashboard Section */} +
+ + +
- {/* Patient Management Section */} -
- {/* Patient Header */} -
-

- Patient Management -

- -
+ {/* Patient Management Section */} +
+ {/* Patient Header */} +
+

+ Patient Management +

+ +
- {/* Patient Table */} - -
-
+ {/* Patient Table */} +
{/* Add/Edit Patient Modal */} diff --git a/apps/Frontend/src/pages/database-management-page.tsx b/apps/Frontend/src/pages/database-management-page.tsx index 42baeb2..f189737 100644 --- a/apps/Frontend/src/pages/database-management-page.tsx +++ b/apps/Frontend/src/pages/database-management-page.tsx @@ -1,6 +1,4 @@ import { useState } from "react"; -import { TopAppBar } from "@/components/layout/top-app-bar"; -import { Sidebar } from "@/components/layout/sidebar"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; @@ -76,128 +74,110 @@ export default function DatabaseManagementPage() { }); return ( -
- +
+
+ {/* Page Header */} +
+

+ + Database Management +

+

+ Manage your dental practice database with backup, export + capabilities +

+
-
- setIsMobileMenuOpen(!isMobileMenuOpen)} - /> + {/* Database Backup Section */} + + + + + Database Backup + + + +

+ Create a complete backup of your dental practice database + including patients, appointments, claims, and all related data. +

-
-
- {/* Page Header */} -
-

- - Database Management -

-

- Manage your dental practice database with backup, export - capabilities -

-
- - {/* Database Backup Section */} - - - - - Database Backup - - - -

- Create a complete backup of your dental practice database - including patients, appointments, claims, and all related - data. -

- -
- - -
- Last backup:{" "} - {dbStatus?.lastBackup - ? formatDateToHumanReadable(dbStatus.lastBackup) - : "Never"} -
-
-
-
- - {/* Database Status Section */} - - - - - Database Status - - - - {isLoadingStatus ? ( -

Loading status...

+
+
-
+ + {backupMutation.isPending + ? "Creating Backup..." + : "Create Backup"} + + + +
+ Last backup:{" "} + {dbStatus?.lastBackup + ? formatDateToHumanReadable(dbStatus.lastBackup) + : "Never"} +
+
+ + + + {/* Database Status Section */} + + + + + Database Status + + + + {isLoadingStatus ? ( +

Loading status...

+ ) : ( +
+
+
+
+ Status +
+

+ {dbStatus?.connected ? "Connected" : "Disconnected"} +

+
+ +
+
+ + Size +
+

+ {dbStatus?.size ?? "Unknown"} +

+
+ +
+
+ + Records +
+

+ {dbStatus?.patients + ? `${dbStatus.patients} patients` + : "N/A"} +

+
+
+ )} +
+
); diff --git a/apps/Frontend/src/pages/documents-page.tsx b/apps/Frontend/src/pages/documents-page.tsx index d8d0c1d..3df20ca 100644 --- a/apps/Frontend/src/pages/documents-page.tsx +++ b/apps/Frontend/src/pages/documents-page.tsx @@ -13,8 +13,6 @@ import { apiRequest, queryClient } from "@/lib/queryClient"; import { Eye, Trash, Download, FolderOpen } from "lucide-react"; import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog"; import { PatientTable } from "@/components/patients/patient-table"; -import { Sidebar } from "@/components/layout/sidebar"; -import { TopAppBar } from "@/components/layout/top-app-bar"; import { Patient, PdfFile } from "@repo/db/types"; export default function DocumentsPage() { @@ -117,165 +115,152 @@ export default function DocumentsPage() { }; return ( -
- - -
- - -
-
-
-
-

Documents

-

- View and manage recent uploaded claim PDFs -

-
-
- - {selectedPatient && ( - - - - Document Groups for {selectedPatient.firstName}{" "} - {selectedPatient.lastName} - - Select a group to view PDFs - - - {groups.length === 0 ? ( -

- No groups found for this patient. -

- ) : ( - groups.map((group: any) => ( - - )) - )} -
-
- )} - - {selectedGroupId && ( - - - PDFs in Group - - - {groupPdfs.length === 0 ? ( -

- No PDFs found in this group. -

- ) : ( - groupPdfs.map((pdf: any) => ( -
- {pdf.filename} -
- - - -
-
- )) - )} -
-
- )} - - {fileBlobUrl && ( - - - Viewing PDF - - - -