From 413bc050cbe0b92cb6d179ccb6b4a7baab47ab2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Fri, 23 Jan 2026 13:28:23 +0100 Subject: [PATCH 01/14] Store field search terms in session storage --- .../app/components/blocks/fields/table.tsx | 16 ++++++++++------ .../app/components/blocks/rotation/table.tsx | 19 ++++++++----------- fdm-app/app/store/field-filter.ts | 12 ++++++++++-- fdm-app/app/store/storage.ts | 7 ++++--- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx index ebbd6db2e..7753b1c99 100644 --- a/fdm-app/app/components/blocks/fields/table.tsx +++ b/fdm-app/app/components/blocks/fields/table.tsx @@ -16,6 +16,7 @@ import fuzzysort from "fuzzysort" import { ChevronDown, Plus } from "lucide-react" import { useEffect, useMemo, useRef, useState } from "react" import { NavLink, useParams } from "react-router-dom" +import { useFieldFilterStore } from "@/app/store/field-filter" import { Button } from "~/components/ui/button" import { DropdownMenu, @@ -56,7 +57,6 @@ export function DataTable({ }: DataTableProps) { const [sorting, setSorting] = useState([]) const [columnFilters, setColumnFilters] = useState([]) - const [globalFilter, setGlobalFilter] = useState("") const isMobile = useIsMobile() const [columnVisibility, setColumnVisibility] = useState( isMobile @@ -65,6 +65,7 @@ export function DataTable({ ) const [rowSelection, setRowSelection] = useState({}) const lastSelectedRowIndex = useRef(null) + const globalFilter = useFieldFilterStore() useEffect(() => { setColumnVisibility( @@ -123,8 +124,9 @@ export function DataTable({ })) }, [data]) - const fuzzyFilter: FilterFn = (row, _columnId, filterValue) => { - const result = fuzzysort.go(filterValue, [ + const fuzzyFilter: FilterFn = (row, _columnId, { searchTerms }) => { + if (searchTerms === "") return true + const result = fuzzysort.go(searchTerms, [ (row.original as any).searchTarget, ]) return result.length > 0 @@ -139,7 +141,7 @@ export function DataTable({ onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, - onGlobalFilterChange: setGlobalFilter, + onGlobalFilterChange: (state) => useFieldFilterStore.setState(state), onRowSelectionChange: setRowSelection, globalFilterFn: fuzzyFilter, state: { @@ -170,8 +172,10 @@ export function DataTable({
setGlobalFilter(event.target.value)} + value={globalFilter.searchTerms ?? ""} + onChange={(event) => + globalFilter.setSearchTerms(event.target.value) + } className="w-full sm:w-auto sm:flex-grow" />
diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index 790b5cf1a..d05060ebf 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -64,7 +64,7 @@ export function DataTable({ }: DataTableProps) { const [sorting, setSorting] = useState([]) const [columnFilters, setColumnFilters] = useState([]) - const [searchTerms, setSearchTerms] = useState("") + const fieldFilter = useFieldFilterStore() const isMobile = useIsMobile() const [columnVisibility, setColumnVisibility] = useState( isMobile @@ -230,11 +230,6 @@ export function DataTable({ ) } - const showProductiveOnly = useFieldFilterStore((s) => s.showProductiveOnly) - const globalFilter = useMemo( - () => ({ searchTerms, showProductiveOnly }), - [searchTerms, showProductiveOnly], - ) const table = useReactTable({ data: memoizedData, columns, @@ -249,8 +244,8 @@ export function DataTable({ row.type === "crop" ? (row.fields as TData[]) : undefined, onColumnVisibilityChange: setColumnVisibility, onGlobalFilterChange: (globalFilter) => { - if (globalFilter?.searchTerms ?? "" !== searchTerms) - setSearchTerms(globalFilter?.searchTerms ?? "") + if (globalFilter?.searchTerms ?? "" !== fieldFilter.searchTerms) + fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") }, onRowSelectionChange: setRowSelection, globalFilterFn: fuzzySearchAndProductivityFilter, @@ -263,7 +258,7 @@ export function DataTable({ sorting, columnFilters, columnVisibility, - globalFilter, + globalFilter: fieldFilter, rowSelection, }, }) @@ -331,8 +326,10 @@ export function DataTable({
setSearchTerms(event.target.value)} + value={fieldFilter?.searchTerms ?? ""} + onChange={(event) => + fieldFilter.setSearchTerms(event.target.value) + } className="w-full sm:w-auto sm:grow" />
diff --git a/fdm-app/app/store/field-filter.ts b/fdm-app/app/store/field-filter.ts index 6aabf378a..2b929f087 100644 --- a/fdm-app/app/store/field-filter.ts +++ b/fdm-app/app/store/field-filter.ts @@ -1,24 +1,32 @@ import { create } from "zustand" import { createJSONStorage, persist } from "zustand/middleware" -import { ssrSafeJSONStorage } from "./storage" +import { ssrSafeSessionJSONStorage } from "./storage" interface FieldFilterState { showProductiveOnly: boolean + searchTerms: string toggleShowProductiveOnly: () => void + setSearchTerms: (value: string) => void } export const useFieldFilterStore = create()( persist( (set) => ({ showProductiveOnly: false, // Default to showing all fields + searchTerms: "", toggleShowProductiveOnly: () => set((state) => ({ showProductiveOnly: !state.showProductiveOnly, })), + setSearchTerms: (value) => { + set({ + searchTerms: value, + }) + }, }), { name: "field-filter-storage", // unique name - storage: createJSONStorage(() => ssrSafeJSONStorage), // Use SSR-safe storage + storage: createJSONStorage(() => ssrSafeSessionJSONStorage), // Use SSR-safe storage }, ), ) diff --git a/fdm-app/app/store/storage.ts b/fdm-app/app/store/storage.ts index 4c8339626..b07b2a948 100644 --- a/fdm-app/app/store/storage.ts +++ b/fdm-app/app/store/storage.ts @@ -1,8 +1,8 @@ import type { StateStorage } from "zustand/middleware" -const createSSRStorage = (): StateStorage => { +const createSSRStorage = (name: keyof Window): StateStorage => { if (typeof window !== "undefined") { - return localStorage + return window[name] } // Return a no-op storage for SSR @@ -13,4 +13,5 @@ const createSSRStorage = (): StateStorage => { } } -export const ssrSafeJSONStorage = createSSRStorage() +export const ssrSafeJSONStorage = createSSRStorage("localStorage") +export const ssrSafeSessionJSONStorage = createSSRStorage("sessionStorage") From 661f3e2d4a6bf242cc3574538de816912bca9b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Fri, 23 Jan 2026 13:37:01 +0100 Subject: [PATCH 02/14] Add changeset --- .changeset/tired-areas-take.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tired-areas-take.md diff --git a/.changeset/tired-areas-take.md b/.changeset/tired-areas-take.md new file mode 100644 index 000000000..c2d87664d --- /dev/null +++ b/.changeset/tired-areas-take.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Improve the user experience when they come back to the field and rotation tables, by storing their filters in the session storage. From 48d3c78b635e1ec493b804ea3de8d1ea5ae0d8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Fri, 23 Jan 2026 13:56:21 +0100 Subject: [PATCH 03/14] Nitpicks --- fdm-app/app/components/blocks/fields/table.tsx | 13 ++++++++----- fdm-app/app/components/blocks/rotation/table.tsx | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx index 7753b1c99..baef10e5b 100644 --- a/fdm-app/app/components/blocks/fields/table.tsx +++ b/fdm-app/app/components/blocks/fields/table.tsx @@ -65,7 +65,7 @@ export function DataTable({ ) const [rowSelection, setRowSelection] = useState({}) const lastSelectedRowIndex = useRef(null) - const globalFilter = useFieldFilterStore() + const fieldFilter = useFieldFilterStore() useEffect(() => { setColumnVisibility( @@ -141,14 +141,17 @@ export function DataTable({ onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, - onGlobalFilterChange: (state) => useFieldFilterStore.setState(state), + onGlobalFilterChange: (globalFilter) => { + if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) + fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") + }, onRowSelectionChange: setRowSelection, globalFilterFn: fuzzyFilter, state: { sorting, columnFilters, columnVisibility, - globalFilter, + globalFilter: fieldFilter, rowSelection, }, }) @@ -172,9 +175,9 @@ export function DataTable({
- globalFilter.setSearchTerms(event.target.value) + fieldFilter.setSearchTerms(event.target.value) } className="w-full sm:w-auto sm:flex-grow" /> diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index d05060ebf..d8a5bb2bb 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -244,7 +244,7 @@ export function DataTable({ row.type === "crop" ? (row.fields as TData[]) : undefined, onColumnVisibilityChange: setColumnVisibility, onGlobalFilterChange: (globalFilter) => { - if (globalFilter?.searchTerms ?? "" !== fieldFilter.searchTerms) + if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") }, onRowSelectionChange: setRowSelection, From e9b88d58d7a558fb1b9963751bc3e315f5c4e79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 26 Jan 2026 12:19:56 +0100 Subject: [PATCH 04/14] Store rotation table selection in session storage --- .../app/components/blocks/rotation/table.tsx | 29 +++++++- fdm-app/app/store/rotation-selection.tsx | 74 +++++++++++++++++++ 2 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 fdm-app/app/store/rotation-selection.tsx diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index d8a5bb2bb..809e30778 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -9,7 +9,6 @@ import { getFilteredRowModel, getSortedRowModel, type Row, - type RowSelectionState, type SortingState, useReactTable, type VisibilityState, @@ -24,6 +23,7 @@ import { toast as notify } from "sonner" import { modifySearchParams } from "@/app/lib/url-utils" import { useActiveTableFormStore } from "@/app/store/active-table-form" import { useFieldFilterStore } from "@/app/store/field-filter" +import { useRotationSelectionStore } from "@/app/store/rotation-selection" import { Button } from "~/components/ui/button" import { DropdownMenu, @@ -71,10 +71,21 @@ export function DataTable({ ? { a_som_loi: false, b_soiltype_agr: false, b_area: false } : {}, ) - const [rowSelection, setRowSelection] = useState({}) const lastSelectedRowIndex = useRef(null) const location = useLocation() + const rotationSelectionStore = useRotationSelectionStore() + function handleRowSelection(selection: Record) { + rotationSelectionStore.setSelectionFrom( + table.getRowModel().rows, + selection, + ) + } + + const [rowSelection, setRowSelection] = useState>( + {}, + ) + useEffect(() => { setColumnVisibility( isMobile @@ -151,7 +162,7 @@ export function DataTable({ (subRow) => newRowSelection[subRow.id], ) } - setRowSelection(newRowSelection) + handleRowSelection(newRowSelection) } } else { lastSelectedRowIndex.current = null @@ -247,7 +258,7 @@ export function DataTable({ if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") }, - onRowSelectionChange: setRowSelection, + onRowSelectionChange: (fn) => handleRowSelection(fn(rowSelection)), globalFilterFn: fuzzySearchAndProductivityFilter, // There are nulls in the columns which can cause false assumptions if this is not provided // The global filter checks the searchTarget field anyways @@ -263,6 +274,16 @@ export function DataTable({ }, }) + // biome-ignore lint/correctness/useExhaustiveDependencies: custom behaviour + useEffect(() => { + rotationSelectionStore.clear() + }, [fieldFilter]) + useEffect(() => { + setRowSelection( + rotationSelectionStore.getSelectionFor(table.getRowModel().rows), + ) + }, [table.getRowModel, rotationSelectionStore]) + // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection is needed for Oogst button activation const selectedCultivations = useMemo(() => { return ( diff --git a/fdm-app/app/store/rotation-selection.tsx b/fdm-app/app/store/rotation-selection.tsx new file mode 100644 index 000000000..18497d316 --- /dev/null +++ b/fdm-app/app/store/rotation-selection.tsx @@ -0,0 +1,74 @@ +import type { Row } from "@tanstack/react-table" +import { create } from "zustand" +import { createJSONStorage, persist } from "zustand/middleware" +import type { + CropRow, + FieldRow, + RotationExtended, +} from "~/components/blocks/rotation/columns" +import { ssrSafeSessionJSONStorage } from "./storage" + +interface FieldFilterState { + selection: Record> + clear(): void + getSelectionFor(rows: Row[]): Record + setSelectionFrom( + rows: Row[], + selection: Record, + ): void +} + +export const useRotationSelectionStore = create()( + persist( + (set, get) => ({ + selection: {}, + clear() { + set({ selection: {} }) + }, + setSelectionFrom(rows, selection) { + console.log(rows, selection) + const finalSelection: Record< + string, + Record + > = {} + + for (const row of rows) { + const subSelection: Record = {} + for (const fieldRow of row.subRows) { + subSelection[(fieldRow.original as FieldRow).b_id] = + selection[fieldRow.id] + } + finalSelection[(row.original as CropRow).b_lu_catalogue] = + subSelection + } + + console.log(finalSelection) + set({ selection: finalSelection }) + }, + + getSelectionFor(rows) { + const rowSelection: Record = {} + const selection = get().selection + for (const row of rows) { + const b_lu_catalogue = (row.original as CropRow) + .b_lu_catalogue + + const subSelection = selection[b_lu_catalogue] ?? {} + let allSelected = row.subRows.length > 0 + for (const fieldRow of row.subRows) { + const b_id = (fieldRow.original as FieldRow).b_id + const val = !!subSelection[b_id] + rowSelection[fieldRow.id] = val + allSelected &&= val + } + if (allSelected) rowSelection[row.id] = allSelected + } + return rowSelection + }, + }), + { + name: "field-filter-storage", // unique name + storage: createJSONStorage(() => ssrSafeSessionJSONStorage), // Use SSR-safe storage + }, + ), +) From 5d03a84b96e6d66ade69145c9ce2d0fa98ba0488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 26 Jan 2026 13:12:24 +0100 Subject: [PATCH 05/14] Refactor rotation selection store --- .../app/components/blocks/rotation/table.tsx | 13 ++++++--- ...on-selection.tsx => rotation-selection.ts} | 28 +++++++------------ 2 files changed, 19 insertions(+), 22 deletions(-) rename fdm-app/app/store/{rotation-selection.tsx => rotation-selection.ts} (74%) diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index 809e30778..ac5a8342a 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -77,7 +77,7 @@ export function DataTable({ const rotationSelectionStore = useRotationSelectionStore() function handleRowSelection(selection: Record) { rotationSelectionStore.setSelectionFrom( - table.getRowModel().rows, + table.getCoreRowModel().rows, selection, ) } @@ -258,7 +258,10 @@ export function DataTable({ if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") }, - onRowSelectionChange: (fn) => handleRowSelection(fn(rowSelection)), + onRowSelectionChange: (fn) => + handleRowSelection( + typeof fn === "function" ? fn(rowSelection) : fn, + ), globalFilterFn: fuzzySearchAndProductivityFilter, // There are nulls in the columns which can cause false assumptions if this is not provided // The global filter checks the searchTarget field anyways @@ -280,9 +283,11 @@ export function DataTable({ }, [fieldFilter]) useEffect(() => { setRowSelection( - rotationSelectionStore.getSelectionFor(table.getRowModel().rows), + rotationSelectionStore.getSelectionFor( + table.getCoreRowModel().rows, + ), ) - }, [table.getRowModel, rotationSelectionStore]) + }, [table.getCoreRowModel, rotationSelectionStore]) // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection is needed for Oogst button activation const selectedCultivations = useMemo(() => { diff --git a/fdm-app/app/store/rotation-selection.tsx b/fdm-app/app/store/rotation-selection.ts similarity index 74% rename from fdm-app/app/store/rotation-selection.tsx rename to fdm-app/app/store/rotation-selection.ts index 18497d316..bfbf2bbc1 100644 --- a/fdm-app/app/store/rotation-selection.tsx +++ b/fdm-app/app/store/rotation-selection.ts @@ -14,7 +14,7 @@ interface FieldFilterState { getSelectionFor(rows: Row[]): Record setSelectionFrom( rows: Row[], - selection: Record, + rowSelection: Record, ): void } @@ -25,34 +25,26 @@ export const useRotationSelectionStore = create()( clear() { set({ selection: {} }) }, - setSelectionFrom(rows, selection) { - console.log(rows, selection) - const finalSelection: Record< - string, - Record - > = {} - + setSelectionFrom(rows, rowSelection) { + const selection: Record> = {} for (const row of rows) { + const b_lu_catalogue = (row.original as CropRow) + .b_lu_catalogue const subSelection: Record = {} for (const fieldRow of row.subRows) { - subSelection[(fieldRow.original as FieldRow).b_id] = - selection[fieldRow.id] + const b_id = (fieldRow.original as FieldRow).b_id + subSelection[b_id] = rowSelection[fieldRow.id] } - finalSelection[(row.original as CropRow).b_lu_catalogue] = - subSelection + selection[b_lu_catalogue] = subSelection } - - console.log(finalSelection) - set({ selection: finalSelection }) + set({ selection }) }, - getSelectionFor(rows) { const rowSelection: Record = {} const selection = get().selection for (const row of rows) { const b_lu_catalogue = (row.original as CropRow) .b_lu_catalogue - const subSelection = selection[b_lu_catalogue] ?? {} let allSelected = row.subRows.length > 0 for (const fieldRow of row.subRows) { @@ -61,7 +53,7 @@ export const useRotationSelectionStore = create()( rowSelection[fieldRow.id] = val allSelected &&= val } - if (allSelected) rowSelection[row.id] = allSelected + rowSelection[row.id] = allSelected } return rowSelection }, From 01409f8a05e86f6d76c5f3c636ecd993c96e7232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 26 Jan 2026 13:54:50 +0100 Subject: [PATCH 06/14] Fix some issues --- .../app/components/blocks/rotation/table.tsx | 17 ----------------- fdm-app/app/store/rotation-selection.ts | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index ac5a8342a..6a0793167 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -168,19 +168,6 @@ export function DataTable({ lastSelectedRowIndex.current = null const newIsSelected = !row.getIsSelected() row.toggleSelected(newIsSelected) - const parentRow = row.getParentRow() - if (parentRow) { - const wantedValue = parentRow?.subRows.every((otherRow) => - otherRow.id === row.id - ? newIsSelected - : otherRow.getIsSelected(), - ) - if (parentRow.getIsSelected() !== wantedValue) { - parentRow.toggleSelected(wantedValue, { - selectChildren: false, - }) - } - } } lastSelectedRowIndex.current = row.id } @@ -277,10 +264,6 @@ export function DataTable({ }, }) - // biome-ignore lint/correctness/useExhaustiveDependencies: custom behaviour - useEffect(() => { - rotationSelectionStore.clear() - }, [fieldFilter]) useEffect(() => { setRowSelection( rotationSelectionStore.getSelectionFor( diff --git a/fdm-app/app/store/rotation-selection.ts b/fdm-app/app/store/rotation-selection.ts index bfbf2bbc1..b39c8ba58 100644 --- a/fdm-app/app/store/rotation-selection.ts +++ b/fdm-app/app/store/rotation-selection.ts @@ -59,7 +59,7 @@ export const useRotationSelectionStore = create()( }, }), { - name: "field-filter-storage", // unique name + name: "rotation-selection-storage", // unique name storage: createJSONStorage(() => ssrSafeSessionJSONStorage), // Use SSR-safe storage }, ), From 6fcd78273db883e5f8b494307a843e14a6e7dde1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 26 Jan 2026 13:58:03 +0100 Subject: [PATCH 07/14] Fix some more --- fdm-app/app/components/blocks/rotation/columns.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/fdm-app/app/components/blocks/rotation/columns.tsx b/fdm-app/app/components/blocks/rotation/columns.tsx index b24928d94..72dd8b22d 100644 --- a/fdm-app/app/components/blocks/rotation/columns.tsx +++ b/fdm-app/app/components/blocks/rotation/columns.tsx @@ -131,20 +131,6 @@ export const columns: ColumnDef[] = [ } onCheckedChange={(value) => { row.toggleSelected(!!value) - const parentRow = row.getParentRow() - if (parentRow) { - const wantedValue = parentRow.subRows.every( - (childRow) => - childRow.id === row.id - ? value - : childRow.getIsSelected(), - ) - if (parentRow.getIsSelected() !== wantedValue) { - parentRow.toggleSelected(wantedValue, { - selectChildren: false, - }) - } - } }} aria-label="Selecteer deze rij" className="text-muted-foreground" From 107001bd62958023a69b6e44c4bc8c6ea067ff2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 26 Jan 2026 14:15:35 +0100 Subject: [PATCH 08/14] Store field row selection in session storage --- .../app/components/blocks/fields/table.tsx | 30 +++++++++++++++++-- fdm-app/app/store/field-selection.ts | 23 ++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 fdm-app/app/store/field-selection.ts diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx index baef10e5b..82eb87b4f 100644 --- a/fdm-app/app/components/blocks/fields/table.tsx +++ b/fdm-app/app/components/blocks/fields/table.tsx @@ -9,6 +9,7 @@ import { type Row, type RowSelectionState, type SortingState, + type Updater, useReactTable, type VisibilityState, } from "@tanstack/react-table" @@ -17,6 +18,7 @@ import { ChevronDown, Plus } from "lucide-react" import { useEffect, useMemo, useRef, useState } from "react" import { NavLink, useParams } from "react-router-dom" import { useFieldFilterStore } from "@/app/store/field-filter" +import { useFieldSelectionStore } from "@/app/store/field-selection" import { Button } from "~/components/ui/button" import { DropdownMenu, @@ -63,6 +65,7 @@ export function DataTable({ ? { a_som_loi: false, b_soiltype_agr: false, b_area: false } : {}, ) + const fieldSelection = useFieldSelectionStore() const [rowSelection, setRowSelection] = useState({}) const lastSelectedRowIndex = useRef(null) const fieldFilter = useFieldFilterStore() @@ -110,7 +113,7 @@ export function DataTable({ rowsToSelect.forEach((id) => { newRowSelection[id] = true }) - setRowSelection(newRowSelection) + handleRowSelection(newRowSelection) } else { row.toggleSelected() } @@ -145,7 +148,7 @@ export function DataTable({ if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") }, - onRowSelectionChange: setRowSelection, + onRowSelectionChange: handleRowSelection, globalFilterFn: fuzzyFilter, state: { sorting, @@ -156,6 +159,29 @@ export function DataTable({ }, }) + function handleRowSelection(fn: Updater>) { + const selection = typeof fn === "function" ? fn(rowSelection) : fn + fieldSelection.setFieldIds( + table + .getCoreRowModel() + .rows.filter((row) => selection[row.id]) + .map((row) => row.original.b_id), + ) + } + + useEffect(() => { + setRowSelection( + Object.fromEntries( + table + .getCoreRowModel() + .rows.map((row) => [ + row.id, + fieldSelection.fieldIds.includes(row.original.b_id), + ]), + ), + ) + }, [table.getCoreRowModel, fieldSelection]) + // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection is needed for Bemesting button activation const selectedFields = useMemo(() => { return table diff --git a/fdm-app/app/store/field-selection.ts b/fdm-app/app/store/field-selection.ts new file mode 100644 index 000000000..5ac3a82c9 --- /dev/null +++ b/fdm-app/app/store/field-selection.ts @@ -0,0 +1,23 @@ +import { create } from "zustand" +import { createJSONStorage, persist } from "zustand/middleware" +import { ssrSafeSessionJSONStorage } from "./storage" + +interface FieldFilterState { + fieldIds: string[] + setFieldIds: (fieldIds: string[]) => void +} + +export const useFieldSelectionStore = create()( + persist( + (set) => ({ + fieldIds: [], + setFieldIds(fieldIds: string[]) { + set({ fieldIds }) + }, + }), + { + name: "field-selection-storage", // unique name + storage: createJSONStorage(() => ssrSafeSessionJSONStorage), // Use SSR-safe storage + }, + ), +) From 66c3a0beef56d47bcacf68cbe5b67d61fc91f63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 26 Jan 2026 16:30:09 +0100 Subject: [PATCH 09/14] Nitpicks --- fdm-app/app/components/blocks/fields/table.tsx | 3 ++- fdm-app/app/store/field-selection.ts | 4 ++-- fdm-app/app/store/rotation-selection.ts | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx index 82eb87b4f..daf572f64 100644 --- a/fdm-app/app/components/blocks/fields/table.tsx +++ b/fdm-app/app/components/blocks/fields/table.tsx @@ -144,7 +144,8 @@ export function DataTable({ onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, - onGlobalFilterChange: (globalFilter) => { + onGlobalFilterChange: (fn) => { + const globalFilter = typeof fn === "function" ? fn(fieldFilter) : fn if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") }, diff --git a/fdm-app/app/store/field-selection.ts b/fdm-app/app/store/field-selection.ts index 5ac3a82c9..ba9fd3189 100644 --- a/fdm-app/app/store/field-selection.ts +++ b/fdm-app/app/store/field-selection.ts @@ -2,12 +2,12 @@ import { create } from "zustand" import { createJSONStorage, persist } from "zustand/middleware" import { ssrSafeSessionJSONStorage } from "./storage" -interface FieldFilterState { +interface FieldSelectionState { fieldIds: string[] setFieldIds: (fieldIds: string[]) => void } -export const useFieldSelectionStore = create()( +export const useFieldSelectionStore = create()( persist( (set) => ({ fieldIds: [], diff --git a/fdm-app/app/store/rotation-selection.ts b/fdm-app/app/store/rotation-selection.ts index b39c8ba58..c32d62af8 100644 --- a/fdm-app/app/store/rotation-selection.ts +++ b/fdm-app/app/store/rotation-selection.ts @@ -8,7 +8,7 @@ import type { } from "~/components/blocks/rotation/columns" import { ssrSafeSessionJSONStorage } from "./storage" -interface FieldFilterState { +interface RotationSelectionState { selection: Record> clear(): void getSelectionFor(rows: Row[]): Record @@ -18,7 +18,7 @@ interface FieldFilterState { ): void } -export const useRotationSelectionStore = create()( +export const useRotationSelectionStore = create()( persist( (set, get) => ({ selection: {}, From d223cf9c89597710d4b1650676614ebbcd012567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 26 Jan 2026 16:38:38 +0100 Subject: [PATCH 10/14] Refresh row.id-based selection state when the table state changes --- fdm-app/app/components/blocks/fields/table.tsx | 3 ++- fdm-app/app/components/blocks/rotation/table.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx index daf572f64..fc1a92923 100644 --- a/fdm-app/app/components/blocks/fields/table.tsx +++ b/fdm-app/app/components/blocks/fields/table.tsx @@ -170,6 +170,7 @@ export function DataTable({ ) } + // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection uses generic table row ids and not field-specific ids, so it needs to be refreshed when the memoized data changes. useEffect(() => { setRowSelection( Object.fromEntries( @@ -181,7 +182,7 @@ export function DataTable({ ]), ), ) - }, [table.getCoreRowModel, fieldSelection]) + }, [memoizedData, table.getCoreRowModel, fieldSelection]) // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection is needed for Bemesting button activation const selectedFields = useMemo(() => { diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index 6a0793167..93ce18a4e 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -264,13 +264,14 @@ export function DataTable({ }, }) + // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection uses generic table row ids and not field-specific ids, so it needs to be refreshed when the memoized data changes. useEffect(() => { setRowSelection( rotationSelectionStore.getSelectionFor( table.getCoreRowModel().rows, ), ) - }, [table.getCoreRowModel, rotationSelectionStore]) + }, [memoizedData, table.getCoreRowModel, rotationSelectionStore]) // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection is needed for Oogst button activation const selectedCultivations = useMemo(() => { From 1e95a843eb24b9d0ec80aead575300020293989c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 27 Jan 2026 09:28:02 +0100 Subject: [PATCH 11/14] Handle updater in global filter --- fdm-app/app/components/blocks/rotation/table.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index 93ce18a4e..bdb8eb5fb 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -241,7 +241,8 @@ export function DataTable({ getSubRows: (row) => row.type === "crop" ? (row.fields as TData[]) : undefined, onColumnVisibilityChange: setColumnVisibility, - onGlobalFilterChange: (globalFilter) => { + onGlobalFilterChange: (fn) => { + const globalFilter = typeof fn === "function" ? fn(fieldFilter) : fn if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") }, From 163feb8da4995ff8ec55ac9896dff1a27234d6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 27 Jan 2026 09:52:48 +0100 Subject: [PATCH 12/14] Update rowSelection immediately when triggered by user --- fdm-app/app/components/blocks/fields/table.tsx | 3 ++- fdm-app/app/components/blocks/rotation/table.tsx | 15 +++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx index fc1a92923..dd4e3f250 100644 --- a/fdm-app/app/components/blocks/fields/table.tsx +++ b/fdm-app/app/components/blocks/fields/table.tsx @@ -160,8 +160,9 @@ export function DataTable({ }, }) - function handleRowSelection(fn: Updater>) { + function handleRowSelection(fn: Updater) { const selection = typeof fn === "function" ? fn(rowSelection) : fn + setRowSelection(selection) fieldSelection.setFieldIds( table .getCoreRowModel() diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index bdb8eb5fb..34006a144 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -9,7 +9,9 @@ import { getFilteredRowModel, getSortedRowModel, type Row, + type RowSelectionState, type SortingState, + type Updater, useReactTable, type VisibilityState, } from "@tanstack/react-table" @@ -75,16 +77,16 @@ export function DataTable({ const location = useLocation() const rotationSelectionStore = useRotationSelectionStore() - function handleRowSelection(selection: Record) { + function handleRowSelection(fn: Updater) { + const selection = typeof fn === "function" ? fn(rowSelection) : fn + setRowSelection(selection) rotationSelectionStore.setSelectionFrom( table.getCoreRowModel().rows, selection, ) } - const [rowSelection, setRowSelection] = useState>( - {}, - ) + const [rowSelection, setRowSelection] = useState({}) useEffect(() => { setColumnVisibility( @@ -246,10 +248,7 @@ export function DataTable({ if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") }, - onRowSelectionChange: (fn) => - handleRowSelection( - typeof fn === "function" ? fn(rowSelection) : fn, - ), + onRowSelectionChange: handleRowSelection, globalFilterFn: fuzzySearchAndProductivityFilter, // There are nulls in the columns which can cause false assumptions if this is not provided // The global filter checks the searchTarget field anyways From 3c656a3665d1a238d8efc099c7c12506061e1fda Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:04:42 +0100 Subject: [PATCH 13/14] refactor: prevent overflowing the sidebar --- fdm-app/app/components/blocks/farm/farm-content.tsx | 2 +- fdm-app/app/components/blocks/rotation/table.tsx | 6 +++--- .../farm.$b_id_farm.$calendar.rotation._index.tsx | 12 +++++------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/fdm-app/app/components/blocks/farm/farm-content.tsx b/fdm-app/app/components/blocks/farm/farm-content.tsx index 235073ea4..1cc8fa77e 100644 --- a/fdm-app/app/components/blocks/farm/farm-content.tsx +++ b/fdm-app/app/components/blocks/farm/farm-content.tsx @@ -20,7 +20,7 @@ export function FarmContent({ sidebarItems, children }: FarmContentProps) { )} -
{children || }
+
{children || }
) diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index 34006a144..29c0ad3ef 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -332,8 +332,8 @@ export function DataTable({ }) } return ( -
-
+
+
({
- + {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx index 45d216ce8..cbd2811f2 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.rotation._index.tsx @@ -535,7 +535,7 @@ export default function FarmRotationIndex() { Bouwplan -
+
{loaderData.fieldOptions.length === 0 ? ( <> ) : ( <> -
- -
+
Date: Tue, 27 Jan 2026 14:25:17 +0100 Subject: [PATCH 14/14] Refactor: FieldsTable and RotationTable to synchronize selection state using a single useFieldSelectionStore, implemented syncFarm to clear selection on farm switch, and removed legacy rotation-selection store. --- .../app/components/blocks/fields/table.tsx | 76 ++++++------ .../app/components/blocks/rotation/table.tsx | 115 +++++++++++------- fdm-app/app/store/field-filter.ts | 10 +- fdm-app/app/store/field-selection.ts | 10 +- fdm-app/app/store/rotation-selection.ts | 66 ---------- 5 files changed, 121 insertions(+), 156 deletions(-) delete mode 100644 fdm-app/app/store/rotation-selection.ts diff --git a/fdm-app/app/components/blocks/fields/table.tsx b/fdm-app/app/components/blocks/fields/table.tsx index dd4e3f250..e8ea4114b 100644 --- a/fdm-app/app/components/blocks/fields/table.tsx +++ b/fdm-app/app/components/blocks/fields/table.tsx @@ -65,11 +65,28 @@ export function DataTable({ ? { a_som_loi: false, b_soiltype_agr: false, b_area: false } : {}, ) - const fieldSelection = useFieldSelectionStore() - const [rowSelection, setRowSelection] = useState({}) + const fieldIds = useFieldSelectionStore((state) => state.fieldIds) + const setFieldIds = useFieldSelectionStore((state) => state.setFieldIds) + const syncFarm = useFieldSelectionStore((state) => state.syncFarm) const lastSelectedRowIndex = useRef(null) const fieldFilter = useFieldFilterStore() + const rowSelection = useMemo( + () => Object.fromEntries(fieldIds.map((id) => [id, true])), + [fieldIds], + ) + + const params = useParams() + const b_id_farm = params.b_id_farm + const calendar = params.calendar + + useEffect(() => { + if (b_id_farm) { + syncFarm(b_id_farm) + fieldFilter.syncFarm(b_id_farm) + } + }, [b_id_farm, syncFarm, fieldFilter.syncFarm]) + useEffect(() => { setColumnVisibility( isMobile @@ -78,10 +95,6 @@ export function DataTable({ ) }, [isMobile]) - const params = useParams() - const b_id_farm = params.b_id_farm - const calendar = params.calendar - const handleRowClick = ( row: Row, event: React.MouseEvent, @@ -107,13 +120,11 @@ export function DataTable({ const rowsToSelect = table .getRowModel() .rows.slice(start, end + 1) - .map((r) => r.id) + .map((r) => r.original.b_id) // Use b_id directly - const newRowSelection = { ...rowSelection } - rowsToSelect.forEach((id) => { - newRowSelection[id] = true - }) - handleRowSelection(newRowSelection) + const newFieldIds = new Set(fieldIds) + rowsToSelect.forEach((id) => newFieldIds.add(id)) + setFieldIds(Array.from(newFieldIds)) } else { row.toggleSelected() } @@ -138,6 +149,7 @@ export function DataTable({ const table = useReactTable({ data: memoizedData, columns, + getRowId: (row) => row.b_id, getCoreRowModel: getCoreRowModel(), onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), @@ -145,11 +157,18 @@ export function DataTable({ getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, onGlobalFilterChange: (fn) => { - const globalFilter = typeof fn === "function" ? fn(fieldFilter) : fn - if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) - fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") + const result = typeof fn === "function" ? fn(fieldFilter) : fn + // Ensure we are dealing with the store object structure before updating + const newSearchTerms = + typeof result === "string" ? result : result?.searchTerms + if ((newSearchTerms ?? "") !== fieldFilter.searchTerms) { + fieldFilter.setSearchTerms(newSearchTerms ?? "") + } + }, + onRowSelectionChange: (fn) => { + const selection = typeof fn === "function" ? fn(rowSelection) : fn + setFieldIds(Object.keys(selection).filter((k) => selection[k])) }, - onRowSelectionChange: handleRowSelection, globalFilterFn: fuzzyFilter, state: { sorting, @@ -160,31 +179,6 @@ export function DataTable({ }, }) - function handleRowSelection(fn: Updater) { - const selection = typeof fn === "function" ? fn(rowSelection) : fn - setRowSelection(selection) - fieldSelection.setFieldIds( - table - .getCoreRowModel() - .rows.filter((row) => selection[row.id]) - .map((row) => row.original.b_id), - ) - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection uses generic table row ids and not field-specific ids, so it needs to be refreshed when the memoized data changes. - useEffect(() => { - setRowSelection( - Object.fromEntries( - table - .getCoreRowModel() - .rows.map((row) => [ - row.id, - fieldSelection.fieldIds.includes(row.original.b_id), - ]), - ), - ) - }, [memoizedData, table.getCoreRowModel, fieldSelection]) - // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection is needed for Bemesting button activation const selectedFields = useMemo(() => { return table diff --git a/fdm-app/app/components/blocks/rotation/table.tsx b/fdm-app/app/components/blocks/rotation/table.tsx index 29c0ad3ef..8daff223b 100644 --- a/fdm-app/app/components/blocks/rotation/table.tsx +++ b/fdm-app/app/components/blocks/rotation/table.tsx @@ -9,9 +9,7 @@ import { getFilteredRowModel, getSortedRowModel, type Row, - type RowSelectionState, type SortingState, - type Updater, useReactTable, type VisibilityState, } from "@tanstack/react-table" @@ -25,7 +23,7 @@ import { toast as notify } from "sonner" import { modifySearchParams } from "@/app/lib/url-utils" import { useActiveTableFormStore } from "@/app/store/active-table-form" import { useFieldFilterStore } from "@/app/store/field-filter" -import { useRotationSelectionStore } from "@/app/store/rotation-selection" +import { useFieldSelectionStore } from "@/app/store/field-selection" import { Button } from "~/components/ui/button" import { DropdownMenu, @@ -76,17 +74,9 @@ export function DataTable({ const lastSelectedRowIndex = useRef(null) const location = useLocation() - const rotationSelectionStore = useRotationSelectionStore() - function handleRowSelection(fn: Updater) { - const selection = typeof fn === "function" ? fn(rowSelection) : fn - setRowSelection(selection) - rotationSelectionStore.setSelectionFrom( - table.getCoreRowModel().rows, - selection, - ) - } - - const [rowSelection, setRowSelection] = useState({}) + const fieldIds = useFieldSelectionStore((state) => state.fieldIds) + const setFieldIds = useFieldSelectionStore((state) => state.setFieldIds) + const syncFarm = useFieldSelectionStore((state) => state.syncFarm) useEffect(() => { setColumnVisibility( @@ -100,6 +90,13 @@ export function DataTable({ const b_id_farm = params.b_id_farm const calendar = params.calendar + useEffect(() => { + if (b_id_farm) { + syncFarm(b_id_farm) + fieldFilter.syncFarm(b_id_farm) + } + }, [b_id_farm, syncFarm, fieldFilter.syncFarm]) + const clearActiveForm = useActiveTableFormStore( (store) => store.clearActiveForm, ) @@ -128,7 +125,7 @@ export function DataTable({ lastSelectedRowIndex.current && table.getRow(lastSelectedRowIndex.current) if (lastSelectedRow) { - const newRowSelection = { ...rowSelection } + const newRowSelection = { ...table.getState().rowSelection } const visibleRows = table.getRowModel().flatRows // Select or deselect everything in between @@ -143,28 +140,22 @@ export function DataTable({ const start = Math.min(lastIndex, currentIndex) const end = Math.max(lastIndex, currentIndex) - const affectedCropRows = [] for (let i = start; i <= end; i++) { - const parentRow = visibleRows[i].getParentRow() - if (parentRow) { - affectedCropRows.push(parentRow) - newRowSelection[visibleRows[i].id] = mode + const r = visibleRows[i] + newRowSelection[r.id] = mode + if (r.original.type === "crop" && r.getCanExpand()) { + // Also select subrows + for (const sub of r.subRows) { + newRowSelection[sub.id] = mode + } } } - // Toggle selection of the currently clicked row - // This behavior can be removed as needed - if (row.getIsSelected() === mode) { - newRowSelection[row.id] = !mode - } - - // Update the derived selection state of crop rows - for (const row of affectedCropRows) { - newRowSelection[row.id] = row.subRows.every( - (subRow) => newRowSelection[subRow.id], - ) - } - handleRowSelection(newRowSelection) + // Sync to store + const newFieldIds = Object.keys(newRowSelection).filter( + (k) => !k.startsWith("crop_") && newRowSelection[k], + ) + setFieldIds(newFieldIds) } } else { lastSelectedRowIndex.current = null @@ -230,9 +221,40 @@ export function DataTable({ ) } + const rowSelection = useMemo(() => { + const sel: Record = {} + for (const id of fieldIds) { + sel[id] = true + } + for (const row of memoizedData) { + if (row.type === "crop") { + let all = true + let has = false + for (const field of row.fields) { + has = true + if (fieldIds.includes(field.b_id)) { + sel[field.b_id] = true + } else { + all = false + } + } + if (has && all) { + sel[`crop_${row.b_lu_catalogue}`] = true + } + } else { + if (fieldIds.includes(row.b_id)) { + sel[row.b_id] = true + } + } + } + return sel + }, [fieldIds, memoizedData]) + const table = useReactTable({ data: memoizedData, columns, + getRowId: (row) => + row.type === "crop" ? `crop_${row.b_lu_catalogue}` : row.b_id, getCoreRowModel: getCoreRowModel(), onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), @@ -244,11 +266,19 @@ export function DataTable({ row.type === "crop" ? (row.fields as TData[]) : undefined, onColumnVisibilityChange: setColumnVisibility, onGlobalFilterChange: (fn) => { - const globalFilter = typeof fn === "function" ? fn(fieldFilter) : fn - if ((globalFilter?.searchTerms ?? "") !== fieldFilter.searchTerms) - fieldFilter.setSearchTerms(globalFilter?.searchTerms ?? "") + const result = typeof fn === "function" ? fn(fieldFilter) : fn + const newSearchTerms = + typeof result === "string" ? result : result?.searchTerms + if ((newSearchTerms ?? "") !== fieldFilter.searchTerms) + fieldFilter.setSearchTerms(newSearchTerms ?? "") + }, + onRowSelectionChange: (fn) => { + const selection = typeof fn === "function" ? fn(rowSelection) : fn + const newFieldIds = Object.keys(selection).filter( + (k) => !k.startsWith("crop_") && selection[k], + ) + setFieldIds(newFieldIds) }, - onRowSelectionChange: handleRowSelection, globalFilterFn: fuzzySearchAndProductivityFilter, // There are nulls in the columns which can cause false assumptions if this is not provided // The global filter checks the searchTarget field anyways @@ -264,15 +294,6 @@ export function DataTable({ }, }) - // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection uses generic table row ids and not field-specific ids, so it needs to be refreshed when the memoized data changes. - useEffect(() => { - setRowSelection( - rotationSelectionStore.getSelectionFor( - table.getCoreRowModel().rows, - ), - ) - }, [memoizedData, table.getCoreRowModel, rotationSelectionStore]) - // biome-ignore lint/correctness/useExhaustiveDependencies: rowSelection is needed for Oogst button activation const selectedCultivations = useMemo(() => { return ( @@ -561,4 +582,4 @@ export function DataTable({
) -} +} \ No newline at end of file diff --git a/fdm-app/app/store/field-filter.ts b/fdm-app/app/store/field-filter.ts index 2b929f087..0dfb1ba9e 100644 --- a/fdm-app/app/store/field-filter.ts +++ b/fdm-app/app/store/field-filter.ts @@ -3,15 +3,18 @@ import { createJSONStorage, persist } from "zustand/middleware" import { ssrSafeSessionJSONStorage } from "./storage" interface FieldFilterState { + farmId: string | null showProductiveOnly: boolean searchTerms: string toggleShowProductiveOnly: () => void setSearchTerms: (value: string) => void + syncFarm: (farmId: string) => void } export const useFieldFilterStore = create()( persist( - (set) => ({ + (set, get) => ({ + farmId: null, showProductiveOnly: false, // Default to showing all fields searchTerms: "", toggleShowProductiveOnly: () => @@ -23,6 +26,11 @@ export const useFieldFilterStore = create()( searchTerms: value, }) }, + syncFarm(farmId: string) { + if (get().farmId !== farmId) { + set({ farmId, searchTerms: "" }) + } + }, }), { name: "field-filter-storage", // unique name diff --git a/fdm-app/app/store/field-selection.ts b/fdm-app/app/store/field-selection.ts index ba9fd3189..654b7d7f6 100644 --- a/fdm-app/app/store/field-selection.ts +++ b/fdm-app/app/store/field-selection.ts @@ -3,17 +3,25 @@ import { createJSONStorage, persist } from "zustand/middleware" import { ssrSafeSessionJSONStorage } from "./storage" interface FieldSelectionState { + farmId: string | null fieldIds: string[] setFieldIds: (fieldIds: string[]) => void + syncFarm: (farmId: string) => void } export const useFieldSelectionStore = create()( persist( - (set) => ({ + (set, get) => ({ + farmId: null, fieldIds: [], setFieldIds(fieldIds: string[]) { set({ fieldIds }) }, + syncFarm(farmId: string) { + if (get().farmId !== farmId) { + set({ farmId, fieldIds: [] }) + } + }, }), { name: "field-selection-storage", // unique name diff --git a/fdm-app/app/store/rotation-selection.ts b/fdm-app/app/store/rotation-selection.ts deleted file mode 100644 index c32d62af8..000000000 --- a/fdm-app/app/store/rotation-selection.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { Row } from "@tanstack/react-table" -import { create } from "zustand" -import { createJSONStorage, persist } from "zustand/middleware" -import type { - CropRow, - FieldRow, - RotationExtended, -} from "~/components/blocks/rotation/columns" -import { ssrSafeSessionJSONStorage } from "./storage" - -interface RotationSelectionState { - selection: Record> - clear(): void - getSelectionFor(rows: Row[]): Record - setSelectionFrom( - rows: Row[], - rowSelection: Record, - ): void -} - -export const useRotationSelectionStore = create()( - persist( - (set, get) => ({ - selection: {}, - clear() { - set({ selection: {} }) - }, - setSelectionFrom(rows, rowSelection) { - const selection: Record> = {} - for (const row of rows) { - const b_lu_catalogue = (row.original as CropRow) - .b_lu_catalogue - const subSelection: Record = {} - for (const fieldRow of row.subRows) { - const b_id = (fieldRow.original as FieldRow).b_id - subSelection[b_id] = rowSelection[fieldRow.id] - } - selection[b_lu_catalogue] = subSelection - } - set({ selection }) - }, - getSelectionFor(rows) { - const rowSelection: Record = {} - const selection = get().selection - for (const row of rows) { - const b_lu_catalogue = (row.original as CropRow) - .b_lu_catalogue - const subSelection = selection[b_lu_catalogue] ?? {} - let allSelected = row.subRows.length > 0 - for (const fieldRow of row.subRows) { - const b_id = (fieldRow.original as FieldRow).b_id - const val = !!subSelection[b_id] - rowSelection[fieldRow.id] = val - allSelected &&= val - } - rowSelection[row.id] = allSelected - } - return rowSelection - }, - }), - { - name: "rotation-selection-storage", // unique name - storage: createJSONStorage(() => ssrSafeSessionJSONStorage), // Use SSR-safe storage - }, - ), -)