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).
101 lines
2.9 KiB
TypeScript
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>
|
|
);
|
|
}
|