"use client"; import { useEffect, useMemo, useState } from "react"; import { ExternalLink, Loader2 } 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"; type ForgejoIssueDetailDialogProps = { issue: ForgejoIssue | null; repositoryName: string; open: boolean; onOpenChange: (open: boolean) => void; }; 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, }: ForgejoIssueDetailDialogProps) { const [detail, setDetail] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!open || !issue) return; let cancelled = false; (async () => { setIsLoading(true); setError(null); try { const result = await getForgejoIssue(issue.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; }; }, [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"; return (
{active.title} {repositoryName} #{active.forgejo_issue_number} {active.state} Opened {formatDateTime(active.forgejo_created_at)} Updated {formatDateTime(active.forgejo_updated_at)}
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 on this issue.
) : ( 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}
); }) )}
); }