"use client" import { useState } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { MoreHorizontal, Eye, Download, Trash2 } from "lucide-react" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { useRouter } from "next/navigation" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import { getAllCategories } from "@/lib/schema" export type EvaluationCardData = { id: string systemName: string provider: string inputModalities: string[] outputModalities: string[] completedDate: string applicableCategories: number completedCategories: number status: "strong" | "adequate" | "weak" | "insufficient" capabilityEval: { strong: number adequate: number weak: number insufficient: number strongCategories: string[] adequateCategories: string[] weakCategories: string[] insufficientCategories: string[] totalApplicable: number } riskEval: { strong: number adequate: number weak: number insufficient: number strongCategories: string[] adequateCategories: string[] weakCategories: string[] insufficientCategories: string[] totalApplicable: number } priorityAreas?: string[] priorityDetails?: Record< string, { yes: string[] negative: { text: string; status: "no" | "na"; reason?: string }[] } > } interface EvaluationCardProps { evaluation: EvaluationCardData onView: (id: string) => void onDelete: (id: string) => void } const getCompletenessColor = (score: number) => { if (score >= 85) return "bg-emerald-500 text-white" if (score >= 70) return "bg-blue-500 text-white" if (score >= 55) return "bg-amber-500 text-white" return "bg-red-500 text-white" } export function EvaluationCard({ evaluation, onView, onDelete }: EvaluationCardProps) { const [expandedAreas, setExpandedAreas] = useState>({}) const toggleArea = (area: string) => setExpandedAreas((p) => ({ ...p, [area]: !p[area] })) const router = useRouter() const handleCardClick = () => { router.push(`/evaluation/${evaluation.id}`) } const handleMenuClick = (e: React.MouseEvent) => { e.stopPropagation() // Prevent card click when clicking menu } const handleExport = (e: React.MouseEvent) => { e.stopPropagation() // Prevent card click when clicking export const reportData = { id: evaluation.id, systemName: evaluation.systemName, provider: evaluation.provider, completedDate: evaluation.completedDate, exportDate: new Date().toISOString(), inputModalities: evaluation.inputModalities, outputModalities: evaluation.outputModalities, completenessScore: Math.round((evaluation.completedCategories / evaluation.applicableCategories) * 100), status: evaluation.status, capabilityEvaluation: evaluation.capabilityEval, riskEvaluation: evaluation.riskEval, priorityAreas: evaluation.priorityAreas, priorityDetails: evaluation.priorityDetails } const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: "application/json" }) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = `evaluation-summary-${evaluation.systemName.replace(/[^a-zA-Z0-9]/g, '-')}-${new Date().toISOString().split("T")[0]}.json` a.click() URL.revokeObjectURL(url) } const modalityMap: Record = { "Text": { label: "Text", emoji: "📝" }, "Image": { label: "Image", emoji: "🖼️" }, "Audio": { label: "Audio", emoji: "🔊" }, "Video": { label: "Video", emoji: "🎥" }, "Tabular": { label: "Tabular", emoji: "📊" }, "Robotics/Action": { label: "Robotics", emoji: "🤖" }, "Other": { label: "Other", emoji: "⚡" }, } const getModalityDisplay = (inputModalities: string[], outputModalities: string[]) => { const inputStr = inputModalities.join(", ") const outputStr = outputModalities.join(", ") // Special cases for common patterns if (inputModalities.length === 1 && outputModalities.length === 1) { if (inputModalities[0] === "Text" && outputModalities[0] === "Text") { return { label: "Text → Text", emoji: "�" } } if (inputModalities[0] === "Text" && outputModalities[0] === "Image") { return { label: "Text → Image", emoji: "�️" } } if (inputModalities[0] === "Image" && outputModalities[0] === "Text") { return { label: "Image → Text", emoji: "📷" } } if (inputModalities[0] === "Tabular" && outputModalities[0] === "Tabular") { return { label: "Tabular", emoji: "📊" } } } // Multimodal cases if (inputModalities.length > 1 || outputModalities.length > 1) { return { label: "Multimodal", emoji: "🤖" } } // Fallback return { label: `${inputStr} → ${outputStr}`, emoji: "⚡" } } const getUniqueCount = (lists: string[][]) => { const set = new Set() lists.forEach((list) => (list || []).forEach((item) => set.add(item))) return set.size } const capTotalComputed = getUniqueCount([ evaluation.capabilityEval.strongCategories, evaluation.capabilityEval.adequateCategories, evaluation.capabilityEval.weakCategories, evaluation.capabilityEval.insufficientCategories, ]) const riskTotalComputed = getUniqueCount([ evaluation.riskEval.strongCategories, evaluation.riskEval.adequateCategories, evaluation.riskEval.weakCategories, evaluation.riskEval.insufficientCategories, ]) const calculateCompletenessScore = () => { const weights = { strong: 4, adequate: 3, weak: 2, insufficient: 1 } const capTotal = capTotalComputed const riskTotal = riskTotalComputed if (capTotal === 0 && riskTotal === 0) { return "0.0" } let capScore = 0 if (capTotal > 0) { capScore = ((evaluation.capabilityEval.strong * weights.strong + evaluation.capabilityEval.adequate * weights.adequate + evaluation.capabilityEval.weak * weights.weak + evaluation.capabilityEval.insufficient * weights.insufficient) / (capTotal * 4)) * 100 } let riskScore = 0 if (riskTotal > 0) { riskScore = ((evaluation.riskEval.strong * weights.strong + evaluation.riskEval.adequate * weights.adequate + evaluation.riskEval.weak * weights.weak + evaluation.riskEval.insufficient * weights.insufficient) / (riskTotal * 4)) * 100 } const totalApplicable = capTotal + riskTotal const weightedScore = (capScore * capTotal + riskScore * riskTotal) / totalApplicable // Ensure we return a valid number return isNaN(weightedScore) ? "0.0" : weightedScore.toFixed(1) } const handleViewDetails = () => { router.push(`/evaluation/${evaluation.id}`) } const completenessScore = Number.parseFloat(calculateCompletenessScore()) return (
{evaluation.systemName}

{evaluation.provider}

{/* Enhanced modality badge with emoji and hover detail */} {(() => { const info = getModalityDisplay(evaluation.inputModalities, evaluation.outputModalities) return ( {info.emoji ? {info.emoji} : null} {info.label}
Input: {evaluation.inputModalities.join(", ")}
Output: {evaluation.outputModalities.join(", ")}
) })()}
View Details Export Report onDelete(evaluation.id)} className="text-destructive"> Delete
{/* Top row: Key metrics */}
Completeness
{completenessScore}%
Submitted

{evaluation.completedDate}

{/* Quick summary stats */}
Capability Eval ({evaluation.capabilityEval.totalApplicable} applicable)
0 ? 1 : 0.2, flexGrow: evaluation.capabilityEval.strong }} />
Strong: {evaluation.capabilityEval.strong} categories - Most evals reported
0 ? 1 : 0.2, flexGrow: evaluation.capabilityEval.adequate }} />
Adequate: {evaluation.capabilityEval.adequate} categories - Many evals reported
0 ? 1 : 0.2, flexGrow: evaluation.capabilityEval.weak }} />
Weak: {evaluation.capabilityEval.weak} categories - Some evals reported
0 ? 1 : 0.2, flexGrow: evaluation.capabilityEval.insufficient }} />
Insufficient: {evaluation.capabilityEval.insufficient} categories - Few evals reported
Risk Eval ({evaluation.riskEval.totalApplicable} applicable)
0 ? 1 : 0.2, flexGrow: evaluation.riskEval.strong }} />
Strong: {evaluation.riskEval.strong} categories - Most evals reported
0 ? 1 : 0.2, flexGrow: evaluation.riskEval.adequate }} />
Adequate: {evaluation.riskEval.adequate} categories - Many evals reported
0 ? 1 : 0.2, flexGrow: evaluation.riskEval.weak }} />
Weak: {evaluation.riskEval.weak} categories - Some evals reported
0 ? 1 : 0.2, flexGrow: evaluation.riskEval.insufficient }} />
Insufficient: {evaluation.riskEval.insufficient} categories - Few evals reported
) }