"use client"; import { useEffect, useMemo, useState } from "react"; import { ExternalLink, Loader2, MessageSquarePlus, Pencil, XCircle } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Markdown } from "@/components/atoms/Markdown"; import { getForgejoIssue, type ForgejoIssue, type ForgejoIssueDetail, } from "@/lib/api-forgejo"; import { CloseForgejoIssueDialog } from "@/components/git/CloseForgejoIssueDialog"; import { EditForgejoIssueDialog } from "@/components/git/EditForgejoIssueDialog"; import { PostForgejoCommentDialog } from "@/components/git/PostForgejoCommentDialog"; type ForgejoIssueDetailDialogProps = { issue: ForgejoIssue | null; repositoryName: string; open: boolean; onOpenChange: (open: boolean) => void; onRefresh?: () => void; canClose?: boolean; }; const formatDateTime = (value: string | null | undefined): string => { if (!value) return "—"; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short", }); }; const objectList = (value: unknown): Record[] => { if (!Array.isArray(value)) return []; return value.filter( (item): item is Record => typeof item === "object" && item !== null, ); }; const asString = (value: unknown): string | null => typeof value === "string" && value.trim() ? value : null; export function ForgejoIssueDetailDialog({ issue, repositoryName, open, onOpenChange, onRefresh, canClose = false, }: ForgejoIssueDetailDialogProps) { const [detail, setDetail] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState("overview"); const [isCommentDialogOpen, setIsCommentDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isCloseIssueDialogOpen, setIsCloseIssueDialogOpen] = useState(false); const loadDetail = (id: string) => { let cancelled = false; setIsLoading(true); setError(null); (async () => { try { const result = await getForgejoIssue(id); if (!cancelled) setDetail(result); } catch (err) { if (!cancelled) setError( err instanceof Error ? err.message : "Could not load issue details from Pipeline.", ); } finally { if (!cancelled) setIsLoading(false); } })(); return () => { cancelled = true; }; }; useEffect(() => { if (!open || !issue) return; return loadDetail(issue.id); }, [issue, open]); const comments = useMemo( () => objectList(detail?.forgejo_comments_payload), [detail?.forgejo_comments_payload], ); const timeline = useMemo( () => objectList(detail?.forgejo_timeline_payload), [detail?.forgejo_timeline_payload], ); const reactions = useMemo( () => objectList(detail?.forgejo_reactions_payload), [detail?.forgejo_reactions_payload], ); if (!issue) return null; const active = detail ?? issue; const body = detail?.body ?? issue.body ?? issue.body_preview ?? ""; const stateVariant = active.state === "open" ? "success" : "default"; const handleCloseIssueSuccess = () => { if (detail) setDetail({ ...detail, state: "closed" }); if (issue) loadDetail(issue.id); onRefresh?.(); }; const handleCommentSuccess = () => { if (issue) loadDetail(issue.id); onRefresh?.(); }; const handleEditSuccess = (updated: { title: string; body: string | null; state: string; }) => { if (detail) { setDetail({ ...detail, title: updated.title, body: updated.body, state: updated.state, }); } if (issue) loadDetail(issue.id); onRefresh?.(); }; return ( <>
{active.title} {repositoryName} #{active.forgejo_issue_number} {active.state} Opened {formatDateTime(active.forgejo_created_at)} Updated {formatDateTime(active.forgejo_updated_at)}
{canClose && active.state === "open" ? ( ) : null} Open in Forgejo
{isLoading ? (
Loading issue details…
) : null} {error ? (
{error}
) : null} Overview Comments ({comments.length}) Timeline ({timeline.length}) Reactions ({reactions.length})
{body ? ( ) : (

No issue body provided.

)}
{comments.length === 0 ? (
No comments yet.{" "}
) : ( <> {comments.map((comment, idx) => { const login = asString( comment.user && (comment.user as Record).login, ) ?? "Unknown"; const bodyText = asString(comment.body) ?? ""; return (
{login} {formatDateTime(asString(comment.created_at))}
{bodyText ? ( ) : (

No comment text.

)}
); })} )}
{timeline.length === 0 ? (
No timeline events found.
) : ( timeline.map((event, idx) => { const label = asString(event.type) ?? asString(event.action) ?? asString(event.event) ?? "event"; const actor = asString( event.user && (event.user as Record).login, ) ?? "system"; return (
{label}{" "} by {actor} {formatDateTime(asString(event.created_at))}
); }) )}
{reactions.length === 0 ? (
No reactions on this issue.
) : ( reactions.map((reaction, idx) => { const content = asString(reaction.content) ?? "reaction"; const login = asString( reaction.user && (reaction.user as Record).login, ) ?? "Unknown"; return (
{content} {login}
); }) )}
); }