feat(base): route toolbar sort/filter changes through local draft

This commit is contained in:
Philipinho
2026-04-20 22:54:25 +01:00
parent 6740912adf
commit 184fa25d3e
2 changed files with 37 additions and 27 deletions
@@ -7,6 +7,10 @@ import { arrayMove } from "@dnd-kit/sortable";
import { generateJitteredKeyBetween } from "fractional-indexing-jittered"; import { generateJitteredKeyBetween } from "fractional-indexing-jittered";
import { useBaseQuery } from "@/features/base/queries/base-query"; import { useBaseQuery } from "@/features/base/queries/base-query";
import { useBaseSocket } from "@/features/base/hooks/use-base-socket"; import { useBaseSocket } from "@/features/base/hooks/use-base-socket";
import {
FilterGroup,
ViewSortConfig,
} from "@/features/base/types/base.types";
import { import {
useBaseRowsQuery, useBaseRowsQuery,
flattenRows, flattenRows,
@@ -179,6 +183,20 @@ export function BaseTable({ baseId }: BaseTableProps) {
persistViewConfig(); persistViewConfig();
}, [persistViewConfig]); }, [persistViewConfig]);
const handleDraftSortsChange = useCallback(
(sorts: ViewSortConfig[] | undefined) => {
setDraftSorts(sorts && sorts.length > 0 ? sorts : undefined);
},
[setDraftSorts],
);
const handleDraftFiltersChange = useCallback(
(filter: FilterGroup | undefined) => {
setDraftFilter(filter);
},
[setDraftFilter],
);
const handleRowReorder = useCallback( const handleRowReorder = useCallback(
(rowId: string, targetRowId: string, dropPosition: "above" | "below") => { (rowId: string, targetRowId: string, dropPosition: "above" | "below") => {
const remainingRows = rows.filter((r) => r.id !== rowId); const remainingRows = rows.filter((r) => r.id !== rowId);
@@ -235,12 +253,14 @@ export function BaseTable({ baseId }: BaseTableProps) {
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}> <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
<BaseToolbar <BaseToolbar
base={base} base={base}
activeView={activeView} activeView={effectiveView}
views={views} views={views}
table={table} table={table}
onViewChange={handleViewChange} onViewChange={handleViewChange}
onAddView={handleAddView} onAddView={handleAddView}
onPersistViewConfig={persistViewConfig} onPersistViewConfig={persistViewConfig}
onDraftSortsChange={handleDraftSortsChange}
onDraftFiltersChange={handleDraftFiltersChange}
/> />
<GridContainer <GridContainer
table={table} table={table}
@@ -16,8 +16,6 @@ import {
FilterCondition, FilterCondition,
FilterGroup, FilterGroup,
} from "@/features/base/types/base.types"; } from "@/features/base/types/base.types";
import { useUpdateViewMutation } from "@/features/base/queries/base-view-query";
import { buildViewConfigFromTable } from "@/features/base/hooks/use-base-table";
import { exportBaseToCsv } from "@/features/base/services/base-service"; import { exportBaseToCsv } from "@/features/base/services/base-service";
import { ViewTabs } from "@/features/base/components/views/view-tabs"; import { ViewTabs } from "@/features/base/components/views/view-tabs";
import { ViewSortConfigPopover } from "@/features/base/components/views/view-sort-config"; import { ViewSortConfigPopover } from "@/features/base/components/views/view-sort-config";
@@ -28,12 +26,18 @@ import classes from "@/features/base/styles/grid.module.css";
type BaseToolbarProps = { type BaseToolbarProps = {
base: IBase; base: IBase;
// Effective view — baseline merged with any local draft. Badge counts
// and sort/filter popover seed data read from this. The real baseline
// only enters via `onDraftSortsChange` / `onDraftFiltersChange`
// callbacks defined by the parent.
activeView: IBaseView | undefined; activeView: IBaseView | undefined;
views: IBaseView[]; views: IBaseView[];
table: Table<IBaseRow>; table: Table<IBaseRow>;
onViewChange: (viewId: string) => void; onViewChange: (viewId: string) => void;
onAddView?: () => void; onAddView?: () => void;
onPersistViewConfig: () => void; onPersistViewConfig: () => void;
onDraftSortsChange: (sorts: ViewSortConfig[] | undefined) => void;
onDraftFiltersChange: (filter: FilterGroup | undefined) => void;
}; };
export function BaseToolbar({ export function BaseToolbar({
@@ -44,6 +48,8 @@ export function BaseToolbar({
onViewChange, onViewChange,
onAddView, onAddView,
onPersistViewConfig, onPersistViewConfig,
onDraftSortsChange,
onDraftFiltersChange,
}: BaseToolbarProps) { }: BaseToolbarProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [sortOpened, setSortOpened] = useState(false); const [sortOpened, setSortOpened] = useState(false);
@@ -113,8 +119,6 @@ export function BaseToolbar({
setFieldsOpened(panel === "fields" ? (v) => !v : false); setFieldsOpened(panel === "fields" ? (v) => !v : false);
}, []); }, []);
const updateViewMutation = useUpdateViewMutation();
const sorts = activeView?.config?.sorts ?? []; const sorts = activeView?.config?.sorts ?? [];
// Stored view config uses the engine's filter tree. The popover edits // Stored view config uses the engine's filter tree. The popover edits
// an AND-only flat list; we unwrap the top-level group's children when // an AND-only flat list; we unwrap the top-level group's children when
@@ -134,38 +138,24 @@ export function BaseToolbar({
const handleSortsChange = useCallback( const handleSortsChange = useCallback(
(newSorts: ViewSortConfig[]) => { (newSorts: ViewSortConfig[]) => {
if (!activeView) return; // Normalize empty to undefined so the draft hook can drop the `sorts`
const config = buildViewConfigFromTable(table, activeView.config, { // axis (and remove its localStorage entry when both axes go clean).
sorts: newSorts, onDraftSortsChange(newSorts.length > 0 ? newSorts : undefined);
});
updateViewMutation.mutate({
viewId: activeView.id,
baseId: base.id,
config,
});
}, },
[activeView, base.id, table, updateViewMutation], [onDraftSortsChange],
); );
const handleFiltersChange = useCallback( const handleFiltersChange = useCallback(
(newConditions: FilterCondition[]) => { (newConditions: FilterCondition[]) => {
if (!activeView) return; // Wrap the AND-flat popover output into the engine's FilterGroup shape.
// Pass `undefined` to drop the filter axis from the draft entirely.
const filter: FilterGroup | undefined = const filter: FilterGroup | undefined =
newConditions.length > 0 newConditions.length > 0
? { op: "and", children: newConditions } ? { op: "and", children: newConditions }
: undefined; : undefined;
// `filter: undefined` in overrides removes the filter key; the helper's onDraftFiltersChange(filter);
// spread-then-overrides order means `undefined` wins over any base filter.
const config = buildViewConfigFromTable(table, activeView.config, {
filter,
});
updateViewMutation.mutate({
viewId: activeView.id,
baseId: base.id,
config,
});
}, },
[activeView, base.id, table, updateViewMutation], [onDraftFiltersChange],
); );
return ( return (