diff --git a/apps/web/components/dev-task/DevTaskDetailDrawer.tsx b/apps/web/components/dev-task/DevTaskDetailDrawer.tsx
index ace120d..fba20da 100644
--- a/apps/web/components/dev-task/DevTaskDetailDrawer.tsx
+++ b/apps/web/components/dev-task/DevTaskDetailDrawer.tsx
@@ -1,14 +1,13 @@
'use client';
import { useState } from 'react';
-import { X, AlertTriangle, Link2, ChevronRight, Clock, User, Tag, Calendar } from 'lucide-react';
+import { X, AlertTriangle, Link2, ChevronRight, Clock, User, Tag, Calendar, Play } from 'lucide-react';
import { StatusBadge } from './StatusBadge';
import { CategoryChip } from './CategoryChip';
-import { WorklogPanel } from './WorklogPanel';
import { useDevTaskStore } from '@/stores/useDevTaskStore';
import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore';
import { useRequirementStore } from '@/stores/useRequirementStore';
-import { ALLOWED_TRANSITIONS, DEV_TASK_STATUS_LABEL, DEV_TASK_STATUS_COLOR, formatHours } from '@/lib/dev-task';
+import { ALLOWED_TRANSITIONS, DEV_TASK_STATUS_LABEL, DEV_TASK_STATUS_COLOR, formatHours, calcActualHoursByDates } from '@/lib/dev-task';
import type { DevTaskStatus } from '@/lib/dev-task';
interface Props {
@@ -153,8 +152,15 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose }: Props) {
实际
- {task.actualHours > 0 ? `${task.actualHours}h` : '-'}
+ {task.startDate ? formatHours(calcActualHoursByDates(task.startDate, task.completedAt)) : '-'}
+ {task.startDate && (
+
+
+
开始开发
+
{task.startDate}
+
+ )}
{task.dueDate && (
@@ -194,10 +200,6 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose }: Props) {
)}
- {/* 卡片5:工时记录 */}
-
-
-
diff --git a/apps/web/components/dev-task/DevTaskRow.tsx b/apps/web/components/dev-task/DevTaskRow.tsx
index 1006000..079769e 100644
--- a/apps/web/components/dev-task/DevTaskRow.tsx
+++ b/apps/web/components/dev-task/DevTaskRow.tsx
@@ -3,7 +3,7 @@
import { AlertTriangle } from 'lucide-react';
import { StatusBadge } from './StatusBadge';
import { CategoryChip } from './CategoryChip';
-import { formatHours } from '@/lib/dev-task';
+import { formatHours, calcActualHoursByDates } from '@/lib/dev-task';
import type { DevTask } from '@/lib/dev-task';
import type { TaskCategory } from '@/lib/task-category';
@@ -39,8 +39,8 @@ export function DevTaskRow({ task, category, onClick }: Props) {
- {task.actualHours > 0 && {task.actualHours}h}
- {task.actualHours > 0 && ' / '}
+ {task.startDate && {calcActualHoursByDates(task.startDate, task.completedAt)}h}
+ {task.startDate && ' / '}
{formatHours(task.estimateHours).split('(')[0]}
{task.assigneeId}
diff --git a/apps/web/lib/dev-task.ts b/apps/web/lib/dev-task.ts
index 729f285..eabe5f7 100644
--- a/apps/web/lib/dev-task.ts
+++ b/apps/web/lib/dev-task.ts
@@ -85,6 +85,22 @@ export function formatHours(hours: number): string {
return `${hours}h(≈${days}人天)`;
}
+export function calcActualHoursByDates(startDate?: string, completedAt?: string): number {
+ if (!startDate) return 0;
+ const end = completedAt || new Date().toISOString().slice(0, 10);
+ const start = new Date(startDate);
+ const endDate = new Date(end);
+ if (isNaN(start.getTime()) || isNaN(endDate.getTime()) || endDate < start) return 0;
+ let workDays = 0;
+ const current = new Date(start);
+ while (current <= endDate) {
+ const day = current.getDay();
+ if (day !== 0 && day !== 6) workDays++;
+ current.setDate(current.getDate() + 1);
+ }
+ return workDays * 8;
+}
+
export function generateTaskNo(existingTasks: DevTask[]): string {
const maxNum = existingTasks.reduce((max, t) => {
const num = parseInt(t.taskNo.replace('DEV-', ''), 10);
diff --git a/apps/web/stores/useDevTaskStore.ts b/apps/web/stores/useDevTaskStore.ts
index 0488532..2c8d8db 100644
--- a/apps/web/stores/useDevTaskStore.ts
+++ b/apps/web/stores/useDevTaskStore.ts
@@ -77,8 +77,10 @@ export const useDevTaskStore = create((set, get) => ({
if (!canTransition(task.status, to)) {
return { ok: false, message: `不允许从「${task.status}」流转到「${to}」` };
}
- const completedAt = to === 'done' ? new Date().toISOString().slice(0, 10) : task.completedAt;
- get().updateTask(id, { status: to, completedAt });
+ const today = new Date().toISOString().slice(0, 10);
+ const startDate = (to === 'in_progress' && !task.startDate) ? today : task.startDate;
+ const completedAt = to === 'done' ? today : task.completedAt;
+ get().updateTask(id, { status: to, startDate, completedAt });
return { ok: true };
},