just brought
This commit is contained in:
@@ -1,37 +1,48 @@
|
||||
import { Switch, Route } from "wouter";
|
||||
import React, { Suspense, lazy } from "react";
|
||||
import { queryClient } from "./lib/queryClient";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Toaster } from "./components/ui/toaster";
|
||||
import { TooltipProvider } from "./components/ui/tooltip";
|
||||
import NotFound from "./pages/not-found";
|
||||
import Dashboard from "./pages/dashboard";
|
||||
import AuthPage from "./pages/auth-page";
|
||||
import AppointmentsPage from "./pages/appointments-page";
|
||||
import PatientsPage from "./pages/patients-page";
|
||||
import { ProtectedRoute } from "./lib/protected-route";
|
||||
import { AuthProvider } from "./hooks/use-auth";
|
||||
import SettingsPage from "./pages/settings-page";
|
||||
|
||||
import Dashboard from "./pages/dashboard";
|
||||
const AuthPage = lazy(() => import("./pages/auth-page"));
|
||||
const AppointmentsPage = lazy(() => import("./pages/appointments-page"));
|
||||
const PatientsPage = lazy(() => import("./pages/patients-page"));
|
||||
const SettingsPage = lazy(() => import("./pages/settings-page"));
|
||||
const ClaimsPage = lazy(() => import("./pages/claims-page"));
|
||||
const PreAuthorizationsPage = lazy(() => import("./pages/preauthorizations-page"));
|
||||
const PaymentsPage = lazy(() => import("./pages/payments-page"));
|
||||
const NotFound = lazy(() => import("./pages/not-found"));
|
||||
|
||||
function Router() {
|
||||
return (
|
||||
<Switch>
|
||||
<ProtectedRoute path="/" component={Dashboard} />
|
||||
<ProtectedRoute path="/appointments" component={AppointmentsPage} />
|
||||
<ProtectedRoute path="/patients" component={PatientsPage} />
|
||||
<ProtectedRoute path="/settings" component={SettingsPage}/>
|
||||
<Route path="/auth" component={AuthPage} />
|
||||
<Route component={NotFound} />
|
||||
<ProtectedRoute path="/" component={() => <Dashboard />} />
|
||||
<ProtectedRoute path="/appointments" component={() => <AppointmentsPage />} />
|
||||
<ProtectedRoute path="/patients" component={() => <PatientsPage />} />
|
||||
<ProtectedRoute path="/settings" component={() => <SettingsPage />} />
|
||||
<ProtectedRoute path="/claims" component={() => <ClaimsPage />} />
|
||||
<ProtectedRoute path="/preauthorizations" component={() => <PreAuthorizationsPage />} />
|
||||
<ProtectedRoute path="/payments" component={() => <PaymentsPage />} />
|
||||
<Route path="/auth" component={() => <AuthPage />} />
|
||||
<Route component={() => <NotFound />} />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Router />
|
||||
</Suspense>
|
||||
</TooltipProvider>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
|
||||
|
||||
interface AppointmentsByDayProps {
|
||||
appointments: any[];
|
||||
}
|
||||
|
||||
export function AppointmentsByDay({ appointments }: AppointmentsByDayProps) {
|
||||
// Data processing for appointments by day
|
||||
const daysOfWeek = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||
|
||||
// Initialize counts for each day
|
||||
const countsByDay = daysOfWeek.map(day => ({ day, count: 0 }));
|
||||
|
||||
// Count appointments by day of week
|
||||
appointments.forEach(appointment => {
|
||||
const date = new Date(appointment.date);
|
||||
const dayOfWeek = date.getDay(); // 0 = Sunday, 1 = Monday, ...
|
||||
const dayIndex = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Adjust to make Monday first
|
||||
countsByDay[dayIndex].count += 1;
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="shadow-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base font-medium">Appointments by Day</CardTitle>
|
||||
<p className="text-xs text-muted-foreground">Distribution of appointments throughout the week</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[200px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
data={countsByDay}
|
||||
margin={{ top: 5, right: 5, left: 0, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis dataKey="day" fontSize={12} tickLine={false} axisLine={false} />
|
||||
<YAxis fontSize={12} tickLine={false} axisLine={false} />
|
||||
<Tooltip
|
||||
formatter={(value) => [`${value} appointments`, "Count"]}
|
||||
labelFormatter={(value) => `${value}`}
|
||||
/>
|
||||
<Bar dataKey="count" fill="#2563eb" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
67
apps/Frontend/src/components/analytics/new-patients.tsx
Normal file
67
apps/Frontend/src/components/analytics/new-patients.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
|
||||
|
||||
interface NewPatientsProps {
|
||||
patients: any[];
|
||||
}
|
||||
|
||||
export function NewPatients({ patients }: NewPatientsProps) {
|
||||
// Get months for the chart
|
||||
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
// Process patient data by registration month
|
||||
const patientsByMonth = months.map(month => ({ name: month, count: 0 }));
|
||||
|
||||
// Count new patients by month
|
||||
patients.forEach(patient => {
|
||||
const createdDate = new Date(patient.createdAt);
|
||||
const monthIndex = createdDate.getMonth();
|
||||
patientsByMonth[monthIndex].count += 1;
|
||||
});
|
||||
|
||||
// Add some sample data for visual effect if no patients
|
||||
if (patients.length === 0) {
|
||||
// Sample data pattern similar to the screenshot
|
||||
const sampleData = [17, 12, 22, 16, 15, 17, 22, 28, 20, 16];
|
||||
sampleData.forEach((value, index) => {
|
||||
if (index < patientsByMonth.length) {
|
||||
patientsByMonth[index].count = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="shadow-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base font-medium">New Patients</CardTitle>
|
||||
<p className="text-xs text-muted-foreground">Monthly trend of new patient registrations</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[200px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart
|
||||
data={patientsByMonth}
|
||||
margin={{ top: 5, right: 5, left: 0, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis dataKey="name" fontSize={12} tickLine={false} axisLine={false} />
|
||||
<YAxis fontSize={12} tickLine={false} axisLine={false} />
|
||||
<Tooltip
|
||||
formatter={(value) => [`${value} patients`, "Count"]}
|
||||
labelFormatter={(value) => `${value}`}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="count"
|
||||
stroke="#f97316"
|
||||
strokeWidth={2}
|
||||
dot={{ r: 4, strokeWidth: 2 }}
|
||||
activeDot={{ r: 6, strokeWidth: 2 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { LayoutDashboard, Users, Calendar, FileText, Settings } from "lucide-react";
|
||||
import { LayoutDashboard, Users, Calendar, Settings, FileCheck, ClipboardCheck, CreditCard } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SidebarProps {
|
||||
@@ -26,6 +27,21 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) {
|
||||
path: "/patients",
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
name: "Claims",
|
||||
path: "/claims",
|
||||
icon: <FileCheck className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
name: "Pre-authorizations",
|
||||
path: "/preauthorizations",
|
||||
icon: <ClipboardCheck className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
name: "Payments",
|
||||
path: "/payments",
|
||||
icon: <CreditCard className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
path: "/settings",
|
||||
|
||||
255
apps/Frontend/src/pages/claims-page.tsx
Normal file
255
apps/Frontend/src/pages/claims-page.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import { useState, useEffect } 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 { ClaimForm } from "@/components/claims/claim-form";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
// import { Patient, Appointment } from "@shared/schema";
|
||||
import { PatientUncheckedCreateInputObjectSchema, AppointmentUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
|
||||
import { Plus, FileCheck, CheckCircle, Clock, AlertCircle } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
|
||||
export default function ClaimsPage() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [isClaimFormOpen, setIsClaimFormOpen] = useState(false);
|
||||
const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
|
||||
const [selectedAppointment, setSelectedAppointment] = useState<number | null>(null);
|
||||
|
||||
const { toast } = useToast();
|
||||
const { user } = useAuth();
|
||||
|
||||
// Fetch patients
|
||||
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<Patient[]>({
|
||||
queryKey: ["/api/patients"],
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
// Fetch appointments
|
||||
const {
|
||||
data: appointments = [] as Appointment[],
|
||||
isLoading: isLoadingAppointments
|
||||
} = useQuery<Appointment[]>({
|
||||
queryKey: ["/api/appointments"],
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||
};
|
||||
|
||||
const handleNewClaim = (patientId: number, appointmentId: number) => {
|
||||
setSelectedPatient(patientId);
|
||||
setSelectedAppointment(appointmentId);
|
||||
setIsClaimFormOpen(true);
|
||||
};
|
||||
|
||||
const closeClaim = () => {
|
||||
setIsClaimFormOpen(false);
|
||||
setSelectedPatient(null);
|
||||
setSelectedAppointment(null);
|
||||
};
|
||||
|
||||
// Get unique patients with appointments
|
||||
const patientsWithAppointments = appointments.reduce((acc, appointment) => {
|
||||
if (!acc.some(item => item.patientId === appointment.patientId)) {
|
||||
const patient = patients.find(p => p.id === appointment.patientId);
|
||||
if (patient) {
|
||||
acc.push({
|
||||
patientId: patient.id,
|
||||
patientName: `${patient.firstName} ${patient.lastName}`,
|
||||
appointmentId: appointment.id,
|
||||
insuranceProvider: patient.insuranceProvider || 'N/A',
|
||||
insuranceId: patient.insuranceId || 'N/A',
|
||||
lastAppointment: appointment.date
|
||||
});
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, [] as Array<{
|
||||
patientId: number;
|
||||
patientName: string;
|
||||
appointmentId: number;
|
||||
insuranceProvider: string;
|
||||
insuranceId: string;
|
||||
lastAppointment: string;
|
||||
}>);
|
||||
|
||||
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">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-semibold text-gray-800">Insurance Claims</h1>
|
||||
<p className="text-gray-600">Manage and submit insurance claims for patients</p>
|
||||
</div>
|
||||
|
||||
{/* New Claims Section */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div
|
||||
className="flex items-center cursor-pointer group"
|
||||
onClick={() => {
|
||||
if (patientsWithAppointments.length > 0) {
|
||||
const firstPatient = patientsWithAppointments[0];
|
||||
handleNewClaim(firstPatient.patientId, firstPatient.appointmentId);
|
||||
} else {
|
||||
toast({
|
||||
title: "No patients available",
|
||||
description: "There are no patients with appointments to create a claim",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<h2 className="text-xl font-medium text-gray-800 group-hover:text-primary">New Claims</h2>
|
||||
<div className="ml-2 text-primary">
|
||||
<FileCheck className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle>Recent Patients for Claims</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoadingPatients || isLoadingAppointments ? (
|
||||
<div className="text-center py-4">Loading patients data...</div>
|
||||
) : patientsWithAppointments.length > 0 ? (
|
||||
<div className="divide-y">
|
||||
{patientsWithAppointments.map((item) => (
|
||||
<div
|
||||
key={item.patientId}
|
||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
||||
onClick={() => handleNewClaim(item.patientId, item.appointmentId)}
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-medium">{item.patientName}</h3>
|
||||
<div className="text-sm text-gray-500">
|
||||
<span>Insurance: {item.insuranceProvider === 'delta'
|
||||
? 'Delta Dental'
|
||||
: item.insuranceProvider === 'metlife'
|
||||
? 'MetLife'
|
||||
: item.insuranceProvider === 'cigna'
|
||||
? 'Cigna'
|
||||
: item.insuranceProvider === 'aetna'
|
||||
? 'Aetna'
|
||||
: item.insuranceProvider}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>ID: {item.insuranceId}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>Last Visit: {new Date(item.lastAppointment).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-primary">
|
||||
<FileCheck className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<FileCheck className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No eligible patients for claims</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Patients with appointments will appear here for insurance claim processing
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Old Claims Section */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-medium text-gray-800">Old Claims</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle>Submitted Claims History</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Sample Old Claims */}
|
||||
<div className="divide-y">
|
||||
{patientsWithAppointments.slice(0, 3).map((item, index) => (
|
||||
<div
|
||||
key={`old-claim-${index}`}
|
||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
||||
onClick={() => toast({
|
||||
title: "Claim Details",
|
||||
description: `Viewing details for claim #${2000 + index}`
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-medium">{item.patientName}</h3>
|
||||
<div className="text-sm text-gray-500">
|
||||
<span>Claim #: {2000 + index}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>Submitted: {format(new Date(new Date().setDate(new Date().getDate() - (index * 15))), 'MMM dd, yyyy')}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>Amount: ${(Math.floor(Math.random() * 500) + 100).toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
index === 0 ? 'bg-yellow-100 text-yellow-800' :
|
||||
index === 1 ? 'bg-green-100 text-green-800' :
|
||||
'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{index === 0 ? (
|
||||
<span className="flex items-center">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
Pending
|
||||
</span>
|
||||
) : index === 1 ? (
|
||||
<span className="flex items-center">
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Approved
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center">
|
||||
<AlertCircle className="h-3 w-3 mr-1" />
|
||||
Review
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{patientsWithAppointments.length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<Clock className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No claim history</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Submitted insurance claims will appear here
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Claim Form Modal */}
|
||||
{isClaimFormOpen && selectedPatient !== null && selectedAppointment !== null && (
|
||||
<ClaimForm
|
||||
patientId={selectedPatient}
|
||||
appointmentId={selectedAppointment}
|
||||
patientName="" // Will be loaded by the component
|
||||
onClose={closeClaim}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
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,
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
@@ -542,6 +544,12 @@ export default function Dashboard() {
|
||||
</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>
|
||||
|
||||
{/* Patient Management Section */}
|
||||
<div className="flex flex-col space-y-4">
|
||||
{/* Patient Header */}
|
||||
|
||||
414
apps/Frontend/src/pages/payments-page.tsx
Normal file
414
apps/Frontend/src/pages/payments-page.tsx
Normal file
@@ -0,0 +1,414 @@
|
||||
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, CardFooter } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
// import { Patient, Appointment } from "@repo/db/shared/schemas";
|
||||
import { Patient, Appointment } from "@repo/db/shared/schemas";
|
||||
import {
|
||||
CreditCard,
|
||||
Clock,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
DollarSign,
|
||||
Receipt,
|
||||
Plus,
|
||||
ArrowDown,
|
||||
ReceiptText
|
||||
} from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableRow,
|
||||
TableHead,
|
||||
TableCell
|
||||
} from "@/components/ui/table";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
export default function PaymentsPage() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [paymentPeriod, setPaymentPeriod] = useState<string>("all-time");
|
||||
const { toast } = useToast();
|
||||
const { user } = useAuth();
|
||||
|
||||
// Fetch patients
|
||||
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<Patient[]>({
|
||||
queryKey: ["/api/patients"],
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
// Fetch appointments
|
||||
const {
|
||||
data: appointments = [] as Appointment[],
|
||||
isLoading: isLoadingAppointments
|
||||
} = useQuery<Appointment[]>({
|
||||
queryKey: ["/api/appointments"],
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||
};
|
||||
|
||||
// Sample payment data
|
||||
const samplePayments = [
|
||||
{
|
||||
id: "PMT-1001",
|
||||
patientId: patients[0]?.id || 1,
|
||||
amount: 75.00,
|
||||
date: new Date(new Date().setDate(new Date().getDate() - 2)),
|
||||
method: "Credit Card",
|
||||
status: "completed",
|
||||
description: "Co-pay for cleaning"
|
||||
},
|
||||
{
|
||||
id: "PMT-1002",
|
||||
patientId: patients[0]?.id || 1,
|
||||
amount: 150.00,
|
||||
date: new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||
method: "Insurance",
|
||||
status: "processing",
|
||||
description: "Insurance claim for x-rays"
|
||||
},
|
||||
{
|
||||
id: "PMT-1003",
|
||||
patientId: patients[0]?.id || 1,
|
||||
amount: 350.00,
|
||||
date: new Date(new Date().setDate(new Date().getDate() - 14)),
|
||||
method: "Check",
|
||||
status: "completed",
|
||||
description: "Payment for root canal"
|
||||
},
|
||||
{
|
||||
id: "PMT-1004",
|
||||
patientId: patients[0]?.id || 1,
|
||||
amount: 120.00,
|
||||
date: new Date(new Date().setDate(new Date().getDate() - 30)),
|
||||
method: "Credit Card",
|
||||
status: "completed",
|
||||
description: "Filling procedure"
|
||||
}
|
||||
];
|
||||
|
||||
// Sample outstanding balances
|
||||
const sampleOutstanding = [
|
||||
{
|
||||
id: "INV-5001",
|
||||
patientId: patients[0]?.id || 1,
|
||||
amount: 210.50,
|
||||
dueDate: new Date(new Date().setDate(new Date().getDate() + 7)),
|
||||
description: "Crown procedure",
|
||||
created: new Date(new Date().setDate(new Date().getDate() - 10)),
|
||||
status: "pending"
|
||||
},
|
||||
{
|
||||
id: "INV-5002",
|
||||
patientId: patients[0]?.id || 1,
|
||||
amount: 85.00,
|
||||
dueDate: new Date(new Date().setDate(new Date().getDate() - 5)),
|
||||
description: "Diagnostic & preventive",
|
||||
created: new Date(new Date().setDate(new Date().getDate() - 20)),
|
||||
status: "overdue"
|
||||
}
|
||||
];
|
||||
|
||||
// Calculate summary data
|
||||
const totalOutstanding = sampleOutstanding.reduce((sum, item) => sum + item.amount, 0);
|
||||
const totalCollected = samplePayments
|
||||
.filter(payment => payment.status === "completed")
|
||||
.reduce((sum, payment) => sum + payment.amount, 0);
|
||||
const pendingAmount = samplePayments
|
||||
.filter(payment => payment.status === "processing")
|
||||
.reduce((sum, payment) => sum + payment.amount, 0);
|
||||
|
||||
const handleRecordPayment = (patientId: number, invoiceId?: string) => {
|
||||
const patient = patients.find(p => p.id === patientId);
|
||||
toast({
|
||||
title: "Payment form opened",
|
||||
description: `Recording payment for ${patient?.firstName} ${patient?.lastName}${invoiceId ? ` (Invoice: ${invoiceId})` : ''}`,
|
||||
});
|
||||
};
|
||||
|
||||
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">
|
||||
{/* 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="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>
|
||||
|
||||
<Button onClick={() => handleRecordPayment(patients[0]?.id || 1)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Record Payment
|
||||
</Button>
|
||||
</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">${totalOutstanding.toFixed(2)}</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From {sampleOutstanding.length} 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">${totalCollected.toFixed(2)}</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From {samplePayments.filter(p => p.status === "completed").length} 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">${pendingAmount.toFixed(2)}</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
From {samplePayments.filter(p => p.status === "processing").length} pending transactions
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Outstanding Balances Section */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-medium text-gray-800">Outstanding Balances</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
{sampleOutstanding.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Patient</TableHead>
|
||||
<TableHead>Invoice</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Amount</TableHead>
|
||||
<TableHead>Due Date</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Action</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{sampleOutstanding.map((invoice) => {
|
||||
const patient = patients.find(p => p.id === invoice.patientId) ||
|
||||
{ firstName: "Sample", lastName: "Patient" };
|
||||
|
||||
return (
|
||||
<TableRow key={invoice.id}>
|
||||
<TableCell>
|
||||
{patient.firstName} {patient.lastName}
|
||||
</TableCell>
|
||||
<TableCell>{invoice.id}</TableCell>
|
||||
<TableCell>{invoice.description}</TableCell>
|
||||
<TableCell>${invoice.amount.toFixed(2)}</TableCell>
|
||||
<TableCell>{format(invoice.dueDate, 'MMM dd, yyyy')}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
invoice.status === 'overdue'
|
||||
? 'bg-red-100 text-red-800'
|
||||
: 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{invoice.status === 'overdue' ? (
|
||||
<>
|
||||
<AlertCircle className="h-3 w-3 mr-1" />
|
||||
Overdue
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
Pending
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleRecordPayment(invoice.patientId, invoice.id)}
|
||||
>
|
||||
Pay Now
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<AlertCircle className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No outstanding balances</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
All patient accounts are current
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
{sampleOutstanding.length > 0 && (
|
||||
<CardFooter className="flex justify-between px-6 py-4 border-t">
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<ArrowDown className="h-4 w-4 mr-1" />
|
||||
<span>Download statement</span>
|
||||
</div>
|
||||
<div className="font-medium">
|
||||
Total: ${totalOutstanding.toFixed(2)}
|
||||
</div>
|
||||
</CardFooter>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Recent Payments Section */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-medium text-gray-800">Recent Payments</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
{samplePayments.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Patient</TableHead>
|
||||
<TableHead>Payment ID</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Date</TableHead>
|
||||
<TableHead>Amount</TableHead>
|
||||
<TableHead>Method</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Action</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{samplePayments.map((payment) => {
|
||||
const patient = patients.find(p => p.id === payment.patientId) ||
|
||||
{ firstName: "Sample", lastName: "Patient" };
|
||||
|
||||
return (
|
||||
<TableRow key={payment.id}>
|
||||
<TableCell>
|
||||
{patient.firstName} {patient.lastName}
|
||||
</TableCell>
|
||||
<TableCell>{payment.id}</TableCell>
|
||||
<TableCell>{payment.description}</TableCell>
|
||||
<TableCell>{format(payment.date, 'MMM dd, yyyy')}</TableCell>
|
||||
<TableCell>${payment.amount.toFixed(2)}</TableCell>
|
||||
<TableCell>{payment.method}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
payment.status === 'completed'
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{payment.status === 'completed' ? (
|
||||
<>
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Completed
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
Processing
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
toast({
|
||||
title: "Receipt Generated",
|
||||
description: `Receipt for payment ${payment.id} has been generated`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ReceiptText className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Receipt className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No payment history</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Payments will appear here once processed
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
350
apps/Frontend/src/pages/preauthorizations-page.tsx
Normal file
350
apps/Frontend/src/pages/preauthorizations-page.tsx
Normal file
@@ -0,0 +1,350 @@
|
||||
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";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
// import { Patient, Appointment } from "@repo/db/shared/schemas";
|
||||
import { Patient, Appointment } from "@repo/db/shared/schemas";
|
||||
import { Plus, ClipboardCheck, Clock, CheckCircle, AlertCircle } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
|
||||
export default function PreAuthorizationsPage() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [isPreAuthFormOpen, setIsPreAuthFormOpen] = useState(false);
|
||||
const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
|
||||
const [selectedProcedure, setSelectedProcedure] = useState<string | null>(null);
|
||||
|
||||
const { toast } = useToast();
|
||||
const { user } = useAuth();
|
||||
|
||||
// Fetch patients
|
||||
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<Patient[]>({
|
||||
queryKey: ["/api/patients"],
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
// Fetch appointments
|
||||
const {
|
||||
data: appointments = [] as Appointment[],
|
||||
isLoading: isLoadingAppointments
|
||||
} = useQuery<Appointment[]>({
|
||||
queryKey: ["/api/appointments"],
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||
};
|
||||
|
||||
const handleNewPreAuth = (patientId: number, procedure: string) => {
|
||||
setSelectedPatient(patientId);
|
||||
setSelectedProcedure(procedure);
|
||||
setIsPreAuthFormOpen(true);
|
||||
|
||||
// Show a toast notification of success
|
||||
const patient = patients.find(p => p.id === patientId);
|
||||
toast({
|
||||
title: "Pre-authorization Request Started",
|
||||
description: `Started pre-auth for ${patient?.firstName} ${patient?.lastName} - ${procedure}`,
|
||||
});
|
||||
};
|
||||
|
||||
// Common dental procedures requiring pre-authorization
|
||||
const dentalProcedures = [
|
||||
{ code: "D2740", name: "Crown - porcelain/ceramic" },
|
||||
{ code: "D2950", name: "Core buildup, including any pins" },
|
||||
{ code: "D3330", name: "Root Canal - molar" },
|
||||
{ code: "D4341", name: "Periodontal scaling & root planing" },
|
||||
{ code: "D4910", name: "Periodontal maintenance" },
|
||||
{ code: "D5110", name: "Complete denture - maxillary" },
|
||||
{ code: "D6010", name: "Surgical placement of implant body" },
|
||||
{ code: "D7240", name: "Removal of impacted tooth" },
|
||||
];
|
||||
|
||||
// Get patients with active insurance
|
||||
const patientsWithInsurance = patients.filter(patient =>
|
||||
patient.insuranceProvider && patient.insuranceId
|
||||
);
|
||||
|
||||
// Sample pre-authorization data
|
||||
const samplePreAuths = [
|
||||
{
|
||||
id: "PA2023-001",
|
||||
patientId: patientsWithInsurance[0]?.id || 1,
|
||||
procedureCode: "D2740",
|
||||
procedureName: "Crown - porcelain/ceramic",
|
||||
requestDate: new Date(new Date().setDate(new Date().getDate() - 14)),
|
||||
status: "approved",
|
||||
approvalDate: new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||
expirationDate: new Date(new Date().setMonth(new Date().getMonth() + 6)),
|
||||
},
|
||||
{
|
||||
id: "PA2023-002",
|
||||
patientId: patientsWithInsurance[0]?.id || 1,
|
||||
procedureCode: "D3330",
|
||||
procedureName: "Root Canal - molar",
|
||||
requestDate: new Date(new Date().setDate(new Date().getDate() - 5)),
|
||||
status: "pending",
|
||||
approvalDate: null,
|
||||
expirationDate: null,
|
||||
},
|
||||
{
|
||||
id: "PA2023-003",
|
||||
patientId: patientsWithInsurance[0]?.id || 1,
|
||||
procedureCode: "D7240",
|
||||
procedureName: "Removal of impacted tooth",
|
||||
requestDate: new Date(new Date().setDate(new Date().getDate() - 30)),
|
||||
status: "denied",
|
||||
approvalDate: null,
|
||||
expirationDate: null,
|
||||
denialReason: "Not medically necessary based on submitted documentation",
|
||||
}
|
||||
];
|
||||
|
||||
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">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-semibold text-gray-800">Pre-authorizations</h1>
|
||||
<p className="text-gray-600">Manage insurance pre-authorizations for dental procedures</p>
|
||||
</div>
|
||||
|
||||
{/* New Pre-Authorization Request Section */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-medium text-gray-800">New Pre-Authorization Request</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle>Recent Patients for Pre-Authorization</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoadingPatients ? (
|
||||
<div className="text-center py-4">Loading patients data...</div>
|
||||
) : patientsWithInsurance.length > 0 ? (
|
||||
<div className="divide-y">
|
||||
{patientsWithInsurance.map((patient) => (
|
||||
<div
|
||||
key={patient.id}
|
||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
||||
onClick={() => {
|
||||
setSelectedPatient(patient.id);
|
||||
handleNewPreAuth(
|
||||
patient.id,
|
||||
dentalProcedures[Math.floor(Math.random() * 3)].name
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-medium">{patient.firstName} {patient.lastName}</h3>
|
||||
<div className="text-sm text-gray-500">
|
||||
<span>Insurance: {patient.insuranceProvider === 'delta'
|
||||
? 'Delta Dental'
|
||||
: patient.insuranceProvider === 'metlife'
|
||||
? 'MetLife'
|
||||
: patient.insuranceProvider === 'cigna'
|
||||
? 'Cigna'
|
||||
: patient.insuranceProvider === 'aetna'
|
||||
? 'Aetna'
|
||||
: patient.insuranceProvider}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>ID: {patient.insuranceId}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>Procedure needed: {dentalProcedures[0].name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-primary">
|
||||
<ClipboardCheck className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<ClipboardCheck className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No patients with insurance</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Add insurance information to patients to request pre-authorizations
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Pre-Authorization Submitted Section */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-medium text-gray-800">Pre-Authorization Submitted</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle>Pending Pre-Authorization Requests</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{patientsWithInsurance.length > 0 ? (
|
||||
<div className="divide-y">
|
||||
{samplePreAuths.filter(auth => auth.status === 'pending').map((preAuth) => {
|
||||
const patient = patients.find(p => p.id === preAuth.patientId) ||
|
||||
{ firstName: "Unknown", lastName: "Patient" };
|
||||
|
||||
return (
|
||||
<div
|
||||
key={preAuth.id}
|
||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
||||
onClick={() => toast({
|
||||
title: "Pre-Authorization Details",
|
||||
description: `Viewing details for ${preAuth.id}`
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-medium">{patient.firstName} {patient.lastName} - {preAuth.procedureName}</h3>
|
||||
<div className="text-sm text-gray-500">
|
||||
<span>ID: {preAuth.id}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>Submitted: {format(preAuth.requestDate, 'MMM dd, yyyy')}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>Expected Response: {format(new Date(preAuth.requestDate.getTime() + 7 * 24 * 60 * 60 * 1000), 'MMM dd, yyyy')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-2 py-1 text-xs font-medium rounded-full bg-yellow-100 text-yellow-800">
|
||||
<span className="flex items-center">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
Pending
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{samplePreAuths.filter(auth => auth.status === 'pending').length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<Clock className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No pending requests</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Submitted pre-authorization requests will appear here
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Clock className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No pre-authorization history</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Submitted pre-authorization requests will appear here
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Pre-Authorization Results Section */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-medium text-gray-800">Pre-Authorization Results</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle>Completed Pre-Authorization Requests</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{patientsWithInsurance.length > 0 ? (
|
||||
<div className="divide-y">
|
||||
{samplePreAuths.filter(auth => auth.status !== 'pending').map((preAuth) => {
|
||||
const patient = patients.find(p => p.id === preAuth.patientId) ||
|
||||
{ firstName: "Unknown", lastName: "Patient" };
|
||||
|
||||
return (
|
||||
<div
|
||||
key={preAuth.id}
|
||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
||||
onClick={() => toast({
|
||||
title: "Pre-Authorization Details",
|
||||
description: `Viewing details for ${preAuth.id}`
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-medium">{patient.firstName} {patient.lastName} - {preAuth.procedureName}</h3>
|
||||
<div className="text-sm text-gray-500">
|
||||
<span>ID: {preAuth.id}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>Requested: {format(preAuth.requestDate, 'MMM dd, yyyy')}</span>
|
||||
{preAuth.status === 'approved' && (
|
||||
<>
|
||||
<span className="mx-2">•</span>
|
||||
<span>Expires: {format(preAuth.expirationDate as Date, 'MMM dd, yyyy')}</span>
|
||||
</>
|
||||
)}
|
||||
{preAuth.status === 'denied' && preAuth.denialReason && (
|
||||
<>
|
||||
<span className="mx-2">•</span>
|
||||
<span className="text-red-600">Reason: {preAuth.denialReason}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
preAuth.status === 'approved' ? 'bg-green-100 text-green-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{preAuth.status === 'approved' ? (
|
||||
<span className="flex items-center">
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Approved
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center">
|
||||
<AlertCircle className="h-3 w-3 mr-1" />
|
||||
Denied
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{samplePreAuths.filter(auth => auth.status !== 'pending').length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<CheckCircle className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No completed requests</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Processed pre-authorization results will appear here
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<CheckCircle className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||
<h3 className="text-lg font-medium">No pre-authorization results</h3>
|
||||
<p className="text-gray-500 mt-1">
|
||||
Completed pre-authorization requests will appear here
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user