Files
Codewalkers/packages/web/src/components/InitiativeList.tsx
Lukas May 6fa025251e feat: Wire up initiative deletion end-to-end
Add deleteInitiative tRPC procedure, wire Delete button in InitiativeCard
with confirm dialog (Shift+click bypass), remove unused onDelete prop chain.
Fix agents table FK constraints (initiative_id, account_id missing ON DELETE
SET NULL) via table recreation migration. Register conversations migration
in journal. Expand cascade delete tests to cover pages, projects, change
sets, agents (set null), and conversations (set null).
2026-02-18 17:54:53 +09:00

101 lines
2.9 KiB
TypeScript

import { AlertCircle, Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/Skeleton";
import { InitiativeCard } from "@/components/InitiativeCard";
import { trpc } from "@/lib/trpc";
interface InitiativeListProps {
statusFilter?: "all" | "active" | "completed" | "archived";
onCreateNew: () => void;
onViewInitiative: (id: string) => void;
onSpawnArchitect: (
initiativeId: string,
mode: "discuss" | "plan",
) => void;
}
export function InitiativeList({
statusFilter = "all",
onCreateNew,
onViewInitiative,
onSpawnArchitect,
}: InitiativeListProps) {
const initiativesQuery = trpc.listInitiatives.useQuery(
statusFilter === "all" ? undefined : { status: statusFilter },
);
// Loading state
if (initiativesQuery.isLoading) {
return (
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<Card key={i} className="p-4">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<Skeleton className="h-5 w-48" />
<div className="flex flex-1 items-center gap-4">
<Skeleton className="h-5 w-16" />
<Skeleton className="h-2 w-32" />
<Skeleton className="h-4 w-24" />
</div>
</div>
</Card>
))}
</div>
);
}
// Error state
if (initiativesQuery.isError) {
return (
<div className="flex flex-col items-center justify-center gap-4 py-12">
<AlertCircle className="h-8 w-8 text-destructive" />
<p className="text-sm text-destructive">
Failed to load initiatives: {initiativesQuery.error.message}
</p>
<Button
variant="outline"
size="sm"
onClick={() => initiativesQuery.refetch()}
>
Retry
</Button>
</div>
);
}
const initiatives = initiativesQuery.data ?? [];
// Empty state
if (initiatives.length === 0) {
return (
<div className="flex flex-col items-center justify-center gap-4 py-16">
<p className="text-lg font-medium text-muted-foreground">
No initiatives yet
</p>
<p className="text-sm text-muted-foreground">
Create your first initiative to start planning and executing work.
</p>
<Button onClick={onCreateNew}>
<Plus className="mr-1 h-4 w-4" />
New Initiative
</Button>
</div>
);
}
// Populated state
return (
<div className="space-y-3">
{initiatives.map((initiative) => (
<InitiativeCard
key={initiative.id}
initiative={initiative}
onView={() => onViewInitiative(initiative.id)}
onSpawnArchitect={(mode) => onSpawnArchitect(initiative.id, mode)}
/>
))}
</div>
);
}