feat(版本): 优化概览与只读状态
关键改动: - 增加需求排序和版本只读状态规则及测试 - 完善版本概览阶段耗时、项目页和工作台展示 - 优化小宝预警请求节流、建议状态和风险过滤 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
@@ -24,6 +24,7 @@ interface Props {
|
||||
planId: string;
|
||||
onClose: () => void;
|
||||
contextLabel?: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const STATUS_STYLE: Record<string, string> = { pending: 'bg-zinc-100 text-zinc-600', in_progress: 'bg-blue-50 text-blue-600', completed: 'bg-emerald-50 text-emerald-600' };
|
||||
@@ -48,7 +49,7 @@ function getFailureLabels(types?: ProductPlanReviewFailureType[]): string[] {
|
||||
.filter(Boolean) as string[];
|
||||
}
|
||||
|
||||
export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
export function PlanDetailDrawer({ planId, onClose, contextLabel, readOnly = false }: Props) {
|
||||
const { plans, updatePlan, completePlan } = useVersionPlanStore();
|
||||
const { requirements } = useRequirementStore();
|
||||
const { members } = useMemberStore();
|
||||
@@ -74,13 +75,14 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
? 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 canEditCoverage = canEditPlanRequirementCoverage(plan);
|
||||
const canEditCoverage = !readOnly && 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 handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (readOnly) return;
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
setFileName(file.name);
|
||||
@@ -90,6 +92,7 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
};
|
||||
|
||||
const toggleFailureType = (type: ProductPlanReviewFailureType) => {
|
||||
if (readOnly) return;
|
||||
const next = new Set(reviewFailureTypes);
|
||||
if (next.has(type)) next.delete(type);
|
||||
else next.add(type);
|
||||
@@ -97,6 +100,7 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
};
|
||||
|
||||
const handleSubmitResult = () => {
|
||||
if (readOnly) return;
|
||||
let payload: PlanResultPayload | null = null;
|
||||
|
||||
if (isProductReviewPlan) {
|
||||
@@ -140,6 +144,7 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
};
|
||||
|
||||
const handleTransfer = () => {
|
||||
if (readOnly) return;
|
||||
if (!transferTo) return;
|
||||
updatePlan(plan.id, { owner: transferTo });
|
||||
setShowTransfer(false);
|
||||
@@ -272,7 +277,7 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
)}
|
||||
|
||||
{/* Transfer Section */}
|
||||
{showTransfer && (
|
||||
{showTransfer && !readOnly && (
|
||||
<div className="rounded-lg border border-[var(--line)] p-3 space-y-2">
|
||||
<div className="text-[11px] font-medium text-[var(--ink-muted)]">转交给</div>
|
||||
<FilterSelect
|
||||
@@ -289,7 +294,7 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
)}
|
||||
|
||||
{/* Complete with result */}
|
||||
{showComplete && (
|
||||
{showComplete && !readOnly && (
|
||||
<div className="rounded-lg border border-emerald-200 bg-emerald-50 p-3 space-y-2">
|
||||
<div className="text-[11px] font-medium text-emerald-700">{getSubmitActionLabel(plan)}</div>
|
||||
{isProductReviewPlan ? (
|
||||
@@ -382,7 +387,7 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
</div>
|
||||
|
||||
{/* Footer Actions */}
|
||||
{plan.status !== 'completed' && (
|
||||
{plan.status !== 'completed' && !readOnly && (
|
||||
<>
|
||||
<div className="flex items-center gap-2 px-5 py-3 border-t border-[var(--line)] shrink-0">
|
||||
{plan.status === 'pending' && (
|
||||
|
||||
Reference in New Issue
Block a user