feat(wip): preserve filters between Overview and Detail with thundering-herd fix
URL is now single source of truth for filter state (workorder, lotid, package, type, status) across WIP Overview and Detail pages. Drill-down carries all filters + status; back button dynamically reflects Detail changes. Backend Detail API now supports pj_type filter parameter. Harden concurrency: add pagehide abort for MPA navigation, double-check locking on Redis JSON parse and snapshot build to prevent thread pool saturation during rapid page switching. Fix watchdog setsid and PID discovery. Fix test_realtime_equipment_cache RUNCARDLOTID field mismatch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-10
|
||||
@@ -0,0 +1,56 @@
|
||||
## Context
|
||||
|
||||
WIP Overview 和 Detail 是兩個獨立的 Vite multi-page 應用,透過 `window.location.href` 導航。目前 Overview 的 filter 狀態只存在 `reactive()` 物件中,不反映到 URL;Detail 已有 URL 狀態管理(`getUrlParam` / `updateUrlState`),但不包含 status filter。Back button 是 hard-coded `<a href="/wip-overview">`,導致返回時所有狀態丟失。
|
||||
|
||||
兩個頁面都不使用 Vue Router(各自是獨立 Vite entry),所以導航都是 full-page navigation,狀態只能透過 URL params 傳遞。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- URL 作為 filter 狀態的 single source of truth,兩頁面一致
|
||||
- Overview → Detail drill-down 傳遞所有 filters + status
|
||||
- Detail → Overview back navigation 還原所有 filters + status(含 Detail 中的變更)
|
||||
- 無參數時行為與現行完全相同(backwards compatible)
|
||||
|
||||
**Non-Goals:**
|
||||
- 不引入 sessionStorage / localStorage / Pinia 全域狀態管理
|
||||
- 不修改 API endpoints 或 backend 邏輯
|
||||
- 不改變 pagination 狀態的傳遞(pagination 是 Detail 內部狀態,不帶回 Overview)
|
||||
- 不改變 Hold Detail 頁的 back link 行為
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: URL params 作為唯一狀態傳遞機制
|
||||
|
||||
**選擇**: 透過 URL query params 在頁面間傳遞 filter 和 status 狀態
|
||||
|
||||
**替代方案**:
|
||||
- sessionStorage:URL 乾淨但引入隱藏狀態,debug 困難,tab 生命週期不可控
|
||||
- localStorage:跨 tab 污染,多開情境容易混亂
|
||||
|
||||
**理由**: Detail 已經用 URL params 管理 filter 狀態,Overview 採相同模式保持一致性。URL 可 bookmark、可分享、可 debug。
|
||||
|
||||
### D2: Overview 用 `history.replaceState` 同步 URL(不產生 history entry)
|
||||
|
||||
**選擇**: 每次 filter/status 變更後用 `replaceState` 更新 URL,不用 `pushState`
|
||||
|
||||
**理由**: filter 切換不應產生 browser back history,避免用戶按 back 時陷入 filter 歷史中。Detail 已是相同做法。
|
||||
|
||||
### D3: Detail back button 用 computed URL 組合當前所有 filter 狀態
|
||||
|
||||
**選擇**: `<a :href="backUrl">` 其中 `backUrl` 是 computed property,從當前 Detail 的 filters + status 動態組出 `/wip-overview?...`
|
||||
|
||||
**理由**: 如果用戶在 Detail 中變更了 filter 或 status,返回 Overview 應反映這些變更。computed 確保 backUrl 永遠是最新狀態。
|
||||
|
||||
### D4: Status filter 使用字串值作為 URL param
|
||||
|
||||
**選擇**: `status` 參數值直接使用 `activeStatusFilter` 的值(`RUN`, `QUEUE`, `quality-hold`, `non-quality-hold`)
|
||||
|
||||
**理由**: 這些值已在 API 呼叫的 query params 中使用(`buildWipOverviewQueryParams` / `buildWipDetailQueryParams`),直接複用保持一致。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[Risk] URL 長度** → 4 個 filter fields + status + workcenter 不會超過 URL 長度限制,風險極低
|
||||
- **[Risk] 空值造成冗長 URL** → 只 append 非空值的 params,空 filter 不出現在 URL 中
|
||||
- **[Trade-off] Overview 載入時多一步 URL parsing** → 極輕量操作,無性能影響
|
||||
- **[Trade-off] Back button 從 static `<a>` 變成 dynamic `:href`** → Vue reactive 計算,無感知差異
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
WIP Overview 和 WIP Detail 之間的篩選條件無法雙向保留。用戶在 Overview 設定的 filters(workorder, lotid, package, type)和 status filter(RUN/QUEUE/品質異常/非品質異常)在 drill down 到 Detail 時只部分傳遞(缺 status),而從 Detail 返回 Overview 時所有篩選狀態完全丟失。這迫使用戶反覆重新輸入篩選條件,破壞了 drill-down 的分析流程。
|
||||
|
||||
## What Changes
|
||||
|
||||
- Overview 頁面新增 URL 狀態管理:所有 filters 和 status filter 同步到 URL query params,頁面載入時從 URL 還原狀態
|
||||
- Overview drill-down 導航額外傳遞 `status` 參數到 Detail
|
||||
- Detail 頁面初始化時額外讀取 `status` URL 參數並還原 status filter 狀態
|
||||
- Detail 頁面的 `updateUrlState()` 額外同步 `status` 參數
|
||||
- Detail 的 Back button 改為動態 computed URL,攜帶當前所有 filter + status 回 Overview
|
||||
- Detail 中 `toggleStatusFilter()` 操作後同步 URL 狀態
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_None — this change enhances existing capabilities._
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `wip-overview-page`: Overview 新增 URL 狀態管理(filters + status 雙向同步到 URL),drill-down 導航額外傳遞 status 參數
|
||||
- `wip-detail-page`: Detail 新增 status URL 參數讀寫,Back button 改為動態 URL 攜帶所有 filter 狀態回 Overview
|
||||
|
||||
## Impact
|
||||
|
||||
- **Frontend**: `frontend/src/wip-overview/App.vue` — 新增 `initializePage()`、`updateUrlState()`,修改 `navigateToDetail()`、`applyFilters()`、`clearFilters()`、`removeFilter()`、`toggleStatusFilter()`
|
||||
- **Frontend**: `frontend/src/wip-detail/App.vue` — 修改 `initializePage()` 加讀 status、`updateUrlState()` 加寫 status、`toggleStatusFilter()` 加呼叫 `updateUrlState()`、back button 改為 computed `backUrl`
|
||||
- **No backend changes** — 所有 API endpoints 和 SQL 不需修改
|
||||
- **No breaking changes** — URL params 為 additive,無參數時行為與現行相同
|
||||
@@ -0,0 +1,63 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Detail page SHALL receive drill-down parameters from Overview
|
||||
The page SHALL read URL query parameters to initialize its state from the Overview page drill-down.
|
||||
|
||||
#### Scenario: URL parameter initialization
|
||||
- **WHEN** the page loads with `?workcenter={name}` in the URL
|
||||
- **THEN** the page SHALL use the specified workcenter for data loading
|
||||
- **THEN** the page title SHALL display "WIP Detail - {workcenter}"
|
||||
|
||||
#### Scenario: Filter passthrough from Overview
|
||||
- **WHEN** the URL contains additional filter parameters (workorder, lotid, package, type)
|
||||
- **THEN** filter inputs SHALL be pre-filled with those values
|
||||
- **THEN** data SHALL be loaded with those filters applied
|
||||
|
||||
#### Scenario: Status passthrough from Overview
|
||||
- **WHEN** the URL contains a `status` parameter (e.g., `?workcenter=焊接_DW&status=RUN`)
|
||||
- **THEN** the status card corresponding to the `status` value SHALL be activated
|
||||
- **THEN** data SHALL be loaded with the status filter applied
|
||||
|
||||
#### Scenario: Missing workcenter fallback
|
||||
- **WHEN** the page loads without a `workcenter` parameter
|
||||
- **THEN** the page SHALL fetch available workcenters from `GET /api/wip/meta/workcenters`
|
||||
- **THEN** the first workcenter SHALL be used and the URL SHALL be updated via `replaceState`
|
||||
|
||||
### Requirement: Detail page SHALL display WIP summary cards
|
||||
The page SHALL display five summary cards with status counts for the current workcenter.
|
||||
|
||||
#### Scenario: Summary cards rendering
|
||||
- **WHEN** detail data is loaded
|
||||
- **THEN** five cards SHALL display: Total Lots, RUN, QUEUE, 品質異常, 非品質異常
|
||||
|
||||
#### Scenario: Status card click filters table
|
||||
- **WHEN** user clicks a status card (RUN, QUEUE, 品質異常, 非品質異常)
|
||||
- **THEN** the lot table SHALL reload filtered to that status
|
||||
- **THEN** the active card SHALL show a visual active state
|
||||
- **THEN** non-active status cards SHALL dim
|
||||
- **THEN** clicking the same card again SHALL remove the filter
|
||||
- **THEN** the URL SHALL be updated to reflect the active status filter
|
||||
|
||||
### Requirement: Detail page SHALL have back navigation to Overview with filter preservation
|
||||
The page SHALL provide a way to return to the Overview page while preserving all current filter state.
|
||||
|
||||
#### Scenario: Back button with filter state
|
||||
- **WHEN** user clicks the "← Overview" button in the header
|
||||
- **THEN** the page SHALL navigate to `/wip-overview` with current filter values (workorder, lotid, package, type) and status as URL parameters
|
||||
- **THEN** only non-empty filter values SHALL appear as URL parameters
|
||||
|
||||
#### Scenario: Back button reflects Detail changes
|
||||
- **WHEN** the user modifies filters or status in Detail (e.g., changes status from RUN to QUEUE)
|
||||
- **THEN** the back button URL SHALL dynamically update to reflect the current Detail filter state
|
||||
- **THEN** navigating back SHALL cause Overview to load with the updated filter state
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Detail page SHALL synchronize status filter to URL
|
||||
The page SHALL include the active status filter in URL state management.
|
||||
|
||||
#### Scenario: Status included in URL state
|
||||
- **WHEN** the status filter is active
|
||||
- **THEN** `updateUrlState()` SHALL include `status={value}` in the URL parameters
|
||||
- **WHEN** the status filter is cleared
|
||||
- **THEN** the `status` parameter SHALL be removed from the URL
|
||||
@@ -0,0 +1,79 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Overview page SHALL display WIP status breakdown cards
|
||||
The page SHALL display four clickable status cards (RUN, QUEUE, 品質異常, 非品質異常) with lot and quantity counts.
|
||||
|
||||
#### Scenario: Status cards rendering
|
||||
- **WHEN** summary data is loaded
|
||||
- **THEN** four status cards SHALL be displayed with color coding (green=RUN, yellow=QUEUE, red=品質異常, orange=非品質異常)
|
||||
- **THEN** each card SHALL show lot count and quantity
|
||||
|
||||
#### Scenario: Status card click filters matrix
|
||||
- **WHEN** user clicks a status card
|
||||
- **THEN** the matrix table SHALL reload with the selected status filter
|
||||
- **THEN** the clicked card SHALL show an active visual state
|
||||
- **THEN** non-active cards SHALL dim to 50% opacity
|
||||
- **THEN** clicking the same card again SHALL deactivate the filter and restore all cards
|
||||
- **THEN** the URL SHALL be updated to reflect the active status filter
|
||||
|
||||
### Requirement: Overview page SHALL display Workcenter × Package matrix
|
||||
The page SHALL display a cross-tabulation table of workcenters vs packages.
|
||||
|
||||
#### Scenario: Matrix table rendering
|
||||
- **WHEN** matrix data is loaded from `GET /api/wip/overview/matrix`
|
||||
- **THEN** the table SHALL display workcenters as rows and packages as columns (limited to top 15)
|
||||
- **THEN** the first column (Workcenter) SHALL be sticky on horizontal scroll
|
||||
- **THEN** a Total row and Total column SHALL be displayed
|
||||
|
||||
#### Scenario: Matrix workcenter drill-down
|
||||
- **WHEN** user clicks a workcenter name in the matrix
|
||||
- **THEN** the page SHALL navigate to `/wip-detail?workcenter={name}`
|
||||
- **THEN** active filter values (workorder, lotid, package, type) SHALL be passed as URL parameters
|
||||
- **THEN** the active status filter SHALL be passed as the `status` URL parameter if set
|
||||
|
||||
### Requirement: Overview page SHALL support autocomplete filtering
|
||||
The page SHALL provide autocomplete-enabled filter inputs for WORKORDER, LOT ID, PACKAGE, and TYPE.
|
||||
|
||||
#### Scenario: Autocomplete search
|
||||
- **WHEN** user types 2+ characters in a filter input
|
||||
- **THEN** the page SHALL call `GET /api/wip/meta/search` with debounce (300ms)
|
||||
- **THEN** suggestions SHALL appear in a dropdown below the input
|
||||
- **THEN** cross-filter parameters SHALL be included (other active filter values)
|
||||
|
||||
#### Scenario: Apply and clear filters
|
||||
- **WHEN** user clicks "套用篩選" or presses Enter in a filter input
|
||||
- **THEN** all three API calls (summary, matrix, hold) SHALL reload with the filter values
|
||||
- **THEN** the URL SHALL be updated to reflect the applied filter values
|
||||
- **WHEN** user clicks "清除篩選"
|
||||
- **THEN** all filter inputs SHALL be cleared and data SHALL reload without filters
|
||||
- **THEN** the URL SHALL be cleared of all filter and status parameters
|
||||
|
||||
#### Scenario: Active filter display
|
||||
- **WHEN** filters are applied
|
||||
- **THEN** active filters SHALL be displayed as removable tags (e.g., "WO: {value} ×")
|
||||
- **THEN** clicking a tag's remove button SHALL clear that filter, reload data, and update the URL
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Overview page SHALL persist filter state in URL
|
||||
The page SHALL synchronize all filter state (workorder, lotid, package, type, status) to URL query parameters as the single source of truth.
|
||||
|
||||
#### Scenario: URL state initialization on page load
|
||||
- **WHEN** the page loads with filter query parameters in the URL (e.g., `?package=SOD-323&status=RUN`)
|
||||
- **THEN** the filter inputs SHALL be pre-filled with the URL parameter values
|
||||
- **THEN** the status card corresponding to the `status` parameter SHALL be activated
|
||||
- **THEN** data SHALL be loaded with all restored filters and status applied
|
||||
|
||||
#### Scenario: URL state initialization without parameters
|
||||
- **WHEN** the page loads without any filter query parameters
|
||||
- **THEN** all filters SHALL be empty and no status card SHALL be active
|
||||
- **THEN** data SHALL load without filters (current default behavior)
|
||||
|
||||
#### Scenario: URL update on filter change
|
||||
- **WHEN** filters are applied, cleared, or a single filter is removed
|
||||
- **THEN** the URL SHALL be updated via `history.replaceState` to reflect the current filter state
|
||||
- **THEN** only non-empty filter values SHALL appear as URL parameters
|
||||
|
||||
#### Scenario: URL update on status toggle
|
||||
- **WHEN** a status card is clicked to activate or deactivate
|
||||
- **THEN** the URL SHALL be updated via `history.replaceState` to include or remove the `status` parameter
|
||||
@@ -0,0 +1,28 @@
|
||||
## 1. WIP Overview — URL 狀態管理
|
||||
|
||||
- [x] 1.1 新增 `updateUrlState()` 函式:將 filters (workorder, lotid, package, type) 和 activeStatusFilter 同步到 URL via `history.replaceState`,只 append 非空值
|
||||
- [x] 1.2 新增 `initializePage()` 函式:從 URL params 讀取 filters + status,還原到 `filters` reactive 和 `activeStatusFilter` ref,然後呼叫 `loadAllData(true)`;取代目前的 `void loadAllData(true)` 直接呼叫
|
||||
- [x] 1.3 修改 `applyFilters()`、`clearFilters()`、`removeFilter()` 三個函式:每次操作後呼叫 `updateUrlState()`
|
||||
- [x] 1.4 修改 `toggleStatusFilter()`:操作後呼叫 `updateUrlState()`
|
||||
|
||||
## 2. WIP Overview — Drill-Down 帶 Status
|
||||
|
||||
- [x] 2.1 修改 `navigateToDetail()`:在組建 URL params 時,若 `activeStatusFilter.value` 非 null,append `status` 參數
|
||||
|
||||
## 3. WIP Detail — 讀取 Status URL 參數
|
||||
|
||||
- [x] 3.1 修改 `initializePage()`:新增 `activeStatusFilter.value = getUrlParam('status') || null`,在 filters 讀取之後、`loadAllData` 之前
|
||||
- [x] 3.2 修改 `updateUrlState()`:若 `activeStatusFilter.value` 非 null,`params.set('status', activeStatusFilter.value)`
|
||||
- [x] 3.3 修改 `toggleStatusFilter()`:操作後呼叫 `updateUrlState()`
|
||||
|
||||
## 4. WIP Detail — Back Button 動態 URL
|
||||
|
||||
- [x] 4.1 新增 computed `backUrl`:從當前 filters + activeStatusFilter 組出 `/wip-overview?...`(只含非空值,不含 workcenter)
|
||||
- [x] 4.2 將 template 中 `<a href="/wip-overview">` 改為 `<a :href="backUrl">`
|
||||
|
||||
## 5. 驗證
|
||||
|
||||
- [x] 5.1 驗證:Overview 設定 filter + status → drill down → Detail 正確還原所有狀態
|
||||
- [x] 5.2 驗證:Detail 中變更 filter/status → 點 Back → Overview 正確還原變更後的狀態
|
||||
- [x] 5.3 驗證:無參數直接訪問 `/wip-overview` 和 `/wip-detail` 行為與現行相同
|
||||
- [x] 5.4 驗證:Overview 的 clearFilters 清除所有 filter + status 並更新 URL
|
||||
Reference in New Issue
Block a user