From 7bf9e33cd544b37f81cfb2deda00e4d25852e16f Mon Sep 17 00:00:00 2001 From: egg Date: Sun, 22 Feb 2026 11:54:51 +0800 Subject: [PATCH] feat: polish reject history UI and enhance WIP filter interactions --- frontend/src/core/wip-derive.js | 22 +- frontend/src/reject-history/App.vue | 623 ++++++------------ .../reject-history/components/DetailTable.vue | 85 +++ .../reject-history/components/FilterPanel.vue | 108 +++ .../components/ParetoSection.vue | 167 +++++ .../components/SummaryCards.vue | 27 + .../reject-history/components/TrendChart.vue | 105 +++ frontend/src/reject-history/style.css | 80 ++- frontend/src/wip-detail/App.vue | 217 +++++- .../src/wip-detail/components/FilterPanel.vue | 150 +++-- frontend/src/wip-detail/style.css | 84 +-- frontend/src/wip-overview/App.vue | 212 ++++-- .../wip-overview/components/FilterPanel.vue | 172 +++-- frontend/src/wip-overview/style.css | 131 +--- frontend/tests/wip-derive.test.js | 12 +- .../.openspec.yaml | 2 + .../design.md | 79 +++ .../proposal.md | 36 + .../specs/wip-overview-page/spec.md | 39 ++ .../tasks.md | 25 + .../reject-history-ui-polish/.openspec.yaml | 2 + .../reject-history-ui-polish/design.md | 57 ++ .../reject-history-ui-polish/proposal.md | 28 + .../specs/reject-history-page/spec.md | 78 +++ .../changes/reject-history-ui-polish/tasks.md | 26 + .../routes/reject_history_routes.py | 190 +++--- src/mes_dashboard/routes/wip_routes.py | 62 +- .../services/reject_history_service.py | 246 ++++++- src/mes_dashboard/services/wip_service.py | 512 ++++++++++++-- .../sql/reject_history/analytics.sql | 19 + .../sql/reject_history/filter_options.sql | 17 + src/mes_dashboard/sql/reject_history/list.sql | 83 ++- .../reject_history/performance_daily_lot.sql | 165 +++++ tests/test_wip_routes.py | 163 ++++- tests/test_wip_service.py | 115 +++- 35 files changed, 3054 insertions(+), 1085 deletions(-) create mode 100644 frontend/src/reject-history/components/DetailTable.vue create mode 100644 frontend/src/reject-history/components/FilterPanel.vue create mode 100644 frontend/src/reject-history/components/ParetoSection.vue create mode 100644 frontend/src/reject-history/components/SummaryCards.vue create mode 100644 frontend/src/reject-history/components/TrendChart.vue create mode 100644 openspec/changes/archive/2026-02-22-wip-overview-filter-dropdown-search/.openspec.yaml create mode 100644 openspec/changes/archive/2026-02-22-wip-overview-filter-dropdown-search/design.md create mode 100644 openspec/changes/archive/2026-02-22-wip-overview-filter-dropdown-search/proposal.md create mode 100644 openspec/changes/archive/2026-02-22-wip-overview-filter-dropdown-search/specs/wip-overview-page/spec.md create mode 100644 openspec/changes/archive/2026-02-22-wip-overview-filter-dropdown-search/tasks.md create mode 100644 openspec/changes/reject-history-ui-polish/.openspec.yaml create mode 100644 openspec/changes/reject-history-ui-polish/design.md create mode 100644 openspec/changes/reject-history-ui-polish/proposal.md create mode 100644 openspec/changes/reject-history-ui-polish/specs/reject-history-page/spec.md create mode 100644 openspec/changes/reject-history-ui-polish/tasks.md create mode 100644 src/mes_dashboard/sql/reject_history/analytics.sql create mode 100644 src/mes_dashboard/sql/reject_history/filter_options.sql create mode 100644 src/mes_dashboard/sql/reject_history/performance_daily_lot.sql diff --git a/frontend/src/core/wip-derive.js b/frontend/src/core/wip-derive.js index e181b33..96f7841 100644 --- a/frontend/src/core/wip-derive.js +++ b/frontend/src/core/wip-derive.js @@ -5,6 +5,16 @@ function toTrimmedString(value) { return String(value).trim(); } +function normalizeFilterValue(value) { + if (Array.isArray(value)) { + return value + .map((item) => toTrimmedString(item)) + .filter((item) => item.length > 0) + .join(','); + } + return toTrimmedString(value); +} + export function normalizeStatusFilter(statusFilter) { if (!statusFilter) { return {}; @@ -20,15 +30,19 @@ export function normalizeStatusFilter(statusFilter) { export function buildWipOverviewQueryParams(filters = {}, statusFilter = null) { const params = {}; - const workorder = toTrimmedString(filters.workorder); - const lotid = toTrimmedString(filters.lotid); - const pkg = toTrimmedString(filters.package); - const type = toTrimmedString(filters.type); + const workorder = normalizeFilterValue(filters.workorder); + const lotid = normalizeFilterValue(filters.lotid); + const pkg = normalizeFilterValue(filters.package); + const type = normalizeFilterValue(filters.type); + const firstname = normalizeFilterValue(filters.firstname); + const waferdesc = normalizeFilterValue(filters.waferdesc); if (workorder) params.workorder = workorder; if (lotid) params.lotid = lotid; if (pkg) params.package = pkg; if (type) params.type = type; + if (firstname) params.firstname = firstname; + if (waferdesc) params.waferdesc = waferdesc; return { ...params, ...normalizeStatusFilter(statusFilter) }; } diff --git a/frontend/src/reject-history/App.vue b/frontend/src/reject-history/App.vue index 5a50e5d..705634b 100644 --- a/frontend/src/reject-history/App.vue +++ b/frontend/src/reject-history/App.vue @@ -1,17 +1,14 @@ + + diff --git a/frontend/src/reject-history/components/FilterPanel.vue b/frontend/src/reject-history/components/FilterPanel.vue new file mode 100644 index 0000000..1d83d6a --- /dev/null +++ b/frontend/src/reject-history/components/FilterPanel.vue @@ -0,0 +1,108 @@ + + + diff --git a/frontend/src/reject-history/components/ParetoSection.vue b/frontend/src/reject-history/components/ParetoSection.vue new file mode 100644 index 0000000..bdbc67b --- /dev/null +++ b/frontend/src/reject-history/components/ParetoSection.vue @@ -0,0 +1,167 @@ + + + diff --git a/frontend/src/reject-history/components/SummaryCards.vue b/frontend/src/reject-history/components/SummaryCards.vue new file mode 100644 index 0000000..ae4a1f5 --- /dev/null +++ b/frontend/src/reject-history/components/SummaryCards.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/reject-history/components/TrendChart.vue b/frontend/src/reject-history/components/TrendChart.vue new file mode 100644 index 0000000..8f79b95 --- /dev/null +++ b/frontend/src/reject-history/components/TrendChart.vue @@ -0,0 +1,105 @@ + + + diff --git a/frontend/src/reject-history/style.css b/frontend/src/reject-history/style.css index 917705f..9efcb7f 100644 --- a/frontend/src/reject-history/style.css +++ b/frontend/src/reject-history/style.css @@ -52,6 +52,10 @@ grid-column: span 2; } +.filter-group-full { + grid-column: 1 / -1; +} + .filter-label { font-size: 12px; font-weight: 700; @@ -78,12 +82,17 @@ min-width: 0; } -.inline-toggle-group { - align-self: center; +.filter-toolbar { + grid-column: 1 / -1; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; } .checkbox-row { - display: inline-flex; + display: flex; align-items: center; flex-wrap: wrap; gap: 10px; @@ -111,8 +120,7 @@ .filter-actions { display: flex; gap: 10px; - justify-content: flex-end; - grid-column: span 2; + flex-shrink: 0; } .active-filter-chip-row { @@ -194,6 +202,17 @@ gap: 12px; } +.pareto-date-badge { + display: inline-block; + margin-left: 8px; + padding: 2px 8px; + border-radius: 999px; + background: #dbeafe; + color: #1e40af; + font-size: 12px; + font-weight: 600; +} + .pareto-layout { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); @@ -226,6 +245,10 @@ z-index: 1; } +.detail-table tbody tr:hover { + background: #f8fbff; +} + .reason-link { border: none; background: transparent; @@ -243,7 +266,39 @@ overflow: auto; } -/* ---- MultiSelect component styles (shared-ui compatible) ---- */ +.detail-table .cell-wrap { + white-space: normal; + max-width: 220px; + word-break: break-all; +} + +.detail-reason-badge { + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: 10px; + padding: 2px 10px; + border-radius: 999px; + background: #fef2f2; + color: #991b1b; + font-size: 12px; + font-weight: 600; +} + +.badge-clear { + border: 0; + background: transparent; + color: #991b1b; + cursor: pointer; + font-size: 14px; + line-height: 1; +} + +/* ---- MultiSelect component styles ---- + Duplicated from resource-shared/styles.css because this page imports + wip-shared/styles.css instead. Cannot import resource-shared directly + due to conflicting global class names (.dashboard, .btn, etc.). + TODO: Add