"use client"; import { useMemo, useState } from "react"; import { Flag, Plus } from "lucide-react"; import { AppShell } from "@/components/AppShell"; import { Badge } from "@/components/ui/Badge"; import { Button } from "@/components/ui/Button"; import { Card } from "@/components/ui/Card"; import { EmptyState } from "@/components/ui/EmptyState"; import { PageHeader } from "@/components/ui/PageHeader"; import type { ForeshadowView, ProjectResponse } from "@/lib/api/types"; import { LANES, LANE_LABELS, countByStatus, groupByStatus, type ForeshadowStatus, } from "@/lib/foreshadow/board"; import type { BadgeVariant } from "@/lib/ui/variants"; import { useForeshadow } from "@/lib/foreshadow/useForeshadow"; import { KanbanColumn } from "./KanbanColumn"; import { RegisterForm } from "./RegisterForm"; interface ForeshadowBoardProps { project: ProjectResponse; initialItems: ForeshadowView[]; } // 伏笔看板主体(UX §6.8):四泳道 + 登记/状态变更交互。 // RSC 取全量种入;Client 承载登记/转移(乐观 + 回滚 + Toast)。 export function ForeshadowBoard({ project, initialItems, }: ForeshadowBoardProps) { const { items, busy, register, transition } = useForeshadow(initialItems); const [registerOpen, setRegisterOpen] = useState(false); const lanes = useMemo(() => groupByStatus(items), [items]); const counts = useMemo(() => countByStatus(items), [items]); const onTransition = ( code: string, toStatus: ForeshadowStatus | null, progressNote: string, ): void => { void transition(code, { projectId: project.id, toStatus, progressEntry: progressNote ? { note: progressNote } : null, }); }; return (
setRegisterOpen(true)} variant="primary" size="sm" >
); } const SUMMARY_VARIANTS: Record = { OPEN: "accent", PARTIAL: "info", CLOSED: "success", OVERDUE: "warning", }; function ForeshadowBoardSummary({ counts, }: { counts: Record; }) { const total = LANES.reduce((sum, lane) => sum + counts[lane], 0); return (
{LANES.map((status) => (
{LANE_LABELS[status]} {counts[status]}

占比{" "} {total > 0 ? `${Math.round((counts[status] / total) * 100)}%` : "0%"}

))}
); }