'use client'; import { Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import type { AggregatedFile, AggregatedGroup, AggregatedWorkflow, } from '@/hooks/use-aggregated-listing'; /** * Discriminated-union of the two item shapes the aggregated projection * surfaces (files / workflows). Keeps `renderRow` strictly typed so callers * don't have to widen to `unknown` or recast inside the row renderer. */ type AggregatedItemKind = | { kind: 'files'; items: AggregatedFile[] } | { kind: 'workflows'; items: AggregatedWorkflow[] }; type ItemOfKind = Extract< AggregatedItemKind, { kind: K } >['items'][number]; interface AggregatedSectionProps { title: string; icon?: React.ReactNode; groups: AggregatedGroup>[]; renderRow: (item: ItemOfKind, group: AggregatedGroup>) => React.ReactNode; emptyMessage?: string; loading?: boolean; onShowAll?: (group: AggregatedGroup>) => void; } /** * Renders a Signing or Files section with one labelled subsection per * owner-source group. Each group shows up to 20 rows; a `Show all (N)` * link drills into the source-scoped flat list. Hidden when groups is * empty. */ export function AggregatedSection({ title, icon, groups, renderRow, emptyMessage = 'Nothing here yet.', loading, onShowAll, }: AggregatedSectionProps) { const total = groups.reduce((sum, g) => sum + g.total, 0); if (loading) { return (

{icon} {title}

); } if (groups.length === 0) { return (

{icon} {title} · 0

{emptyMessage}

); } return (

{icon} {title} · {total}

{groups.map((g) => ( key={`${g.source}-${g.label}`} group={g} renderRow={renderRow} onShowAll={onShowAll} /> ))}
); } function GroupBlock({ group, renderRow, onShowAll, }: { group: AggregatedGroup>; renderRow: (item: ItemOfKind, group: AggregatedGroup>) => React.ReactNode; onShowAll?: (group: AggregatedGroup>) => void; }) { // The server always sets exactly one of `files` / `workflows` per group; // unify them into a single list for rendering. The discriminated-union // generic on `AggregatedSection` keeps the row type correct upstream. const items = ((group.files ?? group.workflows ?? []) as unknown) as ItemOfKind[]; return (
{group.label} · {group.total}
    {items.map((item) => (
  • {renderRow(item, group)}
  • ))}
{group.total > items.length ? ( ) : null}
); }