Files
ftb-project-management/apps/web/lib/dev-task.ts
Script Generator d619846b93 fix(dev-task): 体验优化 — 新建弹窗默认值+详情卡片分区+筛选分页+胶囊联动
- 新建弹窗:负责人默认当前用户,提示版本截止日期,超期需填写原因
- 详情抽屉:重写为卡片分区布局,展示关联需求,状态流转按钮醒目化
- 详情实时更新:改为从 store 实时读取而非 prop 快照
- 列表增加筛选:负责人/状态/阻塞/任务类型 + 分页(每页20条)
- 工时展示:列表行显示 实际Xh / 预计Yh 格式
- 概览胶囊联动:dev 阶段进度由 DevTask 完成度驱动
- 概览统计卡片:替换 mock 为真实 DevTask 数据(待开发/开发中/阻塞中)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 18:51:33 +08:00

95 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Priority } from './derive';
export type DevTaskStatus = 'todo' | 'in_progress' | 'testing' | 'submitted' | 'done';
export interface DevTask {
id: string;
taskNo: string;
requirementId: string;
title: string;
description?: string;
categoryId: string;
assigneeId: string;
reviewerId?: string;
priority: Priority;
estimateHours: number;
actualHours: number;
startDate?: string;
dueDate?: string;
completedAt?: string;
status: DevTaskStatus;
isBlocked: boolean;
blockReason?: string;
blockedById?: string;
predecessorIds?: string[];
riskLevel?: 'low' | 'medium' | 'high';
overdueReason?: string;
createdBy: string;
createdAt: string;
updatedAt: string;
}
export const DEV_TASK_STATUS_LABEL: Record<DevTaskStatus, string> = {
todo: '待开发',
in_progress: '开发中',
testing: '自测',
submitted: '提测',
done: '已完成',
};
export const DEV_TASK_STATUS_COLOR: Record<DevTaskStatus, string> = {
todo: 'bg-zinc-100 text-zinc-600',
in_progress: 'bg-blue-50 text-blue-600',
testing: 'bg-purple-50 text-purple-600',
submitted: 'bg-orange-50 text-orange-600',
done: 'bg-emerald-50 text-emerald-600',
};
export const STATUS_PROGRESS: Record<DevTaskStatus, number> = {
todo: 0,
in_progress: 50,
testing: 80,
submitted: 90,
done: 100,
};
export const ALLOWED_TRANSITIONS: Record<DevTaskStatus, DevTaskStatus[]> = {
todo: ['in_progress'],
in_progress: ['testing'],
testing: ['submitted', 'in_progress'],
submitted: ['done', 'in_progress'],
done: [],
};
export function canTransition(from: DevTaskStatus, to: DevTaskStatus): boolean {
return ALLOWED_TRANSITIONS[from].includes(to);
}
export function calcTaskProgress(task: DevTask): number {
return STATUS_PROGRESS[task.status];
}
export function calcGroupProgress(tasks: DevTask[]): number {
if (tasks.length === 0) return 0;
const totalEstimate = tasks.reduce((sum, t) => sum + t.estimateHours, 0);
if (totalEstimate === 0) return 0;
const weighted = tasks.reduce((sum, t) => sum + t.estimateHours * STATUS_PROGRESS[t.status], 0);
return Math.round(weighted / totalEstimate);
}
export function formatHours(hours: number): string {
if (hours < 8) return `${hours}h`;
const days = Math.floor(hours / 8);
const remainder = hours % 8;
if (remainder === 0) return `${hours}h${days}人天)`;
return `${hours}h${days}人天)`;
}
export function generateTaskNo(existingTasks: DevTask[]): string {
const maxNum = existingTasks.reduce((max, t) => {
const num = parseInt(t.taskNo.replace('DEV-', ''), 10);
return isNaN(num) ? max : Math.max(max, num);
}, 0);
return `DEV-${String(maxNum + 1).padStart(3, '0')}`;
}