Files
ThothII/frontend/src/viewers/PhaseSummaryViewer.tsx
T

130 lines
4.3 KiB
TypeScript

import { Badge } from "../components/ui/badge";
import { MarkdownView } from "./MarkdownView";
import { statusBadgeClass } from "./statusBadge";
import {
phaseOpenQuestionText,
type PhaseCheck,
type PhaseSection,
type PhaseSectionItem,
type PhaseSummaryV2,
type PhaseTable,
} from "./artifactV2";
const CODE_CHIP = "rounded-md bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground/90";
function Check({ check }: { check: PhaseCheck }) {
return (
<div className="flex items-start gap-2">
<Badge variant="outline" className={statusBadgeClass(check.status)}>{check.status}</Badge>
<div className="flex flex-col">
<span className="text-sm text-foreground/90">{check.label}</span>
{check.detail && <span className="text-xs text-muted-foreground">{check.detail}</span>}
</div>
</div>
);
}
function SectionItemRow({ item }: { item: PhaseSectionItem }) {
const tableColumn = item.table && item.column ? `${item.table}.${item.column}` : item.table ?? item.column;
return (
<div className="flex flex-col gap-0.5">
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-foreground/90">{item.label}</span>
{tableColumn && <span className={CODE_CHIP}>{tableColumn}</span>}
{item.value && <Badge variant="outline">{item.value}</Badge>}
</div>
{item.description && <p className="text-xs text-muted-foreground">{item.description}</p>}
{item.rationale && <p className="text-xs italic text-muted-foreground">{item.rationale}</p>}
</div>
);
}
function Section({ section }: { section: PhaseSection }) {
return (
<div>
<h4 className="thot-label mb-1.5">{section.title}</h4>
<div className="flex flex-col gap-2">
{(section.items ?? []).map((item, i) => (
<SectionItemRow key={i} item={item} />
))}
</div>
</div>
);
}
function TableRecap({ table }: { table: PhaseTable }) {
return (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<span className="font-mono text-sm">{table.name}</span>
<Badge variant="outline" className={table.role === "promoted" ? statusBadgeClass(table.role) : undefined}>{table.role}</Badge>
</div>
{table.description && <p className="text-xs text-muted-foreground">{table.description}</p>}
{table.columns && table.columns.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{table.columns.map((c) => (
<div key={c.name} className="flex flex-col">
<span className={CODE_CHIP}>{c.name}</span>
{c.description && <span className="text-xs text-muted-foreground">{c.description}</span>}
{c.value_filter && <span className={CODE_CHIP}>{c.value_filter}</span>}
</div>
))}
</div>
)}
</div>
);
}
export function PhaseSummaryViewer({ phase }: { phase: PhaseSummaryV2 }) {
const openQuestions = Array.isArray(phase.open_questions)
? (phase.open_questions as unknown[])
.map(phaseOpenQuestionText)
.filter((question): question is string => question !== null)
: [];
return (
<div className="flex flex-col gap-4">
<h3 className="thot-label">
{phase.phase.id} — {phase.phase.name}
</h3>
{phase.summary && <MarkdownView source={phase.summary} />}
{phase.checks && phase.checks.length > 0 && (
<div className="flex flex-col gap-2">
{phase.checks.map((c, i) => (
<Check key={i} check={c} />
))}
</div>
)}
{phase.sections && phase.sections.length > 0 && (
<div className="flex flex-col gap-4">
{phase.sections.map((s, i) => (
<Section key={i} section={s} />
))}
</div>
)}
{phase.tables && phase.tables.length > 0 && (
<div className="flex flex-col gap-3">
{phase.tables.map((t) => (
<TableRecap key={t.name} table={t} />
))}
</div>
)}
{openQuestions.length > 0 && (
<div>
<h4 className="thot-label mb-1">Open questions</h4>
<ul className="list-disc space-y-1 pl-5 text-sm text-foreground/90 marker:text-muted-foreground">
{openQuestions.map((q, i) => (
<li key={i}>{q}</li>
))}
</ul>
</div>
)}
</div>
);
}