"use client"; import { type ColumnDef } from "@tanstack/react-table"; import { ArrowUpDown, MoreHorizontal } from "lucide-react"; import * as React from "react"; import { formatDistanceToNow } from "date-fns"; import { AlertCircle } from "lucide-react"; import Link from "next/link"; import { Alert, AlertDescription } from "~/components/ui/alert"; import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import { Checkbox } from "~/components/ui/checkbox"; import { DataTable } from "~/components/ui/data-table"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "~/components/ui/dropdown-menu"; import { useStudyContext } from "~/lib/study-context"; import { api } from "~/trpc/react"; export type Experiment = { id: string; name: string; description: string | null; status: "draft" | "testing" | "ready" | "deprecated"; version: number; estimatedDuration: number | null; createdAt: Date; studyId: string; studyName: string; createdByName: string; trialCount: number; stepCount: number; actionCount: number; latestActivityAt: Date | null; }; const statusConfig = { draft: { label: "Draft", className: "bg-gray-100 text-gray-800", }, testing: { label: "Testing", className: "bg-yellow-100 text-yellow-800", }, ready: { label: "Ready", className: "bg-green-100 text-green-800", }, deprecated: { label: "Deprecated", className: "bg-red-100 text-red-800", }, }; export const columns: ColumnDef[] = [ { id: "select", header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} aria-label="Select all" /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label="Select row" /> ), enableSorting: false, enableHiding: false, }, { accessorKey: "name", header: ({ column }) => { return ( ); }, cell: ({ row }) => { const name = row.getValue("name"); const description = row.original.description; return (
{String(name)}
{description && (
{description}
)}
); }, }, // Study column removed (active study context already selected) { accessorKey: "status", header: "Status", cell: ({ row }) => { const status = row.getValue("status"); const statusInfo = statusConfig[status as keyof typeof statusConfig]; if (!statusInfo) { return ( Unknown ); } return {statusInfo.label}; }, }, { accessorKey: "version", header: "Version", cell: ({ row }) => { const version = row.getValue("version"); return v{String(version)}; }, }, { accessorKey: "stepCount", header: "Steps", cell: ({ row }) => { const stepCount = row.getValue("stepCount"); return ( {Number(stepCount)} step{Number(stepCount) !== 1 ? "s" : ""} ); }, }, { accessorKey: "actionCount", header: "Actions", cell: ({ row }) => { const actionCount = row.getValue("actionCount"); return ( {Number(actionCount)} action{Number(actionCount) !== 1 ? "s" : ""} ); }, }, { accessorKey: "trialCount", header: "Trials", cell: ({ row }) => { const trialCount = row.getValue("trialCount"); if (trialCount === 0) { return ( No trials ); } return ( {Number(trialCount)} trial{Number(trialCount) !== 1 ? "s" : ""} ); }, }, { accessorKey: "latestActivityAt", header: "Last Activity", cell: ({ row }) => { const ts = row.getValue("latestActivityAt"); if (!ts) { return ; } return ( {formatDistanceToNow(new Date(ts as string | number | Date), { addSuffix: true, })} ); }, }, { accessorKey: "estimatedDuration", header: "Duration", cell: ({ row }) => { const duration = row.getValue("estimatedDuration"); if (!duration) { return ; } return {Number(duration)}m; }, }, { accessorKey: "createdAt", header: ({ column }) => { return ( ); }, cell: ({ row }) => { const date = row.getValue("createdAt"); return (
{formatDistanceToNow(new Date(date as string | number | Date), { addSuffix: true, })}
); }, }, { id: "actions", enableHiding: false, cell: ({ row }) => { const experiment = row.original; return ( Actions navigator.clipboard.writeText(experiment.id)} > Copy experiment ID View details Edit experiment Open designer Create trial Archive experiment ); }, }, ]; export function ExperimentsTable() { const { selectedStudyId } = useStudyContext(); const { data: experimentsData, isLoading, error, refetch, } = api.experiments.list.useQuery( { studyId: selectedStudyId ?? "", }, { refetchOnWindowFocus: false, enabled: !!selectedStudyId, }, ); const data: Experiment[] = React.useMemo(() => { if (!experimentsData) return []; interface RawExperiment { id: string; name: string; description?: string | null; status: Experiment["status"]; version: number; estimatedDuration?: number | null; createdAt: string | Date; studyId: string; createdBy?: { name?: string | null; email?: string | null } | null; trialCount?: number | null; stepCount?: number | null; actionCount?: number | null; latestActivityAt?: string | Date | null; } const adapt = (exp: RawExperiment): Experiment => { const createdAt = exp.createdAt instanceof Date ? exp.createdAt : new Date(exp.createdAt); const latestActivityAt = exp.latestActivityAt ? exp.latestActivityAt instanceof Date ? exp.latestActivityAt : new Date(exp.latestActivityAt) : null; return { id: exp.id, name: exp.name, description: exp.description ?? "", status: exp.status, version: exp.version, estimatedDuration: exp.estimatedDuration ?? 0, createdAt, studyId: exp.studyId, studyName: "Active Study", createdByName: exp.createdBy?.name ?? exp.createdBy?.email ?? "Unknown", trialCount: exp.trialCount ?? 0, stepCount: exp.stepCount ?? 0, actionCount: exp.actionCount ?? 0, latestActivityAt, }; }; return experimentsData.map((e) => adapt(e as unknown as RawExperiment)); }, [experimentsData]); if (!selectedStudyId) { return ( Please select a study to view experiments. ); } if (error) { return ( Failed to load experiments: {error.message} ); } return (
); }