mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 19:46:46 +00:00
[PUI] Part charts fix (#8171)
* Refactor "stocktake" chart - Format date axis correctly - Better tooltips * Fix datatable queries - Prevent potential infinite loop * Fix tooltip for BOM table - Bug due to '.' char in names * Do not reassign parameter * Another fix * Check global config value * Cleanup tooltip for BOM pie * Edit and refresh pricing from panel
This commit is contained in:
parent
b1c1981b6d
commit
0f2cfdcfd4
@ -93,7 +93,7 @@ export enum ApiEndpoints {
|
||||
part_parameter_list = 'part/parameter/',
|
||||
part_parameter_template_list = 'part/parameter/template/',
|
||||
part_thumbs_list = 'part/thumbs/',
|
||||
part_pricing_get = 'part/:id/pricing/',
|
||||
part_pricing = 'part/:id/pricing/',
|
||||
part_serial_numbers = 'part/:id/serial-numbers/',
|
||||
part_scheduling = 'part/:id/scheduling/',
|
||||
part_pricing_internal = 'part/internal-price/',
|
||||
|
@ -64,6 +64,7 @@ import {
|
||||
IconProps,
|
||||
IconQrcode,
|
||||
IconQuestionMark,
|
||||
IconRefresh,
|
||||
IconRulerMeasure,
|
||||
IconShoppingCart,
|
||||
IconShoppingCartHeart,
|
||||
@ -143,6 +144,7 @@ const icons = {
|
||||
photo: IconPhoto,
|
||||
upload: IconFileUpload,
|
||||
reject: IconX,
|
||||
refresh: IconRefresh,
|
||||
select_image: IconGridDots,
|
||||
delete: IconTrash,
|
||||
packaging: IconPackage,
|
||||
|
@ -60,6 +60,6 @@ export function showLoginNotification({
|
||||
color: success ? 'green' : 'red',
|
||||
icon: success ? <IconCircleCheck /> : <IconExclamationCircle />,
|
||||
id: 'login',
|
||||
autoClose: 5000
|
||||
autoClose: 2500
|
||||
});
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export type TableState = {
|
||||
export function useTable(tableName: string): TableState {
|
||||
// Function to generate a new ID (to refresh the table)
|
||||
function generateTableName() {
|
||||
return `${tableName}-${randomId()}`;
|
||||
return `${tableName.replaceAll('-', '')}-${randomId()}`;
|
||||
}
|
||||
|
||||
const [tableKey, setTableKey] = useState<string>(generateTableName());
|
||||
|
@ -55,7 +55,6 @@ import {
|
||||
EditItemAction,
|
||||
OptionsActionDropdown
|
||||
} from '../../components/items/ActionDropdown';
|
||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
||||
import { StylishText } from '../../components/items/StylishText';
|
||||
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||
import NavigationTree from '../../components/nav/NavigationTree';
|
||||
@ -401,7 +400,7 @@ export default function PartDetail() {
|
||||
const { data } = useSuspenseQuery({
|
||||
queryKey: ['pricing', id],
|
||||
queryFn: async () => {
|
||||
const url = apiUrl(ApiEndpoints.part_pricing_get, null, {
|
||||
const url = apiUrl(ApiEndpoints.part_pricing, null, {
|
||||
id: id
|
||||
});
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { useMemo, useState } from 'react';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import BomPricingPanel from './pricing/BomPricingPanel';
|
||||
import PriceBreakPanel from './pricing/PriceBreakPanel';
|
||||
@ -31,15 +32,18 @@ export enum panelOptions {
|
||||
export default function PartPricingPanel({ part }: Readonly<{ part: any }>) {
|
||||
const user = useUserState();
|
||||
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
|
||||
const { instance: pricing, instanceQuery } = useInstance({
|
||||
pk: part?.pk,
|
||||
hasPrimaryKey: true,
|
||||
endpoint: ApiEndpoints.part_pricing_get,
|
||||
endpoint: ApiEndpoints.part_pricing,
|
||||
defaultValue: {}
|
||||
});
|
||||
|
||||
// TODO: Do we display internal price? This is a global setting
|
||||
const internalPricing = true;
|
||||
const internalPricing: boolean = useMemo(() => {
|
||||
return globalSettings.isSet('PART_INTERNAL_PRICE');
|
||||
}, [globalSettings]);
|
||||
|
||||
const purchaseOrderPricing = useMemo(() => {
|
||||
return user.hasViewRole(UserRoles.purchase_order) && part?.purchaseable;
|
||||
@ -75,6 +79,7 @@ export default function PartPricingPanel({ part }: Readonly<{ part: any }>) {
|
||||
<PricingOverviewPanel
|
||||
part={part}
|
||||
pricing={pricing}
|
||||
pricingQuery={instanceQuery}
|
||||
doNavigation={doNavigation}
|
||||
/>
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ export default function PartSchedulingDetail({ part }: { part: any }) {
|
||||
yAxisLabel={t`Expected Quantity`}
|
||||
xAxisLabel={t`Date`}
|
||||
xAxisProps={{
|
||||
domain: [chartLimits[0], chartLimits[1]],
|
||||
domain: chartLimits,
|
||||
scale: 'time',
|
||||
type: 'number',
|
||||
tickFormatter: (value: number) => {
|
||||
|
@ -1,10 +1,17 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { LineChart } from '@mantine/charts';
|
||||
import { Center, Loader, SimpleGrid } from '@mantine/core';
|
||||
import { ChartTooltipProps, LineChart } from '@mantine/charts';
|
||||
import {
|
||||
Center,
|
||||
Divider,
|
||||
Loader,
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { formatPriceRange } from '../../defaults/formatters';
|
||||
import { formatDate, formatPriceRange } from '../../defaults/formatters';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import {
|
||||
@ -23,6 +30,42 @@ import { TableColumn } from '../../tables/Column';
|
||||
import { InvenTreeTable } from '../../tables/InvenTreeTable';
|
||||
import { RowDeleteAction, RowEditAction } from '../../tables/RowActions';
|
||||
|
||||
/*
|
||||
* Render a tooltip for the chart, with correct date information
|
||||
*/
|
||||
function ChartTooltip({ label, payload }: ChartTooltipProps) {
|
||||
const formattedLabel: string = useMemo(() => {
|
||||
if (label && typeof label === 'number') {
|
||||
return formatDate(new Date(label).toISOString()) ?? label;
|
||||
} else if (!!label) {
|
||||
return label.toString();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, [label]);
|
||||
|
||||
if (!payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const quantity = payload.find((item) => item.name == 'quantity');
|
||||
const value_min = payload.find((item) => item.name == 'value_min');
|
||||
const value_max = payload.find((item) => item.name == 'value_max');
|
||||
|
||||
return (
|
||||
<Paper px="md" py="sm" withBorder shadow="md" radius="md">
|
||||
<Text key="title">{formattedLabel}</Text>
|
||||
<Divider />
|
||||
<Text key="quantity" fz="sm">
|
||||
{t`Quantity`} : {quantity?.value}
|
||||
</Text>
|
||||
<Text key="values" fz="sm">
|
||||
{t`Value`} : {formatPriceRange(value_min?.value, value_max?.value)}
|
||||
</Text>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PartStocktakeDetail({ partId }: { partId: number }) {
|
||||
const user = useUserState();
|
||||
const table = useTable('part-stocktake');
|
||||
@ -126,7 +169,7 @@ export default function PartStocktakeDetail({ partId }: { partId: number }) {
|
||||
let records =
|
||||
table.records?.map((record: any) => {
|
||||
return {
|
||||
date: record.date,
|
||||
date: new Date(record.date).valueOf(),
|
||||
quantity: record.quantity,
|
||||
value_min: record.cost_min,
|
||||
value_max: record.cost_max
|
||||
@ -135,12 +178,29 @@ export default function PartStocktakeDetail({ partId }: { partId: number }) {
|
||||
|
||||
// Sort records to ensure correct date order
|
||||
records.sort((a, b) => {
|
||||
return new Date(a.date) < new Date(b.date) ? -1 : 1;
|
||||
return a < b ? -1 : 1;
|
||||
});
|
||||
|
||||
return records;
|
||||
}, [table.records]);
|
||||
|
||||
// Calculate the date limits of the chart
|
||||
const chartLimits: number[] = useMemo(() => {
|
||||
let min_date = new Date();
|
||||
let max_date = new Date();
|
||||
|
||||
if (chartData.length > 0) {
|
||||
min_date = new Date(chartData[0].date);
|
||||
max_date = new Date(chartData[chartData.length - 1].date);
|
||||
}
|
||||
|
||||
// Expand limits by one day on either side
|
||||
min_date.setDate(min_date.getDate() - 1);
|
||||
max_date.setDate(max_date.getDate() + 1);
|
||||
|
||||
return [min_date.valueOf(), max_date.valueOf()];
|
||||
}, [chartData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{generateReport.modal}
|
||||
@ -172,6 +232,19 @@ export default function PartStocktakeDetail({ partId }: { partId: number }) {
|
||||
withRightYAxis
|
||||
yAxisLabel={t`Quantity`}
|
||||
rightYAxisLabel={t`Stock Value`}
|
||||
tooltipProps={{
|
||||
content: ({ label, payload }) => (
|
||||
<ChartTooltip label={label} payload={payload} />
|
||||
)
|
||||
}}
|
||||
xAxisProps={{
|
||||
scale: 'time',
|
||||
type: 'number',
|
||||
domain: chartLimits,
|
||||
tickFormatter: (value: number) => {
|
||||
return formatDate(new Date(value).toISOString());
|
||||
}
|
||||
}}
|
||||
series={[
|
||||
{
|
||||
name: 'quantity',
|
||||
@ -181,14 +254,14 @@ export default function PartStocktakeDetail({ partId }: { partId: number }) {
|
||||
},
|
||||
{
|
||||
name: 'value_min',
|
||||
label: t`Min Value`,
|
||||
color: 'teal.6',
|
||||
label: t`Minimum Value`,
|
||||
color: 'yellow.6',
|
||||
yAxisId: 'right'
|
||||
},
|
||||
{
|
||||
name: 'value_max',
|
||||
label: t`Max Value`,
|
||||
color: 'red.6',
|
||||
label: t`Maximum Value`,
|
||||
color: 'teal.6',
|
||||
yAxisId: 'right'
|
||||
}
|
||||
]}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { BarChart, DonutChart } from '@mantine/charts';
|
||||
import { BarChart, ChartTooltipProps, DonutChart } from '@mantine/charts';
|
||||
import {
|
||||
Center,
|
||||
Group,
|
||||
Paper,
|
||||
SegmentedControl,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
@ -12,7 +13,11 @@ import { ReactNode, useMemo, useState } from 'react';
|
||||
|
||||
import { CHART_COLORS } from '../../../components/charts/colors';
|
||||
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
|
||||
import { formatDecimal, formatPriceRange } from '../../../defaults/formatters';
|
||||
import {
|
||||
formatCurrency,
|
||||
formatDecimal,
|
||||
formatPriceRange
|
||||
} from '../../../defaults/formatters';
|
||||
import { ApiEndpoints } from '../../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { useTable } from '../../../hooks/UseTable';
|
||||
@ -22,6 +27,30 @@ import { DateColumn, PartColumn } from '../../../tables/ColumnRenderers';
|
||||
import { InvenTreeTable } from '../../../tables/InvenTreeTable';
|
||||
import { LoadingPricingData, NoPricingData } from './PricingPanel';
|
||||
|
||||
/*
|
||||
* Render a tooltip for the chart, with correct date information
|
||||
*/
|
||||
function ChartTooltip({ label, payload }: ChartTooltipProps) {
|
||||
if (!payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = payload[0] ?? {};
|
||||
|
||||
return (
|
||||
<Paper px="md" py="sm" withBorder shadow="md" radius="md">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text key="title" c={data.payload?.color}>
|
||||
{data.name}
|
||||
</Text>
|
||||
<Text key="price" fz="sm">
|
||||
{formatCurrency(data.payload?.value)}
|
||||
</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
// Display BOM data as a pie chart
|
||||
function BomPieChart({
|
||||
data,
|
||||
@ -57,6 +86,11 @@ function BomPieChart({
|
||||
tooltipDataSource="segment"
|
||||
chartLabel={t`Total Price`}
|
||||
valueFormatter={(value) => tooltipFormatter(value, currency)}
|
||||
tooltipProps={{
|
||||
content: ({ label, payload }) => (
|
||||
<ChartTooltip label={label} payload={payload} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
);
|
||||
@ -78,10 +112,15 @@ function BomBarChart({
|
||||
xAxisLabel={t`Component`}
|
||||
yAxisLabel={t`Price Range`}
|
||||
series={[
|
||||
{ name: 'total_price_min', label: t`Minimum Price`, color: 'blue.6' },
|
||||
{ name: 'total_price_min', label: t`Minimum Price`, color: 'yellow.6' },
|
||||
{ name: 'total_price_max', label: t`Maximum Price`, color: 'teal.6' }
|
||||
]}
|
||||
valueFormatter={(value) => tooltipFormatter(value, currency)}
|
||||
tooltipProps={{
|
||||
content: ({ label, payload }) => (
|
||||
<ChartTooltip label={label} payload={payload} />
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -93,7 +132,7 @@ export default function BomPricingPanel({
|
||||
readonly part: any;
|
||||
readonly pricing: any;
|
||||
}): ReactNode {
|
||||
const table = useTable('pricing-bom');
|
||||
const table = useTable('pricingbom');
|
||||
|
||||
const columns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
|
@ -34,7 +34,7 @@ export default function PriceBreakPanel({
|
||||
endpoint: ApiEndpoints;
|
||||
}>) {
|
||||
const user = useUserState();
|
||||
const table = useTable('pricing-internal');
|
||||
const table = useTable('pricinginternal');
|
||||
|
||||
const priceBreakFields: ApiFormFieldSet = useMemo(() => {
|
||||
return {
|
||||
|
@ -9,20 +9,33 @@ import {
|
||||
Stack,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import {
|
||||
IconBuildingWarehouse,
|
||||
IconChartDonut,
|
||||
IconCircleCheck,
|
||||
IconExclamationCircle,
|
||||
IconList,
|
||||
IconReportAnalytics,
|
||||
IconShoppingCart,
|
||||
IconTriangleSquareCircle
|
||||
} from '@tabler/icons-react';
|
||||
import { UseQueryResult } from '@tanstack/react-query';
|
||||
import { toggleUnorderedList } from 'easymde';
|
||||
import { DataTable } from 'mantine-datatable';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
|
||||
import { api } from '../../../App';
|
||||
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
|
||||
import {
|
||||
EditItemAction,
|
||||
OptionsActionDropdown
|
||||
} from '../../../components/items/ActionDropdown';
|
||||
import { formatCurrency, formatDate } from '../../../defaults/formatters';
|
||||
import { ApiEndpoints } from '../../../enums/ApiEndpoints';
|
||||
import { InvenTreeIcon } from '../../../functions/icons';
|
||||
import { useEditApiFormModal } from '../../../hooks/UseForm';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { panelOptions } from '../PartPricingPanel';
|
||||
|
||||
interface PricingOverviewEntry {
|
||||
@ -38,12 +51,73 @@ interface PricingOverviewEntry {
|
||||
export default function PricingOverviewPanel({
|
||||
part,
|
||||
pricing,
|
||||
pricingQuery,
|
||||
doNavigation
|
||||
}: Readonly<{
|
||||
part: any;
|
||||
pricing: any;
|
||||
pricingQuery: UseQueryResult;
|
||||
doNavigation: (panel: panelOptions) => void;
|
||||
}>): ReactNode {
|
||||
const refreshPricing = useCallback(() => {
|
||||
const url = apiUrl(ApiEndpoints.part_pricing, part.pk);
|
||||
|
||||
notifications.hide('pricing-refresh');
|
||||
|
||||
notifications.show({
|
||||
message: t`Refreshing pricing data`,
|
||||
color: 'green',
|
||||
id: 'pricing-refresh',
|
||||
loading: true,
|
||||
autoClose: false
|
||||
});
|
||||
|
||||
let success: boolean = false;
|
||||
|
||||
api
|
||||
.patch(url, { update: true })
|
||||
.then((response) => {
|
||||
success = response.status === 200;
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
notifications.hide('pricing-refresh');
|
||||
|
||||
if (success) {
|
||||
notifications.show({
|
||||
message: t`Pricing data updated`,
|
||||
color: 'green',
|
||||
icon: <IconCircleCheck />
|
||||
});
|
||||
pricingQuery.refetch();
|
||||
} else {
|
||||
notifications.show({
|
||||
message: t`Failed to update pricing data`,
|
||||
color: 'red',
|
||||
icon: <IconExclamationCircle />
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [part]);
|
||||
|
||||
const editPricing = useEditApiFormModal({
|
||||
title: t`Edit Pricing`,
|
||||
url: apiUrl(ApiEndpoints.part_pricing, part.pk),
|
||||
fields: {
|
||||
override_min: {},
|
||||
override_min_currency: {},
|
||||
override_max: {},
|
||||
override_max_currency: {},
|
||||
update: {
|
||||
hidden: true,
|
||||
value: true
|
||||
}
|
||||
},
|
||||
onFormSuccess: () => {
|
||||
pricingQuery.refetch();
|
||||
}
|
||||
});
|
||||
|
||||
const columns: any[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -163,38 +237,70 @@ export default function PricingOverviewPanel({
|
||||
});
|
||||
}, [part, pricing]);
|
||||
|
||||
// TODO: Add display of "last updated"
|
||||
// TODO: Add "update now" button
|
||||
|
||||
return (
|
||||
<Stack gap="xs">
|
||||
<SimpleGrid cols={2}>
|
||||
<Stack gap="xs">
|
||||
{pricing?.updated && (
|
||||
<>
|
||||
{editPricing.modal}
|
||||
<Stack gap="xs">
|
||||
<SimpleGrid cols={2}>
|
||||
<Stack gap="xs">
|
||||
<Paper p="xs">
|
||||
<Alert color="blue" title={t`Last Updated`}>
|
||||
<Text>{formatDate(pricing.updated)}</Text>
|
||||
</Alert>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
{pricing?.updated ? (
|
||||
<Alert color="blue" title={t`Last Updated`} flex={1}>
|
||||
<Text>{formatDate(pricing.updated)}</Text>
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert color="orange" title={t`Pricing Not Set`} flex={1}>
|
||||
<Text>{t`Pricing data has not been calculated for this part`}</Text>
|
||||
</Alert>
|
||||
)}
|
||||
<OptionsActionDropdown
|
||||
tooltip={t`Pricing Actions`}
|
||||
actions={[
|
||||
{
|
||||
name: t`Refresh`,
|
||||
tooltip: t`Refresh pricing data`,
|
||||
icon: (
|
||||
<InvenTreeIcon
|
||||
icon="refresh"
|
||||
iconProps={{ color: 'green' }}
|
||||
/>
|
||||
),
|
||||
onClick: () => {
|
||||
refreshPricing();
|
||||
}
|
||||
},
|
||||
EditItemAction({
|
||||
onClick: () => {
|
||||
editPricing.open();
|
||||
},
|
||||
tooltip: t`Edit pricing data`
|
||||
})
|
||||
]}
|
||||
/>
|
||||
</Group>
|
||||
</Paper>
|
||||
)}
|
||||
<DataTable
|
||||
idAccessor="name"
|
||||
records={overviewData}
|
||||
columns={columns}
|
||||
<DataTable
|
||||
idAccessor="name"
|
||||
records={overviewData}
|
||||
columns={columns}
|
||||
/>
|
||||
</Stack>
|
||||
<BarChart
|
||||
aria-label="pricing-overview-chart"
|
||||
dataKey="title"
|
||||
data={overviewData}
|
||||
title={t`Pricing Overview`}
|
||||
series={[
|
||||
{ name: 'min_value', label: t`Minimum Value`, color: 'blue.6' },
|
||||
{ name: 'max_value', label: t`Maximum Value`, color: 'teal.6' }
|
||||
]}
|
||||
valueFormatter={(value) =>
|
||||
tooltipFormatter(value, pricing?.currency)
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<BarChart
|
||||
aria-label="pricing-overview-chart"
|
||||
dataKey="title"
|
||||
data={overviewData}
|
||||
title={t`Pricing Overview`}
|
||||
series={[
|
||||
{ name: 'min_value', label: t`Minimum Value`, color: 'blue.6' },
|
||||
{ name: 'max_value', label: t`Maximum Value`, color: 'teal.6' }
|
||||
]}
|
||||
valueFormatter={(value) => tooltipFormatter(value, pricing?.currency)}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ export default function PurchaseHistoryPanel({
|
||||
}: Readonly<{
|
||||
part: any;
|
||||
}>): ReactNode {
|
||||
const table = useTable('pricing-purchase-history');
|
||||
const table = useTable('pricingpurchasehistory');
|
||||
|
||||
const calculateUnitPrice = useCallback((record: any) => {
|
||||
let pack_quantity = record?.supplier_part_detail?.pack_quantity_native ?? 1;
|
||||
|
@ -15,7 +15,7 @@ import { NoPricingData } from './PricingPanel';
|
||||
export default function SaleHistoryPanel({
|
||||
part
|
||||
}: Readonly<{ part: any }>): ReactNode {
|
||||
const table = useTable('pricing-sale-history');
|
||||
const table = useTable('pricingsalehistory');
|
||||
|
||||
const columns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
|
@ -18,7 +18,7 @@ import { NoPricingData } from './PricingPanel';
|
||||
export default function SupplierPricingPanel({
|
||||
part
|
||||
}: Readonly<{ part: any }>) {
|
||||
const table = useTable('pricing-supplier');
|
||||
const table = useTable('pricingsupplier');
|
||||
|
||||
const columns: TableColumn[] = useMemo(() => {
|
||||
return SupplierPriceBreakColumns();
|
||||
|
@ -21,7 +21,7 @@ export default function VariantPricingPanel({
|
||||
part: any;
|
||||
pricing: any;
|
||||
}>): ReactNode {
|
||||
const table = useTable('pricing-variants');
|
||||
const table = useTable('pricingvariants');
|
||||
|
||||
const columns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
|
@ -79,7 +79,7 @@ export const useLocalState = create<LocalStateProps>()(
|
||||
// tables
|
||||
tableColumnNames: {},
|
||||
getTableColumnNames: (tableKey) => {
|
||||
return get().tableColumnNames[tableKey] || {};
|
||||
return get().tableColumnNames[tableKey] || null;
|
||||
},
|
||||
setTableColumnNames: (tableKey) => (names) => {
|
||||
// Update the table column names for the given table
|
||||
|
@ -179,10 +179,17 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
||||
queryKey: ['options', url, tableState.tableKey, props.enableColumnCaching],
|
||||
retry: 3,
|
||||
refetchOnMount: true,
|
||||
gcTime: 5000,
|
||||
queryFn: async () => {
|
||||
if (props.enableColumnCaching == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we already have field names, no need to fetch them again
|
||||
if (fieldNames && Object.keys(fieldNames).length > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return api
|
||||
.options(url, {
|
||||
params: tableProps.params
|
||||
@ -203,7 +210,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
||||
}
|
||||
});
|
||||
|
||||
const cacheKey = tableState.tableKey.split('-')[0];
|
||||
const cacheKey = tableState.tableKey.replaceAll('-', '');
|
||||
|
||||
setFieldNames(names);
|
||||
setTableColumnNames(cacheKey)(names);
|
||||
@ -220,20 +227,19 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheKey = tableState.tableKey.split('-')[0];
|
||||
const cacheKey = tableState.tableKey.replaceAll('-', '');
|
||||
|
||||
// First check the local cache
|
||||
const cachedNames = getTableColumnNames(cacheKey);
|
||||
|
||||
if (Object.keys(cachedNames).length > 0) {
|
||||
if (cachedNames != null) {
|
||||
// Cached names are available - use them!
|
||||
setFieldNames(cachedNames);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, fetch the data from the API
|
||||
tableOptionQuery.refetch();
|
||||
}, [url, tableState.tableKey, props.params, props.enableColumnCaching]);
|
||||
}, [url, props.params, props.enableColumnCaching]);
|
||||
|
||||
// Build table properties based on provided props (and default props)
|
||||
const tableProps: InvenTreeTableProps<T> = useMemo(() => {
|
||||
@ -502,6 +508,8 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
||||
|
||||
const { data, isFetching, isLoading, refetch } = useQuery({
|
||||
queryKey: [
|
||||
'tabledata',
|
||||
url,
|
||||
tableState.page,
|
||||
props.params,
|
||||
sortStatus.columnAccessor,
|
||||
|
Loading…
x
Reference in New Issue
Block a user