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

This commit is contained in:
2026-09-05 14:12:19 -07:00
parent 441e5eb57d
commit deb5197e53
17 changed files with 4675 additions and 71 deletions
+388 -59
View File
@@ -1,69 +1,398 @@
import Image from "next/image";
'use client';
import React, { useState } from 'react';
import {
INITIAL_TENANTS,
INITIAL_PROVIDERS,
INITIAL_PATIENTS,
INITIAL_APPOINTMENTS,
INITIAL_SOAP_NOTES,
INITIAL_SUPERBILLS,
} from '@/lib/mock-data';
import {
ClinicTenant,
Provider,
Patient,
Appointment,
SoapNote,
Superbill,
} 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 {
Calendar,
FileText,
DollarSign,
Users,
ShieldCheck,
Building2,
Globe,
Sparkles,
ChevronDown,
Stethoscope,
Laptop,
CheckCircle2,
} from 'lucide-react';
export default function Home() {
// Global Multi-Tenant State
const [tenants, setTenants] = useState<ClinicTenant[]>(INITIAL_TENANTS);
const [activeTenantId, setActiveTenantId] = useState<string>(INITIAL_TENANTS[0].id);
// Active Navigation Mode: 'clinic' (EHR Practice OS) | 'patient' (Booking/Intake) | 'superadmin' (Mediusa Command)
const [portalMode, setPortalMode] = useState<'clinic' | 'patient' | 'superadmin'>('clinic');
// Clinic Portal Sub-Tabs: 'calendar' | 'charting' | 'billing' | 'retention'
const [clinicTab, setClinicTab] = useState<'calendar' | 'charting' | 'billing' | 'retention'>('calendar');
// Active Entities
const [appointments, setAppointments] = useState<Appointment[]>(INITIAL_APPOINTMENTS);
const [patients, setPatients] = useState<Patient[]>(INITIAL_PATIENTS);
const [soapNotes, setSoapNotes] = useState<SoapNote[]>(INITIAL_SOAP_NOTES);
const [superbills, setSuperbills] = useState<Superbill[]>(INITIAL_SUPERBILLS);
const [activePatient, setActivePatient] = useState<Patient>(INITIAL_PATIENTS[0]); // Marcus Chen
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 = INITIAL_PROVIDERS.filter((p) => p.tenantId === activeTenant.id);
const activeProvider = activeProviders[0] || INITIAL_PROVIDERS[0];
// Handlers
const handleSelectAppointment = (apt: Appointment) => {
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);
setClinicTab('charting');
};
const handleUpdateAppointmentStatus = (aptId: string, status: Appointment['status']) => {
setAppointments((prev) =>
prev.map((a) => (a.id === aptId ? { ...a, status } : a))
);
};
const handleSaveSoapNote = (note: SoapNote) => {
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);
};
const handleGenerateSuperbillFromNote = (note: SoapNote) => {
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');
};
const handleMarkPaid = (superbillId: string, method: Superbill['paymentMethod']) => {
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,
}));
}
};
const handlePatientBookingComplete = (newAptData: any) => {
const newApt: Appointment = {
id: `apt-${Date.now()}`,
tenantId: activeTenant.id,
patientId: `pat-${Date.now()}`,
...newAptData,
};
setAppointments((prev) => [newApt, ...prev]);
};
const handleAddTenant = (newTenant: ClinicTenant) => {
setTenants((prev) => [...prev, newTenant]);
};
const handleSwitchTenant = (tenantId: string) => {
setActiveTenantId(tenantId);
setPortalMode('clinic');
setClinicTab('calendar');
};
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert h-5 w-[100px]"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the{" "}
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
page.tsx
</code>{" "}
file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
<div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col">
{/* Top Global Command Bar */}
<header className="sticky top-0 z-40 bg-slate-950/90 backdrop-blur-md border-b border-slate-800/80 px-4 lg:px-8 py-3">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row md:items-center justify-between gap-4">
{/* Brand & Clinic Domain Selector */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span
className="w-8 h-8 rounded-xl flex items-center justify-center font-black text-xs text-slate-950 shadow-md"
style={{ backgroundColor: activeTenant.brandColor }}
>
M+
</span>
<div>
<div className="text-xs font-black tracking-tight text-white flex items-center gap-1.5">
<span>MEDIUSA CLINIC OS</span>
<span className="text-[10px] px-1.5 py-0.2 rounded bg-teal-500/20 text-teal-300 font-mono">
v2.4 PRO
</span>
</div>
<div className="text-[11px] text-slate-400 font-mono flex items-center gap-1">
<Globe className="w-3 h-3 text-teal-400" />
https://{activeTenant.domain}
</div>
</div>
</div>
{/* Tenant Switcher Dropdown */}
<div className="relative group">
<select
value={activeTenantId}
onChange={(e) => setActiveTenantId(e.target.value)}
className="appearance-none bg-slate-900 hover:bg-slate-800 border border-slate-700 text-slate-200 text-xs font-semibold rounded-xl px-3 py-1.5 pr-8 cursor-pointer focus:outline-none focus:border-teal-500 transition"
>
{tenants.map((t) => (
<option key={t.id} value={t.id}>
🏥 {t.name}
</option>
))}
</select>
<ChevronDown className="w-3.5 h-3.5 text-slate-400 absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none" />
</div>
</div>
{/* Core Portal Mode Switcher */}
<div className="flex items-center gap-1 bg-slate-900 p-1 rounded-xl border border-slate-800 text-xs font-semibold">
<button
type="button"
onClick={() => setPortalMode('clinic')}
className={`px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition ${
portalMode === 'clinic'
? 'bg-teal-500 text-slate-950 font-bold shadow'
: 'text-slate-400 hover:text-white'
}`}
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
<Stethoscope className="w-3.5 h-3.5" />
Doctor &amp; Practice OS
</button>
<button
type="button"
onClick={() => setPortalMode('patient')}
className={`px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition ${
portalMode === 'patient'
? 'bg-teal-500 text-slate-950 font-bold shadow'
: 'text-slate-400 hover:text-white'
}`}
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert h-[14px] w-4"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={14}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
<Laptop className="w-3.5 h-3.5" />
Patient Booking &amp; Intake
</button>
<button
type="button"
onClick={() => setPortalMode('superadmin')}
className={`px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition ${
portalMode === 'superadmin'
? 'bg-indigo-600 text-white font-bold shadow'
: 'text-slate-400 hover:text-white'
}`}
>
<Building2 className="w-3.5 h-3.5 text-indigo-300" />
Mediusa Super-Admin
</button>
</div>
</div>
{/* Sub-Tabs for Doctor Clinic OS */}
{portalMode === 'clinic' && (
<div className="max-w-7xl mx-auto flex items-center gap-2 mt-3 pt-2 border-t border-slate-800/80 overflow-x-auto text-xs">
<button
type="button"
onClick={() => setClinicTab('calendar')}
className={`px-3 py-1.5 rounded-lg font-medium flex items-center gap-1.5 transition whitespace-nowrap ${
clinicTab === 'calendar'
? 'bg-slate-800 text-teal-300 border border-teal-500/40 font-bold'
: 'text-slate-400 hover:text-slate-200'
}`}
>
<Calendar className="w-3.5 h-3.5" />
Fast Calendar &amp; Schedule
</button>
<button
type="button"
onClick={() => setClinicTab('charting')}
className={`px-3 py-1.5 rounded-lg font-medium flex items-center gap-1.5 transition whitespace-nowrap ${
clinicTab === 'charting'
? 'bg-slate-800 text-teal-300 border border-teal-500/40 font-bold'
: 'text-slate-400 hover:text-slate-200'
}`}
>
<FileText className="w-3.5 h-3.5" />
SOAP Chart &amp; 2D Spine Map ({activePatient.firstName} {activePatient.lastName})
</button>
<button
type="button"
onClick={() => setClinicTab('billing')}
className={`px-3 py-1.5 rounded-lg font-medium flex items-center gap-1.5 transition whitespace-nowrap ${
clinicTab === 'billing'
? 'bg-slate-800 text-teal-300 border border-teal-500/40 font-bold'
: 'text-slate-400 hover:text-slate-200'
}`}
>
<DollarSign className="w-3.5 h-3.5" />
Superbills &amp; Stripe Billing
</button>
<button
type="button"
onClick={() => setClinicTab('retention')}
className={`px-3 py-1.5 rounded-lg font-medium flex items-center gap-1.5 transition whitespace-nowrap ${
clinicTab === 'retention'
? 'bg-slate-800 text-amber-300 border border-amber-500/40 font-bold'
: 'text-slate-400 hover:text-slate-200'
}`}
>
<Users className="w-3.5 h-3.5 text-amber-400" />
Care Plan Retention Radar
</button>
</div>
)}
</header>
{/* Main App Container */}
<main className="flex-1 max-w-7xl w-full mx-auto p-4 sm:p-6 lg:p-8">
{/* PORTAL MODE 1: Clinic Practice OS */}
{portalMode === 'clinic' && (
<div>
{clinicTab === 'calendar' && (
<CalendarView
appointments={appointments}
providers={activeProviders}
activeTenant={activeTenant}
onSelectAppointment={handleSelectAppointment}
onUpdateStatus={handleUpdateAppointmentStatus}
onNewAppointmentClick={() => setPortalMode('patient')}
/>
)}
{clinicTab === 'charting' && (
<SoapChartEditor
patient={activePatient}
provider={activeProvider}
initialSoapNote={activeSoapNote}
onSaveSoapNote={handleSaveSoapNote}
onGenerateSuperbill={handleGenerateSuperbillFromNote}
onBackToCalendar={() => setClinicTab('calendar')}
/>
)}
{clinicTab === 'billing' && (
<SuperbillView
superbill={activeSuperbill}
activeTenant={activeTenant}
onBack={() => setClinicTab('charting')}
onMarkPaid={handleMarkPaid}
/>
)}
{clinicTab === 'retention' && (
<RetentionView
patients={patients}
activeTenant={activeTenant}
onSelectPatientChart={(patient) => {
setActivePatient(patient);
const existing = soapNotes.find((s) => s.patientId === patient.id);
setActiveSoapNote(existing);
setClinicTab('charting');
}}
/>
)}
</div>
)}
{/* PORTAL MODE 2: White-Labeled Patient Booking & Intake */}
{portalMode === 'patient' && (
<PatientPortalView
activeTenant={activeTenant}
providers={activeProviders}
onBookingComplete={handlePatientBookingComplete}
/>
)}
{/* PORTAL MODE 3: Mediusa Super-Admin Command Center */}
{portalMode === 'superadmin' && (
<SuperAdminView
tenants={tenants}
onAddTenant={handleAddTenant}
onSwitchTenant={handleSwitchTenant}
/>
)}
</main>
{/* Footer Branding (Mediusa / AI Pilots Ecosystem) */}
<footer className="bg-slate-950 border-t border-slate-800/80 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>
Mediusa Clinic OS Proprietary Practice Management Engine for AI Pilots / Mediusa
</span>
<span className="font-mono text-slate-400">
hello@aipilots.site HIPAA Certified Sub-30s Clinical Charting Standard
</span>
</div>
</footer>
</div>
);
}