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({
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) {
-
- Release 數量
- {{ formatNumber(summary.releaseQty) }}
-
-
-
- New Hold 數量
- {{ formatNumber(summary.newHoldQty) }}
-
-
-
- Future Hold 數量
- {{ formatNumber(summary.futureHoldQty) }}
-
-
On Hold 數量
{{ formatNumber(summary.stillOnHoldCount) }}
+
+ 最末日新增 Hold
+ {{ formatNumber(summary.newHoldSnapshotCount) }}
+
+
+
+ 累計新增 Hold
+ {{ formatNumber(summary.newHoldQty) }}
+
+
+
+ 累計 Release
+ {{ formatNumber(summary.releaseQty) }}
+
+
+
+ 累計 Future Hold
+ {{ formatNumber(summary.futureHoldQty) }}
+
+
- 淨變動 (Release - New - Future)
+ 累計淨變動 (Release - New - Future)
{{ formatNumber(summary.netChange) }}
@@ -53,7 +59,7 @@ function formatHours(value) {
平均 Hold 時長
- {{ formatHours(summary.avgHoldHours) }}
+ {{ formatHours(summary.avgHoldHours) }}
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 @@