From 1c46f5eb698100fbca602f314343452d770cc39e Mon Sep 17 00:00:00 2001 From: egg Date: Mon, 23 Feb 2026 07:10:51 +0800 Subject: [PATCH] chore(openspec): archive query-tool change and commit remaining updates --- .../src/query-tool/components/LotDetail.vue | 12 +- .../query-tool/components/LotJobsTable.vue | 286 ++++++++++++++++++ .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/lineage-engine-core/spec.md | 0 .../specs/progressive-trace-ux/spec.md | 0 .../specs/query-tool-lot-trace/spec.md | 0 .../specs/trace-staged-api/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/reject-history-page/spec.md | 0 .../tasks.md | 0 openspec/specs/reject-history-page/spec.md | 71 ++++- 16 files changed, 352 insertions(+), 17 deletions(-) create mode 100644 frontend/src/query-tool/components/LotJobsTable.vue rename openspec/changes/{query-tool-lineage-model-alignment => archive/2026-02-22-query-tool-lineage-model-alignment}/.openspec.yaml (100%) rename openspec/changes/{query-tool-lineage-model-alignment => archive/2026-02-22-query-tool-lineage-model-alignment}/design.md (100%) rename openspec/changes/{query-tool-lineage-model-alignment => archive/2026-02-22-query-tool-lineage-model-alignment}/proposal.md (100%) rename openspec/changes/{query-tool-lineage-model-alignment => archive/2026-02-22-query-tool-lineage-model-alignment}/specs/lineage-engine-core/spec.md (100%) rename openspec/changes/{query-tool-lineage-model-alignment => archive/2026-02-22-query-tool-lineage-model-alignment}/specs/progressive-trace-ux/spec.md (100%) rename openspec/changes/{query-tool-lineage-model-alignment => archive/2026-02-22-query-tool-lineage-model-alignment}/specs/query-tool-lot-trace/spec.md (100%) rename openspec/changes/{query-tool-lineage-model-alignment => archive/2026-02-22-query-tool-lineage-model-alignment}/specs/trace-staged-api/spec.md (100%) rename openspec/changes/{query-tool-lineage-model-alignment => archive/2026-02-22-query-tool-lineage-model-alignment}/tasks.md (100%) rename openspec/changes/{reject-history-ui-polish => archive/2026-02-22-reject-history-ui-polish}/.openspec.yaml (100%) rename openspec/changes/{reject-history-ui-polish => archive/2026-02-22-reject-history-ui-polish}/design.md (100%) rename openspec/changes/{reject-history-ui-polish => archive/2026-02-22-reject-history-ui-polish}/proposal.md (100%) rename openspec/changes/{reject-history-ui-polish => archive/2026-02-22-reject-history-ui-polish}/specs/reject-history-page/spec.md (100%) rename openspec/changes/{reject-history-ui-polish => archive/2026-02-22-reject-history-ui-polish}/tasks.md (100%) diff --git a/frontend/src/query-tool/components/LotDetail.vue b/frontend/src/query-tool/components/LotDetail.vue index 08ac2d3..9cb8e6e 100644 --- a/frontend/src/query-tool/components/LotDetail.vue +++ b/frontend/src/query-tool/components/LotDetail.vue @@ -4,6 +4,7 @@ import { computed } from 'vue'; import ExportButton from './ExportButton.vue'; import LotAssociationTable from './LotAssociationTable.vue'; import LotHistoryTable from './LotHistoryTable.vue'; +import LotJobsTable from './LotJobsTable.vue'; import LotRejectTable from './LotRejectTable.vue'; import LotTimeline from './LotTimeline.vue'; @@ -73,7 +74,7 @@ const tabMeta = Object.freeze({ materials: { label: '原物料', emptyText: '無原物料資料' }, rejects: { label: '報廢', emptyText: '無報廢資料' }, holds: { label: 'Hold', emptyText: '無 Hold 資料' }, - jobs: { label: 'Job', emptyText: '無 Job 資料' }, + jobs: { label: '維修', emptyText: '無維修資料' }, }); const subTabs = Object.keys(tabMeta); @@ -248,7 +249,7 @@ const detailCountLabel = computed(() => { { :column-order="activeColumnOrder" /> + + +import { computed, ref } from 'vue'; + +import { apiGet, ensureMesApiAvailable } from '../../core/api.js'; +import StatusBadge from '../../shared-ui/components/StatusBadge.vue'; +import { formatCellValue, formatDateTime, parseDateTime } from '../utils/values.js'; + +const props = defineProps({ + rows: { + type: Array, + default: () => [], + }, + loading: { + type: Boolean, + default: false, + }, + emptyText: { + type: String, + default: '無維修資料', + }, +}); + +ensureMesApiAvailable(); + +const JOB_COLUMN_PRIORITY = Object.freeze([ + 'JOBID', + 'RESOURCEID', + 'RESOURCENAME', + 'JOBSTATUS', + 'JOBMODELNAME', + 'JOBORDERNAME', + 'CREATEDATE', + 'COMPLETEDATE', + 'CANCELDATE', + 'FIRSTCLOCKONDATE', + 'LASTCLOCKOFFDATE', + 'CAUSECODENAME', + 'REPAIRCODENAME', + 'SYMPTOMCODENAME', + 'PJ_CAUSECODE2NAME', + 'PJ_REPAIRCODE2NAME', + 'PJ_SYMPTOMCODE2NAME', + 'CREATE_EMPNAME', + 'COMPLETE_EMPNAME', + 'CONTAINERIDS', + 'CONTAINERNAMES', +]); + +const TXN_COLUMN_PRIORITY = Object.freeze([ + 'JOBTXNHISTORYID', + 'JOBID', + 'TXNDATE', + 'FROMJOBSTATUS', + 'JOBSTATUS', + 'STAGENAME', + 'TOSTAGENAME', + 'CAUSECODENAME', + 'REPAIRCODENAME', + 'SYMPTOMCODENAME', + 'USER_EMPNO', + 'USER_NAME', + 'EMP_EMPNO', + 'EMP_NAME', + 'COMMENTS', + 'CDONAME', + 'JOBMODELNAME', + 'JOBORDERNAME', +]); + +const selectedJobId = ref(''); +const txnRows = ref([]); +const loadingTxn = ref(false); +const txnError = ref(''); + +function buildOrderedColumns(rows, preferred) { + const keys = Object.keys(rows?.[0] || {}); + if (keys.length === 0) { + return [...preferred]; + } + + const keySet = new Set(keys); + const ordered = preferred.filter((column) => keySet.has(column)); + const orderedSet = new Set(ordered); + keys.forEach((column) => { + if (!orderedSet.has(column)) { + ordered.push(column); + } + }); + return ordered; +} + +const sortedRows = computed(() => { + return [...(props.rows || [])].sort((a, b) => { + const aDate = parseDateTime(a?.CREATEDATE); + const bDate = parseDateTime(b?.CREATEDATE); + const aTime = aDate ? aDate.getTime() : 0; + const bTime = bDate ? bDate.getTime() : 0; + return bTime - aTime; + }); +}); + +const jobColumns = computed(() => { + return buildOrderedColumns(props.rows, JOB_COLUMN_PRIORITY); +}); + +const txnColumns = computed(() => { + return buildOrderedColumns(txnRows.value, TXN_COLUMN_PRIORITY); +}); + +function rowKey(row, index) { + return String(row?.JOBID || `${row?.RESOURCEID || ''}-${index}`); +} + +function buildStatusTone(status) { + const text = String(status || '').trim().toLowerCase(); + if (!text) { + return 'neutral'; + } + if (['complete', 'completed', 'done', 'closed', 'finish'].some((keyword) => text.includes(keyword))) { + return 'success'; + } + if (['open', 'pending', 'queue', 'wait', 'hold', 'in progress'].some((keyword) => text.includes(keyword))) { + return 'warning'; + } + if (['cancel', 'abort', 'fail', 'error'].some((keyword) => text.includes(keyword))) { + return 'danger'; + } + return 'neutral'; +} + +function renderJobCellValue(row, column) { + if (column === 'CREATEDATE' || column === 'COMPLETEDATE') { + return formatDateTime(row?.[column]); + } + return formatCellValue(row?.[column]); +} + +function renderTxnCellValue(row, column) { + const normalizedColumn = String(column || '').toUpperCase(); + if (normalizedColumn.includes('DATE') || normalizedColumn.includes('TIME')) { + return formatDateTime(row?.[column]); + } + if (column === 'USER_NAME') { + return formatCellValue(row?.USER_NAME || row?.EMP_NAME); + } + return formatCellValue(row?.[column]); +} + +async function loadTxn(jobId) { + const id = String(jobId || '').trim(); + if (!id) { + return; + } + + selectedJobId.value = id; + loadingTxn.value = true; + txnError.value = ''; + txnRows.value = []; + + try { + const payload = await apiGet(`/api/job-query/txn/${encodeURIComponent(id)}`, { + timeout: 60000, + silent: true, + }); + txnRows.value = Array.isArray(payload?.data) ? payload.data : []; + } catch (error) { + txnError.value = error?.message || '載入交易歷程失敗'; + txnRows.value = []; + } finally { + loadingTxn.value = false; + } +} + + + diff --git a/openspec/changes/query-tool-lineage-model-alignment/.openspec.yaml b/openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/.openspec.yaml similarity index 100% rename from openspec/changes/query-tool-lineage-model-alignment/.openspec.yaml rename to openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/.openspec.yaml diff --git a/openspec/changes/query-tool-lineage-model-alignment/design.md b/openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/design.md similarity index 100% rename from openspec/changes/query-tool-lineage-model-alignment/design.md rename to openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/design.md diff --git a/openspec/changes/query-tool-lineage-model-alignment/proposal.md b/openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/proposal.md similarity index 100% rename from openspec/changes/query-tool-lineage-model-alignment/proposal.md rename to openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/proposal.md diff --git a/openspec/changes/query-tool-lineage-model-alignment/specs/lineage-engine-core/spec.md b/openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/specs/lineage-engine-core/spec.md similarity index 100% rename from openspec/changes/query-tool-lineage-model-alignment/specs/lineage-engine-core/spec.md rename to openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/specs/lineage-engine-core/spec.md diff --git a/openspec/changes/query-tool-lineage-model-alignment/specs/progressive-trace-ux/spec.md b/openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/specs/progressive-trace-ux/spec.md similarity index 100% rename from openspec/changes/query-tool-lineage-model-alignment/specs/progressive-trace-ux/spec.md rename to openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/specs/progressive-trace-ux/spec.md diff --git a/openspec/changes/query-tool-lineage-model-alignment/specs/query-tool-lot-trace/spec.md b/openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/specs/query-tool-lot-trace/spec.md similarity index 100% rename from openspec/changes/query-tool-lineage-model-alignment/specs/query-tool-lot-trace/spec.md rename to openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/specs/query-tool-lot-trace/spec.md diff --git a/openspec/changes/query-tool-lineage-model-alignment/specs/trace-staged-api/spec.md b/openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/specs/trace-staged-api/spec.md similarity index 100% rename from openspec/changes/query-tool-lineage-model-alignment/specs/trace-staged-api/spec.md rename to openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/specs/trace-staged-api/spec.md diff --git a/openspec/changes/query-tool-lineage-model-alignment/tasks.md b/openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/tasks.md similarity index 100% rename from openspec/changes/query-tool-lineage-model-alignment/tasks.md rename to openspec/changes/archive/2026-02-22-query-tool-lineage-model-alignment/tasks.md diff --git a/openspec/changes/reject-history-ui-polish/.openspec.yaml b/openspec/changes/archive/2026-02-22-reject-history-ui-polish/.openspec.yaml similarity index 100% rename from openspec/changes/reject-history-ui-polish/.openspec.yaml rename to openspec/changes/archive/2026-02-22-reject-history-ui-polish/.openspec.yaml diff --git a/openspec/changes/reject-history-ui-polish/design.md b/openspec/changes/archive/2026-02-22-reject-history-ui-polish/design.md similarity index 100% rename from openspec/changes/reject-history-ui-polish/design.md rename to openspec/changes/archive/2026-02-22-reject-history-ui-polish/design.md diff --git a/openspec/changes/reject-history-ui-polish/proposal.md b/openspec/changes/archive/2026-02-22-reject-history-ui-polish/proposal.md similarity index 100% rename from openspec/changes/reject-history-ui-polish/proposal.md rename to openspec/changes/archive/2026-02-22-reject-history-ui-polish/proposal.md diff --git a/openspec/changes/reject-history-ui-polish/specs/reject-history-page/spec.md b/openspec/changes/archive/2026-02-22-reject-history-ui-polish/specs/reject-history-page/spec.md similarity index 100% rename from openspec/changes/reject-history-ui-polish/specs/reject-history-page/spec.md rename to openspec/changes/archive/2026-02-22-reject-history-ui-polish/specs/reject-history-page/spec.md diff --git a/openspec/changes/reject-history-ui-polish/tasks.md b/openspec/changes/archive/2026-02-22-reject-history-ui-polish/tasks.md similarity index 100% rename from openspec/changes/reject-history-ui-polish/tasks.md rename to openspec/changes/archive/2026-02-22-reject-history-ui-polish/tasks.md diff --git a/openspec/specs/reject-history-page/spec.md b/openspec/specs/reject-history-page/spec.md index 87b2c48..b89423e 100644 --- a/openspec/specs/reject-history-page/spec.md +++ b/openspec/specs/reject-history-page/spec.md @@ -4,7 +4,7 @@ TBD - created by archiving change reject-history-query-page. Update Purpose after archive. ## Requirements ### Requirement: Reject History page SHALL provide filterable historical query controls -The page SHALL provide a filter area for date range and major production dimensions to drive all report sections, and SHALL provide context-aware option narrowing for exploratory filtering. +The page SHALL provide a filter area for date range and major production dimensions to drive all report sections. #### Scenario: Default filter values - **WHEN** the page is first loaded @@ -23,20 +23,11 @@ The page SHALL provide a filter area for date range and major production dimensi - **THEN** it SHALL include reason filter control - **THEN** it SHALL include `WORKCENTER_GROUP` filter control -#### Scenario: Draft filter options are interdependent -- **WHEN** user changes draft values for `WORKCENTER_GROUP`, `package`, `reason`, or policy toggles -- **THEN** option candidates for reason/workcenter-group/package SHALL reload under the current draft context -- **THEN** unavailable combinations SHALL NOT remain in selectable options - -#### Scenario: Policy toggles affect option scope -- **WHEN** user changes policy toggles (including excluded-scrap and material-scrap switches) -- **THEN** options and query results SHALL use the same policy mode -- **THEN** option narrowing SHALL remain consistent with backend exclusion semantics - -#### Scenario: Invalid selected values are pruned -- **WHEN** narrowed options no longer contain previously selected values -- **THEN** invalid selections SHALL be removed automatically before query commit -- **THEN** apply/query SHALL only send valid selected values +#### Scenario: Header refresh button +- **WHEN** the page header is rendered +- **THEN** it SHALL include a "重新整理" button in the header-right area +- **WHEN** user clicks the refresh button +- **THEN** all sections SHALL reload with current filters (equivalent to "查詢") ### Requirement: Reject History page SHALL expose yield-exclusion toggle control The page SHALL let users decide whether to include policy-marked scrap in yield calculations. @@ -154,3 +145,53 @@ The page SHALL keep the same semantic grouping across desktop and mobile layouts - **WHEN** viewport width is below responsive breakpoint - **THEN** cards and chart panels SHALL stack in a single column - **THEN** filter controls SHALL remain operable without horizontal overflow + +### Requirement: Reject History page SHALL display a loading overlay during initial data load +The page SHALL show a full-screen loading overlay with spinner during the first data load to provide clear feedback. + +#### Scenario: Loading overlay on initial mount +- **WHEN** the page first mounts and `loadAllData` begins +- **THEN** a loading overlay with spinner SHALL be displayed over the page content +- **WHEN** all initial API responses complete +- **THEN** the overlay SHALL be hidden + +#### Scenario: Subsequent queries do not show overlay +- **WHEN** the user triggers a re-query after initial load +- **THEN** no full-screen overlay SHALL appear (inline loading states are sufficient) + +### Requirement: Detail table rows SHALL highlight on hover +The detail table and pareto table rows SHALL visually respond to mouse hover for improved readability. + +#### Scenario: Row hover in detail table +- **WHEN** user hovers over a row in the detail table +- **THEN** the row background SHALL change to a subtle highlight color + +#### Scenario: Row hover in pareto table +- **WHEN** user hovers over a row in the pareto summary table +- **THEN** the row background SHALL change to a subtle highlight color + +### Requirement: Pagination controls SHALL use Chinese labels +The detail list pagination SHALL display controls in Chinese to match the rest of the page language. + +#### Scenario: Pagination button labels +- **WHEN** the pagination controls are rendered +- **THEN** the previous-page button SHALL display "上一頁" +- **THEN** the next-page button SHALL display "下一頁" +- **THEN** the page info text SHALL use Chinese formatting (e.g., "第 1 / 5 頁 · 共 250 筆") + +### Requirement: Reject History page SHALL be structured as modular sub-components +The page template SHALL delegate sections to focused sub-components, following the hold-history architecture pattern. + +#### Scenario: Component decomposition +- **WHEN** the page source is examined +- **THEN** the filter panel SHALL be a separate `FilterPanel.vue` component +- **THEN** the KPI summary cards SHALL be a separate `SummaryCards.vue` component +- **THEN** the trend chart SHALL be a separate `TrendChart.vue` component +- **THEN** the pareto section (chart + table) SHALL be a separate `ParetoSection.vue` component +- **THEN** the detail table with pagination SHALL be a separate `DetailTable.vue` component + +#### Scenario: App.vue acts as orchestrator +- **WHEN** the page runs +- **THEN** `App.vue` SHALL hold all reactive state and API logic +- **THEN** sub-components SHALL receive data via props and communicate via events +