diff --git a/apps/server/src/modules/data/data-keys.ts b/apps/server/src/modules/data/data-keys.ts index b9d8f8a..afdd4e4 100644 --- a/apps/server/src/modules/data/data-keys.ts +++ b/apps/server/src/modules/data/data-keys.ts @@ -8,6 +8,7 @@ export const APP_DATA_KEYS = [ 'members', 'task-categories', 'task-worklogs', + 'work-activities', 'overtime', ] as const; diff --git a/apps/server/src/modules/data/data.service.spec.ts b/apps/server/src/modules/data/data.service.spec.ts index ca1d56a..ec7eb1f 100644 --- a/apps/server/src/modules/data/data.service.spec.ts +++ b/apps/server/src/modules/data/data.service.spec.ts @@ -58,6 +58,10 @@ describe('DataService', () => { key: 'task-worklogs', value, }); + await expect(service.put('work-activities', value)).resolves.toEqual({ + key: 'work-activities', + value, + }); await expect(service.put('overtime', { records: [], reasons: [] })).resolves.toEqual({ key: 'overtime', value, diff --git a/apps/web/app/workspace/page.tsx b/apps/web/app/workspace/page.tsx index d1c36b8..e900573 100644 --- a/apps/web/app/workspace/page.tsx +++ b/apps/web/app/workspace/page.tsx @@ -11,6 +11,7 @@ import { useTestCaseStore } from '@/stores/useTestCaseStore'; import { useBugStore } from '@/stores/useBugStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { useTaskWorklogStore } from '@/stores/useTaskWorklogStore'; +import { useWorkActivityStore } from '@/stores/useWorkActivityStore'; import { flattenVersions } from '@/lib/derive'; import { aggregateWorkItems, WORK_ITEM_TYPE_LABEL } from '@/lib/workspace-engine'; import type { WorkItem, WorkItemType } from '@/lib/workspace-engine'; @@ -55,6 +56,7 @@ export default function WorkspacePage() { const { testCases, fetchTestCases } = useTestCaseStore(); const { bugs, fetchBugs } = useBugStore(); const { worklogs, fetchWorklogs } = useTaskWorklogStore(); + const { activities, fetchActivities } = useWorkActivityStore(); const user = useAuthStore((s) => s.user); const [activeTab, setActiveTab] = useState('all'); const [showCompleted, setShowCompleted] = useState(true); @@ -69,6 +71,7 @@ export default function WorkspacePage() { useEffect(() => { fetchTestCases(); }, [fetchTestCases]); useEffect(() => { fetchBugs(); }, [fetchBugs]); useEffect(() => { fetchWorklogs(); }, [fetchWorklogs]); + useEffect(() => { fetchActivities(); }, [fetchActivities]); const userName = user?.name ?? ''; const allVersions = useMemo(() => flattenVersions(overview), [overview]); @@ -122,12 +125,13 @@ export default function WorkspacePage() { const dailyReport = useMemo(() => getWorkspaceDailyReport({ + activities, worklogs, workItems, userId: userName, date: today, }), - [worklogs, workItems, userName, today] + [activities, worklogs, workItems, userName, today] ); // 待办数量按 tab 分(受 version filter 影响) diff --git a/apps/web/components/dev-task/DevTaskDetailDrawer.tsx b/apps/web/components/dev-task/DevTaskDetailDrawer.tsx index 90e4f5e..ab1eb1c 100644 --- a/apps/web/components/dev-task/DevTaskDetailDrawer.tsx +++ b/apps/web/components/dev-task/DevTaskDetailDrawer.tsx @@ -5,6 +5,7 @@ import { X, AlertTriangle, Link2, ChevronRight, Clock, User, Tag, Play, Trash2, import { StatusBadge } from './StatusBadge'; import { CategoryChip } from './CategoryChip'; import { useDevTaskStore } from '@/stores/useDevTaskStore'; +import { useWorkActivityStore } from '@/stores/useWorkActivityStore'; import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useMemberStore } from '@/stores/useMemberStore'; @@ -29,6 +30,7 @@ interface Props { export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel }: Props) { const { tasks, changeStatus, setBlocked, deleteTask, updateTask } = useDevTaskStore(); + const addProgressNote = useWorkActivityStore((s) => s.addProgressNote); const { categories } = useTaskCategoryStore(); const { requirements } = useRequirementStore(); const { members } = useMemberStore(); @@ -36,6 +38,10 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel const [transferTo, setTransferTo] = useState(''); const [showDelayInput, setShowDelayInput] = useState(false); const [delayReason, setDelayReason] = useState(''); + const [progressNote, setProgressNote] = useState(''); + const [progressBlocker, setProgressBlocker] = useState(''); + const [progressHelperId, setProgressHelperId] = useState(''); + const [progressDelayRisk, setProgressDelayRisk] = useState(''); const task = tasks.find((t) => t.id === taskId); if (!task) return null; @@ -86,6 +92,28 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel setBlockReason(''); }; + const handleProgressNote = () => { + const note = progressNote.trim(); + const blocker = progressBlocker.trim(); + const delayRisk = progressDelayRisk.trim(); + if (!note && !blocker && !delayRisk) return; + + addProgressNote({ + actorId: task.assigneeId, + sourceType: 'dev_task', + sourceId: task.id, + title: task.title, + note: note || '今日进展已更新', + blocker: blocker || undefined, + helperId: progressHelperId || undefined, + delayRisk: delayRisk || undefined, + }); + setProgressNote(''); + setProgressBlocker(''); + setProgressHelperId(''); + setProgressDelayRisk(''); + }; + return (
e.stopPropagation()}> @@ -199,6 +227,50 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel
+ {task.status !== 'todo' && task.status !== 'submitted' && ( +
+
今日进展
+