From af1c61c7d569fa7ee6b0f0aef2c54bdd7607cd38 Mon Sep 17 00:00:00 2001 From: Script Generator Date: Fri, 26 Jun 2026 14:27:29 +0800 Subject: [PATCH] =?UTF-8?q?docs(workspace):=20=E6=96=B0=E5=A2=9E=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E6=97=A5=E6=8A=A5=E5=AE=9E=E7=8E=B0=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-26-workspace-daily-report.md | 532 ++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-26-workspace-daily-report.md diff --git a/docs/superpowers/plans/2026-06-26-workspace-daily-report.md b/docs/superpowers/plans/2026-06-26-workspace-daily-report.md new file mode 100644 index 0000000..2e458bc --- /dev/null +++ b/docs/superpowers/plans/2026-06-26-workspace-daily-report.md @@ -0,0 +1,532 @@ +# 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.