feat(版本): 完善研发计划与预警已读
关键改动: - 增加版本表单、发布校验和调研方向进度规则 - 扩展小宝预警已读状态、风险签名和今日证据 - 补充组长权限、加班查看范围、活动记录与相关测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
@@ -1,24 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { X, Check, Link2, FileUp, ExternalLink, Play, ArrowRightLeft } from 'lucide-react';
|
||||
import { X, Link2, FileUp, ExternalLink, Play, ArrowRightLeft } from 'lucide-react';
|
||||
import { useVersionPlanStore } from '@/stores/useVersionPlanStore';
|
||||
import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||
import { useMemberStore } from '@/stores/useMemberStore';
|
||||
import { useAuthStore } from '@/stores/useAuthStore';
|
||||
import { FilterSelect } from '@/components/FilterSelect';
|
||||
import {
|
||||
calcPlanProgress,
|
||||
getResearchDirectionProgressSummary,
|
||||
getRequirementCoverageSummary,
|
||||
PRODUCT_PLAN_KIND_LABEL,
|
||||
PRODUCT_PLAN_REVIEW_FAILURE_OPTIONS,
|
||||
PRODUCT_PLAN_REVIEW_RESULT_LABEL,
|
||||
} from '@/lib/version-plan';
|
||||
import { formatDateTime } from '@/lib/format';
|
||||
import type { PlanTask, ProductPlanKind, ProductPlanReviewFailureType, ProductPlanReviewResult, VersionPlan } from '@/lib/version-plan';
|
||||
import { canEditPlanRequirementCoverage, canTogglePlanChecklist, getPlanCompletionState } from '@/lib/version-plan-workflow';
|
||||
import type { ProductPlanKind, ProductPlanReviewFailureType, ProductPlanReviewResult, VersionPlan } from '@/lib/version-plan';
|
||||
import { canEditPlanRequirementCoverage, getPlanCompletionState } from '@/lib/version-plan-workflow';
|
||||
import type { PlanResultPayload } from '@/lib/version-plan-workflow';
|
||||
import { PlanLogTimeline, PlanRequirementCoveragePanel } from './PlanRequirementCoveragePanel';
|
||||
import { PlanLinkedRequirementReferenceList, PlanLogTimeline, PlanRequirementCoveragePanel, PlanResearchDirectionPanel } from './PlanRequirementCoveragePanel';
|
||||
|
||||
interface Props {
|
||||
planId: string;
|
||||
@@ -70,23 +70,16 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
if (!plan) return null;
|
||||
|
||||
const completionState = getPlanCompletionState(plan);
|
||||
const isResearch = plan.type === 'research';
|
||||
const progress = isResearch ? calcPlanProgress(plan.tasks) : getRequirementCoverageSummary(plan).percent;
|
||||
const progress = plan.type === 'research'
|
||||
? getResearchDirectionProgressSummary(plan).percent
|
||||
: getRequirementCoverageSummary(plan).percent;
|
||||
const linkedReqs = (plan.linkedRequirementIds || []).map((id) => requirements.find((r) => r.id === id)).filter(Boolean) as { id: string; code: string; title: string }[];
|
||||
const canToggle = canTogglePlanChecklist(plan);
|
||||
const canEditCoverage = canEditPlanRequirementCoverage(plan);
|
||||
const currentUserName = user?.name ?? plan.owner;
|
||||
const productPlanKind = plan.type === 'product' ? getProductPlanKind(plan) : undefined;
|
||||
const isProductDesignPlan = productPlanKind === 'design';
|
||||
const isProductReviewPlan = productPlanKind === 'review';
|
||||
|
||||
const handleToggleTask = (task: PlanTask) => {
|
||||
if (!canToggle) return;
|
||||
const nextStatus = task.status === 'completed' ? 'pending' : 'completed';
|
||||
const updatedTasks = (plan.tasks || []).map((t) => t.id === task.id ? { ...t, status: nextStatus as PlanTask['status'] } : t);
|
||||
updatePlan(plan.id, { tasks: updatedTasks });
|
||||
};
|
||||
|
||||
const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
@@ -214,43 +207,35 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
<div className="h-full rounded-full bg-[var(--accent)] transition-all" style={{ width: `${progress}%` }} />
|
||||
</div>
|
||||
|
||||
{/* Research Tasks */}
|
||||
{isResearch && plan.tasks && plan.tasks.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-[11px] font-medium text-[var(--ink-muted)]">任务清单</div>
|
||||
{plan.tasks.map((task) => (
|
||||
<div key={task.id} className="flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-[var(--bg-subtle)]">
|
||||
<button
|
||||
disabled={!canToggle}
|
||||
onClick={() => handleToggleTask(task)}
|
||||
className={`h-4 w-4 rounded border flex items-center justify-center shrink-0 transition-colors ${!canToggle ? 'opacity-40 cursor-not-allowed' : ''} ${task.status === 'completed' ? 'bg-[var(--accent)] border-[var(--accent)]' : 'border-[var(--line)]'}`}
|
||||
>
|
||||
{task.status === 'completed' && <Check className="h-2.5 w-2.5 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
<span className={`flex-1 text-[12px] ${task.status === 'completed' ? 'line-through text-[var(--ink-muted)]' : 'text-[var(--ink)]'}`}>{task.title}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{plan.type === 'research' && (
|
||||
<PlanResearchDirectionPanel
|
||||
plan={plan}
|
||||
canEdit={canEditCoverage}
|
||||
currentUserName={currentUserName}
|
||||
onUpdate={updatePlan}
|
||||
/>
|
||||
)}
|
||||
|
||||
{linkedReqs.length > 0 && (
|
||||
<div>
|
||||
<PlanRequirementCoveragePanel
|
||||
plan={plan}
|
||||
requirements={linkedReqs}
|
||||
canEdit={canEditCoverage}
|
||||
currentUserName={currentUserName}
|
||||
onUpdate={updatePlan}
|
||||
/>
|
||||
{plan.type === 'research' ? (
|
||||
<PlanLinkedRequirementReferenceList requirements={linkedReqs} />
|
||||
) : (
|
||||
<PlanRequirementCoveragePanel
|
||||
plan={plan}
|
||||
requirements={linkedReqs}
|
||||
canEdit={canEditCoverage}
|
||||
currentUserName={currentUserName}
|
||||
onUpdate={updatePlan}
|
||||
/>
|
||||
)}
|
||||
{plan.status === 'in_progress' && !completionState.canSubmitResult && (
|
||||
<p className="pt-1 text-[11px] text-[var(--ink-muted)]">还不能提交成果:{completionState.missingReasons.join('、')}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isResearch && (
|
||||
<PlanLogTimeline logs={plan.logs} className="border-l-0 border-t border-[var(--line)] pt-4 pl-0" />
|
||||
)}
|
||||
<PlanLogTimeline logs={plan.logs} className="border-l-0 border-t border-[var(--line)] pt-4 pl-0" />
|
||||
|
||||
{/* Result */}
|
||||
{plan.status === 'completed' && plan.resultUrl && (
|
||||
|
||||
Reference in New Issue
Block a user