Clarify active grid filters
This commit is contained in:
@@ -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 (
|
||||
<div className="active-view-summary" aria-label="Active sort and filters">
|
||||
<span className="active-view-label">Active view</span>
|
||||
{sortState ? (
|
||||
<button
|
||||
type="button"
|
||||
className="active-view-chip"
|
||||
onClick={() => setSortState(null)}
|
||||
title="Remove sorting"
|
||||
>
|
||||
Sort: {sortedColumn?.label ?? sortState.key} ({sortState.direction === "asc" ? "ascending" : "descending"})
|
||||
<span aria-hidden="true"> ×</span>
|
||||
</button>
|
||||
) : null}
|
||||
{activeColumnFilters.map(({ column, values }) => {
|
||||
const shownValues = values.slice(0, 2).join(", ");
|
||||
const valueSummary = values.length > 2 ? `${shownValues} +${values.length - 2}` : shownValues;
|
||||
return (
|
||||
<button
|
||||
key={`active-filter-${column.key}`}
|
||||
type="button"
|
||||
className="active-view-chip"
|
||||
onClick={() => clearColumnFilter(column.key)}
|
||||
title={`Remove filter for ${column.label}`}
|
||||
>
|
||||
{column.label}: {valueSummary}
|
||||
<span aria-hidden="true"> ×</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<button type="button" className="active-view-reset" onClick={clearSortAndFilters}>
|
||||
Reset view
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const searchableProjectDevices = useMemo(() => {
|
||||
const term = projectDeviceSearch.trim().toLowerCase();
|
||||
if (!term) {
|
||||
@@ -2247,11 +2317,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSortState(null);
|
||||
setColumnFilters({});
|
||||
setOpenFilterColumn(null);
|
||||
}}
|
||||
onClick={clearSortAndFilters}
|
||||
disabled={!Boolean(sortState) && !hasActiveFilters}
|
||||
>
|
||||
Clear sort/filter
|
||||
@@ -2303,6 +2369,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{renderActiveViewSummary()}
|
||||
{error ? (
|
||||
<div className="notice error editor-error-notice" role="alert">
|
||||
<span>{error}</span>
|
||||
@@ -2372,11 +2439,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSortState(null);
|
||||
setColumnFilters({});
|
||||
setOpenFilterColumn(null);
|
||||
}}
|
||||
onClick={clearSortAndFilters}
|
||||
disabled={!isSortedView && !hasActiveFilters}
|
||||
>
|
||||
Clear sort/filter
|
||||
@@ -2425,6 +2488,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{renderActiveViewSummary()}
|
||||
{hasActiveSortOrFilter ? (
|
||||
<div className="notice muted">Sorting/filtering is view-only. Renumber is disabled while sort/filter is active.</div>
|
||||
) : null}
|
||||
@@ -2587,24 +2651,36 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
type="button"
|
||||
className={`header-filter-btn ${(columnFilters[column.key]?.length ?? 0) > 0 ? "active" : ""}`}
|
||||
onClick={() => setOpenFilterColumn((current) => (current === column.key ? null : column.key))}
|
||||
aria-pressed={(columnFilters[column.key]?.length ?? 0) > 0}
|
||||
title={
|
||||
(columnFilters[column.key]?.length ?? 0) > 0
|
||||
? `${columnFilters[column.key]?.length} filter value(s) selected`
|
||||
: `Filter ${column.label}`
|
||||
}
|
||||
>
|
||||
Filter
|
||||
Filter{(columnFilters[column.key]?.length ?? 0) > 0 ? ` (${columnFilters[column.key]?.length})` : ""}
|
||||
</button>
|
||||
</div>
|
||||
{openFilterColumn === column.key ? (
|
||||
<div className="header-filter-menu">
|
||||
<button
|
||||
type="button"
|
||||
className="header-filter-clear"
|
||||
onClick={() =>
|
||||
setColumnFilters((current) => ({
|
||||
...current,
|
||||
[column.key]: [],
|
||||
}))
|
||||
}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<div className="header-filter-explanation">
|
||||
Select one or more values. A circuit is shown when its block contains any selected value.
|
||||
</div>
|
||||
<div className="header-filter-actions">
|
||||
<span>
|
||||
{(columnFilters[column.key]?.length ?? 0) > 0
|
||||
? `${columnFilters[column.key]?.length} selected`
|
||||
: "Showing all"}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="header-filter-clear"
|
||||
onClick={() => clearColumnFilter(column.key)}
|
||||
disabled={(columnFilters[column.key]?.length ?? 0) === 0}
|
||||
>
|
||||
Show all
|
||||
</button>
|
||||
</div>
|
||||
<div className="header-filter-values">
|
||||
{distinctValuesByColumn[column.key].map((value) => {
|
||||
const selected = columnFilters[column.key] ?? [];
|
||||
|
||||
Reference in New Issue
Block a user