import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { applyFixToBranch, cancelTicket, deleteWorktreeCmd, getTicketResult, getWorktreeDiff, retryTicket, } from "../../lib/api"; import { getErrorMessage } from "../../lib/errors"; import type { ProcessedTicket, Worktree } from "../../lib/types"; import ConfirmModal from "../ui/ConfirmModal"; function statusBadgeClass(status: string): string { switch (status) { case "Pending": return "bg-yellow-100 text-yellow-700"; case "Analyzing": return "bg-blue-100 text-blue-700"; case "Developing": return "bg-purple-100 text-purple-700"; case "Done": return "bg-green-100 text-green-700"; case "Error": return "bg-red-100 text-red-700"; case "Cancelled": return "bg-gray-100 text-gray-500"; default: return "bg-gray-100 text-gray-700"; } } function DiffViewer({ diff }: { diff: string }) { if (!diff) { return
No changes detected.
; } const lines = diff.split("\n"); return (
      {lines.map((line, i) => {
        let cls = "";
        if (line.startsWith("+++") || line.startsWith("---")) cls = "text-gray-400";
        else if (line.startsWith("+")) cls = "bg-green-900/20 text-green-400";
        else if (line.startsWith("-")) cls = "bg-red-900/20 text-red-400";
        else if (line.startsWith("@@")) cls = "text-blue-400";
        else if (line.startsWith("diff ")) cls = "font-bold text-yellow-400";
        return (
          
{line}
); })}
); } export default function TicketDetail() { const { ticketId } = useParams(); const navigate = useNavigate(); const [ticket, setTicket] = useState(null); const [worktree, setWorktree] = useState(null); const [diff, setDiff] = useState(null); const [targetBranch, setTargetBranch] = useState(""); const [tab, setTab] = useState<"info" | "analyst" | "developer" | "diff">("info"); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [isDeleteWorktreeModalOpen, setIsDeleteWorktreeModalOpen] = useState(false); async function loadData() { if (!ticketId) return; try { const result = await getTicketResult(ticketId); setTicket(result.ticket); setWorktree(result.worktree); if (result.ticket.developer_report) setTab("developer"); else if (result.ticket.analyst_report) setTab("analyst"); if (result.worktree && result.worktree.status === "Active") { try { const d = await getWorktreeDiff(result.worktree.id); setDiff(d); } catch { setDiff(null); } } else { setDiff(null); } } catch (err) { setError(getErrorMessage(err)); } } useEffect(() => { loadData(); }, [ticketId]); async function handleRetry() { if (!ticketId) return; setLoading(true); try { await retryTicket(ticketId); await loadData(); } catch (err) { setError(getErrorMessage(err)); } setLoading(false); } async function handleCancel() { if (!ticketId) return; setLoading(true); try { await cancelTicket(ticketId); await loadData(); } catch (err) { setError(getErrorMessage(err)); } setLoading(false); } async function handleApplyFix() { if (!worktree || !targetBranch) return; setLoading(true); setError(""); try { await applyFixToBranch(worktree.id, targetBranch); await loadData(); } catch (err) { setError(getErrorMessage(err)); } setLoading(false); } async function handleDeleteWorktree() { if (!worktree) return; setIsDeleteWorktreeModalOpen(false); setLoading(true); try { await deleteWorktreeCmd(worktree.id); setWorktree(null); setDiff(null); } catch (err) { setError(getErrorMessage(err)); } setLoading(false); } if (!ticket) { return
Loading...
; } const tabs = [ { key: "info" as const, label: "Info" }, { key: "analyst" as const, label: "Analyst Report", disabled: !ticket.analyst_report, }, { key: "developer" as const, label: "Developer Report", disabled: !ticket.developer_report, }, { key: "diff" as const, label: "Diff", disabled: !diff && !worktree }, ]; return (

#{ticket.artifact_id} {ticket.artifact_title} {ticket.status}

{(ticket.status === "Error" || ticket.status === "Done" || ticket.status === "Cancelled") && ( )} {(ticket.status === "Pending" || ticket.status === "Analyzing" || ticket.status === "Developing") && ( )}
{error && (
{error}
)}
{tabs.map((t) => ( ))}
{tab === "info" && (
Status: {ticket.status}
Detected: {new Date(ticket.detected_at).toLocaleString()}
{ticket.processed_at && (
Processed: {new Date(ticket.processed_at).toLocaleString()}
)} {worktree && (
Worktree: {worktree.branch_name} {worktree.status}
)}
{worktree && worktree.status === "Active" && (

Worktree Actions

setTargetBranch(e.target.value)} className="flex-1 rounded border border-gray-300 px-3 py-1.5 text-sm focus:border-transparent focus:ring-2 focus:ring-blue-500" />
)} {worktree && worktree.status === "Merged" && (
Fix applied to branch: {worktree.merged_into}
)}
)} {tab === "analyst" && ticket.analyst_report && (
{ticket.analyst_report}
)} {tab === "developer" && ticket.developer_report && (
{ticket.developer_report}
)} {tab === "diff" && } setIsDeleteWorktreeModalOpen(false)} onConfirm={() => void handleDeleteWorktree()} />
); }