diff --git a/examples/app-showcase/src/ui/pages/command-center.page.ts b/examples/app-showcase/src/ui/pages/command-center.page.ts index 295f56889ba..a98af74d611 100644 --- a/examples/app-showcase/src/ui/pages/command-center.page.ts +++ b/examples/app-showcase/src/ui/pages/command-center.page.ts @@ -98,6 +98,16 @@ function chart(id: string, chartType: string, dataset: string, dimensions: strin const CHART_H = '376px'; +/** + * The review set — tasks whose `status` is `in_review`. ONE value read by two + * blocks: the KPI tile `cc_k3` counts it, and the work queue `cc_queue_g` + * under its 待审核列表 / 审批中 heading lists it, so the panel shows exactly + * the rows the tile counts. `object-grid` reads `filter` (singular, the + * `ViewFilterRule` array — `ComponentPropsMap['object-grid']`) and lowers it + * to `$filter`. + */ +const IN_REVIEW = [{ field: 'status', operator: 'equals', value: 'in_review' }]; + export const CommandCenterPage = definePage({ name: 'showcase_command_center', label: 'Command Center (大屏)', @@ -146,7 +156,7 @@ export const CommandCenterPage = definePage({ child: band('cc_kpi_grid', 6, [ kpi('cc_k1', 'showcase_project', '活跃项目 Active', 'blue', { field: 'id', function: 'count' }, [{ field: 'status', operator: 'equals', value: 'active' }]), kpi('cc_k2', 'showcase_task', '待办任务 Open', 'teal', { field: 'id', function: 'count' }, [{ field: 'status', operator: 'not_equals', value: 'done' }]), - kpi('cc_k3', 'showcase_task', '待复审 Review', 'purple', { field: 'id', function: 'count' }, [{ field: 'status', operator: 'equals', value: 'in_review' }]), + kpi('cc_k3', 'showcase_task', '待复审 Review', 'purple', { field: 'id', function: 'count' }, IN_REVIEW), kpi('cc_k4', 'showcase_project', '风险项目 At-Risk', 'danger', { field: 'id', function: 'count' }, [{ field: 'health', operator: 'equals', value: 'red' }]), kpi('cc_k5', 'showcase_account', '客户 Accounts', 'orange', { field: 'id', function: 'count' }), kpi('cc_k6', 'showcase_project', '总预算 Budget', 'success', { field: 'budget', function: 'sum' }, undefined, '0.0a'), @@ -169,7 +179,7 @@ export const CommandCenterPage = definePage({ // ── Work queue — its own full-width row at the bottom ──────── panel({ id: 'cc_queue', title: '待审核列表 · Work Queue', accent: A.c1, badge: '审批中', minHeight: '0px', - child: { id: 'cc_queue_g', type: 'object-grid', responsiveStyles: { large: { minWidth: '0', display: 'block' } }, properties: { objectName: 'showcase_task', columns: ['title', 'project', 'status', 'priority', 'due_date'] } }, + child: { id: 'cc_queue_g', type: 'object-grid', responsiveStyles: { large: { minWidth: '0', display: 'block' } }, properties: { objectName: 'showcase_task', columns: ['title', 'project', 'status', 'priority', 'due_date'], filter: IN_REVIEW } }, }), ], }, diff --git a/examples/app-showcase/test/command-center-metric-filter.test.ts b/examples/app-showcase/test/command-center-metric-filter.test.ts index 8451366f8fa..a4d8fa6041a 100644 --- a/examples/app-showcase/test/command-center-metric-filter.test.ts +++ b/examples/app-showcase/test/command-center-metric-filter.test.ts @@ -77,3 +77,65 @@ describe('Command Center — object-metric properties parse against their Compon } }); }); + +/** + * The work queue lists the set the KPI tile above it counts. + * + * `cc_queue_g` sits under the heading 待审核列表 · Work Queue with the badge + * 审批中, and the tile `cc_k3` (待复审 Review) counts tasks whose `status` is + * `in_review`. The grid carried no filter, so it listed every task, done ones + * included, while the tile beside it counted two. + * + * `filter` is the key `object-grid` declares (`ComponentPropsMap['object-grid']`, + * the `ViewFilterRule` array) and the one the committed `sdui.manifest.json` + * lists among the grid's inputs; the renderer lowers it to `$filter`. The pin + * asserts that key, parses the whole bag against the grid's row, and holds the + * queue's rules equal to the tile's so the two cannot drift apart. + */ +function nodeById(root: unknown, id: string): AnyRec | undefined { + let found: AnyRec | undefined; + const visit = (v: unknown): void => { + if (found) return; + if (Array.isArray(v)) { + for (const item of v) visit(item); + return; + } + if (!isRec(v)) return; + if (v.id === id) { + found = v; + return; + } + for (const child of Object.values(v)) visit(child); + }; + visit(root); + return found; +} + +describe('Command Center — the work queue lists the review set its KPI counts', () => { + const queue = nodeById(CommandCenterPage.regions, 'cc_queue_g'); + const tile = nodeById(CommandCenterPage.regions, 'cc_k3'); + const queueProps = isRec(queue?.properties) ? queue.properties : {}; + const tileProps = isRec(tile?.properties) ? tile.properties : {}; + + it('finds the queue grid and the Review tile', () => { + expect(queue?.type).toBe('object-grid'); + expect(tile?.type).toBe('object-metric'); + expect(queueProps.objectName).toBe(tileProps.objectName); + }); + + it('scopes the queue to in_review tasks through `filter`', () => { + expect(queueProps.filter).toEqual([{ field: 'status', operator: 'equals', value: 'in_review' }]); + }); + + it("carries the same rules as the tile `cc_k3`", () => { + expect(queueProps.filter).toEqual(tileProps.filter); + }); + + it('the queue properties bag parses clean against its ComponentPropsMap row', () => { + const result = ComponentPropsMap['object-grid'].safeParse(queueProps); + const failures = result.success + ? [] + : result.error.issues.map((issue) => `properties.${issue.path.join('.')}: ${issue.code}`); + expect(failures).toEqual([]); + }); +});