feat(版本详情): 优化任务统计与测试轮次

This commit is contained in:
Script Generator
2026-06-26 15:12:24 +08:00
parent 914e6180c1
commit 2d802318fa
16 changed files with 631 additions and 65 deletions

View File

@@ -12,10 +12,12 @@ import { DevTaskRow } from './DevTaskRow';
import { DevTaskCreateModal } from './DevTaskCreateModal';
import { DevTaskDetailDrawer } from './DevTaskDetailDrawer';
import { calcGroupProgress, DEV_TASK_STATUS_LABEL, aggregateDevTaskHours, devTaskIntervals } from '@/lib/dev-task';
import { formatWorkHours, calcTwoMetrics } from '@/lib/work-hours';
import { calcTwoMetrics } from '@/lib/work-hours';
import { Pagination, usePagination } from '@/components/Pagination';
import { SearchInput, matchTitleOrNo } from '@/components/SearchInput';
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
import { orderByRequirementForGrouping } from '@/lib/requirement-grouping';
import { buildEffortSummaryMetrics, sumPositiveHours } from '@/lib/effort-summary';
import type { DevTask, DevTaskStatus } from '@/lib/dev-task';
interface Props {
@@ -60,14 +62,25 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
return result;
}, [versionTasks, filterAssignee, filterStatus, filterBlocked, filterCategory, debouncedKeyword]);
const { paged, page, setPage, total, pageSize, setPageSize } = usePagination(filteredTasks, 20);
const orderedTasks = useMemo(
() => orderByRequirementForGrouping(filteredTasks, requirementIds, (task) => task.requirementId),
[filteredTasks, requirementIds],
);
const { paged, page, setPage, total, pageSize, setPageSize } = usePagination(orderedTasks, 20);
const progress = calcGroupProgress(versionTasks);
const { estimate: totalEstimate } = aggregateDevTaskHours(versionTasks);
const { calendarHours, manhours } = useMemo(() => calcTwoMetrics(devTaskIntervals(versionTasks)), [versionTasks]);
const overrun = manhours > totalEstimate && totalEstimate > 0;
const underrun = manhours > 0 && manhours < totalEstimate;
const hoursTone = overrun ? 'text-red-600' : underrun ? 'text-emerald-600' : 'text-[var(--ink-muted)]';
const { actual: actualHours } = aggregateDevTaskHours(versionTasks);
const { manhours } = useMemo(() => calcTwoMetrics(devTaskIntervals(versionTasks)), [versionTasks]);
const effortMetrics = useMemo(
() => buildEffortSummaryMetrics({
aiEstimateHours: sumPositiveHours(versionTasks, (task) => task.aiEstimateHours),
estimateHours: sumPositiveHours(versionTasks, (task) => task.estimateHours),
actualHours,
manhours,
}),
[versionTasks, actualHours, manhours],
);
const blockedCount = versionTasks.filter((t) => t.isBlocked).length;
const [showCreate, setShowCreate] = useState(false);
@@ -112,7 +125,12 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
<div className="h-1.5 w-20 rounded-full bg-[var(--bg)] overflow-hidden shrink-0">
<div className="h-full rounded-full bg-[var(--accent)]" style={{ width: `${progress}%` }} />
</div>
<span className="text-[11px] text-[var(--ink-muted)] shrink-0">{versionTasks.length} · {formatWorkHours(totalEstimate)}{manhours > 0 ? <> · {formatWorkHours(calendarHours)} / <span className={`tabular-nums ${hoursTone}`}>{formatWorkHours(manhours)}</span></> : null}</span>
<span className="text-[11px] text-[var(--ink-muted)] shrink-0">{versionTasks.length}</span>
{effortMetrics.map((metric) => (
<span key={metric.label} className="text-[11px] text-[var(--ink-muted)] shrink-0">
{metric.label} <span className="tabular-nums text-[var(--ink)]">{metric.value}</span>
</span>
))}
{blockedCount > 0 && <span className="text-[10px] text-red-500 bg-red-50 px-1.5 py-0.5 rounded shrink-0">{blockedCount}</span>}
<span className="w-px h-4 bg-[var(--line)] mx-1 shrink-0" />