345 lines
16 KiB
TypeScript
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 & 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>
|
|
);
|
|
};
|