Tweak financial calculations
This commit is contained in:
@@ -450,8 +450,9 @@ router.get('/financials', async (req, res) => {
|
||||
grossSales: calculateComparison(totals.grossSales, previousTotals.grossSales),
|
||||
refunds: calculateComparison(totals.refunds, previousTotals.refunds),
|
||||
taxCollected: calculateComparison(totals.taxCollected, previousTotals.taxCollected),
|
||||
discounts: calculateComparison(totals.discounts, previousTotals.discounts),
|
||||
cogs: calculateComparison(totals.cogs, previousTotals.cogs),
|
||||
netRevenue: calculateComparison(totals.netRevenue, previousTotals.netRevenue),
|
||||
income: calculateComparison(totals.income, previousTotals.income),
|
||||
profit: calculateComparison(totals.profit, previousTotals.profit),
|
||||
margin: calculateComparison(totals.margin, previousTotals.margin),
|
||||
};
|
||||
@@ -706,10 +707,13 @@ function buildFinancialTotalsQuery(whereClause) {
|
||||
SELECT
|
||||
COALESCE(SUM(sale_amount), 0) as grossSales,
|
||||
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(discount_total_amount), 0) as discounts,
|
||||
COALESCE(SUM(cogs_amount), 0) as cogs
|
||||
FROM report_sales_data
|
||||
WHERE ${whereClause}
|
||||
AND action IN (1, 2, 3)
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -719,10 +723,13 @@ function buildFinancialTrendQuery(whereClause) {
|
||||
DATE(date_change) as date,
|
||||
SUM(sale_amount) as grossSales,
|
||||
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(discount_total_amount) as discounts,
|
||||
SUM(cogs_amount) as cogs
|
||||
FROM report_sales_data
|
||||
WHERE ${whereClause}
|
||||
AND action IN (1, 2, 3)
|
||||
GROUP BY DATE(date_change)
|
||||
ORDER BY date ASC
|
||||
`;
|
||||
@@ -731,20 +738,23 @@ function buildFinancialTrendQuery(whereClause) {
|
||||
function normalizeFinancialTotals(row = {}) {
|
||||
const grossSales = parseFloat(row.grossSales || 0);
|
||||
const refunds = parseFloat(row.refunds || 0);
|
||||
const shippingFees = parseFloat(row.shippingFees || 0);
|
||||
const taxCollected = parseFloat(row.taxCollected || 0);
|
||||
const discounts = parseFloat(row.discounts || 0);
|
||||
const cogs = parseFloat(row.cogs || 0);
|
||||
const netSales = grossSales - refunds;
|
||||
const netRevenue = netSales - taxCollected;
|
||||
const profit = netRevenue - cogs;
|
||||
const margin = netRevenue !== 0 ? (profit / netRevenue) * 100 : 0;
|
||||
const productNet = grossSales - refunds - discounts;
|
||||
const income = productNet + shippingFees;
|
||||
const profit = income - cogs;
|
||||
const margin = income !== 0 ? (profit / income) * 100 : 0;
|
||||
|
||||
return {
|
||||
grossSales,
|
||||
refunds,
|
||||
shippingFees,
|
||||
taxCollected,
|
||||
discounts,
|
||||
cogs,
|
||||
netSales,
|
||||
netRevenue,
|
||||
income,
|
||||
profit,
|
||||
margin,
|
||||
};
|
||||
@@ -753,12 +763,14 @@ function normalizeFinancialTotals(row = {}) {
|
||||
function normalizeFinancialTrendRow(row = {}) {
|
||||
const grossSales = parseFloat(row.grossSales || 0);
|
||||
const refunds = parseFloat(row.refunds || 0);
|
||||
const shippingFees = parseFloat(row.shippingFees || 0);
|
||||
const taxCollected = parseFloat(row.taxCollected || 0);
|
||||
const discounts = parseFloat(row.discounts || 0);
|
||||
const cogs = parseFloat(row.cogs || 0);
|
||||
const netSales = grossSales - refunds;
|
||||
const netRevenue = netSales - taxCollected;
|
||||
const profit = netRevenue - cogs;
|
||||
const margin = netRevenue !== 0 ? (profit / netRevenue) * 100 : 0;
|
||||
const productNet = grossSales - refunds - discounts;
|
||||
const income = productNet + shippingFees;
|
||||
const profit = income - cogs;
|
||||
const margin = income !== 0 ? (profit / income) * 100 : 0;
|
||||
let timestamp = null;
|
||||
|
||||
if (row.date instanceof Date) {
|
||||
@@ -771,10 +783,11 @@ function normalizeFinancialTrendRow(row = {}) {
|
||||
date: row.date,
|
||||
grossSales,
|
||||
refunds,
|
||||
shippingFees,
|
||||
taxCollected,
|
||||
discounts,
|
||||
cogs,
|
||||
netSales,
|
||||
netRevenue,
|
||||
income,
|
||||
profit,
|
||||
margin,
|
||||
timestamp,
|
||||
|
||||
@@ -61,26 +61,28 @@ type ComparisonValue = {
|
||||
};
|
||||
|
||||
type FinancialTotals = {
|
||||
grossSales: number;
|
||||
refunds: number;
|
||||
taxCollected: number;
|
||||
grossSales?: number;
|
||||
refunds?: number;
|
||||
taxCollected?: number;
|
||||
shippingFees?: number;
|
||||
discounts?: number;
|
||||
cogs: number;
|
||||
netSales: number;
|
||||
netRevenue: number;
|
||||
income?: number;
|
||||
profit: number;
|
||||
margin: number;
|
||||
};
|
||||
|
||||
type FinancialTrendPoint = {
|
||||
date: string | Date | null;
|
||||
grossSales: number;
|
||||
refunds: number;
|
||||
taxCollected: number;
|
||||
income: number;
|
||||
cogs: number;
|
||||
netSales: number;
|
||||
netRevenue: number;
|
||||
profit: number;
|
||||
margin: number;
|
||||
grossSales?: number;
|
||||
refunds?: number;
|
||||
shippingFees?: number;
|
||||
taxCollected?: number;
|
||||
discounts?: number;
|
||||
timestamp: string | null;
|
||||
};
|
||||
|
||||
@@ -88,8 +90,9 @@ type FinancialComparison = {
|
||||
grossSales?: ComparisonValue;
|
||||
refunds?: ComparisonValue;
|
||||
taxCollected?: ComparisonValue;
|
||||
discounts?: ComparisonValue;
|
||||
cogs?: ComparisonValue;
|
||||
netRevenue?: ComparisonValue;
|
||||
income?: ComparisonValue;
|
||||
profit?: ComparisonValue;
|
||||
margin?: ComparisonValue;
|
||||
[key: string]: ComparisonValue | undefined;
|
||||
@@ -129,15 +132,14 @@ type 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 ChartPoint = {
|
||||
label: string;
|
||||
timestamp: string | null;
|
||||
grossSales: number | null;
|
||||
netRevenue: number | null;
|
||||
income: number | null;
|
||||
cogs: number | null;
|
||||
profit: number | null;
|
||||
margin: number | null;
|
||||
@@ -146,18 +148,16 @@ type ChartPoint = {
|
||||
};
|
||||
|
||||
const chartColors: Record<ChartSeriesKey, string> = {
|
||||
grossSales: "#7c3aed",
|
||||
netRevenue: "#6366f1",
|
||||
income: "#2563eb",
|
||||
cogs: "#f97316",
|
||||
profit: "#10b981",
|
||||
margin: "#0ea5e9",
|
||||
};
|
||||
|
||||
const SERIES_LABELS: Record<ChartSeriesKey, string> = {
|
||||
grossSales: "Gross Sales",
|
||||
netRevenue: "Net Revenue",
|
||||
income: "Total Income",
|
||||
cogs: "COGS",
|
||||
profit: "Profit",
|
||||
profit: "Gross Profit",
|
||||
margin: "Profit Margin",
|
||||
};
|
||||
|
||||
@@ -166,8 +166,7 @@ const SERIES_DEFINITIONS: Array<{
|
||||
label: string;
|
||||
type: "currency" | "percentage";
|
||||
}> = [
|
||||
{ key: "grossSales", label: SERIES_LABELS.grossSales, type: "currency" },
|
||||
{ key: "netRevenue", label: SERIES_LABELS.netRevenue, type: "currency" },
|
||||
{ key: "income", label: SERIES_LABELS.income, type: "currency" },
|
||||
{ key: "cogs", label: SERIES_LABELS.cogs, type: "currency" },
|
||||
{ key: "profit", label: SERIES_LABELS.profit, type: "currency" },
|
||||
{ key: "margin", label: SERIES_LABELS.margin, type: "percentage" },
|
||||
@@ -277,10 +276,12 @@ const formatPercentage = (value: number, digits = 1, suffix = "%") => {
|
||||
|
||||
type RawTrendPoint = {
|
||||
date: Date;
|
||||
grossSales: number;
|
||||
netRevenue: number;
|
||||
income: number;
|
||||
cogs: number;
|
||||
profit: number;
|
||||
shippingFees: number;
|
||||
taxCollected: number;
|
||||
grossSales: number;
|
||||
discounts: number;
|
||||
};
|
||||
|
||||
type AggregatedTrendPoint = {
|
||||
@@ -289,8 +290,7 @@ type AggregatedTrendPoint = {
|
||||
tooltipLabel: string;
|
||||
detailLabel: string;
|
||||
timestamp: string;
|
||||
grossSales: number;
|
||||
netRevenue: number;
|
||||
income: number;
|
||||
cogs: number;
|
||||
profit: number;
|
||||
margin: number;
|
||||
@@ -367,10 +367,8 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption):
|
||||
key: string;
|
||||
start: Date;
|
||||
end: Date;
|
||||
grossSales: number;
|
||||
netRevenue: number;
|
||||
income: number;
|
||||
cogs: number;
|
||||
profit: number;
|
||||
}
|
||||
>();
|
||||
|
||||
@@ -382,10 +380,8 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption):
|
||||
key,
|
||||
start: point.date,
|
||||
end: point.date,
|
||||
grossSales: point.grossSales,
|
||||
netRevenue: point.netRevenue,
|
||||
income: point.income,
|
||||
cogs: point.cogs,
|
||||
profit: point.profit,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -397,18 +393,17 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption):
|
||||
bucket.end = point.date;
|
||||
}
|
||||
|
||||
bucket.grossSales += point.grossSales;
|
||||
bucket.netRevenue += point.netRevenue;
|
||||
bucket.income += point.income;
|
||||
bucket.cogs += point.cogs;
|
||||
bucket.profit += point.profit;
|
||||
});
|
||||
|
||||
return Array.from(bucketMap.values())
|
||||
.sort((a, b) => a.start.getTime() - b.start.getTime())
|
||||
.map((bucket) => {
|
||||
const { axisLabel, tooltipLabel, detailLabel } = buildGroupLabels(bucket.start, bucket.end, groupBy);
|
||||
const { netRevenue, profit } = bucket;
|
||||
const margin = netRevenue !== 0 ? (profit / netRevenue) * 100 : 0;
|
||||
const income = bucket.income;
|
||||
const profit = income - bucket.cogs;
|
||||
const margin = income !== 0 ? (profit / income) * 100 : 0;
|
||||
|
||||
return {
|
||||
id: bucket.key,
|
||||
@@ -416,8 +411,7 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption):
|
||||
tooltipLabel,
|
||||
detailLabel,
|
||||
timestamp: bucket.start.toISOString(),
|
||||
grossSales: bucket.grossSales,
|
||||
netRevenue,
|
||||
income,
|
||||
cogs: bucket.cogs,
|
||||
profit,
|
||||
margin,
|
||||
@@ -516,8 +510,7 @@ const extendAggregatedTrendPoints = (
|
||||
tooltipLabel,
|
||||
detailLabel,
|
||||
timestamp: bucketStart.toISOString(),
|
||||
grossSales: 0,
|
||||
netRevenue: 0,
|
||||
income: 0,
|
||||
cogs: 0,
|
||||
profit: 0,
|
||||
margin: 0,
|
||||
@@ -536,14 +529,21 @@ const monthsBetween = (start: Date, end: Date) => {
|
||||
|
||||
const buildTrendLabel = (
|
||||
comparison?: ComparisonValue | null,
|
||||
options?: { isPercentage?: boolean }
|
||||
options?: { isPercentage?: boolean; invertDirection?: boolean }
|
||||
): TrendSummary | null => {
|
||||
if (!comparison || comparison.absolute === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 currentYear = new Date().getFullYear();
|
||||
return Array.from({ length: span }, (_, index) => currentYear - index);
|
||||
@@ -638,9 +671,8 @@ const FinancialOverview = () => {
|
||||
count: 1,
|
||||
});
|
||||
const [metrics, setMetrics] = useState<Record<ChartSeriesKey, boolean>>({
|
||||
grossSales: true,
|
||||
netRevenue: true,
|
||||
cogs: false,
|
||||
income: true,
|
||||
cogs: true,
|
||||
profit: true,
|
||||
margin: true,
|
||||
});
|
||||
@@ -724,12 +756,25 @@ const FinancialOverview = () => {
|
||||
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 {
|
||||
date,
|
||||
grossSales: toNumber(point.grossSales),
|
||||
netRevenue: toNumber(point.netRevenue),
|
||||
income: incomeValue,
|
||||
shippingFees: shippingFeesValue,
|
||||
taxCollected: taxCollectedValue,
|
||||
grossSales: grossSalesValue,
|
||||
discounts: discountsValue,
|
||||
cogs: toNumber(point.cogs),
|
||||
profit: toNumber(point.profit),
|
||||
};
|
||||
})
|
||||
.filter((value): value is RawTrendPoint => Boolean(value))
|
||||
@@ -848,45 +893,74 @@ const FinancialOverview = () => {
|
||||
|
||||
const totals = data.totals;
|
||||
const previous = data.previousTotals ?? undefined;
|
||||
const comparison = data.comparison ?? {};
|
||||
|
||||
const safeCurrency = (value: number | undefined | null, digits = 0) =>
|
||||
typeof value === "number" && Number.isFinite(value) ? formatCurrency(value, digits) : "—";
|
||||
|
||||
const safePercentage = (value: number | undefined | null, digits = 1) =>
|
||||
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 [
|
||||
{
|
||||
key: "grossSales",
|
||||
title: "Gross Sales",
|
||||
value: safeCurrency(totals.grossSales, 0),
|
||||
description: previous?.grossSales != null ? `Previous: ${safeCurrency(previous.grossSales, 0)}` : undefined,
|
||||
trend: buildTrendLabel(comparison.grossSales),
|
||||
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),
|
||||
key: "income",
|
||||
title: "Total Income",
|
||||
value: safeCurrency(totalIncome, 0),
|
||||
description: previousIncome != null ? `Previous: ${safeCurrency(previousIncome, 0)}` : undefined,
|
||||
trend: buildTrendLabel(comparison?.income ?? buildComparisonFromValues(totalIncome, previousIncome ?? null)),
|
||||
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",
|
||||
title: "Profit",
|
||||
value: safeCurrency(totals.profit, 0),
|
||||
description: previous?.profit != null ? `Previous: ${safeCurrency(previous.profit, 0)}` : undefined,
|
||||
trend: buildTrendLabel(comparison.profit),
|
||||
title: "Gross Profit",
|
||||
value: safeCurrency(profitValue, 0),
|
||||
description: previousProfitValue != null ? `Previous: ${safeCurrency(previousProfitValue, 0)}` : undefined,
|
||||
trend: buildTrendLabel(comparison?.profit ?? buildComparisonFromValues(profitValue, previousProfitValue ?? null)),
|
||||
accentClass: "text-emerald-600 dark:text-emerald-400",
|
||||
},
|
||||
{
|
||||
key: "margin",
|
||||
title: "Profit Margin",
|
||||
value: safePercentage(totals.margin, 1),
|
||||
description: previous?.margin != null ? `Previous: ${safePercentage(previous.margin, 1)}` : undefined,
|
||||
trend: buildTrendLabel(comparison.margin, { isPercentage: true }),
|
||||
value: safePercentage(marginValue, 1),
|
||||
description: previousMarginValue != null ? `Previous: ${safePercentage(previousMarginValue, 1)}` : undefined,
|
||||
trend: buildTrendLabel(
|
||||
comparison?.margin ?? buildComparisonFromValues(marginValue, previousMarginValue ?? null),
|
||||
{ isPercentage: true }
|
||||
),
|
||||
accentClass: "text-sky-600 dark:text-sky-400",
|
||||
},
|
||||
];
|
||||
@@ -907,8 +981,7 @@ const FinancialOverview = () => {
|
||||
return aggregatedPoints.map((point) => ({
|
||||
label: point.label,
|
||||
timestamp: point.timestamp,
|
||||
grossSales: point.isFuture ? null : point.grossSales,
|
||||
netRevenue: point.isFuture ? null : point.netRevenue,
|
||||
income: point.isFuture ? null : point.income,
|
||||
cogs: point.isFuture ? null : point.cogs,
|
||||
profit: point.isFuture ? null : point.profit,
|
||||
margin: point.isFuture ? null : point.margin,
|
||||
@@ -977,8 +1050,7 @@ const FinancialOverview = () => {
|
||||
id: point.id,
|
||||
label: point.detailLabel,
|
||||
timestamp: point.timestamp,
|
||||
grossSales: point.grossSales,
|
||||
netRevenue: point.netRevenue,
|
||||
income: point.income,
|
||||
cogs: point.cogs,
|
||||
profit: point.profit,
|
||||
margin: point.margin,
|
||||
@@ -1174,18 +1246,15 @@ const FinancialOverview = () => {
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Date</TableHead>
|
||||
{metrics.grossSales && (
|
||||
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Gross Sales</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.income && (
|
||||
<TableHead className="px-6 py-3 whitespace-nowrap text-center">Total Income</TableHead>
|
||||
)}
|
||||
{metrics.cogs && (
|
||||
<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 && (
|
||||
<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">
|
||||
{row.label || "—"}
|
||||
</TableCell>
|
||||
{metrics.grossSales && (
|
||||
{metrics.income && (
|
||||
<TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium">
|
||||
{row.isFuture ? "—" : formatCurrency(row.grossSales, 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)}
|
||||
{row.isFuture ? "—" : formatCurrency(row.income, 0)}
|
||||
</TableCell>
|
||||
)}
|
||||
{metrics.cogs && (
|
||||
@@ -1217,6 +1276,11 @@ const FinancialOverview = () => {
|
||||
{row.isFuture ? "—" : formatCurrency(row.cogs, 0)}
|
||||
</TableCell>
|
||||
)}
|
||||
{metrics.profit && (
|
||||
<TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium">
|
||||
{row.isFuture ? "—" : formatCurrency(row.profit, 0)}
|
||||
</TableCell>
|
||||
)}
|
||||
{metrics.margin && (
|
||||
<TableCell className="px-6 py-3 whitespace-nowrap text-center font-medium">
|
||||
{row.isFuture ? "—" : formatPercentage(row.margin, 1)}
|
||||
@@ -1387,13 +1451,9 @@ const FinancialOverview = () => {
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ComposedChart data={chartData} margin={{ top: 10, right: 30, left: -10, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id="financialGrossSales" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={chartColors.grossSales} stopOpacity={0.35} />
|
||||
<stop offset="95%" stopColor={chartColors.grossSales} 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 id="financialIncome" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={chartColors.income} stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor={chartColors.income} stopOpacity={0.05} />
|
||||
</linearGradient>
|
||||
<linearGradient id="financialCogs" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={chartColors.cogs} stopOpacity={0.25} />
|
||||
@@ -1424,25 +1484,14 @@ const FinancialOverview = () => {
|
||||
)}
|
||||
<Tooltip content={<FinancialTooltip />} />
|
||||
<Legend formatter={(value: string) => SERIES_LABELS[value as ChartSeriesKey] ?? value} />
|
||||
{metrics.grossSales ? (
|
||||
{metrics.income ? (
|
||||
<Area
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey="grossSales"
|
||||
name={SERIES_LABELS.grossSales}
|
||||
stroke={chartColors.grossSales}
|
||||
fill="url(#financialGrossSales)"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
) : null}
|
||||
{metrics.netRevenue ? (
|
||||
<Area
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey="netRevenue"
|
||||
name={SERIES_LABELS.netRevenue}
|
||||
stroke={chartColors.netRevenue}
|
||||
fill="url(#financialNetRevenue)"
|
||||
dataKey="income"
|
||||
name={SERIES_LABELS.income}
|
||||
stroke={chartColors.income}
|
||||
fill="url(#financialIncome)"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user