1307 lines
55 KiB
TypeScript
1307 lines
55 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import {
|
|
INITIAL_TENANTS,
|
|
INITIAL_PROVIDERS,
|
|
INITIAL_PATIENTS,
|
|
INITIAL_APPOINTMENTS,
|
|
INITIAL_SOAP_NOTES,
|
|
INITIAL_SUPERBILLS,
|
|
INITIAL_PRODUCTS,
|
|
INITIAL_MEMBERSHIPS,
|
|
INITIAL_PACKAGES,
|
|
INITIAL_WAITLIST,
|
|
} from '@/lib/mock-data';
|
|
import {
|
|
ClinicTenant,
|
|
Appointment,
|
|
SoapNote,
|
|
Superbill,
|
|
Patient,
|
|
RetailProduct,
|
|
WellnessMembership,
|
|
PrePaidPackage,
|
|
WaitlistEntry,
|
|
StaffRole,
|
|
Provider,
|
|
} from '@/types/clinical';
|
|
import { CalendarView } from '@/components/calendar/CalendarView';
|
|
import { SoapChartEditor } from '@/components/charting/SoapChartEditor';
|
|
import { SuperbillView } from '@/components/billing/SuperbillView';
|
|
import { RetentionView } from '@/components/retention/RetentionView';
|
|
import { PatientPortalView } from '@/components/intake/PatientPortalView';
|
|
import { SuperAdminView } from '@/components/admin/SuperAdminView';
|
|
import { RetailInventoryView } from '@/components/pos/RetailInventoryView';
|
|
import { MembershipsView } from '@/components/memberships/MembershipsView';
|
|
import { TelehealthRoom } from '@/components/telehealth/TelehealthRoom';
|
|
import { WaitlistModal } from '@/components/waitlist/WaitlistModal';
|
|
import { CourtAuditVaultModal } from '@/components/compliance/CourtAuditVaultModal';
|
|
import { CommandPalette } from '@/components/ui/CommandPalette';
|
|
import { InactivityLockoutModal } from '@/components/ui/InactivityLockoutModal';
|
|
import { ClinicalToastContainer, ToastMessage } from '@/components/ui/ClinicalToast';
|
|
import { DoctorOnboardingView } from '@/components/onboarding/DoctorOnboardingView';
|
|
import { ClinicGrowthView } from '@/components/growth/ClinicGrowthView';
|
|
import { clinicalAudio } from '@/lib/clinical-audio';
|
|
import {
|
|
Calendar,
|
|
FileText,
|
|
DollarSign,
|
|
Users,
|
|
Building2,
|
|
Globe,
|
|
ChevronDown,
|
|
Stethoscope,
|
|
Laptop,
|
|
ShoppingBag,
|
|
Repeat,
|
|
Video,
|
|
Shield,
|
|
ShieldAlert,
|
|
UserCheck,
|
|
Search,
|
|
Volume2,
|
|
VolumeX,
|
|
Sparkles,
|
|
Scale,
|
|
Lock,
|
|
ShieldCheck,
|
|
TrendingUp,
|
|
} from 'lucide-react';
|
|
|
|
export default function Home() {
|
|
const [tenants, setTenants] = useState<ClinicTenant[]>(INITIAL_TENANTS);
|
|
const [activeTenantId, setActiveTenantId] = useState<string>(INITIAL_TENANTS[0].id);
|
|
|
|
// 'clinic' | 'patient' | 'superadmin' | 'onboarding'
|
|
const [portalMode, setPortalMode] = useState<'clinic' | 'patient' | 'superadmin' | 'onboarding'>('clinic');
|
|
|
|
// Staff Role: 'doctor' | 'front_desk' | 'billing_admin'
|
|
const [staffRole, setStaffRole] = useState<StaffRole>('doctor');
|
|
|
|
// Clinic Sub-Tabs: 'calendar' | 'charting' | 'telehealth' | 'retail' | 'memberships' | 'billing' | 'retention' | 'growth'
|
|
const [clinicTab, setClinicTab] = useState<
|
|
'calendar' | 'charting' | 'telehealth' | 'retail' | 'memberships' | 'billing' | 'retention' | 'growth'
|
|
>('calendar');
|
|
|
|
const [appointments, setAppointments] = useState<Appointment[]>(INITIAL_APPOINTMENTS);
|
|
const [patients, setPatients] = useState<Patient[]>(INITIAL_PATIENTS);
|
|
const [providers, setProviders] = useState<Provider[]>(INITIAL_PROVIDERS);
|
|
const [soapNotes, setSoapNotes] = useState<SoapNote[]>(INITIAL_SOAP_NOTES);
|
|
const [superbills, setSuperbills] = useState<Superbill[]>(INITIAL_SUPERBILLS);
|
|
const [products, setProducts] = useState<RetailProduct[]>(INITIAL_PRODUCTS);
|
|
const [memberships, setMemberships] = useState<WellnessMembership[]>(INITIAL_MEMBERSHIPS);
|
|
const [packages, setPackages] = useState<PrePaidPackage[]>(INITIAL_PACKAGES);
|
|
const [waitlist, setWaitlist] = useState<WaitlistEntry[]>(INITIAL_WAITLIST);
|
|
const [isWaitlistOpen, setIsWaitlistOpen] = useState(false);
|
|
const [pendingIntakes, setPendingIntakes] = useState<any[]>([
|
|
{
|
|
id: 'intake-waiting-1',
|
|
patientName: 'Jessica Morales',
|
|
phone: '(555) 302-9912',
|
|
dob: '1991-08-14',
|
|
email: 'jess.morales@example.com',
|
|
chiefComplaint: 'Cervical Spine (Neck), Left Trapezius / Shoulder pain (Sharp / Stabbing)',
|
|
vasScore: 6,
|
|
painAreas: ['Cervical Spine (Neck)', 'Left Trapezius / Shoulder'],
|
|
submittedAt: 'Just now (Mobile Check-in)',
|
|
providerName: 'Dr. Marcus Vance',
|
|
service: 'Initial Chiropractic Exam & Adjustment',
|
|
},
|
|
]);
|
|
|
|
// Polish state: Command Palette, Toasts & Audio
|
|
const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
|
|
const [isCourtVaultOpen, setIsCourtVaultOpen] = useState(false);
|
|
const [isTerminalLocked, setIsTerminalLocked] = useState(false);
|
|
const [toasts, setToasts] = useState<ToastMessage[]>([]);
|
|
const [audioEnabled, setAudioEnabled] = useState(true);
|
|
const [isMoreMenuOpen, setIsMoreMenuOpen] = useState(false);
|
|
|
|
const [activePatient, setActivePatient] = useState<Patient>(INITIAL_PATIENTS[0]);
|
|
const [activeSoapNote, setActiveSoapNote] = useState<SoapNote | undefined>(INITIAL_SOAP_NOTES[0]);
|
|
const [activeSuperbill, setActiveSuperbill] = useState<Superbill>(INITIAL_SUPERBILLS[0]);
|
|
|
|
const activeTenant = tenants.find((t) => t.id === activeTenantId) || tenants[0];
|
|
const activeProviders = providers.filter((p) => p.tenantId === activeTenant.id);
|
|
const activeProvider = activeProviders[0] || providers[0];
|
|
|
|
// Toast notification helper
|
|
const addToast = (type: 'success' | 'alert' | 'info', title: string, description?: string) => {
|
|
const id = `toast-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`;
|
|
const newToast: ToastMessage = {
|
|
id,
|
|
type,
|
|
title,
|
|
description,
|
|
timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
|
|
};
|
|
setToasts((prev) => [...prev, newToast]);
|
|
setTimeout(() => {
|
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
}, 4500);
|
|
};
|
|
|
|
const handleDismissToast = (id: string) => {
|
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
};
|
|
|
|
// Keyboard shortcut listener for Cmd+K / Ctrl+K
|
|
React.useEffect(() => {
|
|
const handleGlobalKeyDown = (e: KeyboardEvent) => {
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
e.preventDefault();
|
|
setIsCommandPaletteOpen((prev) => !prev);
|
|
}
|
|
};
|
|
window.addEventListener('keydown', handleGlobalKeyDown);
|
|
return () => window.removeEventListener('keydown', handleGlobalKeyDown);
|
|
}, []);
|
|
|
|
const handleToggleAudio = () => {
|
|
const next = !audioEnabled;
|
|
setAudioEnabled(next);
|
|
clinicalAudio.setEnabled(next);
|
|
if (next) {
|
|
clinicalAudio.playSuccess();
|
|
addToast('info', 'Tactile Audio Active', 'Synthesized Web Audio clicks & chimes enabled');
|
|
} else {
|
|
addToast('info', 'Tactile Audio Muted', 'Sound effects silenced');
|
|
}
|
|
};
|
|
|
|
const handleSelectAppointment = (apt: Appointment) => {
|
|
clinicalAudio.playClick();
|
|
const patientMatch = patients.find((p) => p.id === apt.patientId) || patients[0];
|
|
setActivePatient(patientMatch);
|
|
|
|
const existingSoap = soapNotes.find((s) => s.patientId === apt.patientId && s.appointmentId === apt.id);
|
|
setActiveSoapNote(existingSoap);
|
|
|
|
if (apt.isTelehealth) {
|
|
setClinicTab('telehealth');
|
|
} else {
|
|
setClinicTab('charting');
|
|
}
|
|
};
|
|
|
|
const handleUpdateAppointmentStatus = (aptId: string, status: Appointment['status']) => {
|
|
clinicalAudio.playClick();
|
|
setAppointments((prev) =>
|
|
prev.map((a) => (a.id === aptId ? { ...a, status } : a))
|
|
);
|
|
addToast('info', 'Encounter Status Updated', `Appointment transitioned to ${status}`);
|
|
};
|
|
|
|
const handleSaveSoapNote = (note: SoapNote) => {
|
|
clinicalAudio.playSuccess();
|
|
setSoapNotes((prev) => {
|
|
const idx = prev.findIndex((s) => s.id === note.id);
|
|
if (idx >= 0) {
|
|
const copy = [...prev];
|
|
copy[idx] = note;
|
|
return copy;
|
|
}
|
|
return [...prev, note];
|
|
});
|
|
setActiveSoapNote(note);
|
|
addToast('success', 'Chart Synced to HIPAA Vault', `${note.patientName} • ${note.status === 'signed' ? 'Signed & Locked' : 'Draft Saved'}`);
|
|
};
|
|
|
|
const handleGenerateSuperbillFromNote = (note: SoapNote) => {
|
|
clinicalAudio.playSuccess();
|
|
const newSb: Superbill = {
|
|
id: `sb-${Date.now()}`,
|
|
tenantId: activeTenant.id,
|
|
invoiceNumber: `SB-2026-${Math.floor(1000 + Math.random() * 9000)}`,
|
|
patientId: activePatient.id,
|
|
patientName: `${activePatient.firstName} ${activePatient.lastName}`,
|
|
patientDob: activePatient.dob,
|
|
patientAddress: activePatient.address,
|
|
providerName: note.providerName,
|
|
providerNpi: activeProvider.npi,
|
|
clinicName: activeTenant.name,
|
|
clinicAddress: activeTenant.address,
|
|
clinicTaxId: activeTenant.taxId,
|
|
dateOfService: note.date,
|
|
posCode: '11 (Office)',
|
|
icd10Codes: note.icd10Codes,
|
|
items: note.cptCodes.map((cpt) => ({
|
|
cptCode: cpt.code,
|
|
description: cpt.description,
|
|
units: 1,
|
|
rate: cpt.fee,
|
|
total: cpt.fee,
|
|
})),
|
|
totalAmount: note.cptCodes.reduce((sum, c) => sum + c.fee, 0),
|
|
patientPaid: note.cptCodes.reduce((sum, c) => sum + c.fee, 0),
|
|
balanceDue: 0,
|
|
paymentMethod: 'Stripe Card',
|
|
generatedAt: new Date().toISOString(),
|
|
};
|
|
|
|
setSuperbills((prev) => [newSb, ...prev]);
|
|
setActiveSuperbill(newSb);
|
|
setClinicTab('billing');
|
|
addToast('success', 'Superbill Claim Created', `${newSb.invoiceNumber} • $${newSb.totalAmount.toFixed(2)} Fee Schedule`);
|
|
};
|
|
|
|
const handleMarkPaid = (superbillId: string, method: Superbill['paymentMethod']) => {
|
|
clinicalAudio.playSuccess();
|
|
setSuperbills((prev) =>
|
|
prev.map((sb) =>
|
|
sb.id === superbillId
|
|
? { ...sb, balanceDue: 0, patientPaid: sb.totalAmount, paymentMethod: method }
|
|
: sb
|
|
)
|
|
);
|
|
if (activeSuperbill.id === superbillId) {
|
|
setActiveSuperbill((prev) => ({
|
|
...prev,
|
|
balanceDue: 0,
|
|
patientPaid: prev.totalAmount,
|
|
paymentMethod: method,
|
|
}));
|
|
}
|
|
addToast('success', 'Stripe Payment Captured', `Receipt processed via ${method}`);
|
|
};
|
|
|
|
const handlePatientBookingComplete = (newAptData: any) => {
|
|
clinicalAudio.playSuccess();
|
|
const newApt: Appointment = {
|
|
id: `apt-${Date.now()}`,
|
|
tenantId: activeTenant.id,
|
|
patientId: `pat-${Date.now()}`,
|
|
...newAptData,
|
|
};
|
|
setAppointments((prev) => [newApt, ...prev]);
|
|
|
|
const newIntakeItem = {
|
|
id: `intake-${Date.now()}`,
|
|
patientName: newAptData.patientName || 'New Patient',
|
|
phone: newAptData.patientPhone || '(555) 000-0000',
|
|
chiefComplaint: newAptData.notes || newAptData.serviceType || 'Chief complaint documented during check-in',
|
|
vasScore: 5,
|
|
painAreas: ['Spine / Core'],
|
|
submittedAt: 'Just now (Mobile Check-in)',
|
|
providerName: newAptData.providerName || activeProvider.name,
|
|
service: newAptData.serviceType || 'Consultation',
|
|
};
|
|
setPendingIntakes((prev) => [newIntakeItem, ...prev]);
|
|
addToast('success', 'Intake & Booking Confirmed', `${newApt.patientName} scheduled for ${newApt.time}`);
|
|
};
|
|
|
|
const handleStartEncounterFromIntake = (intake: any) => {
|
|
clinicalAudio.playSuccess();
|
|
const nameParts = (intake.patientName || 'Jessica Morales').trim().split(' ');
|
|
const firstName = nameParts[0] || 'Jessica';
|
|
const lastName = nameParts.slice(1).join(' ') || 'Morales';
|
|
|
|
const newPatient: Patient = {
|
|
id: `pat-intake-${Date.now()}`,
|
|
tenantId: activeTenant.id,
|
|
firstName,
|
|
lastName,
|
|
email: intake.email || `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`,
|
|
phone: intake.phone || '(555) 302-9912',
|
|
dob: intake.dob || '1991-08-14',
|
|
gender: 'Female',
|
|
address: '284 Monterey Hwy, San Jose, CA 95112',
|
|
insuranceName: 'Blue Shield PPO',
|
|
insuranceId: 'BSP-99214',
|
|
status: 'active',
|
|
chiefComplaint: intake.chiefComplaint,
|
|
daysSinceLastVisit: 0,
|
|
lastVisitDate: new Date().toISOString().substring(0, 10),
|
|
vitals: {
|
|
bloodPressure: '118/76',
|
|
heartRate: 74,
|
|
temperature: '98.6°F',
|
|
oxygenSat: 99,
|
|
painLevel: intake.vasScore || 6,
|
|
bmi: '22.8',
|
|
allergies: ['None reported'],
|
|
contraindications: ['None reported'],
|
|
},
|
|
carePlan: {
|
|
title: 'Cervical Stabilization & Postural Restoration',
|
|
totalVisits: 12,
|
|
completedVisits: 0,
|
|
frequency: '3x / week for 4 weeks',
|
|
targetCondition: 'Cervicalgia & Upper Crossed Syndrome',
|
|
startDate: new Date().toISOString().substring(0, 10),
|
|
status: 'on_track',
|
|
},
|
|
};
|
|
|
|
setPatients((prev) => [newPatient, ...prev]);
|
|
setActivePatient(newPatient);
|
|
|
|
const newNote: SoapNote = {
|
|
id: `soap-intake-${Date.now()}`,
|
|
tenantId: activeTenant.id,
|
|
patientId: newPatient.id,
|
|
patientName: `${firstName} ${lastName}`,
|
|
providerId: activeProvider.id,
|
|
providerName: activeProvider.name,
|
|
date: new Date().toISOString().substring(0, 10),
|
|
status: 'draft',
|
|
discipline: 'chiropractic',
|
|
vasScore: intake.vasScore || 6,
|
|
subjective: `Patient completed digital intake in waiting room. Reports: "${intake.chiefComplaint}". Patient notes sudden sharp exacerbation rated ${intake.vasScore || 6}/10 pain after prolonged desk work. Desires manual alignment and therapeutic decompression.`,
|
|
objective: 'Cervical inspection demonstrates antalgic head tilt and guarded rotation. Motion palpation identifies acute subluxations at C1 (Right Lateral Mass) and C5 (Posterior Right). Paraspinal tenderness and hypertonicity in upper trapezius.',
|
|
assessment: 'Acute cervical segmental dysfunction (M99.01) with cervicogenic spasm. Patient suitable for manual chiropractic manipulation.',
|
|
plan: '1. Diversified adjustment delivered to C1 (Atlas) and C5.\n2. Suboccipital myofascial release (15 min).\n3. Ergonomic posture home stretches prescribed.\n4. Follow-up visit in 48 hours.',
|
|
spinalAdjustments: [
|
|
{ vertebra: 'C1 (Atlas)', region: 'Cervical', listing: 'Right Lateral Mass Anterior', technique: 'Diversified', notes: '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: 'CMT Spinal, 1-2 Regions (Cervical)', fee: 55 },
|
|
{ code: '97140', description: 'Manual Therapy Techniques (15 min)', fee: 45 },
|
|
],
|
|
};
|
|
|
|
setActiveSoapNote(newNote);
|
|
setPendingIntakes((prev) => prev.filter((i) => i.id !== intake.id));
|
|
setClinicTab('charting');
|
|
addToast('success', 'Mobile Intake Imported to SOAP', `${intake.patientName}'s pain map and history loaded.`);
|
|
};
|
|
|
|
const handleAddTenant = (newTenant: ClinicTenant) => {
|
|
setTenants((prev) => [...prev, newTenant]);
|
|
addToast('success', 'New Clinic Deployed', `${newTenant.name} onboarded to Mediusa OS`);
|
|
};
|
|
|
|
const handleCompleteOnboarding = (newTenant: ClinicTenant, leadDoctor: Provider) => {
|
|
setTenants((prev) => [...prev, newTenant]);
|
|
setProviders((prev) => [...prev, leadDoctor]);
|
|
setActiveTenantId(newTenant.id);
|
|
|
|
// Seed 1 active patient and encounter for instant clinic operation
|
|
const samplePatientId = `pat-${Date.now()}`;
|
|
const samplePatient: Patient = {
|
|
id: samplePatientId,
|
|
tenantId: newTenant.id,
|
|
firstName: 'Michael',
|
|
lastName: 'Sterling',
|
|
email: 'm.sterling@example.com',
|
|
phone: '(555) 349-1102',
|
|
dob: '1984-06-12',
|
|
gender: 'Male',
|
|
address: '742 Evergreen Terrace, McLean, VA 22102',
|
|
insuranceName: 'CareFirst BlueCross',
|
|
insuranceId: 'CFB-94021-X',
|
|
status: 'active',
|
|
chiefComplaint: 'Acute thoracic and lumbar stiffness following marathon training; radiates to right hamstring',
|
|
daysSinceLastVisit: 0,
|
|
lastVisitDate: new Date().toISOString().substring(0, 10),
|
|
nextAppointmentDate: new Date().toISOString().substring(0, 10),
|
|
vitals: {
|
|
bloodPressure: '122/78',
|
|
heartRate: 68,
|
|
temperature: '98.4°F',
|
|
oxygenSat: 99,
|
|
painLevel: 6,
|
|
bmi: '23.4',
|
|
allergies: ['Penicillin', 'Latex'],
|
|
contraindications: ['High-velocity cervical rotation'],
|
|
},
|
|
carePlan: {
|
|
title: 'Spinal Alignment & Thoracic Mobility Protocol',
|
|
totalVisits: 12,
|
|
completedVisits: 1,
|
|
frequency: '2x / week for 6 weeks',
|
|
targetCondition: 'Thoracolumbar Subluxation Complex',
|
|
startDate: new Date().toISOString().substring(0, 10),
|
|
status: 'on_track',
|
|
},
|
|
};
|
|
|
|
const sampleApt: Appointment = {
|
|
id: `apt-${Date.now()}`,
|
|
tenantId: newTenant.id,
|
|
patientId: samplePatientId,
|
|
patientName: `${samplePatient.firstName} ${samplePatient.lastName}`,
|
|
patientPhone: samplePatient.phone,
|
|
providerId: leadDoctor.id,
|
|
providerName: leadDoctor.name,
|
|
date: new Date().toISOString().substring(0, 10),
|
|
time: '10:00 AM',
|
|
durationMinutes: 45,
|
|
serviceType: 'Initial Clinical Examination & Spinal Adjustment',
|
|
status: 'confirmed',
|
|
room: 'Operatory 1',
|
|
notes: 'Initial evaluation under newly executed HIPAA BAA enclave. Full spinal visualizer and CMS-1500 queued.',
|
|
fee: 85,
|
|
};
|
|
|
|
setPatients((prev) => [samplePatient, ...prev]);
|
|
setAppointments((prev) => [sampleApt, ...prev]);
|
|
setActivePatient(samplePatient);
|
|
|
|
setPortalMode('clinic');
|
|
setClinicTab('calendar');
|
|
|
|
addToast(
|
|
'success',
|
|
'Clinic Enclave Deployed & BAA Sealed',
|
|
`${newTenant.name} is online on https://${newTenant.domain}. Lead Doctor: ${leadDoctor.name}.`
|
|
);
|
|
};
|
|
|
|
const handleSwitchTenant = (tenantId: string) => {
|
|
clinicalAudio.playClick();
|
|
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) => {
|
|
clinicalAudio.playClick();
|
|
setProducts((prev) =>
|
|
prev.map((p) => (p.id === productId ? { ...p, stockQty: newStock } : p))
|
|
);
|
|
addToast('info', 'POS Inventory Updated', `Stock quantity adjusted to ${newStock}`);
|
|
};
|
|
|
|
const handleEnrollPatientInMembership = (patientId: string, planName: string) => {
|
|
clinicalAudio.playSuccess();
|
|
setPatients((prev) =>
|
|
prev.map((p) => (p.id === patientId ? { ...p, activeMembership: planName } : p))
|
|
);
|
|
addToast('success', 'Membership Enrolled', `Patient subscribed to ${planName}`);
|
|
};
|
|
|
|
const handleAutoFillWaitlistPatient = (entryId: string) => {
|
|
clinicalAudio.playPing();
|
|
const entry = waitlist.find((w) => w.id === entryId);
|
|
setWaitlist((prev) =>
|
|
prev.map((w) => (w.id === entryId ? { ...w, status: 'notified' } : w))
|
|
);
|
|
addToast('alert', 'Waitlist SMS Dispatched', `Slot opening sent to ${entry?.patientName || 'patient'}`);
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50 text-slate-900 flex flex-col w-full max-w-full overflow-x-hidden">
|
|
{/* Top Hospital Command Header */}
|
|
<header className="sticky top-0 z-40 bg-white border-b border-slate-200/90 shadow-2xs">
|
|
{/* Tier 1: System Command & Tenant Navigation */}
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
|
|
<div className="flex items-center justify-between gap-3">
|
|
{/* Left Cluster: Hospital Brand & Clinic Identity */}
|
|
<div className="flex items-center gap-2.5 shrink-0">
|
|
<div className="flex items-center gap-2">
|
|
<span className="w-8 h-8 rounded-lg bg-sky-700 flex items-center justify-center font-black text-xs text-white shadow-xs">
|
|
+M
|
|
</span>
|
|
<div>
|
|
<div className="text-xs font-black tracking-tight text-slate-900 flex items-center gap-1.5">
|
|
<span>MEDIUSA CLINIC OS</span>
|
|
<span className="text-[10px] px-1.5 py-0.2 rounded-full bg-sky-100 text-sky-800 font-bold border border-sky-200">
|
|
v2.5
|
|
</span>
|
|
</div>
|
|
<div className="text-[10px] text-slate-400 font-mono hidden 2xl:flex items-center gap-1">
|
|
<Globe className="w-2.5 h-2.5 text-sky-700" />
|
|
https://{activeTenant.domain}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="h-4 w-[1px] bg-slate-200 hidden sm:block" />
|
|
|
|
{/* Clinic Dropdown Selector */}
|
|
<div className="relative">
|
|
<select
|
|
value={activeTenantId}
|
|
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 pl-2 pr-6 py-1.5 cursor-pointer focus:outline-none focus:border-sky-500 transition shadow-2xs max-w-[150px] sm:max-w-[190px] md:max-w-[210px] truncate"
|
|
title="Switch Clinic Location / Practice"
|
|
>
|
|
{tenants.map((t) => (
|
|
<option key={t.id} value={t.id}>
|
|
🏥 {t.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<ChevronDown className="w-3.5 h-3.5 text-slate-500 absolute right-1.5 top-1/2 -translate-y-1/2 pointer-events-none" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Center: Command Palette / Search Bar (Responsive, doesn't crowd out right tools) */}
|
|
<div className="hidden md:block flex-1 max-w-[200px] lg:max-w-[240px] xl:max-w-xs">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setIsCommandPaletteOpen(true);
|
|
}}
|
|
className="w-full flex items-center justify-between px-2.5 py-1.5 bg-slate-50 hover:bg-slate-100 border border-slate-200 rounded-lg text-slate-500 text-xs transition shadow-2xs group"
|
|
>
|
|
<div className="flex items-center gap-1.5 truncate">
|
|
<Search className="w-3.5 h-3.5 text-slate-400 group-hover:text-sky-600 transition shrink-0" />
|
|
<span className="truncate text-[11px]">Search (⌘K)...</span>
|
|
</div>
|
|
<kbd className="px-1.5 py-0.5 text-[9px] font-mono bg-white border border-slate-200 rounded text-slate-500 font-bold shrink-0 ml-1">
|
|
⌘K
|
|
</kbd>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Right Cluster: Quick Tools + Portal Switcher */}
|
|
<div className="flex items-center gap-2 shrink-0">
|
|
{/* Search button on mobile screens where input is hidden */}
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setIsCommandPaletteOpen(true);
|
|
}}
|
|
className="md:hidden p-1.5 rounded-lg border border-slate-200 bg-slate-50 hover:bg-slate-100 text-slate-600 shadow-2xs"
|
|
title="Search patients (⌘K)"
|
|
>
|
|
<Search className="w-4 h-4" />
|
|
</button>
|
|
|
|
{/* Utility & Security Actions Cluster */}
|
|
<div className="flex items-center gap-0.5 bg-slate-100/80 p-0.5 rounded-lg border border-slate-200">
|
|
{/* Audio Toggle */}
|
|
<button
|
|
type="button"
|
|
onClick={handleToggleAudio}
|
|
title={audioEnabled ? 'Tactile Audio Active (Click to Mute)' : 'Tactile Audio Muted (Click to Enable)'}
|
|
className={`p-1.5 rounded-md text-xs transition ${
|
|
audioEnabled
|
|
? 'bg-white text-sky-700 shadow-2xs font-semibold'
|
|
: 'text-slate-400 hover:text-slate-600'
|
|
}`}
|
|
>
|
|
{audioEnabled ? <Volume2 className="w-3.5 h-3.5 text-sky-600" /> : <VolumeX className="w-3.5 h-3.5" />}
|
|
</button>
|
|
|
|
{/* Court Vault Button */}
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setIsCourtVaultOpen(true);
|
|
}}
|
|
className="flex items-center gap-1 px-2 py-1 rounded-md text-slate-700 hover:text-slate-950 hover:bg-white text-xs font-semibold transition"
|
|
title="21 CFR Part 11 Court Defense Audit Vault & BAA Non-Repudiation Seal"
|
|
>
|
|
<Scale className="w-3.5 h-3.5 text-sky-600" />
|
|
<span className="hidden sm:inline text-[11px]">Vault</span>
|
|
</button>
|
|
|
|
{/* Terminal Lock Button */}
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setIsTerminalLocked(true);
|
|
}}
|
|
className="flex items-center gap-1 px-2 py-1 rounded-md text-slate-700 hover:text-rose-700 hover:bg-rose-50 text-xs font-semibold transition"
|
|
title="Lock Clinical Terminal (HIPAA 45 CFR § 164.312(a)(2)(iii))"
|
|
>
|
|
<Lock className="w-3.5 h-3.5 text-slate-500" />
|
|
<span className="hidden sm:inline text-[11px]">Lock</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Portal Mode Switcher (Always 100% visible on the right) */}
|
|
<div className="flex items-center bg-slate-100 p-0.5 rounded-lg border border-slate-200 text-xs font-semibold">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setPortalMode('clinic');
|
|
}}
|
|
className={`px-2.5 py-1 rounded-md flex items-center gap-1.5 transition text-[11px] ${
|
|
portalMode === 'clinic'
|
|
? 'bg-sky-700 text-white font-bold shadow-xs'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<Stethoscope className="w-3 h-3" />
|
|
<span>Doctor OS</span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setPortalMode('patient');
|
|
}}
|
|
className={`px-2.5 py-1 rounded-md flex items-center gap-1.5 transition text-[11px] ${
|
|
portalMode === 'patient'
|
|
? 'bg-sky-700 text-white font-bold shadow-xs'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<Laptop className="w-3 h-3" />
|
|
<span>Patient Intake</span>
|
|
</button>
|
|
|
|
{/* More Views Dropdown */}
|
|
<div className="relative">
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsMoreMenuOpen((prev) => !prev)}
|
|
className={`px-2 py-1 rounded-md flex items-center gap-1 text-[11px] transition ${
|
|
portalMode === 'superadmin' || portalMode === 'onboarding'
|
|
? 'bg-slate-900 text-white font-bold'
|
|
: 'text-slate-600 hover:text-slate-900 hover:bg-slate-200/60'
|
|
}`}
|
|
title="Administrative & Onboarding Views"
|
|
>
|
|
<span>{portalMode === 'superadmin' ? 'Admin' : portalMode === 'onboarding' ? 'Onboard' : 'More'}</span>
|
|
<ChevronDown className="w-3 h-3 opacity-60" />
|
|
</button>
|
|
|
|
{isMoreMenuOpen && (
|
|
<>
|
|
<div
|
|
className="fixed inset-0 z-40"
|
|
onClick={() => setIsMoreMenuOpen(false)}
|
|
/>
|
|
<div className="absolute right-0 top-full mt-1.5 bg-white border border-slate-200 shadow-xl rounded-xl py-1.5 w-52 z-50 animate-in fade-in zoom-in-95 duration-100">
|
|
<div className="px-3 py-1 text-[10px] uppercase font-bold text-slate-400 tracking-wider">
|
|
Administrative Views
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setPortalMode('superadmin');
|
|
setIsMoreMenuOpen(false);
|
|
}}
|
|
className={`w-full px-3 py-2 text-left text-xs font-semibold flex items-center gap-2 transition ${
|
|
portalMode === 'superadmin' ? 'bg-slate-100 text-slate-950 font-bold' : 'text-slate-700 hover:bg-slate-50'
|
|
}`}
|
|
>
|
|
<Building2 className="w-3.5 h-3.5 text-slate-500" />
|
|
<span>Mediusa Super-Admin</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setPortalMode('onboarding');
|
|
setIsMoreMenuOpen(false);
|
|
}}
|
|
className={`w-full px-3 py-2 text-left text-xs font-semibold flex items-center gap-2 transition ${
|
|
portalMode === 'onboarding' ? 'bg-emerald-50 text-emerald-950 font-bold' : 'text-emerald-700 hover:bg-emerald-50'
|
|
}`}
|
|
>
|
|
<Sparkles className="w-3.5 h-3.5 text-amber-500" />
|
|
<span>+ Onboard Doctor (BAA)</span>
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Pre-Launch / Pending BAA Banner */}
|
|
{portalMode === 'clinic' && activeTenant.baaStatus === 'pending' && (
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-2">
|
|
<div className="p-2.5 bg-emerald-50 border border-emerald-200/90 rounded-xl flex flex-col sm:flex-row items-center justify-between gap-2 text-xs">
|
|
<div className="flex items-center gap-2 text-emerald-950">
|
|
<span className="w-2.5 h-2.5 rounded-full bg-emerald-500 animate-pulse shrink-0" />
|
|
<span className="font-bold">{activeTenant.name}:</span>
|
|
<span className="text-emerald-800">
|
|
Scheduling, calendars, and digital intake are active for testing. Ready for doctor BAA sign-off on go-live!
|
|
</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setPortalMode('onboarding');
|
|
}}
|
|
className="px-3 py-1 bg-emerald-700 hover:bg-emerald-800 text-white font-bold rounded-lg transition shadow-xs flex items-center gap-1.5 shrink-0"
|
|
>
|
|
<ShieldCheck className="w-3.5 h-3.5" /> Sign BAA to Go Live
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tier 2: Sub-Tabs for Doctor Clinic OS + Staff Role Switcher */}
|
|
{portalMode === 'clinic' && (
|
|
<div className="border-t border-slate-100 bg-slate-50/50">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-1.5 flex items-center justify-between flex-wrap gap-2 text-xs">
|
|
{/* Grouped Clinical Workspace Navigation */}
|
|
<div className="flex items-center flex-wrap gap-1.5">
|
|
{/* Group 1: Clinical Operations */}
|
|
<div className="flex items-center bg-white p-0.5 rounded-lg border border-slate-200/90 shadow-2xs">
|
|
<button
|
|
type="button"
|
|
onClick={() => setClinicTab('calendar')}
|
|
className={`px-2.5 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition text-xs ${
|
|
clinicTab === 'calendar'
|
|
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<Calendar className="w-3.5 h-3.5 text-sky-700" />
|
|
<span>Schedule</span>
|
|
</button>
|
|
|
|
{staffRole !== 'front_desk' ? (
|
|
<button
|
|
type="button"
|
|
onClick={() => setClinicTab('charting')}
|
|
className={`px-2.5 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition text-xs ${
|
|
clinicTab === 'charting'
|
|
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<FileText className="w-3.5 h-3.5 text-sky-700" />
|
|
<span>SOAP Chart</span>
|
|
<span className="text-[10px] px-1.5 py-0.2 rounded bg-sky-100 text-sky-800 font-mono font-medium">
|
|
{activePatient.firstName} {activePatient.lastName.charAt(0)}.
|
|
</span>
|
|
</button>
|
|
) : (
|
|
<span className="px-2 py-1 text-[10px] text-amber-700 bg-amber-50 rounded font-medium flex items-center gap-1">
|
|
<Shield className="w-3 h-3 text-amber-600" /> Notes Locked
|
|
</span>
|
|
)}
|
|
|
|
{staffRole !== 'front_desk' && (
|
|
<button
|
|
type="button"
|
|
onClick={() => setClinicTab('telehealth')}
|
|
className={`px-2.5 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition text-xs ${
|
|
clinicTab === 'telehealth'
|
|
? 'bg-purple-50 text-purple-800 border border-purple-200 shadow-2xs font-bold'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<Video className="w-3.5 h-3.5 text-purple-600" />
|
|
<span>Telehealth</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Group 2: Practice & Revenue */}
|
|
<div className="flex items-center bg-white p-0.5 rounded-lg border border-slate-200/90 shadow-2xs">
|
|
<button
|
|
type="button"
|
|
onClick={() => setClinicTab('billing')}
|
|
className={`px-2.5 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition text-xs ${
|
|
clinicTab === 'billing'
|
|
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<DollarSign className="w-3.5 h-3.5 text-emerald-600" />
|
|
<span>Superbills & Billing</span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setClinicTab('retail')}
|
|
className={`px-2.5 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition text-xs ${
|
|
clinicTab === 'retail'
|
|
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<ShoppingBag className="w-3.5 h-3.5 text-sky-700" />
|
|
<span>Retail POS</span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setClinicTab('memberships')}
|
|
className={`px-2.5 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition text-xs ${
|
|
clinicTab === 'memberships'
|
|
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<Repeat className="w-3.5 h-3.5 text-sky-700" />
|
|
<span>Memberships</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Group 3: Patient Retention & Growth */}
|
|
<div className="flex items-center bg-white p-0.5 rounded-lg border border-slate-200/90 shadow-2xs">
|
|
<button
|
|
type="button"
|
|
onClick={() => setClinicTab('retention')}
|
|
className={`px-2.5 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition text-xs ${
|
|
clinicTab === 'retention'
|
|
? 'bg-amber-50 text-amber-800 border border-amber-200 shadow-2xs font-bold'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<Users className="w-3.5 h-3.5 text-amber-600" />
|
|
<span>Retention</span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setClinicTab('growth')}
|
|
className={`px-2.5 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition text-xs ${
|
|
clinicTab === 'growth'
|
|
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
>
|
|
<TrendingUp className="w-3.5 h-3.5 text-emerald-600" />
|
|
<span>Growth & SEO</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Side: Staff Role Switcher (RBAC) + Attending Doctor */}
|
|
<div className="flex items-center gap-2">
|
|
{/* Staff Role Switcher */}
|
|
<div className="flex items-center bg-slate-100 p-0.5 rounded-lg border border-slate-200 text-xs">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setStaffRole('doctor');
|
|
addToast('info', 'Role: Attending Doctor (D.C.)', 'Full clinical SOAP, 2D spine & telehealth unlocked');
|
|
}}
|
|
className={`px-2 py-1 rounded-md text-[11px] font-bold transition ${
|
|
staffRole === 'doctor'
|
|
? 'bg-white text-sky-800 shadow-2xs border border-slate-200'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
title="Full Doctor Clinical Access"
|
|
>
|
|
Doctor
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setStaffRole('front_desk');
|
|
if (clinicTab === 'charting' || clinicTab === 'telehealth') {
|
|
setClinicTab('calendar');
|
|
}
|
|
addToast('alert', 'Role: Front Desk (HIPAA Safe)', 'Protected medical notes locked for privacy');
|
|
}}
|
|
className={`px-2 py-1 rounded-md text-[11px] font-bold transition ${
|
|
staffRole === 'front_desk'
|
|
? 'bg-amber-100 text-amber-900 shadow-2xs border border-amber-200'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
title="HIPAA-Safe: Hides Clinical Notes, Shows Schedule, Retail & Billing"
|
|
>
|
|
Front Desk
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
clinicalAudio.playClick();
|
|
setStaffRole('billing_admin');
|
|
addToast('info', 'Role: Billing Administrator', 'Superbill claims and memberships prioritized');
|
|
}}
|
|
className={`px-2 py-1 rounded-md text-[11px] font-bold transition ${
|
|
staffRole === 'billing_admin'
|
|
? 'bg-white text-emerald-800 shadow-2xs border border-slate-200'
|
|
: 'text-slate-600 hover:text-slate-900'
|
|
}`}
|
|
title="Financial & Billing Administrator"
|
|
>
|
|
Billing
|
|
</button>
|
|
</div>
|
|
|
|
{/* Active Clinician Presence Indicator */}
|
|
<div className="hidden 2xl:flex items-center gap-1.5 text-xs text-slate-600 font-mono bg-white px-2 py-1 rounded-lg border border-slate-200 shadow-2xs">
|
|
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
<span className="font-semibold">{activeProvider.name}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</header>
|
|
|
|
{/* Main Container */}
|
|
<main className="flex-1 max-w-7xl w-full mx-auto p-4 sm:p-6 lg:p-8">
|
|
{/* Clinic Executive Collections & Billing Pulse Bar */}
|
|
{portalMode === 'clinic' && (
|
|
<div className="mb-5 grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
<div className="bg-white border border-slate-200/90 rounded-xl p-3.5 shadow-2xs flex items-center justify-between">
|
|
<div>
|
|
<span className="text-[11px] font-bold text-slate-500 uppercase tracking-wider block">
|
|
Today's Collections
|
|
</span>
|
|
<span className="text-base font-extrabold text-slate-900 font-mono tracking-tight">$2,380.00</span>
|
|
</div>
|
|
<span className="text-[10px] font-bold text-emerald-700 bg-emerald-50 px-2 py-0.5 rounded border border-emerald-200 font-mono">
|
|
+14.2% vs avg
|
|
</span>
|
|
</div>
|
|
|
|
<div className="bg-white border border-slate-200/90 rounded-xl p-3.5 shadow-2xs flex items-center justify-between">
|
|
<div>
|
|
<span className="text-[11px] font-bold text-slate-500 uppercase tracking-wider block">
|
|
Clean Claim Pass Rate
|
|
</span>
|
|
<span className="text-base font-extrabold text-emerald-700 font-mono tracking-tight">99.8%</span>
|
|
</div>
|
|
<span className="text-[10px] font-bold text-emerald-700 bg-emerald-50 px-2 py-0.5 rounded border border-emerald-200 font-mono">
|
|
0 NCCI Denials
|
|
</span>
|
|
</div>
|
|
|
|
<div className="bg-white border border-slate-200/90 rounded-xl p-3.5 shadow-2xs flex items-center justify-between">
|
|
<div>
|
|
<span className="text-[11px] font-bold text-slate-500 uppercase tracking-wider block">
|
|
Average Days in A/R
|
|
</span>
|
|
<span className="text-base font-extrabold text-sky-800 font-mono tracking-tight">11.4 Days</span>
|
|
</div>
|
|
<span className="text-[10px] font-bold text-sky-700 bg-sky-50 px-2 py-0.5 rounded border border-sky-200 font-mono">
|
|
3x Industry Speed
|
|
</span>
|
|
</div>
|
|
|
|
<div className="bg-white border border-slate-200/90 rounded-xl p-3.5 shadow-2xs flex items-center justify-between">
|
|
<div>
|
|
<span className="text-[11px] font-bold text-slate-500 uppercase tracking-wider block">
|
|
Frontier AI Engine
|
|
</span>
|
|
<span className="text-base font-extrabold text-indigo-700 font-mono tracking-tight">GPT-5.4 • Claude 3.7</span>
|
|
</div>
|
|
<span className="text-[10px] font-bold text-indigo-700 bg-indigo-50 px-2 py-0.5 rounded border border-indigo-200 font-mono">
|
|
Active Scribe
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{portalMode === 'clinic' && (() => {
|
|
const tenantPatients = patients.filter((p) => !p.tenantId || p.tenantId === activeTenant.id);
|
|
const tenantSuperbills = superbills.filter((s) => !s.tenantId || s.tenantId === activeTenant.id);
|
|
|
|
return (
|
|
<div>
|
|
{clinicTab === 'calendar' && (
|
|
<CalendarView
|
|
appointments={appointments}
|
|
providers={activeProviders}
|
|
activeTenant={activeTenant}
|
|
patients={tenantPatients}
|
|
waitlistCount={waitlist.length}
|
|
pendingIntakes={pendingIntakes}
|
|
onSelectAppointment={handleSelectAppointment}
|
|
onUpdateStatus={handleUpdateAppointmentStatus}
|
|
onNewAppointmentClick={() => setPortalMode('patient')}
|
|
onQuickSchedule={handleQuickScheduleAppointment}
|
|
onOpenWaitlist={() => setIsWaitlistOpen(true)}
|
|
onStartEncounterFromIntake={handleStartEncounterFromIntake}
|
|
/>
|
|
)}
|
|
|
|
{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 === 'telehealth' && staffRole !== 'front_desk' && (
|
|
<TelehealthRoom
|
|
patient={activePatient}
|
|
provider={activeProvider}
|
|
onEndCall={handleEndTelehealthCall}
|
|
onClose={() => setClinicTab('calendar')}
|
|
/>
|
|
)}
|
|
|
|
{clinicTab === 'retail' && (
|
|
<RetailInventoryView
|
|
products={products}
|
|
activeTenant={activeTenant}
|
|
onUpdateStock={handleUpdateProductStock}
|
|
/>
|
|
)}
|
|
|
|
{clinicTab === 'memberships' && (
|
|
<MembershipsView
|
|
memberships={memberships}
|
|
packages={packages}
|
|
patients={tenantPatients}
|
|
activeTenant={activeTenant}
|
|
onEnrollPatient={handleEnrollPatientInMembership}
|
|
/>
|
|
)}
|
|
|
|
{clinicTab === 'billing' && (
|
|
<SuperbillView
|
|
superbill={activeSuperbill}
|
|
superbills={tenantSuperbills}
|
|
activeTenant={activeTenant}
|
|
onBack={() => setClinicTab('calendar')}
|
|
onSelectSuperbill={(sb) => setActiveSuperbill(sb)}
|
|
onMarkPaid={handleMarkPaid}
|
|
/>
|
|
)}
|
|
|
|
{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
|
|
activeTenant={activeTenant}
|
|
providers={activeProviders}
|
|
onBookingComplete={handlePatientBookingComplete}
|
|
/>
|
|
)}
|
|
|
|
{portalMode === 'superadmin' && (
|
|
<SuperAdminView
|
|
tenants={tenants}
|
|
onAddTenant={handleAddTenant}
|
|
onSwitchTenant={handleSwitchTenant}
|
|
/>
|
|
)}
|
|
|
|
{portalMode === 'onboarding' && (
|
|
<DoctorOnboardingView
|
|
onCompleteOnboarding={handleCompleteOnboarding}
|
|
onCancel={() => setPortalMode('clinic')}
|
|
/>
|
|
)}
|
|
</main>
|
|
|
|
{/* Cancellation Waitlist Modal */}
|
|
<WaitlistModal
|
|
waitlist={waitlist}
|
|
isOpen={isWaitlistOpen}
|
|
onClose={() => setIsWaitlistOpen(false)}
|
|
onAutoFillPatient={handleAutoFillWaitlistPatient}
|
|
/>
|
|
|
|
{/* Court-Ready Legal & HIPAA Compliance Vault Modal */}
|
|
<CourtAuditVaultModal
|
|
isOpen={isCourtVaultOpen}
|
|
onClose={() => setIsCourtVaultOpen(false)}
|
|
activePatient={activePatient}
|
|
activeSoapNote={activeSoapNote}
|
|
activeTenant={activeTenant}
|
|
/>
|
|
|
|
{/* HIPAA Inactivity & Break-Glass Lockout Modal (45 CFR § 164.312(a)(2)(iii) & § 164.312(a)(2)(ii)) */}
|
|
<InactivityLockoutModal
|
|
isLocked={isTerminalLocked}
|
|
onUnlock={() => setIsTerminalLocked(false)}
|
|
onManualLock={() => setIsTerminalLocked(true)}
|
|
doctorName={activeProvider.name}
|
|
clinicName={activeTenant.name}
|
|
inactivityTimeoutMinutes={15}
|
|
/>
|
|
|
|
{/* Global Command Palette (Cmd+K) */}
|
|
<CommandPalette
|
|
isOpen={isCommandPaletteOpen}
|
|
onClose={() => setIsCommandPaletteOpen(false)}
|
|
patients={patients}
|
|
onSelectPatient={(p) => {
|
|
setActivePatient(p);
|
|
const existing = soapNotes.find((s) => s.patientId === p.id);
|
|
setActiveSoapNote(existing);
|
|
}}
|
|
onNavigateTab={(tab) => setClinicTab(tab)}
|
|
onOpenWaitlist={() => setIsWaitlistOpen(true)}
|
|
onSetStaffRole={(role) => {
|
|
setStaffRole(role);
|
|
if (role === 'front_desk' && (clinicTab === 'charting' || clinicTab === 'telehealth')) {
|
|
setClinicTab('calendar');
|
|
}
|
|
}}
|
|
onToggleAudio={handleToggleAudio}
|
|
audioEnabled={audioEnabled}
|
|
onOpenCourtVault={() => setIsCourtVaultOpen(true)}
|
|
onLockTerminal={() => setIsTerminalLocked(true)}
|
|
onOpenOnboarding={() => setPortalMode('onboarding')}
|
|
/>
|
|
|
|
{/* Hospital Clinical Toast Notification System */}
|
|
<ClinicalToastContainer
|
|
toasts={toasts}
|
|
onDismiss={handleDismissToast}
|
|
/>
|
|
|
|
{/* Hospital Footer */}
|
|
<footer className="bg-white border-t border-slate-200 py-4 px-6 text-center text-xs text-slate-500">
|
|
<div className="flex flex-col sm:flex-row items-center justify-between max-w-7xl mx-auto gap-2">
|
|
<span className="font-medium text-slate-600">
|
|
Mediusa Clinic OS • Complete Hospital-Grade Practice Management Platform
|
|
</span>
|
|
<span className="font-mono text-slate-500">
|
|
hello@aipilots.site • HIPAA Certified • Sub-30s Clinical Charting Standard
|
|
</span>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|