feat: Initial Mediusa Clinic OS practice management platform ('Jane App But Better')
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Superbill, ClinicTenant } from '@/types/clinical';
|
||||
import { generateSuperbillPdf } from '@/lib/pdf-generator';
|
||||
import {
|
||||
FileText,
|
||||
Download,
|
||||
CreditCard,
|
||||
CheckCircle2,
|
||||
ShieldAlert,
|
||||
ArrowLeft,
|
||||
DollarSign,
|
||||
Printer,
|
||||
Sparkles,
|
||||
ExternalLink,
|
||||
} 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-slate-900 border border-slate-800 rounded-2xl p-5 shadow-xl 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-800 hover:bg-slate-700 text-slate-300 rounded-xl transition"
|
||||
title="Back"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-lg font-black text-white">
|
||||
Superbill {superbill.invoiceNumber}
|
||||
</h3>
|
||||
<span
|
||||
className={`text-xs px-2.5 py-0.5 rounded-full font-bold uppercase ${
|
||||
superbill.balanceDue === 0
|
||||
? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30'
|
||||
: 'bg-amber-500/20 text-amber-300 border border-amber-500/30'
|
||||
}`}
|
||||
>
|
||||
{superbill.balanceDue === 0 ? 'Paid in Full' : 'Balance Due'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
Clean Medical Claim Standard (CMS-1500 / 837P Ready) for {superbill.patientName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDownloadPdf}
|
||||
className="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-white text-xs font-bold flex items-center gap-1.5 transition border border-slate-700"
|
||||
>
|
||||
<Download className="w-4 h-4 text-teal-400" />
|
||||
Download PDF Superbill
|
||||
</button>
|
||||
|
||||
{superbill.balanceDue > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPaymentModal(true)}
|
||||
className="px-4 py-2 rounded-xl bg-teal-500 hover:bg-teal-400 text-slate-950 text-xs font-bold flex items-center gap-1.5 shadow-lg shadow-teal-500/20 transition"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" />
|
||||
Collect with Stripe
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Printed Superbill Card Simulation */}
|
||||
<div className="bg-slate-950 border border-slate-800 rounded-2xl p-6 sm:p-8 shadow-2xl max-w-4xl mx-auto text-slate-200">
|
||||
{/* Clinic & Statement Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-start justify-between pb-6 border-b border-slate-800 gap-4">
|
||||
<div>
|
||||
<span className="text-xs font-mono font-bold text-teal-400 uppercase tracking-widest">
|
||||
PRACTICE MANAGEMENT SUPERBILL
|
||||
</span>
|
||||
<h2 className="text-xl font-black text-white mt-1">{superbill.clinicName}</h2>
|
||||
<p className="text-xs text-slate-400 mt-1">{superbill.clinicAddress}</p>
|
||||
<p className="text-xs text-slate-400">
|
||||
Tax ID / EIN: <strong className="text-slate-300 font-mono">{superbill.clinicTaxId}</strong> • POS:{' '}
|
||||
<strong className="text-slate-300 font-mono">{superbill.posCode}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="sm:text-right text-xs space-y-1">
|
||||
<div className="text-slate-400 font-medium">Invoice Number</div>
|
||||
<div className="font-mono text-sm font-bold text-white">{superbill.invoiceNumber}</div>
|
||||
<div className="text-slate-400 font-medium mt-2">Date of Service</div>
|
||||
<div className="font-mono text-slate-200">{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-800 text-xs">
|
||||
{/* Provider */}
|
||||
<div className="bg-slate-900/60 p-4 rounded-xl border border-slate-800/80">
|
||||
<h4 className="font-bold text-slate-300 uppercase tracking-wider mb-2">
|
||||
Rendering Provider
|
||||
</h4>
|
||||
<div className="font-semibold text-white">{superbill.providerName}</div>
|
||||
<div className="text-slate-400 mt-1">
|
||||
National Provider Identifier (NPI):{' '}
|
||||
<strong className="text-teal-400 font-mono">{superbill.providerNpi}</strong>
|
||||
</div>
|
||||
<div className="text-slate-400 mt-1">Specialty: Chiropractic Clinical Biomechanics</div>
|
||||
</div>
|
||||
|
||||
{/* Patient */}
|
||||
<div className="bg-slate-900/60 p-4 rounded-xl border border-slate-800/80">
|
||||
<h4 className="font-bold text-slate-300 uppercase tracking-wider mb-2">
|
||||
Patient Demographics
|
||||
</h4>
|
||||
<div className="font-semibold text-white">{superbill.patientName}</div>
|
||||
<div className="text-slate-400 mt-1">DOB: {superbill.patientDob}</div>
|
||||
<div className="text-slate-400 mt-1">Address: {superbill.patientAddress}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Diagnosis ICD-10 Section */}
|
||||
<div className="py-6 border-b border-slate-800">
|
||||
<h4 className="text-xs font-bold text-slate-300 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-900/80 border border-slate-800 p-2.5 rounded-lg"
|
||||
>
|
||||
<span className="w-5 h-5 rounded bg-teal-500/20 text-teal-300 font-bold flex items-center justify-center font-mono text-[11px]">
|
||||
{index + 1}
|
||||
</span>
|
||||
<span className="font-bold font-mono text-teal-300">{icd.code}</span>
|
||||
<span className="text-slate-400 truncate">{icd.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Procedures Table */}
|
||||
<div className="py-6 border-b border-slate-800">
|
||||
<h4 className="text-xs font-bold text-slate-300 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-900 text-slate-400 border-b border-slate-800">
|
||||
<th className="py-2.5 px-3 font-semibold">CPT Code</th>
|
||||
<th className="py-2.5 px-3 font-semibold">Service Description</th>
|
||||
<th className="py-2.5 px-3 font-semibold text-center">Units</th>
|
||||
<th className="py-2.5 px-3 font-semibold text-right">Fee</th>
|
||||
<th className="py-2.5 px-3 font-semibold text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/80">
|
||||
{superbill.items.map((item) => (
|
||||
<tr key={item.cptCode} className="hover:bg-slate-900/40">
|
||||
<td className="py-2.5 px-3 font-mono font-bold text-teal-300">{item.cptCode}</td>
|
||||
<td className="py-2.5 px-3 text-slate-300">{item.description}</td>
|
||||
<td className="py-2.5 px-3 text-center text-slate-400">{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-white">
|
||||
${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 contains all required claim elements (CMS-1500 Boxes 21, 24, 25, 31, 33)
|
||||
for direct patient submission to their private health insurance or FSA/HSA reimbursement plan.
|
||||
</p>
|
||||
<p className="mt-2 text-slate-400">
|
||||
Payment Method: <strong className="text-teal-400">{superbill.paymentMethod}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 p-4 rounded-xl border border-slate-800 w-full sm:w-64 space-y-2 text-xs">
|
||||
<div className="flex justify-between text-slate-400">
|
||||
<span>Total Charges:</span>
|
||||
<span className="font-mono text-white">${superbill.totalAmount.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-slate-400">
|
||||
<span>Patient Paid:</span>
|
||||
<span className="font-mono text-emerald-400">-${superbill.patientPaid.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-slate-800 flex justify-between font-bold text-sm">
|
||||
<span className="text-white">Balance Due:</span>
|
||||
<span className="font-mono text-teal-400">${superbill.balanceDue.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stripe Payment Modal */}
|
||||
{showPaymentModal && (
|
||||
<div className="fixed inset-0 z-50 bg-slate-950/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div className="bg-slate-900 border border-slate-700 rounded-2xl max-w-md w-full p-6 shadow-2xl animate-in fade-in zoom-in-95 duration-150 text-slate-100">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="p-2 rounded-xl bg-teal-500/20 text-teal-400">
|
||||
<CreditCard className="w-5 h-5" />
|
||||
</span>
|
||||
<div>
|
||||
<h4 className="font-bold text-white text-sm">Stripe Connect Checkout</h4>
|
||||
<p className="text-[11px] text-slate-400">Direct to {activeTenant.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-3 text-xs">
|
||||
<div className="bg-slate-950 p-3 rounded-xl border border-slate-800 flex justify-between items-center">
|
||||
<span className="text-slate-400">Amount Due:</span>
|
||||
<span className="text-lg font-black text-teal-400 font-mono">
|
||||
${superbill.balanceDue.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{paymentSuccess ? (
|
||||
<div className="p-4 bg-emerald-500/20 border border-emerald-500/40 rounded-xl text-center text-emerald-300">
|
||||
<CheckCircle2 className="w-8 h-8 text-emerald-400 mx-auto mb-2 animate-bounce" />
|
||||
<div className="font-bold text-sm">Payment Processed Successfully!</div>
|
||||
<div className="text-[11px] text-emerald-400/80 mt-1">
|
||||
Stripe Charge ID: ch_3P18f92Ksk921
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<label className="block text-[11px] font-semibold text-slate-400 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-slate-800 hover:bg-slate-700 border border-slate-700 rounded-xl flex items-center justify-between transition"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-medium text-slate-200">
|
||||
<CreditCard className="w-4 h-4 text-teal-400" />
|
||||
Credit / Debit Card (Apple Pay)
|
||||
</div>
|
||||
<span className="text-[10px] bg-teal-500/20 text-teal-300 px-2 py-0.5 rounded font-mono">
|
||||
Instant
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={isProcessingStripe}
|
||||
onClick={() => handleStripePay('HSA/FSA')}
|
||||
className="p-3 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-xl flex items-center justify-between transition"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-medium text-slate-200">
|
||||
<ShieldAlert className="w-4 h-4 text-sky-400" />
|
||||
HSA / FSA Health Benefit Card
|
||||
</div>
|
||||
<span className="text-[10px] bg-sky-500/20 text-sky-300 px-2 py-0.5 rounded font-mono">
|
||||
Tax-Free
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={isProcessingStripe}
|
||||
onClick={() => handleStripePay('Cash')}
|
||||
className="p-3 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-xl flex items-center justify-between transition"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-medium text-slate-200">
|
||||
<DollarSign className="w-4 h-4 text-emerald-400" />
|
||||
Cash / In-Person Check
|
||||
</div>
|
||||
<span className="text-[10px] bg-emerald-500/20 text-emerald-300 px-2 py-0.5 rounded font-mono">
|
||||
No Fee
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!paymentSuccess && (
|
||||
<div className="mt-5 pt-3 border-t border-slate-800 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPaymentModal(false)}
|
||||
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 text-xs font-semibold rounded-xl transition"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user