'use client'; import { useState } from 'react'; import { FilterSelect } from '@/components/FilterSelect'; import { formatDateTime } from '@/lib/format'; import type { Requirement } from '@/lib/requirement'; import type { PlanTask, RequirementCoverageStatus, VersionPlan, VersionPlanLog, VersionPlanLogView } from '@/lib/version-plan'; import { canSaveRequirementCoverageDraft, canOpenRequirementCoverageRecord, getResearchDirectionProgressSummary, getResearchDirectionStatus, getRequirementCoverage, getRequirementCoverageStatus, getRequirementCoverageSummary, REQUIREMENT_COVERAGE_LABEL, startResearchDirectionWork, startRequirementCoverageWork, updateResearchDirectionProgress, 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 DirectionProps { plan: VersionPlan; canEdit: boolean; currentUserName: string; onUpdate: (id: string, data: Partial) => void; } interface LinkedRequirementReferenceProps { requirements: RequirementOption[]; } interface LogTimelineProps { logs?: Array; className?: string; fillHeight?: boolean; } 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 getLogTone(log: VersionPlanLog): { badge: string } { if (log.aiStatus === 'error') { return { badge: 'border-red-200 bg-red-50 text-red-700', }; } if (log.type === 'ai_decompose') { return { badge: 'border-violet-200 bg-violet-50 text-violet-700', }; } if (log.coverageStatus === 'completed') { return { badge: 'border-emerald-200 bg-emerald-50 text-emerald-700', }; } if (log.coverageStatus === 'partial') { return { badge: 'border-amber-200 bg-amber-50 text-amber-700', }; } return { badge: 'border-zinc-200 bg-zinc-50 text-zinc-600', }; } function getLogTypeLabel(log: VersionPlanLog): string { if (log.type === 'ai_decompose') return 'AI 拆解'; if (log.type === 'research_direction_progress') return '调研方向'; if (log.type === 'requirement_progress') return '需求进度'; return '系统记录'; } function getLogMonthKey(createdAt: string): string | null { const date = new Date(createdAt); if (Number.isNaN(date.getTime())) return null; return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; } function getLogMonthLabel(monthKey: string): string { const [year, month] = monthKey.split('-'); return `${year}年${month}月`; } export function PlanRequirementCoveragePanel({ plan, requirements, canEdit, currentUserName, onUpdate }: CoverageProps) { const [editingRequirementId, setEditingRequirementId] = useState(null); 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); setCompletedContent(coverage?.completedContent ?? ''); setRemainingContent(coverage?.remainingContent ?? ''); }; const closeEditor = () => { setEditingRequirementId(null); setCompletedContent(''); setRemainingContent(''); }; const startCoverage = (req: RequirementOption) => { if (!canEdit) return; const patch = startRequirementCoverageWork(plan, { requirementId: req.id, updatedBy: currentUserName, }); onUpdate(plan.id, patch); }; const saveCoverage = (req: RequirementOption, status: Extract) => { const coverage = getRequirementCoverage(plan, req.id); const workStartedAt = coverage?.currentWorkStartedAt; if (!canEdit || !canSaveRequirementCoverageDraft(status, completedContent, remainingContent, workStartedAt)) return; const patch = updateRequirementCoverage(plan, { requirementId: req.id, status, completedContent: status === 'partial' ? completedContent : undefined, remainingContent: status === '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; const hasStartedWork = Boolean(coverage?.currentWorkStartedAt); const canOpenRecord = canOpenRequirementCoverageRecord(status, canEdit) && hasStartedWork; const canStartWork = canEdit && status !== 'completed' && !hasStartedWork; const canSaveCompleted = canSaveRequirementCoverageDraft('completed', undefined, undefined, coverage?.currentWorkStartedAt); const canSavePartial = canSaveRequirementCoverageDraft('partial', completedContent, remainingContent, coverage?.currentWorkStartedAt); return (
{REQUIREMENT_COVERAGE_LABEL[status]}
{req.code} {req.title} {req.isHistorical && 历史}
{(coverage?.completedContent || coverage?.remainingContent || coverage?.currentWorkStartedAt) && (
{coverage.completedContent &&
已完成:{coverage.completedContent}
} {coverage.remainingContent &&
剩余:{coverage.remainingContent}
} {coverage.currentWorkStartedAt &&
已开始:{formatDateTime(coverage.currentWorkStartedAt)}
}
)}
{canEdit && status !== 'completed' && (
{canStartWork && ( )}
)}
{isEditing && (