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:
parent
c5a6cba041
commit
b4d4f34864
1 changed files with 75 additions and 6 deletions
|
|
@ -40,6 +40,11 @@ const ProfitAnalysis = () => {
|
|||
return ProfitAnalysisService.calculateProfitMetrics(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
|
||||
const plData = useMemo(() => {
|
||||
const revenue = filteredOrders.reduce((s, o) => s + orderNetRevenue(o), 0);
|
||||
|
|
@ -456,13 +461,77 @@ const ProfitAnalysis = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Placeholder for trends view */}
|
||||
{/* Monthly trends */}
|
||||
{selectedView === 'trends' && (
|
||||
monthlyTrends.length === 0 ? (
|
||||
<div className="bg-white p-8 rounded-lg shadow text-center">
|
||||
<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>
|
||||
<p className="text-gray-600">Interactive charts coming soon...</p>
|
||||
<p className="text-gray-600">No data for the selected period.</p>
|
||||
</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 & 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 */}
|
||||
|
|
|
|||
Loading…
Reference in a new issue