Gross, Net Profit calculate

This commit is contained in:
Satyam Rastogi
2026-05-11 18:56:04 +05:30
parent e9c67d8dda
commit 0b6ee9d626
2 changed files with 122 additions and 9 deletions
+98 -9
View File
@@ -12,7 +12,7 @@ import {
ArrowLeft, CheckCircle, Clock, AlertTriangle, PauseCircle, ShieldAlert,
FileText, DollarSign, GitPullRequest, AlertCircle, Activity, Milestone,
Shield, ChevronRight, Phone, MessageSquare, Mail, AlertOctagon, TrendingUp, Key, Zap, Camera, Plus, Map, User, Crosshair, Image as ImageIcon, MapPin,
Upload, Trash2, Eye, Pencil, X as XIcon, FolderOpen, Download, File, StickyNote, Banknote, ChevronLeft, ChevronDown as ChevronDownIcon
Upload, Trash2, Eye, Pencil, X as XIcon, FolderOpen, Download, File, StickyNote, Banknote, ChevronLeft, ChevronDown as ChevronDownIcon, Info
} from 'lucide-react';
import ChangeOrderDrawer from '../../components/owner/ChangeOrderDrawer';
import InvoiceDetailModal from '../../components/owner/InvoiceDetailModal';
@@ -220,6 +220,18 @@ const OwnerProjectDetail = () => {
projects.find(p => p.id === projectId && p.ownerId === user?.id)
, [projects, projectId, user]);
const profitMetrics = useMemo(() => {
if (!project) return { totalCosts: 0, commission: 0, additionalExp: 0, grossProfit: 0, netProfit: 0, grossMargin: 0, netMargin: 0 };
const totalCosts = Number(project.actualCost ?? project.spent) || 0;
const commission = Number(project.commission) || 0;
const additionalExp = Number(project.additionalExpenses) || 0;
const gross = totalReceived - totalCosts;
const net = gross - commission - additionalExp;
const grossMargin = totalReceived > 0 ? (gross / totalReceived) * 100 : 0;
const netMargin = totalReceived > 0 ? (net / totalReceived) * 100 : 0;
return { totalCosts, commission, additionalExp, grossProfit: gross, netProfit: net, grossMargin, netMargin };
}, [project, totalReceived]);
if (!project) {
return (
<div className="min-h-full bg-zinc-50 dark:bg-[#09090b] flex items-center justify-center text-zinc-500">
@@ -905,22 +917,99 @@ const OwnerProjectDetail = () => {
{/* PAYMENTS TAB */}
{activeTab === 'payments' && (
<div className="space-y-6 max-w-7xl mx-auto">
{/* Summary Row */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{/* Financial Summary Row */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<SpotlightCard className="p-4">
<div className={`text-xl font-extrabold ${NEON_GREEN}`}>{formatCurrency(totalReceived)}</div>
<div className="text-[10px] uppercase font-bold tracking-wider text-zinc-500 mt-1">Total Received</div>
</SpotlightCard>
<SpotlightCard className="p-4">
<div className="text-xl font-extrabold text-zinc-900 dark:text-white">{payments.length}</div>
<div className="text-[10px] uppercase font-bold tracking-wider text-zinc-500 mt-1">Payments</div>
<div className="text-xl font-extrabold text-zinc-900 dark:text-white">{formatCurrency(profitMetrics.totalCosts)}</div>
<div className="text-[10px] uppercase font-bold tracking-wider text-zinc-500 mt-1">Total Costs</div>
</SpotlightCard>
<SpotlightCard className="p-4">
<div className="text-xl font-extrabold text-zinc-900 dark:text-white font-mono">
{payments.length > 0 ? [...payments].sort((a, b) => b.date.localeCompare(a.date))[0].date : '—'}
</div>
<div className="text-[10px] uppercase font-bold tracking-wider text-zinc-500 mt-1">Latest Payment</div>
<div className="text-xl font-extrabold text-zinc-900 dark:text-white">{formatCurrency(profitMetrics.commission)}</div>
<div className="text-[10px] uppercase font-bold tracking-wider text-zinc-500 mt-1">Commission</div>
</SpotlightCard>
<SpotlightCard className="p-4">
<div className="text-xl font-extrabold text-zinc-900 dark:text-white">{formatCurrency(profitMetrics.additionalExp)}</div>
<div className="text-[10px] uppercase font-bold tracking-wider text-zinc-500 mt-1">Addl. Expenses</div>
</SpotlightCard>
</div>
{/* Profit Summary Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Gross Profit */}
<NeoCard
spotlightColor={profitMetrics.grossProfit >= 0 ? "rgba(57, 255, 20, 0.12)" : "rgba(255, 0, 60, 0.12)"}
innerClassName="!p-5"
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<TrendingUp size={18} className={profitMetrics.grossProfit >= 0 ? NEON_GREEN : NEON_RED} />
<h4 className="text-xs font-mono font-bold text-zinc-500 dark:text-zinc-400 uppercase tracking-widest">Gross Profit</h4>
</div>
<div className="relative group">
<Info size={14} className="text-zinc-400 dark:text-zinc-500 cursor-help" />
<div className="absolute right-0 top-6 z-30 w-64 p-3 rounded-xl bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-white/10 shadow-xl text-xs text-zinc-600 dark:text-zinc-300 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all pointer-events-none">
<p className="font-bold text-zinc-900 dark:text-white mb-1">Gross Profit Formula</p>
<p className="font-mono text-[11px]">Total Received Total Actual Costs</p>
<div className="mt-2 pt-2 border-t border-zinc-100 dark:border-white/5 space-y-1">
<p>{formatCurrency(totalReceived)} {formatCurrency(profitMetrics.totalCosts)}</p>
<p className="font-bold">= {formatCurrency(profitMetrics.grossProfit)}</p>
</div>
</div>
</div>
</div>
<div className={`text-3xl sm:text-4xl font-black font-mono tracking-tight ${profitMetrics.grossProfit >= 0 ? NEON_GREEN : NEON_RED}`}>
{profitMetrics.grossProfit >= 0 ? '+' : ''}{formatCurrency(profitMetrics.grossProfit)}
</div>
<div className="flex items-center gap-2 mt-2">
<span className={`text-xs font-bold px-2 py-0.5 rounded-full ${
profitMetrics.grossMargin >= 0
? 'text-green-600 dark:text-[#39ff14] bg-green-50 dark:bg-[#39ff14]/10 border border-green-200 dark:border-[#39ff14]/20'
: 'text-red-600 dark:text-[#ff003c] bg-red-50 dark:bg-[#ff003c]/10 border border-red-200 dark:border-[#ff003c]/20'
}`}>
{profitMetrics.grossMargin >= 0 ? '+' : ''}{profitMetrics.grossMargin.toFixed(1)}% margin
</span>
</div>
</NeoCard>
{/* Net Profit */}
<NeoCard
spotlightColor={profitMetrics.netProfit >= 0 ? "rgba(57, 255, 20, 0.12)" : "rgba(255, 0, 60, 0.12)"}
innerClassName="!p-5"
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<DollarSign size={18} className={profitMetrics.netProfit >= 0 ? NEON_GREEN : NEON_RED} />
<h4 className="text-xs font-mono font-bold text-zinc-500 dark:text-zinc-400 uppercase tracking-widest">Net Profit</h4>
</div>
<div className="relative group">
<Info size={14} className="text-zinc-400 dark:text-zinc-500 cursor-help" />
<div className="absolute right-0 top-6 z-50 w-64 p-3 rounded-xl bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-white/10 shadow-xl text-xs text-zinc-600 dark:text-zinc-300 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all pointer-events-none">
<p className="font-bold text-zinc-900 dark:text-white mb-1">Net Profit Formula</p>
<p className="font-mono text-[11px]">Gross Profit Commission Addl. Expenses</p>
<div className="mt-2 pt-2 border-t border-zinc-100 dark:border-white/5 space-y-1">
<p>{formatCurrency(profitMetrics.grossProfit)} {formatCurrency(profitMetrics.commission)} {formatCurrency(profitMetrics.additionalExp)}</p>
<p className="font-bold">= {formatCurrency(profitMetrics.netProfit)}</p>
</div>
</div>
</div>
</div>
<div className={`text-3xl sm:text-4xl font-black font-mono tracking-tight ${profitMetrics.netProfit >= 0 ? NEON_GREEN : NEON_RED}`}>
{profitMetrics.netProfit >= 0 ? '+' : ''}{formatCurrency(profitMetrics.netProfit)}
</div>
<div className="flex items-center gap-2 mt-2">
<span className={`text-xs font-bold px-2 py-0.5 rounded-full ${
profitMetrics.netMargin >= 0
? 'text-green-600 dark:text-[#39ff14] bg-green-50 dark:bg-[#39ff14]/10 border border-green-200 dark:border-[#39ff14]/20'
: 'text-red-600 dark:text-[#ff003c] bg-red-50 dark:bg-[#ff003c]/10 border border-red-200 dark:border-[#ff003c]/20'
}`}>
{profitMetrics.netMargin >= 0 ? '+' : ''}{profitMetrics.netMargin.toFixed(1)}% margin
</span>
</div>
</NeoCard>
</div>
{/* Payments Table */}