diff --git a/src/frontend/src/hooks/UseFilter.tsx b/src/frontend/src/hooks/UseFilter.tsx index 4595866c98..90bf32ea40 100644 --- a/src/frontend/src/hooks/UseFilter.tsx +++ b/src/frontend/src/hooks/UseFilter.tsx @@ -6,9 +6,7 @@ import { useQuery } from '@tanstack/react-query'; import { useCallback, useMemo } from 'react'; import { useApi } from '../contexts/ApiContext'; -import { ApiEndpoints } from '../enums/ApiEndpoints'; import { resolveItem } from '../functions/conversion'; -import { apiUrl } from '../states/ApiState'; import type { TableFilterChoice } from '../tables/Filter'; type UseFilterProps = { @@ -67,42 +65,3 @@ export function useFilters(props: UseFilterProps) { refresh }; } - -// Provide list of project code filters -export function useProjectCodeFilters() { - return useFilters({ - url: apiUrl(ApiEndpoints.project_code_list), - transform: (item) => ({ - value: item.pk, - label: item.code - }) - }); -} - -// Provide list of user filters -export function useUserFilters() { - return useFilters({ - url: apiUrl(ApiEndpoints.user_list), - params: { - is_active: true - }, - transform: (item) => ({ - value: item.pk, - label: item.username - }) - }); -} - -// Provide list of owner filters -export function useOwnerFilters() { - return useFilters({ - url: apiUrl(ApiEndpoints.owner_list), - params: { - is_active: true - }, - transform: (item) => ({ - value: item.pk, - label: item.name - }) - }); -} diff --git a/src/frontend/src/tables/Filter.tsx b/src/frontend/src/tables/Filter.tsx index 00cdb4ca55..3437ec473d 100644 --- a/src/frontend/src/tables/Filter.tsx +++ b/src/frontend/src/tables/Filter.tsx @@ -4,7 +4,9 @@ import type { StatusCodeInterface, StatusCodeListInterface } from '../components/render/StatusRenderer'; -import type { ModelType } from '../enums/ModelType'; +import { ApiEndpoints } from '../enums/ApiEndpoints'; +import { ModelType } from '../enums/ModelType'; +import { apiUrl } from '../states/ApiState'; import { useGlobalSettingsState } from '../states/SettingsState'; import { type StatusLookup, useGlobalStatusState } from '../states/StatusState'; @@ -23,8 +25,9 @@ export type TableFilterChoice = { * choice: A filter which allows selection from a list of (supplied) * date: A filter which allows selection from a date input * text: A filter which allows raw text input + * api: A filter which fetches its options from an API endpoint */ -export type TableFilterType = 'boolean' | 'choice' | 'date' | 'text'; +export type TableFilterType = 'boolean' | 'choice' | 'date' | 'text' | 'api'; /** * Interface for the table filter type. Provides a number of options for selecting filter value: @@ -39,6 +42,9 @@ export type TableFilterType = 'boolean' | 'choice' | 'date' | 'text'; * value: The current value of the filter * displayValue: The current display value of the filter * active: Whether the filter is active (false = hidden, not used) + * apiUrl: The API URL to use for fetching dynamic filter options + * model: The model type to use for fetching dynamic filter options + * modelRenderer: A function to render a simple text version of the model type */ export type TableFilter = { name: string; @@ -51,6 +57,9 @@ export type TableFilter = { value?: any; displayValue?: any; active?: boolean; + apiUrl?: string; + model?: ModelType; + modelRenderer?: (instance: any) => string; }; /** @@ -247,9 +256,7 @@ export function OrderStatusFilter({ }; } -export function ProjectCodeFilter({ - choices -}: { choices: TableFilterChoice[] }): TableFilter { +export function ProjectCodeFilter(): TableFilter { const globalSettings = useGlobalSettingsState.getState(); const enabled = globalSettings.isSet('PROJECT_CODES_ENABLED', true); @@ -258,28 +265,73 @@ export function ProjectCodeFilter({ label: t`Project Code`, description: t`Filter by project code`, active: enabled, - choices: choices + type: 'api', + apiUrl: apiUrl(ApiEndpoints.project_code_list), + model: ModelType.projectcode, + modelRenderer: (instance) => instance.code }; } -export function ResponsibleFilter({ - choices -}: { choices: TableFilterChoice[] }): TableFilter { +export function OwnerFilter({ + name, + label, + description +}: { + name: string; + label: string; + description: string; +}): TableFilter { return { + name: name, + label: label, + description: description, + type: 'api', + apiUrl: apiUrl(ApiEndpoints.owner_list), + model: ModelType.owner, + modelRenderer: (instance: any) => instance.name + }; +} + +export function ResponsibleFilter(): TableFilter { + return OwnerFilter({ name: 'assigned_to', label: t`Responsible`, - description: t`Filter by responsible owner`, - choices: choices + description: t`Filter by responsible owner` + }); +} + +export function UserFilter({ + name, + label, + description +}: { + name?: string; + label?: string; + description?: string; +}): TableFilter { + return { + name: name ?? 'user', + label: label ?? t`User`, + description: description ?? t`Filter by user`, + type: 'api', + apiUrl: apiUrl(ApiEndpoints.user_list), + model: ModelType.user, + modelRenderer: (instance: any) => instance.username }; } -export function CreatedByFilter({ - choices -}: { choices: TableFilterChoice[] }): TableFilter { - return { +export function CreatedByFilter(): TableFilter { + return UserFilter({ name: 'created_by', label: t`Created By`, - description: t`Filter by user who created the order`, - choices: choices - }; + description: t`Filter by user who created the order` + }); +} + +export function IssuedByFilter(): TableFilter { + return UserFilter({ + name: 'issued_by', + label: t`Issued By`, + description: t`Filter by user who issued the order` + }); } diff --git a/src/frontend/src/tables/FilterSelectDrawer.tsx b/src/frontend/src/tables/FilterSelectDrawer.tsx index c209d30d04..8db116da91 100644 --- a/src/frontend/src/tables/FilterSelectDrawer.tsx +++ b/src/frontend/src/tables/FilterSelectDrawer.tsx @@ -19,6 +19,7 @@ import dayjs from 'dayjs'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { IconCheck } from '@tabler/icons-react'; +import { StandaloneField } from '../components/forms/StandaloneField'; import { StylishText } from '../components/items/StylishText'; import type { TableState } from '../hooks/UseTable'; import { @@ -64,13 +65,15 @@ function FilterItem({ } function FilterElement({ - filterType, + filterName, + filterProps, valueOptions, onValueChange }: { - filterType: TableFilterType; + filterName: string; + filterProps: TableFilter; valueOptions: TableFilterChoice[]; - onValueChange: (value: string | null) => void; + onValueChange: (value: string | null, displayValue?: any) => void; }) { const setDateValue = useCallback( (value: DateValue) => { @@ -86,7 +89,23 @@ function FilterElement({ const [textValue, setTextValue] = useState(''); - switch (filterType) { + switch (filterProps.type) { + case 'api': + return ( + { + onValueChange(value, filterProps.modelRenderer?.(instance)); + } + }} + /> + ); case 'text': return ( onValueChange(value)} @@ -177,23 +196,32 @@ function FilterAddGroup({ return getTableFilterOptions(filter); }, [selectedFilter]); - // Determine the "type" of filter (default = boolean) - const filterType: TableFilterType = useMemo(() => { - const filter = availableFilters?.find((flt) => flt.name === selectedFilter); - - if (filter?.type) { + // Determine the filter "type" - if it is not supplied + const getFilterType = (filter: TableFilter): TableFilterType => { + if (filter.type) { return filter.type; - } else if (filter?.choices) { - // If choices are provided, it is a choice filter + } else if (filter.apiUrl && filter.model) { + return 'api'; + } else if (filter.choices || filter.choiceFunction) { return 'choice'; } else { - // Default fallback return 'boolean'; } - }, [selectedFilter]); + }; + + // Extract filter definition + const filterProps: TableFilter | undefined = useMemo(() => { + const filter = availableFilters?.find((flt) => flt.name === selectedFilter); + + if (filter) { + filter.type = getFilterType(filter); + } + + return filter; + }, [availableFilters, selectedFilter]); const setSelectedValue = useCallback( - (value: string | null) => { + (value: string | null, displayValue?: any) => { // Find the matching filter const filter: TableFilter | undefined = availableFilters.find( (flt) => flt.name === selectedFilter @@ -211,7 +239,8 @@ function FilterAddGroup({ const newFilter: TableFilter = { ...filter, value: value, - displayValue: valueOptions.find((v) => v.value === value)?.label + displayValue: + displayValue ?? valueOptions.find((v) => v.value === value)?.label }; tableState.setActiveFilters([...filters, newFilter]); @@ -233,9 +262,10 @@ function FilterAddGroup({ onChange={(value: string | null) => setSelectedFilter(value)} maxDropdownHeight={800} /> - {selectedFilter && ( + {selectedFilter && filterProps && ( diff --git a/src/frontend/src/tables/build/BuildOrderTable.tsx b/src/frontend/src/tables/build/BuildOrderTable.tsx index b94ae28c71..8ebe095753 100644 --- a/src/frontend/src/tables/build/BuildOrderTable.tsx +++ b/src/frontend/src/tables/build/BuildOrderTable.tsx @@ -8,13 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { useBuildOrderFields } from '../../forms/BuildForms'; -import { shortenString } from '../../functions/tables'; -import { - useFilters, - useOwnerFilters, - useProjectCodeFilters, - useUserFilters -} from '../../hooks/UseFilter'; import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; @@ -37,6 +30,7 @@ import { CreatedAfterFilter, CreatedBeforeFilter, HasProjectCodeFilter, + IssuedByFilter, MaxDateFilter, MinDateFilter, OrderStatusFilter, @@ -128,21 +122,6 @@ export function BuildOrderTable({ ]; }, [parentBuildId]); - const projectCodeFilters = useProjectCodeFilters(); - const ownerFilters = useOwnerFilters(); - const userFilters = useUserFilters(); - - const categoryFilters = useFilters({ - url: apiUrl(ApiEndpoints.category_list), - transform: (item) => ({ - value: item.pk, - label: shortenString({ - str: item.pathstring, - len: 50 - }) - }) - }); - const tableFilters: TableFilter[] = useMemo(() => { const filters: TableFilter[] = [ OutstandingFilter(), @@ -171,20 +150,17 @@ export function BuildOrderTable({ }, CompletedBeforeFilter(), CompletedAfterFilter(), - ProjectCodeFilter({ choices: projectCodeFilters.choices }), + ProjectCodeFilter(), HasProjectCodeFilter(), - { - name: 'issued_by', - label: t`Issued By`, - description: t`Filter by user who issued this order`, - choices: userFilters.choices - }, - ResponsibleFilter({ choices: ownerFilters.choices }), + IssuedByFilter(), + ResponsibleFilter(), { name: 'category', label: t`Category`, description: t`Filter by part category`, - choices: categoryFilters.choices + apiUrl: apiUrl(ApiEndpoints.category_list), + model: ModelType.partcategory, + modelRenderer: (instance: any) => instance.name } ]; @@ -199,13 +175,7 @@ export function BuildOrderTable({ } return filters; - }, [ - partId, - categoryFilters.choices, - projectCodeFilters.choices, - ownerFilters.choices, - userFilters.choices - ]); + }, [partId]); const user = useUserState(); diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx index 19c1432f7d..c1a27e6a13 100644 --- a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx +++ b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx @@ -8,11 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms'; -import { - useOwnerFilters, - useProjectCodeFilters, - useUserFilters -} from '../../hooks/UseFilter'; import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; @@ -66,10 +61,6 @@ export function PurchaseOrderTable({ const table = useTable('purchase-order'); const user = useUserState(); - const projectCodeFilters = useProjectCodeFilters(); - const responsibleFilters = useOwnerFilters(); - const createdByFilters = useUserFilters(); - const tableFilters: TableFilter[] = useMemo(() => { return [ OrderStatusFilter({ model: ModelType.purchaseorder }), @@ -98,16 +89,12 @@ export function PurchaseOrderTable({ }, CompletedBeforeFilter(), CompletedAfterFilter(), - ProjectCodeFilter({ choices: projectCodeFilters.choices }), + ProjectCodeFilter(), HasProjectCodeFilter(), - ResponsibleFilter({ choices: responsibleFilters.choices }), - CreatedByFilter({ choices: createdByFilters.choices }) + ResponsibleFilter(), + CreatedByFilter() ]; - }, [ - projectCodeFilters.choices, - responsibleFilters.choices, - createdByFilters.choices - ]); + }, []); const tableColumns = useMemo(() => { return [ diff --git a/src/frontend/src/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/tables/sales/ReturnOrderTable.tsx index 76c0038d15..6101f28505 100644 --- a/src/frontend/src/tables/sales/ReturnOrderTable.tsx +++ b/src/frontend/src/tables/sales/ReturnOrderTable.tsx @@ -8,11 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { useReturnOrderFields } from '../../forms/ReturnOrderForms'; -import { - useOwnerFilters, - useProjectCodeFilters, - useUserFilters -} from '../../hooks/UseFilter'; import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; @@ -63,10 +58,6 @@ export function ReturnOrderTable({ const table = useTable(!!partId ? 'returnorders-part' : 'returnorders-index'); const user = useUserState(); - const projectCodeFilters = useProjectCodeFilters(); - const responsibleFilters = useOwnerFilters(); - const createdByFilters = useUserFilters(); - const tableFilters: TableFilter[] = useMemo(() => { const filters: TableFilter[] = [ OrderStatusFilter({ model: ModelType.returnorder }), @@ -96,9 +87,9 @@ export function ReturnOrderTable({ CompletedBeforeFilter(), CompletedAfterFilter(), HasProjectCodeFilter(), - ProjectCodeFilter({ choices: projectCodeFilters.choices }), - ResponsibleFilter({ choices: responsibleFilters.choices }), - CreatedByFilter({ choices: createdByFilters.choices }) + ProjectCodeFilter(), + ResponsibleFilter(), + CreatedByFilter() ]; if (!!partId) { @@ -111,12 +102,7 @@ export function ReturnOrderTable({ } return filters; - }, [ - partId, - projectCodeFilters.choices, - responsibleFilters.choices, - createdByFilters.choices - ]); + }, [partId]); const tableColumns = useMemo(() => { return [ diff --git a/src/frontend/src/tables/sales/SalesOrderTable.tsx b/src/frontend/src/tables/sales/SalesOrderTable.tsx index f07f0cad1f..acae06c1da 100644 --- a/src/frontend/src/tables/sales/SalesOrderTable.tsx +++ b/src/frontend/src/tables/sales/SalesOrderTable.tsx @@ -9,11 +9,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { useSalesOrderFields } from '../../forms/SalesOrderForms'; -import { - useOwnerFilters, - useProjectCodeFilters, - useUserFilters -} from '../../hooks/UseFilter'; import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; @@ -64,10 +59,6 @@ export function SalesOrderTable({ const table = useTable(!!partId ? 'salesorder-part' : 'salesorder-index'); const user = useUserState(); - const projectCodeFilters = useProjectCodeFilters(); - const responsibleFilters = useOwnerFilters(); - const createdByFilters = useUserFilters(); - const tableFilters: TableFilter[] = useMemo(() => { const filters: TableFilter[] = [ OrderStatusFilter({ model: ModelType.salesorder }), @@ -97,9 +88,9 @@ export function SalesOrderTable({ CompletedBeforeFilter(), CompletedAfterFilter(), HasProjectCodeFilter(), - ProjectCodeFilter({ choices: projectCodeFilters.choices }), - ResponsibleFilter({ choices: responsibleFilters.choices }), - CreatedByFilter({ choices: createdByFilters.choices }) + ProjectCodeFilter(), + ResponsibleFilter(), + CreatedByFilter() ]; if (!!partId) { @@ -112,12 +103,7 @@ export function SalesOrderTable({ } return filters; - }, [ - partId, - projectCodeFilters.choices, - responsibleFilters.choices, - createdByFilters.choices - ]); + }, [partId]); const salesOrderFields = useSalesOrderFields({}); diff --git a/src/frontend/src/tables/settings/BarcodeScanHistoryTable.tsx b/src/frontend/src/tables/settings/BarcodeScanHistoryTable.tsx index 98d5471bec..4cde02cdb0 100644 --- a/src/frontend/src/tables/settings/BarcodeScanHistoryTable.tsx +++ b/src/frontend/src/tables/settings/BarcodeScanHistoryTable.tsx @@ -21,14 +21,13 @@ import { RenderUser } from '../../components/render/User'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { UserRoles } from '../../enums/Roles'; import { shortenString } from '../../functions/tables'; -import { useUserFilters } from '../../hooks/UseFilter'; import { useDeleteApiFormModal } from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; import { useGlobalSettingsState } from '../../states/SettingsState'; import { useUserState } from '../../states/UserState'; import type { TableColumn } from '../Column'; -import type { TableFilter } from '../Filter'; +import { type TableFilter, UserFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; import { RowDeleteAction } from '../RowActions'; @@ -148,8 +147,6 @@ export default function BarcodeScanHistoryTable() { const globalSettings = useGlobalSettingsState(); - const userFilters = useUserFilters(); - const [opened, { open, close }] = useDisclosure(false); const tableColumns: TableColumn[] = useMemo(() => { @@ -204,19 +201,14 @@ export default function BarcodeScanHistoryTable() { const filters: TableFilter[] = useMemo(() => { return [ - { - name: 'user', - label: t`User`, - choices: userFilters.choices, - description: t`Filter by user` - }, + UserFilter({}), { name: 'result', label: t`Result`, description: t`Filter by result` } ]; - }, [userFilters]); + }, []); const canDelete: boolean = useMemo(() => { return user.isStaff() && user.hasDeleteRole(UserRoles.admin); diff --git a/src/frontend/src/tables/settings/ImportSessionTable.tsx b/src/frontend/src/tables/settings/ImportSessionTable.tsx index 8a99ca1d12..12fe574683 100644 --- a/src/frontend/src/tables/settings/ImportSessionTable.tsx +++ b/src/frontend/src/tables/settings/ImportSessionTable.tsx @@ -9,7 +9,7 @@ import { RenderUser } from '../../components/render/User'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { dataImporterSessionFields } from '../../forms/ImporterForms'; -import { useFilters, useUserFilters } from '../../hooks/UseFilter'; +import { useFilters } from '../../hooks/UseFilter'; import { useCreateApiFormModal, useDeleteApiFormModal @@ -18,7 +18,7 @@ import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; import type { TableColumn } from '../Column'; import { DateColumn, StatusColumn } from '../ColumnRenderers'; -import { StatusFilterOptions, type TableFilter } from '../Filter'; +import { StatusFilterOptions, type TableFilter, UserFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; import { type RowAction, RowDeleteAction } from '../RowActions'; @@ -88,8 +88,6 @@ export default function ImportSesssionTable() { ]; }, []); - const userFilter = useUserFilters(); - const modelTypeFilters = useFilters({ url: apiUrl(ApiEndpoints.import_session_list), method: 'OPTIONS', @@ -116,14 +114,9 @@ export default function ImportSesssionTable() { description: t`Filter by import session status`, choiceFunction: StatusFilterOptions(ModelType.importsession) }, - { - name: 'user', - label: t`User`, - description: t`Filter by user`, - choices: userFilter.choices - } + UserFilter({}) ]; - }, [modelTypeFilters.choices, userFilter.choices]); + }, [modelTypeFilters.choices]); const tableActions = useMemo(() => { return [ diff --git a/src/frontend/src/tables/stock/StockLocationTable.tsx b/src/frontend/src/tables/stock/StockLocationTable.tsx index 9f569b3a73..3a9f045bc9 100644 --- a/src/frontend/src/tables/stock/StockLocationTable.tsx +++ b/src/frontend/src/tables/stock/StockLocationTable.tsx @@ -8,7 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { stockLocationFields } from '../../forms/StockForms'; -import { useFilters } from '../../hooks/UseFilter'; import { useCreateApiFormModal, useEditApiFormModal @@ -29,14 +28,6 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) { const table = useTable('stocklocation'); const user = useUserState(); - const locationTypeFilters = useFilters({ - url: apiUrl(ApiEndpoints.stock_location_type_list), - transform: (item) => ({ - value: item.pk, - label: item.name - }) - }); - const tableFilters: TableFilter[] = useMemo(() => { return [ { @@ -62,10 +53,12 @@ export function StockLocationTable({ parentId }: Readonly<{ parentId?: any }>) { name: 'location_type', label: t`Location Type`, description: t`Filter by location type`, - choices: locationTypeFilters.choices + apiUrl: apiUrl(ApiEndpoints.stock_location_type_list), + model: ModelType.stocklocationtype, + modelRenderer: (instance: any) => instance.name } ]; - }, [locationTypeFilters.choices]); + }, []); const tableColumns: TableColumn[] = useMemo(() => { return [ diff --git a/src/frontend/src/tables/stock/StockTrackingTable.tsx b/src/frontend/src/tables/stock/StockTrackingTable.tsx index 3e615860d2..7cb2513a30 100644 --- a/src/frontend/src/tables/stock/StockTrackingTable.tsx +++ b/src/frontend/src/tables/stock/StockTrackingTable.tsx @@ -19,12 +19,11 @@ import { import { RenderUser } from '../../components/render/User'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; -import { useUserFilters } from '../../hooks/UseFilter'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; import type { TableColumn } from '../Column'; import { DateColumn, DescriptionColumn } from '../ColumnRenderers'; -import type { TableFilter } from '../Filter'; +import { type TableFilter, UserFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; type StockTrackingEntry = { @@ -37,8 +36,6 @@ export function StockTrackingTable({ itemId }: Readonly<{ itemId: number }>) { const navigate = useNavigate(); const table = useTable('stock_tracking'); - const userFilters = useUserFilters(); - // Render "details" for a stock tracking record const renderDetails = useCallback( (record: any) => { @@ -186,14 +183,13 @@ export function StockTrackingTable({ itemId }: Readonly<{ itemId: number }>) { const filters: TableFilter[] = useMemo(() => { return [ - { + UserFilter({ name: 'user', label: t`User`, - choices: userFilters.choices, description: t`Filter by user` - } + }) ]; - }, [userFilters]); + }, []); const tableColumns: TableColumn[] = useMemo(() => { return [