Files
DashBoard/openspec/changes/reject-history-ui-polish/design.md

4.0 KiB

Context

The reject-history page is a monolithic App.vue (~968 lines template+script) with a co-located style.css. It was built quickly and works, but differs from the hold-history page (the maturity benchmark) in structure and several UI details. The hold-history page delegates to 7 sub-components and follows project-wide conventions (loading overlay, hover effects, Chinese pagination text, header refresh button).

The page imports wip-shared/styles.css for design tokens and global classes, and resource-shared/components/MultiSelect.vue for multi-select dropdowns — but also duplicates ~120 lines of MultiSelect CSS in its own style.css.

Goals / Non-Goals

Goals:

  • Match hold-history's visual baseline: loading overlay, table hover, Chinese pagination, header refresh button
  • Extract App.vue into sub-components following hold-history's proven pattern
  • Remove duplicated MultiSelect CSS
  • Keep all existing functionality and API interactions unchanged

Non-Goals:

  • Changing column names or data display (user explicitly excluded #1)
  • Adding new features, APIs, or functional capabilities
  • Migrating to Tailwind or shared-ui components (page stays on wip-shared CSS)
  • Touching backend code

Decisions

D1: Component extraction mirrors hold-history's architecture

Extract into 5 sub-components under frontend/src/reject-history/components/:

Component Responsibility hold-history equivalent
FilterPanel.vue Filter grid, checkboxes, action buttons, active chips FilterBar.vue
SummaryCards.vue 6 KPI cards with lane colors SummaryCards.vue
TrendChart.vue Quantity trend bar chart (vue-echarts) DailyTrend.vue
ParetoSection.vue Pareto chart + table side-by-side ReasonPareto.vue
DetailTable.vue Detail table + pagination DetailTable.vue

Rationale: The hold-history pattern is proven and familiar to the team. Same granularity, same naming convention.

D2: State stays in App.vue, components receive props + emit events

App.vue keeps all reactive state (filters, summary, trend, pareto, detail, loading, etc.) and API functions. Sub-components are presentational. This matches hold-history exactly and avoids over-engineering with composables for a single-page report.

D3: Remove duplicated MultiSelect CSS, rely on resource-shared import chain

The MultiSelect component from resource-shared/components/MultiSelect.vue already bundles its own styles. The ~120 lines duplicated in reject-history/style.css (.multi-select, .multi-select-trigger, .multi-select-dropdown, etc.) can be deleted.

Risk: If some pages import MultiSelect without importing resource-shared/styles.css, they break. But reject-history doesn't import resource-shared/styles.css either — the MultiSelect component uses scoped styles or injects its own. Verify before deleting.

D4: Loading overlay uses existing wip-shared pattern

Add <div v-if="loading.initial" class="loading-overlay"><span class="loading-spinner"></span></div> after the .dashboard div, identical to hold-history. The .loading-overlay and .loading-spinner classes are already defined in wip-shared/styles.css.

Risks / Trade-offs

  • [Risk] MultiSelect CSS deletion breaks styling → Verify the MultiSelect component renders correctly after removing the duplicated CSS. If it doesn't, the component may need its own <style scoped> block or the import chain needs adjustment.
  • [Risk] Extraction introduces subtle regressions → Each component boundary is a potential data-flow bug. Mitigate by keeping the extraction mechanical: cut template section → paste into component → add props/emits.
  • [Trade-off] No composable extraction → The script logic stays in App.vue (400+ lines). This is acceptable for now — hold-history works the same way. Future refactoring can extract a useRejectHistory composable if needed.