style: convert UI palette to clean clinical hospital theme
This commit is contained in:
+31
-15
@@ -1,26 +1,42 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--background: #f8fafc; /* Hospital sterile off-white */
|
||||
--foreground: #0f172a; /* Deep clinical slate */
|
||||
--medical-blue: #0284c7; /* Classic Hospital / Medical Cross Blue */
|
||||
--medical-navy: #0f172a; /* Clinical Navy */
|
||||
--medical-slate: #f1f5f9; /* Examination table cool slate */
|
||||
--medical-border: #e2e8f0;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
.dark {
|
||||
--background: #0b1329; /* Hospital Night Shift Deep Navy */
|
||||
--foreground: #f8fafc;
|
||||
--medical-blue: #38bdf8;
|
||||
--medical-navy: #0f172a;
|
||||
--medical-slate: #1e293b;
|
||||
--medical-border: #334155;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* Custom Hospital Scrollbars */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: #475569;
|
||||
}
|
||||
|
||||
+4
-18
@@ -1,21 +1,10 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Geist, Geist_Mono } from 'next/font/google';
|
||||
import './globals.css';
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: '--font-geist-sans',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: '--font-geist-mono',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Mediusa Clinic OS — Next-Gen Practice Management Platform',
|
||||
title: 'Mediusa Clinic OS — Hospital-Grade Practice Management Platform',
|
||||
description:
|
||||
'High-speed, proprietary white-label medical practice management platform for chiropractors and wellness clinics. Scheduling, paperless intake, ambient AI clinical notes, and billing.',
|
||||
'Clinical-grade, high-speed practice management platform for chiropractors, physical therapy, and wellness clinics. HIPAA compliant scheduling, paperless intake, ambient AI clinical notes, and billing.',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -24,11 +13,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased dark`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col bg-slate-950 text-slate-100 font-sans selection:bg-teal-500 selection:text-slate-950">
|
||||
<html lang="en" className="h-full antialiased">
|
||||
<body className="min-h-full flex flex-col bg-slate-50 text-slate-900 font-sans selection:bg-sky-500 selection:text-white transition-colors duration-200">
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+58
-71
@@ -11,11 +11,10 @@ import {
|
||||
} from '@/lib/mock-data';
|
||||
import {
|
||||
ClinicTenant,
|
||||
Provider,
|
||||
Patient,
|
||||
Appointment,
|
||||
SoapNote,
|
||||
Superbill,
|
||||
Patient,
|
||||
} from '@/types/clinical';
|
||||
import { CalendarView } from '@/components/calendar/CalendarView';
|
||||
import { SoapChartEditor } from '@/components/charting/SoapChartEditor';
|
||||
@@ -28,34 +27,29 @@ import {
|
||||
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)
|
||||
// 'clinic' | 'patient' | 'superadmin'
|
||||
const [portalMode, setPortalMode] = useState<'clinic' | 'patient' | 'superadmin'>('clinic');
|
||||
|
||||
// Clinic Portal Sub-Tabs: 'calendar' | 'charting' | 'billing' | 'retention'
|
||||
// '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 [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]);
|
||||
|
||||
@@ -63,7 +57,6 @@ export default function Home() {
|
||||
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);
|
||||
@@ -167,39 +160,36 @@ export default function Home() {
|
||||
};
|
||||
|
||||
return (
|
||||
<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="min-h-screen bg-slate-50 text-slate-900 flex flex-col">
|
||||
{/* Top Hospital Command Header */}
|
||||
<header className="sticky top-0 z-40 bg-white border-b border-slate-200/90 px-4 lg:px-8 py-3 shadow-2xs">
|
||||
<div className="max-w-7xl mx-auto flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
{/* Brand & Clinic Domain Selector */}
|
||||
{/* Hospital Brand & Domain */}
|
||||
<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+
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="w-9 h-9 rounded-lg bg-sky-700 flex items-center justify-center font-black text-sm text-white shadow-xs">
|
||||
+M
|
||||
</span>
|
||||
<div>
|
||||
<div className="text-xs font-black tracking-tight text-white flex items-center gap-1.5">
|
||||
<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 bg-teal-500/20 text-teal-300 font-mono">
|
||||
v2.4 PRO
|
||||
<span className="text-[10px] px-2 py-0.5 rounded-full bg-sky-100 text-sky-800 font-bold border border-sky-200">
|
||||
CLINICAL v2.4
|
||||
</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" />
|
||||
<div className="text-xs text-slate-500 font-mono flex items-center gap-1 mt-0.5">
|
||||
<Globe className="w-3 h-3 text-sky-700" />
|
||||
https://{activeTenant.domain}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tenant Switcher Dropdown */}
|
||||
<div className="relative group">
|
||||
{/* Clinic Dropdown */}
|
||||
<div className="relative">
|
||||
<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"
|
||||
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) => (
|
||||
<option key={t.id} value={t.id}>
|
||||
@@ -207,19 +197,19 @@ export default function Home() {
|
||||
</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" />
|
||||
<ChevronDown className="w-3.5 h-3.5 text-slate-500 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">
|
||||
{/* Portal Switcher Tabs */}
|
||||
<div className="flex items-center gap-1 bg-slate-100 p-1 rounded-lg border border-slate-200 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 ${
|
||||
className={`px-3 py-1.5 rounded-md flex items-center gap-1.5 transition ${
|
||||
portalMode === 'clinic'
|
||||
? 'bg-teal-500 text-slate-950 font-bold shadow'
|
||||
: 'text-slate-400 hover:text-white'
|
||||
? 'bg-sky-700 text-white font-bold shadow-xs'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
<Stethoscope className="w-3.5 h-3.5" />
|
||||
@@ -229,10 +219,10 @@ export default function Home() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPortalMode('patient')}
|
||||
className={`px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition ${
|
||||
className={`px-3 py-1.5 rounded-md flex items-center gap-1.5 transition ${
|
||||
portalMode === 'patient'
|
||||
? 'bg-teal-500 text-slate-950 font-bold shadow'
|
||||
: 'text-slate-400 hover:text-white'
|
||||
? 'bg-sky-700 text-white font-bold shadow-xs'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
<Laptop className="w-3.5 h-3.5" />
|
||||
@@ -242,13 +232,13 @@ export default function Home() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPortalMode('superadmin')}
|
||||
className={`px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition ${
|
||||
className={`px-3 py-1.5 rounded-md flex items-center gap-1.5 transition ${
|
||||
portalMode === 'superadmin'
|
||||
? 'bg-indigo-600 text-white font-bold shadow'
|
||||
: 'text-slate-400 hover:text-white'
|
||||
? 'bg-slate-900 text-white font-bold shadow-xs'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
<Building2 className="w-3.5 h-3.5 text-indigo-300" />
|
||||
<Building2 className="w-3.5 h-3.5" />
|
||||
Mediusa Super-Admin
|
||||
</button>
|
||||
</div>
|
||||
@@ -256,65 +246,64 @@ export default function Home() {
|
||||
|
||||
{/* 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">
|
||||
<div className="max-w-7xl mx-auto flex items-center gap-2 mt-3 pt-2.5 border-t border-slate-100 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 ${
|
||||
className={`px-3 py-1.5 rounded-md font-semibold 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'
|
||||
? '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" />
|
||||
Fast Calendar & Schedule
|
||||
<Calendar className="w-3.5 h-3.5 text-sky-700" />
|
||||
Schedule & Encounters
|
||||
</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 ${
|
||||
className={`px-3 py-1.5 rounded-md font-semibold 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'
|
||||
? '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" />
|
||||
SOAP Chart & 2D Spine Map ({activePatient.firstName} {activePatient.lastName})
|
||||
<FileText className="w-3.5 h-3.5 text-sky-700" />
|
||||
Clinical SOAP & 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 ${
|
||||
className={`px-3 py-1.5 rounded-md font-semibold 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'
|
||||
? '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" />
|
||||
Superbills & Stripe Billing
|
||||
<DollarSign className="w-3.5 h-3.5 text-sky-700" />
|
||||
Superbills & Invoicing
|
||||
</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 ${
|
||||
className={`px-3 py-1.5 rounded-md font-semibold 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'
|
||||
? '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-400" />
|
||||
<Users className="w-3.5 h-3.5 text-amber-600" />
|
||||
Care Plan Retention Radar
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* Main App Container */}
|
||||
{/* Main 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' && (
|
||||
@@ -363,7 +352,6 @@ export default function Home() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* PORTAL MODE 2: White-Labeled Patient Booking & Intake */}
|
||||
{portalMode === 'patient' && (
|
||||
<PatientPortalView
|
||||
activeTenant={activeTenant}
|
||||
@@ -372,7 +360,6 @@ export default function Home() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* PORTAL MODE 3: Mediusa Super-Admin Command Center */}
|
||||
{portalMode === 'superadmin' && (
|
||||
<SuperAdminView
|
||||
tenants={tenants}
|
||||
@@ -382,13 +369,13 @@ export default function Home() {
|
||||
)}
|
||||
</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">
|
||||
{/* 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>
|
||||
Mediusa Clinic OS • Proprietary Practice Management Engine for AI Pilots / Mediusa
|
||||
<span className="font-medium text-slate-600">
|
||||
Mediusa Clinic OS • Hospital-Grade Practice Management Platform
|
||||
</span>
|
||||
<span className="font-mono text-slate-400">
|
||||
<span className="font-mono text-slate-500">
|
||||
hello@aipilots.site • HIPAA Certified • Sub-30s Clinical Charting Standard
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -9,12 +9,9 @@ import {
|
||||
Users,
|
||||
Plus,
|
||||
ExternalLink,
|
||||
ShieldCheck,
|
||||
CheckCircle2,
|
||||
Sparkles,
|
||||
Zap,
|
||||
Globe,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface SuperAdminViewProps {
|
||||
@@ -37,8 +34,8 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
const [isProvisioning, setIsProvisioning] = useState(false);
|
||||
const [provisionSuccess, setProvisionSuccess] = useState(false);
|
||||
|
||||
const totalMRR = tenants.reduce((sum, t) => sum + t.mrr, 0) + 3120; // 24 clinics total simulated
|
||||
const totalClinics = tenants.length + 21; // 24 active clinics
|
||||
const totalMRR = tenants.reduce((sum, t) => sum + t.mrr, 0) + 3120;
|
||||
const totalClinics = tenants.length + 21;
|
||||
|
||||
const handleNameChange = (val: string) => {
|
||||
setNewClinicName(val);
|
||||
@@ -68,8 +65,8 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
npi: newNpi || '1992019481',
|
||||
taxId: '82-9481920',
|
||||
logoText: newClinicName.slice(0, 10).toUpperCase(),
|
||||
brandColor: '#0d9488',
|
||||
accentColor: '#14b8a6',
|
||||
brandColor: '#0284c7',
|
||||
accentColor: '#38bdf8',
|
||||
plan: newPlan,
|
||||
mrr: newPlan === 'Starter' ? 99 : newPlan === 'Pro Clinic' ? 149 : 199,
|
||||
stripeConnected: true,
|
||||
@@ -90,29 +87,29 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Super-Admin Agency Banner */}
|
||||
<div className="bg-gradient-to-r from-slate-900 via-indigo-950/60 to-slate-900 border border-indigo-500/30 rounded-2xl p-6 shadow-2xl flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
{/* Super-Admin Header */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 shadow-xs flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="p-1.5 rounded-lg bg-indigo-500/20 text-indigo-400 border border-indigo-500/30">
|
||||
<Zap className="w-4 h-4" />
|
||||
<span className="p-1.5 rounded-md bg-sky-100 text-sky-800 font-bold text-xs">
|
||||
MEDIUSA HEALTHCARE OS
|
||||
</span>
|
||||
<span className="text-xs font-mono font-bold uppercase tracking-widest text-indigo-300">
|
||||
AI PILOTS / MEDIUSA AGENCY ENGINE
|
||||
<span className="text-xs font-mono font-bold text-slate-500 uppercase tracking-widest">
|
||||
AGENCY CONTROL TOWER
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-2xl font-black text-white mt-1">
|
||||
Clinic OS Multi-Tenant Command Center
|
||||
<h2 className="text-2xl font-black text-slate-900 mt-1">
|
||||
Healthcare Multi-Tenant Command Center
|
||||
</h2>
|
||||
<p className="text-xs text-slate-300 mt-1">
|
||||
Autonomous Practice Management SaaS Platform. Onboard 20–100+ chiropractic & wellness clinics with zero developer overhead.
|
||||
<p className="text-xs text-slate-600 mt-1">
|
||||
Manage practice instances across 24–100+ chiropractic & wellness clinics with automated onboarding.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowProvisionModal(true)}
|
||||
className="px-5 py-2.5 rounded-xl bg-gradient-to-r from-teal-500 to-emerald-500 hover:from-teal-400 hover:to-emerald-400 text-slate-950 text-xs font-black flex items-center gap-2 shadow-lg shadow-teal-500/20 transition shrink-0"
|
||||
className="px-5 py-2.5 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" />
|
||||
Provision New Clinic (60s)
|
||||
@@ -121,66 +118,66 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
|
||||
{/* High-Level SaaS Metrics */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="bg-slate-900 border border-slate-800 p-5 rounded-2xl shadow-xl">
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="bg-white border border-slate-200 p-5 rounded-xl shadow-xs">
|
||||
<div className="flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span>Current SaaS MRR</span>
|
||||
<DollarSign className="w-4 h-4 text-emerald-400" />
|
||||
<DollarSign className="w-4 h-4 text-emerald-700" />
|
||||
</div>
|
||||
<div className="text-3xl font-black text-white mt-2 font-mono">
|
||||
<div className="text-3xl font-black text-slate-900 mt-2 font-mono">
|
||||
${totalMRR.toLocaleString()}.00
|
||||
</div>
|
||||
<p className="text-[11px] text-emerald-400 mt-1 font-semibold">
|
||||
<p className="text-xs text-emerald-700 mt-1 font-semibold">
|
||||
+$596.00 added this month
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 border border-slate-800 p-5 rounded-2xl shadow-xl">
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="bg-white border border-slate-200 p-5 rounded-xl shadow-xs">
|
||||
<div className="flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span>Target MRR @ 100 Clinics</span>
|
||||
<TrendingUp className="w-4 h-4 text-teal-400" />
|
||||
<TrendingUp className="w-4 h-4 text-sky-700" />
|
||||
</div>
|
||||
<div className="text-3xl font-black text-teal-400 mt-2 font-mono">$17,900.00</div>
|
||||
<p className="text-[11px] text-slate-400 mt-1">Based on $179 avg clinic subscription</p>
|
||||
<div className="text-3xl font-black text-sky-800 mt-2 font-mono">$17,900.00</div>
|
||||
<p className="text-xs text-slate-500 mt-1">Based on $179 average clinic subscription</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 border border-slate-800 p-5 rounded-2xl shadow-xl">
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="bg-white border border-slate-200 p-5 rounded-xl shadow-xs">
|
||||
<div className="flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span>Active Clinic Tenants</span>
|
||||
<Building2 className="w-4 h-4 text-indigo-400" />
|
||||
<Building2 className="w-4 h-4 text-blue-700" />
|
||||
</div>
|
||||
<div className="text-3xl font-black text-white mt-2 font-mono">{totalClinics} Clinics</div>
|
||||
<p className="text-[11px] text-indigo-300 mt-1">100% white-label retention</p>
|
||||
<div className="text-3xl font-black text-slate-900 mt-2 font-mono">{totalClinics} Clinics</div>
|
||||
<p className="text-xs text-blue-700 mt-1 font-medium">100% white-label retention</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 border border-slate-800 p-5 rounded-2xl shadow-xl">
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="bg-white border border-slate-200 p-5 rounded-xl shadow-xs">
|
||||
<div className="flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span>Active Patients Managed</span>
|
||||
<Users className="w-4 h-4 text-sky-400" />
|
||||
<Users className="w-4 h-4 text-sky-700" />
|
||||
</div>
|
||||
<div className="text-3xl font-black text-sky-400 mt-2 font-mono">3,842</div>
|
||||
<p className="text-[11px] text-slate-400 mt-1">Across California & US clinics</p>
|
||||
<div className="text-3xl font-black text-sky-800 mt-2 font-mono">3,842</div>
|
||||
<p className="text-xs text-slate-500 mt-1">Across California & US clinics</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active Clinic Tenant Registry */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between pb-4 border-b border-slate-800 gap-2">
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 shadow-xs">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between pb-4 border-b border-slate-100 gap-2">
|
||||
<div>
|
||||
<h3 className="font-bold text-white text-base">Client Tenant Registry</h3>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
Each clinic operates on its isolated schema with custom branding and domain routing.
|
||||
<h3 className="font-bold text-slate-900 text-base">Client Tenant Registry</h3>
|
||||
<p className="text-xs text-slate-500 mt-0.5">
|
||||
Each clinic operates on an isolated schema with custom branding and domain routing.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs text-teal-400 font-mono">
|
||||
{tenants.length} Primary Demo Instances Loaded
|
||||
<span className="text-xs text-sky-800 font-mono font-bold">
|
||||
{tenants.length} Demo Instances Loaded
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-slate-800 mt-3">
|
||||
<div className="divide-y divide-slate-100 mt-3">
|
||||
{tenants.map((t) => (
|
||||
<div
|
||||
key={t.id}
|
||||
className="py-4 flex flex-col md:flex-row md:items-center justify-between gap-4 hover:bg-slate-800/30 px-3 rounded-xl transition"
|
||||
className="py-4 flex flex-col md:flex-row md:items-center justify-between gap-4 hover:bg-slate-50 px-3 rounded-lg transition"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -188,18 +185,18 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
className="w-3 h-3 rounded-full shrink-0"
|
||||
style={{ backgroundColor: t.brandColor }}
|
||||
/>
|
||||
<h4 className="font-bold text-white text-sm">{t.name}</h4>
|
||||
<span className="text-[10px] px-2 py-0.5 rounded-full bg-slate-800 text-slate-300 font-mono border border-slate-700">
|
||||
<h4 className="font-bold text-slate-900 text-sm">{t.name}</h4>
|
||||
<span className="text-[11px] px-2 py-0.5 rounded-full bg-slate-100 text-slate-700 font-mono border border-slate-200">
|
||||
{t.plan} (${t.mrr}/mo)
|
||||
</span>
|
||||
<span className="text-[10px] px-2 py-0.5 rounded-full bg-emerald-500/20 text-emerald-300 font-semibold border border-emerald-500/30">
|
||||
<span className="text-[11px] px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-800 font-semibold border border-emerald-200">
|
||||
Stripe Active
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-400 mt-1">
|
||||
<span className="flex items-center gap-1 text-teal-300 font-mono">
|
||||
<Globe className="w-3 h-3 text-teal-400" />
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-500 mt-1">
|
||||
<span className="flex items-center gap-1 text-sky-800 font-mono">
|
||||
<Globe className="w-3.5 h-3.5 text-sky-700" />
|
||||
https://{t.domain}
|
||||
</span>
|
||||
<span>•</span>
|
||||
@@ -215,7 +212,7 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSwitchTenant(t.id)}
|
||||
className="px-3.5 py-1.5 bg-teal-500 hover:bg-teal-400 text-slate-950 font-bold text-xs rounded-xl shadow transition flex items-center gap-1.5"
|
||||
className="px-3.5 py-1.5 bg-sky-700 hover:bg-sky-800 text-white font-bold text-xs rounded-lg shadow-xs transition flex items-center gap-1.5"
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
Launch Clinic OS
|
||||
@@ -226,94 +223,94 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 60-Second Instant Clinic Provisioning Modal */}
|
||||
{/* Provisioning Modal */}
|
||||
{showProvisionModal && (
|
||||
<div className="fixed inset-0 z-50 bg-slate-950/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div className="bg-slate-900 border border-slate-700 rounded-2xl max-w-lg w-full p-6 shadow-2xl animate-in fade-in zoom-in-95 duration-150 text-slate-100">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
||||
<div className="fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-xs flex items-center justify-center p-4">
|
||||
<div className="bg-white border border-slate-200 rounded-2xl max-w-lg w-full p-6 shadow-xl text-slate-900">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="p-2 rounded-xl bg-teal-500/20 text-teal-400">
|
||||
<span className="p-2 rounded-lg bg-sky-50 text-sky-700">
|
||||
<Sparkles className="w-5 h-5" />
|
||||
</span>
|
||||
<div>
|
||||
<h4 className="font-bold text-white text-sm">
|
||||
<h4 className="font-bold text-slate-900 text-sm">
|
||||
60-Second Instant Clinic Provisioning
|
||||
</h4>
|
||||
<p className="text-[11px] text-slate-400">
|
||||
Creates database tenant, seed templates & custom domain
|
||||
<p className="text-xs text-slate-500">
|
||||
Generates database tenant, seed templates & custom domain
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{provisionSuccess ? (
|
||||
<div className="py-8 text-center text-emerald-300">
|
||||
<CheckCircle2 className="w-12 h-12 text-emerald-400 mx-auto mb-3 animate-bounce" />
|
||||
<h4 className="text-lg font-bold text-white">Clinic Provisioned Successfully!</h4>
|
||||
<p className="text-xs text-slate-300 mt-1">
|
||||
Tenant schema generated. Redirecting to newly created white-labeled instance...
|
||||
<div className="py-8 text-center text-emerald-800">
|
||||
<CheckCircle2 className="w-12 h-12 text-emerald-600 mx-auto mb-3 animate-bounce" />
|
||||
<h4 className="text-lg font-bold text-slate-900">Clinic Provisioned Successfully!</h4>
|
||||
<p className="text-xs text-slate-600 mt-1">
|
||||
Tenant schema generated. Redirecting to instance...
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleProvisionSubmit} className="mt-4 space-y-4 text-xs">
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1 font-medium">Clinic Name</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Clinic Name</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
placeholder="e.g. Pacific Coast Chiropractic & Wellness"
|
||||
value={newClinicName}
|
||||
onChange={(e) => handleNameChange(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 focus:outline-none focus:border-teal-500"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1 font-medium">Subdomain Slug</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Subdomain Slug</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={newSlug}
|
||||
onChange={(e) => setNewSlug(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 font-mono focus:outline-none focus:border-teal-500"
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-lg p-3 text-slate-900 font-mono focus:bg-white focus:outline-none focus:border-sky-500"
|
||||
/>
|
||||
<span className="text-[10px] text-slate-500 mt-1 block">
|
||||
<span className="text-[11px] text-slate-500 mt-1 block">
|
||||
https://{newSlug || 'slug'}.mediusa.app
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1 font-medium">Lead Doctor Name</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Lead Doctor Name</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
placeholder="Dr. Samuel Wright, D.C."
|
||||
value={newDoctorName}
|
||||
onChange={(e) => setNewDoctorName(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 focus:outline-none focus:border-teal-500"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1 font-medium">Provider NPI</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Provider NPI</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="10-digit NPI"
|
||||
value={newNpi}
|
||||
onChange={(e) => setNewNpi(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 font-mono focus:outline-none focus:border-teal-500"
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-lg p-3 text-slate-900 font-mono focus:bg-white focus:outline-none focus:border-sky-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1 font-medium">Subscription Plan</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Subscription Plan</label>
|
||||
<select
|
||||
value={newPlan}
|
||||
onChange={(e: any) => setNewPlan(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 focus:outline-none focus:border-teal-500"
|
||||
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"
|
||||
>
|
||||
<option value="Starter">Starter Clinic ($99/mo)</option>
|
||||
<option value="Pro Clinic">Pro Clinic ($149/mo)</option>
|
||||
@@ -322,11 +319,11 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-slate-800 flex items-center justify-between">
|
||||
<div className="pt-4 border-t border-slate-200 flex items-center justify-between">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowProvisionModal(false)}
|
||||
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 font-semibold rounded-xl transition"
|
||||
className="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 font-semibold rounded-lg transition"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -334,12 +331,12 @@ export const SuperAdminView: React.FC<SuperAdminViewProps> = ({
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isProvisioning}
|
||||
className="px-5 py-2.5 bg-gradient-to-r from-teal-500 to-emerald-500 hover:from-teal-400 hover:to-emerald-400 text-slate-950 font-black rounded-xl shadow-lg shadow-teal-500/20 transition flex items-center gap-2 disabled:opacity-50"
|
||||
className="px-5 py-2.5 bg-sky-700 hover:bg-sky-800 text-white font-bold rounded-lg shadow-xs transition flex items-center gap-2 disabled:opacity-50"
|
||||
>
|
||||
{isProvisioning ? (
|
||||
<>
|
||||
<span className="w-3.5 h-3.5 border-2 border-slate-950 border-t-transparent rounded-full animate-spin" />
|
||||
Generating Tenant Schema...
|
||||
<span className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
Generating Tenant...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -4,7 +4,6 @@ import React, { useState } from 'react';
|
||||
import { Superbill, ClinicTenant } from '@/types/clinical';
|
||||
import { generateSuperbillPdf } from '@/lib/pdf-generator';
|
||||
import {
|
||||
FileText,
|
||||
Download,
|
||||
CreditCard,
|
||||
CheckCircle2,
|
||||
@@ -12,8 +11,7 @@ import {
|
||||
ArrowLeft,
|
||||
DollarSign,
|
||||
Printer,
|
||||
Sparkles,
|
||||
ExternalLink,
|
||||
FileCheck,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface SuperbillViewProps {
|
||||
@@ -53,33 +51,33 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Top Header Bar */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-xl flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div className="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">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
className="p-2 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-xl transition"
|
||||
className="p-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-lg transition"
|
||||
title="Back"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-lg font-black text-white">
|
||||
Superbill {superbill.invoiceNumber}
|
||||
<h3 className="text-lg font-bold text-slate-900">
|
||||
Superbill Statement {superbill.invoiceNumber}
|
||||
</h3>
|
||||
<span
|
||||
className={`text-xs px-2.5 py-0.5 rounded-full font-bold uppercase ${
|
||||
superbill.balanceDue === 0
|
||||
? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30'
|
||||
: 'bg-amber-500/20 text-amber-300 border border-amber-500/30'
|
||||
? 'bg-emerald-50 text-emerald-800 border border-emerald-200'
|
||||
: 'bg-amber-50 text-amber-800 border border-amber-200'
|
||||
}`}
|
||||
>
|
||||
{superbill.balanceDue === 0 ? 'Paid in Full' : 'Balance Due'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
Clean Medical Claim Standard (CMS-1500 / 837P Ready) for {superbill.patientName}
|
||||
<p className="text-xs text-slate-500 mt-0.5">
|
||||
Standard CMS-1500 Reimbursement Statement for {superbill.patientName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,9 +86,9 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDownloadPdf}
|
||||
className="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-white text-xs font-bold flex items-center gap-1.5 transition border border-slate-700"
|
||||
className="px-4 py-2 rounded-lg bg-sky-700 hover:bg-sky-800 text-white text-xs font-bold flex items-center gap-1.5 transition shadow-xs"
|
||||
>
|
||||
<Download className="w-4 h-4 text-teal-400" />
|
||||
<Download className="w-4 h-4" />
|
||||
Download PDF Superbill
|
||||
</button>
|
||||
|
||||
@@ -98,110 +96,110 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPaymentModal(true)}
|
||||
className="px-4 py-2 rounded-xl bg-teal-500 hover:bg-teal-400 text-slate-950 text-xs font-bold flex items-center gap-1.5 shadow-lg shadow-teal-500/20 transition"
|
||||
className="px-4 py-2 rounded-lg bg-emerald-700 hover:bg-emerald-800 text-white text-xs font-bold flex items-center gap-1.5 shadow-xs transition"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" />
|
||||
Collect with Stripe
|
||||
Collect Payment
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Printed Superbill Card Simulation */}
|
||||
<div className="bg-slate-950 border border-slate-800 rounded-2xl p-6 sm:p-8 shadow-2xl max-w-4xl mx-auto text-slate-200">
|
||||
<div className="bg-white border border-slate-200/90 rounded-xl p-6 sm:p-10 shadow-sm max-w-4xl mx-auto text-slate-800">
|
||||
{/* Clinic & Statement Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-start justify-between pb-6 border-b border-slate-800 gap-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-start justify-between pb-6 border-b border-slate-200 gap-4">
|
||||
<div>
|
||||
<span className="text-xs font-mono font-bold text-teal-400 uppercase tracking-widest">
|
||||
PRACTICE MANAGEMENT SUPERBILL
|
||||
<span className="text-xs font-mono font-bold text-sky-800 uppercase tracking-widest">
|
||||
OFFICIAL MEDICAL SUPERBILL & CLAIM
|
||||
</span>
|
||||
<h2 className="text-xl font-black text-white mt-1">{superbill.clinicName}</h2>
|
||||
<p className="text-xs text-slate-400 mt-1">{superbill.clinicAddress}</p>
|
||||
<p className="text-xs text-slate-400">
|
||||
Tax ID / EIN: <strong className="text-slate-300 font-mono">{superbill.clinicTaxId}</strong> • POS:{' '}
|
||||
<strong className="text-slate-300 font-mono">{superbill.posCode}</strong>
|
||||
<h2 className="text-xl font-black text-slate-900 mt-1">{superbill.clinicName}</h2>
|
||||
<p className="text-xs text-slate-600 mt-1">{superbill.clinicAddress}</p>
|
||||
<p className="text-xs text-slate-600">
|
||||
Tax ID / EIN: <strong className="text-slate-800 font-mono">{superbill.clinicTaxId}</strong> • POS:{' '}
|
||||
<strong className="text-slate-800 font-mono">{superbill.posCode}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="sm:text-right text-xs space-y-1">
|
||||
<div className="text-slate-400 font-medium">Invoice Number</div>
|
||||
<div className="font-mono text-sm font-bold text-white">{superbill.invoiceNumber}</div>
|
||||
<div className="text-slate-400 font-medium mt-2">Date of Service</div>
|
||||
<div className="font-mono text-slate-200">{superbill.dateOfService}</div>
|
||||
<div className="text-slate-500 font-medium">Invoice Number</div>
|
||||
<div className="font-mono text-sm font-bold text-slate-900">{superbill.invoiceNumber}</div>
|
||||
<div className="text-slate-500 font-medium mt-2">Date of Service</div>
|
||||
<div className="font-mono text-slate-800">{superbill.dateOfService}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Provider & Patient 2-Col Info */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 py-6 border-b border-slate-800 text-xs">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 py-6 border-b border-slate-200 text-xs">
|
||||
{/* Provider */}
|
||||
<div className="bg-slate-900/60 p-4 rounded-xl border border-slate-800/80">
|
||||
<h4 className="font-bold text-slate-300 uppercase tracking-wider mb-2">
|
||||
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200">
|
||||
<h4 className="font-bold text-slate-700 uppercase tracking-wider mb-2">
|
||||
Rendering Provider
|
||||
</h4>
|
||||
<div className="font-semibold text-white">{superbill.providerName}</div>
|
||||
<div className="text-slate-400 mt-1">
|
||||
<div className="font-bold text-slate-900 text-sm">{superbill.providerName}</div>
|
||||
<div className="text-slate-600 mt-1">
|
||||
National Provider Identifier (NPI):{' '}
|
||||
<strong className="text-teal-400 font-mono">{superbill.providerNpi}</strong>
|
||||
<strong className="text-sky-800 font-mono">{superbill.providerNpi}</strong>
|
||||
</div>
|
||||
<div className="text-slate-400 mt-1">Specialty: Chiropractic Clinical Biomechanics</div>
|
||||
<div className="text-slate-600 mt-1">Specialty: Chiropractic Clinical Biomechanics</div>
|
||||
</div>
|
||||
|
||||
{/* Patient */}
|
||||
<div className="bg-slate-900/60 p-4 rounded-xl border border-slate-800/80">
|
||||
<h4 className="font-bold text-slate-300 uppercase tracking-wider mb-2">
|
||||
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200">
|
||||
<h4 className="font-bold text-slate-700 uppercase tracking-wider mb-2">
|
||||
Patient Demographics
|
||||
</h4>
|
||||
<div className="font-semibold text-white">{superbill.patientName}</div>
|
||||
<div className="text-slate-400 mt-1">DOB: {superbill.patientDob}</div>
|
||||
<div className="text-slate-400 mt-1">Address: {superbill.patientAddress}</div>
|
||||
<div className="font-bold text-slate-900 text-sm">{superbill.patientName}</div>
|
||||
<div className="text-slate-600 mt-1">DOB: {superbill.patientDob}</div>
|
||||
<div className="text-slate-600 mt-1">Address: {superbill.patientAddress}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Diagnosis ICD-10 Section */}
|
||||
<div className="py-6 border-b border-slate-800">
|
||||
<h4 className="text-xs font-bold text-slate-300 uppercase tracking-wider mb-3">
|
||||
<div className="py-6 border-b border-slate-200">
|
||||
<h4 className="text-xs font-bold text-slate-700 uppercase tracking-wider mb-3">
|
||||
Primary Diagnosis Codes (ICD-10-CM)
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 text-xs">
|
||||
{superbill.icd10Codes.map((icd, index) => (
|
||||
<div
|
||||
key={icd.code}
|
||||
className="flex items-center gap-2 bg-slate-900/80 border border-slate-800 p-2.5 rounded-lg"
|
||||
className="flex items-center gap-2 bg-slate-50 border border-slate-200 p-2.5 rounded-lg"
|
||||
>
|
||||
<span className="w-5 h-5 rounded bg-teal-500/20 text-teal-300 font-bold flex items-center justify-center font-mono text-[11px]">
|
||||
<span className="w-5 h-5 rounded bg-sky-100 text-sky-800 font-bold flex items-center justify-center font-mono text-xs">
|
||||
{index + 1}
|
||||
</span>
|
||||
<span className="font-bold font-mono text-teal-300">{icd.code}</span>
|
||||
<span className="text-slate-400 truncate">{icd.description}</span>
|
||||
<span className="font-bold font-mono text-sky-800">{icd.code}</span>
|
||||
<span className="text-slate-600 truncate">{icd.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Procedures Table */}
|
||||
<div className="py-6 border-b border-slate-800">
|
||||
<h4 className="text-xs font-bold text-slate-300 uppercase tracking-wider mb-3">
|
||||
<div className="py-6 border-b border-slate-200">
|
||||
<h4 className="text-xs font-bold text-slate-700 uppercase tracking-wider mb-3">
|
||||
Rendered Procedures (CPT Codes)
|
||||
</h4>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs text-left">
|
||||
<thead>
|
||||
<tr className="bg-slate-900 text-slate-400 border-b border-slate-800">
|
||||
<th className="py-2.5 px-3 font-semibold">CPT Code</th>
|
||||
<th className="py-2.5 px-3 font-semibold">Service Description</th>
|
||||
<th className="py-2.5 px-3 font-semibold text-center">Units</th>
|
||||
<th className="py-2.5 px-3 font-semibold text-right">Fee</th>
|
||||
<th className="py-2.5 px-3 font-semibold text-right">Total</th>
|
||||
<tr className="bg-slate-100 text-slate-700 border-b border-slate-200">
|
||||
<th className="py-2.5 px-3 font-bold">CPT Code</th>
|
||||
<th className="py-2.5 px-3 font-bold">Service Description</th>
|
||||
<th className="py-2.5 px-3 font-bold text-center">Units</th>
|
||||
<th className="py-2.5 px-3 font-bold text-right">Fee</th>
|
||||
<th className="py-2.5 px-3 font-bold text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/80">
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
{superbill.items.map((item) => (
|
||||
<tr key={item.cptCode} className="hover:bg-slate-900/40">
|
||||
<td className="py-2.5 px-3 font-mono font-bold text-teal-300">{item.cptCode}</td>
|
||||
<td className="py-2.5 px-3 text-slate-300">{item.description}</td>
|
||||
<td className="py-2.5 px-3 text-center text-slate-400">{item.units}</td>
|
||||
<tr key={item.cptCode} className="hover:bg-slate-50">
|
||||
<td className="py-2.5 px-3 font-mono font-bold text-sky-800">{item.cptCode}</td>
|
||||
<td className="py-2.5 px-3 text-slate-800">{item.description}</td>
|
||||
<td className="py-2.5 px-3 text-center text-slate-600">{item.units}</td>
|
||||
<td className="py-2.5 px-3 text-right font-mono">${item.rate.toFixed(2)}</td>
|
||||
<td className="py-2.5 px-3 text-right font-mono font-bold text-white">
|
||||
<td className="py-2.5 px-3 text-right font-mono font-bold text-slate-900">
|
||||
${item.total.toFixed(2)}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -215,26 +213,26 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
<div className="pt-6 flex flex-col sm:flex-row sm:items-end justify-between gap-6">
|
||||
<div className="text-xs text-slate-500 max-w-sm">
|
||||
<p>
|
||||
This Superbill contains all required claim elements (CMS-1500 Boxes 21, 24, 25, 31, 33)
|
||||
for direct patient submission to their private health insurance or FSA/HSA reimbursement plan.
|
||||
This Superbill fulfills the standard medical reimbursement criteria for direct patient
|
||||
submission to commercial insurers, Medicare supplements, and HSA/FSA plans.
|
||||
</p>
|
||||
<p className="mt-2 text-slate-400">
|
||||
Payment Method: <strong className="text-teal-400">{superbill.paymentMethod}</strong>
|
||||
<p className="mt-2 text-slate-700">
|
||||
Payment Record: <strong className="text-sky-800">{superbill.paymentMethod}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 p-4 rounded-xl border border-slate-800 w-full sm:w-64 space-y-2 text-xs">
|
||||
<div className="flex justify-between text-slate-400">
|
||||
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200 w-full sm:w-64 space-y-2 text-xs">
|
||||
<div className="flex justify-between text-slate-600">
|
||||
<span>Total Charges:</span>
|
||||
<span className="font-mono text-white">${superbill.totalAmount.toFixed(2)}</span>
|
||||
<span className="font-mono text-slate-900 font-semibold">${superbill.totalAmount.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-slate-400">
|
||||
<div className="flex justify-between text-slate-600">
|
||||
<span>Patient Paid:</span>
|
||||
<span className="font-mono text-emerald-400">-${superbill.patientPaid.toFixed(2)}</span>
|
||||
<span className="font-mono text-emerald-700 font-semibold">-${superbill.patientPaid.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-slate-800 flex justify-between font-bold text-sm">
|
||||
<span className="text-white">Balance Due:</span>
|
||||
<span className="font-mono text-teal-400">${superbill.balanceDue.toFixed(2)}</span>
|
||||
<div className="pt-2 border-t border-slate-200 flex justify-between font-bold text-sm">
|
||||
<span className="text-slate-900">Balance Due:</span>
|
||||
<span className="font-mono text-sky-800">${superbill.balanceDue.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,39 +240,39 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
|
||||
{/* Stripe Payment Modal */}
|
||||
{showPaymentModal && (
|
||||
<div className="fixed inset-0 z-50 bg-slate-950/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div className="bg-slate-900 border border-slate-700 rounded-2xl max-w-md w-full p-6 shadow-2xl animate-in fade-in zoom-in-95 duration-150 text-slate-100">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
||||
<div className="fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-xs flex items-center justify-center p-4">
|
||||
<div className="bg-white border border-slate-200 rounded-2xl max-w-md w-full p-6 shadow-xl text-slate-900">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="p-2 rounded-xl bg-teal-500/20 text-teal-400">
|
||||
<span className="p-2 rounded-lg bg-sky-50 text-sky-700 border border-sky-100">
|
||||
<CreditCard className="w-5 h-5" />
|
||||
</span>
|
||||
<div>
|
||||
<h4 className="font-bold text-white text-sm">Stripe Connect Checkout</h4>
|
||||
<p className="text-[11px] text-slate-400">Direct to {activeTenant.name}</p>
|
||||
<h4 className="font-bold text-slate-900 text-sm">Hospital Payment Terminal</h4>
|
||||
<p className="text-xs text-slate-500">Connected to {activeTenant.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-3 text-xs">
|
||||
<div className="bg-slate-950 p-3 rounded-xl border border-slate-800 flex justify-between items-center">
|
||||
<span className="text-slate-400">Amount Due:</span>
|
||||
<span className="text-lg font-black text-teal-400 font-mono">
|
||||
<div className="bg-slate-50 p-3 rounded-lg border border-slate-200 flex justify-between items-center">
|
||||
<span className="text-slate-600 font-medium">Balance Due:</span>
|
||||
<span className="text-lg font-black text-sky-800 font-mono">
|
||||
${superbill.balanceDue.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{paymentSuccess ? (
|
||||
<div className="p-4 bg-emerald-500/20 border border-emerald-500/40 rounded-xl text-center text-emerald-300">
|
||||
<CheckCircle2 className="w-8 h-8 text-emerald-400 mx-auto mb-2 animate-bounce" />
|
||||
<div className="p-4 bg-emerald-50 border border-emerald-200 rounded-lg text-center text-emerald-800">
|
||||
<CheckCircle2 className="w-8 h-8 text-emerald-600 mx-auto mb-2 animate-bounce" />
|
||||
<div className="font-bold text-sm">Payment Processed Successfully!</div>
|
||||
<div className="text-[11px] text-emerald-400/80 mt-1">
|
||||
Stripe Charge ID: ch_3P18f92Ksk921
|
||||
<div className="text-xs text-emerald-700 mt-1 font-mono">
|
||||
Transaction ID: TX-90281-OK
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<label className="block text-[11px] font-semibold text-slate-400 uppercase tracking-wider">
|
||||
<label className="block text-xs font-bold text-slate-700 uppercase tracking-wider">
|
||||
Select Payment Method
|
||||
</label>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
@@ -282,14 +280,14 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
type="button"
|
||||
disabled={isProcessingStripe}
|
||||
onClick={() => handleStripePay('Stripe Card')}
|
||||
className="p-3 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-xl flex items-center justify-between transition"
|
||||
className="p-3 bg-white hover:bg-slate-50 border border-slate-200 rounded-lg flex items-center justify-between transition shadow-2xs"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-medium text-slate-200">
|
||||
<CreditCard className="w-4 h-4 text-teal-400" />
|
||||
<div className="flex items-center gap-2 font-semibold text-slate-800">
|
||||
<CreditCard className="w-4 h-4 text-sky-700" />
|
||||
Credit / Debit Card (Apple Pay)
|
||||
</div>
|
||||
<span className="text-[10px] bg-teal-500/20 text-teal-300 px-2 py-0.5 rounded font-mono">
|
||||
Instant
|
||||
<span className="text-[11px] bg-sky-50 text-sky-800 px-2 py-0.5 rounded font-mono font-bold border border-sky-200">
|
||||
Terminal
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -297,14 +295,14 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
type="button"
|
||||
disabled={isProcessingStripe}
|
||||
onClick={() => handleStripePay('HSA/FSA')}
|
||||
className="p-3 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-xl flex items-center justify-between transition"
|
||||
className="p-3 bg-white hover:bg-slate-50 border border-slate-200 rounded-lg flex items-center justify-between transition shadow-2xs"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-medium text-slate-200">
|
||||
<ShieldAlert className="w-4 h-4 text-sky-400" />
|
||||
<div className="flex items-center gap-2 font-semibold text-slate-800">
|
||||
<ShieldAlert className="w-4 h-4 text-blue-700" />
|
||||
HSA / FSA Health Benefit Card
|
||||
</div>
|
||||
<span className="text-[10px] bg-sky-500/20 text-sky-300 px-2 py-0.5 rounded font-mono">
|
||||
Tax-Free
|
||||
<span className="text-[11px] bg-blue-50 text-blue-800 px-2 py-0.5 rounded font-mono font-bold border border-blue-200">
|
||||
Tax-Exempt
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -312,14 +310,14 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
type="button"
|
||||
disabled={isProcessingStripe}
|
||||
onClick={() => handleStripePay('Cash')}
|
||||
className="p-3 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-xl flex items-center justify-between transition"
|
||||
className="p-3 bg-white hover:bg-slate-50 border border-slate-200 rounded-lg flex items-center justify-between transition shadow-2xs"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-medium text-slate-200">
|
||||
<DollarSign className="w-4 h-4 text-emerald-400" />
|
||||
Cash / In-Person Check
|
||||
<div className="flex items-center gap-2 font-semibold text-slate-800">
|
||||
<DollarSign className="w-4 h-4 text-emerald-700" />
|
||||
Cash / Clinic Receipt
|
||||
</div>
|
||||
<span className="text-[10px] bg-emerald-500/20 text-emerald-300 px-2 py-0.5 rounded font-mono">
|
||||
No Fee
|
||||
<span className="text-[11px] bg-emerald-50 text-emerald-800 px-2 py-0.5 rounded font-mono font-bold border border-emerald-200">
|
||||
Receipt
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -328,11 +326,11 @@ export const SuperbillView: React.FC<SuperbillViewProps> = ({
|
||||
</div>
|
||||
|
||||
{!paymentSuccess && (
|
||||
<div className="mt-5 pt-3 border-t border-slate-800 flex justify-end">
|
||||
<div className="mt-5 pt-3 border-t border-slate-100 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPaymentModal(false)}
|
||||
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 text-xs font-semibold rounded-xl transition"
|
||||
className="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
||||
@@ -8,12 +8,11 @@ import {
|
||||
User,
|
||||
Plus,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
PlayCircle,
|
||||
XCircle,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Filter,
|
||||
Stethoscope,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface CalendarViewProps {
|
||||
@@ -34,7 +33,6 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
|
||||
onNewAppointmentClick,
|
||||
}) => {
|
||||
const [selectedProviderId, setSelectedProviderId] = useState<string>('all');
|
||||
const [activeDateStr, setActiveDateStr] = useState<string>('2026-09-05');
|
||||
|
||||
const filteredAppointments = appointments.filter(
|
||||
(a) => selectedProviderId === 'all' || a.providerId === selectedProviderId
|
||||
@@ -44,75 +42,78 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return (
|
||||
<span className="px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 text-[11px] font-semibold flex items-center gap-1">
|
||||
<CheckCircle className="w-3 h-3" /> Completed
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-emerald-50 text-emerald-700 border border-emerald-200 text-xs font-semibold flex items-center gap-1">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-emerald-600" /> Completed
|
||||
</span>
|
||||
);
|
||||
case 'in_room':
|
||||
return (
|
||||
<span className="px-2 py-0.5 rounded-md bg-amber-500/10 text-amber-400 border border-amber-500/20 text-[11px] font-semibold flex items-center gap-1 animate-pulse">
|
||||
<PlayCircle className="w-3 h-3" /> In Operatory
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-amber-50 text-amber-700 border border-amber-200 text-xs font-semibold flex items-center gap-1 animate-pulse">
|
||||
<PlayCircle className="w-3.5 h-3.5 text-amber-600" /> In Operatory
|
||||
</span>
|
||||
);
|
||||
case 'arrived':
|
||||
return (
|
||||
<span className="px-2 py-0.5 rounded-md bg-sky-500/10 text-sky-400 border border-sky-500/20 text-[11px] font-semibold flex items-center gap-1">
|
||||
<User className="w-3 h-3" /> Arrived / Waiting
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-sky-50 text-sky-700 border border-sky-200 text-xs font-semibold flex items-center gap-1">
|
||||
<User className="w-3.5 h-3.5 text-sky-600" /> Arrived / Waiting
|
||||
</span>
|
||||
);
|
||||
case 'confirmed':
|
||||
return (
|
||||
<span className="px-2 py-0.5 rounded-md bg-teal-500/10 text-teal-400 border border-teal-500/20 text-[11px] font-semibold flex items-center gap-1">
|
||||
<CalendarIcon className="w-3 h-3" /> Confirmed
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-blue-50 text-blue-700 border border-blue-200 text-xs font-semibold flex items-center gap-1">
|
||||
<CalendarIcon className="w-3.5 h-3.5 text-blue-600" /> Confirmed
|
||||
</span>
|
||||
);
|
||||
case 'booked':
|
||||
return (
|
||||
<span className="px-2 py-0.5 rounded-md bg-slate-800 text-slate-300 border border-slate-700 text-[11px] font-semibold">
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-slate-100 text-slate-700 border border-slate-200 text-xs font-medium">
|
||||
Booked
|
||||
</span>
|
||||
);
|
||||
case 'no_show':
|
||||
return (
|
||||
<span className="px-2 py-0.5 rounded-md bg-red-500/10 text-red-400 border border-red-500/20 text-[11px] font-semibold flex items-center gap-1">
|
||||
<XCircle className="w-3 h-3" /> No Show
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-red-50 text-red-700 border border-red-200 text-xs font-semibold flex items-center gap-1">
|
||||
<XCircle className="w-3.5 h-3.5 text-red-600" /> No Show
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Action Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 bg-slate-900 border border-slate-800 p-4 rounded-2xl">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center bg-slate-800/80 rounded-xl p-1 border border-slate-700/60">
|
||||
<div className="space-y-5">
|
||||
{/* 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 */}
|
||||
<div className="flex items-center bg-slate-100/90 rounded-lg p-1 border border-slate-200">
|
||||
<button
|
||||
type="button"
|
||||
className="p-1.5 hover:bg-slate-700 rounded-lg text-slate-400 hover:text-white transition"
|
||||
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-white tracking-wide">
|
||||
<span className="px-3 text-xs font-bold text-slate-800 tracking-wide">
|
||||
Saturday, September 5, 2026
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="p-1.5 hover:bg-slate-700 rounded-lg text-slate-400 hover:text-white transition"
|
||||
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>
|
||||
</div>
|
||||
|
||||
{/* Provider Filter */}
|
||||
<div className="flex items-center gap-1 bg-slate-800/50 p-1 rounded-xl border border-slate-700/60 text-xs">
|
||||
{/* 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
|
||||
type="button"
|
||||
onClick={() => setSelectedProviderId('all')}
|
||||
className={`px-3 py-1 rounded-lg font-medium transition ${
|
||||
className={`px-3 py-1 rounded-md font-semibold transition ${
|
||||
selectedProviderId === 'all'
|
||||
? 'bg-teal-500 text-slate-950 font-bold shadow'
|
||||
: 'text-slate-400 hover:text-white'
|
||||
? 'bg-white text-sky-800 shadow-xs border border-slate-200/60'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
All Providers
|
||||
@@ -122,10 +123,10 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedProviderId(p.id)}
|
||||
className={`px-3 py-1 rounded-lg font-medium transition ${
|
||||
className={`px-3 py-1 rounded-md font-semibold transition ${
|
||||
selectedProviderId === p.id
|
||||
? 'bg-teal-500 text-slate-950 font-bold shadow'
|
||||
: 'text-slate-400 hover:text-white'
|
||||
? 'bg-white text-sky-800 shadow-xs border border-slate-200/60'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
{p.name}
|
||||
@@ -137,66 +138,71 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onNewAppointmentClick}
|
||||
className="px-4 py-2 rounded-xl bg-teal-500 hover:bg-teal-400 text-slate-950 text-xs font-bold flex items-center gap-2 shadow-lg shadow-teal-500/20 transition shrink-0"
|
||||
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" />
|
||||
Book New Patient
|
||||
Schedule Patient
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Grid of Appointments */}
|
||||
{/* 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-slate-900 border border-slate-800 hover:border-teal-500/40 rounded-2xl p-4 shadow-xl transition-all duration-200 group flex flex-col justify-between"
|
||||
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>
|
||||
{/* Top Row: Time & Status */}
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
||||
{/* 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="p-1.5 rounded-lg bg-teal-500/10 text-teal-400 font-mono text-xs font-bold">
|
||||
<Clock className="w-3.5 h-3.5 inline mr-1" />
|
||||
<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-400 font-medium">
|
||||
<span className="text-[11px] text-slate-500 font-medium">
|
||||
{apt.durationMinutes} min
|
||||
</span>
|
||||
</div>
|
||||
{getStatusBadge(apt.status)}
|
||||
</div>
|
||||
|
||||
{/* Patient & Service Details */}
|
||||
{/* Patient & Examination Room */}
|
||||
<div className="mt-3">
|
||||
<h4 className="text-sm font-bold text-white group-hover:text-teal-300 transition">
|
||||
{apt.patientName}
|
||||
</h4>
|
||||
<p className="text-xs text-teal-400/90 font-medium mt-0.5">
|
||||
<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-[11px] text-slate-400 mt-2">
|
||||
<span>Provider: {apt.providerName}</span>
|
||||
<div className="flex items-center gap-2 text-xs text-slate-500 mt-2">
|
||||
<span>Attending: {apt.providerName}</span>
|
||||
<span>•</span>
|
||||
<span>{apt.room}</span>
|
||||
<span className="font-medium text-slate-700">{apt.room}</span>
|
||||
</div>
|
||||
|
||||
{apt.notes && (
|
||||
<div className="mt-2.5 p-2 bg-slate-950/60 rounded-lg border border-slate-800/80 text-[11px] text-slate-300 italic">
|
||||
<div className="mt-2.5 p-2 bg-slate-50 rounded-lg border border-slate-200 text-xs text-slate-700 italic">
|
||||
"{apt.notes}"
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Bar */}
|
||||
<div className="mt-4 pt-3 border-t border-slate-800 flex items-center justify-between gap-2">
|
||||
{/* 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-teal-500/10 hover:bg-teal-500/20 text-teal-300 text-xs font-semibold border border-teal-500/30 transition flex items-center gap-1.5"
|
||||
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"
|
||||
>
|
||||
<PlayCircle className="w-3.5 h-3.5" />
|
||||
Open SOAP Chart
|
||||
<Stethoscope className="w-3.5 h-3.5 text-sky-700" />
|
||||
Open Clinical Chart
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -204,7 +210,7 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onUpdateStatus(apt.id, 'arrived')}
|
||||
className="px-2.5 py-1 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 text-[11px] font-medium"
|
||||
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>
|
||||
@@ -213,16 +219,16 @@ export const CalendarView: React.FC<CalendarViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onUpdateStatus(apt.id, 'in_room')}
|
||||
className="px-2.5 py-1 rounded-lg bg-amber-500/20 hover:bg-amber-500/30 text-amber-300 text-[11px] font-medium border border-amber-500/30"
|
||||
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"
|
||||
>
|
||||
Move to Table
|
||||
Place in Room
|
||||
</button>
|
||||
)}
|
||||
{apt.status === 'in_room' && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onUpdateStatus(apt.id, 'completed')}
|
||||
className="px-2.5 py-1 rounded-lg bg-emerald-500/20 hover:bg-emerald-500/30 text-emerald-300 text-[11px] font-medium border border-emerald-500/30"
|
||||
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>
|
||||
|
||||
@@ -7,16 +7,12 @@ import { AmbientAudioRecorder } from '@/components/ui/AmbientAudioRecorder';
|
||||
import { STANDARD_CPT_CODES, STANDARD_ICD10_CODES } from '@/lib/mock-data';
|
||||
import {
|
||||
FileText,
|
||||
Sparkles,
|
||||
CheckCircle,
|
||||
Copy,
|
||||
PenTool,
|
||||
Save,
|
||||
DollarSign,
|
||||
ChevronLeft,
|
||||
Tag,
|
||||
ShieldCheck,
|
||||
AlertCircle,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface SoapChartEditorProps {
|
||||
@@ -61,16 +57,15 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
]
|
||||
);
|
||||
const [selectedIcd10, setSelectedIcd10] = useState<Icd10CodeItem[]>(
|
||||
initialSoapNote?.icd10Codes || [STANDARD_ICD10_CODES[2], STANDARD_ICD10_CODES[5]] // M99.03 & M54.50
|
||||
initialSoapNote?.icd10Codes || [STANDARD_ICD10_CODES[2], STANDARD_ICD10_CODES[5]]
|
||||
);
|
||||
const [selectedCpt, setSelectedCpt] = useState<CptCodeItem[]>(
|
||||
initialSoapNote?.cptCodes || [STANDARD_CPT_CODES[0], STANDARD_CPT_CODES[3]] // 98940 & 97140
|
||||
initialSoapNote?.cptCodes || [STANDARD_CPT_CODES[0], STANDARD_CPT_CODES[3]]
|
||||
);
|
||||
const [vasScore, setVasScore] = useState<number>(initialSoapNote?.vasScore || 3);
|
||||
const [isSigned, setIsSigned] = useState<boolean>(initialSoapNote?.status === 'signed');
|
||||
const [signedTimestamp, setSignedTimestamp] = useState<string | undefined>(initialSoapNote?.signedAt);
|
||||
|
||||
// Ambient AI Scribe callback
|
||||
const handleApplyExtractedSoap = (data: {
|
||||
vasScore: number;
|
||||
subjective: string;
|
||||
@@ -91,11 +86,9 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
setAdjustments(data.spinalAdjustments);
|
||||
}
|
||||
|
||||
// Auto map ICD10
|
||||
const matchedIcds = STANDARD_ICD10_CODES.filter((icd) => data.icd10.includes(icd.code));
|
||||
if (matchedIcds.length > 0) setSelectedIcd10(matchedIcds);
|
||||
|
||||
// Auto map CPT
|
||||
const matchedCpts = STANDARD_CPT_CODES.filter((cpt) => data.cpt.includes(cpt.code));
|
||||
if (matchedCpts.length > 0) setSelectedCpt(matchedCpts);
|
||||
};
|
||||
@@ -174,13 +167,13 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Top Breadcrumb & Patient Header */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-xl flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Top Patient Header Bar */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-5 shadow-xs flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBackToCalendar}
|
||||
className="p-2 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-xl transition"
|
||||
className="p-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-lg transition"
|
||||
title="Back to Calendar"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
@@ -188,29 +181,29 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-lg font-black text-white">
|
||||
<h3 className="text-lg font-bold text-slate-900">
|
||||
{patient.firstName} {patient.lastName}
|
||||
</h3>
|
||||
<span className="text-xs px-2.5 py-0.5 rounded-full bg-teal-500/20 text-teal-300 border border-teal-500/30 font-semibold">
|
||||
<span className="text-xs px-2.5 py-0.5 rounded-full bg-sky-50 text-sky-800 border border-sky-200 font-semibold">
|
||||
Care Plan: Visit {patient.carePlan.completedVisits + 1} of {patient.carePlan.totalVisits}
|
||||
</span>
|
||||
{isSigned ? (
|
||||
<span className="text-xs px-2.5 py-0.5 rounded-full bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 font-semibold flex items-center gap-1">
|
||||
<span className="text-xs px-2.5 py-0.5 rounded-full bg-emerald-50 text-emerald-700 border border-emerald-200 font-semibold flex items-center gap-1">
|
||||
<ShieldCheck className="w-3.5 h-3.5" /> Signed & Locked
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs px-2.5 py-0.5 rounded-full bg-amber-500/20 text-amber-300 border border-amber-500/30 font-semibold">
|
||||
<span className="text-xs px-2.5 py-0.5 rounded-full bg-amber-50 text-amber-700 border border-amber-200 font-semibold">
|
||||
In Progress (Draft)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-400 mt-1">
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-500 mt-1">
|
||||
<span>DOB: {patient.dob}</span>
|
||||
<span>•</span>
|
||||
<span>Insurance: {patient.insuranceName}</span>
|
||||
<span>•</span>
|
||||
<span className="text-teal-400 font-medium">Attending: {provider.name}</span>
|
||||
<span className="text-sky-800 font-medium">Attending: {provider.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -220,9 +213,9 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCloneLastNote}
|
||||
className="px-3 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 text-xs font-semibold flex items-center gap-1.5 transition"
|
||||
className="px-3 py-2 rounded-lg bg-slate-100 hover:bg-slate-200 text-slate-700 border border-slate-200 text-xs font-semibold flex items-center gap-1.5 transition"
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
<Copy className="w-3.5 h-3.5 text-slate-600" />
|
||||
Clone Last Note
|
||||
</button>
|
||||
|
||||
@@ -230,20 +223,20 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
type="button"
|
||||
onClick={handleSignChart}
|
||||
disabled={isSigned}
|
||||
className={`px-4 py-2 rounded-xl text-xs font-bold flex items-center gap-1.5 shadow-lg transition ${
|
||||
className={`px-4 py-2 rounded-lg text-xs font-bold flex items-center gap-1.5 transition shadow-xs ${
|
||||
isSigned
|
||||
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/40 cursor-default'
|
||||
: 'bg-teal-500 hover:bg-teal-400 text-slate-950 shadow-teal-500/20'
|
||||
? 'bg-emerald-50 text-emerald-700 border border-emerald-200 cursor-default'
|
||||
: 'bg-emerald-700 hover:bg-emerald-800 text-white'
|
||||
}`}
|
||||
>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
{isSigned ? 'Signed by Doctor' : 'Sign & Complete Note'}
|
||||
{isSigned ? 'Signed & Locked' : 'Sign & Complete Note'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCreateSuperbillClick}
|
||||
className="px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold flex items-center gap-1.5 shadow-lg shadow-indigo-600/20 transition"
|
||||
className="px-4 py-2 rounded-lg bg-sky-700 hover:bg-sky-800 text-white text-xs font-bold flex items-center gap-1.5 shadow-xs transition"
|
||||
>
|
||||
<DollarSign className="w-4 h-4" />
|
||||
Generate Superbill
|
||||
@@ -251,56 +244,57 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ambient AI Clinical Scribe Section */}
|
||||
{/* Ambient AI Scribe */}
|
||||
<AmbientAudioRecorder onApplyExtractedSoap={handleApplyExtractedSoap} />
|
||||
|
||||
{/* 2D Spine Visualizer Subluxation Clicker */}
|
||||
{/* Spine Subluxation Visualizer */}
|
||||
<SpineVisualizer
|
||||
adjustments={adjustments}
|
||||
onToggleAdjustment={handleToggleAdjustment}
|
||||
readOnly={isSigned}
|
||||
/>
|
||||
|
||||
{/* Structured SOAP Fields Grid */}
|
||||
{/* Structured SOAP Fields */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
{/* Subjective */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-4 shadow-xl flex flex-col justify-between">
|
||||
{/* S */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-4 shadow-xs flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-800 mb-2">
|
||||
<span className="text-xs font-bold text-teal-400 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span className="w-5 h-5 rounded-md bg-teal-500/20 flex items-center justify-center font-black text-teal-300">
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-100 mb-2">
|
||||
<span className="text-xs font-bold text-sky-800 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span className="w-5 h-5 rounded bg-sky-100 text-sky-800 flex items-center justify-center font-bold">
|
||||
S
|
||||
</span>
|
||||
Subjective Complaint & History
|
||||
Subjective History & Complaint
|
||||
</span>
|
||||
<span className="text-xs font-mono font-semibold text-slate-600 bg-slate-100 px-2 py-0.5 rounded">
|
||||
VAS: {vasScore}/10
|
||||
</span>
|
||||
<span className="text-xs font-mono text-slate-400">VAS: {vasScore}/10</span>
|
||||
</div>
|
||||
<textarea
|
||||
rows={4}
|
||||
value={subjective}
|
||||
disabled={isSigned}
|
||||
onChange={(e) => setSubjective(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-xs text-slate-200 focus:outline-none focus:border-teal-500 resize-none font-sans"
|
||||
placeholder="Patient reports symptoms, VAS rating, changes since last visit..."
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-lg p-3 text-xs text-slate-800 focus:bg-white focus:outline-none focus:border-sky-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 mt-2">
|
||||
<div className="text-[11px] text-slate-400 mt-2">
|
||||
Auto-populated from Intake and Ambient Scribe.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Objective */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-4 shadow-xl flex flex-col justify-between">
|
||||
{/* O */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-4 shadow-xs flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-800 mb-2">
|
||||
<span className="text-xs font-bold text-teal-400 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span className="w-5 h-5 rounded-md bg-teal-500/20 flex items-center justify-center font-black text-teal-300">
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-100 mb-2">
|
||||
<span className="text-xs font-bold text-sky-800 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span className="w-5 h-5 rounded bg-sky-100 text-sky-800 flex items-center justify-center font-bold">
|
||||
O
|
||||
</span>
|
||||
Objective Findings & Palpation
|
||||
Objective Exam & Palpation
|
||||
</span>
|
||||
<span className="text-xs font-mono text-slate-400">
|
||||
{adjustments.length} Segment(s) Listed
|
||||
<span className="text-xs font-mono font-semibold text-slate-600 bg-slate-100 px-2 py-0.5 rounded">
|
||||
{adjustments.length} Segment(s)
|
||||
</span>
|
||||
</div>
|
||||
<textarea
|
||||
@@ -308,21 +302,20 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
value={objective}
|
||||
disabled={isSigned}
|
||||
onChange={(e) => setObjective(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-xs text-slate-200 focus:outline-none focus:border-teal-500 resize-none font-sans"
|
||||
placeholder="Physical findings, spinal fixations, muscle spasms, range of motion..."
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-lg p-3 text-xs text-slate-800 focus:bg-white focus:outline-none focus:border-sky-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 mt-2">
|
||||
Vertebrae selected in 2D Spine Map sync automatically.
|
||||
<div className="text-[11px] text-slate-400 mt-2">
|
||||
Vertebrae selected in Spine Map sync automatically.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Assessment */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-4 shadow-xl flex flex-col justify-between">
|
||||
{/* A */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-4 shadow-xs flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-800 mb-2">
|
||||
<span className="text-xs font-bold text-teal-400 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span className="w-5 h-5 rounded-md bg-teal-500/20 flex items-center justify-center font-black text-teal-300">
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-100 mb-2">
|
||||
<span className="text-xs font-bold text-sky-800 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span className="w-5 h-5 rounded bg-sky-100 text-sky-800 flex items-center justify-center font-bold">
|
||||
A
|
||||
</span>
|
||||
Clinical Assessment & Progress
|
||||
@@ -333,24 +326,23 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
value={assessment}
|
||||
disabled={isSigned}
|
||||
onChange={(e) => setAssessment(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-xs text-slate-200 focus:outline-none focus:border-teal-500 resize-none font-sans"
|
||||
placeholder="Clinical impression, diagnosis evaluation, treatment response..."
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-lg p-3 text-xs text-slate-800 focus:bg-white focus:outline-none focus:border-sky-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 mt-2">
|
||||
Maps to ICD-10 diagnostic codes below.
|
||||
<div className="text-[11px] text-slate-400 mt-2">
|
||||
Mapped to ICD-10 diagnostic codes below.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plan */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-4 shadow-xl flex flex-col justify-between">
|
||||
{/* P */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-4 shadow-xs flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-800 mb-2">
|
||||
<span className="text-xs font-bold text-teal-400 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span className="w-5 h-5 rounded-md bg-teal-500/20 flex items-center justify-center font-black text-teal-300">
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-100 mb-2">
|
||||
<span className="text-xs font-bold text-sky-800 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span className="w-5 h-5 rounded bg-sky-100 text-sky-800 flex items-center justify-center font-bold">
|
||||
P
|
||||
</span>
|
||||
Treatment Plan & Home Care
|
||||
Treatment Plan & Modalities
|
||||
</span>
|
||||
</div>
|
||||
<textarea
|
||||
@@ -358,26 +350,27 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
value={plan}
|
||||
disabled={isSigned}
|
||||
onChange={(e) => setPlan(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-xs text-slate-200 focus:outline-none focus:border-teal-500 resize-none font-sans"
|
||||
placeholder="Techniques delivered, rehabilitation modalities, next visit schedule..."
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-lg p-3 text-xs text-slate-800 focus:bg-white focus:outline-none focus:border-sky-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 mt-2">
|
||||
Determines CPT procedure line items on Superbill.
|
||||
<div className="text-[11px] text-slate-400 mt-2">
|
||||
Populates CPT procedure line items on Superbill.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ICD-10 & CPT Billing Code Selectors */}
|
||||
{/* ICD-10 & CPT Selectors */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
{/* ICD-10 Codes */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-4 shadow-xl">
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-800 mb-3">
|
||||
<span className="text-xs font-bold text-slate-200 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<Tag className="w-3.5 h-3.5 text-teal-400" />
|
||||
{/* ICD-10 */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-4 shadow-xs">
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-100 mb-3">
|
||||
<span className="text-xs font-bold text-slate-800 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<Tag className="w-3.5 h-3.5 text-sky-700" />
|
||||
ICD-10 Diagnostic Codes
|
||||
</span>
|
||||
<span className="text-xs text-teal-400 font-mono">{selectedIcd10.length} Selected</span>
|
||||
<span className="text-xs text-sky-800 font-mono font-bold">
|
||||
{selectedIcd10.length} Selected
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5 max-h-40 overflow-y-auto pr-1">
|
||||
{STANDARD_ICD10_CODES.map((item) => {
|
||||
@@ -394,10 +387,10 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
setSelectedIcd10((prev) => [...prev, item]);
|
||||
}
|
||||
}}
|
||||
className={`px-2.5 py-1.5 rounded-lg text-xs font-medium text-left transition ${
|
||||
className={`px-3 py-1.5 rounded-md text-xs font-medium text-left transition ${
|
||||
active
|
||||
? 'bg-teal-500 text-slate-950 font-bold shadow-sm'
|
||||
: 'bg-slate-800 text-slate-300 hover:bg-slate-700'
|
||||
? 'bg-sky-700 text-white font-bold shadow-xs'
|
||||
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
<span className="font-mono font-bold mr-1">{item.code}</span>
|
||||
@@ -409,14 +402,14 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
</div>
|
||||
|
||||
{/* CPT Codes */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-4 shadow-xl">
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-800 mb-3">
|
||||
<span className="text-xs font-bold text-slate-200 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<DollarSign className="w-3.5 h-3.5 text-teal-400" />
|
||||
CPT Procedure Codes & Fee Schedule
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-4 shadow-xs">
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-100 mb-3">
|
||||
<span className="text-xs font-bold text-slate-800 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<DollarSign className="w-3.5 h-3.5 text-emerald-600" />
|
||||
CPT Procedure Codes
|
||||
</span>
|
||||
<span className="text-xs text-teal-400 font-mono">
|
||||
Total: ${selectedCpt.reduce((sum, c) => sum + c.fee, 0)}
|
||||
<span className="text-xs text-emerald-800 font-mono font-bold">
|
||||
Fee Total: ${selectedCpt.reduce((sum, c) => sum + c.fee, 0)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5 max-h-40 overflow-y-auto pr-1">
|
||||
@@ -434,17 +427,19 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
setSelectedCpt((prev) => [...prev, item]);
|
||||
}
|
||||
}}
|
||||
className={`px-2.5 py-1.5 rounded-lg text-xs font-medium text-left transition flex items-center justify-between gap-2 ${
|
||||
className={`px-3 py-1.5 rounded-md text-xs font-medium text-left transition flex items-center justify-between gap-2 ${
|
||||
active
|
||||
? 'bg-indigo-600 text-white font-bold shadow-sm'
|
||||
: 'bg-slate-800 text-slate-300 hover:bg-slate-700'
|
||||
? 'bg-emerald-700 text-white font-bold shadow-xs'
|
||||
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
<span className="font-mono font-bold mr-1">{item.code}</span>
|
||||
<span className="text-[11px] opacity-90 truncate">{item.description}</span>
|
||||
</div>
|
||||
<span className="font-mono text-teal-300 shrink-0 font-bold">${item.fee}</span>
|
||||
<span className="font-mono text-emerald-800 bg-white/20 px-1.5 rounded shrink-0 font-bold">
|
||||
${item.fee}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -454,12 +449,12 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
|
||||
{/* Signature Attestation Footer */}
|
||||
{isSigned && (
|
||||
<div className="p-4 bg-emerald-500/10 border border-emerald-500/30 rounded-2xl flex items-center justify-between text-xs text-emerald-300">
|
||||
<div className="p-4 bg-emerald-50 border border-emerald-200 rounded-xl flex items-center justify-between text-xs text-emerald-900">
|
||||
<div className="flex items-center gap-2">
|
||||
<ShieldCheck className="w-5 h-5 text-emerald-400" />
|
||||
<ShieldCheck className="w-5 h-5 text-emerald-600" />
|
||||
<div>
|
||||
<strong>Electronically Signed by:</strong> {provider.name}, {provider.credentials} (NPI {provider.npi})
|
||||
<div className="text-[11px] text-emerald-400/80 mt-0.5">
|
||||
<div className="text-[11px] text-emerald-700 mt-0.5">
|
||||
Timestamp: {signedTimestamp || '2026-09-05T09:25:00-07:00'} • Immutable Legal Record
|
||||
</div>
|
||||
</div>
|
||||
@@ -467,7 +462,7 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCreateSuperbillClick}
|
||||
className="px-3 py-1.5 bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-bold rounded-lg transition"
|
||||
className="px-3.5 py-1.5 bg-emerald-700 hover:bg-emerald-800 text-white font-bold rounded-lg transition"
|
||||
>
|
||||
Open Superbill
|
||||
</button>
|
||||
|
||||
@@ -5,11 +5,8 @@ import { ClinicTenant, Provider } from '@/types/clinical';
|
||||
import { BodyPainMap } from '@/components/ui/BodyPainMap';
|
||||
import { DigitalSignaturePad } from '@/components/ui/DigitalSignaturePad';
|
||||
import {
|
||||
Calendar,
|
||||
Clock,
|
||||
User,
|
||||
CheckCircle2,
|
||||
Sparkles,
|
||||
MapPin,
|
||||
Phone,
|
||||
ArrowRight,
|
||||
@@ -29,13 +26,11 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
}) => {
|
||||
const [step, setStep] = useState<'service' | 'intake' | 'confirmed'>('service');
|
||||
|
||||
// Booking selections
|
||||
const [selectedProvider, setSelectedProvider] = useState<Provider>(providers[0]);
|
||||
const [selectedService, setSelectedService] = useState('Initial Chiropractic Exam & Adjustment');
|
||||
const [selectedDate, setSelectedDate] = useState('2026-09-08');
|
||||
const [selectedTime, setSelectedTime] = useState('10:30 AM');
|
||||
|
||||
// Intake form state
|
||||
const [fullName, setFullName] = useState('Jessica Morales');
|
||||
const [dob, setDob] = useState('1991-08-14');
|
||||
const [phone, setPhone] = useState('(555) 302-9912');
|
||||
@@ -81,87 +76,83 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
status: 'booked',
|
||||
room: 'Table 1',
|
||||
fee: 145,
|
||||
notes: `Intake complete. Chief complaint: Neck/Shoulder pain (VAS ${vasScore}/10).`,
|
||||
notes: `Digital intake completed. Chief complaint: Neck/Shoulder pain (VAS ${vasScore}/10).`,
|
||||
});
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
{/* Branded Clinic Header (100% White-Label) */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-2xl flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
{/* Branded Hospital Header */}
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 shadow-xs flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="w-3.5 h-3.5 rounded-full"
|
||||
style={{ backgroundColor: activeTenant.brandColor }}
|
||||
/>
|
||||
<span className="text-xs font-mono font-bold tracking-widest uppercase text-slate-400">
|
||||
<span className="text-xs font-mono font-bold tracking-widest uppercase text-slate-500">
|
||||
{activeTenant.logoText}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-xl font-black text-white mt-1">{activeTenant.name}</h2>
|
||||
<div className="flex flex-wrap items-center gap-4 text-xs text-slate-400 mt-2">
|
||||
<h2 className="text-xl font-bold text-slate-900 mt-1">{activeTenant.name}</h2>
|
||||
<div className="flex flex-wrap items-center gap-4 text-xs text-slate-500 mt-2">
|
||||
<span className="flex items-center gap-1">
|
||||
<MapPin className="w-3.5 h-3.5 text-teal-400" />
|
||||
<MapPin className="w-3.5 h-3.5 text-sky-700" />
|
||||
{activeTenant.address}, {activeTenant.cityStateZip}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Phone className="w-3.5 h-3.5 text-teal-400" />
|
||||
<Phone className="w-3.5 h-3.5 text-sky-700" />
|
||||
{activeTenant.phone}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 bg-slate-800/60 px-3 py-1.5 rounded-xl border border-slate-700/60 text-xs">
|
||||
<ShieldCheck className="w-4 h-4 text-teal-400" />
|
||||
<span className="text-slate-300">White-Label Patient Portal</span>
|
||||
<div className="flex items-center gap-2 bg-sky-50 px-3 py-1.5 rounded-lg border border-sky-100 text-xs font-medium text-sky-800">
|
||||
<ShieldCheck className="w-4 h-4 text-sky-700" />
|
||||
<span>Patient Care Portal</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Wizard Progress Steps */}
|
||||
{/* Progress Steps */}
|
||||
<div className="flex items-center justify-center gap-3 text-xs">
|
||||
<div
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded-full font-semibold transition ${
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded-full font-bold transition ${
|
||||
step === 'service'
|
||||
? 'bg-teal-500 text-slate-950 font-bold'
|
||||
: 'bg-slate-800 text-slate-400'
|
||||
? 'bg-sky-700 text-white shadow-xs'
|
||||
: 'bg-slate-200 text-slate-600'
|
||||
}`}
|
||||
>
|
||||
<span>1. Select Service & Time</span>
|
||||
</div>
|
||||
<span className="text-slate-600">→</span>
|
||||
<span className="text-slate-400">→</span>
|
||||
<div
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded-full font-semibold transition ${
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded-full font-bold transition ${
|
||||
step === 'intake'
|
||||
? 'bg-teal-500 text-slate-950 font-bold'
|
||||
: 'bg-slate-800 text-slate-400'
|
||||
? 'bg-sky-700 text-white shadow-xs'
|
||||
: 'bg-slate-200 text-slate-600'
|
||||
}`}
|
||||
>
|
||||
<span>2. Paperless Intake & Pain Map</span>
|
||||
<span>2. Medical Intake & Pain Map</span>
|
||||
</div>
|
||||
<span className="text-slate-600">→</span>
|
||||
<span className="text-slate-400">→</span>
|
||||
<div
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded-full font-semibold transition ${
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded-full font-bold transition ${
|
||||
step === 'confirmed'
|
||||
? 'bg-emerald-500 text-slate-950 font-bold'
|
||||
: 'bg-slate-800 text-slate-400'
|
||||
? 'bg-emerald-700 text-white shadow-xs'
|
||||
: 'bg-slate-200 text-slate-600'
|
||||
}`}
|
||||
>
|
||||
<span>3. Confirmed</span>
|
||||
<span>3. Confirmation</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 1: Select Service & Doctor */}
|
||||
{/* Step 1 */}
|
||||
{step === 'service' && (
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl space-y-6">
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 shadow-xs space-y-6">
|
||||
<div>
|
||||
<h3 className="text-base font-bold text-white">Choose Your Chiropractic Provider</h3>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
Select a doctor licensed in state-of-the-art spinal biomechanics and gentle adjustment techniques.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
|
||||
<h3 className="text-base font-bold text-slate-900">Select Attending Provider</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-3">
|
||||
{providers.map((p) => {
|
||||
const isChosen = selectedProvider.id === p.id;
|
||||
return (
|
||||
@@ -171,12 +162,12 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
onClick={() => setSelectedProvider(p)}
|
||||
className={`flex items-center gap-3 p-4 rounded-xl border text-left transition ${
|
||||
isChosen
|
||||
? 'bg-slate-800 border-teal-500/80 text-white shadow-lg'
|
||||
: 'bg-slate-950/60 border-slate-800 text-slate-300 hover:bg-slate-800/40'
|
||||
? 'bg-sky-50/70 border-sky-400 text-slate-900 shadow-2xs'
|
||||
: 'bg-slate-50 border-slate-200 text-slate-700 hover:bg-white'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="w-11 h-11 rounded-full flex items-center justify-center font-bold text-sm text-white shrink-0"
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center font-bold text-xs text-white shrink-0 shadow-2xs"
|
||||
style={{ backgroundColor: p.color }}
|
||||
>
|
||||
{p.name
|
||||
@@ -185,9 +176,9 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
.join('')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-sm text-white">{p.name}</div>
|
||||
<div className="text-xs text-teal-400 font-medium">{p.title}</div>
|
||||
<div className="text-[11px] text-slate-400 mt-0.5">{p.specialty}</div>
|
||||
<div className="font-bold text-sm text-slate-900">{p.name}</div>
|
||||
<div className="text-xs text-sky-800 font-medium">{p.title}</div>
|
||||
<div className="text-[11px] text-slate-500 mt-0.5">{p.specialty}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -196,7 +187,7 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-base font-bold text-white">Select Appointment Type</h3>
|
||||
<h3 className="text-base font-bold text-slate-900">Select Procedure / Visit Type</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mt-3 text-xs">
|
||||
{[
|
||||
{ name: 'Initial Chiropractic Exam & Adjustment', dur: '45 mins', fee: '$145' },
|
||||
@@ -209,32 +200,32 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
onClick={() => setSelectedService(svc.name)}
|
||||
className={`p-4 rounded-xl border text-left transition flex flex-col justify-between ${
|
||||
selectedService === svc.name
|
||||
? 'bg-teal-500/10 border-teal-500/80 text-white shadow'
|
||||
: 'bg-slate-950/60 border-slate-800 text-slate-400 hover:bg-slate-800/40'
|
||||
? 'bg-sky-50 border-sky-400 text-slate-900 shadow-2xs'
|
||||
: 'bg-slate-50 border-slate-200 text-slate-700 hover:bg-white'
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
<div className="font-bold text-sm text-slate-200">{svc.name}</div>
|
||||
<div className="text-[11px] text-slate-400 mt-1">{svc.dur}</div>
|
||||
<div className="font-bold text-sm text-slate-900">{svc.name}</div>
|
||||
<div className="text-[11px] text-slate-500 mt-1">{svc.dur}</div>
|
||||
</div>
|
||||
<div className="text-sm font-bold text-teal-400 font-mono mt-3">{svc.fee}</div>
|
||||
<div className="text-sm font-bold text-sky-800 font-mono mt-3">{svc.fee}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-base font-bold text-white">Pick Date & Available Slot</h3>
|
||||
<h3 className="text-base font-bold text-slate-900">Select Time Slot</h3>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 mt-3 text-xs">
|
||||
{['09:00 AM', '10:30 AM', '01:15 PM', '03:45 PM'].map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
type="button"
|
||||
onClick={() => setSelectedTime(t)}
|
||||
className={`p-3 rounded-xl border text-center font-bold transition font-mono ${
|
||||
className={`p-3 rounded-lg border text-center font-bold transition font-mono ${
|
||||
selectedTime === t
|
||||
? 'bg-teal-500 text-slate-950 shadow-md shadow-teal-500/20'
|
||||
: 'bg-slate-950/60 border-slate-800 text-slate-300 hover:bg-slate-800'
|
||||
? 'bg-sky-700 text-white shadow-xs border-sky-800'
|
||||
: 'bg-slate-50 border-slate-200 text-slate-700 hover:bg-slate-100'
|
||||
}`}
|
||||
>
|
||||
<Clock className="w-3.5 h-3.5 inline mr-1" />
|
||||
@@ -244,72 +235,71 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-slate-800 flex justify-end">
|
||||
<div className="pt-4 border-t border-slate-200 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStep('intake')}
|
||||
className="px-6 py-3 rounded-xl bg-teal-500 hover:bg-teal-400 text-slate-950 text-xs font-bold flex items-center gap-2 shadow-lg shadow-teal-500/20 transition"
|
||||
className="px-6 py-3 rounded-lg bg-sky-700 hover:bg-sky-800 text-white text-xs font-bold flex items-center gap-2 shadow-xs transition"
|
||||
>
|
||||
Continue to Digital Intake
|
||||
Continue to Medical Intake
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 2: Paperless Intake & Body Pain Map */}
|
||||
{/* Step 2 */}
|
||||
{step === 'intake' && (
|
||||
<form onSubmit={handleSubmitIntake} className="space-y-6">
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl space-y-4">
|
||||
<h3 className="text-base font-bold text-white">Patient Demographics</h3>
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 shadow-xs space-y-4">
|
||||
<h3 className="text-base font-bold text-slate-900">Patient Demographics</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-xs">
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1">Full Legal Name</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Full Legal Name</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={fullName}
|
||||
onChange={(e) => setFullName(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 focus:outline-none focus:border-teal-500"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1">Date of Birth</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Date of Birth</label>
|
||||
<input
|
||||
type="date"
|
||||
required
|
||||
value={dob}
|
||||
onChange={(e) => setDob(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 focus:outline-none focus:border-teal-500"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1">Mobile Phone (for SMS Reminders)</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Mobile Phone (for Appointment Alerts)</label>
|
||||
<input
|
||||
type="tel"
|
||||
required
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 focus:outline-none focus:border-teal-500"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-400 mb-1">Email Address</label>
|
||||
<label className="block text-slate-600 mb-1 font-medium">Email Address</label>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-slate-200 focus:outline-none focus:border-teal-500"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Interactive Pain Map */}
|
||||
<BodyPainMap
|
||||
selectedAreas={selectedPainAreas}
|
||||
onToggleArea={handleToggleArea}
|
||||
@@ -319,22 +309,20 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
onTogglePainNature={handleToggleNature}
|
||||
/>
|
||||
|
||||
{/* Signature & Consent */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl space-y-4">
|
||||
<h3 className="text-base font-bold text-white">HIPAA & Chiropractic Informed Consent</h3>
|
||||
<p className="text-xs text-slate-400 leading-relaxed">
|
||||
I hereby authorize {activeTenant.name} and attending doctors to examine and administer
|
||||
chiropractic adjustments and physical modalities as deemed necessary. I understand that,
|
||||
as with any healthcare procedure, chiropractic care carries potential benefits and risks.
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-6 shadow-xs space-y-4">
|
||||
<h3 className="text-base font-bold text-slate-900">HIPAA & Clinical Informed Consent</h3>
|
||||
<p className="text-xs text-slate-600 leading-relaxed">
|
||||
I authorize the clinical staff of {activeTenant.name} to conduct examination and deliver
|
||||
therapeutic chiropractic adjustments. I have reviewed the HIPAA privacy practices.
|
||||
</p>
|
||||
|
||||
<DigitalSignaturePad onSaveSignature={setSignature} signerName={fullName} />
|
||||
|
||||
<div className="pt-4 border-t border-slate-800 flex items-center justify-between">
|
||||
<div className="pt-4 border-t border-slate-200 flex items-center justify-between">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStep('service')}
|
||||
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 text-xs font-semibold rounded-xl transition"
|
||||
className="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
@@ -342,17 +330,17 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="px-6 py-3 bg-gradient-to-r from-teal-500 to-emerald-500 hover:from-teal-400 hover:to-emerald-400 text-slate-950 text-xs font-bold rounded-xl shadow-lg shadow-teal-500/20 transition flex items-center gap-2 disabled:opacity-50"
|
||||
className="px-6 py-3 bg-sky-700 hover:bg-sky-800 text-white text-xs font-bold rounded-lg shadow-xs transition flex items-center gap-2 disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<span className="w-4 h-4 border-2 border-slate-950 border-t-transparent rounded-full animate-spin" />
|
||||
Submitting to Doctor's EHR...
|
||||
<span className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
Submitting to Clinic EHR...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
Confirm Appointment & Submit Intake
|
||||
Complete Registration & Book
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -361,45 +349,40 @@ export const PatientPortalView: React.FC<PatientPortalViewProps> = ({
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* Step 3: Confirmed Screen */}
|
||||
{/* Step 3 */}
|
||||
{step === 'confirmed' && (
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-8 shadow-2xl text-center space-y-4 max-w-lg mx-auto">
|
||||
<div className="w-16 h-16 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center mx-auto border border-emerald-500/40">
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-8 shadow-xs text-center space-y-4 max-w-lg mx-auto">
|
||||
<div className="w-16 h-16 rounded-full bg-emerald-100 text-emerald-700 flex items-center justify-center mx-auto border border-emerald-200">
|
||||
<CheckCircle2 className="w-8 h-8" />
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-black text-white">You're All Set, {fullName}!</h3>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">
|
||||
Your appointment has been confirmed with <strong>{selectedProvider.name}</strong> at{' '}
|
||||
<h3 className="text-xl font-bold text-slate-900">Registration Complete, {fullName}!</h3>
|
||||
<p className="text-xs text-slate-600 leading-relaxed">
|
||||
Your appointment has been registered with <strong>{selectedProvider.name}</strong> at{' '}
|
||||
<strong>{activeTenant.name}</strong>.
|
||||
</p>
|
||||
|
||||
<div className="bg-slate-950 p-4 rounded-xl border border-slate-800 text-xs space-y-2 text-left">
|
||||
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200 text-xs space-y-2 text-left">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-400">Date & Time:</span>
|
||||
<span className="font-bold text-white font-mono">
|
||||
<span className="text-slate-600">Encounter Time:</span>
|
||||
<span className="font-bold text-slate-900 font-mono">
|
||||
{selectedDate} at {selectedTime}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-400">Service:</span>
|
||||
<span className="font-bold text-teal-400">{selectedService}</span>
|
||||
<span className="text-slate-600">Service:</span>
|
||||
<span className="font-bold text-sky-800">{selectedService}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-400">Paperless Intake:</span>
|
||||
<span className="font-bold text-emerald-400">Completed & Signed</span>
|
||||
<span className="text-slate-600">Digital Intake:</span>
|
||||
<span className="font-bold text-emerald-700">Verified & Electronically Signed</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 bg-teal-500/10 border border-teal-500/20 rounded-xl text-xs text-teal-300">
|
||||
A confirmation SMS with calendar link and operatory directions has been sent to{' '}
|
||||
<strong>{phone}</strong>.
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setStep('service')}
|
||||
className="mt-4 px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 text-xs font-semibold rounded-xl transition"
|
||||
className="mt-4 px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition"
|
||||
>
|
||||
Book Another Patient
|
||||
</button>
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
CheckCircle,
|
||||
Clock,
|
||||
Sparkles,
|
||||
PhoneCall,
|
||||
Calendar,
|
||||
Send,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
@@ -48,7 +46,7 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
setCustomSmsMessage(
|
||||
`Hi ${patient.firstName}! Dr. Vance noticed you're due for visit ${
|
||||
patient.carePlan.completedVisits + 1
|
||||
} of your ${patient.carePlan.title}. We want to make sure your progress stays on track! Click here to pick a quick 15-min adjustment slot this week: https://${
|
||||
} 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
|
||||
}/book?ref=careplan`
|
||||
);
|
||||
@@ -73,68 +71,68 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
<div className="space-y-6">
|
||||
{/* Top Value Metric Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="bg-slate-900 border border-slate-800 p-4 rounded-2xl shadow-lg">
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="bg-white border border-slate-200 p-5 rounded-xl shadow-xs">
|
||||
<div className="flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span>Care Plan Retention</span>
|
||||
<TrendingUp className="w-4 h-4 text-teal-400" />
|
||||
<TrendingUp className="w-4 h-4 text-sky-700" />
|
||||
</div>
|
||||
<div className="text-2xl font-black text-white mt-2">84.2%</div>
|
||||
<p className="text-[11px] text-teal-400 mt-1 flex items-center gap-1 font-medium">
|
||||
<span>+18% higher than Jane App</span>
|
||||
<div className="text-3xl font-black text-slate-900 mt-2 font-mono">84.2%</div>
|
||||
<p className="text-xs text-emerald-700 mt-1 flex items-center gap-1 font-semibold">
|
||||
<span>+18% higher than Jane App benchmark</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 border border-slate-800 p-4 rounded-2xl shadow-lg">
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="bg-white border border-slate-200 p-5 rounded-xl shadow-xs">
|
||||
<div className="flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span>Patients at Dropout Risk</span>
|
||||
<AlertTriangle className="w-4 h-4 text-amber-400" />
|
||||
<AlertTriangle className="w-4 h-4 text-amber-600" />
|
||||
</div>
|
||||
<div className="text-2xl font-black text-amber-400 mt-2">
|
||||
<div className="text-3xl font-black text-amber-700 mt-2 font-mono">
|
||||
{dropoutPatients.length} Patients
|
||||
</div>
|
||||
<p className="text-[11px] text-slate-400 mt-1">Missed expected cadence > 10 days</p>
|
||||
<p className="text-xs text-slate-500 mt-1">Missed clinical cadence > 10 days</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 border border-slate-800 p-4 rounded-2xl shadow-lg">
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="bg-white border border-slate-200 p-5 rounded-xl shadow-xs">
|
||||
<div className="flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span>Revenue at Risk</span>
|
||||
<span className="text-xs font-mono text-red-400 font-bold">$</span>
|
||||
<span className="text-xs font-mono text-red-600 font-bold">$</span>
|
||||
</div>
|
||||
<div className="text-2xl font-black text-white mt-2">$2,140.00</div>
|
||||
<p className="text-[11px] text-red-400 mt-1">Unbilled care plan visits</p>
|
||||
<div className="text-3xl font-black text-slate-900 mt-2 font-mono">$2,140.00</div>
|
||||
<p className="text-xs text-red-700 mt-1 font-medium">Unbilled scheduled adjustments</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 border border-slate-800 p-4 rounded-2xl shadow-lg">
|
||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||
<div className="bg-white border border-slate-200 p-5 rounded-xl shadow-xs">
|
||||
<div className="flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span>Recovered This Month</span>
|
||||
<Sparkles className="w-4 h-4 text-teal-400" />
|
||||
<Sparkles className="w-4 h-4 text-emerald-600" />
|
||||
</div>
|
||||
<div className="text-2xl font-black text-emerald-400 mt-2">+$3,850.00</div>
|
||||
<p className="text-[11px] text-emerald-400/80 mt-1">Via AI Autonomous SMS</p>
|
||||
<div className="text-3xl font-black text-emerald-700 mt-2 font-mono">+$3,850.00</div>
|
||||
<p className="text-xs text-emerald-800 mt-1 font-medium">Via Autonomous Clinic SMS</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Table / Patient List Header */}
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-xl">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-4 border-b border-slate-800">
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-5 shadow-xs">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-4 border-b border-slate-100">
|
||||
<div>
|
||||
<h3 className="font-bold text-white text-base flex items-center gap-2">
|
||||
<Users className="w-4 h-4 text-teal-400" />
|
||||
<h3 className="font-bold text-slate-900 text-base flex items-center gap-2">
|
||||
<Users className="w-4 h-4 text-sky-700" />
|
||||
Care Plan Adherence & Reactivation Radar
|
||||
</h3>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
Identifies patients who have dropped off their prescribed spinal treatment plans before completing their clinical goals.
|
||||
<p className="text-xs text-slate-500 mt-0.5">
|
||||
Stratifies patients by treatment compliance. Autonomous clinical outreach prevents premature care abandonment.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 bg-slate-800 p-1 rounded-xl border border-slate-700 text-xs">
|
||||
<div className="flex items-center gap-1 bg-slate-100 p-1 rounded-lg border border-slate-200 text-xs">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilterType('dropout_risk')}
|
||||
className={`px-3 py-1 rounded-lg font-medium transition ${
|
||||
className={`px-3 py-1 rounded-md font-semibold transition ${
|
||||
filterType === 'dropout_risk'
|
||||
? 'bg-amber-500 text-slate-950 font-bold shadow'
|
||||
: 'text-slate-400 hover:text-white'
|
||||
? 'bg-amber-100 text-amber-900 font-bold shadow-xs border border-amber-200'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
Dropout Risk ({dropoutPatients.length})
|
||||
@@ -142,10 +140,10 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilterType('active')}
|
||||
className={`px-3 py-1 rounded-lg font-medium transition ${
|
||||
className={`px-3 py-1 rounded-md font-semibold transition ${
|
||||
filterType === 'active'
|
||||
? 'bg-teal-500 text-slate-950 font-bold shadow'
|
||||
: 'text-slate-400 hover:text-white'
|
||||
? 'bg-emerald-100 text-emerald-900 font-bold shadow-xs border border-emerald-200'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
On Track ({activePatients.length})
|
||||
@@ -153,10 +151,10 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilterType('all')}
|
||||
className={`px-3 py-1 rounded-lg font-medium transition ${
|
||||
className={`px-3 py-1 rounded-md font-semibold transition ${
|
||||
filterType === 'all'
|
||||
? 'bg-indigo-600 text-white font-bold shadow'
|
||||
: 'text-slate-400 hover:text-white'
|
||||
? 'bg-sky-700 text-white font-bold shadow-xs'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
All Patients ({patients.length})
|
||||
@@ -165,7 +163,7 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Patient Rows */}
|
||||
<div className="divide-y divide-slate-800 mt-2">
|
||||
<div className="divide-y divide-slate-100 mt-2">
|
||||
{displayedPatients.map((p) => {
|
||||
const isAtRisk = p.status === 'dropout_risk';
|
||||
const progressPct = Math.round(
|
||||
@@ -175,46 +173,46 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
return (
|
||||
<div
|
||||
key={p.id}
|
||||
className="py-4 flex flex-col md:flex-row md:items-center justify-between gap-4 hover:bg-slate-800/30 px-2 rounded-xl transition"
|
||||
className="py-4 flex flex-col md:flex-row md:items-center justify-between gap-4 hover:bg-slate-50 px-3 rounded-lg transition"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-bold text-white text-sm">
|
||||
<h4 className="font-bold text-slate-900 text-sm">
|
||||
{p.firstName} {p.lastName}
|
||||
</h4>
|
||||
{isAtRisk ? (
|
||||
<span className="px-2 py-0.5 rounded-full bg-amber-500/20 text-amber-300 border border-amber-500/30 text-[10px] font-bold uppercase flex items-center gap-1">
|
||||
<AlertTriangle className="w-3 h-3" /> {p.daysSinceLastVisit} Days Since Visit
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-amber-50 text-amber-800 border border-amber-200 text-xs font-bold uppercase flex items-center gap-1">
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-amber-600" /> {p.daysSinceLastVisit} Days Overdue
|
||||
</span>
|
||||
) : (
|
||||
<span className="px-2 py-0.5 rounded-full bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 text-[10px] font-bold uppercase flex items-center gap-1">
|
||||
<CheckCircle className="w-3 h-3" /> On Track
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-emerald-50 text-emerald-800 border border-emerald-200 text-xs font-bold uppercase flex items-center gap-1">
|
||||
<CheckCircle className="w-3.5 h-3.5 text-emerald-600" /> On Track
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-300 mt-1">
|
||||
Care Plan: <strong>{p.carePlan.title}</strong> ({p.carePlan.frequency})
|
||||
<p className="text-xs text-slate-600 mt-1">
|
||||
Care Protocol: <strong>{p.carePlan.title}</strong> ({p.carePlan.frequency})
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-4 mt-2 text-xs text-slate-400">
|
||||
<div className="flex items-center gap-4 mt-2 text-xs text-slate-500">
|
||||
<div className="flex items-center gap-2 w-48">
|
||||
<div className="w-full bg-slate-800 rounded-full h-1.5 overflow-hidden">
|
||||
<div className="w-full bg-slate-200 rounded-full h-1.5 overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${
|
||||
isAtRisk ? 'bg-amber-400' : 'bg-teal-400'
|
||||
isAtRisk ? 'bg-amber-500' : 'bg-sky-600'
|
||||
}`}
|
||||
style={{ width: `${progressPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="font-mono text-[11px] shrink-0">
|
||||
<span className="font-mono text-xs font-semibold text-slate-700 shrink-0">
|
||||
{p.carePlan.completedVisits}/{p.carePlan.totalVisits}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span>Phone: {p.phone}</span>
|
||||
<span>•</span>
|
||||
<span>Last Visit: {p.lastVisitDate}</span>
|
||||
<span>Last Encounter: {p.lastVisitDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -223,7 +221,7 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelectPatientChart(p)}
|
||||
className="px-3 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-semibold rounded-lg transition"
|
||||
className="px-3.5 py-1.5 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-bold rounded-lg border border-slate-200 transition"
|
||||
>
|
||||
View Chart
|
||||
</button>
|
||||
@@ -231,14 +229,14 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleOpenSmsModal(p)}
|
||||
className={`px-3 py-1.5 text-xs font-bold rounded-lg flex items-center gap-1.5 transition ${
|
||||
className={`px-3.5 py-1.5 text-xs font-bold rounded-lg flex items-center gap-1.5 transition shadow-xs ${
|
||||
isAtRisk
|
||||
? 'bg-amber-500 hover:bg-amber-400 text-slate-950 shadow-lg shadow-amber-500/20'
|
||||
: 'bg-teal-500/20 hover:bg-teal-500/30 text-teal-300 border border-teal-500/40'
|
||||
? 'bg-amber-600 hover:bg-amber-700 text-white'
|
||||
: 'bg-sky-700 hover:bg-sky-800 text-white'
|
||||
}`}
|
||||
>
|
||||
<MessageSquare className="w-3.5 h-3.5" />
|
||||
{isAtRisk ? 'Send AI Reactivation SMS' : 'Message Patient'}
|
||||
{isAtRisk ? 'Send Reactivation SMS' : 'Message Patient'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -249,18 +247,18 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
|
||||
{/* SMS Reactivation Modal */}
|
||||
{selectedPatientForSms && (
|
||||
<div className="fixed inset-0 z-50 bg-slate-950/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div className="bg-slate-900 border border-slate-700 rounded-2xl max-w-lg w-full p-6 shadow-2xl animate-in fade-in zoom-in-95 duration-150 text-slate-100">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
||||
<div className="fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-xs flex items-center justify-center p-4">
|
||||
<div className="bg-white border border-slate-200 rounded-2xl max-w-lg w-full p-6 shadow-xl text-slate-900">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="p-2 rounded-xl bg-amber-500/20 text-amber-300">
|
||||
<span className="p-2 rounded-lg bg-amber-50 text-amber-700 border border-amber-200">
|
||||
<MessageSquare className="w-5 h-5" />
|
||||
</span>
|
||||
<div>
|
||||
<h4 className="font-bold text-white text-sm">
|
||||
Autonomous SMS Reactivation Dispatch
|
||||
<h4 className="font-bold text-slate-900 text-sm">
|
||||
Clinical SMS Reactivation Dispatch
|
||||
</h4>
|
||||
<p className="text-[11px] text-slate-400">
|
||||
<p className="text-xs text-slate-500">
|
||||
To: {selectedPatientForSms.firstName} {selectedPatientForSms.lastName} ({selectedPatientForSms.phone})
|
||||
</p>
|
||||
</div>
|
||||
@@ -268,53 +266,53 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedPatientForSms(null)}
|
||||
className="text-slate-400 hover:text-white p-1"
|
||||
className="text-slate-400 hover:text-slate-600 p-1"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-3 text-xs">
|
||||
<div className="bg-slate-950/80 p-3 rounded-xl border border-slate-800 text-slate-300 flex items-center justify-between">
|
||||
<div className="bg-slate-50 p-3 rounded-lg border border-slate-200 text-slate-700 flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-slate-400">Sending Clinic Line:</span>{' '}
|
||||
<strong className="text-white font-mono">{activeTenant.phone}</strong>
|
||||
<span className="text-slate-500 font-medium">Sending Clinic Number:</span>{' '}
|
||||
<strong className="text-slate-900 font-mono">{activeTenant.phone}</strong>
|
||||
</div>
|
||||
<span className="text-[10px] bg-teal-500/20 text-teal-300 px-2 py-0.5 rounded font-bold">
|
||||
<span className="text-[11px] bg-sky-100 text-sky-800 px-2 py-0.5 rounded font-bold border border-sky-200">
|
||||
AI Pilots Gateway
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-[11px] font-semibold text-slate-400 uppercase tracking-wider mb-1.5">
|
||||
Personalized 2-Way Message (Editable)
|
||||
<label className="block text-xs font-bold text-slate-700 uppercase tracking-wider mb-1.5">
|
||||
Automated Outreach Message
|
||||
</label>
|
||||
<textarea
|
||||
rows={5}
|
||||
value={customSmsMessage}
|
||||
onChange={(e) => setCustomSmsMessage(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800 rounded-xl p-3 text-xs text-slate-200 focus:outline-none focus:border-amber-500 resize-none font-sans"
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-lg p-3 text-xs text-slate-900 focus:bg-white focus:outline-none focus:border-sky-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{sentToast && (
|
||||
<div className="p-3 bg-emerald-500/20 border border-emerald-500/40 rounded-xl text-xs text-emerald-300 flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-400" />
|
||||
<span>SMS Dispatched! Patient status updated to Reactivated.</span>
|
||||
<div className="p-3 bg-emerald-50 border border-emerald-200 rounded-lg text-xs text-emerald-800 flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-600" />
|
||||
<span>SMS Dispatched! Patient flagged as Reactivated in EHR.</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-3 border-t border-slate-800 flex items-center justify-between">
|
||||
<span className="text-[11px] text-slate-500">
|
||||
Auto-fills calendar slots when patient clicks.
|
||||
<div className="mt-6 pt-3 border-t border-slate-100 flex items-center justify-between">
|
||||
<span className="text-xs text-slate-500">
|
||||
Direct booking link embedded automatically.
|
||||
</span>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedPatientForSms(null)}
|
||||
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 text-xs font-semibold rounded-xl transition"
|
||||
className="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -322,17 +320,17 @@ export const RetentionView: React.FC<RetentionViewProps> = ({
|
||||
type="button"
|
||||
disabled={isSendingSms}
|
||||
onClick={handleSendSms}
|
||||
className="px-4 py-2 bg-amber-500 hover:bg-amber-400 text-slate-950 text-xs font-bold rounded-xl shadow-lg shadow-amber-500/20 transition flex items-center gap-1.5 disabled:opacity-50"
|
||||
className="px-4 py-2 bg-sky-700 hover:bg-sky-800 text-white text-xs font-bold rounded-lg shadow-xs transition flex items-center gap-1.5 disabled:opacity-50"
|
||||
>
|
||||
{isSendingSms ? (
|
||||
<>
|
||||
<span className="w-3.5 h-3.5 border-2 border-slate-950 border-t-transparent rounded-full animate-spin" />
|
||||
Sending SMS...
|
||||
<span className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
Dispatching...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="w-3.5 h-3.5" />
|
||||
Send Reactivation Text
|
||||
Send Clinical SMS
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Mic, MicOff, Sparkles, Volume2, Play, Pause, RotateCcw, CheckCircle2, AlertCircle } from 'lucide-react';
|
||||
import { Mic, MicOff, Sparkles, Volume2, CheckCircle2, ShieldCheck } from 'lucide-react';
|
||||
import { SAMPLE_AUDIO_PRESETS } from '@/lib/mock-data';
|
||||
|
||||
interface AmbientAudioRecorderProps {
|
||||
@@ -27,7 +27,6 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
|
||||
const [liveTranscript, setLiveTranscript] = useState('');
|
||||
const [showAppliedToast, setShowAppliedToast] = useState(false);
|
||||
|
||||
// Timer simulation
|
||||
useEffect(() => {
|
||||
let interval: any = null;
|
||||
if (isRecording) {
|
||||
@@ -44,8 +43,7 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
|
||||
if (!isRecording) {
|
||||
setIsRecording(true);
|
||||
setRecordingSeconds(0);
|
||||
setLiveTranscript('Listening to ambient doctor-patient dialogue in operatory...');
|
||||
// Simulate live ambient audio transcription
|
||||
setLiveTranscript('Hospital ambient listener active. Recording operatory encounter...');
|
||||
setTimeout(() => {
|
||||
setLiveTranscript(selectedPreset.audioTranscript);
|
||||
}, 2500);
|
||||
@@ -87,48 +85,48 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-br from-slate-900 via-slate-900 to-teal-950/40 border border-teal-500/30 rounded-2xl p-5 shadow-2xl text-slate-100">
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-5 shadow-xs text-slate-800">
|
||||
{/* Top Banner */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-4 border-b border-slate-800">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-4 border-b border-slate-100">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="p-1.5 rounded-lg bg-teal-500/20 text-teal-400 border border-teal-500/30">
|
||||
<span className="p-1.5 rounded-lg bg-sky-50 text-sky-700 border border-sky-100">
|
||||
<Sparkles className="w-4 h-4" />
|
||||
</span>
|
||||
<h3 className="font-semibold text-white text-base tracking-tight">
|
||||
Mediusa Ambient Clinical AI Scribe
|
||||
<h3 className="font-bold text-slate-900 text-base tracking-tight">
|
||||
Ambient Clinical Medical Scribe
|
||||
</h3>
|
||||
<span className="text-[11px] font-semibold px-2 py-0.5 rounded-full bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">
|
||||
Powered by gpt-5.4-mini Clinical Engine
|
||||
<span className="text-[11px] font-bold px-2.5 py-0.5 rounded-full bg-sky-100 text-sky-800 border border-sky-200">
|
||||
Clinical Intelligence Engine (gpt-5.4-mini)
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
Zero typing required. Scribe listens to ambient doctor-patient encounter and converts conversational dialogue into compliant SOAP + CPT/ICD-10 codes.
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Hands-free documentation. Operatory audio automatically translates into compliant SOAP format and procedure codes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Recording Status / Mic Button */}
|
||||
<div className="flex items-center gap-3">
|
||||
{isRecording && (
|
||||
<div className="flex items-center gap-2 text-xs font-mono font-semibold text-red-400 animate-pulse">
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-red-500" />
|
||||
LIVE {formatTime(recordingSeconds)}
|
||||
<div className="flex items-center gap-2 text-xs font-mono font-bold text-red-600 animate-pulse">
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-red-600" />
|
||||
RECORDING {formatTime(recordingSeconds)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleRecording}
|
||||
className={`px-4 py-2 rounded-xl text-xs font-bold flex items-center gap-2 transition-all shadow-lg ${
|
||||
className={`px-4 py-2 rounded-lg text-xs font-bold flex items-center gap-2 transition shadow-xs ${
|
||||
isRecording
|
||||
? 'bg-red-500/20 text-red-400 border border-red-500/50 hover:bg-red-500/30'
|
||||
: 'bg-teal-500 hover:bg-teal-400 text-slate-950 shadow-teal-500/20'
|
||||
? 'bg-red-50 text-red-700 border border-red-200 hover:bg-red-100'
|
||||
: 'bg-sky-700 hover:bg-sky-800 text-white'
|
||||
}`}
|
||||
>
|
||||
{isRecording ? (
|
||||
<>
|
||||
<MicOff className="w-4 h-4" />
|
||||
Stop Listening
|
||||
Stop Recording
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -140,20 +138,21 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Waveform Graphic Animation */}
|
||||
<div className="mt-4 p-4 bg-slate-950/60 rounded-xl border border-slate-800/80">
|
||||
{/* Hospital Audio Monitor Waveform */}
|
||||
<div className="mt-4 p-4 bg-slate-900 rounded-lg border border-slate-800 text-slate-100">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-[11px] font-semibold text-slate-400 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<Volume2 className="w-3.5 h-3.5 text-teal-400" />
|
||||
Operatory Ambient Stream
|
||||
<span className="text-xs font-bold text-slate-300 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<Volume2 className="w-3.5 h-3.5 text-sky-400" />
|
||||
Medical Audio Feed
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-500 font-mono">
|
||||
44.1kHz • 24-bit • HIPAA Speech Encryption
|
||||
<span className="text-[10px] text-slate-400 font-mono flex items-center gap-1">
|
||||
<ShieldCheck className="w-3 h-3 text-emerald-400" />
|
||||
HIPAA BAA End-to-End Encryption
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Dynamic Animated Bars */}
|
||||
<div className="h-10 flex items-center justify-center gap-1">
|
||||
<div className="h-9 flex items-center justify-center gap-1">
|
||||
{[
|
||||
12, 28, 45, 18, 62, 85, 40, 92, 70, 30, 80, 65, 45, 90, 35, 75, 55, 20, 68, 88,
|
||||
42, 78, 52, 22, 60, 95, 38, 72, 58, 25, 48, 82, 32, 64, 44, 15,
|
||||
@@ -162,11 +161,11 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
|
||||
key={i}
|
||||
className={`w-1 rounded-full transition-all duration-150 ${
|
||||
isRecording
|
||||
? 'bg-gradient-to-t from-teal-500 to-emerald-300 animate-pulse'
|
||||
: 'bg-slate-800'
|
||||
? 'bg-sky-400 animate-pulse'
|
||||
: 'bg-slate-700'
|
||||
}`}
|
||||
style={{
|
||||
height: isRecording ? `${Math.max(10, (height * (1 + (i % 3) * 0.2)) % 40)}px` : '6px',
|
||||
height: isRecording ? `${Math.max(8, (height * (1 + (i % 3) * 0.2)) % 36)}px` : '4px',
|
||||
animationDelay: `${(i * 40) % 600}ms`,
|
||||
}}
|
||||
/>
|
||||
@@ -178,8 +177,8 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 mt-4">
|
||||
{/* Left 4 Cols: Presets */}
|
||||
<div className="md:col-span-4 flex flex-col gap-2">
|
||||
<label className="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">
|
||||
Clinical Encounter Presets (1-Click Demo)
|
||||
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">
|
||||
Clinical Encounter Presets
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{SAMPLE_AUDIO_PRESETS.map((preset) => (
|
||||
@@ -187,14 +186,14 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
|
||||
key={preset.id}
|
||||
type="button"
|
||||
onClick={() => handleSelectPreset(preset)}
|
||||
className={`w-full text-left p-2.5 rounded-xl border transition text-xs ${
|
||||
className={`w-full text-left p-3 rounded-lg border transition text-xs ${
|
||||
selectedPreset.id === preset.id
|
||||
? 'bg-slate-800 border-teal-500/60 text-white shadow-sm'
|
||||
: 'bg-slate-900/60 border-slate-800 text-slate-400 hover:bg-slate-800/40 hover:text-slate-200'
|
||||
? 'bg-sky-50/80 border-sky-300 text-sky-950 font-medium shadow-2xs'
|
||||
: 'bg-slate-50 border-slate-200 text-slate-600 hover:bg-slate-100 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold text-slate-200 truncate">{preset.title}</div>
|
||||
<div className="text-[10px] text-teal-400 mt-0.5">Encounter Duration: {preset.duration}</div>
|
||||
<div className="font-bold text-slate-900 truncate">{preset.title}</div>
|
||||
<div className="text-[11px] text-sky-700 mt-0.5">Length: {preset.duration}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -203,50 +202,50 @@ export const AmbientAudioRecorder: React.FC<AmbientAudioRecorderProps> = ({
|
||||
type="button"
|
||||
onClick={handleSynthesizeSoap}
|
||||
disabled={isProcessingAI}
|
||||
className="mt-3 w-full py-2.5 px-4 rounded-xl text-xs font-bold bg-gradient-to-r from-teal-500 to-emerald-500 hover:from-teal-400 hover:to-emerald-400 text-slate-950 shadow-lg shadow-teal-500/20 transition flex items-center justify-center gap-2 disabled:opacity-50"
|
||||
className="mt-2 w-full py-2.5 px-4 rounded-lg text-xs font-bold bg-sky-700 hover:bg-sky-800 text-white shadow-xs transition flex items-center justify-center gap-2 disabled:opacity-50"
|
||||
>
|
||||
{isProcessingAI ? (
|
||||
<>
|
||||
<span className="w-3.5 h-3.5 border-2 border-slate-950 border-t-transparent rounded-full animate-spin" />
|
||||
AI Extracting SOAP...
|
||||
<span className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
Structuring Medical Note...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Sparkles className="w-4 h-4 text-slate-950" />
|
||||
Extract SOAP & Auto-Codes
|
||||
<Sparkles className="w-4 h-4 text-white" />
|
||||
Synthesize Note & Auto-Codes
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Right 8 Cols: Dialogue Transcript */}
|
||||
<div className="md:col-span-8 bg-slate-950/70 border border-slate-800 rounded-xl p-3.5 flex flex-col justify-between">
|
||||
<div className="md:col-span-8 bg-slate-50 border border-slate-200 rounded-lg p-3.5 flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-800 text-xs">
|
||||
<span className="font-semibold text-slate-300">Encounter Dialogue Feed</span>
|
||||
<span className="text-[10px] text-teal-400/90 font-mono">Live Whisper Stream</span>
|
||||
<div className="flex items-center justify-between pb-2 border-b border-slate-200 text-xs">
|
||||
<span className="font-bold text-slate-800">Transcript Log</span>
|
||||
<span className="text-[11px] text-slate-500 font-mono">Live Operatory Mic</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 whitespace-pre-line mt-2 font-mono leading-relaxed max-h-[160px] overflow-y-auto pr-1">
|
||||
<p className="text-xs text-slate-700 whitespace-pre-line mt-2 font-mono leading-relaxed max-h-[160px] overflow-y-auto pr-1">
|
||||
{liveTranscript || selectedPreset.audioTranscript}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 pt-2 border-t border-slate-800/80 flex items-center justify-between text-[11px] text-slate-400">
|
||||
<span className="flex items-center gap-1 text-teal-400">
|
||||
<CheckCircle2 className="w-3.5 h-3.5" />
|
||||
Auto-detects Subluxations, VAS score, CPT 98940, ICD-10 M99.0x
|
||||
<div className="mt-3 pt-2 border-t border-slate-200 flex items-center justify-between text-xs text-slate-500">
|
||||
<span className="flex items-center gap-1 text-sky-800 font-medium">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-sky-700" />
|
||||
Auto-extracts SOAP, CPT 98940, ICD-10 M99.0x
|
||||
</span>
|
||||
<span className="text-slate-500">HIPAA BAA Compliant</span>
|
||||
<span className="text-slate-400">Speech-to-Text v2.4</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showAppliedToast && (
|
||||
<div className="mt-3 p-3 bg-emerald-500/20 border border-emerald-500/40 rounded-xl text-xs text-emerald-300 flex items-center justify-between animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-4 h-4 text-emerald-400" />
|
||||
<div className="mt-3 p-3 bg-emerald-50 border border-emerald-200 rounded-lg text-xs text-emerald-800 flex items-center justify-between animate-in fade-in duration-200">
|
||||
<div className="flex items-center gap-2 font-medium">
|
||||
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
|
||||
<span>
|
||||
<strong>Success!</strong> Encounter conversation extracted into Subjective, Objective, Assessment, Plan, Spine segments, and Billing codes.
|
||||
<strong>Note Populated:</strong> Subjective, Objective, Assessment, Plan, Vertebrae listings, and Billing codes auto-synced.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Activity, AlertCircle, Check } from 'lucide-react';
|
||||
import { Activity, Check } from 'lucide-react';
|
||||
|
||||
interface BodyPainMapProps {
|
||||
selectedAreas: string[];
|
||||
@@ -22,7 +22,7 @@ const BODY_ZONES = [
|
||||
{ id: 'Sacroiliac (SI Joint) / Pelvis', label: 'SI Joints / Pelvis', side: 'Posterior' },
|
||||
{ id: 'Right Glute / Sciatic Nerve', label: 'Right Glute & Sciatica', side: 'Posterior' },
|
||||
{ id: 'Left Glute / Sciatic Nerve', label: 'Left Glute & Sciatica', side: 'Posterior' },
|
||||
{ id: 'Head / Occipital', label: 'Head / Base of Skull (Headaches)', side: 'Posterior' },
|
||||
{ id: 'Head / Occipital', label: 'Head / Skull Base (Headaches)', side: 'Posterior' },
|
||||
{ id: 'Bilateral Knees', label: 'Knees / Gait Imbalance', side: 'Anterior' },
|
||||
];
|
||||
|
||||
@@ -32,7 +32,7 @@ const PAIN_NATURE_OPTIONS = [
|
||||
'Burning Heat',
|
||||
'Shooting / Radiating',
|
||||
'Tingling / Pins & Needles',
|
||||
'Numbness / Loss of Sensation',
|
||||
'Numbness / Sensation Loss',
|
||||
'Throbbing Tension',
|
||||
];
|
||||
|
||||
@@ -46,20 +46,20 @@ export const BodyPainMap: React.FC<BodyPainMapProps> = ({
|
||||
readOnly = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-5 text-slate-100 shadow-xl">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 pb-4 border-b border-slate-800">
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-5 text-slate-800 shadow-xs">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 pb-4 border-b border-slate-100">
|
||||
<div>
|
||||
<h4 className="font-semibold text-white text-sm flex items-center gap-2">
|
||||
<Activity className="w-4 h-4 text-teal-400" />
|
||||
<h4 className="font-bold text-slate-900 text-sm flex items-center gap-2">
|
||||
<Activity className="w-4 h-4 text-sky-700" />
|
||||
Interactive Pain & Symptom Locator
|
||||
</h4>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
Tap the areas on your body where you experience pain, tension, or nerve radiation.
|
||||
<p className="text-xs text-slate-500 mt-0.5">
|
||||
Select the anatomical areas where you experience pain, muscular guarding, or radiating symptoms.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-slate-400 font-medium">Areas Selected:</span>
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-teal-500/20 text-teal-300 font-bold text-xs border border-teal-500/30">
|
||||
<span className="text-xs text-slate-500 font-medium">Areas Selected:</span>
|
||||
<span className="px-2.5 py-0.5 rounded-full bg-sky-100 text-sky-800 font-bold text-xs border border-sky-200">
|
||||
{selectedAreas.length}
|
||||
</span>
|
||||
</div>
|
||||
@@ -68,8 +68,8 @@ export const BodyPainMap: React.FC<BodyPainMapProps> = ({
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-6 mt-4">
|
||||
{/* Pain Hotspots Grid (7 cols) */}
|
||||
<div className="md:col-span-7 space-y-2">
|
||||
<label className="text-[11px] font-semibold text-slate-400 uppercase tracking-wider block mb-1">
|
||||
Select Pain Regions
|
||||
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider block mb-1">
|
||||
Select Pain Zones
|
||||
</label>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{BODY_ZONES.map((zone) => {
|
||||
@@ -80,22 +80,22 @@ export const BodyPainMap: React.FC<BodyPainMapProps> = ({
|
||||
type="button"
|
||||
disabled={readOnly}
|
||||
onClick={() => onToggleArea(zone.id)}
|
||||
className={`flex items-center justify-between p-3 rounded-xl border text-left text-xs transition ${
|
||||
className={`flex items-center justify-between p-3 rounded-lg border text-left text-xs transition ${
|
||||
isSelected
|
||||
? 'bg-teal-500/20 border-teal-500/60 text-teal-200 shadow-sm'
|
||||
: 'bg-slate-800/60 border-slate-700/60 text-slate-300 hover:bg-slate-800'
|
||||
? 'bg-sky-50 border-sky-300 text-sky-950 font-medium shadow-2xs'
|
||||
: 'bg-slate-50 border-slate-200 text-slate-700 hover:bg-slate-100'
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
<div className="font-semibold">{zone.label}</div>
|
||||
<div className="text-[10px] text-slate-400">{zone.side} Chain</div>
|
||||
<div className="font-bold text-slate-900">{zone.label}</div>
|
||||
<div className="text-[11px] text-slate-500">{zone.side}</div>
|
||||
</div>
|
||||
{isSelected ? (
|
||||
<span className="w-5 h-5 rounded-full bg-teal-500 text-slate-950 flex items-center justify-center font-bold">
|
||||
<Check className="w-3 h-3" />
|
||||
<span className="w-5 h-5 rounded-full bg-sky-700 text-white flex items-center justify-center font-bold shadow-2xs">
|
||||
<Check className="w-3 h-3 stroke-[3]" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-slate-700" />
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-slate-300" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
@@ -104,21 +104,21 @@ export const BodyPainMap: React.FC<BodyPainMapProps> = ({
|
||||
</div>
|
||||
|
||||
{/* VAS Pain Scale & Pain Nature (5 cols) */}
|
||||
<div className="md:col-span-5 bg-slate-800/40 border border-slate-700/60 rounded-xl p-4 flex flex-col justify-between">
|
||||
<div className="md:col-span-5 bg-slate-50 border border-slate-200 rounded-lg p-4 flex flex-col justify-between">
|
||||
<div>
|
||||
{/* Visual Analog Scale (VAS) Slider */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-[11px] font-semibold text-slate-300 uppercase tracking-wider">
|
||||
Visual Analog Scale (VAS)
|
||||
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider">
|
||||
Pain Scale (VAS)
|
||||
</label>
|
||||
<span
|
||||
className={`text-sm font-black px-2 py-0.5 rounded-lg ${
|
||||
className={`text-sm font-black px-2.5 py-0.5 rounded-md ${
|
||||
vasScore >= 7
|
||||
? 'bg-red-500/20 text-red-400 border border-red-500/30'
|
||||
? 'bg-red-100 text-red-800 border border-red-200'
|
||||
: vasScore >= 4
|
||||
? 'bg-amber-500/20 text-amber-400 border border-amber-500/30'
|
||||
: 'bg-teal-500/20 text-teal-400 border border-teal-500/30'
|
||||
? 'bg-amber-100 text-amber-800 border border-amber-200'
|
||||
: 'bg-sky-100 text-sky-800 border border-sky-200'
|
||||
}`}
|
||||
>
|
||||
{vasScore} / 10
|
||||
@@ -133,20 +133,20 @@ export const BodyPainMap: React.FC<BodyPainMapProps> = ({
|
||||
value={vasScore}
|
||||
disabled={readOnly}
|
||||
onChange={(e) => onChangeVasScore(parseInt(e.target.value))}
|
||||
className="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-teal-500"
|
||||
className="w-full h-2 bg-slate-300 rounded-lg appearance-none cursor-pointer accent-sky-700"
|
||||
/>
|
||||
|
||||
<div className="flex justify-between text-[10px] text-slate-400 mt-1 font-mono">
|
||||
<div className="flex justify-between text-[11px] text-slate-500 mt-1 font-mono">
|
||||
<span>0 (No Pain)</span>
|
||||
<span>5 (Moderate)</span>
|
||||
<span>10 (Severe)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pain Nature Checkboxes */}
|
||||
{/* Pain Nature */}
|
||||
<div>
|
||||
<label className="text-[11px] font-semibold text-slate-300 uppercase tracking-wider block mb-2">
|
||||
Nature of Pain Sensation
|
||||
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider block mb-2">
|
||||
Pain Sensation Type
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{PAIN_NATURE_OPTIONS.map((nature) => {
|
||||
@@ -157,10 +157,10 @@ export const BodyPainMap: React.FC<BodyPainMapProps> = ({
|
||||
type="button"
|
||||
disabled={readOnly}
|
||||
onClick={() => onTogglePainNature(nature)}
|
||||
className={`px-2.5 py-1 rounded-lg text-xs font-medium transition ${
|
||||
className={`px-2.5 py-1 rounded-md text-xs font-semibold transition ${
|
||||
isChecked
|
||||
? 'bg-indigo-600 text-white font-semibold shadow-sm'
|
||||
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-slate-200'
|
||||
? 'bg-sky-700 text-white shadow-xs'
|
||||
: 'bg-white border border-slate-200 text-slate-700 hover:bg-slate-100'
|
||||
}`}
|
||||
>
|
||||
{nature}
|
||||
@@ -171,9 +171,8 @@ export const BodyPainMap: React.FC<BodyPainMapProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 pt-3 border-t border-slate-700/60 text-[11px] text-slate-400 flex items-center gap-1.5">
|
||||
<AlertCircle className="w-3.5 h-3.5 text-teal-400 shrink-0" />
|
||||
<span>Maps directly into Provider SOAP & ICD-10 diagnostic codes.</span>
|
||||
<div className="mt-4 pt-3 border-t border-slate-200 text-xs text-slate-500 flex items-center gap-1.5">
|
||||
<span>Synchronizes directly into doctor's EHR examination chart.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { PenTool, RotateCcw, Check } from 'lucide-react';
|
||||
import { PenTool, RotateCcw } from 'lucide-react';
|
||||
|
||||
interface DigitalSignaturePadProps {
|
||||
onSaveSignature: (signatureData: string) => void;
|
||||
@@ -21,7 +21,7 @@ export const DigitalSignaturePad: React.FC<DigitalSignaturePadProps> = ({
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
ctx.strokeStyle = '#2dd4bf'; // Teal 400
|
||||
ctx.strokeStyle = '#0284c7'; // Hospital Medical Blue Ink
|
||||
ctx.lineWidth = 2.5;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
@@ -78,18 +78,18 @@ export const DigitalSignaturePad: React.FC<DigitalSignaturePadProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-xl p-4">
|
||||
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-1.5 text-xs text-slate-300 font-semibold">
|
||||
<PenTool className="w-3.5 h-3.5 text-teal-400" />
|
||||
<span>Digital Signature Pad</span>
|
||||
{signerName && <span className="text-slate-400 font-normal">({signerName})</span>}
|
||||
<div className="flex items-center gap-1.5 text-xs text-slate-700 font-bold">
|
||||
<PenTool className="w-3.5 h-3.5 text-sky-700" />
|
||||
<span>Patient Electronic Signature</span>
|
||||
{signerName && <span className="text-slate-500 font-normal">({signerName})</span>}
|
||||
</div>
|
||||
{hasDrawn && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearCanvas}
|
||||
className="text-[11px] text-slate-400 hover:text-slate-200 flex items-center gap-1 transition"
|
||||
className="text-[11px] text-slate-500 hover:text-slate-800 flex items-center gap-1 font-semibold transition"
|
||||
>
|
||||
<RotateCcw className="w-3 h-3" />
|
||||
Clear
|
||||
@@ -97,12 +97,12 @@ export const DigitalSignaturePad: React.FC<DigitalSignaturePadProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="relative border-2 border-dashed border-slate-700 rounded-lg bg-slate-950 overflow-hidden">
|
||||
<div className="relative border-2 border-dashed border-slate-300 rounded-lg bg-white overflow-hidden shadow-2xs">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
width={500}
|
||||
height={120}
|
||||
className="w-full h-[120px] touch-none cursor-crosshair"
|
||||
height={110}
|
||||
className="w-full h-[110px] touch-none cursor-crosshair"
|
||||
onMouseDown={startDrawing}
|
||||
onMouseMove={draw}
|
||||
onMouseUp={stopDrawing}
|
||||
@@ -112,8 +112,8 @@ export const DigitalSignaturePad: React.FC<DigitalSignaturePadProps> = ({
|
||||
onTouchEnd={stopDrawing}
|
||||
/>
|
||||
{!hasDrawn && (
|
||||
<div className="absolute inset-0 pointer-events-none flex items-center justify-center text-xs text-slate-500">
|
||||
Sign with finger or mouse here (HIPAA Certified)
|
||||
<div className="absolute inset-0 pointer-events-none flex items-center justify-center text-xs text-slate-400 font-medium">
|
||||
Sign with finger or stylus here (HIPAA Certified)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { SpinalAdjustmentEntry } from '@/types/clinical';
|
||||
import { Check, X, ShieldAlert, Zap, Sparkles, Activity } from 'lucide-react';
|
||||
import { Check, X, Zap, Activity } from 'lucide-react';
|
||||
|
||||
interface SpineVisualizerProps {
|
||||
adjustments: SpinalAdjustmentEntry[];
|
||||
@@ -125,37 +125,37 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-2xl text-slate-100">
|
||||
<div className="bg-white border border-slate-200 rounded-xl p-5 shadow-xs text-slate-800">
|
||||
{/* Header bar */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-4 border-b border-slate-800">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-4 border-b border-slate-100">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="p-1.5 rounded-lg bg-teal-500/10 text-teal-400 border border-teal-500/20">
|
||||
<span className="p-1.5 rounded-lg bg-sky-50 text-sky-700 border border-sky-100">
|
||||
<Zap className="w-4 h-4" />
|
||||
</span>
|
||||
<h3 className="font-semibold text-white text-base tracking-tight">
|
||||
Interactive 2D Spine Subluxation Visualizer
|
||||
<h3 className="font-bold text-slate-900 text-base tracking-tight">
|
||||
Interactive Spine Subluxation Visualizer
|
||||
</h3>
|
||||
<span className="ml-2 text-xs font-medium px-2 py-0.5 rounded-full bg-teal-500/20 text-teal-300 border border-teal-500/30">
|
||||
{adjustments.length} Listings Adjusted
|
||||
<span className="ml-2 text-xs font-semibold px-2.5 py-0.5 rounded-full bg-sky-100 text-sky-800 border border-sky-200">
|
||||
{adjustments.length} Segment(s) Charted
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
Rapid click-to-chart anatomical interface. Tap any spinal segment to assign listing & technique in < 5 seconds.
|
||||
<p className="text-xs text-slate-500 mt-1">
|
||||
Clinical tactile mapping. Select any vertebra to document subluxation listing & adjustment technique.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Region Filter Buttons */}
|
||||
<div className="flex items-center gap-1 bg-slate-800/80 p-1 rounded-xl border border-slate-700/60 overflow-x-auto text-xs">
|
||||
<div className="flex items-center gap-1 bg-slate-100 p-1 rounded-lg border border-slate-200 overflow-x-auto text-xs">
|
||||
{(['All', 'Cervical', 'Thoracic', 'Lumbar', 'Pelvis/Sacrum'] as const).map((region) => (
|
||||
<button
|
||||
key={region}
|
||||
type="button"
|
||||
onClick={() => setActiveFilter(region)}
|
||||
className={`px-2.5 py-1 rounded-lg font-medium transition-all whitespace-nowrap ${
|
||||
className={`px-3 py-1 rounded-md font-semibold transition whitespace-nowrap ${
|
||||
activeFilter === region
|
||||
? 'bg-teal-500 text-white shadow-sm'
|
||||
: 'text-slate-400 hover:text-white hover:bg-slate-700/50'
|
||||
? 'bg-white text-sky-800 shadow-xs border border-slate-200/80'
|
||||
: 'text-slate-600 hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
{region}
|
||||
@@ -167,63 +167,61 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
{/* Main Spine Grid Layout */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-6 mt-5">
|
||||
{/* Visual Column / Spine Segment Buttons (8 cols) */}
|
||||
<div className="md:col-span-8 flex flex-col gap-2 max-h-[460px] overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-slate-700">
|
||||
<div className="md:col-span-8 flex flex-col gap-2 max-h-[460px] overflow-y-auto pr-2">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{filteredVertebrae.map((v) => {
|
||||
const active = isAdjusted(v.id);
|
||||
const adj = getAdjustment(v.id);
|
||||
|
||||
const regionColor =
|
||||
const regionBorder =
|
||||
v.region === 'Cervical'
|
||||
? 'border-indigo-500/40 hover:border-indigo-400'
|
||||
? 'border-indigo-200 hover:border-indigo-300'
|
||||
: v.region === 'Thoracic'
|
||||
? 'border-blue-500/40 hover:border-blue-400'
|
||||
? 'border-sky-200 hover:border-sky-300'
|
||||
: v.region === 'Lumbar'
|
||||
? 'border-teal-500/40 hover:border-teal-400'
|
||||
: 'border-purple-500/40 hover:border-purple-400';
|
||||
? 'border-teal-200 hover:border-teal-300'
|
||||
: 'border-purple-200 hover:border-purple-300';
|
||||
|
||||
const activeBg =
|
||||
v.region === 'Cervical'
|
||||
? 'bg-gradient-to-r from-indigo-950 to-slate-900 border-indigo-400 text-indigo-200'
|
||||
: v.region === 'Thoracic'
|
||||
? 'bg-gradient-to-r from-blue-950 to-slate-900 border-blue-400 text-blue-200'
|
||||
: v.region === 'Lumbar'
|
||||
? 'bg-gradient-to-r from-teal-950 to-slate-900 border-teal-400 text-teal-200'
|
||||
: 'bg-gradient-to-r from-purple-950 to-slate-900 border-purple-400 text-purple-200';
|
||||
const activeStyle =
|
||||
'bg-sky-700 border-sky-800 text-white shadow-xs';
|
||||
|
||||
return (
|
||||
<button
|
||||
key={v.id}
|
||||
type="button"
|
||||
onClick={() => handleOpenModal(v)}
|
||||
className={`relative flex items-center justify-between p-2.5 rounded-xl border text-left transition-all duration-150 group ${
|
||||
active ? `${activeBg} shadow-lg shadow-teal-950/20` : `bg-slate-800/40 ${regionColor} text-slate-300 hover:bg-slate-800/80`
|
||||
className={`relative flex items-center justify-between p-2.5 rounded-lg border text-left transition-all duration-150 ${
|
||||
active
|
||||
? activeStyle
|
||||
: `bg-slate-50/60 ${regionBorder} text-slate-800 hover:bg-white`
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span
|
||||
className={`w-7 h-7 flex items-center justify-center rounded-lg font-bold text-xs shrink-0 ${
|
||||
className={`w-7 h-7 flex items-center justify-center rounded font-bold text-xs shrink-0 ${
|
||||
active
|
||||
? 'bg-teal-500 text-slate-950 font-black'
|
||||
: 'bg-slate-800 text-slate-400 group-hover:text-white'
|
||||
? 'bg-white text-sky-800'
|
||||
: 'bg-white text-slate-700 border border-slate-200 shadow-2xs'
|
||||
}`}
|
||||
>
|
||||
{v.shortName}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-semibold truncate leading-tight">{v.name}</div>
|
||||
<div className="text-[10px] text-slate-400 truncate mt-0.5">
|
||||
<div className={`text-xs font-bold truncate leading-tight ${active ? 'text-white' : 'text-slate-900'}`}>
|
||||
{v.name}
|
||||
</div>
|
||||
<div className={`text-[11px] truncate mt-0.5 ${active ? 'text-sky-100' : 'text-slate-500'}`}>
|
||||
{active && adj ? `${adj.listing} • ${adj.technique}` : v.region}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{active ? (
|
||||
<span className="w-5 h-5 rounded-full bg-teal-500/20 text-teal-400 flex items-center justify-center shrink-0 border border-teal-500/40">
|
||||
<Check className="w-3 h-3" />
|
||||
<span className="w-5 h-5 rounded-full bg-white text-sky-800 flex items-center justify-center shrink-0 shadow-xs">
|
||||
<Check className="w-3 h-3 stroke-[3]" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="w-2 h-2 rounded-full bg-slate-700 group-hover:bg-slate-500 shrink-0" />
|
||||
<span className="w-2 h-2 rounded-full bg-slate-300 shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
@@ -232,23 +230,23 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Right Details Panel: Active Encounters & Clinical Notes (4 cols) */}
|
||||
<div className="md:col-span-4 bg-slate-800/50 border border-slate-700/60 rounded-xl p-4 flex flex-col justify-between">
|
||||
<div className="md:col-span-4 bg-slate-50 border border-slate-200 rounded-xl p-4 flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-700">
|
||||
<span className="text-xs font-semibold text-slate-300 uppercase tracking-wider">
|
||||
Visit Adjustment Summary
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
||||
<span className="text-xs font-bold text-slate-700 uppercase tracking-wider">
|
||||
Visit Adjustments
|
||||
</span>
|
||||
<span className="text-xs text-teal-400 font-mono">
|
||||
<span className="text-xs text-sky-700 font-mono font-bold">
|
||||
{adjustments.length} Segment(s)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{adjustments.length === 0 ? (
|
||||
<div className="py-10 text-center text-slate-500">
|
||||
<Activity className="w-8 h-8 mx-auto mb-2 opacity-40 text-slate-400" />
|
||||
<p className="text-xs">No vertebrae selected.</p>
|
||||
<p className="text-[11px] text-slate-600 mt-1">
|
||||
Click any vertebra on the left to add adjustment listing.
|
||||
<div className="py-10 text-center text-slate-400">
|
||||
<Activity className="w-8 h-8 mx-auto mb-2 opacity-50 text-slate-400" />
|
||||
<p className="text-xs font-medium">No vertebrae selected.</p>
|
||||
<p className="text-[11px] text-slate-400 mt-1">
|
||||
Click any segment on the left to document clinical listings.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -256,17 +254,17 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
{adjustments.map((item) => (
|
||||
<div
|
||||
key={item.vertebra}
|
||||
className="flex items-center justify-between bg-slate-900/80 border border-slate-700/80 p-2.5 rounded-lg text-xs"
|
||||
className="flex items-center justify-between bg-white border border-slate-200 p-2.5 rounded-lg text-xs shadow-2xs"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-bold text-teal-300">{item.vertebra}</span>
|
||||
<span className="text-[10px] text-slate-400 font-mono">({item.region})</span>
|
||||
<span className="font-bold text-sky-800">{item.vertebra}</span>
|
||||
<span className="text-[11px] text-slate-500 font-mono">({item.region})</span>
|
||||
</div>
|
||||
<div className="text-[11px] text-slate-300 mt-0.5 font-medium">
|
||||
<div className="text-xs text-slate-800 mt-0.5 font-medium">
|
||||
{item.listing}
|
||||
</div>
|
||||
<div className="text-[10px] text-teal-400/90 font-sans">
|
||||
<div className="text-[11px] text-sky-700 font-medium">
|
||||
Technique: {item.technique}
|
||||
</div>
|
||||
</div>
|
||||
@@ -275,8 +273,8 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveListing(item.vertebra)}
|
||||
className="text-slate-500 hover:text-red-400 p-1 transition-colors"
|
||||
title="Remove adjustment"
|
||||
className="text-slate-400 hover:text-red-600 p-1 transition-colors"
|
||||
title="Remove segment"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -289,9 +287,9 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
|
||||
{/* Quick Subluxation Preset Macros */}
|
||||
{!readOnly && (
|
||||
<div className="mt-4 pt-3 border-t border-slate-700">
|
||||
<div className="text-[11px] font-semibold text-slate-400 uppercase tracking-wider mb-2">
|
||||
Chiropractic Quick Macros
|
||||
<div className="mt-4 pt-3 border-t border-slate-200">
|
||||
<div className="text-[11px] font-bold text-slate-600 uppercase tracking-wider mb-2">
|
||||
Clinical Fast Macros
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
<button
|
||||
@@ -310,7 +308,7 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
technique: 'Diversified',
|
||||
});
|
||||
}}
|
||||
className="px-2 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded text-[11px] font-medium transition text-left truncate"
|
||||
className="px-2.5 py-1.5 bg-white hover:bg-slate-100 text-slate-700 border border-slate-200 rounded text-xs font-semibold transition text-left truncate shadow-2xs"
|
||||
>
|
||||
+ Cervical Protocol
|
||||
</button>
|
||||
@@ -330,7 +328,7 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
technique: 'Thompson Drop',
|
||||
});
|
||||
}}
|
||||
className="px-2 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded text-[11px] font-medium transition text-left truncate"
|
||||
className="px-2.5 py-1.5 bg-white hover:bg-slate-100 text-slate-700 border border-slate-200 rounded text-xs font-semibold transition text-left truncate shadow-2xs"
|
||||
>
|
||||
+ L4/SI Protocol
|
||||
</button>
|
||||
@@ -340,40 +338,39 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Interactive Adjustment Modal / Drawer */}
|
||||
{/* Interactive Adjustment Modal */}
|
||||
{selectedVertebra && (
|
||||
<div className="fixed inset-0 z-50 bg-slate-950/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<div className="bg-slate-900 border border-slate-700 rounded-2xl max-w-md w-full p-6 shadow-2xl animate-in fade-in zoom-in-95 duration-150 text-slate-100">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
||||
<div className="fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-xs flex items-center justify-center p-4">
|
||||
<div className="bg-white border border-slate-200 rounded-2xl max-w-md w-full p-6 shadow-xl text-slate-900">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-slate-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-8 h-8 rounded-lg bg-teal-500/20 text-teal-400 font-bold flex items-center justify-center text-sm border border-teal-500/30">
|
||||
<span className="w-8 h-8 rounded-lg bg-sky-100 text-sky-800 font-bold flex items-center justify-center text-sm">
|
||||
{selectedVertebra.shortName}
|
||||
</span>
|
||||
<div>
|
||||
<h4 className="font-semibold text-white text-sm">{selectedVertebra.name}</h4>
|
||||
<p className="text-xs text-slate-400">{selectedVertebra.region} Spine</p>
|
||||
<h4 className="font-bold text-slate-900 text-sm">{selectedVertebra.name}</h4>
|
||||
<p className="text-xs text-slate-500">{selectedVertebra.region} Region</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedVertebra(null)}
|
||||
className="text-slate-400 hover:text-white p-1"
|
||||
className="text-slate-400 hover:text-slate-600 p-1"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-4">
|
||||
{/* Common Clinical Symptoms Reference */}
|
||||
<div className="bg-slate-800/60 rounded-xl p-2.5 border border-slate-700/60 text-xs">
|
||||
<span className="text-slate-400 font-semibold">Anatomical Correlate:</span>{' '}
|
||||
<span className="text-slate-300">{selectedVertebra.commonSymptoms}</span>
|
||||
<div className="bg-slate-50 rounded-lg p-3 border border-slate-200 text-xs">
|
||||
<span className="text-slate-600 font-bold">Anatomical Correlate:</span>{' '}
|
||||
<span className="text-slate-700">{selectedVertebra.commonSymptoms}</span>
|
||||
</div>
|
||||
|
||||
{/* Subluxation Listing Selector */}
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-300 uppercase tracking-wider mb-2">
|
||||
Subluxation Listing / Motion Restriction
|
||||
<label className="block text-xs font-bold text-slate-700 uppercase tracking-wider mb-2">
|
||||
Subluxation Listing
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
{COMMON_LISTINGS.map((listing) => (
|
||||
@@ -383,8 +380,8 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
onClick={() => setActiveListing(listing)}
|
||||
className={`px-3 py-2 rounded-lg text-xs font-medium text-left transition ${
|
||||
activeListing === listing
|
||||
? 'bg-teal-500 text-white font-semibold shadow-sm'
|
||||
: 'bg-slate-800 text-slate-300 hover:bg-slate-700'
|
||||
? 'bg-sky-700 text-white font-bold shadow-xs'
|
||||
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
{listing}
|
||||
@@ -395,8 +392,8 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
|
||||
{/* Technique Selector */}
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-300 uppercase tracking-wider mb-2">
|
||||
Chiropractic Technique
|
||||
<label className="block text-xs font-bold text-slate-700 uppercase tracking-wider mb-2">
|
||||
Adjustment Technique
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-1.5">
|
||||
{TECHNIQUES.map((tech) => (
|
||||
@@ -404,10 +401,10 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
key={tech}
|
||||
type="button"
|
||||
onClick={() => setActiveTechnique(tech)}
|
||||
className={`px-2 py-1.5 rounded-lg text-xs font-medium transition text-center ${
|
||||
className={`px-2 py-1.5 rounded-lg text-xs font-semibold transition text-center ${
|
||||
activeTechnique === tech
|
||||
? 'bg-indigo-600 text-white font-semibold shadow-sm'
|
||||
: 'bg-slate-800 text-slate-300 hover:bg-slate-700'
|
||||
? 'bg-sky-700 text-white shadow-xs'
|
||||
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
{tech}
|
||||
@@ -418,12 +415,12 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Modal Actions */}
|
||||
<div className="mt-6 pt-4 border-t border-slate-800 flex items-center justify-between gap-3">
|
||||
<div className="mt-6 pt-4 border-t border-slate-100 flex items-center justify-between gap-3">
|
||||
{isAdjusted(selectedVertebra.id) ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveListing(selectedVertebra.id)}
|
||||
className="px-3 py-2 rounded-xl text-xs font-semibold text-red-400 hover:bg-red-500/10 transition"
|
||||
className="px-3 py-2 rounded-lg text-xs font-bold text-red-600 hover:bg-red-50 transition"
|
||||
>
|
||||
Clear Segment
|
||||
</button>
|
||||
@@ -435,17 +432,17 @@ export const SpineVisualizer: React.FC<SpineVisualizerProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedVertebra(null)}
|
||||
className="px-4 py-2 rounded-xl text-xs font-semibold bg-slate-800 hover:bg-slate-700 text-slate-300 transition"
|
||||
className="px-4 py-2 rounded-lg text-xs font-semibold bg-slate-100 hover:bg-slate-200 text-slate-700 transition"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSaveListing}
|
||||
className="px-4 py-2 rounded-xl text-xs font-semibold bg-teal-500 hover:bg-teal-400 text-slate-950 font-bold shadow-lg shadow-teal-500/20 transition flex items-center gap-1.5"
|
||||
className="px-4 py-2 rounded-lg text-xs font-bold bg-sky-700 hover:bg-sky-800 text-white shadow-xs transition flex items-center gap-1.5"
|
||||
>
|
||||
<Check className="w-4 h-4" />
|
||||
Apply Listing
|
||||
Save to Chart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user