"use client"; import React from "react"; import { Bot, User, Activity, Settings, Wifi, WifiOff, AlertCircle, CheckCircle, Zap, } from "lucide-react"; import { Badge } from "~/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Separator } from "~/components/ui/separator"; import { ScrollArea } from "~/components/ui/scroll-area"; import { Alert, AlertDescription } from "~/components/ui/alert"; interface TrialData { id: string; status: "scheduled" | "in_progress" | "completed" | "aborted" | "failed"; scheduledAt: Date | null; startedAt: Date | null; completedAt: Date | null; duration: number | null; sessionNumber: number | null; notes: string | null; experimentId: string; participantId: string | null; wizardId: string | null; experiment: { id: string; name: string; description: string | null; studyId: string; }; participant: { id: string; participantCode: string; demographics: Record | null; }; } interface TrialEvent { type: string; timestamp: Date; data?: unknown; message?: string; } interface MonitoringPanelProps { trial: TrialData; trialEvents: TrialEvent[]; isConnected: boolean; wsError?: string; } export function MonitoringPanel({ trial, trialEvents, isConnected, wsError, }: MonitoringPanelProps) { const formatTimestamp = (timestamp: Date) => { return new Date(timestamp).toLocaleTimeString(); }; const getEventIcon = (eventType: string) => { switch (eventType.toLowerCase()) { case "trial_started": case "trial_resumed": return CheckCircle; case "trial_paused": case "trial_stopped": return AlertCircle; case "step_completed": case "action_completed": return CheckCircle; case "robot_action": case "robot_status": return Bot; case "wizard_action": case "wizard_intervention": return User; case "system_error": case "connection_error": return AlertCircle; default: return Activity; } }; const getEventColor = (eventType: string) => { switch (eventType.toLowerCase()) { case "trial_started": case "trial_resumed": case "step_completed": case "action_completed": return "text-green-600"; case "trial_paused": case "trial_stopped": return "text-yellow-600"; case "system_error": case "connection_error": case "trial_failed": return "text-red-600"; case "robot_action": case "robot_status": return "text-blue-600"; case "wizard_action": case "wizard_intervention": return "text-purple-600"; default: return "text-muted-foreground"; } }; return (
{/* Connection Status */} Connection Status
{isConnected ? ( ) : ( )} WebSocket
{isConnected ? "Connected" : "Offline"}
{wsError && ( {wsError} )}
Trial ID {trial.id.slice(-8)}
Session #{trial.sessionNumber}
{trial.startedAt && (
Started {formatTimestamp(new Date(trial.startedAt))}
)}
{/* Robot Status */} Robot Status
Status {isConnected ? "Ready" : "Unknown"}
Battery --
Position --
Robot monitoring requires WebSocket connection
{/* Participant Info */} Participant
Code {trial.participant.participantCode}
Session #{trial.sessionNumber}
{trial.participant.demographics && (
Demographics {Object.keys(trial.participant.demographics).length} fields
)}
{/* Live Events */} Live Events {trialEvents.length > 0 && ( {trialEvents.length} )} {trialEvents.length === 0 ? (
No events yet
) : (
{trialEvents .slice() .reverse() .map((event, index) => { const EventIcon = getEventIcon(event.type); const eventColor = getEventColor(event.type); return (
{event.type.replace(/_/g, " ")}
{event.message && (
{event.message}
)}
{formatTimestamp(event.timestamp)}
); })}
)}
{/* System Info */} System
Experiment {trial.experiment.name}
Study ID {trial.experiment.studyId.slice(-8)}
Platform HRIStudio
); }