From 8550f6dc3e1df6fce9406b53e1832ac836ebd7d7 Mon Sep 17 00:00:00 2001 From: egg Date: Thu, 12 Feb 2026 08:13:07 +0800 Subject: [PATCH] fix(hold-history): align KPI cards with trend data, improve filters and UX across pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use Daily Trend as single source of truth for On Hold and New Hold KPI cards instead of separate snapshot SQL queries, eliminating value mismatches. Fix timezone bug in default date range (toISOString UTC offset), add 1st-of-month fallback to previous month, replace Hold Type radio buttons with select dropdown, reorder/relabel summary cards with 累計 prefix, add job-query MultiSelect for equipment filter, and fix heatmap chart X-axis overlap with visualMap legend. Co-Authored-By: Claude Opus 4.6 --- data/page_status.json | 4 +- frontend/src/hold-history/App.vue | 28 +++++++++--- .../src/hold-history/components/FilterBar.vue | 26 +++++------ .../hold-history/components/SummaryCards.vue | 40 ++++++++++------- frontend/src/hold-history/style.css | 22 +++++++--- frontend/src/job-query/App.vue | 44 ++++++++----------- .../src/portal-shell/nativeModuleRegistry.js | 2 +- .../components/HeatmapChart.vue | 4 +- .../routes/hold_history_routes.py | 4 -- .../services/hold_history_service.py | 22 ---------- .../sql/hold_history/still_on_hold_count.sql | 16 ------- tests/test_hold_history_routes.py | 10 ++--- tests/test_hold_history_service.py | 22 ---------- 13 files changed, 100 insertions(+), 144 deletions(-) delete mode 100644 src/mes_dashboard/sql/hold_history/still_on_hold_count.sql diff --git a/data/page_status.json b/data/page_status.json index 6d112a2..8e74e46 100644 --- a/data/page_status.json +++ b/data/page_status.json @@ -15,14 +15,14 @@ { "route": "/hold-overview", "name": "Hold 即時概況", - "status": "dev", + "status": "released", "drawer_id": "reports", "order": 2 }, { "route": "/hold-history", "name": "Hold 歷史績效", - "status": "dev", + "status": "released", "drawer_id": "drawer-2", "order": 3 }, diff --git a/frontend/src/hold-history/App.vue b/frontend/src/hold-history/App.vue index ac233cf..2fb6ef9 100644 --- a/frontend/src/hold-history/App.vue +++ b/frontend/src/hold-history/App.vue @@ -50,7 +50,10 @@ const errorMessage = ref(''); let activeRequestId = 0; function toDateString(value) { - return value.toISOString().slice(0, 10); + const y = value.getFullYear(); + const m = String(value.getMonth() + 1).padStart(2, '0'); + const d = String(value.getDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; } function getUrlParam(name) { @@ -75,8 +78,19 @@ function parseRecordTypeCsv(value) { function setDefaultDateRange() { const now = new Date(); - const start = new Date(now.getFullYear(), now.getMonth(), 1); - const end = new Date(now.getFullYear(), now.getMonth() + 1, 0); + let year = now.getFullYear(); + let month = now.getMonth(); + + if (now.getDate() === 1) { + month -= 1; + if (month < 0) { + month = 11; + year -= 1; + } + } + + const start = new Date(year, month, 1); + const end = new Date(year, month + 1, 0); filterBar.startDate = toDateString(start); filterBar.endDate = toDateString(end); } @@ -359,14 +373,18 @@ const summary = computed(() => { const netChange = releaseQty - newHoldQty - futureHoldQty; const avgHoldHours = estimateAvgHoldHours(durationData.value?.items || []); - const counts = trendData.value?.stillOnHoldCount || {}; - const stillOnHoldCount = Number(counts[trendTypeKey.value] || 0); + const today = new Date().toISOString().slice(0, 10); + const pastDays = days.filter((d) => d.date <= today); + const lastDay = pastDays.length > 0 ? pastDays[pastDays.length - 1] : {}; + const stillOnHoldCount = Number(lastDay.holdQty || 0); + const newHoldSnapshotCount = Number(lastDay.newHoldQty || 0); return { releaseQty, newHoldQty, futureHoldQty, stillOnHoldCount, + newHoldSnapshotCount, netChange, avgHoldHours, }; diff --git a/frontend/src/hold-history/components/FilterBar.vue b/frontend/src/hold-history/components/FilterBar.vue index 08b5f01..d0c9cb9 100644 --- a/frontend/src/hold-history/components/FilterBar.vue +++ b/frontend/src/hold-history/components/FilterBar.vue @@ -83,21 +83,17 @@ const holdTypeModel = computed({
- Hold Type -
- - - -
+ +
diff --git a/frontend/src/hold-history/components/SummaryCards.vue b/frontend/src/hold-history/components/SummaryCards.vue index c76d77c..845b140 100644 --- a/frontend/src/hold-history/components/SummaryCards.vue +++ b/frontend/src/hold-history/components/SummaryCards.vue @@ -7,6 +7,7 @@ const props = defineProps({ newHoldQty: 0, futureHoldQty: 0, stillOnHoldCount: 0, + newHoldSnapshotCount: 0, netChange: 0, avgHoldHours: 0, }), @@ -24,28 +25,33 @@ function formatHours(value) { diff --git a/frontend/src/hold-history/style.css b/frontend/src/hold-history/style.css index 15a6ba4..4e5a729 100644 --- a/frontend/src/hold-history/style.css +++ b/frontend/src/hold-history/style.css @@ -85,14 +85,27 @@ box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.18); } -.radio-group, +.hold-type-select { + min-width: 140px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 8px; + font-size: 13px; + background: #ffffff; +} + +.hold-type-select:focus { + outline: none; + border-color: #0ea5e9; + box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.18); +} + .checkbox-group { display: inline-flex; flex-wrap: wrap; gap: 10px; } -.radio-option, .checkbox-option { display: inline-flex; align-items: center; @@ -105,7 +118,6 @@ font-size: 13px; } -.radio-option.active, .checkbox-option.active { border-color: #0284c7; background: #e0f2fe; @@ -120,7 +132,7 @@ } .hold-history-summary-row { - grid-template-columns: repeat(6, minmax(0, 1fr)); + grid-template-columns: repeat(7, minmax(0, 1fr)); } .summary-card { @@ -290,7 +302,7 @@ @media (max-width: 1440px) { .hold-history-summary-row { - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(4, minmax(0, 1fr)); } } diff --git a/frontend/src/job-query/App.vue b/frontend/src/job-query/App.vue index 5c5ae1c..ad1a15d 100644 --- a/frontend/src/job-query/App.vue +++ b/frontend/src/job-query/App.vue @@ -1,6 +1,7 @@