(
+ ({ label, description, ...others }, ref) => (
+
+ {label}
+ {description}
+
+ )
+);
+
+function FilterAddGroup({
+ tableState,
+ availableFilters
+}: {
+ tableState: TableState;
+ availableFilters: TableFilter[];
+}) {
+ const filterOptions = useMemo(() => {
+ let activeFilterNames = tableState.activeFilters.map((flt) => flt.name);
+
+ return availableFilters
+ .filter((flt) => !activeFilterNames.includes(flt.name))
+ .map((flt) => ({
+ value: flt.name,
+ label: flt.label,
+ description: flt.description
+ }));
+ }, [tableState.activeFilters, availableFilters]);
+
+ const [selectedFilter, setSelectedFilter] = useState(null);
+
+ const valueOptions: TableFilterChoice[] = useMemo(() => {
+ // Find the matching filter
+ let filter: TableFilter | undefined = availableFilters.find(
+ (flt) => flt.name === selectedFilter
+ );
+
+ if (!filter) {
+ return [];
+ }
+
+ return getTableFilterOptions(filter);
+ }, [selectedFilter]);
+
+ const setSelectedValue = useCallback(
+ (value: string | null) => {
+ // Find the matching filter
+ let filter: TableFilter | undefined = availableFilters.find(
+ (flt) => flt.name === selectedFilter
+ );
+
+ if (!filter) {
+ return;
+ }
+
+ let filters = tableState.activeFilters.filter(
+ (flt) => flt.name !== selectedFilter
+ );
+
+ let newFilter: TableFilter = {
+ ...filter,
+ value: value,
+ displayValue: valueOptions.find((v) => v.value === value)?.label
+ };
+
+ tableState.setActiveFilters([...filters, newFilter]);
+ },
+ [selectedFilter]
+ );
+
+ return (
+
+
+
+ );
+}
+
+export function FilterSelectDrawer({
+ availableFilters,
+ tableState,
+ opened,
+ onClose
+}: {
+ availableFilters: TableFilter[];
+ tableState: TableState;
+ opened: boolean;
+ onClose: () => void;
+}) {
+ const [addFilter, setAddFilter] = useState(false);
+
+ // Hide the "add filter" selection whenever the selected filters change
+ useEffect(() => {
+ setAddFilter(false);
+ }, [tableState.activeFilters]);
+
+ return (
+ {t`Table Filters`}}
+ >
+
+ {tableState.activeFilters.map((f) => (
+
+ ))}
+ {tableState.activeFilters.length > 0 && }
+ {addFilter && (
+
+
+
+ )}
+ {addFilter && (
+
+ )}
+ {!addFilter &&
+ tableState.activeFilters.length < availableFilters.length && (
+
+ )}
+ {!addFilter && tableState.activeFilters.length > 0 && (
+
+ )}
+
+
+ );
+}
diff --git a/src/frontend/src/components/tables/FilterSelectModal.tsx b/src/frontend/src/components/tables/FilterSelectModal.tsx
deleted file mode 100644
index b09d942515..0000000000
--- a/src/frontend/src/components/tables/FilterSelectModal.tsx
+++ /dev/null
@@ -1,178 +0,0 @@
-import { t } from '@lingui/macro';
-import { Modal } from '@mantine/core';
-import { Select } from '@mantine/core';
-import { Stack } from '@mantine/core';
-import { Button, Group, Text } from '@mantine/core';
-import { forwardRef, useMemo, useState } from 'react';
-
-import { TableFilter, TableFilterChoice } from './Filter';
-
-/**
- * Construct the selection of filters
- */
-function constructAvailableFilters(
- activeFilters: TableFilter[],
- availableFilters: TableFilter[]
-) {
- // Collect a list of active filters
- let activeFilterNames = activeFilters.map((flt) => flt.name);
-
- let options = availableFilters
- .filter((flt) => !activeFilterNames.includes(flt.name))
- .map((flt) => ({
- value: flt.name,
- label: flt.label,
- description: flt.description
- }));
-
- return options;
-}
-
-/**
- * Construct the selection of available values for the selected filter
- */
-function constructValueOptions(
- availableFilters: TableFilter[],
- selectedFilter: string | null
-) {
- // No options if no filter is selected
- if (!selectedFilter) {
- return [];
- }
-
- let filter = availableFilters.find((flt) => flt.name === selectedFilter);
-
- if (!filter) {
- console.error(`Could not find filter ${selectedFilter}`);
- return [];
- }
-
- let options: TableFilterChoice[] = [];
-
- switch (filter.type) {
- case 'boolean':
- // Boolean filter values True / False
- options = [
- { value: 'true', label: t`True` },
- { value: 'false', label: t`False` }
- ];
- break;
- default:
- // Choices are supplied by the filter definition
- if (filter.choices) {
- options = filter.choices;
- } else if (filter.choiceFunction) {
- options = filter.choiceFunction();
- } else {
- console.error(`Filter choices not supplied for filter ${filter.name}`);
- }
- break;
- }
-
- return options;
-}
-
-interface FilterProps extends React.ComponentPropsWithoutRef<'div'> {
- name: string;
- label: string;
- description?: string;
-}
-
-/*
- * Custom component for the filter select
- */
-const FilterSelectItem = forwardRef(
- ({ name, label, description, ...others }, ref) => (
-
- {label}
- {description}
-
- )
-);
-
-/**
- * Modal dialog to add a} new filter for a particular table
- * @param opened : boolean - Whether the modal is opened or not
- * @param onClose : () => void - Function called when the modal is closed
- * @returns
- */
-export function FilterSelectModal({
- availableFilters,
- activeFilters,
- opened,
- onCreateFilter,
- onClose
-}: {
- availableFilters: TableFilter[];
- activeFilters: TableFilter[];
- opened: boolean;
- onCreateFilter: (name: string, value: string) => void;
- onClose: () => void;
-}) {
- let filterOptions = useMemo(
- () => constructAvailableFilters(activeFilters, availableFilters),
- [activeFilters, availableFilters]
- );
-
- // Internal state variable for the selected filter
- let [selectedFilter, setSelectedFilter] = useState(null);
-
- // Internal state variable for the selected filter value
- let [value, setValue] = useState(null);
-
- let valueOptions = useMemo(
- () => constructValueOptions(availableFilters, selectedFilter),
- [availableFilters, activeFilters, selectedFilter]
- );
-
- // Callback when the modal is closed. Ensure that the internal state is reset
- function closeModal() {
- setSelectedFilter(null);
- setValue(null);
- onClose();
- }
-
- function createFilter() {
- if (selectedFilter && value) {
- onCreateFilter(selectedFilter, value);
- }
- closeModal();
- }
-
- return (
-
-
- {t`Select from the available filters`}
- setSelectedFilter(value)}
- withinPortal={true}
- maxDropdownHeight={400}
- />
- setValue(value)}
- withinPortal={true}
- maxDropdownHeight={400}
- />
-
-
-
-
-
-
- );
-}
diff --git a/src/frontend/src/components/tables/InvenTreeTable.tsx b/src/frontend/src/components/tables/InvenTreeTable.tsx
index 5232ce5d3e..ca17406fef 100644
--- a/src/frontend/src/components/tables/InvenTreeTable.tsx
+++ b/src/frontend/src/components/tables/InvenTreeTable.tsx
@@ -15,8 +15,7 @@ import { TableColumn } from './Column';
import { TableColumnSelect } from './ColumnSelect';
import { DownloadAction } from './DownloadAction';
import { TableFilter } from './Filter';
-import { FilterGroup } from './FilterGroup';
-import { FilterSelectModal } from './FilterSelectModal';
+import { FilterSelectDrawer } from './FilterSelectDrawer';
import { RowAction, RowActions } from './RowActions';
import { TableSearchInput } from './Search';
@@ -126,13 +125,6 @@ export function InvenTreeTable({
defaultValue: []
});
- // Active filters (saved to local storage)
- const [activeFilters, setActiveFilters] = useLocalStorage({
- key: `inventree-active-table-filters-${tableName}`,
- defaultValue: [],
- getInitialValueInEffect: false
- });
-
// Data selection
const [selectedRecords, setSelectedRecords] = useState([]);
@@ -198,50 +190,12 @@ export function InvenTreeTable({
);
}
- // Filter selection open state
- const [filterSelectOpen, setFilterSelectOpen] = useState(false);
-
// Pagination
const [page, setPage] = useState(1);
// Filter list visibility
const [filtersVisible, setFiltersVisible] = useState(false);
- /*
- * Callback for the "add filter" button.
- * Launches a modal dialog to add a new filter
- */
- function onFilterAdd(name: string, value: string) {
- let filters = [...activeFilters];
-
- let newFilter = tableProps.customFilters?.find((flt) => flt.name == name);
-
- if (newFilter) {
- filters.push({
- ...newFilter,
- value: value
- });
-
- setActiveFilters(filters);
- }
- }
-
- /*
- * Callback function when a specified filter is removed from the table
- */
- function onFilterRemove(filterName: string) {
- let filters = activeFilters.filter((flt) => flt.name != filterName);
-
- setActiveFilters(filters);
- }
-
- /*
- * Callback function when all custom filters are removed from the table
- */
- function onFilterClearAll() {
- setActiveFilters([]);
- }
-
// Search term
const [searchTerm, setSearchTerm] = useState('');
@@ -259,7 +213,9 @@ export function InvenTreeTable({
};
// Add custom filters
- activeFilters.forEach((flt) => (queryParams[flt.name] = flt.value));
+ tableState.activeFilters.forEach(
+ (flt) => (queryParams[flt.name] = flt.value)
+ );
// Add custom search term
if (searchTerm) {
@@ -398,11 +354,12 @@ export function InvenTreeTable({
const { data, isError, isFetching, isLoading, refetch } = useQuery({
queryKey: [
- `table-${tableName}`,
+ tableState.tableKey,
+ props.params,
sortStatus.columnAccessor,
sortStatus.direction,
page,
- activeFilters,
+ tableState.activeFilters,
searchTerm
],
queryFn: fetchTableData,
@@ -412,23 +369,17 @@ export function InvenTreeTable({
const [recordCount, setRecordCount] = useState(0);
- /*
- * Reload the table whenever the tableKey changes
- * this allows us to programmatically refresh the table
- */
- useEffect(() => {
- refetch();
- }, [tableState?.tableKey, props.params]);
-
return (
<>
- setFilterSelectOpen(false)}
- />
+ {tableProps.enableFilters &&
+ (tableProps.customFilters?.length ?? 0) > 0 && (
+ setFiltersVisible(false)}
+ />
+ )}
@@ -478,8 +429,8 @@ export function InvenTreeTable({
(tableProps.customFilters?.length ?? 0 > 0) && (
@@ -498,14 +449,6 @@ export function InvenTreeTable({
)}
- {filtersVisible && (
- setFilterSelectOpen(true)}
- onFilterRemove={onFilterRemove}
- onFilterClearAll={onFilterClearAll}
- />
- )}
{
return [
+ {
+ name: 'sub_part_trackable',
+ label: t`Trackable Part`,
+ description: t`Show trackable items`
+ },
+ {
+ name: 'sub_part_assembly',
+ label: t`Assembled Part`,
+ description: t`Show asssmbled items`
+ },
+ {
+ name: 'available_stock',
+ label: t`Has Available Stock`,
+ description: t`Show items with available stock`
+ },
+ {
+ name: 'on_order',
+ label: t`On Order`,
+ description: t`Show items on order`
+ },
+ {
+ name: 'validated',
+ label: t`Validated`,
+ description: t`Show validated items`
+ },
+ {
+ name: 'inherited',
+ label: t`Gets Inherited`,
+ description: t`Show inherited items`
+ },
+ {
+ name: 'optional',
+ label: t`Optional`,
+ description: t`Show optional items`
+ },
{
name: 'consumable',
label: t`Consumable`,
- type: 'boolean'
+ description: t`Show consumable items`
+ },
+ {
+ name: 'has_pricing',
+ label: t`Has Pricing`,
+ description: t`Show items with pricing`
}
- // TODO: More BOM table filters here
];
}, [partId, params]);
diff --git a/src/frontend/src/components/tables/bom/UsedInTable.tsx b/src/frontend/src/components/tables/bom/UsedInTable.tsx
index 407f51df7d..439af13954 100644
--- a/src/frontend/src/components/tables/bom/UsedInTable.tsx
+++ b/src/frontend/src/components/tables/bom/UsedInTable.tsx
@@ -80,7 +80,28 @@ export function UsedInTable({
}, [partId]);
const tableFilters: TableFilter[] = useMemo(() => {
- return [];
+ return [
+ {
+ name: 'inherited',
+ label: t`Gets Inherited`,
+ description: t`Show inherited items`
+ },
+ {
+ name: 'optional',
+ label: t`Optional`,
+ description: t`Show optional items`
+ },
+ {
+ name: 'part_active',
+ label: t`Active`,
+ description: t`Show active assemblies`
+ },
+ {
+ name: 'part_trackable',
+ label: t`Trackable`,
+ description: t`Show trackable assemblies`
+ }
+ ];
}, [partId]);
return (
diff --git a/src/frontend/src/components/tables/build/BuildOrderTable.tsx b/src/frontend/src/components/tables/build/BuildOrderTable.tsx
index 7526421875..b10ebc685e 100644
--- a/src/frontend/src/components/tables/build/BuildOrderTable.tsx
+++ b/src/frontend/src/components/tables/build/BuildOrderTable.tsx
@@ -18,6 +18,7 @@ import {
StatusColumn,
TargetDateColumn
} from '../ColumnRenderers';
+import { StatusFilterOptions, TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
/**
@@ -101,26 +102,39 @@ function buildOrderTableColumns(): TableColumn[] {
export function BuildOrderTable({ params = {} }: { params?: any }) {
const tableColumns = useMemo(() => buildOrderTableColumns(), []);
- const tableFilters = useMemo(() => {
+ const tableFilters: TableFilter[] = useMemo(() => {
return [
{
- // TODO: Filter by status code
name: 'active',
type: 'boolean',
- label: t`Active`
+ label: t`Active`,
+ description: t`Show active orders`
+ },
+ {
+ name: 'status',
+ label: t`Status`,
+ description: t`Filter by order status`,
+ choiceFunction: StatusFilterOptions(ModelType.build)
},
{
name: 'overdue',
type: 'boolean',
- label: t`Overdue`
+ label: t`Overdue`,
+ description: t`Show overdue status`
},
{
name: 'assigned_to_me',
type: 'boolean',
- label: t`Assigned to me`
+ label: t`Assigned to me`,
+ description: t`Show orders assigned to me`
}
// TODO: 'assigned to' filter
// TODO: 'issued by' filter
+ // {
+ // name: 'has_project_code',
+ // title: t`Has Project Code`,
+ // description: t`Show orders with project code`,
+ // }
// TODO: 'has project code' filter (see table_filters.js)
// TODO: 'project code' filter (see table_filters.js)
];
diff --git a/src/frontend/src/components/tables/part/PartCategoryTable.tsx b/src/frontend/src/components/tables/part/PartCategoryTable.tsx
index 3c8e277e52..8ec22999c0 100644
--- a/src/frontend/src/components/tables/part/PartCategoryTable.tsx
+++ b/src/frontend/src/components/tables/part/PartCategoryTable.tsx
@@ -5,8 +5,10 @@ import { useNavigate } from 'react-router-dom';
import { ApiPaths } from '../../../enums/ApiEndpoints';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
+import { YesNoButton } from '../../items/YesNoButton';
import { TableColumn } from '../Column';
import { DescriptionColumn } from '../ColumnRenderers';
+import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
/**
@@ -31,6 +33,14 @@ export function PartCategoryTable({ params = {} }: { params?: any }) {
title: t`Path`,
sortable: false
},
+ {
+ accessor: 'structural',
+ title: t`Structural`,
+ sortable: true,
+ render: (record: any) => {
+ return ;
+ }
+ },
{
accessor: 'part_count',
title: t`Parts`,
@@ -39,6 +49,21 @@ export function PartCategoryTable({ params = {} }: { params?: any }) {
];
}, []);
+ const tableFilters: TableFilter[] = useMemo(() => {
+ return [
+ {
+ name: 'cascade',
+ label: t`Include Subcategories`,
+ description: t`Include subcategories in results`
+ },
+ {
+ name: 'structural',
+ label: t`Structural`,
+ description: t`Show structural categories`
+ }
+ ];
+ }, []);
+
return (
{
navigate(`/part/category/${record.pk}`);
}
diff --git a/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx b/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx
index 11dd0aaed2..dc028c0db3 100644
--- a/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx
+++ b/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx
@@ -14,6 +14,7 @@ import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { TableColumn } from '../Column';
+import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
@@ -22,6 +23,26 @@ export default function PartParameterTemplateTable() {
const user = useUserState();
+ const tableFilters: TableFilter[] = useMemo(() => {
+ return [
+ {
+ name: 'checkbox',
+ label: t`Checkbox`,
+ description: t`Show checkbox templates`
+ },
+ {
+ name: 'has_choices',
+ label: t`Has choices`,
+ description: t`Show templates with choices`
+ },
+ {
+ name: 'has_units',
+ label: t`Has Units`,
+ description: t`Show templates with units`
+ }
+ ];
+ }, []);
+
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
@@ -113,6 +134,7 @@ export default function PartParameterTemplateTable() {
columns={tableColumns}
props={{
rowActions: rowActions,
+ customFilters: tableFilters,
customActionGroups: tableActions
}}
/>
diff --git a/src/frontend/src/components/tables/part/PartVariantTable.tsx b/src/frontend/src/components/tables/part/PartVariantTable.tsx
index 9f768a0d9d..7477b6bc2e 100644
--- a/src/frontend/src/components/tables/part/PartVariantTable.tsx
+++ b/src/frontend/src/components/tables/part/PartVariantTable.tsx
@@ -1,14 +1,43 @@
+import { t } from '@lingui/macro';
+import { useMemo } from 'react';
+
+import { TableFilter } from '../Filter';
import { PartListTable } from './PartTable';
/**
* Display variant parts for the specified parent part
*/
export function PartVariantTable({ partId }: { partId: string }) {
+ const tableFilters: TableFilter[] = useMemo(() => {
+ return [
+ {
+ name: 'active',
+ label: t`Active`,
+ description: t`Show active variants`
+ },
+ {
+ name: 'template',
+ label: t`Template`,
+ description: t`Show template variants`
+ },
+ {
+ name: 'virtual',
+ label: t`Virtual`,
+ description: t`Show virtual variants`
+ },
+ {
+ name: 'trackable',
+ label: t`Trackable`,
+ description: t`Show trackable variants`
+ }
+ ];
+ }, []);
+
return (
{
+ return [
+ {
+ name: 'status',
+ label: t`Status`,
+ description: t`Filter by order status`,
+ choiceFunction: StatusFilterOptions(ModelType.purchaseorder)
+ },
+ OutstandingFilter(),
+ OverdueFilter(),
+ AssignedToMeFilter()
+ // TODO: has_project_code
+ // TODO: project_code
+ ];
+ }, []);
// TODO: Row actions
@@ -83,6 +104,7 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
...params,
supplier_detail: true
},
+ customFilters: tableFilters,
onRowClick: (row: any) => {
if (row.pk) {
navigate(`/purchasing/purchase-order/${row.pk}`);
diff --git a/src/frontend/src/components/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/components/tables/sales/ReturnOrderTable.tsx
index 4e18ec8ce9..2b5b05dbe3 100644
--- a/src/frontend/src/components/tables/sales/ReturnOrderTable.tsx
+++ b/src/frontend/src/components/tables/sales/ReturnOrderTable.tsx
@@ -16,6 +16,13 @@ import {
StatusColumn,
TargetDateColumn
} from '../ColumnRenderers';
+import {
+ AssignedToMeFilter,
+ OutstandingFilter,
+ OverdueFilter,
+ StatusFilterOptions,
+ TableFilter
+} from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
export function ReturnOrderTable({ params }: { params?: any }) {
@@ -23,7 +30,19 @@ export function ReturnOrderTable({ params }: { params?: any }) {
const navigate = useNavigate();
- // TODO: Custom filters
+ const tableFilters: TableFilter[] = useMemo(() => {
+ return [
+ {
+ name: 'status',
+ label: t`Status`,
+ description: t`Filter by order status`,
+ choiceFunction: StatusFilterOptions(ModelType.returnorder)
+ },
+ OutstandingFilter(),
+ OverdueFilter(),
+ AssignedToMeFilter()
+ ];
+ }, []);
// TODO: Row actions
@@ -81,6 +100,7 @@ export function ReturnOrderTable({ params }: { params?: any }) {
...params,
customer_detail: true
},
+ customFilters: tableFilters,
onRowClick: (row: any) => {
if (row.pk) {
navigate(`/sales/return-order/${row.pk}/`);
diff --git a/src/frontend/src/components/tables/sales/SalesOrderTable.tsx b/src/frontend/src/components/tables/sales/SalesOrderTable.tsx
index ff0e489e0d..b6bd435d17 100644
--- a/src/frontend/src/components/tables/sales/SalesOrderTable.tsx
+++ b/src/frontend/src/components/tables/sales/SalesOrderTable.tsx
@@ -17,6 +17,13 @@ import {
TargetDateColumn,
TotalPriceColumn
} from '../ColumnRenderers';
+import {
+ AssignedToMeFilter,
+ OutstandingFilter,
+ OverdueFilter,
+ StatusFilterOptions,
+ TableFilter
+} from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
export function SalesOrderTable({ params }: { params?: any }) {
@@ -24,7 +31,21 @@ export function SalesOrderTable({ params }: { params?: any }) {
const navigate = useNavigate();
- // TODO: Custom filters
+ const tableFilters: TableFilter[] = useMemo(() => {
+ return [
+ {
+ name: 'status',
+ label: t`Status`,
+ description: t`Filter by order status`,
+ choiceFunction: StatusFilterOptions(ModelType.salesorder)
+ },
+ OutstandingFilter(),
+ OverdueFilter(),
+ AssignedToMeFilter()
+ // TODO: has_project_code
+ // TODO: project_code
+ ];
+ }, []);
// TODO: Row actions
@@ -80,6 +101,7 @@ export function SalesOrderTable({ params }: { params?: any }) {
...params,
customer_detail: true
},
+ customFilters: tableFilters,
onRowClick: (row: any) => {
if (row.pk) {
navigate(`/sales/sales-order/${row.pk}/`);
diff --git a/src/frontend/src/components/tables/stock/StockItemTable.tsx b/src/frontend/src/components/tables/stock/StockItemTable.tsx
index 4b97df238d..da2fc6d1a2 100644
--- a/src/frontend/src/components/tables/stock/StockItemTable.tsx
+++ b/src/frontend/src/components/tables/stock/StockItemTable.tsx
@@ -11,7 +11,7 @@ import { apiUrl } from '../../../states/ApiState';
import { Thumbnail } from '../../images/Thumbnail';
import { TableColumn } from '../Column';
import { StatusColumn } from '../ColumnRenderers';
-import { TableFilter } from '../Filter';
+import { StatusFilterOptions, TableFilter } from '../Filter';
import { TableHoverCard } from '../TableHoverCard';
import { InvenTreeTable } from './../InvenTreeTable';
@@ -243,15 +243,98 @@ function stockItemTableColumns(): TableColumn[] {
function stockItemTableFilters(): TableFilter[] {
return [
{
- name: 'test_filter',
- label: t`Test Filter`,
- description: t`This is a test filter`,
- type: 'choice',
- choiceFunction: () => [
- { value: '1', label: 'One' },
- { value: '2', label: 'Two' },
- { value: '3', label: 'Three' }
- ]
+ name: 'active',
+ label: t`Active`,
+ description: t`Show stock for active parts`
+ },
+ {
+ name: 'status',
+ label: t`Status`,
+ description: t`Filter by stock status`,
+ choiceFunction: StatusFilterOptions(ModelType.stockitem)
+ },
+ {
+ name: 'assembly',
+ label: t`Assembly`,
+ description: t`Show stock for assmebled parts`
+ },
+ {
+ name: 'allocated',
+ label: t`Allocated`,
+ description: t`Show items which have been allocated`
+ },
+ {
+ name: 'available',
+ label: t`Available`,
+ description: t`Show items which are available`
+ },
+ {
+ name: 'cascade',
+ label: t`Include Sublocations`,
+ description: t`Include stock in sublocations`
+ },
+ {
+ name: 'depleted',
+ label: t`Depleted`,
+ description: t`Show depleted stock items`
+ },
+ {
+ name: 'in_stock',
+ label: t`In Stock`,
+ description: t`Show items which are in stock`
+ },
+ {
+ name: 'is_building',
+ label: t`In Production`,
+ description: t`Show items which are in production`
+ },
+ {
+ name: 'include_variants',
+ label: t`Include Variants`,
+ description: t`Include stock items for variant parts`
+ },
+ {
+ name: 'installed',
+ label: t`Installed`,
+ description: t`Show stock items which are installed in other items`
+ },
+ {
+ name: 'sent_to_customer',
+ label: t`Sent to Customer`,
+ description: t`Show items which have been sent to a customer`
+ },
+ {
+ name: 'serialized',
+ label: t`Is Serialized`,
+ description: t`Show items which have a serial number`
+ },
+ // TODO: serial
+ // TODO: serial_gte
+ // TODO: serial_lte
+ {
+ name: 'has_batch',
+ label: t`Has Batch Code`,
+ description: t`Show items which have a batch code`
+ },
+ // TODO: batch
+ {
+ name: 'tracked',
+ label: t`Tracked`,
+ description: t`Show tracked items`
+ },
+ {
+ name: 'has_purchase_price',
+ label: t`Has Purchase Price`,
+ description: t`Show items which have a purchase price`
+ },
+ // TODO: Expired
+ // TODO: stale
+ // TODO: expiry_date_lte
+ // TODO: expiry_date_gte
+ {
+ name: 'external',
+ label: t`External Location`,
+ description: t`Show items in an external location`
}
];
}
diff --git a/src/frontend/src/components/tables/stock/StockLocationTable.tsx b/src/frontend/src/components/tables/stock/StockLocationTable.tsx
index 5c1e969e7d..66513cbcbc 100644
--- a/src/frontend/src/components/tables/stock/StockLocationTable.tsx
+++ b/src/frontend/src/components/tables/stock/StockLocationTable.tsx
@@ -8,6 +8,7 @@ import { apiUrl } from '../../../states/ApiState';
import { YesNoButton } from '../../items/YesNoButton';
import { TableColumn } from '../Column';
import { DescriptionColumn } from '../ColumnRenderers';
+import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
/**
@@ -18,6 +19,31 @@ export function StockLocationTable({ params = {} }: { params?: any }) {
const navigate = useNavigate();
+ const tableFilters: TableFilter[] = useMemo(() => {
+ return [
+ {
+ name: 'cascade',
+ label: t`Include Sublocations`,
+ description: t`Include sublocations in results`
+ },
+ {
+ name: 'structural',
+ label: t`Structural`,
+ description: t`Show structural locations`
+ },
+ {
+ name: 'external',
+ label: t`External`,
+ description: t`Show external locations`
+ },
+ {
+ name: 'has_location_type',
+ label: t`Has location type`
+ }
+ // TODO: location_type
+ ];
+ }, []);
+
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
@@ -69,6 +95,7 @@ export function StockLocationTable({ params = {} }: { params?: any }) {
props={{
enableDownload: true,
params: params,
+ customFilters: tableFilters,
onRowClick: (record) => {
navigate(`/stock/location/${record.pk}`);
}
diff --git a/src/frontend/src/hooks/UseTable.tsx b/src/frontend/src/hooks/UseTable.tsx
index 686fe74749..b4fb340ef6 100644
--- a/src/frontend/src/hooks/UseTable.tsx
+++ b/src/frontend/src/hooks/UseTable.tsx
@@ -1,17 +1,27 @@
-import { randomId } from '@mantine/hooks';
+import { randomId, useLocalStorage } from '@mantine/hooks';
import { useCallback, useState } from 'react';
+import { TableFilter } from '../components/tables/Filter';
+
+/*
+ * Type definition for representing the state of a table:
+ *
+ * tableKey: A unique key for the table. When this key changes, the table will be refreshed.
+ * refreshTable: A callback function to externally refresh the table.
+ * activeFilters: An array of active filters (saved to local storage)
+ */
export type TableState = {
tableKey: string;
+ activeFilters: TableFilter[];
+ setActiveFilters: (filters: TableFilter[]) => void;
+ clearActiveFilters: () => void;
refreshTable: () => void;
};
/**
* A custom hook for managing the state of an component.
*
- * tableKey: A unique key for the table. When this key changes, the table will be refreshed.
- * refreshTable: A callback function to externally refresh the table.
- *
+ * Refer to the TableState type definition for more information.
*/
export function useTable(tableName: string): TableState {
@@ -27,8 +37,23 @@ export function useTable(tableName: string): TableState {
setTableKey(generateTableName());
}, []);
+ // Array of active filters (saved to local storage)
+ const [activeFilters, setActiveFilters] = useLocalStorage({
+ key: `inventree-table-filters-${tableName}`,
+ defaultValue: [],
+ getInitialValueInEffect: false
+ });
+
+ // Callback to clear all active filters from the table
+ const clearActiveFilters = useCallback(() => {
+ setActiveFilters([]);
+ }, []);
+
return {
tableKey,
+ activeFilters,
+ setActiveFilters,
+ clearActiveFilters,
refreshTable
};
}