# Workspace Daily Report Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add a right-side “今日日报” column to `/workspace` that summarizes the current logged-in user's worklog entries for today. **Architecture:** Keep the feature as a derived workspace view. A pure helper combines `TaskWorklog[]` with existing `WorkItem[]`, a focused `DailyReportPanel` renders the summary, and `WorkspacePage` wires the existing stores together without creating a new daily report entity. **Tech Stack:** Next.js App Router, React client components, Zustand stores, TypeScript, Node `node:test`. --- ## File Structure - Create `apps/web/lib/workspace-daily-report.ts` - Owns the pure daily report derivation logic. - Depends only on `TaskWorklog` and `WorkItem` types. - Create `apps/web/lib/workspace-daily-report.test.ts` - Covers filtering, totals, task context resolution, and deleted/missing task behavior. - Create `apps/web/components/workspace/DailyReportPanel.tsx` - Owns the right-side column UI for the already-derived report. - No store access inside the component. - Modify `apps/web/app/workspace/page.tsx` - Fetches task worklogs. - Builds the daily report with current user + today's date. - Adds the fixed right-side column. --- ### Task 1: Daily Report Derivation Helper **Files:** - Create: `apps/web/lib/workspace-daily-report.test.ts` - Create: `apps/web/lib/workspace-daily-report.ts` - [ ] **Step 1: Write the failing helper tests** Create `apps/web/lib/workspace-daily-report.test.ts`: ```ts import test from 'node:test'; import assert from 'node:assert/strict'; import type { TaskWorklog } from './task-worklog'; import type { WorkItem } from './workspace-engine'; import { getWorkspaceDailyReport } from './workspace-daily-report'; const workItems: WorkItem[] = [ { id: 'task-1', type: 'devTask', title: '实现登录接口', status: 'in_progress', completed: false, productName: 'FTB', projectName: '项目管理', versionName: 'V1.0', versionId: 'version-1', raw: {} as any, }, { id: 'task-2', type: 'bug', title: '修复菜单错位', status: 'fixing', completed: false, productName: 'FTB', projectName: '项目管理', versionName: 'V1.0', versionId: 'version-1', raw: {} as any, }, ]; const worklogs: TaskWorklog[] = [ { id: 'wl-1', taskId: 'task-1', userId: '张三', date: '2026-06-26', hours: 1.5, workContent: '完成登录接口联调', createdAt: '2026-06-26T02:00:00.000Z', }, { id: 'wl-2', taskId: 'task-2', userId: '张三', date: '2026-06-26', hours: 0.5, workContent: '定位菜单错位原因', createdAt: '2026-06-26T03:00:00.000Z', }, { id: 'wl-other-user', taskId: 'task-1', userId: '李四', date: '2026-06-26', hours: 8, workContent: '其他人的日报', createdAt: '2026-06-26T04:00:00.000Z', }, { id: 'wl-other-date', taskId: 'task-1', userId: '张三', date: '2026-06-25', hours: 8, workContent: '昨天的日报', createdAt: '2026-06-25T04:00:00.000Z', }, ]; test('getWorkspaceDailyReport filters by current user and date', () => { const report = getWorkspaceDailyReport({ worklogs, workItems, userId: '张三', date: '2026-06-26', }); assert.equal(report.totalHours, 2); assert.equal(report.totalCount, 2); assert.deepEqual(report.items.map((item) => item.id), ['wl-2', 'wl-1']); }); test('getWorkspaceDailyReport resolves task title and workspace context', () => { const report = getWorkspaceDailyReport({ worklogs, workItems, userId: '张三', date: '2026-06-26', }); assert.equal(report.items[0].taskTitle, '修复菜单错位'); assert.equal(report.items[0].productName, 'FTB'); assert.equal(report.items[0].projectName, '项目管理'); assert.equal(report.items[0].versionName, 'V1.0'); }); test('getWorkspaceDailyReport keeps logs whose task is no longer visible', () => { const report = getWorkspaceDailyReport({ worklogs: [ { id: 'wl-missing', taskId: 'deleted-task', userId: '张三', date: '2026-06-26', hours: 0.5, workContent: '处理历史任务', createdAt: '2026-06-26T01:00:00.000Z', }, ], workItems: [], userId: '张三', date: '2026-06-26', }); assert.equal(report.totalHours, 0.5); assert.equal(report.totalCount, 1); assert.equal(report.items[0].taskTitle, '未知任务'); assert.equal(report.items[0].workContent, '处理历史任务'); }); ``` - [ ] **Step 2: Run the helper tests and verify they fail** Run: ```bash pnpm --filter=web test ``` Expected: TypeScript compilation fails because `./workspace-daily-report` does not exist. - [ ] **Step 3: Implement the helper** Create `apps/web/lib/workspace-daily-report.ts`: ```ts import type { TaskWorklog } from './task-worklog'; import type { WorkItem } from './workspace-engine'; export interface WorkspaceDailyReportItem { id: string; taskId: string; taskTitle: string; workContent: string; hours: number; createdAt: string; productName?: string; projectName?: string; versionName?: string; } export interface WorkspaceDailyReport { date: string; totalHours: number; totalCount: number; items: WorkspaceDailyReportItem[]; } interface GetWorkspaceDailyReportInput { worklogs: TaskWorklog[]; workItems: WorkItem[]; userId: string; date: string; } export function getWorkspaceDailyReport({ worklogs, workItems, userId, date, }: GetWorkspaceDailyReportInput): WorkspaceDailyReport { const workItemMap = new Map(workItems.map((item) => [item.id, item])); const items = worklogs .filter((log) => log.userId === userId && log.date === date) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) .map((log) => { const item = workItemMap.get(log.taskId); return { id: log.id, taskId: log.taskId, taskTitle: item?.title ?? '未知任务', workContent: log.workContent, hours: log.hours, createdAt: log.createdAt, productName: item?.productName, projectName: item?.projectName, versionName: item?.versionName, }; }); return { date, totalHours: items.reduce((sum, item) => sum + item.hours, 0), totalCount: items.length, items, }; } ``` - [ ] **Step 4: Run the helper tests and verify they pass** Run: ```bash pnpm --filter=web test ``` Expected: PASS for the new `workspace-daily-report` tests. - [ ] **Step 5: Commit Task 1** Run: ```bash git add apps/web/lib/workspace-daily-report.ts apps/web/lib/workspace-daily-report.test.ts git commit -m "feat(workspace): 添加个人日报派生逻辑" ``` Expected: commit succeeds with only the helper and helper test staged. --- ### Task 2: Daily Report Panel Component **Files:** - Create: `apps/web/components/workspace/DailyReportPanel.tsx` - [ ] **Step 1: Create the presentational component** Create `apps/web/components/workspace/DailyReportPanel.tsx`: ```tsx 'use client'; import { CalendarDays, ClipboardList, Clock3 } from 'lucide-react'; import type { WorkspaceDailyReport } from '@/lib/workspace-daily-report'; import { formatWorkHours, formatWorkHoursShort } from '@/lib/work-hours'; interface Props { report: WorkspaceDailyReport; } export function DailyReportPanel({ report }: Props) { return ( ); } ``` - [ ] **Step 2: Run type-check for the new component** Run: ```bash pnpm --filter=web type-check ``` Expected: PASS. If it fails because later integration imports are not present, fix only this component's imports and prop types. - [ ] **Step 3: Commit Task 2** Run: ```bash git add apps/web/components/workspace/DailyReportPanel.tsx git commit -m "feat(workspace): 添加今日日报侧栏组件" ``` Expected: commit succeeds with only the new component staged. --- ### Task 3: Wire the Panel Into Workspace Page **Files:** - Modify: `apps/web/app/workspace/page.tsx` - [ ] **Step 1: Add imports** In `apps/web/app/workspace/page.tsx`, add these imports with the existing store and helper imports: ```ts import { useTaskWorklogStore } from '@/stores/useTaskWorklogStore'; import { DailyReportPanel } from '@/components/workspace/DailyReportPanel'; import { getWorkspaceDailyReport } from '@/lib/workspace-daily-report'; ``` - [ ] **Step 2: Read and fetch worklogs** Inside `WorkspacePage`, after the existing store hooks, add: ```ts const { worklogs, fetchWorklogs } = useTaskWorklogStore(); ``` After the existing fetch effects, add: ```ts useEffect(() => { fetchWorklogs(); }, [fetchWorklogs]); ``` - [ ] **Step 3: Derive today's report** After `filteredItems` is defined, add: ```ts const today = useMemo(() => new Date().toISOString().slice(0, 10), []); const dailyReport = useMemo(() => getWorkspaceDailyReport({ worklogs, workItems, userId: userName, date: today, }), [worklogs, workItems, userName, today] ); ``` - [ ] **Step 4: Add the right-side column** In the JSX returned by `WorkspacePage`, keep the existing main task list block and add the daily report panel immediately after it, before the detail drawers: ```tsx {/* 右侧:任务列表 */}

{TABS.find((t) => t.key === activeTab)?.label}

{filteredItems.length} 项 {selectedVersionId && ( {versionMap.get(selectedVersionId)?.name} )}
{filteredItems.length === 0 ? (

暂无相关任务

) : (
{filteredItems.map((item) => ( item.versionId && router.push(`/versions/${item.versionId}`)} onClick={() => setDrawerItem(item)} /> ))}
)}
``` The important class change is `min-w-0 flex-1` on the task list container. This lets the center list shrink correctly after adding the fixed `w-80` daily report column. - [ ] **Step 5: Run tests and type-check** Run: ```bash pnpm --filter=web test pnpm --filter=web type-check ``` Expected: both commands pass. - [ ] **Step 6: Commit Task 3** Run: ```bash git add apps/web/app/workspace/page.tsx git commit -m "feat(workspace): 在工作台接入今日日报列" ``` Expected: commit succeeds with only `WorkspacePage` staged. --- ### Task 4: Final Verification **Files:** - Verify: `apps/web/lib/workspace-daily-report.ts` - Verify: `apps/web/components/workspace/DailyReportPanel.tsx` - Verify: `apps/web/app/workspace/page.tsx` - [ ] **Step 1: Run the full web test suite** Run: ```bash pnpm --filter=web test ``` Expected: PASS. - [ ] **Step 2: Run TypeScript checking** Run: ```bash pnpm --filter=web type-check ``` Expected: PASS. - [ ] **Step 3: Check the workspace page if the dev server is already running** Run: ```bash curl http://localhost:3000/workspace ``` Expected: HTTP 200 HTML response. If the dev server is not running, record that this browser-level check was not available and do not start unrelated services unless the execution context asks for interactive verification. - [ ] **Step 4: Confirm git state** Run: ```bash git status --short ``` Expected: only pre-existing unrelated files remain modified or untracked; the files from this plan are committed.