Files
mediusa-clinic-os/src/components/billing/SuperbillView.tsx
T

345 lines
16 KiB
TypeScript

'use client';
import React, { useState } from 'react';
import { Superbill, ClinicTenant } from '@/types/clinical';
import { generateSuperbillPdf } from '@/lib/pdf-generator';
import {
Download,
CreditCard,
CheckCircle2,
ShieldAlert,
ArrowLeft,
DollarSign,
Printer,
FileCheck,
} from 'lucide-react';
interface SuperbillViewProps {
superbill: Superbill;
activeTenant: ClinicTenant;
onBack: () => void;
onMarkPaid: (superbillId: string, method: Superbill['paymentMethod']) => void;
}
export const SuperbillView: React.FC<SuperbillViewProps> = ({
superbill,
activeTenant,
onBack,
onMarkPaid,
}) => {
const [showPaymentModal, setShowPaymentModal] = useState(false);
const [isProcessingStripe, setIsProcessingStripe] = useState(false);
const [paymentSuccess, setPaymentSuccess] = useState(false);
const handleDownloadPdf = () => {
generateSuperbillPdf(superbill);
};
const handleStripePay = (method: Superbill['paymentMethod']) => {
setIsProcessingStripe(true);
setTimeout(() => {
setIsProcessingStripe(false);
setPaymentSuccess(true);
onMarkPaid(superbill.id, method);
setTimeout(() => {
setShowPaymentModal(false);
setPaymentSuccess(false);
}, 1500);
}, 1200);
};
return (
<div className="space-y-6">
{/* Top Header Bar */}
<div className="bg-white border border-slate-200 rounded-xl p-5 shadow-xs flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex items-center gap-3">
<button
type="button"
onClick={onBack}
className="p-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-lg transition"
title="Back"
>
<ArrowLeft className="w-5 h-5" />
</button>
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-bold text-slate-900">
Superbill Statement {superbill.invoiceNumber}
</h3>
<span
className={`text-xs px-2.5 py-0.5 rounded-full font-bold uppercase ${
superbill.balanceDue === 0
? 'bg-emerald-50 text-emerald-800 border border-emerald-200'
: 'bg-amber-50 text-amber-800 border border-amber-200'
}`}
>
{superbill.balanceDue === 0 ? 'Paid in Full' : 'Balance Due'}
</span>
</div>
<p className="text-xs text-slate-500 mt-0.5">
Standard CMS-1500 Reimbursement Statement for {superbill.patientName}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={handleDownloadPdf}
className="px-4 py-2 rounded-lg bg-sky-700 hover:bg-sky-800 text-white text-xs font-bold flex items-center gap-1.5 transition shadow-xs"
>
<Download className="w-4 h-4" />
Download PDF Superbill
</button>
{superbill.balanceDue > 0 && (
<button
type="button"
onClick={() => setShowPaymentModal(true)}
className="px-4 py-2 rounded-lg bg-emerald-700 hover:bg-emerald-800 text-white text-xs font-bold flex items-center gap-1.5 shadow-xs transition"
>
<CreditCard className="w-4 h-4" />
Collect Payment
</button>
)}
</div>
</div>
{/* Main Printed Superbill Card Simulation */}
<div className="bg-white border border-slate-200/90 rounded-xl p-6 sm:p-10 shadow-sm max-w-4xl mx-auto text-slate-800">
{/* Clinic & Statement Header */}
<div className="flex flex-col sm:flex-row sm:items-start justify-between pb-6 border-b border-slate-200 gap-4">
<div>
<span className="text-xs font-mono font-bold text-sky-800 uppercase tracking-widest">
OFFICIAL MEDICAL SUPERBILL &amp; CLAIM
</span>
<h2 className="text-xl font-black text-slate-900 mt-1">{superbill.clinicName}</h2>
<p className="text-xs text-slate-600 mt-1">{superbill.clinicAddress}</p>
<p className="text-xs text-slate-600">
Tax ID / EIN: <strong className="text-slate-800 font-mono">{superbill.clinicTaxId}</strong> POS:{' '}
<strong className="text-slate-800 font-mono">{superbill.posCode}</strong>
</p>
</div>
<div className="sm:text-right text-xs space-y-1">
<div className="text-slate-500 font-medium">Invoice Number</div>
<div className="font-mono text-sm font-bold text-slate-900">{superbill.invoiceNumber}</div>
<div className="text-slate-500 font-medium mt-2">Date of Service</div>
<div className="font-mono text-slate-800">{superbill.dateOfService}</div>
</div>
</div>
{/* Provider & Patient 2-Col Info */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 py-6 border-b border-slate-200 text-xs">
{/* Provider */}
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200">
<h4 className="font-bold text-slate-700 uppercase tracking-wider mb-2">
Rendering Provider
</h4>
<div className="font-bold text-slate-900 text-sm">{superbill.providerName}</div>
<div className="text-slate-600 mt-1">
National Provider Identifier (NPI):{' '}
<strong className="text-sky-800 font-mono">{superbill.providerNpi}</strong>
</div>
<div className="text-slate-600 mt-1">Specialty: Chiropractic Clinical Biomechanics</div>
</div>
{/* Patient */}
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200">
<h4 className="font-bold text-slate-700 uppercase tracking-wider mb-2">
Patient Demographics
</h4>
<div className="font-bold text-slate-900 text-sm">{superbill.patientName}</div>
<div className="text-slate-600 mt-1">DOB: {superbill.patientDob}</div>
<div className="text-slate-600 mt-1">Address: {superbill.patientAddress}</div>
</div>
</div>
{/* Diagnosis ICD-10 Section */}
<div className="py-6 border-b border-slate-200">
<h4 className="text-xs font-bold text-slate-700 uppercase tracking-wider mb-3">
Primary Diagnosis Codes (ICD-10-CM)
</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 text-xs">
{superbill.icd10Codes.map((icd, index) => (
<div
key={icd.code}
className="flex items-center gap-2 bg-slate-50 border border-slate-200 p-2.5 rounded-lg"
>
<span className="w-5 h-5 rounded bg-sky-100 text-sky-800 font-bold flex items-center justify-center font-mono text-xs">
{index + 1}
</span>
<span className="font-bold font-mono text-sky-800">{icd.code}</span>
<span className="text-slate-600 truncate">{icd.description}</span>
</div>
))}
</div>
</div>
{/* Procedures Table */}
<div className="py-6 border-b border-slate-200">
<h4 className="text-xs font-bold text-slate-700 uppercase tracking-wider mb-3">
Rendered Procedures (CPT Codes)
</h4>
<div className="overflow-x-auto">
<table className="w-full text-xs text-left">
<thead>
<tr className="bg-slate-100 text-slate-700 border-b border-slate-200">
<th className="py-2.5 px-3 font-bold">CPT Code</th>
<th className="py-2.5 px-3 font-bold">Service Description</th>
<th className="py-2.5 px-3 font-bold text-center">Units</th>
<th className="py-2.5 px-3 font-bold text-right">Fee</th>
<th className="py-2.5 px-3 font-bold text-right">Total</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{superbill.items.map((item) => (
<tr key={item.cptCode} className="hover:bg-slate-50">
<td className="py-2.5 px-3 font-mono font-bold text-sky-800">{item.cptCode}</td>
<td className="py-2.5 px-3 text-slate-800">{item.description}</td>
<td className="py-2.5 px-3 text-center text-slate-600">{item.units}</td>
<td className="py-2.5 px-3 text-right font-mono">${item.rate.toFixed(2)}</td>
<td className="py-2.5 px-3 text-right font-mono font-bold text-slate-900">
${item.total.toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Financial Summary */}
<div className="pt-6 flex flex-col sm:flex-row sm:items-end justify-between gap-6">
<div className="text-xs text-slate-500 max-w-sm">
<p>
This Superbill fulfills the standard medical reimbursement criteria for direct patient
submission to commercial insurers, Medicare supplements, and HSA/FSA plans.
</p>
<p className="mt-2 text-slate-700">
Payment Record: <strong className="text-sky-800">{superbill.paymentMethod}</strong>
</p>
</div>
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200 w-full sm:w-64 space-y-2 text-xs">
<div className="flex justify-between text-slate-600">
<span>Total Charges:</span>
<span className="font-mono text-slate-900 font-semibold">${superbill.totalAmount.toFixed(2)}</span>
</div>
<div className="flex justify-between text-slate-600">
<span>Patient Paid:</span>
<span className="font-mono text-emerald-700 font-semibold">-${superbill.patientPaid.toFixed(2)}</span>
</div>
<div className="pt-2 border-t border-slate-200 flex justify-between font-bold text-sm">
<span className="text-slate-900">Balance Due:</span>
<span className="font-mono text-sky-800">${superbill.balanceDue.toFixed(2)}</span>
</div>
</div>
</div>
</div>
{/* Stripe Payment Modal */}
{showPaymentModal && (
<div className="fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-xs flex items-center justify-center p-4">
<div className="bg-white border border-slate-200 rounded-2xl max-w-md w-full p-6 shadow-xl text-slate-900">
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
<div className="flex items-center gap-2">
<span className="p-2 rounded-lg bg-sky-50 text-sky-700 border border-sky-100">
<CreditCard className="w-5 h-5" />
</span>
<div>
<h4 className="font-bold text-slate-900 text-sm">Hospital Payment Terminal</h4>
<p className="text-xs text-slate-500">Connected to {activeTenant.name}</p>
</div>
</div>
</div>
<div className="mt-4 space-y-3 text-xs">
<div className="bg-slate-50 p-3 rounded-lg border border-slate-200 flex justify-between items-center">
<span className="text-slate-600 font-medium">Balance Due:</span>
<span className="text-lg font-black text-sky-800 font-mono">
${superbill.balanceDue.toFixed(2)}
</span>
</div>
{paymentSuccess ? (
<div className="p-4 bg-emerald-50 border border-emerald-200 rounded-lg text-center text-emerald-800">
<CheckCircle2 className="w-8 h-8 text-emerald-600 mx-auto mb-2 animate-bounce" />
<div className="font-bold text-sm">Payment Processed Successfully!</div>
<div className="text-xs text-emerald-700 mt-1 font-mono">
Transaction ID: TX-90281-OK
</div>
</div>
) : (
<>
<label className="block text-xs font-bold text-slate-700 uppercase tracking-wider">
Select Payment Method
</label>
<div className="grid grid-cols-1 gap-2">
<button
type="button"
disabled={isProcessingStripe}
onClick={() => handleStripePay('Stripe Card')}
className="p-3 bg-white hover:bg-slate-50 border border-slate-200 rounded-lg flex items-center justify-between transition shadow-2xs"
>
<div className="flex items-center gap-2 font-semibold text-slate-800">
<CreditCard className="w-4 h-4 text-sky-700" />
Credit / Debit Card (Apple Pay)
</div>
<span className="text-[11px] bg-sky-50 text-sky-800 px-2 py-0.5 rounded font-mono font-bold border border-sky-200">
Terminal
</span>
</button>
<button
type="button"
disabled={isProcessingStripe}
onClick={() => handleStripePay('HSA/FSA')}
className="p-3 bg-white hover:bg-slate-50 border border-slate-200 rounded-lg flex items-center justify-between transition shadow-2xs"
>
<div className="flex items-center gap-2 font-semibold text-slate-800">
<ShieldAlert className="w-4 h-4 text-blue-700" />
HSA / FSA Health Benefit Card
</div>
<span className="text-[11px] bg-blue-50 text-blue-800 px-2 py-0.5 rounded font-mono font-bold border border-blue-200">
Tax-Exempt
</span>
</button>
<button
type="button"
disabled={isProcessingStripe}
onClick={() => handleStripePay('Cash')}
className="p-3 bg-white hover:bg-slate-50 border border-slate-200 rounded-lg flex items-center justify-between transition shadow-2xs"
>
<div className="flex items-center gap-2 font-semibold text-slate-800">
<DollarSign className="w-4 h-4 text-emerald-700" />
Cash / Clinic Receipt
</div>
<span className="text-[11px] bg-emerald-50 text-emerald-800 px-2 py-0.5 rounded font-mono font-bold border border-emerald-200">
Receipt
</span>
</button>
</div>
</>
)}
</div>
{!paymentSuccess && (
<div className="mt-5 pt-3 border-t border-slate-100 flex justify-end">
<button
type="button"
onClick={() => setShowPaymentModal(false)}
className="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition"
>
Close
</button>
</div>
)}
</div>
</div>
)}
</div>
);
};