feat(17-02): create InitiativeCard component

- Renders initiative name, StatusBadge, ProgressBar, and phase count
- Fetches phase stats per-card via trpc.listPhases (self-contained)
- Spawn Architect dropdown with discuss/breakdown modes
- More actions menu with edit/duplicate/archive/delete
- Responsive: stacks vertically on mobile, hides phase count text
- Card clickable with stopPropagation on action buttons
This commit is contained in:
Lukas May
2026-02-04 21:03:44 +01:00
parent ff9c17c05b
commit ec93835ae5

View File

@@ -0,0 +1,118 @@
import { MoreHorizontal, Eye, Bot } from "lucide-react";
import type { Initiative } from "@codewalk-district/shared";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { StatusBadge } from "@/components/StatusBadge";
import { ProgressBar } from "@/components/ProgressBar";
import { trpc } from "@/lib/trpc";
interface InitiativeCardProps {
initiative: Initiative;
onView: () => void;
onSpawnArchitect: (mode: "discuss" | "breakdown") => void;
onDelete: () => void;
}
export function InitiativeCard({
initiative,
onView,
onSpawnArchitect,
onDelete,
}: InitiativeCardProps) {
// Each card fetches its own phase stats (N+1 acceptable for v1 small counts)
const phasesQuery = trpc.listPhases.useQuery({
initiativeId: initiative.id,
});
const phases = phasesQuery.data ?? [];
const completedCount = phases.filter((p) => p.status === "completed").length;
const totalCount = phases.length;
return (
<Card
className="cursor-pointer p-4 transition-colors hover:bg-accent/50"
onClick={onView}
>
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
{/* Left: Initiative name */}
<div className="min-w-0 flex-shrink-0">
<span className="text-base font-bold">{initiative.name}</span>
</div>
{/* Middle: Status + Progress + Phase count */}
<div className="flex flex-1 items-center gap-4">
<StatusBadge status={initiative.status} />
<ProgressBar
completed={completedCount}
total={totalCount}
className="w-32"
/>
<span className="hidden text-sm text-muted-foreground md:inline">
{completedCount}/{totalCount} phases
</span>
</div>
{/* Right: Action buttons */}
<div
className="flex items-center gap-2"
onClick={(e) => e.stopPropagation()}
>
<Button variant="outline" size="sm" onClick={onView}>
<Eye className="mr-1 h-4 w-4" />
View
</Button>
{/* Spawn Architect Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
<Bot className="mr-1 h-4 w-4" />
Spawn Architect
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => onSpawnArchitect("discuss")}
>
Discuss
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onSpawnArchitect("breakdown")}
>
Breakdown
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* More Actions Menu */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={onView}>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuItem>Archive</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-destructive"
onClick={onDelete}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</Card>
);
}