fix(wiring): multi-tenant isolation, in-calendar quick scheduler, telehealth note sync, and superbill history

This commit is contained in:
2026-09-07 12:09:00 -07:00
parent 02a861a833
commit d449dd2411
8 changed files with 914 additions and 209 deletions
+210 -82
View File
@@ -349,6 +349,127 @@ export default function Home() {
setActiveTenantId(tenantId);
setPortalMode('clinic');
setClinicTab('calendar');
// Automatically sync active patient, note, and superbills for the selected clinic
const tenantPatients = patients.filter((p) => p.tenantId === tenantId);
if (tenantPatients.length > 0) {
setActivePatient(tenantPatients[0]);
const matchingSoap =
soapNotes.find((s) => s.patientId === tenantPatients[0].id) ||
soapNotes.find((s) => s.tenantId === tenantId);
setActiveSoapNote(matchingSoap);
}
const tenantSuperbills = superbills.filter((sb) => sb.tenantId === tenantId);
if (tenantSuperbills.length > 0) {
setActiveSuperbill(tenantSuperbills[0]);
}
};
const handleQuickScheduleAppointment = (newAptData: any) => {
clinicalAudio.playSuccess();
const newApt: Appointment = {
id: `apt-${Date.now()}`,
...newAptData,
};
const existingPatient = patients.find((p) => p.id === newAptData.patientId);
if (!existingPatient) {
const nameParts = (newAptData.patientName || 'New Patient').split(' ');
const firstName = nameParts[0] || 'Walk-In';
const lastName = nameParts.slice(1).join(' ') || 'Patient';
const newPatient: Patient = {
id: newAptData.patientId,
tenantId: activeTenant.id,
firstName,
lastName,
email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`,
phone: newAptData.patientPhone || '(555) 000-0000',
dob: '1990-01-01',
gender: 'Undisclosed',
address: activeTenant.cityStateZip,
insuranceName: 'Direct / Stripe Connect',
insuranceId: 'SELF-PAY',
chiefComplaint: newAptData.notes || newAptData.serviceType,
daysSinceLastVisit: 0,
lastVisitDate: newAptData.date,
nextAppointmentDate: newAptData.date,
status: 'active',
carePlan: {
title: 'Acute Relief & Biomechanical Alignment',
totalVisits: 6,
completedVisits: 1,
frequency: '1-2x / week',
targetCondition: newAptData.serviceType,
startDate: newAptData.date,
status: 'on_track',
},
};
setPatients((prev) => [newPatient, ...prev]);
setActivePatient(newPatient);
} else {
setActivePatient(existingPatient);
}
setAppointments((prev) => [newApt, ...prev]);
addToast(
'success',
'Encounter Scheduled',
`${newApt.patientName} scheduled for ${newApt.time} (${newApt.serviceType})`
);
};
const handleEndTelehealthCall = (consultData: {
subjective: string;
objective: string;
assessment: string;
plan: string;
duration: string;
}) => {
clinicalAudio.playSuccess();
const today = new Date().toISOString().substring(0, 10);
const updatedNote: SoapNote = {
id: activeSoapNote?.id || `soap-${Date.now()}`,
tenantId: activeTenant.id,
patientId: activePatient.id,
patientName: `${activePatient.firstName} ${activePatient.lastName}`,
providerId: activeProvider.id,
providerName: activeProvider.name,
date: today,
status: 'draft',
discipline: 'chiropractic',
vasScore: 4,
subjective: consultData.subjective,
objective: consultData.objective,
assessment: consultData.assessment,
plan: `${consultData.plan}\n\n[Telehealth Video Consult • Duration: ${consultData.duration} • Encrypted WebRTC Session]`,
spinalAdjustments: activeSoapNote?.spinalAdjustments || [],
icd10Codes: activeSoapNote?.icd10Codes || [
{ code: 'M99.01', description: 'Segmental and somatic dysfunction of cervical region' },
],
cptCodes: [
{ code: '99203', description: 'Office/Telehealth Outpatient Visit, 30 min', fee: 110 },
{ code: '97110', description: 'Therapeutic Exercises & Ergonomic Protocol', fee: 45 },
],
};
setSoapNotes((prev) => {
const idx = prev.findIndex((s) => s.patientId === activePatient.id);
if (idx >= 0) {
const copy = [...prev];
copy[idx] = updatedNote;
return copy;
}
return [updatedNote, ...prev];
});
setActiveSoapNote(updatedNote);
setClinicTab('charting');
addToast(
'success',
'Telehealth Note Synced to Chart',
`Clinical findings for ${activePatient.firstName} ${activePatient.lastName} transferred to SOAP editor (${consultData.duration})`
);
};
const handleUpdateProductStock = (productId: string, newStock: number) => {
@@ -464,7 +585,7 @@ export default function Home() {
<div className="relative">
<select
value={activeTenantId}
onChange={(e) => setActiveTenantId(e.target.value)}
onChange={(e) => handleSwitchTenant(e.target.value)}
className="appearance-none bg-slate-50 hover:bg-slate-100 border border-slate-300 text-slate-800 text-xs font-bold rounded-lg px-3 py-1.5 pr-8 cursor-pointer focus:outline-none focus:border-sky-500 transition shadow-2xs"
>
{tenants.map((t) => (
@@ -742,94 +863,101 @@ export default function Home() {
{/* Main Container */}
<main className="flex-1 max-w-7xl w-full mx-auto p-4 sm:p-6 lg:p-8">
{portalMode === 'clinic' && (
<div>
{clinicTab === 'calendar' && (
<CalendarView
appointments={appointments}
providers={activeProviders}
activeTenant={activeTenant}
waitlistCount={waitlist.length}
onSelectAppointment={handleSelectAppointment}
onUpdateStatus={handleUpdateAppointmentStatus}
onNewAppointmentClick={() => setPortalMode('patient')}
onOpenWaitlist={() => setIsWaitlistOpen(true)}
/>
)}
{portalMode === 'clinic' && (() => {
const tenantPatients = patients.filter((p) => !p.tenantId || p.tenantId === activeTenant.id);
const tenantSuperbills = superbills.filter((s) => !s.tenantId || s.tenantId === activeTenant.id);
{clinicTab === 'charting' && staffRole !== 'front_desk' && (
<SoapChartEditor
patient={activePatient}
provider={activeProvider}
initialSoapNote={activeSoapNote}
onSaveSoapNote={handleSaveSoapNote}
onGenerateSuperbill={handleGenerateSuperbillFromNote}
onLaunchTelehealth={() => setClinicTab('telehealth')}
onBackToCalendar={() => setClinicTab('calendar')}
onOpenCourtVault={() => setIsCourtVaultOpen(true)}
/>
)}
return (
<div>
{clinicTab === 'calendar' && (
<CalendarView
appointments={appointments}
providers={activeProviders}
activeTenant={activeTenant}
patients={tenantPatients}
waitlistCount={waitlist.length}
onSelectAppointment={handleSelectAppointment}
onUpdateStatus={handleUpdateAppointmentStatus}
onNewAppointmentClick={() => setPortalMode('patient')}
onQuickSchedule={handleQuickScheduleAppointment}
onOpenWaitlist={() => setIsWaitlistOpen(true)}
/>
)}
{clinicTab === 'telehealth' && staffRole !== 'front_desk' && (
<TelehealthRoom
patient={activePatient}
provider={activeProvider}
onEndCall={(noteData) => {
setClinicTab('charting');
}}
onClose={() => setClinicTab('calendar')}
/>
)}
{clinicTab === 'charting' && staffRole !== 'front_desk' && (
<SoapChartEditor
patient={activePatient}
provider={activeProvider}
initialSoapNote={activeSoapNote}
onSaveSoapNote={handleSaveSoapNote}
onGenerateSuperbill={handleGenerateSuperbillFromNote}
onLaunchTelehealth={() => setClinicTab('telehealth')}
onBackToCalendar={() => setClinicTab('calendar')}
onOpenCourtVault={() => setIsCourtVaultOpen(true)}
/>
)}
{clinicTab === 'retail' && (
<RetailInventoryView
products={products}
activeTenant={activeTenant}
onUpdateStock={handleUpdateProductStock}
/>
)}
{clinicTab === 'telehealth' && staffRole !== 'front_desk' && (
<TelehealthRoom
patient={activePatient}
provider={activeProvider}
onEndCall={handleEndTelehealthCall}
onClose={() => setClinicTab('calendar')}
/>
)}
{clinicTab === 'memberships' && (
<MembershipsView
memberships={memberships}
packages={packages}
patients={patients}
activeTenant={activeTenant}
onEnrollPatient={handleEnrollPatientInMembership}
/>
)}
{clinicTab === 'retail' && (
<RetailInventoryView
products={products}
activeTenant={activeTenant}
onUpdateStock={handleUpdateProductStock}
/>
)}
{clinicTab === 'billing' && (
<SuperbillView
superbill={activeSuperbill}
activeTenant={activeTenant}
onBack={() => setClinicTab('calendar')}
onMarkPaid={handleMarkPaid}
/>
)}
{clinicTab === 'memberships' && (
<MembershipsView
memberships={memberships}
packages={packages}
patients={tenantPatients}
activeTenant={activeTenant}
onEnrollPatient={handleEnrollPatientInMembership}
/>
)}
{clinicTab === 'retention' && (
<RetentionView
patients={patients}
activeTenant={activeTenant}
onSelectPatientChart={(patient) => {
setActivePatient(patient);
const existing = soapNotes.find((s) => s.patientId === patient.id);
setActiveSoapNote(existing);
setClinicTab('charting');
}}
/>
)}
{clinicTab === 'billing' && (
<SuperbillView
superbill={activeSuperbill}
superbills={tenantSuperbills}
activeTenant={activeTenant}
onBack={() => setClinicTab('calendar')}
onSelectSuperbill={(sb) => setActiveSuperbill(sb)}
onMarkPaid={handleMarkPaid}
/>
)}
{clinicTab === 'growth' && (
<ClinicGrowthView
activeTenant={activeTenant}
activeProvider={activeProvider}
onOpenBookingPortal={() => setPortalMode('patient')}
/>
)}
</div>
)}
{clinicTab === 'retention' && (
<RetentionView
patients={tenantPatients}
activeTenant={activeTenant}
onSelectPatientChart={(patient) => {
setActivePatient(patient);
const existing = soapNotes.find((s) => s.patientId === patient.id);
setActiveSoapNote(existing);
setClinicTab('charting');
}}
/>
)}
{clinicTab === 'growth' && (
<ClinicGrowthView
activeTenant={activeTenant}
activeProvider={activeProvider}
onOpenBookingPortal={() => setPortalMode('patient')}
/>
)}
</div>
);
})()}
{portalMode === 'patient' && (
<PatientPortalView
+1 -1
View File
@@ -57,7 +57,7 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
id: `tenant-${Date.now()}`,
name: newClinicName,
slug: newSlug,
domain: `${newSlug}.mediusa.app`,
domain: `${newSlug}.mediusaos.com`,
phone: '(555) 839-2041',
email: `contact@${newSlug}.com`,
address: '100 Wellness Way, Suite 10',
+44
View File
@@ -16,15 +16,19 @@ import {
interface SuperbillViewProps {
superbill: Superbill;
superbills?: Superbill[];
activeTenant: ClinicTenant;
onBack: () => void;
onSelectSuperbill?: (superbill: Superbill) => void;
onMarkPaid: (superbillId: string, method: Superbill['paymentMethod']) => void;
}
export const SuperbillView: React.FC<SuperbillViewProps> = ({
superbill,
superbills = [],
activeTenant,
onBack,
onSelectSuperbill,
onMarkPaid,
}) => {
const [showPaymentModal, setShowPaymentModal] = useState(false);
@@ -50,6 +54,46 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
return (
<div className="space-y-6">
{/* Superbill Invoices & Claims Switcher Ribbon */}
{superbills.length > 1 && (
<div className="bg-white border border-slate-200/90 rounded-xl p-3 shadow-2xs flex items-center justify-between gap-3 overflow-x-auto text-xs">
<div className="flex items-center gap-2 shrink-0 font-bold text-slate-700">
<DollarSign className="w-4 h-4 text-sky-700" />
<span>Clinic Claims ({superbills.length}):</span>
</div>
<div className="flex items-center gap-2 overflow-x-auto">
{superbills.map((sb) => {
const isSelected = sb.id === superbill.id;
const isPaid = sb.balanceDue === 0;
return (
<button
key={sb.id}
type="button"
onClick={() => onSelectSuperbill && onSelectSuperbill(sb)}
className={`px-3 py-1.5 rounded-lg border text-left transition flex items-center gap-2 shrink-0 ${
isSelected
? 'bg-sky-50 border-sky-400 text-sky-950 font-bold shadow-2xs'
: 'bg-slate-50 border-slate-200 text-slate-700 hover:bg-slate-100'
}`}
>
<span className="font-mono text-[11px]">{sb.invoiceNumber}</span>
<span></span>
<span>{sb.patientName}</span>
<span className="font-mono font-bold">${sb.totalAmount.toFixed(0)}</span>
<span
className={`text-[10px] px-1.5 py-0.5 rounded font-bold uppercase ${
isPaid ? 'bg-emerald-100 text-emerald-800' : 'bg-amber-100 text-amber-800'
}`}
>
{isPaid ? 'Paid' : 'Due'}
</span>
</button>
);
})}
</div>
</div>
)}
{/* 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">
+493 -101
View File
@@ -1,7 +1,7 @@
'use client';
import React, { useState } from 'react';
import { Appointment, ClinicTenant, Provider } from '@/types/clinical';
import { Appointment, ClinicTenant, Provider, Patient } from '@/types/clinical';
import {
Calendar as CalendarIcon,
Clock,
@@ -15,16 +15,23 @@ import {
Stethoscope,
Video,
Users,
X,
MapPin,
Check,
Sparkles,
} from 'lucide-react';
import { clinicalAudio } from '@/lib/clinical-audio';
interface CalendarViewProps {
appointments: Appointment[];
providers: Provider[];
activeTenant: ClinicTenant;
patients?: Patient[];
waitlistCount?: number;
onSelectAppointment: (apt: Appointment) => void;
onUpdateStatus: (aptId: string, status: Appointment['status']) => void;
onNewAppointmentClick: () => void;
onQuickSchedule?: (newApt: any) => void;
onOpenWaitlist: () => void;
}
@@ -32,18 +39,127 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
appointments,
providers,
activeTenant,
patients = [],
waitlistCount = 3,
onSelectAppointment,
onUpdateStatus,
onNewAppointmentClick,
onQuickSchedule,
onOpenWaitlist,
}) => {
const [selectedProviderId, setSelectedProviderId] = useState<string>('all');
const [selectedDate, setSelectedDate] = useState<Date>(new Date('2026-09-08T09:00:00'));
const [showAllDates, setShowAllDates] = useState(true);
const filteredAppointments = appointments.filter(
(a) => selectedProviderId === 'all' || a.providerId === selectedProviderId
// In-calendar Quick Scheduling Modal State
const [isQuickScheduleOpen, setIsQuickScheduleOpen] = useState(false);
const [patientMode, setPatientMode] = useState<'existing' | 'new'>('existing');
const [selectedPatientId, setSelectedPatientId] = useState<string>(patients[0]?.id || '');
const [newPatientName, setNewPatientName] = useState('');
const [newPatientPhone, setNewPatientPhone] = useState('');
const [scheduleProviderId, setScheduleProviderId] = useState<string>(providers[0]?.id || '');
const [scheduleService, setScheduleService] = useState('Spinal Adjustment & CMT');
const [scheduleFee, setScheduleFee] = useState(75);
const [scheduleTime, setScheduleTime] = useState('10:00 AM');
const [scheduleDuration, setScheduleDuration] = useState(20);
const [scheduleRoom, setScheduleRoom] = useState('Table 1');
const [scheduleNotes, setScheduleNotes] = useState('');
const [isTelehealthVisit, setIsTelehealthVisit] = useState(false);
// Format date display
const formattedDateString = selectedDate.toLocaleDateString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric',
});
const selectedDateIso = selectedDate.toISOString().substring(0, 10);
// Tenant-isolated appointments
const tenantAppointments = appointments.filter(
(a) => !a.tenantId || a.tenantId === activeTenant.id
);
const filteredAppointments = tenantAppointments.filter((a) => {
const matchesProvider = selectedProviderId === 'all' || a.providerId === selectedProviderId;
const matchesDate = showAllDates || a.date === selectedDateIso;
return matchesProvider && matchesDate;
});
const handlePrevDay = () => {
clinicalAudio.playClick();
setSelectedDate((prev) => new Date(prev.getTime() - 86400000));
};
const handleNextDay = () => {
clinicalAudio.playClick();
setSelectedDate((prev) => new Date(prev.getTime() + 86400000));
};
const handleToday = () => {
clinicalAudio.playClick();
setSelectedDate(new Date('2026-09-08T09:00:00'));
};
const handleOpenScheduleModal = () => {
clinicalAudio.playClick();
setIsQuickScheduleOpen(true);
if (patients.length > 0 && !selectedPatientId) {
setSelectedPatientId(patients[0].id);
}
if (providers.length > 0 && !scheduleProviderId) {
setScheduleProviderId(providers[0].id);
}
};
const handleConfirmQuickSchedule = (e: React.FormEvent) => {
e.preventDefault();
clinicalAudio.playSuccess();
let targetPatientName = '';
let targetPhone = '';
let targetPatientId = selectedPatientId;
if (patientMode === 'existing') {
const p = patients.find((pat) => pat.id === selectedPatientId);
targetPatientName = p ? `${p.firstName} ${p.lastName}` : 'Walk-In Patient';
targetPhone = p?.phone || '(555) 000-0000';
} else {
targetPatientName = newPatientName.trim() || 'New Patient';
targetPhone = newPatientPhone.trim() || '(555) 123-4567';
targetPatientId = `pat-${Date.now()}`;
}
const assignedProvider = providers.find((prov) => prov.id === scheduleProviderId) || providers[0];
const newAptData = {
tenantId: activeTenant.id,
patientId: targetPatientId,
patientName: targetPatientName,
patientPhone: targetPhone,
providerId: assignedProvider.id,
providerName: assignedProvider.name,
date: selectedDateIso,
time: scheduleTime,
durationMinutes: scheduleDuration,
serviceType: scheduleService,
status: 'confirmed' as Appointment['status'],
room: scheduleRoom,
notes: scheduleNotes || 'Quick-scheduled via front desk calendar',
fee: scheduleFee,
isTelehealth: isTelehealthVisit,
};
if (onQuickSchedule) {
onQuickSchedule(newAptData);
}
setIsQuickScheduleOpen(false);
setNewPatientName('');
setNewPatientPhone('');
setScheduleNotes('');
};
const getStatusBadge = (status: Appointment['status']) => {
switch (status) {
case 'completed':
@@ -90,27 +206,49 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
{/* Hospital Sub-Header Bar */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 bg-white border border-slate-200/90 p-4 rounded-xl shadow-xs">
<div className="flex flex-wrap items-center gap-3">
{/* Date Selector */}
{/* Dynamic Date Selector */}
<div className="flex items-center bg-slate-100/90 rounded-lg p-1 border border-slate-200">
<button
type="button"
onClick={handlePrevDay}
className="p-1.5 hover:bg-white rounded-md text-slate-600 hover:text-slate-900 transition"
title="Previous Day"
>
<ChevronLeft className="w-4 h-4" />
</button>
<span className="px-3 text-xs font-bold text-slate-800 tracking-wide">
Saturday, September 5, 2026
{formattedDateString}
</span>
<button
type="button"
onClick={handleNextDay}
className="p-1.5 hover:bg-white rounded-md text-slate-600 hover:text-slate-900 transition"
title="Next Day"
>
<ChevronRight className="w-4 h-4" />
</button>
<button
type="button"
onClick={handleToday}
className="ml-1 px-2 py-0.5 text-[10px] font-bold text-sky-700 bg-white hover:bg-sky-50 rounded border border-slate-200 transition"
>
Today
</button>
</div>
{/* Toggle All Dates vs Selected Day */}
<button
type="button"
onClick={() => setShowAllDates(!showAllDates)}
className={`px-2.5 py-1 rounded-lg text-xs font-bold transition border ${
showAllDates
? 'bg-sky-50 text-sky-800 border-sky-200'
: 'bg-slate-100 text-slate-600 border-slate-200 hover:bg-slate-200'
}`}
>
{showAllDates ? 'Showing All Encounters' : 'Filtered to Single Day'}
</button>
{/* Attending Provider Filter Tabs */}
<div className="flex items-center gap-1 bg-slate-100 p-1 rounded-lg border border-slate-200 text-xs">
<button
@@ -122,22 +260,28 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
: 'text-slate-600 hover:text-slate-900'
}`}
>
All Providers
All Providers ({tenantAppointments.length})
</button>
{providers.map((p) => (
<button
key={p.id}
type="button"
onClick={() => setSelectedProviderId(p.id)}
className={`px-3 py-1 rounded-md font-semibold transition ${
selectedProviderId === p.id
? 'bg-white text-sky-800 shadow-xs border border-slate-200/80'
: 'text-slate-600 hover:text-slate-900'
}`}
>
{p.name}
</button>
))}
{providers.map((p) => {
const provCount = tenantAppointments.filter((a) => a.providerId === p.id).length;
return (
<button
key={p.id}
type="button"
onClick={() => setSelectedProviderId(p.id)}
className={`px-3 py-1 rounded-md font-semibold transition flex items-center gap-1.5 ${
selectedProviderId === p.id
? 'bg-white text-sky-800 shadow-xs border border-slate-200/80'
: 'text-slate-600 hover:text-slate-900'
}`}
>
<span>{p.name}</span>
<span className="text-[10px] px-1.5 py-0.2 bg-slate-200 text-slate-700 rounded-full font-mono">
{provCount}
</span>
</button>
);
})}
</div>
</div>
@@ -151,9 +295,10 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
Cancellation Waitlist ({waitlistCount})
</button>
{/* In-Calendar Quick Schedule Button */}
<button
type="button"
onClick={onNewAppointmentClick}
onClick={handleOpenScheduleModal}
className="px-4 py-2 rounded-lg bg-sky-700 hover:bg-sky-800 text-white text-xs font-bold flex items-center gap-2 shadow-xs transition shrink-0"
>
<Plus className="w-4 h-4" />
@@ -163,103 +308,350 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
</div>
{/* Grid of Hospital Patient Encounter Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredAppointments.map((apt) => (
<div
key={apt.id}
className="bg-white border border-slate-200/90 hover:border-sky-300 rounded-xl p-4 shadow-xs hover:shadow-md transition-all duration-200 flex flex-col justify-between"
{filteredAppointments.length === 0 ? (
<div className="bg-white border border-slate-200 rounded-2xl p-12 text-center shadow-xs">
<CalendarIcon className="w-12 h-12 text-slate-300 mx-auto mb-3" />
<h4 className="text-base font-bold text-slate-800">No scheduled encounters for this view</h4>
<p className="text-xs text-slate-500 mt-1 max-w-md mx-auto">
No patient appointments found for the selected date or provider filter. Click below to quickly book an encounter.
</p>
<button
type="button"
onClick={handleOpenScheduleModal}
className="mt-4 px-4 py-2 bg-sky-700 hover:bg-sky-800 text-white text-xs font-bold rounded-lg transition inline-flex items-center gap-1.5 shadow-xs"
>
<div>
{/* Encounter Time & Clinical Status */}
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
<div className="flex items-center gap-2">
<span className="px-2.5 py-1 rounded-md bg-sky-50 text-sky-800 font-mono text-xs font-bold border border-sky-100">
<Clock className="w-3.5 h-3.5 inline mr-1 text-sky-600" />
{apt.time}
</span>
<span className="text-[11px] text-slate-500 font-medium">
{apt.durationMinutes} min
</span>
{apt.isTelehealth && (
<span className="px-2 py-0.5 rounded bg-purple-50 text-purple-700 text-[10px] font-bold border border-purple-200 flex items-center gap-1">
<Video className="w-3 h-3 text-purple-600" /> Telehealth
<Plus className="w-4 h-4" /> Quick Schedule Patient
</button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredAppointments.map((apt) => (
<div
key={apt.id}
className="bg-white border border-slate-200/90 hover:border-sky-300 rounded-xl p-4 shadow-xs hover:shadow-md transition-all duration-200 flex flex-col justify-between"
>
<div>
{/* Encounter Time & Clinical Status */}
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
<div className="flex items-center gap-2">
<span className="px-2.5 py-1 rounded-md bg-sky-50 text-sky-800 font-mono text-xs font-bold border border-sky-100">
<Clock className="w-3.5 h-3.5 inline mr-1 text-sky-600" />
{apt.time}
</span>
<span className="text-[11px] text-slate-500 font-medium">
{apt.durationMinutes} min
</span>
{apt.isTelehealth && (
<span className="px-2 py-0.5 rounded bg-purple-50 text-purple-700 text-[10px] font-bold border border-purple-200 flex items-center gap-1">
<Video className="w-3 h-3 text-purple-600" /> Telehealth
</span>
)}
</div>
{getStatusBadge(apt.status)}
</div>
{/* Patient & Examination Room */}
<div className="mt-3">
<div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-slate-900 group-hover:text-sky-700 transition">
{apt.patientName}
</h4>
<span className="text-[11px] font-mono font-semibold text-slate-600 bg-slate-50 px-2 py-0.5 rounded border border-slate-200">
${apt.fee}
</span>
</div>
<p className="text-xs text-sky-700 font-medium mt-0.5">
{apt.serviceType}
</p>
<div className="flex items-center gap-2 text-xs text-slate-500 mt-2">
<span>Attending: {apt.providerName}</span>
<span></span>
<span className="font-medium text-slate-700">{apt.room}</span>
</div>
{apt.notes && (
<div className="mt-2.5 p-2 bg-slate-50 rounded-lg border border-slate-200 text-xs text-slate-700 italic">
&quot;{apt.notes}&quot;
</div>
)}
</div>
{getStatusBadge(apt.status)}
</div>
{/* Patient & Examination Room */}
<div className="mt-3">
<div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-slate-900 group-hover:text-sky-700 transition">
{apt.patientName}
</h4>
<span className="text-[11px] font-mono font-semibold text-slate-600 bg-slate-50 px-2 py-0.5 rounded border border-slate-200">
${apt.fee}
</span>
{/* Clinical Action Bar */}
<div className="mt-4 pt-3 border-t border-slate-100 flex items-center justify-between gap-2">
<button
type="button"
onClick={() => onSelectAppointment(apt)}
className="px-3 py-1.5 rounded-lg bg-sky-50 hover:bg-sky-100 text-sky-800 text-xs font-bold border border-sky-200 transition flex items-center gap-1.5"
>
<Stethoscope className="w-3.5 h-3.5 text-sky-700" />
Open Clinical Chart
</button>
<div className="flex items-center gap-1">
{apt.status === 'booked' && (
<button
type="button"
onClick={() => onUpdateStatus(apt.id, 'arrived')}
className="px-2.5 py-1 rounded-md bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-medium border border-slate-200"
>
Check In
</button>
)}
{apt.status === 'arrived' && (
<button
type="button"
onClick={() => onUpdateStatus(apt.id, 'in_room')}
className="px-2.5 py-1 rounded-md bg-amber-100 hover:bg-amber-200 text-amber-900 text-xs font-bold border border-amber-300"
>
Place in Room
</button>
)}
{apt.status === 'in_room' && (
<button
type="button"
onClick={() => onUpdateStatus(apt.id, 'completed')}
className="px-2.5 py-1 rounded-md bg-emerald-100 hover:bg-emerald-200 text-emerald-900 text-xs font-bold border border-emerald-300"
>
Mark Done
</button>
)}
</div>
<p className="text-xs text-sky-700 font-medium mt-0.5">
{apt.serviceType}
</p>
<div className="flex items-center gap-2 text-xs text-slate-500 mt-2">
<span>Attending: {apt.providerName}</span>
<span></span>
<span className="font-medium text-slate-700">{apt.room}</span>
</div>
</div>
))}
</div>
)}
{/* In-Calendar Quick Schedule Modal */}
{isQuickScheduleOpen && (
<div
role="dialog"
aria-modal="true"
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/60 backdrop-blur-xs animate-in fade-in"
>
<div className="bg-white rounded-2xl shadow-2xl border border-slate-200 max-w-lg w-full overflow-hidden flex flex-col max-h-[90vh]">
<div className="px-6 py-4 bg-slate-900 text-white flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 rounded-lg bg-sky-600 flex items-center justify-center text-white font-black text-sm">
+
</div>
<div>
<h3 className="font-bold text-sm">Quick Schedule Encounter</h3>
<p className="text-[11px] text-slate-400 font-mono">
{activeTenant.name} 2-Click Appointment Booking
</p>
</div>
</div>
<button
type="button"
onClick={() => setIsQuickScheduleOpen(false)}
className="text-slate-400 hover:text-white p-1 transition"
>
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleConfirmQuickSchedule} className="p-6 space-y-4 overflow-y-auto text-xs">
{/* Patient Mode: Existing vs Walk-In */}
<div>
<label className="block text-slate-700 font-bold mb-1.5">Select Patient</label>
<div className="flex items-center gap-2 mb-2">
<button
type="button"
onClick={() => setPatientMode('existing')}
className={`px-3 py-1 rounded-md font-bold transition border ${
patientMode === 'existing'
? 'bg-sky-50 text-sky-800 border-sky-300 shadow-2xs'
: 'bg-slate-50 text-slate-600 border-slate-200'
}`}
>
Existing Clinic Patient
</button>
<button
type="button"
onClick={() => setPatientMode('new')}
className={`px-3 py-1 rounded-md font-bold transition border ${
patientMode === 'new'
? 'bg-sky-50 text-sky-800 border-sky-300 shadow-2xs'
: 'bg-slate-50 text-slate-600 border-slate-200'
}`}
>
+ Walk-In / New Patient
</button>
</div>
{apt.notes && (
<div className="mt-2.5 p-2 bg-slate-50 rounded-lg border border-slate-200 text-xs text-slate-700 italic">
&quot;{apt.notes}&quot;
{patientMode === 'existing' ? (
<select
value={selectedPatientId}
onChange={(e) => setSelectedPatientId(e.target.value)}
className="w-full p-2 bg-slate-50 border border-slate-200 rounded-lg text-slate-800 font-medium focus:bg-white focus:outline-none focus:border-sky-500"
>
{patients.map((p) => (
<option key={p.id} value={p.id}>
{p.firstName} {p.lastName} {p.phone}
</option>
))}
</select>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
<input
type="text"
placeholder="Full Name (e.g. Jason Miller)"
value={newPatientName}
onChange={(e) => setNewPatientName(e.target.value)}
required={patientMode === 'new'}
className="p-2 bg-slate-50 border border-slate-200 rounded-lg text-slate-800 focus:bg-white focus:outline-none focus:border-sky-500"
/>
<input
type="tel"
placeholder="Mobile Phone (555-123-4567)"
value={newPatientPhone}
onChange={(e) => setNewPatientPhone(e.target.value)}
className="p-2 bg-slate-50 border border-slate-200 rounded-lg text-slate-800 focus:bg-white focus:outline-none focus:border-sky-500"
/>
</div>
)}
</div>
</div>
{/* Clinical Action Bar */}
<div className="mt-4 pt-3 border-t border-slate-100 flex items-center justify-between gap-2">
<button
type="button"
onClick={() => onSelectAppointment(apt)}
className="px-3 py-1.5 rounded-lg bg-sky-50 hover:bg-sky-100 text-sky-800 text-xs font-bold border border-sky-200 transition flex items-center gap-1.5"
>
<Stethoscope className="w-3.5 h-3.5 text-sky-700" />
Open Clinical Chart
</button>
{/* Attending Provider & Operatory Room */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="block text-slate-700 font-bold mb-1">Attending Provider</label>
<select
value={scheduleProviderId}
onChange={(e) => setScheduleProviderId(e.target.value)}
className="w-full p-2 bg-slate-50 border border-slate-200 rounded-lg text-slate-800 font-medium focus:bg-white focus:outline-none focus:border-sky-500"
>
{providers.map((p) => (
<option key={p.id} value={p.id}>
{p.name} ({p.credentials})
</option>
))}
</select>
</div>
<div className="flex items-center gap-1">
{apt.status === 'booked' && (
<button
type="button"
onClick={() => onUpdateStatus(apt.id, 'arrived')}
className="px-2.5 py-1 rounded-md bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-medium border border-slate-200"
<div>
<label className="block text-slate-700 font-bold mb-1">Operatory / Table</label>
<select
value={scheduleRoom}
onChange={(e) => setScheduleRoom(e.target.value)}
className="w-full p-2 bg-slate-50 border border-slate-200 rounded-lg text-slate-800 font-medium focus:bg-white focus:outline-none focus:border-sky-500"
>
Check In
</button>
)}
{apt.status === 'arrived' && (
<button
type="button"
onClick={() => onUpdateStatus(apt.id, 'in_room')}
className="px-2.5 py-1 rounded-md bg-amber-100 hover:bg-amber-200 text-amber-900 text-xs font-bold border border-amber-300"
>
Place in Room
</button>
)}
{apt.status === 'in_room' && (
<button
type="button"
onClick={() => onUpdateStatus(apt.id, 'completed')}
className="px-2.5 py-1 rounded-md bg-emerald-100 hover:bg-emerald-200 text-emerald-900 text-xs font-bold border border-emerald-300"
>
Mark Done
</button>
)}
<option value="Table 1 (Zenith Hi-Lo)">Table 1 (Zenith Hi-Lo)</option>
<option value="Table 2 (Drop Table)">Table 2 (Drop Table)</option>
<option value="Decompression Suite">Decompression Suite</option>
<option value="Suite 2 (Pregnancy Cushion)">Suite 2 (Pregnancy Cushion)</option>
<option value="Virtual Telehealth Room">Virtual Telehealth Room</option>
</select>
</div>
</div>
</div>
{/* Service Type & Fee */}
<div>
<label className="block text-slate-700 font-bold mb-1">Clinical Procedure / Service</label>
<div className="grid grid-cols-2 gap-2">
{[
{ name: 'Spinal Adjustment & CMT', fee: 75, dur: 20 },
{ name: 'Initial Examination & CMT', fee: 145, dur: 45 },
{ name: 'Triton Disc Decompression', fee: 95, dur: 30 },
{ name: 'Webster Sacral Alignment', fee: 85, dur: 30 },
].map((s) => {
const isSelected = scheduleService === s.name;
return (
<button
key={s.name}
type="button"
onClick={() => {
setScheduleService(s.name);
setScheduleFee(s.fee);
setScheduleDuration(s.dur);
}}
className={`p-2 rounded-lg border text-left transition ${
isSelected
? 'bg-sky-50 border-sky-400 text-sky-900 font-bold shadow-2xs'
: 'bg-slate-50 border-slate-200 text-slate-700 hover:bg-white'
}`}
>
<div className="font-bold truncate">{s.name}</div>
<div className="text-[10px] text-slate-500 mt-0.5">
${s.fee} {s.dur} min
</div>
</button>
);
})}
</div>
</div>
{/* Time Slots */}
<div>
<label className="block text-slate-700 font-bold mb-1">Available Time Slot</label>
<div className="grid grid-cols-4 gap-1.5">
{['08:30 AM', '09:00 AM', '09:45 AM', '10:30 AM', '11:15 AM', '01:15 PM', '02:00 PM', '03:30 PM'].map(
(t) => (
<button
key={t}
type="button"
onClick={() => setScheduleTime(t)}
className={`py-1.5 px-2 rounded-lg font-mono text-center transition border ${
scheduleTime === t
? 'bg-sky-700 text-white font-bold border-sky-800 shadow-2xs'
: 'bg-slate-50 text-slate-700 border-slate-200 hover:bg-slate-100'
}`}
>
{t}
</button>
)
)}
</div>
</div>
{/* Telehealth toggle & Notes */}
<div className="flex items-center justify-between p-2.5 bg-purple-50 border border-purple-200 rounded-lg">
<div className="flex items-center gap-2">
<Video className="w-4 h-4 text-purple-700" />
<span className="font-bold text-purple-900">Telehealth Video Encounter</span>
</div>
<input
type="checkbox"
checked={isTelehealthVisit}
onChange={(e) => setIsTelehealthVisit(e.target.checked)}
className="w-4 h-4 text-purple-600 rounded border-slate-300"
/>
</div>
<div>
<label className="block text-slate-700 font-bold mb-1">Visit Notes (Optional)</label>
<input
type="text"
placeholder="e.g. Acute low back spasm after moving heavy furniture"
value={scheduleNotes}
onChange={(e) => setScheduleNotes(e.target.value)}
className="w-full p-2 bg-slate-50 border border-slate-200 rounded-lg text-slate-800 focus:bg-white focus:outline-none focus:border-sky-500"
/>
</div>
{/* Submit Buttons */}
<div className="pt-3 border-t border-slate-100 flex items-center justify-end gap-2">
<button
type="button"
onClick={() => setIsQuickScheduleOpen(false)}
className="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 font-bold rounded-lg transition"
>
Cancel
</button>
<button
type="submit"
className="px-5 py-2 bg-sky-700 hover:bg-sky-800 text-white font-bold rounded-lg shadow-xs transition flex items-center gap-1.5"
>
<Check className="w-4 h-4" />
Confirm &amp; Book Encounter
</button>
</div>
</form>
</div>
))}
</div>
</div>
)}
</div>
);
};
+62 -19
View File
@@ -90,25 +90,66 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
const [signedTimestamp, setSignedTimestamp] = useState<string | undefined>(initialSoapNote?.signedAt);
const [lastAutoSaved, setLastAutoSaved] = useState<string | null>(null);
// Restore draft from local storage if note is un-signed
// Sync and restore state when patient or active note changes
useEffect(() => {
if (!initialSoapNote && typeof window !== 'undefined') {
try {
const key = `mediusa_draft_soap_${patient.id}`;
const raw = localStorage.getItem(key);
if (raw) {
const draft = JSON.parse(raw);
if (draft.subjective) setSubjective(draft.subjective);
if (draft.objective) setObjective(draft.objective);
if (draft.assessment) setAssessment(draft.assessment);
if (draft.plan) setPlan(draft.plan);
if (draft.vasScore !== undefined) setVasScore(draft.vasScore);
if (draft.adjustments && Array.isArray(draft.adjustments)) setAdjustments(draft.adjustments);
setLastAutoSaved(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
}
} catch {}
if (initialSoapNote) {
setDiscipline(initialSoapNote.discipline || 'chiropractic');
setSubjective(initialSoapNote.subjective);
setObjective(initialSoapNote.objective);
setAssessment(initialSoapNote.assessment);
setPlan(initialSoapNote.plan);
setAdjustments(initialSoapNote.spinalAdjustments || []);
setSelectedIcd10(initialSoapNote.icd10Codes || []);
setSelectedCpt(initialSoapNote.cptCodes || []);
setVasScore(initialSoapNote.vasScore || 3);
setIsSigned(initialSoapNote.status === 'signed');
setSignedTimestamp(initialSoapNote.signedAt);
} else {
let hasDraft = false;
if (typeof window !== 'undefined') {
try {
const key = `mediusa_draft_soap_${patient.id}`;
const raw = localStorage.getItem(key);
if (raw) {
const draft = JSON.parse(raw);
if (draft.subjective) setSubjective(draft.subjective);
if (draft.objective) setObjective(draft.objective);
if (draft.assessment) setAssessment(draft.assessment);
if (draft.plan) setPlan(draft.plan);
if (draft.vasScore !== undefined) setVasScore(draft.vasScore);
if (draft.adjustments && Array.isArray(draft.adjustments)) setAdjustments(draft.adjustments);
hasDraft = true;
setLastAutoSaved(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
}
} catch {}
}
if (!hasDraft) {
setSubjective(
`Patient presents for scheduled visit ${patient.carePlan.completedVisits + 1} of ${
patient.carePlan.totalVisits
}. Chief complaint: ${patient.chiefComplaint}. VAS pain reported at 4/10.`
);
setObjective(
'Postural examination reveals mild anterior head carriage and unleveling of right iliac crest. Motion palpation identifies segmental hypomobility with paraspinal guarding.'
);
setAssessment(
'Segmental and somatic dysfunction of spine. Patient progressing steadily toward functional goals outlined in care plan.'
);
setPlan(
'1. High velocity low amplitude (HVLA) manipulative therapy delivered to listed subluxations.\n2. Prescribed postural home stabilization exercises.\n3. Return for scheduled care plan visit.'
);
setVasScore(4);
setAdjustments([
{ vertebra: 'L4', region: 'Lumbar', listing: 'Right Posterior (RP)', technique: 'Diversified' },
{ vertebra: 'Sacrum', region: 'Pelvis/Sacrum', listing: 'Right Sacral Base Anterior', technique: 'Thompson Drop' },
]);
setSelectedIcd10([STANDARD_ICD10_CODES[2], STANDARD_ICD10_CODES[5]]);
setSelectedCpt([STANDARD_CPT_CODES[0], STANDARD_CPT_CODES[3]]);
}
setIsSigned(false);
setSignedTimestamp(undefined);
}
}, [patient.id, initialSoapNote]);
}, [patient.id, initialSoapNote?.id]);
// Debounced auto-save draft to local storage
useEffect(() => {
@@ -222,6 +263,7 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
} catch {}
}
const encounterDate = initialSoapNote?.date || new Date().toISOString().substring(0, 10);
const savedNote: SoapNote = {
id: initialSoapNote?.id || `soap-${Date.now()}`,
tenantId: patient.tenantId,
@@ -229,7 +271,7 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
patientName: `${patient.firstName} ${patient.lastName}`,
providerId: provider.id,
providerName: provider.name,
date: '2026-09-05',
date: encounterDate,
status: 'signed',
discipline,
vasScore,
@@ -277,6 +319,7 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
const handleCreateSuperbillClick = () => {
clinicalAudio.playSuccess();
const encounterDate = initialSoapNote?.date || new Date().toISOString().substring(0, 10);
const savedNote: SoapNote = {
id: initialSoapNote?.id || `soap-${Date.now()}`,
tenantId: patient.tenantId,
@@ -284,7 +327,7 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
patientName: `${patient.firstName} ${patient.lastName}`,
providerId: provider.id,
providerName: provider.name,
date: '2026-09-05',
date: encounterDate,
status: isSigned ? 'signed' : 'draft',
discipline,
vasScore,
+10 -2
View File
@@ -31,10 +31,18 @@ export const MembershipsView: React.FC<MembershipsViewProps> = ({
activeTenant,
onEnrollPatient,
}) => {
const tenantPatients = patients.filter((p) => !p.tenantId || p.tenantId === activeTenant.id);
const [selectedPlanForEnroll, setSelectedPlanForEnroll] = useState<string | null>(null);
const [selectedPatientId, setSelectedPatientId] = useState<string>(patients[0]?.id || '');
const [selectedPatientId, setSelectedPatientId] = useState<string>(tenantPatients[0]?.id || '');
const [enrollSuccess, setEnrollSuccess] = useState(false);
// Sync selected patient if clinic changes
React.useEffect(() => {
if (tenantPatients.length > 0 && !tenantPatients.some((p) => p.id === selectedPatientId)) {
setSelectedPatientId(tenantPatients[0].id);
}
}, [activeTenant.id, tenantPatients, selectedPatientId]);
const totalClinicMembershipMRR = memberships.reduce(
(sum, m) => sum + m.mrrContribution,
0
@@ -238,7 +246,7 @@ export const MembershipsView: React.FC<MembershipsViewProps> = ({
onChange={(e) => setSelectedPatientId(e.target.value)}
className="w-full bg-slate-50 border border-slate-200 rounded-lg p-3 text-slate-900 focus:bg-white focus:outline-none focus:border-sky-500"
>
{patients.map((p) => (
{tenantPatients.map((p) => (
<option key={p.id} value={p.id}>
{p.firstName} {p.lastName} ({p.phone})
</option>
+5 -4
View File
@@ -31,20 +31,21 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
const [sentToast, setSentToast] = useState(false);
const [filterType, setFilterType] = useState<'all' | 'dropout_risk' | 'active'>('dropout_risk');
const dropoutPatients = patients.filter((p) => p.status === 'dropout_risk');
const activePatients = patients.filter((p) => p.status === 'active');
const tenantPatients = patients.filter((p) => !p.tenantId || p.tenantId === activeTenant.id);
const dropoutPatients = tenantPatients.filter((p) => p.status === 'dropout_risk');
const activePatients = tenantPatients.filter((p) => p.status === 'active');
const displayedPatients =
filterType === 'dropout_risk'
? dropoutPatients
: filterType === 'active'
? activePatients
: patients;
: tenantPatients;
const handleOpenSmsModal = (patient: Patient) => {
setSelectedPatientForSms(patient);
setCustomSmsMessage(
`Hi ${patient.firstName}! Dr. Vance noticed you're due for visit ${
`Hi ${patient.firstName}! Our clinical team at ${activeTenant.name} noticed you're due for visit ${
patient.carePlan.completedVisits + 1
} of your ${patient.carePlan.title}. We want to ensure your recovery stays on track! Click here to pick a quick 15-min adjustment slot this week: https://${
activeTenant.domain
+89
View File
@@ -576,6 +576,37 @@ export const INITIAL_SOAP_NOTES: SoapNote[] = [
signedAt: '2026-09-05T09:25:00-07:00',
signedBy: 'Dr. Marcus Vance, D.C. (NPI 1942857391)',
},
{
id: 'soap-hope-1',
tenantId: 'tenant-hope',
patientId: 'pat-hope-1',
patientName: 'Chloe Adams',
providerId: 'prov-hope-1',
providerName: 'Dr. Hope Sullivan',
appointmentId: 'apt-hope-1',
date: '2026-09-08',
status: 'signed',
discipline: 'chiropractic',
vasScore: 4,
subjective: 'Patient reports persistent upper cervical stiffness and bilateral trapezius achiness after working 50+ hours at desk. No radicular arm numbness.',
objective: 'Cervical range of motion: Right rotation 60 deg with end-range pain, Left rotation 72 deg. Segmental restriction identified at C1-C2 and C5-C6 with suboccipital hypertonicity.',
assessment: 'Segmental somatic dysfunction of cervical region (M99.01) and cervicothoracic junction with postural strain.',
plan: '1. Diversified adjustment to C1 (Right Lateral Mass) and C5.\n2. Suboccipital myofascial release.\n3. Prescribed chin-tuck postural reset exercises.',
spinalAdjustments: [
{ vertebra: 'C1 (Atlas)', region: 'Cervical', listing: 'Right Lateral Mass Anterior', technique: 'Diversified', notes: 'Gentle rotary thrust with audible cavitation' },
{ vertebra: 'C5', region: 'Cervical', listing: 'Posterior Right', technique: 'Diversified', notes: 'Immediate reduction in hypertonicity' },
],
icd10Codes: [
{ code: 'M99.01', description: 'Segmental and somatic dysfunction of cervical region' },
{ code: 'M54.2', description: 'Cervicalgia / Neck Pain' },
],
cptCodes: [
{ code: '98940', description: 'Chiropractic Manipulative Treatment (CMT); Spinal, 1-2 Regions', fee: 55 },
{ code: '97140', description: 'Manual Therapy Techniques / Myofascial Release (15 min)', fee: 45 },
],
signedAt: '2026-09-08T09:30:00-04:00',
signedBy: 'Dr. Hope Sullivan, D.C. (NPI 1982736450)',
},
];
export const INITIAL_SUPERBILLS: Superbill[] = [
@@ -609,6 +640,64 @@ export const INITIAL_SUPERBILLS: Superbill[] = [
paymentMethod: 'Stripe Card',
generatedAt: '2026-09-05T09:30:00-07:00',
},
{
id: 'sb-2',
tenantId: 'tenant-1',
invoiceNumber: 'SB-2026-0902',
patientId: 'pat-2',
patientName: 'Sarah Holloway',
patientDob: '1993-11-20',
patientAddress: '924 Lincoln Ave, San Jose, CA 95125',
providerName: 'Dr. Marcus Vance, D.C.',
providerNpi: '1942857391',
clinicName: 'Apex Spine & Wellness Clinic',
clinicAddress: '1420 Meridian Ave, Suite 300, San Jose, CA 95125',
clinicTaxId: '84-2918471',
dateOfService: '2026-09-05',
posCode: '11 (Office)',
icd10Codes: [
{ code: 'M99.01', description: 'Segmental dysfunction cervical region' },
{ code: 'G44.209', description: 'Tension-type headache, unspecified' },
],
items: [
{ cptCode: '98940', description: 'CMT Spinal, 1-2 Regions', units: 1, rate: 55, total: 55 },
{ cptCode: '97110', description: 'Therapeutic Exercise (15 min)', units: 1, rate: 40, total: 40 },
],
totalAmount: 95,
patientPaid: 0,
balanceDue: 95,
paymentMethod: 'Unpaid',
generatedAt: '2026-09-05T14:30:00-07:00',
},
{
id: 'sb-hope-1',
tenantId: 'tenant-hope',
invoiceNumber: 'SB-HOPE-2026-01',
patientId: 'pat-hope-1',
patientName: 'Chloe Adams',
patientDob: '1993-11-22',
patientAddress: '450 Monument Ave, Richmond, VA 23219',
providerName: 'Dr. Hope Sullivan, D.C.',
providerNpi: '1982736450',
clinicName: 'Hope Integrative Health & Chiropractic',
clinicAddress: '1250 Hope Valley Road, Suite 100, Richmond, VA 23219',
clinicTaxId: '54-9812734',
dateOfService: '2026-09-08',
posCode: '11 (Office)',
icd10Codes: [
{ code: 'M99.01', description: 'Segmental and somatic dysfunction of cervical region' },
{ code: 'M54.2', description: 'Cervicalgia / Neck Pain' },
],
items: [
{ cptCode: '98940', description: 'CMT Spinal, 1-2 Regions (Cervical)', units: 1, rate: 55, total: 55 },
{ cptCode: '97140', description: 'Manual Therapy Techniques / Myofascial Release', units: 1, rate: 45, total: 45 },
],
totalAmount: 100,
patientPaid: 100,
balanceDue: 0,
paymentMethod: 'Stripe Card',
generatedAt: '2026-09-08T09:45:00-04:00',
},
];
export const INITIAL_PRODUCTS: RetailProduct[] = [