mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-02 03:30:54 +00:00
[UI] Stored table state (#9902)
* Add useStoredTableState zustand * Store table page size in the zustand state * Store tablesorting data in zustand state * Actually provide records... * Transfer table names state * Store hidden columns too
This commit is contained in:
@ -52,19 +52,16 @@ export type TableState = {
|
|||||||
hasSelectedRecords: boolean;
|
hasSelectedRecords: boolean;
|
||||||
setSelectedRecords: (records: any[]) => void;
|
setSelectedRecords: (records: any[]) => void;
|
||||||
clearSelectedRecords: () => void;
|
clearSelectedRecords: () => void;
|
||||||
hiddenColumns: string[] | null;
|
|
||||||
setHiddenColumns: (columns: string[]) => void;
|
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
setSearchTerm: (term: string) => void;
|
setSearchTerm: (term: string) => void;
|
||||||
recordCount: number;
|
recordCount: number;
|
||||||
setRecordCount: (count: number) => void;
|
setRecordCount: (count: number) => void;
|
||||||
page: number;
|
page: number;
|
||||||
setPage: (page: number) => void;
|
setPage: (page: number) => void;
|
||||||
pageSize: number;
|
|
||||||
setPageSize: (pageSize: number) => void;
|
|
||||||
storedDataLoaded: boolean;
|
|
||||||
records: any[];
|
records: any[];
|
||||||
setRecords: (records: any[]) => void;
|
setRecords: (records: any[]) => void;
|
||||||
updateRecord: (record: any) => void;
|
updateRecord: (record: any) => void;
|
||||||
|
hiddenColumns: string[];
|
||||||
|
setHiddenColumns: (columns: string[]) => void;
|
||||||
idAccessor?: string;
|
idAccessor?: string;
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@ import { useShallow } from 'zustand/react/shallow';
|
|||||||
import { api } from '../App';
|
import { api } from '../App';
|
||||||
import { useLocalState } from '../states/LocalState';
|
import { useLocalState } from '../states/LocalState';
|
||||||
import { useServerApiState } from '../states/ServerApiState';
|
import { useServerApiState } from '../states/ServerApiState';
|
||||||
|
import { useStoredTableState } from '../states/StoredTableState';
|
||||||
import { fetchGlobalStates } from '../states/states';
|
import { fetchGlobalStates } from '../states/states';
|
||||||
|
|
||||||
export const defaultLocale = 'en';
|
export const defaultLocale = 'en';
|
||||||
@ -117,7 +118,7 @@ export function LanguageContext({
|
|||||||
fetchGlobalStates();
|
fetchGlobalStates();
|
||||||
|
|
||||||
// Clear out cached table column names
|
// Clear out cached table column names
|
||||||
useLocalState.getState().clearTableColumnNames();
|
useStoredTableState.getState().clearTableColumnNames();
|
||||||
})
|
})
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { randomId, useLocalStorage } from '@mantine/hooks';
|
import { randomId } from '@mantine/hooks';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
@ -6,12 +6,6 @@ import type { FilterSetState } from '@lib/types/Filters';
|
|||||||
import type { TableState } from '@lib/types/Tables';
|
import type { TableState } from '@lib/types/Tables';
|
||||||
import { useFilterSet } from './UseFilterSet';
|
import { useFilterSet } from './UseFilterSet';
|
||||||
|
|
||||||
// Interface for the stored table data in local storage
|
|
||||||
interface StoredTableData {
|
|
||||||
pageSize: number;
|
|
||||||
hiddenColumns: string[] | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom hook for managing the state of an <InvenTreeTable> component.
|
* A custom hook for managing the state of an <InvenTreeTable> component.
|
||||||
*
|
*
|
||||||
@ -51,6 +45,9 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
|||||||
[expandedRecords]
|
[expandedRecords]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Array of columns which are hidden
|
||||||
|
const [hiddenColumns, setHiddenColumns] = useState<string[]>([]);
|
||||||
|
|
||||||
// Array of selected records
|
// Array of selected records
|
||||||
const [selectedRecords, setSelectedRecords] = useState<any[]>([]);
|
const [selectedRecords, setSelectedRecords] = useState<any[]>([]);
|
||||||
|
|
||||||
@ -73,70 +70,6 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
|||||||
|
|
||||||
const [page, setPage] = useState<number>(1);
|
const [page, setPage] = useState<number>(1);
|
||||||
|
|
||||||
const [storedDataLoaded, setStoredDataLoaded] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [tableData, setTableData] = useState<StoredTableData>({
|
|
||||||
pageSize: 25,
|
|
||||||
hiddenColumns: null
|
|
||||||
});
|
|
||||||
|
|
||||||
const [storedTableData, setStoredTableData] =
|
|
||||||
useLocalStorage<StoredTableData>({
|
|
||||||
key: `inventree-table-data-${tableName}`,
|
|
||||||
getInitialValueInEffect: true,
|
|
||||||
// sync: false, // Do not use this option - see below
|
|
||||||
defaultValue: {
|
|
||||||
pageSize: 25,
|
|
||||||
hiddenColumns: null
|
|
||||||
},
|
|
||||||
deserialize: (value: any) => {
|
|
||||||
const tableData =
|
|
||||||
value === undefined
|
|
||||||
? {
|
|
||||||
pageSize: 25,
|
|
||||||
hiddenColumns: null
|
|
||||||
}
|
|
||||||
: JSON.parse(value);
|
|
||||||
|
|
||||||
if (!storedDataLoaded) {
|
|
||||||
setStoredDataLoaded((wasLoaded: boolean) => {
|
|
||||||
if (!wasLoaded) {
|
|
||||||
// First load of stored table data - copy to local state
|
|
||||||
// We only do this on first load, to avoid live syncing between tabs
|
|
||||||
// Note: The 'sync: false' option is not used, it does not perform as expected
|
|
||||||
setTableData(tableData);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return tableData;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const setPageSize = useCallback((size: number) => {
|
|
||||||
setStoredTableData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
pageSize: size
|
|
||||||
}));
|
|
||||||
setTableData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
pageSize: size
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setHiddenColumns = useCallback((columns: string[] | null) => {
|
|
||||||
setStoredTableData((prev) => {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
hiddenColumns: columns
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setTableData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
hiddenColumns: columns
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Search term
|
// Search term
|
||||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||||
|
|
||||||
@ -186,17 +119,14 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
|||||||
setSelectedRecords,
|
setSelectedRecords,
|
||||||
clearSelectedRecords,
|
clearSelectedRecords,
|
||||||
hasSelectedRecords,
|
hasSelectedRecords,
|
||||||
pageSize: tableData.pageSize,
|
|
||||||
hiddenColumns: tableData.hiddenColumns,
|
|
||||||
setHiddenColumns,
|
|
||||||
searchTerm,
|
searchTerm,
|
||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
recordCount,
|
recordCount,
|
||||||
setRecordCount,
|
setRecordCount,
|
||||||
|
hiddenColumns,
|
||||||
|
setHiddenColumns,
|
||||||
page,
|
page,
|
||||||
setPage,
|
setPage,
|
||||||
setPageSize,
|
|
||||||
storedDataLoaded,
|
|
||||||
records,
|
records,
|
||||||
setRecords,
|
setRecords,
|
||||||
updateRecord,
|
updateRecord,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import type { DataTableSortStatus } from 'mantine-datatable';
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
@ -35,17 +34,6 @@ interface LocalStateProps {
|
|||||||
// panels
|
// panels
|
||||||
lastUsedPanels: Record<string, string>;
|
lastUsedPanels: Record<string, string>;
|
||||||
setLastUsedPanel: (panelKey: string) => (value: string) => void;
|
setLastUsedPanel: (panelKey: string) => (value: string) => void;
|
||||||
tableColumnNames: Record<string, Record<string, string>>;
|
|
||||||
getTableColumnNames: (tableKey: string) => Record<string, string>;
|
|
||||||
setTableColumnNames: (
|
|
||||||
tableKey: string
|
|
||||||
) => (names: Record<string, string>) => void;
|
|
||||||
tableSorting: Record<string, any>;
|
|
||||||
getTableSorting: (tableKey: string) => DataTableSortStatus;
|
|
||||||
setTableSorting: (
|
|
||||||
tableKey: string
|
|
||||||
) => (sorting: DataTableSortStatus<any>) => void;
|
|
||||||
clearTableColumnNames: () => void;
|
|
||||||
detailDrawerStack: number;
|
detailDrawerStack: number;
|
||||||
addDetailDrawer: (value: number | false) => void;
|
addDetailDrawer: (value: number | false) => void;
|
||||||
navigationOpen: boolean;
|
navigationOpen: boolean;
|
||||||
@ -140,36 +128,7 @@ export const useLocalState = create<LocalStateProps>()(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// tables
|
|
||||||
tableColumnNames: {},
|
|
||||||
getTableColumnNames: (tableKey) => {
|
|
||||||
return get().tableColumnNames[tableKey] || null;
|
|
||||||
},
|
|
||||||
setTableColumnNames: (tableKey) => (names) => {
|
|
||||||
// Update the table column names for the given table
|
|
||||||
set({
|
|
||||||
tableColumnNames: {
|
|
||||||
...get().tableColumnNames,
|
|
||||||
[tableKey]: names
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
clearTableColumnNames: () => {
|
|
||||||
set({ tableColumnNames: {} });
|
|
||||||
},
|
|
||||||
tableSorting: {},
|
|
||||||
getTableSorting: (tableKey) => {
|
|
||||||
return get().tableSorting[tableKey] || {};
|
|
||||||
},
|
|
||||||
setTableSorting: (tableKey) => (sorting) => {
|
|
||||||
// Update the table sorting for the given table
|
|
||||||
set({
|
|
||||||
tableSorting: {
|
|
||||||
...get().tableSorting,
|
|
||||||
[tableKey]: sorting
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// detail drawers
|
// detail drawers
|
||||||
detailDrawerStack: 0,
|
detailDrawerStack: 0,
|
||||||
addDetailDrawer: (value) => {
|
addDetailDrawer: (value) => {
|
||||||
|
91
src/frontend/src/states/StoredTableState.tsx
Normal file
91
src/frontend/src/states/StoredTableState.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import type { DataTableSortStatus } from 'mantine-datatable';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE: number = 25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interfacing for storing persistent table state in the browser.
|
||||||
|
*
|
||||||
|
* The following properties are stored:
|
||||||
|
* - pageSize: The number of rows to display per page in the table.
|
||||||
|
* - hiddenColumns: An array of column names that are hidden in a given table.
|
||||||
|
* - columnNames: An object mapping table keys to arrays of column names.
|
||||||
|
* - sorting: An object mapping table keys to sorting configurations.
|
||||||
|
*/
|
||||||
|
interface StoredTableStateProps {
|
||||||
|
pageSize: number;
|
||||||
|
setPageSize: (size: number) => void;
|
||||||
|
tableSorting: Record<string, any>;
|
||||||
|
getTableSorting: (tableKey: string) => DataTableSortStatus;
|
||||||
|
setTableSorting: (
|
||||||
|
tableKey: string
|
||||||
|
) => (sorting: DataTableSortStatus<any>) => void;
|
||||||
|
tableColumnNames: Record<string, Record<string, string>>;
|
||||||
|
getTableColumnNames: (tableKey: string) => Record<string, string>;
|
||||||
|
setTableColumnNames: (
|
||||||
|
tableKey: string
|
||||||
|
) => (names: Record<string, string>) => void;
|
||||||
|
clearTableColumnNames: () => void;
|
||||||
|
hiddenColumns: Record<string, string[]>;
|
||||||
|
getHiddenColumns: (tableKey: string) => string[] | null;
|
||||||
|
setHiddenColumns: (tableKey: string) => (columns: string[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStoredTableState = create<StoredTableStateProps>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
|
setPageSize: (size: number) => {
|
||||||
|
set((state) => ({
|
||||||
|
pageSize: size
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
tableSorting: {},
|
||||||
|
getTableSorting: (tableKey) => {
|
||||||
|
return get().tableSorting[tableKey] || {};
|
||||||
|
},
|
||||||
|
setTableSorting: (tableKey) => (sorting) => {
|
||||||
|
// Update the table sorting for the given table
|
||||||
|
set({
|
||||||
|
tableSorting: {
|
||||||
|
...get().tableSorting,
|
||||||
|
[tableKey]: sorting
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tableColumnNames: {},
|
||||||
|
getTableColumnNames: (tableKey) => {
|
||||||
|
return get().tableColumnNames[tableKey] || null;
|
||||||
|
},
|
||||||
|
setTableColumnNames: (tableKey) => (names) => {
|
||||||
|
// Update the table column names for the given table
|
||||||
|
set({
|
||||||
|
tableColumnNames: {
|
||||||
|
...get().tableColumnNames,
|
||||||
|
[tableKey]: names
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearTableColumnNames: () => {
|
||||||
|
set({ tableColumnNames: {} });
|
||||||
|
},
|
||||||
|
hiddenColumns: {},
|
||||||
|
getHiddenColumns: (tableKey) => {
|
||||||
|
return get().hiddenColumns?.[tableKey] ?? null;
|
||||||
|
},
|
||||||
|
setHiddenColumns: (tableKey) => (columns) => {
|
||||||
|
// Update the hidden columns for the given table
|
||||||
|
set({
|
||||||
|
hiddenColumns: {
|
||||||
|
...get().hiddenColumns,
|
||||||
|
[tableKey]: columns
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'inventree-table-state'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
@ -1,10 +1,5 @@
|
|||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import {
|
import { Box, type MantineStyleProp, Stack } from '@mantine/core';
|
||||||
Box,
|
|
||||||
LoadingOverlay,
|
|
||||||
type MantineStyleProp,
|
|
||||||
Stack
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
type ContextMenuItemOptions,
|
type ContextMenuItemOptions,
|
||||||
@ -35,12 +30,12 @@ import { resolveItem } from '../functions/conversion';
|
|||||||
import { extractAvailableFields, mapFields } from '../functions/forms';
|
import { extractAvailableFields, mapFields } from '../functions/forms';
|
||||||
import { showApiErrorMessage } from '../functions/notifications';
|
import { showApiErrorMessage } from '../functions/notifications';
|
||||||
import { useLocalState } from '../states/LocalState';
|
import { useLocalState } from '../states/LocalState';
|
||||||
|
import { useStoredTableState } from '../states/StoredTableState';
|
||||||
import type { TableColumn } from './Column';
|
import type { TableColumn } from './Column';
|
||||||
import InvenTreeTableHeader from './InvenTreeTableHeader';
|
import InvenTreeTableHeader from './InvenTreeTableHeader';
|
||||||
import { type RowAction, RowActions } from './RowActions';
|
import { type RowAction, RowActions } from './RowActions';
|
||||||
|
|
||||||
const ACTIONS_COLUMN_ACCESSOR: string = '--actions--';
|
const ACTIONS_COLUMN_ACCESSOR: string = '--actions--';
|
||||||
const defaultPageSize: number = 25;
|
|
||||||
const PAGE_SIZES = [10, 15, 20, 25, 50, 100, 500];
|
const PAGE_SIZES = [10, 15, 20, 25, 50, 100, 500];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,13 +135,18 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
columns: TableColumn<T>[];
|
columns: TableColumn<T>[];
|
||||||
props: InvenTreeTableProps<T>;
|
props: InvenTreeTableProps<T>;
|
||||||
}>) {
|
}>) {
|
||||||
|
const { userTheme } = useLocalState();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
pageSize,
|
||||||
|
setPageSize,
|
||||||
|
getHiddenColumns,
|
||||||
|
setHiddenColumns,
|
||||||
getTableColumnNames,
|
getTableColumnNames,
|
||||||
setTableColumnNames,
|
setTableColumnNames,
|
||||||
getTableSorting,
|
getTableSorting,
|
||||||
setTableSorting,
|
setTableSorting
|
||||||
userTheme
|
} = useStoredTableState();
|
||||||
} = useLocalState();
|
|
||||||
|
|
||||||
const [fieldNames, setFieldNames] = useState<Record<string, string>>({});
|
const [fieldNames, setFieldNames] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
|
|
||||||
// Request OPTIONS data from the API, before we load the table
|
// Request OPTIONS data from the API, before we load the table
|
||||||
const tableOptionQuery = useQuery({
|
const tableOptionQuery = useQuery({
|
||||||
enabled: !!url && !tableData && tableState.storedDataLoaded,
|
enabled: !!url && !tableData,
|
||||||
queryKey: [
|
queryKey: [
|
||||||
'options',
|
'options',
|
||||||
url,
|
url,
|
||||||
@ -274,30 +274,6 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
return tableProps.enableSelection || tableProps.enableBulkDelete || false;
|
return tableProps.enableSelection || tableProps.enableBulkDelete || false;
|
||||||
}, [tableProps]);
|
}, [tableProps]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// On first table render, "hide" any default hidden columns
|
|
||||||
if (tableProps.enableColumnSwitching == false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A "null" value indicates that the initial "hidden" columns have not been set
|
|
||||||
if (tableState.storedDataLoaded && tableState.hiddenColumns == null) {
|
|
||||||
const columnNames: string[] = columns
|
|
||||||
.filter((col) => {
|
|
||||||
// Find any switchable columns which are hidden by default
|
|
||||||
return col.switchable != false && col.defaultVisible == false;
|
|
||||||
})
|
|
||||||
.map((col) => col.accessor);
|
|
||||||
|
|
||||||
tableState.setHiddenColumns(columnNames);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
columns,
|
|
||||||
tableProps.enableColumnSwitching,
|
|
||||||
tableState.hiddenColumns,
|
|
||||||
tableState.storedDataLoaded
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Check if any columns are switchable (can be hidden)
|
// Check if any columns are switchable (can be hidden)
|
||||||
const hasSwitchableColumns: boolean = useMemo(() => {
|
const hasSwitchableColumns: boolean = useMemo(() => {
|
||||||
if (props.enableColumnSwitching == false) {
|
if (props.enableColumnSwitching == false) {
|
||||||
@ -327,10 +303,6 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
const dataColumns: any = useMemo(() => {
|
const dataColumns: any = useMemo(() => {
|
||||||
let cols: TableColumn[] = columns.filter((col) => col?.hidden != true);
|
let cols: TableColumn[] = columns.filter((col) => col?.hidden != true);
|
||||||
|
|
||||||
if (!tableState.storedDataLoaded) {
|
|
||||||
cols = cols.filter((col) => col?.defaultVisible != false);
|
|
||||||
}
|
|
||||||
|
|
||||||
cols = cols.map((col) => {
|
cols = cols.map((col) => {
|
||||||
// If the column is *not* switchable, it is always visible
|
// If the column is *not* switchable, it is always visible
|
||||||
// Otherwise, check if it is "default hidden"
|
// Otherwise, check if it is "default hidden"
|
||||||
@ -373,24 +345,29 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
fieldNames,
|
fieldNames,
|
||||||
tableProps.rowActions,
|
tableProps.rowActions,
|
||||||
tableState.hiddenColumns,
|
tableState.hiddenColumns,
|
||||||
tableState.selectedRecords,
|
tableState.selectedRecords
|
||||||
tableState.storedDataLoaded
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Callback when column visibility is toggled
|
// Callback when column visibility is toggled
|
||||||
function toggleColumn(columnName: string) {
|
const toggleColumn = useCallback(
|
||||||
const newColumns = [...dataColumns];
|
(columnName: string) => {
|
||||||
|
const newColumns = [...dataColumns];
|
||||||
|
|
||||||
const colIdx = newColumns.findIndex((col) => col.accessor == columnName);
|
const colIdx = newColumns.findIndex((col) => col.accessor == columnName);
|
||||||
|
|
||||||
if (colIdx >= 0 && colIdx < newColumns.length) {
|
if (colIdx >= 0 && colIdx < newColumns.length) {
|
||||||
newColumns[colIdx].hidden = !newColumns[colIdx].hidden;
|
newColumns[colIdx].hidden = !newColumns[colIdx].hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
tableState.setHiddenColumns(
|
const hiddenColumns = newColumns
|
||||||
newColumns.filter((col) => col.hidden).map((col) => col.accessor)
|
.filter((col) => col.hidden)
|
||||||
);
|
.map((col) => col.accessor);
|
||||||
}
|
|
||||||
|
tableState.setHiddenColumns(hiddenColumns);
|
||||||
|
setHiddenColumns(cacheKey)(hiddenColumns);
|
||||||
|
},
|
||||||
|
[cacheKey, dataColumns]
|
||||||
|
);
|
||||||
|
|
||||||
// Final state of the table columns
|
// Final state of the table columns
|
||||||
const tableColumns = useDataTableColumns({
|
const tableColumns = useDataTableColumns({
|
||||||
@ -456,8 +433,6 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
|
|
||||||
// Pagination
|
// Pagination
|
||||||
if (tableProps.enablePagination && paginate) {
|
if (tableProps.enablePagination && paginate) {
|
||||||
const pageSize = tableState.pageSize ?? defaultPageSize;
|
|
||||||
if (pageSize != tableState.pageSize) tableState.setPageSize(pageSize);
|
|
||||||
queryParams.limit = pageSize;
|
queryParams.limit = pageSize;
|
||||||
queryParams.offset = (tableState.page - 1) * pageSize;
|
queryParams.offset = (tableState.page - 1) * pageSize;
|
||||||
}
|
}
|
||||||
@ -476,30 +451,44 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
return queryParams;
|
return queryParams;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
sortStatus,
|
||||||
tableProps.params,
|
tableProps.params,
|
||||||
tableProps.enablePagination,
|
tableProps.enablePagination,
|
||||||
tableState.filterSet.activeFilters,
|
tableState.filterSet.activeFilters,
|
||||||
tableState.queryFilters,
|
tableState.queryFilters,
|
||||||
tableState.searchTerm,
|
tableState.searchTerm,
|
||||||
tableState.pageSize,
|
|
||||||
tableState.setPageSize,
|
|
||||||
sortStatus,
|
|
||||||
getOrderingTerm
|
getOrderingTerm
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [sortingLoaded, setSortingLoaded] = useState<boolean>(false);
|
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tableKey: string = tableState.tableKey.split('-')[0];
|
const sorting: DataTableSortStatus = getTableSorting(cacheKey);
|
||||||
const sorting: DataTableSortStatus = getTableSorting(tableKey);
|
|
||||||
|
|
||||||
if (sorting && !!sorting.columnAccessor && !!sorting.direction) {
|
if (sorting && !!sorting.columnAccessor && !!sorting.direction) {
|
||||||
setSortStatus(sorting);
|
setSortStatus(sorting);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSortingLoaded(true);
|
const hiddenColumns = getHiddenColumns(cacheKey);
|
||||||
}, []);
|
|
||||||
|
if (hiddenColumns == null) {
|
||||||
|
// A "null" value indicates that the initial "hidden" columns have not been set
|
||||||
|
const columnNames: string[] = columns
|
||||||
|
.filter((col) => {
|
||||||
|
// Find any switchable columns which are hidden by default
|
||||||
|
return col.switchable != false && col.defaultVisible == false;
|
||||||
|
})
|
||||||
|
.map((col) => col.accessor);
|
||||||
|
|
||||||
|
setHiddenColumns(cacheKey)(columnNames);
|
||||||
|
tableState.setHiddenColumns(columnNames);
|
||||||
|
} else {
|
||||||
|
tableState.setHiddenColumns(hiddenColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCacheLoaded(true);
|
||||||
|
}, [cacheKey]);
|
||||||
|
|
||||||
// Return the ordering parameter
|
// Return the ordering parameter
|
||||||
function getOrderingTerm() {
|
function getOrderingTerm() {
|
||||||
@ -521,13 +510,15 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
tableProps.noRecordsText ?? t`No records found`
|
tableProps.noRecordsText ?? t`No records found`
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSortStatusChange = (status: DataTableSortStatus<T>) => {
|
const handleSortStatusChange = useCallback(
|
||||||
tableState.setPage(1);
|
(status: DataTableSortStatus<T>) => {
|
||||||
setSortStatus(status);
|
tableState.setPage(1);
|
||||||
|
setSortStatus(status);
|
||||||
|
|
||||||
const tableKey = tableState.tableKey.split('-')[0];
|
setTableSorting(cacheKey)(status);
|
||||||
setTableSorting(tableKey)(status);
|
},
|
||||||
};
|
[cacheKey]
|
||||||
|
);
|
||||||
|
|
||||||
// Function to perform API query to fetch required data
|
// Function to perform API query to fetch required data
|
||||||
const fetchTableData = async () => {
|
const fetchTableData = async () => {
|
||||||
@ -538,16 +529,11 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sortingLoaded) {
|
if (!cacheLoaded) {
|
||||||
// Sorting not yet loaded - do not load!
|
// Sorting not yet loaded - do not load!
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tableState.storedDataLoaded) {
|
|
||||||
// Table data not yet loaded - do not load!
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.get(url, {
|
.get(url, {
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
@ -581,15 +567,13 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
queryKey: [
|
queryKey: [
|
||||||
'tabledata',
|
'tabledata',
|
||||||
url,
|
url,
|
||||||
tableState.tableKey,
|
|
||||||
tableState.page,
|
tableState.page,
|
||||||
props.params,
|
props.params,
|
||||||
sortingLoaded,
|
cacheLoaded,
|
||||||
sortStatus.columnAccessor,
|
sortStatus.columnAccessor,
|
||||||
sortStatus.direction,
|
sortStatus.direction,
|
||||||
tableState.tableKey,
|
tableState.tableKey,
|
||||||
tableState.filterSet.activeFilters,
|
tableState.filterSet.activeFilters,
|
||||||
tableState.storedDataLoaded,
|
|
||||||
tableState.searchTerm
|
tableState.searchTerm
|
||||||
],
|
],
|
||||||
retry: 5,
|
retry: 5,
|
||||||
@ -602,7 +586,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
enabled: !!url && !tableData && tableState.storedDataLoaded,
|
enabled: !!url && !tableData,
|
||||||
queryFn: fetchTableData
|
queryFn: fetchTableData
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -629,16 +613,17 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
tableState.isLoading
|
tableState.isLoading
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const tablePageSize = useMemo(() => {
|
||||||
|
if (tableProps.enablePagination != false) {
|
||||||
|
return pageSize;
|
||||||
|
} else {
|
||||||
|
return tableState.recordCount;
|
||||||
|
}
|
||||||
|
}, [tableProps.enablePagination, pageSize, tableState.recordCount]);
|
||||||
|
|
||||||
// Update tableState.records when new data received
|
// Update tableState.records when new data received
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data = tableData ?? apiData ?? [];
|
tableState.setRecords(tableData ?? apiData ?? []);
|
||||||
|
|
||||||
tableState.setRecords(data);
|
|
||||||
|
|
||||||
// set pagesize to length if pagination is disabled
|
|
||||||
if (!tableProps.enablePagination) {
|
|
||||||
tableState.setPageSize(data?.length ?? defaultPageSize);
|
|
||||||
}
|
|
||||||
}, [tableData, apiData]);
|
}, [tableData, apiData]);
|
||||||
|
|
||||||
// Callback when a cell is clicked
|
// Callback when a cell is clicked
|
||||||
@ -735,12 +720,12 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
return showContextMenu(items)(event);
|
return showContextMenu(items)(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// pagination refresh table if pageSize changes
|
// Pagination refresh table if pageSize changes
|
||||||
function updatePageSize(newData: number) {
|
const updatePageSize = useCallback((size: number) => {
|
||||||
tableState.setPageSize(newData);
|
setPageSize(size);
|
||||||
tableState.setPage(1);
|
tableState.setPage(1);
|
||||||
tableState.refreshTable();
|
tableState.refreshTable();
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memoize row expansion options:
|
* Memoize row expansion options:
|
||||||
@ -776,7 +761,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
_params = {
|
_params = {
|
||||||
..._params,
|
..._params,
|
||||||
totalRecords: tableState.recordCount,
|
totalRecords: tableState.recordCount,
|
||||||
recordsPerPage: tableState.pageSize,
|
recordsPerPage: tablePageSize,
|
||||||
page: tableState.page,
|
page: tableState.page,
|
||||||
onPageChange: tableState.setPage,
|
onPageChange: tableState.setPage,
|
||||||
recordsPerPageOptions: PAGE_SIZES,
|
recordsPerPageOptions: PAGE_SIZES,
|
||||||
@ -786,9 +771,9 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
|
|
||||||
return _params;
|
return _params;
|
||||||
}, [
|
}, [
|
||||||
|
tablePageSize,
|
||||||
tableProps.enablePagination,
|
tableProps.enablePagination,
|
||||||
tableState.recordCount,
|
tableState.recordCount,
|
||||||
tableState.pageSize,
|
|
||||||
tableState.page,
|
tableState.page,
|
||||||
tableState.setPage,
|
tableState.setPage,
|
||||||
updatePageSize
|
updatePageSize
|
||||||
@ -806,7 +791,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
<>
|
<>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
{!tableProps.noHeader && (
|
{!tableProps.noHeader && (
|
||||||
<Boundary label={`InvenTreeTableHeader-${tableState.tableKey}`}>
|
<Boundary label={`InvenTreeTableHeader-${cacheKey}`}>
|
||||||
<InvenTreeTableHeader
|
<InvenTreeTableHeader
|
||||||
tableUrl={url}
|
tableUrl={url}
|
||||||
tableState={tableState}
|
tableState={tableState}
|
||||||
@ -818,9 +803,8 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
/>
|
/>
|
||||||
</Boundary>
|
</Boundary>
|
||||||
)}
|
)}
|
||||||
<Boundary label={`InvenTreeTable-${tableState.tableKey}`}>
|
<Boundary label={`InvenTreeTable-${cacheKey}`}>
|
||||||
<Box pos='relative'>
|
<Box pos='relative'>
|
||||||
<LoadingOverlay visible={!tableState.storedDataLoaded} />
|
|
||||||
<DataTable
|
<DataTable
|
||||||
withTableBorder={!tableProps.noHeader}
|
withTableBorder={!tableProps.noHeader}
|
||||||
withColumnBorders
|
withColumnBorders
|
||||||
|
Reference in New Issue
Block a user