applayout added, sidebar updated
This commit is contained in:
@@ -2,8 +2,6 @@ import { useState, useEffect } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { format, addDays, startOfToday, addMinutes } from "date-fns";
|
||||
import { parseLocalDate, formatLocalDate } from "@/utils/dateUtils";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -625,231 +623,212 @@ export default function AppointmentsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
/>
|
||||
<div className="">
|
||||
<div className="container mx-auto">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
Appointment Schedule
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
View and manage the dental practice schedule
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setEditingAppointment(undefined);
|
||||
setIsAddModalOpen(true);
|
||||
}}
|
||||
className="gap-1"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
New Appointment
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
{/* Context Menu */}
|
||||
<Menu id={APPOINTMENT_CONTEXT_MENU_ID} animation="fade">
|
||||
<Item
|
||||
onClick={({ props }) => {
|
||||
const fullAppointment = appointments.find(
|
||||
(a) => a.id === props.appointmentId
|
||||
);
|
||||
if (fullAppointment) {
|
||||
handleEditAppointment(fullAppointment);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
Edit Appointment
|
||||
</span>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={({ props }) =>
|
||||
handleDeleteAppointment(props.appointmentId)
|
||||
}
|
||||
>
|
||||
<span className="flex items-center gap-2 text-red-600">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Delete Appointment
|
||||
</span>
|
||||
</Item>
|
||||
</Menu>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4">
|
||||
<div className="container mx-auto">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
Appointment Schedule
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
View and manage the dental practice schedule
|
||||
</p>
|
||||
{/* Main Content - Split into Schedule and Calendar */}
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
{/* Left side - Schedule Grid */}
|
||||
<div className="w-full lg:w-3/4 overflow-x-auto bg-white rounded-md shadow">
|
||||
<div className="p-4 border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setSelectedDate(addDays(selectedDate, -1))}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<h2 className="text-xl font-semibold">{formattedDate}</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setSelectedDate(addDays(selectedDate, 1))}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setEditingAppointment(undefined);
|
||||
setIsAddModalOpen(true);
|
||||
}}
|
||||
className="gap-1"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
New Appointment
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Context Menu */}
|
||||
<Menu id={APPOINTMENT_CONTEXT_MENU_ID} animation="fade">
|
||||
<Item
|
||||
onClick={({ props }) => {
|
||||
const fullAppointment = appointments.find(
|
||||
(a) => a.id === props.appointmentId
|
||||
);
|
||||
if (fullAppointment) {
|
||||
handleEditAppointment(fullAppointment);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
Edit Appointment
|
||||
</span>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={({ props }) =>
|
||||
handleDeleteAppointment(props.appointmentId)
|
||||
}
|
||||
>
|
||||
<span className="flex items-center gap-2 text-red-600">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Delete Appointment
|
||||
</span>
|
||||
</Item>
|
||||
</Menu>
|
||||
{/* Schedule Grid with Drag and Drop */}
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse min-w-[800px]">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-2 border bg-gray-50 w-[100px]">Time</th>
|
||||
{staffMembers.map((staff) => (
|
||||
<th
|
||||
key={staff.id}
|
||||
className={`p-2 border bg-gray-50 ${staff.role === "doctor" ? "font-bold" : ""}`}
|
||||
>
|
||||
{staff.name}
|
||||
<div className="text-xs text-gray-500">
|
||||
{staff.role}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{timeSlots.map((timeSlot) => (
|
||||
<tr key={timeSlot.time}>
|
||||
<td className="border px-2 py-1 text-xs text-gray-600 font-medium">
|
||||
{timeSlot.displayTime}
|
||||
</td>
|
||||
{staffMembers.map((staff) => (
|
||||
<DroppableTimeSlot
|
||||
key={`${timeSlot.time}-${staff.id}`}
|
||||
timeSlot={timeSlot}
|
||||
staffId={Number(staff.id)}
|
||||
appointment={getAppointmentAtSlot(
|
||||
timeSlot,
|
||||
Number(staff.id)
|
||||
)}
|
||||
staff={staff}
|
||||
/>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</DndProvider>
|
||||
</div>
|
||||
|
||||
{/* Main Content - Split into Schedule and Calendar */}
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
{/* Left side - Schedule Grid */}
|
||||
<div className="w-full lg:w-3/4 overflow-x-auto bg-white rounded-md shadow">
|
||||
<div className="p-4 border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
setSelectedDate(addDays(selectedDate, -1))
|
||||
}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<h2 className="text-xl font-semibold">{formattedDate}</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
setSelectedDate(addDays(selectedDate, 1))
|
||||
}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{/* Right side - Calendar and Stats */}
|
||||
<div className="w-full lg:w-1/4 space-y-6">
|
||||
{/* Calendar Card */}
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle>Calendar</CardTitle>
|
||||
<CardDescription>
|
||||
Select a date to view or schedule appointments
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
onSelect={(date) => {
|
||||
if (date) setSelectedDate(date);
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Statistics Card */}
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>Appointments</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => refetchAppointments()}
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Statistics for {formattedDate}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">
|
||||
Total appointments:
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{selectedDateAppointments.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">With doctors:</span>
|
||||
<span className="font-semibold">
|
||||
{
|
||||
processedAppointments.filter(
|
||||
(apt) =>
|
||||
staffMembers.find(
|
||||
(s) => Number(s.id) === apt.staffId
|
||||
)?.role === "doctor"
|
||||
).length
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">
|
||||
With hygienists:
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{
|
||||
processedAppointments.filter(
|
||||
(apt) =>
|
||||
staffMembers.find(
|
||||
(s) => Number(s.id) === apt.staffId
|
||||
)?.role === "hygienist"
|
||||
).length
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Schedule Grid with Drag and Drop */}
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse min-w-[800px]">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-2 border bg-gray-50 w-[100px]">
|
||||
Time
|
||||
</th>
|
||||
{staffMembers.map((staff) => (
|
||||
<th
|
||||
key={staff.id}
|
||||
className={`p-2 border bg-gray-50 ${staff.role === "doctor" ? "font-bold" : ""}`}
|
||||
>
|
||||
{staff.name}
|
||||
<div className="text-xs text-gray-500">
|
||||
{staff.role}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{timeSlots.map((timeSlot) => (
|
||||
<tr key={timeSlot.time}>
|
||||
<td className="border px-2 py-1 text-xs text-gray-600 font-medium">
|
||||
{timeSlot.displayTime}
|
||||
</td>
|
||||
{staffMembers.map((staff) => (
|
||||
<DroppableTimeSlot
|
||||
key={`${timeSlot.time}-${staff.id}`}
|
||||
timeSlot={timeSlot}
|
||||
staffId={Number(staff.id)}
|
||||
appointment={getAppointmentAtSlot(
|
||||
timeSlot,
|
||||
Number(staff.id)
|
||||
)}
|
||||
staff={staff}
|
||||
/>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</DndProvider>
|
||||
</div>
|
||||
|
||||
{/* Right side - Calendar and Stats */}
|
||||
<div className="w-full lg:w-1/4 space-y-6">
|
||||
{/* Calendar Card */}
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle>Calendar</CardTitle>
|
||||
<CardDescription>
|
||||
Select a date to view or schedule appointments
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
onSelect={(date) => {
|
||||
if (date) setSelectedDate(date);
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Statistics Card */}
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>Appointments</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => refetchAppointments()}
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Statistics for {formattedDate}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">
|
||||
Total appointments:
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{selectedDateAppointments.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">
|
||||
With doctors:
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{
|
||||
processedAppointments.filter(
|
||||
(apt) =>
|
||||
staffMembers.find(
|
||||
(s) => Number(s.id) === apt.staffId
|
||||
)?.role === "doctor"
|
||||
).length
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">
|
||||
With hygienists:
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{
|
||||
processedAppointments.filter(
|
||||
(apt) =>
|
||||
staffMembers.find(
|
||||
(s) => Number(s.id) === apt.staffId
|
||||
)?.role === "hygienist"
|
||||
).length
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add/Edit Appointment Modal */}
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
<div>
|
||||
<SeleniumTaskBanner
|
||||
status={status}
|
||||
message={message}
|
||||
show={show}
|
||||
onClear={() => dispatch(clearTaskStatus())}
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
|
||||
<SeleniumTaskBanner
|
||||
status={status}
|
||||
message={message}
|
||||
show={show}
|
||||
onClear={() => dispatch(clearTaskStatus())}
|
||||
/>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4">
|
||||
<div className="container mx-auto space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
Insurance Claims
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage and submit insurance claims for patients
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container mx-auto space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
Insurance Claims
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage and submit insurance claims for patients
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Recent Claims by Patients also handles new claims */}
|
||||
<ClaimsOfPatientModal onNewClaim={handleNewClaim} />
|
||||
|
||||
{/* Recent Claims Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Recently Submitted Claims</CardTitle>
|
||||
<CardDescription>
|
||||
View and manage all recent claims information
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ClaimsRecentTable
|
||||
allowEdit={true}
|
||||
allowView={true}
|
||||
allowDelete={true}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Claims by Patients also handles new claims */}
|
||||
<ClaimsOfPatientModal onNewClaim={handleNewClaim} />
|
||||
|
||||
{/* Recent Claims Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Recently Submitted Claims</CardTitle>
|
||||
<CardDescription>
|
||||
View and manage all recent claims information
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ClaimsRecentTable
|
||||
allowEdit={true}
|
||||
allowView={true}
|
||||
allowDelete={true}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Claim Form Modal */}
|
||||
{isClaimFormOpen && selectedPatientId !== null && (
|
||||
<ClaimForm
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { format, parse, isValid, parseISO } from "date-fns";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { StatCard } from "@/components/ui/stat-card";
|
||||
import { PatientTable } from "@/components/patients/patient-table";
|
||||
import { AddPatientModal } from "@/components/patients/add-patient-modal";
|
||||
@@ -14,8 +12,6 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { AppointmentsByDay } from "@/components/analytics/appointments-by-day";
|
||||
import { NewPatients } from "@/components/analytics/new-patients";
|
||||
import { AppointmentUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import {
|
||||
Users,
|
||||
Calendar,
|
||||
@@ -25,7 +21,6 @@ import {
|
||||
Clock,
|
||||
} from "lucide-react";
|
||||
import { Link } from "wouter";
|
||||
import { z } from "zod";
|
||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||
import {
|
||||
Appointment,
|
||||
@@ -220,116 +215,107 @@ export default function Dashboard() {
|
||||
}).length;
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
/>
|
||||
<div>
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<StatCard
|
||||
title="Total Patients"
|
||||
value={patients.length}
|
||||
icon={Users}
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard
|
||||
title="Today's Appointments"
|
||||
value={todaysAppointments.length}
|
||||
icon={Calendar}
|
||||
color="secondary"
|
||||
/>
|
||||
<StatCard
|
||||
title="Completed Today"
|
||||
value={completedTodayCount}
|
||||
icon={CheckCircle}
|
||||
color="success"
|
||||
/>
|
||||
<StatCard
|
||||
title="Pending Payments"
|
||||
value={0}
|
||||
icon={CreditCard}
|
||||
color="warning"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
{/* Today's Appointments Section */}
|
||||
<div className="flex flex-col space-y-4 mb-6">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<h2 className="text-xl font-medium text-gray-800">
|
||||
Today's Appointments
|
||||
</h2>
|
||||
<Button
|
||||
className="mt-2 md:mt-0"
|
||||
onClick={() => {
|
||||
setSelectedAppointment(undefined);
|
||||
setIsAddAppointmentOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
New Appointment
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4">
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<StatCard
|
||||
title="Total Patients"
|
||||
value={patients.length}
|
||||
icon={Users}
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard
|
||||
title="Today's Appointments"
|
||||
value={todaysAppointments.length}
|
||||
icon={Calendar}
|
||||
color="secondary"
|
||||
/>
|
||||
<StatCard
|
||||
title="Completed Today"
|
||||
value={completedTodayCount}
|
||||
icon={CheckCircle}
|
||||
color="success"
|
||||
/>
|
||||
<StatCard
|
||||
title="Pending Payments"
|
||||
value={0}
|
||||
icon={CreditCard}
|
||||
color="warning"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Today's Appointments Section */}
|
||||
<div className="flex flex-col space-y-4 mb-6">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<h2 className="text-xl font-medium text-gray-800">
|
||||
Today's Appointments
|
||||
</h2>
|
||||
<Button
|
||||
className="mt-2 md:mt-0"
|
||||
onClick={() => {
|
||||
setSelectedAppointment(undefined);
|
||||
setIsAddAppointmentOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
New Appointment
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
{todaysAppointments.length > 0 ? (
|
||||
<div className="divide-y">
|
||||
{todaysAppointments.map((appointment) => {
|
||||
const patient = patients.find(
|
||||
(p) => p.id === appointment.patientId
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={appointment.id}
|
||||
className="p-4 flex items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="h-10 w-10 rounded-full bg-opacity-10 text-primary flex items-center justify-center">
|
||||
<Clock className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium">
|
||||
{patient
|
||||
? `${patient.firstName} ${patient.lastName}`
|
||||
: "Unknown Patient"}
|
||||
</h3>
|
||||
<div className="text-sm text-gray-500 flex items-center space-x-2">
|
||||
<span>
|
||||
<span>
|
||||
{`${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"
|
||||
)}`}
|
||||
</span>
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{appointment.type.charAt(0).toUpperCase() +
|
||||
appointment.type.slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
{todaysAppointments.length > 0 ? (
|
||||
<div className="divide-y">
|
||||
{todaysAppointments.map((appointment) => {
|
||||
const patient = patients.find(
|
||||
(p) => p.id === appointment.patientId
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={appointment.id}
|
||||
className="p-4 flex items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="h-10 w-10 rounded-full bg-opacity-10 text-primary flex items-center justify-center">
|
||||
<Clock className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium">
|
||||
{patient
|
||||
? `${patient.firstName} ${patient.lastName}`
|
||||
: "Unknown Patient"}
|
||||
</h3>
|
||||
<div className="text-sm text-gray-500 flex items-center space-x-2">
|
||||
<span>
|
||||
<span>
|
||||
{`${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"
|
||||
)}`}
|
||||
</span>
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{appointment.type.charAt(0).toUpperCase() +
|
||||
appointment.type.slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span
|
||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span
|
||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
${
|
||||
appointment.status === "completed"
|
||||
? "bg-green-100 text-green-800"
|
||||
@@ -339,81 +325,75 @@ export default function Dashboard() {
|
||||
? "bg-blue-100 text-blue-800"
|
||||
: "bg-yellow-100 text-yellow-800"
|
||||
}`}
|
||||
>
|
||||
{appointment.status
|
||||
? appointment.status.charAt(0).toUpperCase() +
|
||||
appointment.status.slice(1)
|
||||
: "Scheduled"}
|
||||
</span>
|
||||
<Link
|
||||
to="/appointments"
|
||||
className="text-primary hover:text-primary/80 text-sm"
|
||||
>
|
||||
View All
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-6 text-center">
|
||||
<Calendar className="h-12 w-12 mx-auto text-gray-400 mb-2" />
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
No appointments today
|
||||
</h3>
|
||||
<p className="mt-1 text-gray-500">
|
||||
You don't have any appointments scheduled for today.
|
||||
</p>
|
||||
<Button
|
||||
className="mt-4"
|
||||
onClick={() => {
|
||||
setSelectedAppointment(undefined);
|
||||
setIsAddAppointmentOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Schedule an Appointment
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
>
|
||||
{appointment.status
|
||||
? appointment.status.charAt(0).toUpperCase() +
|
||||
appointment.status.slice(1)
|
||||
: "Scheduled"}
|
||||
</span>
|
||||
<Link
|
||||
to="/appointments"
|
||||
className="text-primary hover:text-primary/80 text-sm"
|
||||
>
|
||||
View All
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-6 text-center">
|
||||
<Calendar className="h-12 w-12 mx-auto text-gray-400 mb-2" />
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
No appointments today
|
||||
</h3>
|
||||
<p className="mt-1 text-gray-500">
|
||||
You don't have any appointments scheduled for today.
|
||||
</p>
|
||||
<Button
|
||||
className="mt-4"
|
||||
onClick={() => {
|
||||
setSelectedAppointment(undefined);
|
||||
setIsAddAppointmentOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Schedule an Appointment
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Analytics Dashboard Section */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<AppointmentsByDay appointments={appointments} />
|
||||
<NewPatients patients={patients} />
|
||||
</div>
|
||||
{/* Analytics Dashboard Section */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<AppointmentsByDay appointments={appointments} />
|
||||
<NewPatients patients={patients} />
|
||||
</div>
|
||||
|
||||
{/* Patient Management Section */}
|
||||
<div className="flex flex-col space-y-4">
|
||||
{/* Patient Header */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<h2 className="text-xl font-medium text-gray-800">
|
||||
Patient Management
|
||||
</h2>
|
||||
<Button
|
||||
className="mt-2 md:mt-0"
|
||||
onClick={() => {
|
||||
setCurrentPatient(undefined);
|
||||
setIsAddPatientOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Patient
|
||||
</Button>
|
||||
</div>
|
||||
{/* Patient Management Section */}
|
||||
<div className="flex flex-col space-y-4">
|
||||
{/* Patient Header */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<h2 className="text-xl font-medium text-gray-800">
|
||||
Patient Management
|
||||
</h2>
|
||||
<Button
|
||||
className="mt-2 md:mt-0"
|
||||
onClick={() => {
|
||||
setCurrentPatient(undefined);
|
||||
setIsAddPatientOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Patient
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Patient Table */}
|
||||
<PatientTable
|
||||
allowDelete={true}
|
||||
allowEdit={true}
|
||||
allowView={true}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
{/* Patient Table */}
|
||||
<PatientTable allowDelete={true} allowEdit={true} allowView={true} />
|
||||
</div>
|
||||
|
||||
{/* Add/Edit Patient Modal */}
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
/>
|
||||
<div>
|
||||
<div className="container mx-auto space-y-6">
|
||||
{/* Page Header */}
|
||||
<div className="bg-white rounded-lg shadow-sm p-6 border">
|
||||
<h1 className="text-2xl font-bold text-gray-900 flex items-center space-x-3">
|
||||
<Database className="h-8 w-8 text-blue-600" />
|
||||
<span>Database Management</span>
|
||||
</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Manage your dental practice database with backup, export
|
||||
capabilities
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar
|
||||
toggleMobileMenu={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
/>
|
||||
{/* Database Backup Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<HardDrive className="h-5 w-5" />
|
||||
<span>Database Backup</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-gray-600">
|
||||
Create a complete backup of your dental practice database
|
||||
including patients, appointments, claims, and all related data.
|
||||
</p>
|
||||
|
||||
<main className="flex-1 overflow-x-hidden overflow-y-auto bg-gray-100 p-6">
|
||||
<div className="max-w-6xl mx-auto space-y-6">
|
||||
{/* Page Header */}
|
||||
<div className="bg-white rounded-lg shadow-sm p-6 border">
|
||||
<h1 className="text-2xl font-bold text-gray-900 flex items-center space-x-3">
|
||||
<Database className="h-8 w-8 text-blue-600" />
|
||||
<span>Database Management</span>
|
||||
</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Manage your dental practice database with backup, export
|
||||
capabilities
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Database Backup Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<HardDrive className="h-5 w-5" />
|
||||
<span>Database Backup</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-gray-600">
|
||||
Create a complete backup of your dental practice database
|
||||
including patients, appointments, claims, and all related
|
||||
data.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button
|
||||
onClick={() => backupMutation.mutate()}
|
||||
disabled={backupMutation.isPending}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
{backupMutation.isPending ? (
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<FileArchive className="h-4 w-4" />
|
||||
)}
|
||||
<span>
|
||||
{backupMutation.isPending
|
||||
? "Creating Backup..."
|
||||
: "Create Backup"}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
Last backup:{" "}
|
||||
{dbStatus?.lastBackup
|
||||
? formatDateToHumanReadable(dbStatus.lastBackup)
|
||||
: "Never"}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Database Status Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<Database className="h-5 w-5" />
|
||||
<span>Database Status</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoadingStatus ? (
|
||||
<p className="text-gray-500">Loading status...</p>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button
|
||||
onClick={() => backupMutation.mutate()}
|
||||
disabled={backupMutation.isPending}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
{backupMutation.isPending ? (
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="p-4 bg-green-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
||||
<span className="font-medium text-green-800">
|
||||
Status
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-green-600 mt-1">
|
||||
{dbStatus?.connected ? "Connected" : "Disconnected"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-blue-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Database className="h-4 w-4 text-blue-500" />
|
||||
<span className="font-medium text-blue-800">Size</span>
|
||||
</div>
|
||||
<p className="text-blue-600 mt-1">
|
||||
{dbStatus?.size ?? "Unknown"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-purple-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Cloud className="h-4 w-4 text-purple-500" />
|
||||
<span className="font-medium text-purple-800">
|
||||
Records
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-purple-600 mt-1">
|
||||
{dbStatus?.patients
|
||||
? `${dbStatus.patients} patients`
|
||||
: "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<FileArchive className="h-4 w-4" />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
<span>
|
||||
{backupMutation.isPending
|
||||
? "Creating Backup..."
|
||||
: "Create Backup"}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<div className="text-sm text-gray-500">
|
||||
Last backup:{" "}
|
||||
{dbStatus?.lastBackup
|
||||
? formatDateToHumanReadable(dbStatus.lastBackup)
|
||||
: "Never"}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Database Status Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<Database className="h-5 w-5" />
|
||||
<span>Database Status</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoadingStatus ? (
|
||||
<p className="text-gray-500">Loading status...</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="p-4 bg-green-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
||||
<span className="font-medium text-green-800">Status</span>
|
||||
</div>
|
||||
<p className="text-green-600 mt-1">
|
||||
{dbStatus?.connected ? "Connected" : "Disconnected"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-blue-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Database className="h-4 w-4 text-blue-500" />
|
||||
<span className="font-medium text-blue-800">Size</span>
|
||||
</div>
|
||||
<p className="text-blue-600 mt-1">
|
||||
{dbStatus?.size ?? "Unknown"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-purple-50 rounded-lg">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Cloud className="h-4 w-4 text-purple-500" />
|
||||
<span className="font-medium text-purple-800">Records</span>
|
||||
</div>
|
||||
<p className="text-purple-600 mt-1">
|
||||
{dbStatus?.patients
|
||||
? `${dbStatus.patients} patients`
|
||||
: "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4">
|
||||
<div className="container mx-auto space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Documents</h1>
|
||||
<p className="text-muted-foreground">
|
||||
View and manage recent uploaded claim PDFs
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedPatient && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
Document Groups for {selectedPatient.firstName}{" "}
|
||||
{selectedPatient.lastName}
|
||||
</CardTitle>
|
||||
<CardDescription>Select a group to view PDFs</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{groups.length === 0 ? (
|
||||
<p className="text-muted-foreground">
|
||||
No groups found for this patient.
|
||||
</p>
|
||||
) : (
|
||||
groups.map((group: any) => (
|
||||
<Button
|
||||
key={group.id}
|
||||
variant={
|
||||
group.id === selectedGroupId ? "default" : "outline"
|
||||
}
|
||||
onClick={() =>
|
||||
setSelectedGroupId((prevId) =>
|
||||
prevId === group.id ? null : group.id
|
||||
)
|
||||
}
|
||||
>
|
||||
<FolderOpen className="w-4 h-4 mr-2" />
|
||||
Group - {group.title}
|
||||
</Button>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{selectedGroupId && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>PDFs in Group</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{groupPdfs.length === 0 ? (
|
||||
<p className="text-muted-foreground">
|
||||
No PDFs found in this group.
|
||||
</p>
|
||||
) : (
|
||||
groupPdfs.map((pdf: any) => (
|
||||
<div
|
||||
key={pdf.id}
|
||||
className="flex justify-between items-center border p-2 rounded"
|
||||
>
|
||||
<span className="text-sm">{pdf.filename}</span>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleViewPdf(pdf.id)}
|
||||
>
|
||||
<Eye className="w-4 h-4 text-gray-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdf.id, pdf.filename)
|
||||
}
|
||||
>
|
||||
<Download className="w-4 h-4 text-blue-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setCurrentPdf(pdf);
|
||||
setIsDeletePdfOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash className="w-4 h-4 text-red-600" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{fileBlobUrl && (
|
||||
<Card>
|
||||
<CardHeader className="flex justify-between items-center">
|
||||
<CardTitle>Viewing PDF</CardTitle>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="ml-auto text-red-600 border-red-500 hover:bg-red-100 hover:border-red-600"
|
||||
onClick={() => {
|
||||
setFileBlobUrl(null);
|
||||
setSelectedPdfId(null);
|
||||
}}
|
||||
>
|
||||
✕ Close
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<iframe
|
||||
src={fileBlobUrl}
|
||||
className="w-full h-[80vh] border rounded"
|
||||
title="PDF Viewer"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Patient Records</CardTitle>
|
||||
<CardDescription>
|
||||
Select a patient to view document groups
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PatientTable
|
||||
allowView
|
||||
allowDelete
|
||||
allowCheckbox
|
||||
allowEdit
|
||||
onSelectPatient={setSelectedPatient}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<DeleteConfirmationDialog
|
||||
isOpen={isDeletePdfOpen}
|
||||
onConfirm={handleConfirmDeletePdf}
|
||||
onCancel={() => setIsDeletePdfOpen(false)}
|
||||
entityName={`PDF #${currentPdf?.id}`}
|
||||
/>
|
||||
<div>
|
||||
<div className="container mx-auto space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Documents</h1>
|
||||
<p className="text-muted-foreground">
|
||||
View and manage recent uploaded claim PDFs
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{selectedPatient && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
Document Groups for {selectedPatient.firstName}{" "}
|
||||
{selectedPatient.lastName}
|
||||
</CardTitle>
|
||||
<CardDescription>Select a group to view PDFs</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{groups.length === 0 ? (
|
||||
<p className="text-muted-foreground">
|
||||
No groups found for this patient.
|
||||
</p>
|
||||
) : (
|
||||
groups.map((group: any) => (
|
||||
<Button
|
||||
key={group.id}
|
||||
variant={
|
||||
group.id === selectedGroupId ? "default" : "outline"
|
||||
}
|
||||
onClick={() =>
|
||||
setSelectedGroupId((prevId) =>
|
||||
prevId === group.id ? null : group.id
|
||||
)
|
||||
}
|
||||
>
|
||||
<FolderOpen className="w-4 h-4 mr-2" />
|
||||
Group - {group.title}
|
||||
</Button>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{selectedGroupId && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>PDFs in Group</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{groupPdfs.length === 0 ? (
|
||||
<p className="text-muted-foreground">
|
||||
No PDFs found in this group.
|
||||
</p>
|
||||
) : (
|
||||
groupPdfs.map((pdf: any) => (
|
||||
<div
|
||||
key={pdf.id}
|
||||
className="flex justify-between items-center border p-2 rounded"
|
||||
>
|
||||
<span className="text-sm">{pdf.filename}</span>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleViewPdf(pdf.id)}
|
||||
>
|
||||
<Eye className="w-4 h-4 text-gray-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDownloadPdf(pdf.id, pdf.filename)}
|
||||
>
|
||||
<Download className="w-4 h-4 text-blue-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setCurrentPdf(pdf);
|
||||
setIsDeletePdfOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash className="w-4 h-4 text-red-600" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{fileBlobUrl && (
|
||||
<Card>
|
||||
<CardHeader className="flex justify-between items-center">
|
||||
<CardTitle>Viewing PDF</CardTitle>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="ml-auto text-red-600 border-red-500 hover:bg-red-100 hover:border-red-600"
|
||||
onClick={() => {
|
||||
setFileBlobUrl(null);
|
||||
setSelectedPdfId(null);
|
||||
}}
|
||||
>
|
||||
✕ Close
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<iframe
|
||||
src={fileBlobUrl}
|
||||
className="w-full h-[80vh] border rounded"
|
||||
title="PDF Viewer"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Patient Records</CardTitle>
|
||||
<CardDescription>
|
||||
Select a patient to view document groups
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PatientTable
|
||||
allowView
|
||||
allowDelete
|
||||
allowCheckbox
|
||||
allowEdit
|
||||
onSelectPatient={setSelectedPatient}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<DeleteConfirmationDialog
|
||||
isOpen={isDeletePdfOpen}
|
||||
onConfirm={handleConfirmDeletePdf}
|
||||
onCancel={() => setIsDeletePdfOpen(false)}
|
||||
entityName={`PDF #${currentPdf?.id}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -210,125 +208,114 @@ export default function InsuranceEligibilityPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
<div>
|
||||
<SeleniumTaskBanner
|
||||
status={status}
|
||||
message={message}
|
||||
show={show}
|
||||
onClear={() => dispatch(clearTaskStatus())}
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
<div className="container mx-auto space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
Insurance Eligibility
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Check insurance eligibility and view patient information
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SeleniumTaskBanner
|
||||
status={status}
|
||||
message={message}
|
||||
show={show}
|
||||
onClear={() => dispatch(clearTaskStatus())}
|
||||
/>
|
||||
{/* Insurance Eligibility Check Form */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Check Insurance Eligibility</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-4 md:grid-cols-4 gap-4 mb-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="memberId">Member ID</Label>
|
||||
<Input
|
||||
id="memberId"
|
||||
placeholder="Enter member ID"
|
||||
value={memberId}
|
||||
onChange={(e) => setMemberId(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4">
|
||||
<div className="container mx-auto space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
Insurance Eligibility
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Check insurance eligibility and view patient information
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<DateInput
|
||||
label="Date of Birth"
|
||||
value={dateOfBirth}
|
||||
onChange={setDateOfBirth}
|
||||
disableFuture
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="firstName">First Name</Label>
|
||||
<Input
|
||||
id="firstName"
|
||||
placeholder="Enter first name"
|
||||
value={firstName}
|
||||
onChange={(e) => setFirstName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="lastName">Last Name</Label>
|
||||
<Input
|
||||
id="lastName"
|
||||
placeholder="Enter last name"
|
||||
value={lastName}
|
||||
onChange={(e) => setLastName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Insurance Eligibility Check Form */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Check Insurance Eligibility</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-4 md:grid-cols-4 gap-4 mb-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="memberId">Member ID</Label>
|
||||
<Input
|
||||
id="memberId"
|
||||
placeholder="Enter member ID"
|
||||
value={memberId}
|
||||
onChange={(e) => setMemberId(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => handleMHButton()}
|
||||
className="w-full"
|
||||
disabled={isCheckingEligibility}
|
||||
>
|
||||
{isCheckingEligibility ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
MH
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="space-y-2">
|
||||
<DateInput
|
||||
label="Date of Birth"
|
||||
value={dateOfBirth}
|
||||
onChange={setDateOfBirth}
|
||||
disableFuture
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="firstName">First Name</Label>
|
||||
<Input
|
||||
id="firstName"
|
||||
placeholder="Enter first name"
|
||||
value={firstName}
|
||||
onChange={(e) => setFirstName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="lastName">Last Name</Label>
|
||||
<Input
|
||||
id="lastName"
|
||||
placeholder="Enter last name"
|
||||
value={lastName}
|
||||
onChange={(e) => setLastName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => handleMHButton()}
|
||||
className="w-full"
|
||||
disabled={isCheckingEligibility}
|
||||
>
|
||||
{isCheckingEligibility ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
MH
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Patients Table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Patient Records</CardTitle>
|
||||
<CardDescription>
|
||||
Select Patients and Check Their Eligibility
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PatientTable
|
||||
allowView={true}
|
||||
allowDelete={true}
|
||||
allowCheckbox={true}
|
||||
allowEdit={true}
|
||||
onSelectPatient={setSelectedPatient}
|
||||
onPageChange={setCurrentTablePage}
|
||||
onSearchChange={setCurrentTableSearchTerm}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
{/* Patients Table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Patient Records</CardTitle>
|
||||
<CardDescription>
|
||||
Select Patients and Check Their Eligibility
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PatientTable
|
||||
allowView={true}
|
||||
allowDelete={true}
|
||||
allowCheckbox={true}
|
||||
allowEdit={true}
|
||||
onSelectPatient={setSelectedPatient}
|
||||
onPageChange={setCurrentTablePage}
|
||||
onSearchChange={setCurrentTableSearchTerm}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { PatientTable } from "@/components/patients/patient-table";
|
||||
import { AddPatientModal } from "@/components/patients/add-patient-modal";
|
||||
import { FileUploadZone } from "@/components/file-upload/file-upload-zone";
|
||||
@@ -137,107 +135,96 @@ export default function PatientsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
/>
|
||||
<div>
|
||||
<div className="container mx-auto space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Patients</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage patient records and information
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCurrentPatient(undefined);
|
||||
setIsAddPatientOpen(true);
|
||||
}}
|
||||
className="gap-1"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
New Patient
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4">
|
||||
<div className="container mx-auto space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Patients</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage patient records and information
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCurrentPatient(undefined);
|
||||
setIsAddPatientOpen(true);
|
||||
}}
|
||||
className="gap-1"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
New Patient
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File Upload Zone */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<div className="md:col-span-3">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Upload Patient Document
|
||||
</CardTitle>
|
||||
<File className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FileUploadZone
|
||||
onFileUpload={handleFileUpload}
|
||||
isUploading={isUploading}
|
||||
acceptedFileTypes="application/pdf"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="md:col-span-1 flex items-end">
|
||||
<Button
|
||||
className="w-full h-12 gap-2"
|
||||
disabled={!uploadedFile || isExtracting}
|
||||
onClick={handleExtract}
|
||||
>
|
||||
{isExtracting ? (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FilePlus className="h-4 w-4" />
|
||||
Extract Info And Claim
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Patients Table */}
|
||||
{/* File Upload Zone */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<div className="md:col-span-3">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Patient Records</CardTitle>
|
||||
<CardDescription>
|
||||
View and manage all patient information
|
||||
</CardDescription>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Upload Patient Document
|
||||
</CardTitle>
|
||||
<File className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PatientTable
|
||||
allowDelete={true}
|
||||
allowEdit={true}
|
||||
allowView={true}
|
||||
<FileUploadZone
|
||||
onFileUpload={handleFileUpload}
|
||||
isUploading={isUploading}
|
||||
acceptedFileTypes="application/pdf"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Add/Edit Patient Modal */}
|
||||
<AddPatientModal
|
||||
ref={addPatientModalRef}
|
||||
open={isAddPatientOpen}
|
||||
onOpenChange={setIsAddPatientOpen}
|
||||
onSubmit={handleAddPatient}
|
||||
isLoading={isLoading}
|
||||
patient={currentPatient}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
<div className="md:col-span-1 flex items-end">
|
||||
<Button
|
||||
className="w-full h-12 gap-2"
|
||||
disabled={!uploadedFile || isExtracting}
|
||||
onClick={handleExtract}
|
||||
>
|
||||
{isExtracting ? (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FilePlus className="h-4 w-4" />
|
||||
Extract Info And Claim
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Patients Table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Patient Records</CardTitle>
|
||||
<CardDescription>
|
||||
View and manage all patient information
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PatientTable
|
||||
allowDelete={true}
|
||||
allowEdit={true}
|
||||
allowView={true}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Add/Edit Patient Modal */}
|
||||
<AddPatientModal
|
||||
ref={addPatientModalRef}
|
||||
open={isAddPatientOpen}
|
||||
onOpenChange={setIsAddPatientOpen}
|
||||
onSubmit={handleAddPatient}
|
||||
isLoading={isLoading}
|
||||
patient={currentPatient}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
@@ -10,12 +8,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import {
|
||||
DollarSign,
|
||||
Upload,
|
||||
Image,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { DollarSign, Upload, Image, X } from "lucide-react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -116,228 +109,217 @@ export default function PaymentsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
/>
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-gray-800">Payments</h1>
|
||||
<p className="text-gray-600">
|
||||
Manage patient payments and outstanding balances
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
<div className="mt-4 md:mt-0 flex items-center space-x-2">
|
||||
<Select
|
||||
defaultValue="all-time"
|
||||
onValueChange={(value) => setPaymentPeriod(value)}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select period" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all-time">All Time</SelectItem>
|
||||
<SelectItem value="this-month">This Month</SelectItem>
|
||||
<SelectItem value="last-month">Last Month</SelectItem>
|
||||
<SelectItem value="last-90-days">Last 90 Days</SelectItem>
|
||||
<SelectItem value="this-year">This Year</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-gray-800">Payments</h1>
|
||||
<p className="text-gray-600">
|
||||
Manage patient payments and outstanding balances
|
||||
</p>
|
||||
{/* Payment Summary Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-gray-500">
|
||||
Outstanding Balance
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<DollarSign className="h-5 w-5 text-yellow-500 mr-2" />
|
||||
<div className="text-2xl font-bold">$0</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From 0 outstanding invoices
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="mt-4 md:mt-0 flex items-center space-x-2">
|
||||
<Select
|
||||
defaultValue="all-time"
|
||||
onValueChange={(value) => setPaymentPeriod(value)}
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-gray-500">
|
||||
Payments Collected
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<DollarSign className="h-5 w-5 text-green-500 mr-2" />
|
||||
<div className="text-2xl font-bold">${0}</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From 0 completed payments
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-gray-500">
|
||||
Pending Payments
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<DollarSign className="h-5 w-5 text-blue-500 mr-2" />
|
||||
<div className="text-2xl font-bold">$0</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From 0 pending transactions
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* OCR Image Upload Section - not working rn*/}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-medium text-gray-800">
|
||||
Payment Document OCR
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex gap-4">
|
||||
<div
|
||||
className={`flex-1 border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
|
||||
isDragging
|
||||
? "border-blue-400 bg-blue-50"
|
||||
: uploadedImage
|
||||
? "border-green-400 bg-green-50"
|
||||
: "border-gray-300 bg-gray-50 hover:border-gray-400"
|
||||
}`}
|
||||
onDrop={handleImageDrop}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
}}
|
||||
onDragLeave={() => setIsDragging(false)}
|
||||
onClick={() =>
|
||||
document.getElementById("image-upload-input")?.click()
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select period" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all-time">All Time</SelectItem>
|
||||
<SelectItem value="this-month">This Month</SelectItem>
|
||||
<SelectItem value="last-month">Last Month</SelectItem>
|
||||
<SelectItem value="last-90-days">Last 90 Days</SelectItem>
|
||||
<SelectItem value="this-year">This Year</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment Summary Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-gray-500">
|
||||
Outstanding Balance
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<DollarSign className="h-5 w-5 text-yellow-500 mr-2" />
|
||||
<div className="text-2xl font-bold">$0</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From 0 outstanding invoices
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-gray-500">
|
||||
Payments Collected
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<DollarSign className="h-5 w-5 text-green-500 mr-2" />
|
||||
<div className="text-2xl font-bold">${0}</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From 0 completed payments
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-gray-500">
|
||||
Pending Payments
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center">
|
||||
<DollarSign className="h-5 w-5 text-blue-500 mr-2" />
|
||||
<div className="text-2xl font-bold">$0</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From 0 pending transactions
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* OCR Image Upload Section - not working rn*/}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-medium text-gray-800">
|
||||
Payment Document OCR
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex gap-4">
|
||||
<div
|
||||
className={`flex-1 border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
|
||||
isDragging
|
||||
? "border-blue-400 bg-blue-50"
|
||||
: uploadedImage
|
||||
? "border-green-400 bg-green-50"
|
||||
: "border-gray-300 bg-gray-50 hover:border-gray-400"
|
||||
}`}
|
||||
onDrop={handleImageDrop}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
}}
|
||||
onDragLeave={() => setIsDragging(false)}
|
||||
onClick={() =>
|
||||
document.getElementById("image-upload-input")?.click()
|
||||
}
|
||||
>
|
||||
{uploadedImage ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-center space-x-4">
|
||||
<Image className="h-8 w-8 text-green-500" />
|
||||
<div className="text-left">
|
||||
<p className="font-medium text-green-700">
|
||||
{uploadedImage.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{(uploadedImage.size / 1024 / 1024).toFixed(2)} MB
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeUploadedImage();
|
||||
}}
|
||||
className="ml-auto"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{isExtracting && (
|
||||
<div className="text-sm text-blue-600">
|
||||
Extracting payment information...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<Upload className="h-12 w-12 text-gray-400 mx-auto" />
|
||||
<div>
|
||||
<p className="text-lg font-medium text-gray-700 mb-2">
|
||||
Upload Payment Document
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
Drag and drop an image or click to browse
|
||||
</p>
|
||||
<Button variant="outline" type="button">
|
||||
Choose Image
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400">
|
||||
Supported formats: JPG, PNG, GIF • Max size: 10MB
|
||||
{uploadedImage ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-center space-x-4">
|
||||
<Image className="h-8 w-8 text-green-500" />
|
||||
<div className="text-left">
|
||||
<p className="font-medium text-green-700">
|
||||
{uploadedImage.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{(uploadedImage.size / 1024 / 1024).toFixed(2)} MB
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeUploadedImage();
|
||||
}}
|
||||
className="ml-auto"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{isExtracting && (
|
||||
<div className="text-sm text-blue-600">
|
||||
Extracting payment information...
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
id="image-upload-input"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleImageSelect}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-center">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!uploadedImage) {
|
||||
toast({
|
||||
title: "No Image Selected",
|
||||
description:
|
||||
"Please upload an image first before extracting information",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
handleOCRExtraction(uploadedImage);
|
||||
}}
|
||||
disabled={!uploadedImage || isExtracting}
|
||||
className="min-w-32"
|
||||
>
|
||||
{isExtracting ? "Extracting..." : "Extract Info"}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<Upload className="h-12 w-12 text-gray-400 mx-auto" />
|
||||
<div>
|
||||
<p className="text-lg font-medium text-gray-700 mb-2">
|
||||
Upload Payment Document
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
Drag and drop an image or click to browse
|
||||
</p>
|
||||
<Button variant="outline" type="button">
|
||||
Choose Image
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400">
|
||||
Supported formats: JPG, PNG, GIF • Max size: 10MB
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Recent Payments table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Payment's Records</CardTitle>
|
||||
<CardDescription>
|
||||
View and manage all recents patient's claims payments
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PaymentsRecentTable allowEdit allowDelete />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Recent Payments by Patients*/}
|
||||
<PaymentsOfPatientModal/>
|
||||
</main>
|
||||
<input
|
||||
id="image-upload-input"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleImageSelect}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-center">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!uploadedImage) {
|
||||
toast({
|
||||
title: "No Image Selected",
|
||||
description:
|
||||
"Please upload an image first before extracting information",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
handleOCRExtraction(uploadedImage);
|
||||
}}
|
||||
disabled={!uploadedImage || isExtracting}
|
||||
className="min-w-32"
|
||||
>
|
||||
{isExtracting ? "Extracting..." : "Extract Info"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Recent Payments table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Payment's Records</CardTitle>
|
||||
<CardDescription>
|
||||
View and manage all recents patient's claims payments
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PaymentsRecentTable allowEdit allowDelete />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Recent Payments by Patients*/}
|
||||
<PaymentsOfPatientModal />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
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";
|
||||
@@ -104,13 +102,7 @@ export default function PreAuthorizationsPage() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar isMobileOpen={isMobileMenuOpen} setIsMobileOpen={setIsMobileMenuOpen} />
|
||||
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4">
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-semibold text-gray-800">Pre-authorizations</h1>
|
||||
@@ -342,8 +334,6 @@ export default function PreAuthorizationsPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
304
apps/Frontend/src/pages/reports-page.tsx
Normal file
304
apps/Frontend/src/pages/reports-page.tsx
Normal file
@@ -0,0 +1,304 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Search,
|
||||
Edit,
|
||||
Eye,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Patient } from "@repo/db/types";
|
||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||
|
||||
export default function ReportsPage() {
|
||||
const { user } = useAuth();
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [searchField, setSearchField] = useState("all");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 5;
|
||||
|
||||
// Fetch patients
|
||||
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<
|
||||
Patient[]
|
||||
>({
|
||||
queryKey: ["/api/patients"],
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
// Filter patients based on search
|
||||
const filteredPatients = patients.filter((patient) => {
|
||||
if (!searchTerm) return true;
|
||||
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const fullName = `${patient.firstName} ${patient.lastName}`.toLowerCase();
|
||||
const patientId = `PID-${patient?.id?.toString().padStart(4, "0")}`;
|
||||
|
||||
switch (searchField) {
|
||||
case "name":
|
||||
return fullName.includes(searchLower);
|
||||
case "id":
|
||||
return patientId.toLowerCase().includes(searchLower);
|
||||
case "phone":
|
||||
return patient.phone?.toLowerCase().includes(searchLower) || false;
|
||||
case "all":
|
||||
default:
|
||||
return (
|
||||
fullName.includes(searchLower) ||
|
||||
patientId.toLowerCase().includes(searchLower) ||
|
||||
patient.phone?.toLowerCase().includes(searchLower) ||
|
||||
patient.email?.toLowerCase().includes(searchLower) ||
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Pagination
|
||||
const totalPages = Math.ceil(filteredPatients.length / itemsPerPage);
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = startIndex + itemsPerPage;
|
||||
const currentPatients = filteredPatients.slice(startIndex, endIndex);
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||
};
|
||||
|
||||
const getPatientInitials = (firstName: string, lastName: string) => {
|
||||
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-semibold text-gray-900 mb-2">Reports</h1>
|
||||
<p className="text-gray-600">
|
||||
View and manage all patient information
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search and Filters */}
|
||||
<Card className="mb-6">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||
<Input
|
||||
placeholder="Search patients..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Select value={searchField} onValueChange={setSearchField}>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Fields</SelectItem>
|
||||
<SelectItem value="name">Name</SelectItem>
|
||||
<SelectItem value="id">Patient ID</SelectItem>
|
||||
<SelectItem value="phone">Phone</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" size="sm">
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
Advanced
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Patient List */}
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
{isLoadingPatients ? (
|
||||
<div className="text-center py-8">Loading patients...</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Table Header */}
|
||||
<div className="grid grid-cols-12 gap-4 p-4 bg-gray-50 border-b text-sm font-medium text-gray-600">
|
||||
<div className="col-span-3">Patient</div>
|
||||
<div className="col-span-2">DOB / Gender</div>
|
||||
<div className="col-span-2">Contact</div>
|
||||
<div className="col-span-2">Insurance</div>
|
||||
<div className="col-span-2">Status</div>
|
||||
<div className="col-span-1">Actions</div>
|
||||
</div>
|
||||
|
||||
{/* Table Rows */}
|
||||
{currentPatients.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
{searchTerm
|
||||
? "No patients found matching your search."
|
||||
: "No patients available."}
|
||||
</div>
|
||||
) : (
|
||||
currentPatients.map((patient) => (
|
||||
<div
|
||||
key={patient.id}
|
||||
className="grid grid-cols-12 gap-4 p-4 border-b hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
{/* Patient Info */}
|
||||
<div className="col-span-3 flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-gray-200 rounded-full flex items-center justify-center text-sm font-medium text-gray-600">
|
||||
{getPatientInitials(
|
||||
patient.firstName,
|
||||
patient.lastName
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">
|
||||
{patient.firstName} {patient.lastName}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
PID-{patient.id?.toString().padStart(4, "0")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DOB / Gender */}
|
||||
<div className="col-span-2">
|
||||
<div className="text-sm text-gray-900">
|
||||
{formatDateToHumanReadable(patient.dateOfBirth)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 capitalize">
|
||||
{patient.gender}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact */}
|
||||
<div className="col-span-2">
|
||||
<div className="text-sm text-gray-900">
|
||||
{patient.phone || "Not provided"}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{patient.email || "No email"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Insurance */}
|
||||
<div className="col-span-2">
|
||||
<div className="text-sm text-gray-900">
|
||||
{patient.insuranceProvider
|
||||
? `${patient.insuranceProvider.charAt(0).toUpperCase()}${patient.insuranceProvider.slice(1)}`
|
||||
: "Not specified"}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
ID: {patient.insuranceId || "N/A"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div className="col-span-2">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
||||
patient.status === "active"
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-gray-100 text-gray-800"
|
||||
)}
|
||||
>
|
||||
{patient.status === "active" ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="col-span-1">
|
||||
<div className="flex space-x-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Edit className="h-4 w-4 text-blue-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Eye className="h-4 w-4 text-gray-600" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-between p-4 border-t bg-gray-50">
|
||||
<div className="text-sm text-gray-700">
|
||||
Showing {startIndex + 1} to{" "}
|
||||
{Math.min(endIndex, filteredPatients.length)} of{" "}
|
||||
{filteredPatients.length} results
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setCurrentPage(Math.max(1, currentPage - 1))
|
||||
}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4 mr-1" />
|
||||
Previous
|
||||
</Button>
|
||||
|
||||
{/* Page Numbers */}
|
||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map(
|
||||
(page) => (
|
||||
<Button
|
||||
key={page}
|
||||
variant={
|
||||
currentPage === page ? "default" : "outline"
|
||||
}
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage(page)}
|
||||
className="w-8 h-8 p-0"
|
||||
>
|
||||
{page}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setCurrentPage(Math.min(totalPages, currentPage + 1))
|
||||
}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { StaffTable } from "@/components/staffs/staff-table";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
@@ -259,14 +257,7 @@ export default function SettingsPage() {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||
<Sidebar
|
||||
isMobileOpen={isMobileMenuOpen}
|
||||
setIsMobileOpen={setIsMobileMenuOpen}
|
||||
/>
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||
<main className="flex-1 overflow-y-auto p-2">
|
||||
<div>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className="mt-8">
|
||||
@@ -371,8 +362,6 @@ export default function SettingsPage() {
|
||||
<div className="mt-6">
|
||||
<CredentialTable />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user