summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/history/TimelineEventCard.tsx
blob: f48466f32e7305e997fce5afadad67ab7b0d7703 (plain) (tree)










































































































































                                                                                                                                   
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>
  );
}