Build out Profit Analysis Trends tab (was a placeholder)

Replace 'coming soon' with a real monthly trend view: revenue vs profit
bar chart (px-scaled) plus a per-month breakdown table (orders, revenue,
printing, expenses, profit, margin) from calculateMonthlyTrends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dlawler489 2026-06-14 16:07:17 +10:00
parent c5a6cba041
commit b4d4f34864

View file

@ -40,6 +40,11 @@ const ProfitAnalysis = () => {
return ProfitAnalysisService.calculateProfitMetrics(filteredOrders, products || [], filteredExpenses); return ProfitAnalysisService.calculateProfitMetrics(filteredOrders, products || [], filteredExpenses);
}, [filteredOrders, products, filteredExpenses]); }, [filteredOrders, products, filteredExpenses]);
// Monthly revenue / cost / profit trend
const monthlyTrends = useMemo(() => {
return ProfitAnalysisService.calculateMonthlyTrends(filteredOrders, products || [], filteredExpenses);
}, [filteredOrders, products, filteredExpenses]);
// Profit & loss statement + indicative GST summary for the selected period // Profit & loss statement + indicative GST summary for the selected period
const plData = useMemo(() => { const plData = useMemo(() => {
const revenue = filteredOrders.reduce((s, o) => s + orderNetRevenue(o), 0); const revenue = filteredOrders.reduce((s, o) => s + orderNetRevenue(o), 0);
@ -456,13 +461,77 @@ const ProfitAnalysis = () => {
</div> </div>
)} )}
{/* Placeholder for trends view */} {/* Monthly trends */}
{selectedView === 'trends' && ( {selectedView === 'trends' && (
monthlyTrends.length === 0 ? (
<div className="bg-white p-8 rounded-lg shadow text-center"> <div className="bg-white p-8 rounded-lg shadow text-center">
<BarChart3 className="w-16 h-16 text-gray-400 mx-auto mb-4" /> <BarChart3 className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Profit Trends</h3> <h3 className="text-lg font-medium text-gray-900 mb-2">Profit Trends</h3>
<p className="text-gray-600">Interactive charts coming soon...</p> <p className="text-gray-600">No data for the selected period.</p>
</div> </div>
) : (
<>
{/* Revenue & profit bars */}
<div className="bg-white rounded-lg shadow p-6 mb-6">
<div className="flex items-center gap-4 mb-4">
<h3 className="text-lg font-medium text-gray-900">Revenue &amp; Profit by Month</h3>
<span className="flex items-center gap-1 text-xs text-gray-500"><span className="w-3 h-3 rounded-sm bg-blue-500 inline-block" /> Revenue</span>
<span className="flex items-center gap-1 text-xs text-gray-500"><span className="w-3 h-3 rounded-sm bg-green-500 inline-block" /> Profit</span>
</div>
{(() => {
const maxVal = Math.max(...monthlyTrends.map(m => Math.max(m.revenue, m.profit)), 0);
const maxBarPx = 180;
return (
<div className="h-60 flex items-end justify-between gap-2 pb-6 overflow-x-auto">
{monthlyTrends.map((m, i) => {
const revPx = maxVal > 0 ? Math.max((m.revenue / maxVal) * maxBarPx, m.revenue > 0 ? 4 : 0) : 0;
const profitPx = maxVal > 0 ? Math.max((Math.max(m.profit, 0) / maxVal) * maxBarPx, 0) : 0;
return (
<div key={i} className="flex-1 min-w-[40px] flex flex-col items-center justify-end h-full group">
<div className="flex items-end gap-0.5 w-full justify-center" style={{ height: `${maxBarPx}px` }}>
<div className="w-1/2 bg-blue-500 rounded-t-sm" style={{ height: `${revPx}px` }}
title={`Revenue ${formatCurrency(m.revenue)}`} />
<div className="w-1/2 bg-green-500 rounded-t-sm" style={{ height: `${profitPx}px` }}
title={`Profit ${formatCurrency(m.profit)}`} />
</div>
<span className="text-[10px] text-gray-500 mt-2 whitespace-nowrap">{m.month.split(' ')[0]}</span>
</div>
);
})}
</div>
);
})()}
</div>
{/* Monthly breakdown table */}
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-50">
<tr>
{['Month', 'Orders', 'Revenue', 'Printing', 'Expenses', 'Profit', 'Margin'].map(h => (
<th key={h} className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{h}</th>
))}
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-100">
{monthlyTrends.map((m, i) => (
<tr key={i} className="hover:bg-gray-50">
<td className="px-4 py-2 text-gray-900">{m.month}</td>
<td className="px-4 py-2 text-gray-700">{m.orderCount}</td>
<td className="px-4 py-2 text-gray-900">{formatCurrency(m.revenue)}</td>
<td className="px-4 py-2 text-gray-500">{formatCurrency(m.costs)}</td>
<td className="px-4 py-2 text-gray-500">{formatCurrency(m.expenses)}</td>
<td className={`px-4 py-2 font-medium ${m.profit >= 0 ? 'text-green-600' : 'text-red-600'}`}>{formatCurrency(m.profit)}</td>
<td className="px-4 py-2 text-gray-700">{m.margin.toFixed(1)}%</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</>
)
)} )}
{/* Order Analysis */} {/* Order Analysis */}