import type { HistoryEvent } from "../../lib/api";
interface TimelineEventCardProps {
event: HistoryEvent;
isSelected: boolean;
onClick: () => void;
}
// Get icon and color based on event type
function getEventStyle(eventType: string, eventSubtype: string | null) {
const type = eventType.toLowerCase();
const subtype = eventSubtype?.toLowerCase();
if (type === "task") {
if (subtype === "created") return { icon: "+", color: "text-[#9bc3ff]" };
if (subtype === "started") return { icon: "\u25B6", color: "text-green-400" };
if (subtype === "completed") return { icon: "\u2713", color: "text-emerald-400" };
if (subtype === "failed") return { icon: "\u2717", color: "text-red-400" };
if (subtype === "stopped") return { icon: "\u25A0", color: "text-yellow-400" };
return { icon: "\u2022", color: "text-[#9bc3ff]" };
}
if (type === "checkpoint") {
return { icon: "\u2691", color: "text-purple-400" };
}
if (type === "phase") {
return { icon: "\u21B3", color: "text-cyan-400" };
}
if (type === "chat") {
return { icon: "\u2709", color: "text-[#75aafc]" };
}
if (type === "contract") {
return { icon: "\u2606", color: "text-[#9bc3ff]" };
}
if (type === "file") {
return { icon: "\u2630", color: "text-[#7788aa]" };
}
return { icon: "\u2022", color: "text-[#7788aa]" };
}
// Format relative time
function formatRelativeTime(dateStr: string): string {
const date = new Date(dateStr);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHour / 24);
if (diffSec < 60) return "just now";
if (diffMin < 60) return `${diffMin}m ago`;
if (diffHour < 24) return `${diffHour}h ago`;
if (diffDay < 7) return `${diffDay}d ago`;
return date.toLocaleDateString();
}
// Extract a preview from event data
function getEventPreview(event: HistoryEvent): string {
const data = event.eventData as Record<string, unknown>;
// Task events
if (data.taskName) return String(data.taskName);
if (data.name) return String(data.name);
// Chat events
if (data.message) {
const msg = String(data.message);
return msg.length > 50 ? msg.slice(0, 50) + "..." : msg;
}
// Checkpoint events
if (data.checkpointMessage) return String(data.checkpointMessage);
if (data.commitSha) return `Commit ${String(data.commitSha).slice(0, 7)}`;
// Phase events
if (data.phase) return `Phase: ${data.phase}`;
// Contract events
if (data.contractName) return String(data.contractName);
return "";
}
export function TimelineEventCard({ event, isSelected, onClick }: TimelineEventCardProps) {
const { icon, color } = getEventStyle(event.eventType, event.eventSubtype);
const preview = getEventPreview(event);
return (
<button
onClick={onClick}
className={`w-full text-left p-3 transition-colors ${
isSelected
? "bg-[rgba(63,111,179,0.2)] border-l-2 border-[#3f6fb3]"
: "hover:bg-[rgba(117,170,252,0.05)] border-l-2 border-transparent"
}`}
>
<div className="flex items-start gap-3">
{/* Icon */}
<div className={`font-mono text-sm ${color}`}>{icon}</div>
{/* Content */}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<div className="font-mono text-xs text-[#9bc3ff] uppercase truncate">
{event.eventType}
{event.eventSubtype && (
<span className="text-[#7788aa]"> / {event.eventSubtype}</span>
)}
</div>
<div className="font-mono text-[10px] text-[#556677] shrink-0">
{formatRelativeTime(event.createdAt)}
</div>
</div>
{preview && (
<div className="font-mono text-[10px] text-[#7788aa] mt-1 truncate">
{preview}
</div>
)}
{event.phase && (
<div className="mt-1">
<span className="font-mono text-[9px] text-[#75aafc] uppercase px-1.5 py-0.5 border border-[rgba(117,170,252,0.25)]">
{event.phase}
</span>
</div>
)}
</div>
</div>
</button>
);
}