Tweak financial calculations

This commit is contained in:
2025-09-20 17:40:34 -04:00
parent 512b351429
commit 5d46a2a7e5
2 changed files with 193 additions and 131 deletions

View File

@@ -450,8 +450,9 @@ router.get('/financials', async (req, res) => {
grossSales: calculateComparison(totals.grossSales, previousTotals.grossSales), grossSales: calculateComparison(totals.grossSales, previousTotals.grossSales),
refunds: calculateComparison(totals.refunds, previousTotals.refunds), refunds: calculateComparison(totals.refunds, previousTotals.refunds),
taxCollected: calculateComparison(totals.taxCollected, previousTotals.taxCollected), taxCollected: calculateComparison(totals.taxCollected, previousTotals.taxCollected),
discounts: calculateComparison(totals.discounts, previousTotals.discounts),
cogs: calculateComparison(totals.cogs, previousTotals.cogs), cogs: calculateComparison(totals.cogs, previousTotals.cogs),
netRevenue: calculateComparison(totals.netRevenue, previousTotals.netRevenue), income: calculateComparison(totals.income, previousTotals.income),
profit: calculateComparison(totals.profit, previousTotals.profit), profit: calculateComparison(totals.profit, previousTotals.profit),
margin: calculateComparison(totals.margin, previousTotals.margin), margin: calculateComparison(totals.margin, previousTotals.margin),
}; };
@@ -706,10 +707,13 @@ function buildFinancialTotalsQuery(whereClause) {
SELECT SELECT
COALESCE(SUM(sale_amount), 0) as grossSales, COALESCE(SUM(sale_amount), 0) as grossSales,
COALESCE(SUM(refund_amount), 0) as refunds, COALESCE(SUM(refund_amount), 0) as refunds,
COALESCE(SUM(shipping_collected_amount + small_order_fee_amount + rush_fee_amount), 0) as shippingFees,
COALESCE(SUM(tax_collected_amount), 0) as taxCollected, COALESCE(SUM(tax_collected_amount), 0) as taxCollected,
COALESCE(SUM(discount_total_amount), 0) as discounts,
COALESCE(SUM(cogs_amount), 0) as cogs COALESCE(SUM(cogs_amount), 0) as cogs
FROM report_sales_data FROM report_sales_data
WHERE ${whereClause} WHERE ${whereClause}
AND action IN (1, 2, 3)
`; `;
} }
@@ -719,10 +723,13 @@ function buildFinancialTrendQuery(whereClause) {
DATE(date_change) as date, DATE(date_change) as date,
SUM(sale_amount) as grossSales, SUM(sale_amount) as grossSales,
SUM(refund_amount) as refunds, SUM(refund_amount) as refunds,
SUM(shipping_collected_amount + small_order_fee_amount + rush_fee_amount) as shippingFees,
SUM(tax_collected_amount) as taxCollected, SUM(tax_collected_amount) as taxCollected,
SUM(discount_total_amount) as discounts,
SUM(cogs_amount) as cogs SUM(cogs_amount) as cogs
FROM report_sales_data FROM report_sales_data
WHERE ${whereClause} WHERE ${whereClause}
AND action IN (1, 2, 3)
GROUP BY DATE(date_change) GROUP BY DATE(date_change)
ORDER BY date ASC ORDER BY date ASC
`; `;
@@ -731,20 +738,23 @@ function buildFinancialTrendQuery(whereClause) {
function normalizeFinancialTotals(row = {}) { function normalizeFinancialTotals(row = {}) {
const grossSales = parseFloat(row.grossSales || 0); const grossSales = parseFloat(row.grossSales || 0);
const refunds = parseFloat(row.refunds || 0); const refunds = parseFloat(row.refunds || 0);
const shippingFees = parseFloat(row.shippingFees || 0);
const taxCollected = parseFloat(row.taxCollected || 0); const taxCollected = parseFloat(row.taxCollected || 0);
const discounts = parseFloat(row.discounts || 0);
const cogs = parseFloat(row.cogs || 0); const cogs = parseFloat(row.cogs || 0);
const netSales = grossSales - refunds; const productNet = grossSales - refunds - discounts;
const netRevenue = netSales - taxCollected; const income = productNet + shippingFees;
const profit = netRevenue - cogs; const profit = income - cogs;
const margin = netRevenue !== 0 ? (profit / netRevenue) * 100 : 0; const margin = income !== 0 ? (profit / income) * 100 : 0;
return { return {
grossSales, grossSales,
refunds, refunds,
shippingFees,
taxCollected, taxCollected,
discounts,
cogs, cogs,
netSales, income,
netRevenue,
profit, profit,
margin, margin,
}; };
@@ -753,12 +763,14 @@ function normalizeFinancialTotals(row = {}) {
function normalizeFinancialTrendRow(row = {}) { function normalizeFinancialTrendRow(row = {}) {
const grossSales = parseFloat(row.grossSales || 0); const grossSales = parseFloat(row.grossSales || 0);
const refunds = parseFloat(row.refunds || 0); const refunds = parseFloat(row.refunds || 0);
const shippingFees = parseFloat(row.shippingFees || 0);
const taxCollected = parseFloat(row.taxCollected || 0); const taxCollected = parseFloat(row.taxCollected || 0);
const discounts = parseFloat(row.discounts || 0);
const cogs = parseFloat(row.cogs || 0); const cogs = parseFloat(row.cogs || 0);
const netSales = grossSales - refunds; const productNet = grossSales - refunds - discounts;
const netRevenue = netSales - taxCollected; const income = productNet + shippingFees;
const profit = netRevenue - cogs; const profit = income - cogs;
const margin = netRevenue !== 0 ? (profit / netRevenue) * 100 : 0; const margin = income !== 0 ? (profit / income) * 100 : 0;
let timestamp = null; let timestamp = null;
if (row.date instanceof Date) { if (row.date instanceof Date) {
@@ -771,10 +783,11 @@ function normalizeFinancialTrendRow(row = {}) {
date: row.date, date: row.date,
grossSales, grossSales,
refunds, refunds,
shippingFees,
taxCollected, taxCollected,
discounts,
cogs, cogs,
netSales, income,
netRevenue,
profit, profit,
margin, margin,
timestamp, timestamp,

View File

@@ -61,26 +61,28 @@ type ComparisonValue = {
}; };
type FinancialTotals = { type FinancialTotals = {
grossSales: number; grossSales?: number;
refunds: number; refunds?: number;
taxCollected: number; taxCollected?: number;
shippingFees?: number;
discounts?: number;
cogs: number; cogs: number;
netSales: number; income?: number;
netRevenue: number;
profit: number; profit: number;
margin: number; margin: number;
}; };
type FinancialTrendPoint = { type FinancialTrendPoint = {
date: string | Date | null; date: string | Date | null;
grossSales: number; income: number;
refunds: number;
taxCollected: number;
cogs: number; cogs: number;
netSales: number;
netRevenue: number;
profit: number; profit: number;
margin: number; margin: number;
grossSales?: number;
refunds?: number;
shippingFees?: number;
taxCollected?: number;
discounts?: number;
timestamp: string | null; timestamp: string | null;
}; };
@@ -88,8 +90,9 @@ type FinancialComparison = {
grossSales?: ComparisonValue; grossSales?: ComparisonValue;
refunds?: ComparisonValue; refunds?: ComparisonValue;
taxCollected?: ComparisonValue; taxCollected?: ComparisonValue;
discounts?: ComparisonValue;
cogs?: ComparisonValue; cogs?: ComparisonValue;
netRevenue?: ComparisonValue; income?: ComparisonValue;
profit?: ComparisonValue; profit?: ComparisonValue;
margin?: ComparisonValue; margin?: ComparisonValue;
[key: string]: ComparisonValue | undefined; [key: string]: ComparisonValue | undefined;
@@ -129,15 +132,14 @@ type YearPeriod = {
type CustomPeriod = MonthPeriod | QuarterPeriod | YearPeriod; type CustomPeriod = MonthPeriod | QuarterPeriod | YearPeriod;
type ChartSeriesKey = "grossSales" | "netRevenue" | "cogs" | "profit" | "margin"; type ChartSeriesKey = "income" | "cogs" | "profit" | "margin";
type GroupByOption = "day" | "month" | "quarter" | "year"; type GroupByOption = "day" | "month" | "quarter" | "year";
type ChartPoint = { type ChartPoint = {
label: string; label: string;
timestamp: string | null; timestamp: string | null;
grossSales: number | null; income: number | null;
netRevenue: number | null;
cogs: number | null; cogs: number | null;
profit: number | null; profit: number | null;
margin: number | null; margin: number | null;
@@ -146,18 +148,16 @@ type ChartPoint = {
}; };
const chartColors: Record<ChartSeriesKey, string> = { const chartColors: Record<ChartSeriesKey, string> = {
grossSales: "#7c3aed", income: "#2563eb",
netRevenue: "#6366f1",
cogs: "#f97316", cogs: "#f97316",
profit: "#10b981", profit: "#10b981",
margin: "#0ea5e9", margin: "#0ea5e9",
}; };
const SERIES_LABELS: Record<ChartSeriesKey, string> = { const SERIES_LABELS: Record<ChartSeriesKey, string> = {
grossSales: "Gross Sales", income: "Total Income",
netRevenue: "Net Revenue",
cogs: "COGS", cogs: "COGS",
profit: "Profit", profit: "Gross Profit",
margin: "Profit Margin", margin: "Profit Margin",
}; };
@@ -166,8 +166,7 @@ const SERIES_DEFINITIONS: Array<{
label: string; label: string;
type: "currency" | "percentage"; type: "currency" | "percentage";
}> = [ }> = [
{ key: "grossSales", label: SERIES_LABELS.grossSales, type: "currency" }, { key: "income", label: SERIES_LABELS.income, type: "currency" },
{ key: "netRevenue", label: SERIES_LABELS.netRevenue, type: "currency" },
{ key: "cogs", label: SERIES_LABELS.cogs, type: "currency" }, { key: "cogs", label: SERIES_LABELS.cogs, type: "currency" },
{ key: "profit", label: SERIES_LABELS.profit, type: "currency" }, { key: "profit", label: SERIES_LABELS.profit, type: "currency" },
{ key: "margin", label: SERIES_LABELS.margin, type: "percentage" }, { key: "margin", label: SERIES_LABELS.margin, type: "percentage" },
@@ -277,10 +276,12 @@ const formatPercentage = (value: number, digits = 1, suffix = "%") => {
type RawTrendPoint = { type RawTrendPoint = {
date: Date; date: Date;
grossSales: number; income: number;
netRevenue: number;
cogs: number; cogs: number;
profit: number; shippingFees: number;
taxCollected: number;
grossSales: number;
discounts: number;
}; };
type AggregatedTrendPoint = { type AggregatedTrendPoint = {
@@ -289,8 +290,7 @@ type AggregatedTrendPoint = {
tooltipLabel: string; tooltipLabel: string;
detailLabel: string; detailLabel: string;
timestamp: string; timestamp: string;
grossSales: number; income: number;
netRevenue: number;
cogs: number; cogs: number;
profit: number; profit: number;
margin: number; margin: number;
@@ -367,10 +367,8 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption):
key: string; key: string;
start: Date; start: Date;
end: Date; end: Date;
grossSales: number; income: number;
netRevenue: number;
cogs: number; cogs: number;
profit: number;
} }
>(); >();
@@ -382,10 +380,8 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption):
key, key,
start: point.date, start: point.date,
end: point.date, end: point.date,
grossSales: point.grossSales, income: point.income,
netRevenue: point.netRevenue,
cogs: point.cogs, cogs: point.cogs,
profit: point.profit,
}); });
return; return;
} }
@@ -397,18 +393,17 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption):
bucket.end = point.date; bucket.end = point.date;
} }
bucket.grossSales += point.grossSales; bucket.income += point.income;
bucket.netRevenue += point.netRevenue;
bucket.cogs += point.cogs; bucket.cogs += point.cogs;
bucket.profit += point.profit;
}); });
return Array.from(bucketMap.values()) return Array.from(bucketMap.values())
.sort((a, b) => a.start.getTime() - b.start.getTime()) .sort((a, b) => a.start.getTime() - b.start.getTime())
.map((bucket) => { .map((bucket) => {
const { axisLabel, tooltipLabel, detailLabel } = buildGroupLabels(bucket.start, bucket.end, groupBy); const { axisLabel, tooltipLabel, detailLabel } = buildGroupLabels(bucket.start, bucket.end, groupBy);
const { netRevenue, profit } = bucket; const income = bucket.income;
const margin = netRevenue !== 0 ? (profit / netRevenue) * 100 : 0; const profit = income - bucket.cogs;
const margin = income !== 0 ? (profit / income) * 100 : 0;
return { return {
id: bucket.key, id: bucket.key,
@@ -416,8 +411,7 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption):
tooltipLabel, tooltipLabel,
detailLabel, detailLabel,
timestamp: bucket.start.toISOString(), timestamp: bucket.start.toISOString(),
grossSales: bucket.grossSales, income,
netRevenue,
cogs: bucket.cogs, cogs: bucket.cogs,
profit, profit,
margin, margin,
@@ -516,8 +510,7 @@ const extendAggregatedTrendPoints = (
tooltipLabel, tooltipLabel,
detailLabel, detailLabel,
timestamp: bucketStart.toISOString(), timestamp: bucketStart.toISOString(),
grossSales: 0, income: 0,
netRevenue: 0,
cogs: 0, cogs: 0,
profit: 0, profit: 0,
margin: 0, margin: 0,
@@ -536,14 +529,21 @@ const monthsBetween = (start: Date, end: Date) => {
const buildTrendLabel = ( const buildTrendLabel = (
comparison?: ComparisonValue | null, comparison?: ComparisonValue | null,
options?: { isPercentage?: boolean } options?: { isPercentage?: boolean; invertDirection?: boolean }
): TrendSummary | null => { ): TrendSummary | null => {
if (!comparison || comparison.absolute === null) { if (!comparison || comparison.absolute === null) {
return null; return null;
} }
const { absolute, percentage } = comparison; const { absolute, percentage } = comparison;
const direction: TrendDirection = absolute > 0 ? "up" : absolute < 0 ? "down" : "flat"; const rawDirection: TrendDirection = absolute > 0 ? "up" : absolute < 0 ? "down" : "flat";
const direction: TrendDirection = options?.invertDirection
? rawDirection === "up"
? "down"
: rawDirection === "down"
? "up"
: "flat"
: rawDirection;
const absoluteValue = Math.abs(absolute); const absoluteValue = Math.abs(absolute);
if (options?.isPercentage) { if (options?.isPercentage) {
@@ -566,6 +566,39 @@ const buildTrendLabel = (
}; };
}; };
const safeNumeric = (value: number | null | undefined) =>
typeof value === "number" && Number.isFinite(value) ? value : 0;
const computeTotalIncome = (totals?: FinancialTotals | null) => {
if (!totals || typeof totals.income !== "number" || !Number.isFinite(totals.income)) {
return 0;
}
return totals.income;
};
const computeProfitFrom = (income: number, cogs?: number | null | undefined) => income - safeNumeric(cogs);
const computeMarginFrom = (profit: number, income: number) => (income !== 0 ? (profit / income) * 100 : 0);
const buildComparisonFromValues = (current?: number | null, previous?: number | null): ComparisonValue | null => {
if (current == null || previous == null) {
return null;
}
if (!Number.isFinite(current) || !Number.isFinite(previous)) {
return null;
}
const absolute = current - previous;
const percentage = previous !== 0 ? (absolute / Math.abs(previous)) * 100 : null;
return {
absolute,
percentage,
};
};
const generateYearOptions = (span: number) => { const generateYearOptions = (span: number) => {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
return Array.from({ length: span }, (_, index) => currentYear - index); return Array.from({ length: span }, (_, index) => currentYear - index);
@@ -638,9 +671,8 @@ const FinancialOverview = () => {
count: 1, count: 1,
}); });
const [metrics, setMetrics] = useState<Record<ChartSeriesKey, boolean>>({ const [metrics, setMetrics] = useState<Record<ChartSeriesKey, boolean>>({
grossSales: true, income: true,
netRevenue: true, cogs: true,
cogs: false,
profit: true, profit: true,
margin: true, margin: true,
}); });
@@ -724,12 +756,25 @@ const FinancialOverview = () => {
return null; return null;
} }
if (typeof point.income !== "number" || !Number.isFinite(point.income)) {
return null;
}
const grossSalesValue = toNumber((point as { grossSales?: number }).grossSales);
const refundsValue = toNumber((point as { refunds?: number }).refunds);
const shippingFeesValue = toNumber((point as { shippingFees?: number }).shippingFees);
const taxCollectedValue = toNumber((point as { taxCollected?: number }).taxCollected);
const discountsValue = toNumber((point as { discounts?: number }).discounts);
const incomeValue = point.income;
return { return {
date, date,
grossSales: toNumber(point.grossSales), income: incomeValue,
netRevenue: toNumber(point.netRevenue), shippingFees: shippingFeesValue,
taxCollected: taxCollectedValue,
grossSales: grossSalesValue,
discounts: discountsValue,
cogs: toNumber(point.cogs), cogs: toNumber(point.cogs),
profit: toNumber(point.profit),
}; };
}) })
.filter((value): value is RawTrendPoint => Boolean(value)) .filter((value): value is RawTrendPoint => Boolean(value))
@@ -848,45 +893,74 @@ const FinancialOverview = () => {
const totals = data.totals; const totals = data.totals;
const previous = data.previousTotals ?? undefined; const previous = data.previousTotals ?? undefined;
const comparison = data.comparison ?? {};
const safeCurrency = (value: number | undefined | null, digits = 0) => const safeCurrency = (value: number | undefined | null, digits = 0) =>
typeof value === "number" && Number.isFinite(value) ? formatCurrency(value, digits) : "—"; typeof value === "number" && Number.isFinite(value) ? formatCurrency(value, digits) : "—";
const safePercentage = (value: number | undefined | null, digits = 1) => const safePercentage = (value: number | undefined | null, digits = 1) =>
typeof value === "number" && Number.isFinite(value) ? formatPercentage(value, digits) : "—"; typeof value === "number" && Number.isFinite(value) ? formatPercentage(value, digits) : "—";
const comparison = data.comparison ?? {};
const totalIncome = computeTotalIncome(totals);
const previousIncome = previous ? computeTotalIncome(previous) : null;
const cogsValue = safeNumeric(totals.cogs);
const previousCogs = previous?.cogs != null ? safeNumeric(previous.cogs) : null;
const profitValue = Number.isFinite(totals.profit)
? safeNumeric(totals.profit)
: computeProfitFrom(totalIncome, totals.cogs);
const previousProfitValue = previousIncome != null
? Number.isFinite(previous?.profit)
? safeNumeric(previous?.profit)
: computeProfitFrom(previousIncome, previous?.cogs)
: null;
const marginValue = Number.isFinite(totals.margin)
? safeNumeric(totals.margin)
: computeMarginFrom(profitValue, totalIncome);
const previousMarginValue = previousIncome != null
? Number.isFinite(previous?.margin)
? safeNumeric(previous?.margin)
: computeMarginFrom(previousProfitValue ?? 0, previousIncome)
: null;
return [ return [
{ {
key: "grossSales", key: "income",
title: "Gross Sales", title: "Total Income",
value: safeCurrency(totals.grossSales, 0), value: safeCurrency(totalIncome, 0),
description: previous?.grossSales != null ? `Previous: ${safeCurrency(previous.grossSales, 0)}` : undefined, description: previousIncome != null ? `Previous: ${safeCurrency(previousIncome, 0)}` : undefined,
trend: buildTrendLabel(comparison.grossSales), trend: buildTrendLabel(comparison?.income ?? buildComparisonFromValues(totalIncome, previousIncome ?? null)),
accentClass: "text-emerald-600 dark:text-emerald-400",
},
{
key: "netRevenue",
title: "Net Revenue",
value: safeCurrency(totals.netRevenue, 0),
description: previous?.netRevenue != null ? `Previous: ${safeCurrency(previous.netRevenue, 0)}` : undefined,
trend: buildTrendLabel(comparison.netRevenue),
accentClass: "text-indigo-600 dark:text-indigo-400", accentClass: "text-indigo-600 dark:text-indigo-400",
}, },
{
key: "cogs",
title: "COGS",
value: safeCurrency(cogsValue, 0),
description: previousCogs != null ? `Previous: ${safeCurrency(previousCogs, 0)}` : undefined,
trend: buildTrendLabel(comparison?.cogs ?? buildComparisonFromValues(cogsValue, previousCogs ?? null), {
invertDirection: true,
}),
accentClass: "text-amber-600 dark:text-amber-400",
},
{ {
key: "profit", key: "profit",
title: "Profit", title: "Gross Profit",
value: safeCurrency(totals.profit, 0), value: safeCurrency(profitValue, 0),
description: previous?.profit != null ? `Previous: ${safeCurrency(previous.profit, 0)}` : undefined, description: previousProfitValue != null ? `Previous: ${safeCurrency(previousProfitValue, 0)}` : undefined,
trend: buildTrendLabel(comparison.profit), trend: buildTrendLabel(comparison?.profit ?? buildComparisonFromValues(profitValue, previousProfitValue ?? null)),
accentClass: "text-emerald-600 dark:text-emerald-400", accentClass: "text-emerald-600 dark:text-emerald-400",
}, },
{ {
key: "margin", key: "margin",
title: "Profit Margin", title: "Profit Margin",
value: safePercentage(totals.margin, 1), value: safePercentage(marginValue, 1),
description: previous?.margin != null ? `Previous: ${safePercentage(previous.margin, 1)}` : undefined, description: previousMarginValue != null ? `Previous: ${safePercentage(previousMarginValue, 1)}` : undefined,
trend: buildTrendLabel(comparison.margin, { isPercentage: true }), trend: buildTrendLabel(
comparison?.margin ?? buildComparisonFromValues(marginValue, previousMarginValue ?? null),
{ isPercentage: true }
),
accentClass: "text-sky-600 dark:text-sky-400", accentClass: "text-sky-600 dark:text-sky-400",
}, },
]; ];
@@ -907,8 +981,7 @@ const FinancialOverview = () => {
return aggregatedPoints.map((point) => ({ return aggregatedPoints.map((point) => ({
label: point.label, label: point.label,
timestamp: point.timestamp, timestamp: point.timestamp,
grossSales: point.isFuture ? null : point.grossSales, income: point.isFuture ? null : point.income,
netRevenue: point.isFuture ? null : point.netRevenue,
cogs: point.isFuture ? null : point.cogs, cogs: point.isFuture ? null : point.cogs,
profit: point.isFuture ? null : point.profit, profit: point.isFuture ? null : point.profit,
margin: point.isFuture ? null : point.margin, margin: point.isFuture ? null : point.margin,
@@ -977,8 +1050,7 @@ const FinancialOverview = () => {
id: point.id, id: point.id,
label: point.detailLabel, label: point.detailLabel,
timestamp: point.timestamp, timestamp: point.timestamp,
grossSales: point.grossSales, income: point.income,
netRevenue: point.netRevenue,
cogs: point.cogs, cogs: point.cogs,
profit: point.profit, profit: point.profit,
margin: point.margin, margin: point.margin,
@@ -1174,18 +1246,15 @@ const FinancialOverview = () => {
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Date</TableHead> <TableHead className="px-6 py-3 whitespace-nowrap text-center">Date</TableHead>
{metrics.grossSales && ( {metrics.income && (
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Gross Sales</TableHead> <TableHead className="px-6 py-3 whitespace-nowrap text-center">Total Income</TableHead>
)}
{metrics.netRevenue && (
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Net Revenue</TableHead>
)}
{metrics.profit && (
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Profit</TableHead>
)} )}
{metrics.cogs && ( {metrics.cogs && (
<TableHead className="px-6 py-3 whitespace-nowrap text-center">COGS</TableHead> <TableHead className="px-6 py-3 whitespace-nowrap text-center">COGS</TableHead>
)} )}
{metrics.profit && (
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Gross Profit</TableHead>
)}
{metrics.margin && ( {metrics.margin && (
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Margin</TableHead> <TableHead className="px-6 py-3 whitespace-nowrap text-center">Margin</TableHead>
)} )}
@@ -1197,19 +1266,9 @@ const FinancialOverview = () => {
<TableCell className="px-6 py-3 whitespace-nowrap text-center text-sm text-muted-foreground"> <TableCell className="px-6 py-3 whitespace-nowrap text-center text-sm text-muted-foreground">
{row.label || "—"} {row.label || "—"}
</TableCell> </TableCell>
{metrics.grossSales && ( {metrics.income && (
<TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium"> <TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium">
{row.isFuture ? "—" : formatCurrency(row.grossSales, 0)} {row.isFuture ? "—" : formatCurrency(row.income, 0)}
</TableCell>
)}
{metrics.netRevenue && (
<TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium">
{row.isFuture ? "—" : formatCurrency(row.netRevenue, 0)}
</TableCell>
)}
{metrics.profit && (
<TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium">
{row.isFuture ? "—" : formatCurrency(row.profit, 0)}
</TableCell> </TableCell>
)} )}
{metrics.cogs && ( {metrics.cogs && (
@@ -1217,6 +1276,11 @@ const FinancialOverview = () => {
{row.isFuture ? "—" : formatCurrency(row.cogs, 0)} {row.isFuture ? "—" : formatCurrency(row.cogs, 0)}
</TableCell> </TableCell>
)} )}
{metrics.profit && (
<TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium">
{row.isFuture ? "—" : formatCurrency(row.profit, 0)}
</TableCell>
)}
{metrics.margin && ( {metrics.margin && (
<TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium"> <TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium">
{row.isFuture ? "—" : formatPercentage(row.margin, 1)} {row.isFuture ? "—" : formatPercentage(row.margin, 1)}
@@ -1387,13 +1451,9 @@ const FinancialOverview = () => {
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<ComposedChart data={chartData} margin={{ top: 10, right: 30, left: -10, bottom: 0 }}> <ComposedChart data={chartData} margin={{ top: 10, right: 30, left: -10, bottom: 0 }}>
<defs> <defs>
<linearGradient id="financialGrossSales" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="financialIncome" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={chartColors.grossSales} stopOpacity={0.35} /> <stop offset="5%" stopColor={chartColors.income} stopOpacity={0.3} />
<stop offset="95%" stopColor={chartColors.grossSales} stopOpacity={0.05} /> <stop offset="95%" stopColor={chartColors.income} stopOpacity={0.05} />
</linearGradient>
<linearGradient id="financialNetRevenue" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={chartColors.netRevenue} stopOpacity={0.3} />
<stop offset="95%" stopColor={chartColors.netRevenue} stopOpacity={0.05} />
</linearGradient> </linearGradient>
<linearGradient id="financialCogs" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="financialCogs" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={chartColors.cogs} stopOpacity={0.25} /> <stop offset="5%" stopColor={chartColors.cogs} stopOpacity={0.25} />
@@ -1424,25 +1484,14 @@ const FinancialOverview = () => {
)} )}
<Tooltip content={<FinancialTooltip />} /> <Tooltip content={<FinancialTooltip />} />
<Legend formatter={(value: string) => SERIES_LABELS[value as ChartSeriesKey] ?? value} /> <Legend formatter={(value: string) => SERIES_LABELS[value as ChartSeriesKey] ?? value} />
{metrics.grossSales ? ( {metrics.income ? (
<Area <Area
yAxisId="left" yAxisId="left"
type="monotone" type="monotone"
dataKey="grossSales" dataKey="income"
name={SERIES_LABELS.grossSales} name={SERIES_LABELS.income}
stroke={chartColors.grossSales} stroke={chartColors.income}
fill="url(#financialGrossSales)" fill="url(#financialIncome)"
strokeWidth={2}
/>
) : null}
{metrics.netRevenue ? (
<Area
yAxisId="left"
type="monotone"
dataKey="netRevenue"
name={SERIES_LABELS.netRevenue}
stroke={chartColors.netRevenue}
fill="url(#financialNetRevenue)"
strokeWidth={2} strokeWidth={2}
/> />
) : null} ) : null}