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
useRejectHistorycomposable if needed.