diff --git a/.project-docs/30-worklog/tasks/20260902-dashboard-bar-chart-f184c2.md b/.project-docs/30-worklog/tasks/20260902-dashboard-bar-chart-f184c2.md new file mode 100644 index 0000000..b210805 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260902-dashboard-bar-chart-f184c2.md @@ -0,0 +1,61 @@ +# Task: Replace ranking progress tracks with horizontal bar chart + +## Identity + +- Task ID: 20260902-dashboard-bar-chart-f184c2 +- Mode: Feature +- Branch: main +- Worktree: /Users/inmanx/Documents/lwltAPI +- Base commit: de9f33dc45b137d1e48f3853904e45d1589b4b2d +- Owner: codex +- Status: Ready for integration + +## Scope + +- Replace the two-layer progress-track treatment in both leadership rankings with a conventional vertical list of horizontal bars. +- Make each task-type or employee row itself the filled bar, with bar length representing operation count. +- Explicitly sort both ranking datasets by operation count descending so longer bars appear above shorter bars. +- Preserve exact operation/success counts, fixed panel sizing, scrolling, and click-through filters. +- Update static cache versions and dashboard regression assertions. + +## Intent And Constraints + +- Follow the clarified visual model: no separate track and no inner success-progress line; the whole row background is the chart bar. +- Use operation count as the only encoded bar length. Keep success count as readable text rather than a second visual series or percentage. +- Work within the existing vanilla HTML/CSS/JavaScript stack and current dashboard response; do not add a charting dependency or change the API. +- Preserve leadership-only read access, business-safe language, employee/task-type drill-through, detail viewing, and all task/ERP authorization boundaries. +- Do not restart, deploy, mutate runtime data, access ERP, or introduce an organization concept. + +## Outcome + +- Simplified the reusable ranking-bar renderer to one full-height background shape whose width is normalized to the largest operation count in that ranking. +- Removed the separate pale track and dark success-progress overlay. Each ranking row is now the bar itself, with name, rank, operation count, and success count rendered directly above the filled background. +- Returned ranking rows to a compact three-column layout because the chart no longer needs a dedicated track column or mobile second row. +- Added explicit descending client-side sorting for task types and employees by operation count, independent of response ordering. +- Kept success counts highlighted in green text and retained all accessible button labels; completion-rate text and percentages remain absent. +- Preserved fixed 304px panels, vertical scrolling, task-type/employee click-through, filters, task detail, and business-safe dashboard copy. +- Updated asset cache versions and regression assertions for the bar-chart presentation and descending sort. +- The running 8786 service serves these static changes directly. No restart, deployment, database/task mutation, ERP access, or external send was performed. + +## Verification + +- `node --check LianSyn-platform/app.js`: passed with the bundled Node runtime. +- Focused account/dashboard regression: 8/8 passed. +- `node --run check:repo`: 10/10 passed. +- `node --run check`: passed. +- `node --run test:control-plane`: 153/153 passed. +- `node --run test:legacy`: 256/256 passed. +- `node --run build`: passed. +- `git diff --check`: passed. +- Authenticated browser verification on `http://127.0.0.1:8786/operations-dashboard?bar-chart=1` loaded 359 operations, 251 successes, 23 task types, and 1 participating employee. +- Visual verification confirmed that every row background is a single full-height horizontal bar, with 43 operations at full width followed by visibly shorter 27, 26, 24, and 24 operation bars. +- DOM order verification confirmed the first five task types were “删除订单 / 恢复订单 / 安排导游 / 酒店安排 / 取消订单” with operation counts `43 / 27 / 26 / 24 / 24`. +- Interaction verification confirmed that clicking the 43-operation bar selected “删除订单” and narrowed the detail table to 43 matching operations while completion-rate language remained absent. + +## Follow-ups + +- The current standard data contains one employee, so the employee chart currently contains one full-width bar; descending multi-employee comparison will appear automatically as additional accounts gain task activity. + +## Promotion Candidates + +- None. This corrects the visual encoding of the already accepted count-based rankings without changing product, authorization, API, or data semantics. diff --git a/LianSyn-platform/app.js b/LianSyn-platform/app.js index 0de0115..2d5c04a 100644 --- a/LianSyn-platform/app.js +++ b/LianSyn-platform/app.js @@ -1322,21 +1322,15 @@ function renderOperationsDashboardSummary() { } } -function operationsDashboardRankBar(total, completed, maximum) { +function operationsDashboardRankBar(total, maximum) { const safeMaximum = Math.max(1, Number(maximum || 0)); const safeTotal = Math.max(0, Number(total || 0)); - const safeCompleted = Math.min(safeTotal, Math.max(0, Number(completed || 0))); const bar = el('span', 'operations-dashboard-rank-bar'); bar.setAttribute('aria-hidden', 'true'); if (safeTotal > 0) { - const totalFill = el('i', 'is-total'); - totalFill.style.width = `${Math.min(100, (safeTotal / safeMaximum) * 100)}%`; - bar.append(totalFill); - } - if (safeCompleted > 0) { - const successFill = el('i', 'is-success'); - successFill.style.width = `${Math.min(100, (safeCompleted / safeMaximum) * 100)}%`; - bar.append(successFill); + bar.style.width = `${Math.min(100, (safeTotal / safeMaximum) * 100)}%`; + } else { + bar.hidden = true; } return bar; } @@ -1345,7 +1339,8 @@ function renderOperationsDashboardBusinesses() { const container = $('#operationsDashboardBusinesses'); if (!container) return; container.replaceChildren(); - const businesses = operationsDashboardData?.businesses || []; + const businesses = [...(operationsDashboardData?.businesses || [])] + .sort((left, right) => Number(right.total || 0) - Number(left.total || 0)); if (!businesses.length) { container.append(el('p', 'operations-dashboard-empty', '当前范围内还没有任务类型数据。')); return; @@ -1360,7 +1355,7 @@ function renderOperationsDashboardBusinesses() { el('span', 'operations-dashboard-rank-number', String(index + 1).padStart(2, '0')), el('strong', 'operations-dashboard-rank-name', operationsDashboardBusinessLabel(item.label)) ); - button.append(operationsDashboardRankBar(item.total, item.completed, maximum)); + button.prepend(operationsDashboardRankBar(item.total, maximum)); const metrics = el('span', 'operations-dashboard-rank-metrics'); metrics.append( el('span', 'is-total', `操作 ${Number(item.total || 0)}`), @@ -1379,7 +1374,8 @@ function renderOperationsDashboardUsers() { const container = $('#operationsDashboardUsers'); if (!container) return; container.replaceChildren(); - const users = operationsDashboardData?.users || []; + const users = [...(operationsDashboardData?.users || [])] + .sort((left, right) => Number(right.total || 0) - Number(left.total || 0)); $('#operationsDashboardUserCount').textContent = `${users.length} 名员工`; if (!users.length) { container.append(el('p', 'operations-dashboard-empty', '当前范围内还没有人员工作数据。')); @@ -1401,13 +1397,14 @@ function renderOperationsDashboardUsers() { el('strong', '', item.actor.username), el('small', '', accountRoleLabel(item.actor.role)) ); - const bar = operationsDashboardRankBar(item.total, item.completed, maximum); + const bar = operationsDashboardRankBar(item.total, maximum); const metrics = el('div', 'operations-dashboard-user-metrics'); metrics.append( el('span', 'is-total', `操作 ${Number(item.total || 0)}`), el('span', 'is-success', `成功 ${Number(item.completed || 0)}`) ); - button.append(identity, bar, metrics); + button.prepend(bar); + button.append(identity, metrics); container.append(button); } } diff --git a/LianSyn-platform/index.html b/LianSyn-platform/index.html index 3ab5e93..5374bef 100644 --- a/LianSyn-platform/index.html +++ b/LianSyn-platform/index.html @@ -5,7 +5,7 @@