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 }; },