diff --git a/docs/spec/07-implementation-phases-todo.md b/docs/spec/07-implementation-phases-todo.md index b3d2c96..45700ad 100644 --- a/docs/spec/07-implementation-phases-todo.md +++ b/docs/spec/07-implementation-phases-todo.md @@ -287,6 +287,8 @@ Implemented layout foundation: - the toolbar remains reachable while the page scrolls - programmatic grid focus preserves the viewport during undo/redo reloads - wide tables no longer expand the surrounding browser layout +- active sorts and filters are summarized above the grid and can be removed individually or reset together +- column filter buttons show their selected-value count and filter menus explain their matching behavior Acceptance criteria: diff --git a/src/app/globals.css b/src/app/globals.css index 9038930..dae8ba5 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -71,6 +71,44 @@ body { opacity: 0.45; } +.active-view-summary { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.35rem; + padding: 0.4rem 0.5rem; + border: 1px solid #bfdbfe; + border-radius: 5px; + background: #eff6ff; + color: #1e3a5f; + font-size: 0.78rem; +} + +.active-view-label { + font-weight: 600; +} + +.active-view-chip, +.active-view-reset { + border: 1px solid #93b4df; + border-radius: 999px; + background: #fff; + color: #1e3a5f; + padding: 0.18rem 0.48rem; + font-size: 0.76rem; + cursor: pointer; +} + +.active-view-chip:hover, +.active-view-reset:hover { + border-color: #2563eb; +} + +.active-view-reset { + margin-left: auto; + font-weight: 600; +} + .column-settings-menu { position: absolute; z-index: 9; @@ -278,6 +316,7 @@ body { .tree-grid .header-filter-btn.active { border-color: #2563eb; color: #2563eb; + background: #eff6ff; } .tree-grid .header-filter-menu { @@ -293,13 +332,33 @@ body { padding: 0.35rem; } +.tree-grid .header-filter-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.4rem; + margin-bottom: 0.35rem; + font-size: 0.72rem; +} + +.tree-grid .header-filter-explanation { + margin-bottom: 0.4rem; + color: #4b5563; + font-size: 0.72rem; + line-height: 1.3; + white-space: normal; +} + .tree-grid .header-filter-clear { border: 1px solid #c4cddc; background: #fff; border-radius: 3px; font-size: 0.72rem; padding: 0.12rem 0.3rem; - margin-bottom: 0.25rem; +} + +.tree-grid .header-filter-clear:disabled { + opacity: 0.45; } .tree-grid .header-filter-values { diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index b5a5927..6fae96c 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -315,6 +315,15 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str [columnFilters] ); + const activeColumnFilters = useMemo( + () => + visibleColumns.flatMap((column) => { + const values = columnFilters[column.key] ?? []; + return values.length > 0 ? [{ column, values }] : []; + }), + [columnFilters, visibleColumns] + ); + const distinctValuesByColumn = useMemo(() => { // Filter options are generated only for visible columns to match current header UI. // Hidden columns still exist in row model, but are intentionally not filterable from header. @@ -333,6 +342,67 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str return buildVisibleGridRows(filteredSortedSections); }, [filteredSortedSections]); + function clearColumnFilter(key: CellKey) { + setColumnFilters((current) => { + const next = { ...current }; + delete next[key]; + return next; + }); + setOpenFilterColumn((current) => (current === key ? null : current)); + } + + function clearSortAndFilters() { + setSortState(null); + setColumnFilters({}); + setOpenFilterColumn(null); + } + + function renderActiveViewSummary() { + if (!sortState && activeColumnFilters.length === 0) { + return null; + } + + const sortedColumn = sortState + ? allColumns.find((column) => column.key === sortState.key) + : undefined; + + return ( +