"use client"; import { format, formatDistanceToNow } from "date-fns"; import { Clock, Eye, Play, Plus, Settings, Square } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { useStudyContext } from "~/lib/study-context"; import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card"; import { Separator } from "~/components/ui/separator"; import { api } from "~/trpc/react"; type TrialWithRelations = { id: string; experimentId: string; participantId: string | null; scheduledAt: Date | null; startedAt: Date | null; completedAt: Date | null; status: "scheduled" | "in_progress" | "completed" | "aborted" | "failed"; duration: number | null; notes: string | null; wizardId: string | null; createdAt: Date; experiment?: { id: string; name: string; study?: { id: string; name: string; }; }; participant?: { id: string; participantCode: string; email: string | null; name: string | null; } | null; wizard?: { id: string; name: string | null; email: string; } | null; _count?: { events: number; mediaCaptures: number; }; }; const statusConfig = { scheduled: { label: "Scheduled", className: "bg-blue-100 text-blue-800 hover:bg-blue-200", icon: Clock, action: "Start Trial", actionIcon: Play, }, in_progress: { label: "In Progress", className: "bg-green-100 text-green-800 hover:bg-green-200", icon: Play, action: "Monitor", actionIcon: Eye, }, completed: { label: "Completed", className: "bg-gray-100 text-gray-800 hover:bg-gray-200", icon: Square, action: "Review", actionIcon: Eye, }, aborted: { label: "Aborted", className: "bg-red-100 text-red-800 hover:bg-red-200", icon: Square, action: "View", actionIcon: Eye, }, failed: { label: "Failed", className: "bg-red-100 text-red-800 hover:bg-red-200", icon: Square, action: "View", actionIcon: Eye, }, }; interface TrialCardProps { trial: TrialWithRelations; userRole: string; onTrialAction: (trialId: string, action: string) => void; } function TrialCard({ trial, userRole, onTrialAction }: TrialCardProps) { const statusInfo = statusConfig[trial.status]; const StatusIcon = statusInfo.icon; const ActionIcon = statusInfo.actionIcon; const isScheduledSoon = trial.status === "scheduled" && trial.scheduledAt ? new Date(trial.scheduledAt).getTime() - Date.now() < 60 * 60 * 1000 : false; // Within 1 hour const canControl = userRole === "wizard" || userRole === "researcher" || userRole === "administrator"; return (
{trial.experiment?.name ?? "Unknown Experiment"} Participant: {trial.participant?.participantCode ?? "Unknown"}
{trial.experiment?.study?.name ?? "Unknown Study"} {trial.wizard && ( Wizard: {trial.wizard.name ?? trial.wizard.email} )}
{statusInfo.label} {isScheduledSoon && ( Starting Soon )}
{/* Schedule Information */}
Scheduled: {trial.scheduledAt ? format(trial.scheduledAt, "MMM d, yyyy 'at' h:mm a") : "Not scheduled"}
{trial.startedAt && (
Started: {formatDistanceToNow(trial.startedAt, { addSuffix: true })}
)} {trial.completedAt && (
Completed: {formatDistanceToNow(trial.completedAt, { addSuffix: true })}
)} {trial.duration && (
Duration: {Math.round(trial.duration / 60)} minutes
)}
{/* Statistics */} {trial._count && ( <>
Events: {trial._count.events}
Media: {trial._count.mediaCaptures}
)} {/* Notes Preview */} {trial.notes && ( <>
Notes: {trial.notes.substring(0, 100)}...
)} {/* Actions */}
{trial.status === "scheduled" && canControl && ( )} {trial.status === "in_progress" && ( )} {trial.status === "completed" && ( )}
); } export function TrialsGrid() { const [statusFilter, setStatusFilter] = useState("all"); const { selectedStudyId } = useStudyContext(); const { data: userSession } = api.auth.me.useQuery(); const { data: trialsData, isLoading, error, refetch, } = api.trials.getUserTrials.useQuery( { page: 1, limit: 50, status: statusFilter === "all" ? undefined : (statusFilter as | "scheduled" | "in_progress" | "completed" | "aborted" | "failed"), }, { refetchOnWindowFocus: false, refetchInterval: 30000, // Refetch every 30 seconds for real-time updates }, ); const startTrialMutation = api.trials.start.useMutation({ onSuccess: () => { void refetch(); }, }); const trials = trialsData?.trials ?? []; const userRole = userSession?.roles?.[0] ?? "observer"; const handleTrialAction = async (trialId: string, action: string) => { if (action === "start") { try { await startTrialMutation.mutateAsync({ id: trialId }); } catch (error) { console.error("Failed to start trial:", error); } } }; // Group trials by status for better organization const upcomingTrials = trials.filter((t) => t.status === "scheduled"); const activeTrials = trials.filter((t) => t.status === "in_progress"); const completedTrials = trials.filter((t) => t.status === "completed"); if (isLoading) { return (
{/* Status Filter Skeleton */}
{Array.from({ length: 4 }).map((_, i) => (
))}
{/* Grid Skeleton */}
{Array.from({ length: 6 }).map((_, i) => (
))}
); } if (error) { return (

Failed to Load Trials

{error.message || "An error occurred while loading your trials."}

); } return (
{/* Header */}

Trials

Schedule, execute, and monitor HRI experiment trials with real-time wizard control

{/* Quick Actions Bar */}
{selectedStudyId && ( )}
{/* Active Trials Section (Priority) */} {activeTrials.length > 0 && (statusFilter === "all" || statusFilter === "in_progress") && (

Active Trials

{activeTrials.length} running
{activeTrials.map((trial) => ( ))}
)} {/* Main Trials Grid */}
{statusFilter !== "in_progress" && (

{statusFilter === "all" ? "All Trials" : statusFilter === "scheduled" ? "Scheduled Trials" : statusFilter === "completed" ? "Completed Trials" : "Cancelled Trials"}

)} {trials.length === 0 ? (

No Trials Yet

Schedule your first trial to start collecting data with real participants. Trials let you execute your designed experiments with wizard control.

{selectedStudyId && ( )}
) : (
{trials .filter( (trial) => statusFilter === "all" || trial.status === statusFilter || (statusFilter === "in_progress" && trial.status === "in_progress"), ) .map((trial) => ( ))}
)}
); }