[PUI] Pricing UX improvements (#7053)

* Only render categories in overview if there is data
Red #7025

* add option to disable accordions

* remove unneeded log

* make optional

* add disabled state to panels

* add missing panels to overview

* use enum for refs

* add quickjump anchors

* use navigation function instaed

* make links more distinguishable

* fix type

* format ticks using currency

* add tooltip formatter
This commit is contained in:
Matthias Mair 2024-04-17 01:52:14 +02:00 committed by GitHub
parent 5bf246c478
commit 9435a4c3fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 274 additions and 52 deletions

View File

@ -0,0 +1,9 @@
import { formatCurrency } from '../../defaults/formatters';
export function tooltipFormatter(label: any, currency: string) {
return (
formatCurrency(label, {
currency: currency
})?.toString() ?? ''
);
}

View File

@ -1,6 +1,6 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Accordion, Alert, LoadingOverlay, Stack, Text } from '@mantine/core'; import { Accordion, Alert, LoadingOverlay, Stack, Text } from '@mantine/core';
import { ReactNode, useMemo } from 'react'; import { useMemo, useState } from 'react';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
@ -15,6 +15,19 @@ import SaleHistoryPanel from './pricing/SaleHistoryPanel';
import SupplierPricingPanel from './pricing/SupplierPricingPanel'; import SupplierPricingPanel from './pricing/SupplierPricingPanel';
import VariantPricingPanel from './pricing/VariantPricingPanel'; import VariantPricingPanel from './pricing/VariantPricingPanel';
export enum panelOptions {
overview = 'overview',
purchase = 'purchase',
internal = 'internal',
supplier = 'supplier',
bom = 'bom',
variant = 'variant',
sale_pricing = 'sale-pricing',
sale_history = 'sale-history',
override = 'override',
overall = 'overall'
}
export default function PartPricingPanel({ part }: { part: any }) { export default function PartPricingPanel({ part }: { part: any }) {
const user = useUserState(); const user = useUserState();
@ -40,6 +53,17 @@ export default function PartPricingPanel({ part }: { part: any }) {
return user.hasViewRole(UserRoles.sales_order) && part?.salable; return user.hasViewRole(UserRoles.sales_order) && part?.salable;
}, [user, part]); }, [user, part]);
const [value, setValue] = useState<string[]>([panelOptions.overview]);
function doNavigation(panel: panelOptions) {
if (!value.includes(panel)) {
setValue([...value, panel]);
}
const element = document.getElementById(panel);
if (element) {
element.scrollIntoView();
}
}
return ( return (
<Stack spacing="xs"> <Stack spacing="xs">
<LoadingOverlay visible={instanceQuery.isLoading} /> <LoadingOverlay visible={instanceQuery.isLoading} />
@ -49,18 +73,27 @@ export default function PartPricingPanel({ part }: { part: any }) {
</Alert> </Alert>
)} )}
{pricing && ( {pricing && (
<Accordion multiple defaultValue={['overview']}> <Accordion multiple value={value} onChange={setValue}>
<PricingPanel <PricingPanel
content={<PricingOverviewPanel part={part} pricing={pricing} />} content={
label="overview" <PricingOverviewPanel
part={part}
pricing={pricing}
doNavigation={doNavigation}
/>
}
label={panelOptions.overview}
title={t`Pricing Overview`} title={t`Pricing Overview`}
visible={true} visible={true}
/> />
<PricingPanel <PricingPanel
content={<PurchaseHistoryPanel part={part} />} content={<PurchaseHistoryPanel part={part} />}
label="purchase" label={panelOptions.purchase}
title={t`Purchase History`} title={t`Purchase History`}
visible={purchaseOrderPricing} visible={purchaseOrderPricing}
disabled={
!pricing?.purchase_cost_min || !pricing?.purchase_cost_max
}
/> />
<PricingPanel <PricingPanel
content={ content={
@ -69,27 +102,35 @@ export default function PartPricingPanel({ part }: { part: any }) {
endpoint={ApiEndpoints.part_pricing_internal} endpoint={ApiEndpoints.part_pricing_internal}
/> />
} }
label="internal" label={panelOptions.internal}
title={t`Internal Pricing`} title={t`Internal Pricing`}
visible={internalPricing} visible={internalPricing}
disabled={
!pricing?.internal_cost_min || !pricing?.internal_cost_max
}
/> />
<PricingPanel <PricingPanel
content={<SupplierPricingPanel part={part} />} content={<SupplierPricingPanel part={part} />}
label="supplier" label={panelOptions.supplier}
title={t`Supplier Pricing`} title={t`Supplier Pricing`}
visible={purchaseOrderPricing} visible={purchaseOrderPricing}
disabled={
!pricing?.supplier_price_min || !pricing?.supplier_price_max
}
/> />
<PricingPanel <PricingPanel
content={<BomPricingPanel part={part} pricing={pricing} />} content={<BomPricingPanel part={part} pricing={pricing} />}
label="bom" label={panelOptions.bom}
title={t`BOM Pricing`} title={t`BOM Pricing`}
visible={part?.assembly} visible={part?.assembly}
disabled={!pricing?.bom_cost_min || !pricing?.bom_cost_max}
/> />
<PricingPanel <PricingPanel
content={<VariantPricingPanel part={part} pricing={pricing} />} content={<VariantPricingPanel part={part} pricing={pricing} />}
label="variant" label={panelOptions.variant}
title={t`Variant Pricing`} title={t`Variant Pricing`}
visible={part?.is_template} visible={part?.is_template}
disabled={!pricing?.variant_cost_min || !pricing?.variant_cost_max}
/> />
<PricingPanel <PricingPanel
content={ content={
@ -98,15 +139,17 @@ export default function PartPricingPanel({ part }: { part: any }) {
endpoint={ApiEndpoints.part_pricing_sale} endpoint={ApiEndpoints.part_pricing_sale}
/> />
} }
label="sale-pricing" label={panelOptions.sale_pricing}
title={t`Sale Pricing`} title={t`Sale Pricing`}
visible={salesOrderPricing} visible={salesOrderPricing}
disabled={!pricing?.sale_price_min || !pricing?.sale_price_max}
/> />
<PricingPanel <PricingPanel
content={<SaleHistoryPanel part={part} />} content={<SaleHistoryPanel part={part} />}
label="sale-history" label={panelOptions.sale_history}
title={t`Sale History`} title={t`Sale History`}
visible={salesOrderPricing} visible={salesOrderPricing}
disabled={!pricing?.sale_history_min || !pricing?.sale_history_max}
/> />
</Accordion> </Accordion>
)} )}

View File

@ -21,7 +21,12 @@ import {
} from 'recharts'; } from 'recharts';
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { formatDecimal, formatPriceRange } from '../../../defaults/formatters'; import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
import {
formatCurrency,
formatDecimal,
formatPriceRange
} from '../../../defaults/formatters';
import { ApiEndpoints } from '../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { ModelType } from '../../../enums/ModelType'; import { ModelType } from '../../../enums/ModelType';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
@ -32,7 +37,7 @@ import { InvenTreeTable } from '../../../tables/InvenTreeTable';
import { NoPricingData } from './PricingPanel'; import { NoPricingData } from './PricingPanel';
// Display BOM data as a pie chart // Display BOM data as a pie chart
function BomPieChart({ data }: { data: any[] }) { function BomPieChart({ data, currency }: { data: any[]; currency: string }) {
return ( return (
<ResponsiveContainer width="100%" height={500}> <ResponsiveContainer width="100%" height={500}>
<PieChart> <PieChart>
@ -64,20 +69,30 @@ function BomPieChart({ data }: { data: any[] }) {
/> />
))} ))}
</Pie> </Pie>
<Tooltip /> <Tooltip
formatter={(label, payload) => tooltipFormatter(label, currency)}
/>
</PieChart> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>
); );
} }
// Display BOM data as a bar chart // Display BOM data as a bar chart
function BomBarChart({ data }: { data: any[] }) { function BomBarChart({ data, currency }: { data: any[]; currency: string }) {
return ( return (
<ResponsiveContainer width="100%" height={500}> <ResponsiveContainer width="100%" height={500}>
<BarChart data={data}> <BarChart data={data}>
<XAxis dataKey="name" /> <XAxis dataKey="name" />
<YAxis /> <YAxis
<Tooltip /> tickFormatter={(value, index) =>
formatCurrency(value, {
currency: currency
})?.toString() ?? ''
}
/>
<Tooltip
formatter={(label, payload) => tooltipFormatter(label, currency)}
/>
<Legend /> <Legend />
<Bar <Bar
dataKey="total_price_min" dataKey="total_price_min"
@ -202,8 +217,12 @@ export default function BomPricingPanel({
/> />
{bomPricingData.length > 0 ? ( {bomPricingData.length > 0 ? (
<Stack spacing="xs"> <Stack spacing="xs">
{chartType == 'bar' && <BomBarChart data={bomPricingData} />} {chartType == 'bar' && (
{chartType == 'pie' && <BomPieChart data={bomPricingData} />} <BomBarChart data={bomPricingData} currency={pricing?.currency} />
)}
{chartType == 'pie' && (
<BomPieChart data={bomPricingData} currency={pricing?.currency} />
)}
<SegmentedControl <SegmentedControl
value={chartType} value={chartType}
onChange={setChartType} onChange={setChartType}

View File

@ -1,5 +1,5 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Alert, SimpleGrid } from '@mantine/core'; import { SimpleGrid } from '@mantine/core';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { import {
Bar, Bar,
@ -13,6 +13,7 @@ import {
import { AddItemButton } from '../../../components/buttons/AddItemButton'; import { AddItemButton } from '../../../components/buttons/AddItemButton';
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
import { ApiFormFieldSet } from '../../../components/forms/fields/ApiFormField'; import { ApiFormFieldSet } from '../../../components/forms/fields/ApiFormField';
import { formatCurrency } from '../../../defaults/formatters'; import { formatCurrency } from '../../../defaults/formatters';
import { ApiEndpoints } from '../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../enums/ApiEndpoints';
@ -144,6 +145,13 @@ export default function PriceBreakPanel({
[user] [user]
); );
const currency: string = useMemo(() => {
if (table.records.length === 0) {
return '';
}
return table.records[0].currency;
}, [table.records]);
return ( return (
<> <>
{newPriceBreak.modal} {newPriceBreak.modal}
@ -166,8 +174,18 @@ export default function PriceBreakPanel({
<ResponsiveContainer width="100%" height={500}> <ResponsiveContainer width="100%" height={500}>
<BarChart data={table.records}> <BarChart data={table.records}>
<XAxis dataKey="quantity" /> <XAxis dataKey="quantity" />
<YAxis /> <YAxis
<Tooltip /> tickFormatter={(value, index) =>
formatCurrency(value, {
currency: currency
})?.toString() ?? ''
}
/>
<Tooltip
formatter={(label, payload) =>
tooltipFormatter(label, currency)
}
/>
<Legend /> <Legend />
<Bar <Bar
dataKey="price" dataKey="price"

View File

@ -1,5 +1,13 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Alert, Group, Paper, SimpleGrid, Stack, Text } from '@mantine/core'; import {
Alert,
Anchor,
Group,
Paper,
SimpleGrid,
Stack,
Text
} from '@mantine/core';
import { import {
IconBuildingWarehouse, IconBuildingWarehouse,
IconChartDonut, IconChartDonut,
@ -22,11 +30,13 @@ import {
} from 'recharts'; } from 'recharts';
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
import { formatCurrency, renderDate } from '../../../defaults/formatters'; import { formatCurrency, renderDate } from '../../../defaults/formatters';
import { panelOptions } from '../PartPricingPanel';
interface PricingOverviewEntry { interface PricingOverviewEntry {
icon: ReactNode; icon: ReactNode;
name: string; name: panelOptions;
title: string; title: string;
min_value: number | null | undefined; min_value: number | null | undefined;
max_value: number | null | undefined; max_value: number | null | undefined;
@ -36,10 +46,12 @@ interface PricingOverviewEntry {
export default function PricingOverviewPanel({ export default function PricingOverviewPanel({
part, part,
pricing pricing,
doNavigation
}: { }: {
part: any; part: any;
pricing: any; pricing: any;
doNavigation: (panel: panelOptions) => void;
}): ReactNode { }): ReactNode {
const columns: DataTableColumn<any>[] = useMemo(() => { const columns: DataTableColumn<any>[] = useMemo(() => {
return [ return [
@ -47,10 +59,17 @@ export default function PricingOverviewPanel({
accessor: 'title', accessor: 'title',
title: t`Pricing Category`, title: t`Pricing Category`,
render: (record: PricingOverviewEntry) => { render: (record: PricingOverviewEntry) => {
const is_link = record.name !== panelOptions.overall;
return ( return (
<Group position="left" spacing="xs"> <Group position="left" spacing="xs">
{record.icon} {record.icon}
{is_link ? (
<Anchor weight={700} onClick={() => doNavigation(record.name)}>
{record.title}
</Anchor>
) : (
<Text weight={700}>{record.title}</Text> <Text weight={700}>{record.title}</Text>
)}
</Group> </Group>
); );
} }
@ -86,56 +105,70 @@ export default function PricingOverviewPanel({
const overviewData: PricingOverviewEntry[] = useMemo(() => { const overviewData: PricingOverviewEntry[] = useMemo(() => {
return [ return [
{ {
name: 'internal', name: panelOptions.internal,
title: t`Internal Pricing`, title: t`Internal Pricing`,
icon: <IconList />, icon: <IconList />,
min_value: pricing?.internal_cost_min, min_value: pricing?.internal_cost_min,
max_value: pricing?.internal_cost_max max_value: pricing?.internal_cost_max
}, },
{ {
name: 'bom', name: panelOptions.bom,
title: t`BOM Pricing`, title: t`BOM Pricing`,
icon: <IconChartDonut />, icon: <IconChartDonut />,
min_value: pricing?.bom_cost_min, min_value: pricing?.bom_cost_min,
max_value: pricing?.bom_cost_max max_value: pricing?.bom_cost_max
}, },
{ {
name: 'purchase', name: panelOptions.purchase,
title: t`Purchase Pricing`, title: t`Purchase Pricing`,
icon: <IconShoppingCart />, icon: <IconShoppingCart />,
min_value: pricing?.purchase_cost_min, min_value: pricing?.purchase_cost_min,
max_value: pricing?.purchase_cost_max max_value: pricing?.purchase_cost_max
}, },
{ {
name: 'supplier', name: panelOptions.supplier,
title: t`Supplier Pricing`, title: t`Supplier Pricing`,
icon: <IconBuildingWarehouse />, icon: <IconBuildingWarehouse />,
min_value: pricing?.supplier_price_min, min_value: pricing?.supplier_price_min,
max_value: pricing?.supplier_price_max max_value: pricing?.supplier_price_max
}, },
{ {
name: 'variants', name: panelOptions.variant,
title: t`Variant Pricing`, title: t`Variant Pricing`,
icon: <IconTriangleSquareCircle />, icon: <IconTriangleSquareCircle />,
min_value: pricing?.variant_cost_min, min_value: pricing?.variant_cost_min,
max_value: pricing?.variant_cost_max max_value: pricing?.variant_cost_max
}, },
{ {
name: 'override', name: panelOptions.sale_pricing,
title: t`Sale Pricing`,
icon: <IconTriangleSquareCircle />,
min_value: pricing?.sale_price_min,
max_value: pricing?.sale_price_max
},
{
name: panelOptions.sale_history,
title: t`Sale History`,
icon: <IconTriangleSquareCircle />,
min_value: pricing?.sale_history_min,
max_value: pricing?.sale_history_max
},
{
name: panelOptions.override,
title: t`Override Pricing`, title: t`Override Pricing`,
icon: <IconExclamationCircle />, icon: <IconExclamationCircle />,
min_value: pricing?.override_min, min_value: pricing?.override_min,
max_value: pricing?.override_max max_value: pricing?.override_max
}, },
{ {
name: 'overall', name: panelOptions.overall,
title: t`Overall Pricing`, title: t`Overall Pricing`,
icon: <IconReportAnalytics />, icon: <IconReportAnalytics />,
min_value: pricing?.overall_min, min_value: pricing?.overall_min,
max_value: pricing?.overall_max max_value: pricing?.overall_max
} }
].filter((entry) => { ].filter((entry) => {
return entry.min_value !== null || entry.max_value !== null; return !(entry.min_value == null || entry.max_value == null);
}); });
}, [part, pricing]); }, [part, pricing]);
@ -158,8 +191,18 @@ export default function PricingOverviewPanel({
<ResponsiveContainer width="100%" height={500}> <ResponsiveContainer width="100%" height={500}>
<BarChart data={overviewData}> <BarChart data={overviewData}>
<XAxis dataKey="title" /> <XAxis dataKey="title" />
<YAxis /> <YAxis
<Tooltip /> tickFormatter={(value, index) =>
formatCurrency(value, {
currency: pricing?.currency
})?.toString() ?? ''
}
/>
<Tooltip
formatter={(label, payload) =>
tooltipFormatter(label, pricing?.currency)
}
/>
<Legend /> <Legend />
<Bar <Bar
dataKey="min_value" dataKey="min_value"

View File

@ -1,28 +1,58 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Accordion, Alert, Space, Stack, Text } from '@mantine/core'; import {
import { IconExclamationCircle } from '@tabler/icons-react'; Accordion,
AccordionControlProps,
Alert,
Box,
Space,
Stack,
Text,
Tooltip
} from '@mantine/core';
import { IconAlertCircle, IconExclamationCircle } from '@tabler/icons-react';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { StylishText } from '../../../components/items/StylishText'; import { StylishText } from '../../../components/items/StylishText';
import { panelOptions } from '../PartPricingPanel';
function AccordionControl(props: AccordionControlProps) {
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{props.disabled && (
<Tooltip
label={t`No data available`}
children={<IconAlertCircle size="1rem" color="gray" />}
/>
)}
<Accordion.Control
{...props}
pl={props.disabled ? '0.25rem' : '1.25rem'}
/>
</Box>
);
}
export default function PricingPanel({ export default function PricingPanel({
content, content,
label, label,
title, title,
visible visible,
disabled = undefined
}: { }: {
content: ReactNode; content: ReactNode;
label: string; label: panelOptions;
title: string; title: string;
visible: boolean; visible: boolean;
disabled?: boolean | undefined;
}): ReactNode { }): ReactNode {
const is_disabled = disabled === undefined ? false : disabled;
return ( return (
visible && ( visible && (
<Accordion.Item value={label}> <Accordion.Item value={label} id={label}>
<Accordion.Control> <AccordionControl disabled={is_disabled}>
<StylishText size="lg">{title}</StylishText> <StylishText size="lg">{title}</StylishText>
</Accordion.Control> </AccordionControl>
<Accordion.Panel>{content}</Accordion.Panel> <Accordion.Panel>{!is_disabled && content}</Accordion.Panel>
</Accordion.Item> </Accordion.Item>
) )
); );

View File

@ -12,6 +12,7 @@ import {
} from 'recharts'; } from 'recharts';
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
import { formatCurrency, renderDate } from '../../../defaults/formatters'; import { formatCurrency, renderDate } from '../../../defaults/formatters';
import { ApiEndpoints } from '../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
@ -95,6 +96,13 @@ export default function PurchaseHistoryPanel({
]; ];
}, []); }, []);
const currency: string = useMemo(() => {
if (table.records.length === 0) {
return '';
}
return table.records[0].purchase_price_currency;
}, [table.records]);
const purchaseHistoryData = useMemo(() => { const purchaseHistoryData = useMemo(() => {
return table.records.map((record: any) => { return table.records.map((record: any) => {
return { return {
@ -126,8 +134,16 @@ export default function PurchaseHistoryPanel({
<ResponsiveContainer width="100%" height={500}> <ResponsiveContainer width="100%" height={500}>
<BarChart data={purchaseHistoryData}> <BarChart data={purchaseHistoryData}>
<XAxis dataKey="name" /> <XAxis dataKey="name" />
<YAxis /> <YAxis
<Tooltip /> tickFormatter={(value, index) =>
formatCurrency(value, {
currency: currency
})?.toString() ?? ''
}
/>
<Tooltip
formatter={(label, payload) => tooltipFormatter(label, currency)}
/>
<Legend /> <Legend />
<Bar <Bar
dataKey="unit_price" dataKey="unit_price"

View File

@ -12,6 +12,7 @@ import {
} from 'recharts'; } from 'recharts';
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
import { formatCurrency, renderDate } from '../../../defaults/formatters'; import { formatCurrency, renderDate } from '../../../defaults/formatters';
import { ApiEndpoints } from '../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
@ -60,6 +61,13 @@ export default function SaleHistoryPanel({ part }: { part: any }): ReactNode {
]; ];
}, []); }, []);
const currency: string = useMemo(() => {
if (table.records.length === 0) {
return '';
}
return table.records[0].sale_price_currency;
}, [table.records]);
const saleHistoryData = useMemo(() => { const saleHistoryData = useMemo(() => {
return table.records.map((record: any) => { return table.records.map((record: any) => {
return { return {
@ -90,8 +98,16 @@ export default function SaleHistoryPanel({ part }: { part: any }): ReactNode {
<ResponsiveContainer width="100%" height={500}> <ResponsiveContainer width="100%" height={500}>
<BarChart data={saleHistoryData}> <BarChart data={saleHistoryData}>
<XAxis dataKey="name" /> <XAxis dataKey="name" />
<YAxis /> <YAxis
<Tooltip /> tickFormatter={(value, index) =>
formatCurrency(value, {
currency: currency
})?.toString() ?? ''
}
/>
<Tooltip
formatter={(label, payload) => tooltipFormatter(label, currency)}
/>
<Legend /> <Legend />
<Bar <Bar
dataKey="sale_price" dataKey="sale_price"

View File

@ -11,6 +11,8 @@ import {
} from 'recharts'; } from 'recharts';
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
import { formatCurrency } from '../../../defaults/formatters';
import { ApiEndpoints } from '../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
@ -29,6 +31,13 @@ export default function SupplierPricingPanel({ part }: { part: any }) {
return SupplierPriceBreakColumns(); return SupplierPriceBreakColumns();
}, []); }, []);
const currency: string = useMemo(() => {
if (table.records.length === 0) {
return '';
}
return table.records[0].currency;
}, [table.records]);
const supplierPricingData = useMemo(() => { const supplierPricingData = useMemo(() => {
return table.records.map((record: any) => { return table.records.map((record: any) => {
return { return {
@ -58,8 +67,16 @@ export default function SupplierPricingPanel({ part }: { part: any }) {
<ResponsiveContainer width="100%" height={500}> <ResponsiveContainer width="100%" height={500}>
<BarChart data={supplierPricingData}> <BarChart data={supplierPricingData}>
<XAxis dataKey="name" /> <XAxis dataKey="name" />
<YAxis /> <YAxis
<Tooltip /> tickFormatter={(value, index) =>
formatCurrency(value, {
currency: currency
})?.toString() ?? ''
}
/>
<Tooltip
formatter={(label, payload) => tooltipFormatter(label, currency)}
/>
<Bar <Bar
dataKey="unit_price" dataKey="unit_price"
fill={CHART_COLORS[0]} fill={CHART_COLORS[0]}

View File

@ -12,6 +12,7 @@ import {
} from 'recharts'; } from 'recharts';
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { tooltipFormatter } from '../../../components/charts/tooltipFormatter';
import { formatCurrency } from '../../../defaults/formatters'; import { formatCurrency } from '../../../defaults/formatters';
import { ApiEndpoints } from '../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { ModelType } from '../../../enums/ModelType'; import { ModelType } from '../../../enums/ModelType';
@ -99,8 +100,18 @@ export default function VariantPricingPanel({
<ResponsiveContainer width="100%" height={500}> <ResponsiveContainer width="100%" height={500}>
<BarChart data={variantPricingData}> <BarChart data={variantPricingData}>
<XAxis dataKey="name" /> <XAxis dataKey="name" />
<YAxis /> <YAxis
<Tooltip /> tickFormatter={(value, index) =>
formatCurrency(value, {
currency: pricing?.currency
})?.toString() ?? ''
}
/>
<Tooltip
formatter={(label, payload) =>
tooltipFormatter(label, pricing?.currency)
}
/>
<Legend /> <Legend />
<Bar <Bar
dataKey="pmin" dataKey="pmin"