mirror of
https://github.com/inventree/InvenTree.git
synced 2026-03-04 03:11:46 +00:00
[UI] Default table filters (#11405)
* Support "default filters" for table views - User overrides apply in preference - Only when there is no stored value (null) * Correctly handle partially constructed filters - Reverse lookup on available filter set * Add default filters for order tables * Default filters for company tables * More default filters * Add some more default filters * Bump CHANGELOG * build fix * Tweaks for playwright testing * Tweak playwright test * Improve test flexibility
This commit is contained in:
@@ -39,7 +39,7 @@ export type TableFilterType = 'boolean' | 'choice' | 'date' | 'text' | 'api';
|
||||
*/
|
||||
export type TableFilter = {
|
||||
name: string;
|
||||
label: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
type?: TableFilterType;
|
||||
choices?: TableFilterChoice[];
|
||||
|
||||
@@ -1,21 +1,43 @@
|
||||
import type { FilterSetState, TableFilter } from '@lib/types/Filters';
|
||||
import { useLocalStorage } from '@mantine/hooks';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
export function useFilterSet(filterKey: string): FilterSetState {
|
||||
export function useFilterSet(
|
||||
filterKey: string,
|
||||
initialFilters?: TableFilter[]
|
||||
): FilterSetState {
|
||||
// Array of active filters (saved to local storage)
|
||||
const [activeFilters, setActiveFilters] = useLocalStorage<TableFilter[]>({
|
||||
const [storedFilters, setStoredFilters] = useLocalStorage<
|
||||
TableFilter[] | null
|
||||
>({
|
||||
key: `inventree-filterset-${filterKey}`,
|
||||
defaultValue: [],
|
||||
defaultValue: null,
|
||||
sync: false,
|
||||
getInitialValueInEffect: false
|
||||
});
|
||||
|
||||
const activeFilters: TableFilter[] = useMemo(() => {
|
||||
if (storedFilters == null) {
|
||||
// If there are no stored filters, set initial values
|
||||
const filters = initialFilters || [];
|
||||
setStoredFilters(filters);
|
||||
return filters;
|
||||
}
|
||||
return storedFilters || [];
|
||||
}, [storedFilters]);
|
||||
|
||||
// Callback to clear all active filters from the table
|
||||
const clearActiveFilters = useCallback(() => {
|
||||
setActiveFilters([]);
|
||||
setStoredFilters([]);
|
||||
}, []);
|
||||
|
||||
const setActiveFilters = useCallback(
|
||||
(filters: TableFilter[]) => {
|
||||
setStoredFilters(filters);
|
||||
},
|
||||
[setStoredFilters]
|
||||
);
|
||||
|
||||
return {
|
||||
filterKey,
|
||||
activeFilters,
|
||||
|
||||
@@ -2,17 +2,28 @@ import { randomId } from '@mantine/hooks';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import type { FilterSetState } from '@lib/types/Filters';
|
||||
import type { FilterSetState, TableFilter } from '@lib/types/Filters';
|
||||
import type { TableState } from '@lib/types/Tables';
|
||||
import { useFilterSet } from './UseFilterSet';
|
||||
|
||||
export type TableStateExtraProps = {
|
||||
idAccessor?: string;
|
||||
initialFilters?: TableFilter[];
|
||||
};
|
||||
|
||||
/**
|
||||
* A custom hook for managing the state of an <InvenTreeTable> component.
|
||||
*
|
||||
* Refer to the TableState type definition for more information.
|
||||
*/
|
||||
|
||||
export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
||||
export function useTable(
|
||||
tableName: string,
|
||||
tableProps: TableStateExtraProps = {
|
||||
idAccessor: 'pk',
|
||||
initialFilters: []
|
||||
}
|
||||
): TableState {
|
||||
// Function to generate a new ID (to refresh the table)
|
||||
function generateTableName() {
|
||||
return `${tableName.replaceAll('-', '')}-${randomId()}`;
|
||||
@@ -38,7 +49,10 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
||||
[generateTableName]
|
||||
);
|
||||
|
||||
const filterSet: FilterSetState = useFilterSet(`table-${tableName}`);
|
||||
const filterSet: FilterSetState = useFilterSet(
|
||||
`table-${tableName}`,
|
||||
tableProps.initialFilters
|
||||
);
|
||||
|
||||
// Array of expanded records
|
||||
const [expandedRecords, setExpandedRecords] = useState<any[]>([]);
|
||||
@@ -59,7 +73,7 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
||||
|
||||
// Array of selected primary key values
|
||||
const selectedIds = useMemo(
|
||||
() => selectedRecords.map((r) => r[idAccessor || 'pk']),
|
||||
() => selectedRecords.map((r) => r[tableProps.idAccessor || 'pk']),
|
||||
[selectedRecords]
|
||||
);
|
||||
|
||||
@@ -89,7 +103,7 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
||||
|
||||
// Find the matching record in the table
|
||||
const index = _records.findIndex(
|
||||
(r) => r[idAccessor || 'pk'] === record.pk
|
||||
(r) => r[tableProps.idAccessor || 'pk'] === record.pk
|
||||
);
|
||||
|
||||
if (index >= 0) {
|
||||
@@ -106,6 +120,11 @@ export function useTable(tableName: string, idAccessor = 'pk'): TableState {
|
||||
[records]
|
||||
);
|
||||
|
||||
const idAccessor = useMemo(
|
||||
() => tableProps.idAccessor || 'pk',
|
||||
[tableProps.idAccessor]
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
return {
|
||||
|
||||
@@ -20,7 +20,7 @@ import { InvenTreeTable } from '../../../../tables/InvenTreeTable';
|
||||
export function CurrencyTable({
|
||||
setInfo
|
||||
}: Readonly<{ setInfo: (info: any) => void }>) {
|
||||
const table = useTable('currency', 'currency');
|
||||
const table = useTable('currency', { idAccessor: 'currency' });
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ import { InvenTreeTable } from '../../../../tables/InvenTreeTable';
|
||||
import CustomUnitsTable from '../../../../tables/settings/CustomUnitsTable';
|
||||
|
||||
function AllUnitTable() {
|
||||
const table = useTable('all-units', 'name');
|
||||
const table = useTable('all-units', { idAccessor: 'name' });
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -469,6 +469,7 @@ export default function BuildDetail() {
|
||||
tableName='build-consumed'
|
||||
showLocation={false}
|
||||
allowReturn
|
||||
defaultInStock={null}
|
||||
params={{
|
||||
consumed_by: id
|
||||
}}
|
||||
|
||||
@@ -248,6 +248,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||
tableName='assigned-stock'
|
||||
showLocation={false}
|
||||
allowReturn
|
||||
defaultInStock={null}
|
||||
params={{ customer: company.pk }}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -108,6 +108,7 @@ export default function PurchasingIndex() {
|
||||
icon: <IconTable />,
|
||||
content: (
|
||||
<CompanyTable
|
||||
companyType='supplier'
|
||||
path='purchasing/supplier'
|
||||
params={{ is_supplier: true }}
|
||||
/>
|
||||
@@ -157,6 +158,7 @@ export default function PurchasingIndex() {
|
||||
icon: <IconTable />,
|
||||
content: (
|
||||
<CompanyTable
|
||||
companyType='manufacturer'
|
||||
path='purchasing/manufacturer'
|
||||
params={{ is_manufacturer: true }}
|
||||
/>
|
||||
|
||||
@@ -141,6 +141,7 @@ export default function SalesIndex() {
|
||||
icon: <IconTable />,
|
||||
content: (
|
||||
<CompanyTable
|
||||
companyType='customer'
|
||||
path='sales/customer'
|
||||
params={{ is_customer: true }}
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { isTrue } from '@lib/functions/Conversion';
|
||||
import type { TableFilter, TableFilterChoice } from '@lib/types/Filters';
|
||||
import type {
|
||||
StatusCodeInterface,
|
||||
@@ -14,6 +15,47 @@ import {
|
||||
} from '../states/GlobalStatusState';
|
||||
import { useGlobalSettingsState } from '../states/SettingsStates';
|
||||
|
||||
// Determine the appropriate display label for a given filter, based on its name and the list of available filters
|
||||
export function filterDisplayLabel(
|
||||
name: string,
|
||||
filters?: TableFilter[]
|
||||
): string {
|
||||
const filter = filters?.find((f) => f.name === name);
|
||||
return filter?.label ?? name;
|
||||
}
|
||||
|
||||
// Determine the appropriate display value for a filter, based on its type and value
|
||||
// This is useful for recreating a display value if we only have a name:value pair
|
||||
export function filterDisplayValue(
|
||||
name: string,
|
||||
value: any,
|
||||
filters?: TableFilter[]
|
||||
) {
|
||||
const filterDef = filters?.find((f) => f.name === name);
|
||||
|
||||
if (!filterDef) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!filterDef.type || filterDef.type == 'boolean') {
|
||||
return isTrue(value) ? t`Yes` : t`No`;
|
||||
}
|
||||
|
||||
if (filterDef.type === 'choice' && filterDef.choices) {
|
||||
const choice = filterDef.choices.find((c) => c.value === value);
|
||||
return choice ? choice.label : value;
|
||||
}
|
||||
|
||||
if (filterDef.type === 'choice' && filterDef.choiceFunction) {
|
||||
const choices = filterDef.choiceFunction();
|
||||
const choice = choices.find((c) => c.value === value);
|
||||
return choice ? choice.label : value;
|
||||
}
|
||||
|
||||
// No obvious match - return the raw value
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of available filter options for a given filter
|
||||
* @param filter - TableFilter object
|
||||
|
||||
@@ -28,17 +28,46 @@ import type {
|
||||
import { IconCheck } from '@tabler/icons-react';
|
||||
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||
import { StylishText } from '../components/items/StylishText';
|
||||
import { getTableFilterOptions } from './Filter';
|
||||
import {
|
||||
filterDisplayLabel,
|
||||
filterDisplayValue,
|
||||
getTableFilterOptions
|
||||
} from './Filter';
|
||||
|
||||
/*
|
||||
* Render a preview of a single filter
|
||||
*/
|
||||
export function FilterPreview({
|
||||
filter,
|
||||
filters
|
||||
}: Readonly<{
|
||||
filter: TableFilter;
|
||||
filters?: TableFilter[];
|
||||
}>) {
|
||||
return (
|
||||
<Group key={filter.name} justify='space-between' gap='xl' wrap='nowrap'>
|
||||
<Text size='sm'>
|
||||
{filter.label ?? filterDisplayLabel(filter.name, filters)}
|
||||
</Text>
|
||||
<Text size='xs'>
|
||||
{filter.displayValue ??
|
||||
filterDisplayValue(filter.name, filter.value, filters)}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Render a single table filter item
|
||||
*/
|
||||
function FilterItem({
|
||||
flt,
|
||||
filterSet
|
||||
filterSet,
|
||||
availableFilters
|
||||
}: Readonly<{
|
||||
flt: TableFilter;
|
||||
filterSet: FilterSetState;
|
||||
availableFilters?: TableFilter[];
|
||||
}>) {
|
||||
const removeFilter = useCallback(() => {
|
||||
const newFilters = filterSet.activeFilters.filter(
|
||||
@@ -47,17 +76,29 @@ function FilterItem({
|
||||
filterSet.setActiveFilters(newFilters);
|
||||
}, [flt]);
|
||||
|
||||
// Find the matching filter definition
|
||||
const filterProps: TableFilter | undefined = useMemo(() => {
|
||||
return availableFilters?.find((f) => f.name === flt.name);
|
||||
}, [availableFilters, flt]);
|
||||
|
||||
return (
|
||||
<Paper p='sm' shadow='sm' radius='xs'>
|
||||
<Group justify='space-between' key={flt.name} wrap='nowrap'>
|
||||
<Stack gap='xs'>
|
||||
<Text size='sm'>{flt.label}</Text>
|
||||
<Text size='xs'>{flt.description}</Text>
|
||||
<Text size='sm'>{flt.label ?? filterProps?.label ?? flt.name}</Text>
|
||||
<Text size='xs'>{flt.description ?? filterProps?.description}</Text>
|
||||
</Stack>
|
||||
<Group justify='right'>
|
||||
<Badge>{flt.displayValue ?? flt.value}</Badge>
|
||||
<Tooltip label={t`Remove filter`} withinPortal={true}>
|
||||
<CloseButton size='md' color='red' onClick={removeFilter} />
|
||||
<Badge>
|
||||
{flt.displayValue ??
|
||||
filterDisplayValue(flt.name, flt.value, availableFilters)}
|
||||
</Badge>
|
||||
<Tooltip
|
||||
label={t`Remove filter`}
|
||||
withinPortal={true}
|
||||
position='top-end'
|
||||
>
|
||||
<CloseButton size='md' c='red' onClick={removeFilter} />
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Group>
|
||||
@@ -174,10 +215,10 @@ function FilterAddGroup({
|
||||
return (
|
||||
availableFilters
|
||||
?.filter((flt) => !activeFilterNames.includes(flt.name))
|
||||
?.sort((a, b) => a.label.localeCompare(b.label))
|
||||
?.sort((a, b) => (a.label ?? a.name).localeCompare(b.label ?? b.name))
|
||||
?.map((flt) => ({
|
||||
value: flt.name,
|
||||
label: flt.label,
|
||||
label: flt.label ?? flt.name,
|
||||
description: flt.description
|
||||
})) ?? []
|
||||
);
|
||||
@@ -317,7 +358,12 @@ export function FilterSelectDrawer({
|
||||
<Stack gap='xs'>
|
||||
{hasFilters &&
|
||||
filterSet.activeFilters?.map((f) => (
|
||||
<FilterItem key={f.name} flt={f} filterSet={filterSet} />
|
||||
<FilterItem
|
||||
key={f.name}
|
||||
flt={f}
|
||||
filterSet={filterSet}
|
||||
availableFilters={availableFilters}
|
||||
/>
|
||||
))}
|
||||
{addFilter && (
|
||||
<Stack gap='xs'>
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
Paper,
|
||||
Space,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
@@ -37,7 +36,7 @@ import { StylishText } from '../components/items/StylishText';
|
||||
import useDataExport from '../hooks/UseDataExport';
|
||||
import { useDeleteApiFormModal } from '../hooks/UseForm';
|
||||
import { TableColumnSelect } from './ColumnSelect';
|
||||
import { FilterSelectDrawer } from './FilterSelectDrawer';
|
||||
import { FilterPreview, FilterSelectDrawer } from './FilterSelectDrawer';
|
||||
|
||||
/**
|
||||
* Render a composite header for an InvenTree table
|
||||
@@ -272,15 +271,10 @@ export default function InvenTreeTableHeader({
|
||||
<StylishText size='md'>{t`Active Filters`}</StylishText>
|
||||
<Divider />
|
||||
{tableState.filterSet.activeFilters?.map((filter) => (
|
||||
<Group
|
||||
key={filter.name}
|
||||
justify='space-between'
|
||||
gap='xl'
|
||||
wrap='nowrap'
|
||||
>
|
||||
<Text size='sm'>{filter.label}</Text>
|
||||
<Text size='xs'>{filter.displayValue}</Text>
|
||||
</Group>
|
||||
<FilterPreview
|
||||
filter={filter}
|
||||
filters={tableProps.tableFilters}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -64,7 +64,14 @@ export function BuildOrderTable({
|
||||
salesOrderId?: number;
|
||||
}>) {
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
const table = useTable(!!partId ? 'buildorder-part' : 'buildorder-index');
|
||||
const table = useTable(!!partId ? 'buildorder-part' : 'buildorder-index', {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'outstanding',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
return [
|
||||
|
||||
@@ -29,13 +29,22 @@ import { InvenTreeTable } from '../InvenTreeTable';
|
||||
* based on the provided filter parameters
|
||||
*/
|
||||
export function CompanyTable({
|
||||
companyType,
|
||||
params,
|
||||
path
|
||||
}: Readonly<{
|
||||
companyType?: string;
|
||||
params?: any;
|
||||
path?: string;
|
||||
}>) {
|
||||
const table = useTable('company');
|
||||
const table = useTable(`company-${companyType ?? 'index'}`, {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'active',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function BarcodeScanTable({
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
|
||||
const table = useTable('barcode-scan-results', 'id');
|
||||
const table = useTable('barcode-scan-results', { idAccessor: 'id' });
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
|
||||
@@ -338,17 +338,26 @@ export function PartListTable({
|
||||
enableImport = true,
|
||||
basePartInstance,
|
||||
props,
|
||||
tableName = 'part-list',
|
||||
defaultPartData
|
||||
}: Readonly<{
|
||||
enableImport?: boolean;
|
||||
props?: InvenTreeTableProps;
|
||||
basePartInstance?: any;
|
||||
tableName?: string;
|
||||
defaultPartData?: any;
|
||||
}>) {
|
||||
const tableColumns = useMemo(() => partTableColumns(), []);
|
||||
const tableFilters = useMemo(() => partTableFilters(), []);
|
||||
|
||||
const table = useTable('part-list');
|
||||
const table = useTable(tableName ?? 'part-list', {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'active',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
const user = useUserState();
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ export function PartVariantTable({ part }: Readonly<{ part: any }>) {
|
||||
ancestor: part.pk
|
||||
}
|
||||
}}
|
||||
tableName='part-variants'
|
||||
basePartInstance={part}
|
||||
defaultPartData={{
|
||||
...part,
|
||||
|
||||
@@ -19,7 +19,9 @@ export interface PluginRegistryErrorI {
|
||||
* Table displaying list of plugin registry errors
|
||||
*/
|
||||
export default function PluginErrorTable() {
|
||||
const table = useTable('registryErrors', 'id');
|
||||
const table = useTable('registryErrors', {
|
||||
idAccessor: 'id'
|
||||
});
|
||||
|
||||
const registryErrorTableColumns: TableColumn<PluginRegistryErrorI>[] =
|
||||
useMemo(
|
||||
|
||||
@@ -54,7 +54,29 @@ export function ManufacturerPartTable({
|
||||
return tId;
|
||||
}, [manufacturerId, partId]);
|
||||
|
||||
const table = useTable(tableId);
|
||||
const initialFilters = useMemo(() => {
|
||||
const filters: TableFilter[] = [];
|
||||
|
||||
if (!manufacturerId) {
|
||||
filters.push({
|
||||
name: 'manufacturer_active',
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
if (!partId) {
|
||||
filters.push({
|
||||
name: 'part_active',
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, [manufacturerId, partId]);
|
||||
|
||||
const table = useTable(tableId, {
|
||||
initialFilters: initialFilters
|
||||
});
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
|
||||
@@ -60,7 +60,14 @@ export function PurchaseOrderTable({
|
||||
supplierPartId?: number;
|
||||
externalBuildId?: number;
|
||||
}>) {
|
||||
const table = useTable('purchase-order');
|
||||
const table = useTable('purchase-order', {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'outstanding',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
const user = useUserState();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
|
||||
@@ -54,7 +54,34 @@ export function SupplierPartTable({
|
||||
partId?: number;
|
||||
supplierId?: number;
|
||||
}>): ReactNode {
|
||||
const table = useTable('supplierparts');
|
||||
const initialFilters = useMemo(() => {
|
||||
const filters: TableFilter[] = [
|
||||
{
|
||||
name: 'active',
|
||||
value: 'true'
|
||||
}
|
||||
];
|
||||
|
||||
if (!supplierId) {
|
||||
filters.push({
|
||||
name: 'supplier_active',
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
if (!partId) {
|
||||
filters.push({
|
||||
name: 'part_active',
|
||||
value: 'true'
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, [supplierId, partId]);
|
||||
|
||||
const table = useTable('supplierparts', {
|
||||
initialFilters: initialFilters
|
||||
});
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
|
||||
@@ -56,7 +56,18 @@ export function ReturnOrderTable({
|
||||
partId?: number;
|
||||
customerId?: number;
|
||||
}>) {
|
||||
const table = useTable(!!partId ? 'returnorders-part' : 'returnorders-index');
|
||||
const table = useTable(
|
||||
!!partId ? 'returnorders-part' : 'returnorders-index',
|
||||
{
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'outstanding',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
|
||||
@@ -58,7 +58,14 @@ export function SalesOrderTable({
|
||||
partId?: number;
|
||||
customerId?: number;
|
||||
}>) {
|
||||
const table = useTable(!!partId ? 'salesorder-part' : 'salesorder-index');
|
||||
const table = useTable(!!partId ? 'salesorder-part' : 'salesorder-index', {
|
||||
initialFilters: [
|
||||
{
|
||||
name: 'outstanding',
|
||||
value: 'true'
|
||||
}
|
||||
]
|
||||
});
|
||||
const user = useUserState();
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
|
||||
@@ -48,7 +48,7 @@ export function ApiTokenTable({
|
||||
return [];
|
||||
}, [only_myself]);
|
||||
|
||||
const table = useTable('api-tokens', 'id');
|
||||
const table = useTable('api-tokens', { idAccessor: 'id' });
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
const cols = [
|
||||
|
||||
@@ -61,7 +61,7 @@ export function EmailTable() {
|
||||
];
|
||||
}, []);
|
||||
|
||||
const table = useTable('emails', 'pk');
|
||||
const table = useTable('emails', { idAccessor: 'pk' });
|
||||
|
||||
const [selectedEmailId, setSelectedEmailId] = useState<string>('');
|
||||
|
||||
|
||||
@@ -315,6 +315,8 @@ export function StockItemTable({
|
||||
showLocation = true,
|
||||
showPricing = true,
|
||||
allowReturn = false,
|
||||
initialFilters,
|
||||
defaultInStock = true,
|
||||
tableName = 'stockitems'
|
||||
}: Readonly<{
|
||||
params?: any;
|
||||
@@ -322,9 +324,34 @@ export function StockItemTable({
|
||||
showLocation?: boolean;
|
||||
showPricing?: boolean;
|
||||
allowReturn?: boolean;
|
||||
defaultInStock?: boolean | null;
|
||||
initialFilters?: TableFilter[];
|
||||
tableName: string;
|
||||
}>) {
|
||||
const table = useTable(tableName);
|
||||
const initialStockFilters: TableFilter[] = useMemo(() => {
|
||||
if (!!initialFilters) {
|
||||
return initialFilters;
|
||||
}
|
||||
|
||||
const filters: TableFilter[] = [];
|
||||
|
||||
// Optionally set the default "in_stock" filter
|
||||
// Typically, we default to only displaying "in_stock" items,
|
||||
// but this can be overridden by the caller if required
|
||||
if (defaultInStock != undefined && defaultInStock != null) {
|
||||
filters.push({
|
||||
name: 'in_stock',
|
||||
value: defaultInStock ? 'true' : 'false'
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, [defaultInStock, initialFilters]);
|
||||
|
||||
const table = useTable(tableName, {
|
||||
initialFilters: initialStockFilters
|
||||
});
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const settings = useGlobalSettingsState();
|
||||
|
||||
@@ -723,6 +723,8 @@ test('Build Order - External', async ({ browser }) => {
|
||||
await navigate(page, 'manufacturing/build-order/26/details');
|
||||
await loadTab(page, 'External Orders');
|
||||
|
||||
await clearTableFilters(page);
|
||||
|
||||
await page.getByRole('cell', { name: 'PO0017' }).waitFor();
|
||||
await page.getByRole('cell', { name: 'PO0018' }).waitFor();
|
||||
});
|
||||
|
||||
@@ -15,21 +15,28 @@ test('Company', async ({ browser }) => {
|
||||
await navigate(page, 'company/1/details');
|
||||
await page.getByLabel('Details').getByText('DigiKey Electronics').waitFor();
|
||||
await page.getByRole('cell', { name: 'https://www.digikey.com/' }).waitFor();
|
||||
|
||||
await loadTab(page, 'Supplied Parts');
|
||||
await page
|
||||
.getByRole('cell', { name: 'RR05P100KDTR-ND', exact: true })
|
||||
.waitFor();
|
||||
|
||||
await loadTab(page, 'Purchase Orders');
|
||||
await clearTableFilters(page);
|
||||
await page.getByRole('cell', { name: 'Molex connectors' }).first().waitFor();
|
||||
|
||||
await loadTab(page, 'Stock Items');
|
||||
await page
|
||||
.getByRole('cell', { name: 'Blue plastic enclosure' })
|
||||
.first()
|
||||
.waitFor();
|
||||
|
||||
await loadTab(page, 'Contacts');
|
||||
await page.getByRole('cell', { name: 'jimmy.mcleod@digikey.com' }).waitFor();
|
||||
|
||||
await loadTab(page, 'Addresses');
|
||||
await page.getByRole('cell', { name: 'Carla Tunnel' }).waitFor();
|
||||
|
||||
await loadTab(page, 'Attachments');
|
||||
await loadTab(page, 'Notes');
|
||||
|
||||
|
||||
@@ -66,14 +66,16 @@ test('Parts - Tabs', async ({ browser }) => {
|
||||
});
|
||||
|
||||
test('Parts - Manufacturer Parts', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, { url: 'part/84/suppliers' });
|
||||
const page = await doCachedLogin(browser, { url: 'part/84/' });
|
||||
|
||||
// Load the "suppliers" tab
|
||||
await loadTab(page, 'Suppliers');
|
||||
await page.getByText('Hammond Manufacturing').click();
|
||||
await loadTab(page, 'Parameters');
|
||||
await loadTab(page, 'Suppliers');
|
||||
await loadTab(page, 'Attachments');
|
||||
|
||||
// Wait for manufacturer part page to load
|
||||
await page.getByText('1551ACLR - 1551ACLR').waitFor();
|
||||
await loadTab(page, 'Parameters');
|
||||
await loadTab(page, 'Attachments');
|
||||
});
|
||||
|
||||
test('Parts - Supplier Parts', async ({ browser }) => {
|
||||
|
||||
@@ -25,6 +25,14 @@ test('Purchasing - Index', async ({ browser }) => {
|
||||
await showCalendarView(page);
|
||||
await showTableView(page);
|
||||
|
||||
// Check default filters are applied
|
||||
// By default, only outstanding orders are visible
|
||||
await page.getByText(/1 - \d+ \/ \d+/).waitFor();
|
||||
|
||||
// Clearing the filters, more orders should be visible
|
||||
await clearTableFilters(page);
|
||||
await page.getByText(/1 - 1\d \/ 1\d/).waitFor();
|
||||
|
||||
// Suppliers tab
|
||||
await loadTab(page, 'Suppliers');
|
||||
await showParametricView(page);
|
||||
|
||||
@@ -33,7 +33,7 @@ test('Exporting - Orders', async ({ browser }) => {
|
||||
await page.getByText('Process completed successfully').waitFor();
|
||||
|
||||
// Download list of purchase order items
|
||||
await page.getByRole('cell', { name: 'PO0011' }).click();
|
||||
await page.getByRole('cell', { name: 'PO0014' }).click();
|
||||
await loadTab(page, 'Line Items');
|
||||
await openExportDialog(page);
|
||||
await page.getByRole('button', { name: 'Export', exact: true }).click();
|
||||
|
||||
@@ -62,7 +62,7 @@ test('Printing - Report Printing', async ({ browser }) => {
|
||||
await loadTab(page, 'Purchase Orders');
|
||||
await activateTableView(page);
|
||||
|
||||
await page.getByRole('cell', { name: 'PO0009' }).click();
|
||||
await page.getByRole('cell', { name: 'PO0013' }).click();
|
||||
|
||||
// Select "print report"
|
||||
await page.getByLabel('action-menu-printing-actions').click();
|
||||
|
||||
Reference in New Issue
Block a user