mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[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:
parent
5bf246c478
commit
9435a4c3fd
9
src/frontend/src/components/charts/tooltipFormatter.tsx
Normal file
9
src/frontend/src/components/charts/tooltipFormatter.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { formatCurrency } from '../../defaults/formatters';
|
||||||
|
|
||||||
|
export function tooltipFormatter(label: any, currency: string) {
|
||||||
|
return (
|
||||||
|
formatCurrency(label, {
|
||||||
|
currency: currency
|
||||||
|
})?.toString() ?? ''
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
@ -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}
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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]}
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user