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