"use client"; import { Activity, Heart, Thermometer, Droplets, Wind, Scale, Calendar, ChevronRight, Shield, Check, Circle, Bell, } from "lucide-react"; import { VITAL_META, todayISO, type VitalReading, type Medication, type Appointment, type VitalType, } from "@/lib/health-store"; import { t, type SupportedLanguage } from "@/lib/i18n"; interface RightPanelProps { language?: SupportedLanguage; emergencyNumber?: string; // Health data for the Vitals Today + Upcoming sections vitals?: VitalReading[]; medications?: Medication[]; appointments?: Appointment[]; isMedTaken?: (medId: string, date: string, time: string) => boolean; onNavigate?: (view: string) => void; // Notification count notificationCount?: number; onOpenNotifications?: () => void; /** * When false, the panel renders a minimal sign-in card instead of the * empty-state "Get started" prompt or the Vitals/Upcoming sections * (which would have no data anyway). Emergency access is NOT shown * here — that lives in the sidebar Tools group on every screen. */ isAuthenticated?: boolean; } const VITAL_ICONS: Record = { "heart-rate": Heart, "blood-pressure": Activity, "oxygen-saturation": Wind, temperature: Thermometer, weight: Scale, "blood-glucose": Droplets, }; const VITAL_COLORS: Record = { "heart-rate": { bg: "bg-red-50 dark:bg-red-900/20", text: "text-red-600 dark:text-red-400", border: "border-red-200 dark:border-red-700/40", }, "blood-pressure": { bg: "bg-blue-50 dark:bg-blue-900/20", text: "text-blue-600 dark:text-blue-400", border: "border-blue-200 dark:border-blue-700/40", }, "oxygen-saturation": { bg: "bg-sky-50 dark:bg-sky-900/20", text: "text-sky-600 dark:text-sky-400", border: "border-sky-200 dark:border-sky-700/40", }, temperature: { bg: "bg-orange-50 dark:bg-orange-900/20", text: "text-orange-600 dark:text-orange-400", border: "border-orange-200 dark:border-orange-700/40", }, weight: { bg: "bg-purple-50 dark:bg-purple-900/20", text: "text-purple-600 dark:text-purple-400", border: "border-purple-200 dark:border-purple-700/40", }, "blood-glucose": { bg: "bg-amber-50 dark:bg-amber-900/20", text: "text-amber-600 dark:text-amber-400", border: "border-amber-200 dark:border-amber-700/40", }, }; function getStatus(type: VitalType, value: string): string { // Simple heuristic — not medical advice const num = parseFloat(value.split("/")[0]); if (isNaN(num)) return ""; switch (type) { case "heart-rate": return num >= 60 && num <= 100 ? "Normal (60-100)" : num < 60 ? "Low" : "High"; case "oxygen-saturation": return num >= 95 ? "Excellent" : num >= 90 ? "Fair" : "Low"; case "temperature": return num >= 36.1 && num <= 37.2 ? "Stable" : num > 37.2 ? "Elevated" : "Low"; case "blood-pressure": return num < 120 ? "Normal" : num < 140 ? "Elevated" : "High"; default: return ""; } } export function RightPanel({ language = "en", emergencyNumber: _emergencyNumber, vitals = [], medications = [], appointments = [], isMedTaken, onNavigate, notificationCount: _notificationCount, onOpenNotifications: _onOpenNotifications, isAuthenticated = true, }: RightPanelProps) { const today = todayISO(); // Latest reading per vital type. const latestVitals = (Object.keys(VITAL_META) as VitalType[]) .map((type) => { const reading = vitals .filter((v) => v.type === type) .sort((a, b) => `${b.date}${b.time}`.localeCompare(`${a.date}${a.time}`))[0]; return { type, reading }; }) .filter((v) => v.reading); // Today's upcoming events (meds + appointments). const upcoming: Array<{ id: string; title: string; time: string; type: string; done: boolean; }> = []; for (const med of medications.filter((m) => m.active)) { for (const time of med.times) { const done = isMedTaken?.(med.id, today, time) ?? false; upcoming.push({ id: `med-${med.id}-${time}`, title: `${med.name} (${med.dose})`, time, type: "medication", done, }); } } for (const appt of appointments.filter( (a) => a.date === today && a.status !== "cancelled", )) { upcoming.push({ id: `appt-${appt.id}`, title: appt.title, time: appt.time, type: appt.type, done: appt.status === "completed", }); } upcoming.sort((a, b) => a.time.localeCompare(b.time)); const hasHealthData = latestVitals.length > 0 || upcoming.length > 0; return (
{/* Vitals Today — only if there are readings */} {latestVitals.length > 0 && (

Vitals Today

{latestVitals.slice(0, 3).map(({ type, reading }) => { const meta = VITAL_META[type]; const colors = VITAL_COLORS[type]; const Icon = VITAL_ICONS[type]; const status = getStatus(type, reading!.value); return (
{meta.label}
{reading!.value} {meta.unit}
{status && ( {status} )}
); })}
)} {/* Upcoming — meds + appointments for today */} {upcoming.length > 0 && (

Upcoming

{upcoming.slice(0, 5).map((item) => (
{item.done ? (
) : ( )}
{item.title} {item.time} · {item.type}
))}
)} {/* Empty state. * * MedOSApp now only mounts RightPanel for authenticated users, so * the right rail is always personal context — no duplicate * sign-up card here (the left sidebar already carries the auth * CTA). When the user is signed in but has no data yet, we keep * the gentle setup nudge pointing at the Health Dashboard. */} {!hasHealthData && (

Track your vitals and medications to see them here

)} {/* Privacy footer — always at bottom. * * The big red 'Call ' button that used to live * here is gone. Emergency access stays in the sidebar Tools group * (NavItem with urgent flag) where it's one click away without * dominating every screen with anxious red chrome. The * deterministic safety engine still routes any R5 input to an * emergency template at the chat-route level. */}
{t("badge_private", language)} · {t("badge_free", language)}
); }