130 lines
4.3 KiB
TypeScript
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>
|
|
);
|
|
}
|