feat: Initial Mediusa Clinic OS practice management platform ('Jane App But Better')

This commit is contained in:
2026-09-05 14:12:19 -07:00
parent 441e5eb57d
commit deb5197e53
17 changed files with 4675 additions and 71 deletions
+346
View File
@@ -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>
);
};