'use client'; import { useState } from 'react'; import { Check, Clock3, Sparkles } from 'lucide-react'; import { formatDateTime } from '@/lib/format'; import type { Requirement } from '@/lib/requirement'; import type { RequirementCoverageStatus, VersionPlan, VersionPlanLog } from '@/lib/version-plan'; import { getRequirementCoverage, getRequirementCoverageStatus, getRequirementCoverageSummary, REQUIREMENT_COVERAGE_LABEL, updateRequirementCoverage, } from '@/lib/version-plan'; type RequirementOption = Pick & { isHistorical?: boolean }; interface CoverageProps { plan: VersionPlan; requirements: RequirementOption[]; canEdit: boolean; currentUserName: string; onUpdate: (id: string, data: Partial) => void; } interface LogTimelineProps { logs?: VersionPlanLog[]; className?: string; } const COVERAGE_STATUS_OPTIONS: RequirementCoverageStatus[] = ['partial', 'completed', 'not_started']; const COVERAGE_BADGE_STYLE: Record = { not_started: 'border-zinc-200 bg-zinc-50 text-zinc-500', partial: 'border-amber-200 bg-amber-50 text-amber-700', completed: 'border-emerald-200 bg-emerald-50 text-emerald-700', }; function getLogIcon(log: VersionPlanLog) { if (log.type === 'ai_decompose') return ; if (log.type === 'requirement_progress') return ; return ; } function getLogTone(log: VersionPlanLog): string { if (log.aiStatus === 'error') return 'bg-red-50 text-red-700 ring-red-100'; if (log.type === 'ai_decompose') return 'bg-purple-50 text-purple-700 ring-purple-100'; if (log.coverageStatus === 'completed') return 'bg-emerald-50 text-emerald-700 ring-emerald-100'; if (log.coverageStatus === 'partial') return 'bg-amber-50 text-amber-700 ring-amber-100'; return 'bg-zinc-50 text-zinc-600 ring-zinc-100'; } export function PlanRequirementCoveragePanel({ plan, requirements, canEdit, currentUserName, onUpdate }: CoverageProps) { const [editingRequirementId, setEditingRequirementId] = useState(null); const [draftStatus, setDraftStatus] = useState('partial'); const [completedContent, setCompletedContent] = useState(''); const [remainingContent, setRemainingContent] = useState(''); const summary = getRequirementCoverageSummary(plan); if (requirements.length === 0) return null; const openEditor = (req: RequirementOption) => { const coverage = getRequirementCoverage(plan, req.id); setEditingRequirementId(req.id); setDraftStatus(coverage?.status === 'completed' ? 'completed' : coverage?.status === 'not_started' ? 'not_started' : 'partial'); setCompletedContent(coverage?.completedContent ?? ''); setRemainingContent(coverage?.remainingContent ?? ''); }; const closeEditor = () => { setEditingRequirementId(null); setCompletedContent(''); setRemainingContent(''); setDraftStatus('partial'); }; const canSave = draftStatus === 'not_started' || (draftStatus === 'completed' && completedContent.trim().length > 0) || (draftStatus === 'partial' && completedContent.trim().length > 0 && remainingContent.trim().length > 0); const saveCoverage = (req: RequirementOption) => { if (!canEdit || !canSave) return; const patch = updateRequirementCoverage(plan, { requirementId: req.id, status: draftStatus, completedContent: draftStatus === 'not_started' ? undefined : completedContent, remainingContent: draftStatus === 'partial' ? remainingContent : undefined, updatedBy: currentUserName, requirementCode: req.code, requirementTitle: req.title, }); onUpdate(plan.id, patch); closeEditor(); }; return (
引用需求
完全完成 {summary.completed} / {summary.total} {summary.partial > 0 && 部分完成 {summary.partial}}
{summary.percent}%
{requirements.map((req) => { const status = getRequirementCoverageStatus(plan, req.id); const coverage = getRequirementCoverage(plan, req.id); const isEditing = editingRequirementId === req.id; return (
{REQUIREMENT_COVERAGE_LABEL[status]}
{req.code} {req.title} {req.isHistorical && 历史}
{(coverage?.completedContent || coverage?.remainingContent) && (
{coverage.completedContent &&
已完成:{coverage.completedContent}
} {coverage.remainingContent &&
剩余:{coverage.remainingContent}
}
)}
{canEdit && ( )}
{isEditing && (
{COVERAGE_STATUS_OPTIONS.map((statusOption) => ( ))}
{draftStatus !== 'not_started' && (