feat(reject-history): multi-pareto 3×2 grid with cross-filter linkage

Replace single-dimension Pareto dropdown with 6 simultaneous Pareto charts
(不良原因, PACKAGE, TYPE, WORKFLOW, 站點, 機台) in a responsive 3-column grid.
Clicking items in one Pareto cross-filters the other 5 (exclude-self logic),
and the detail table applies all dimension selections with AND logic.

Backend:
- Add batch-pareto endpoint (cache-only, no Oracle queries)
- Add _apply_cross_filter() with exclude-self pattern
- Extend view/export endpoints for multi-dimension sel_* params

Frontend:
- New ParetoGrid.vue wrapping 6 ParetoSection instances
- Simplify ParetoSection: remove dimension dropdown, keep TOP20 toggle
- Replace single-dimension state with paretoSelections reactive object
- Adaptive x-axis labels (font size, rotation, hideOverlap) for compact grid
- Responsive grid: 3-col desktop, 2-col tablet, 1-col mobile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-03-02 14:43:35 +08:00
parent e83d8e1a36
commit 2568fd836c
20 changed files with 1558 additions and 534 deletions

View File

@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
import {
buildRejectOptionsRequestParams,
buildViewParams,
pruneRejectFilterSelections,
} from '../src/core/reject-history-filters.js';
import {
@@ -62,6 +63,45 @@ test('reject-history prune removes invalid selected values', () => {
assert.equal(pruned.removedCount, 3);
});
test('reject-history view params include multi-dimension pareto selections', () => {
const params = buildViewParams('qid-001', {
supplementaryFilters: {
packages: ['PKG-A'],
workcenterGroups: ['WB'],
reason: '001_A',
},
trendDates: ['2026-02-01'],
paretoSelections: {
reason: ['001_A'],
type: ['TYPE-A', 'TYPE-B'],
workflow: ['WF-01'],
},
page: 2,
perPage: 80,
policyFilters: {
includeExcludedScrap: true,
excludeMaterialScrap: false,
excludePbDiode: false,
},
});
assert.deepEqual(params, {
query_id: 'qid-001',
packages: ['PKG-A'],
workcenter_groups: ['WB'],
reason: '001_A',
trend_dates: ['2026-02-01'],
sel_reason: ['001_A'],
sel_type: ['TYPE-A', 'TYPE-B'],
sel_workflow: ['WF-01'],
page: 2,
per_page: 80,
include_excluded_scrap: 'true',
exclude_material_scrap: 'false',
exclude_pb_diode: 'false',
});
});
test('resource-history derives families from upstream group and flags', () => {
const resources = [
{ id: 'R1', name: 'MC-01', family: 'FAM-A', workcenterGroup: 'WB', isProduction: true, isKey: true, isMonitor: false },