remove Payments tab and adds sections into budges- costs

This commit is contained in:
Satyam-Rastogi
2026-05-12 16:58:33 +05:30
parent c82cadd547
commit 3a47e1f88f
2 changed files with 343 additions and 378 deletions
-12
View File
@@ -2014,7 +2014,6 @@ const MOCK_PROJECTS = [
commissionType: 'percent_gross', commissionType: 'percent_gross',
commissionRate: 10, commissionRate: 10,
commission: 4500, commission: 4500,
additionalExpenses: 1200,
startDate: '2026-01-20', startDate: '2026-01-20',
endDate: '2026-03-05', endDate: '2026-03-05',
completionPercentage: 55, completionPercentage: 55,
@@ -2091,7 +2090,6 @@ const MOCK_PROJECTS = [
commissionType: 'flat', commissionType: 'flat',
commissionRate: 850, commissionRate: 850,
commission: 850, commission: 850,
additionalExpenses: 350,
startDate: '2025-11-15', startDate: '2025-11-15',
endDate: '2025-12-10', endDate: '2025-12-10',
completionPercentage: 100, completionPercentage: 100,
@@ -2141,7 +2139,6 @@ const MOCK_PROJECTS = [
commissionType: 'percent_net', commissionType: 'percent_net',
commissionRate: 15, commissionRate: 15,
commission: 3200, commission: 3200,
additionalExpenses: 900,
startDate: '2026-01-06', startDate: '2026-01-06',
endDate: '2026-02-28', endDate: '2026-02-28',
completionPercentage: 42, completionPercentage: 42,
@@ -2217,7 +2214,6 @@ const MOCK_PROJECTS = [
commissionType: 'custom', commissionType: 'custom',
commissionRate: 5500, commissionRate: 5500,
commission: 5500, commission: 5500,
additionalExpenses: 3800,
startDate: '2025-12-01', startDate: '2025-12-01',
endDate: '2026-02-28', endDate: '2026-02-28',
completionPercentage: 78, completionPercentage: 78,
@@ -2304,7 +2300,6 @@ const MOCK_PROJECTS = [
commissionType: 'percent_gross', commissionType: 'percent_gross',
commissionRate: 10, commissionRate: 10,
commission: 2800, commission: 2800,
additionalExpenses: 600,
startDate: '2026-01-06', startDate: '2026-01-06',
endDate: '2026-02-20', endDate: '2026-02-20',
completionPercentage: 38, completionPercentage: 38,
@@ -2378,7 +2373,6 @@ const MOCK_PROJECTS = [
commissionType: 'flat', commissionType: 'flat',
commissionRate: 1850, commissionRate: 1850,
commission: 1850, commission: 1850,
additionalExpenses: 450,
startDate: '2026-02-03', startDate: '2026-02-03',
endDate: '2026-03-15', endDate: '2026-03-15',
completionPercentage: 22, completionPercentage: 22,
@@ -2431,7 +2425,6 @@ const MOCK_PROJECTS = [
commissionType: 'percent_gross', commissionType: 'percent_gross',
commissionRate: 10, commissionRate: 10,
commission: 12500, commission: 12500,
additionalExpenses: 2800,
startDate: '2026-01-27', startDate: '2026-01-27',
endDate: '2026-04-15', endDate: '2026-04-15',
completionPercentage: 32, completionPercentage: 32,
@@ -2526,7 +2519,6 @@ const MOCK_PROJECTS = [
commissionType: 'percent_net', commissionType: 'percent_net',
commissionRate: 12, commissionRate: 12,
commission: 7500, commission: 7500,
additionalExpenses: 4200,
startDate: '2025-11-18', startDate: '2025-11-18',
endDate: '2026-02-28', endDate: '2026-02-28',
completionPercentage: 88, completionPercentage: 88,
@@ -2577,7 +2569,6 @@ const MOCK_PROJECTS = [
commissionType: 'percent_gross', commissionType: 'percent_gross',
commissionRate: 10, commissionRate: 10,
commission: 4200, commission: 4200,
additionalExpenses: 1100,
startDate: '2026-01-13', startDate: '2026-01-13',
endDate: '2026-03-10', endDate: '2026-03-10',
completionPercentage: 50, completionPercentage: 50,
@@ -2626,7 +2617,6 @@ const MOCK_PROJECTS = [
commissionType: 'custom', commissionType: 'custom',
commissionRate: 3500, commissionRate: 3500,
commission: 3500, commission: 3500,
additionalExpenses: 800,
startDate: '2025-09-15', startDate: '2025-09-15',
endDate: '2025-10-28', endDate: '2025-10-28',
completionPercentage: 100, completionPercentage: 100,
@@ -2675,7 +2665,6 @@ const MOCK_PROJECTS = [
commissionType: 'flat', commissionType: 'flat',
commissionRate: 9500, commissionRate: 9500,
commission: 9500, commission: 9500,
additionalExpenses: 5200,
startDate: '2025-12-08', startDate: '2025-12-08',
endDate: '2026-03-20', endDate: '2026-03-20',
completionPercentage: 62, completionPercentage: 62,
@@ -2732,7 +2721,6 @@ const MOCK_PROJECTS = [
commissionType: 'percent_gross', commissionType: 'percent_gross',
commissionRate: 10, commissionRate: 10,
commission: 5800, commission: 5800,
additionalExpenses: 1500,
startDate: '2026-01-13', startDate: '2026-01-13',
endDate: '2026-04-01', endDate: '2026-04-01',
completionPercentage: 28, completionPercentage: 28,
+343 -366
View File
@@ -68,7 +68,6 @@ const tabs = [
{ id: 'rfis', label: 'RFIs', icon: FileText }, { id: 'rfis', label: 'RFIs', icon: FileText },
{ id: 'milestones', label: 'Milestones', icon: Milestone }, { id: 'milestones', label: 'Milestones', icon: Milestone },
{ id: 'invoices', label: 'Invoices', icon: DollarSign }, { id: 'invoices', label: 'Invoices', icon: DollarSign },
{ id: 'payments', label: 'Payments', icon: Banknote },
{ id: 'risks', label: 'Risk & Issues', icon: AlertCircle }, { id: 'risks', label: 'Risk & Issues', icon: AlertCircle },
{ id: 'activity', label: 'Activity', icon: Clock }, { id: 'activity', label: 'Activity', icon: Clock },
{ id: 'docs', label: 'Docs', icon: FolderOpen }, { id: 'docs', label: 'Docs', icon: FolderOpen },
@@ -265,9 +264,8 @@ const OwnerProjectDetail = () => {
const activeCommissionConfig = resolvedCommission; const activeCommissionConfig = resolvedCommission;
const profitMetrics = useMemo(() => { const profitMetrics = useMemo(() => {
if (!project) return { totalCosts: 0, commission: 0, commissionFormula: '', additionalExp: 0, grossProfit: 0, netProfit: 0, grossMargin: 0, netMargin: 0 }; if (!project) return { totalCosts: 0, commission: 0, commissionFormula: '', grossProfit: 0, netProfit: 0, grossMargin: 0, netMargin: 0 };
const totalCosts = Number(project.actualCost ?? project.spent) || 0; const totalCosts = Number(project.actualCost ?? project.spent) || 0;
const additionalExp = Number(project.additionalExpenses) || 0;
const gross = totalReceived - totalCosts; const gross = totalReceived - totalCosts;
const { type, rate } = activeCommissionConfig; const { type, rate } = activeCommissionConfig;
@@ -297,10 +295,10 @@ const OwnerProjectDetail = () => {
} }
commission = Number.isFinite(commission) ? commission : 0; commission = Number.isFinite(commission) ? commission : 0;
const net = gross - commission - additionalExp; const net = gross - commission;
const grossMargin = totalReceived > 0 ? (gross / totalReceived) * 100 : 0; const grossMargin = totalReceived > 0 ? (gross / totalReceived) * 100 : 0;
const netMargin = totalReceived > 0 ? (net / totalReceived) * 100 : 0; const netMargin = totalReceived > 0 ? (net / totalReceived) * 100 : 0;
return { totalCosts, commission, commissionFormula, additionalExp, grossProfit: gross, netProfit: net, grossMargin, netMargin }; return { totalCosts, commission, commissionFormula, grossProfit: gross, netProfit: net, grossMargin, netMargin };
}, [project, totalReceived, activeCommissionConfig]); }, [project, totalReceived, activeCommissionConfig]);
if (!project) { if (!project) {
@@ -736,6 +734,23 @@ const OwnerProjectDetail = () => {
); );
})} })}
</tbody> </tbody>
<tfoot className="border-t-2 border-zinc-300 dark:border-white/10">
<tr className="bg-zinc-50 dark:bg-white/[0.03]">
<td className="px-5 py-4" colSpan={2}>
<span className="text-xs font-bold uppercase tracking-widest text-zinc-500 dark:text-zinc-400">Totals</span>
</td>
<td className="px-5 py-4 text-right font-mono text-sm font-bold text-green-600 dark:text-[#39ff14]">{formatCurrency(totalReceived)}</td>
<td className="px-5 py-4 text-right font-mono text-sm font-bold text-[#00f0ff]">{formatCurrency(profitMetrics.totalCosts)}</td>
{(() => {
const totalVariance = profitMetrics.totalCosts - totalReceived;
return (
<td className={`px-5 py-4 text-right font-mono text-sm font-bold ${totalVariance > 0 ? 'text-[#ff4500]' : 'text-[#39ff14]'}`}>
{totalVariance > 0 ? '+' : ''}{formatCurrency(totalVariance)}
</td>
);
})()}
</tr>
</tfoot>
</table> </table>
</div> </div>
</NeoCard> </NeoCard>
@@ -747,6 +762,329 @@ const OwnerProjectDetail = () => {
<p className="text-sm mt-2 text-zinc-400 font-mono">Budget: {formatCurrency(project.approvedBudget || project.budget)} &nbsp;&middot;&nbsp; Spent: {formatCurrency(project.actualCost || project.spent)}</p> <p className="text-sm mt-2 text-zinc-400 font-mono">Budget: {formatCurrency(project.approvedBudget || project.budget)} &nbsp;&middot;&nbsp; Spent: {formatCurrency(project.actualCost || project.spent)}</p>
</NeoCard> </NeoCard>
)} )}
{/* Commission Configuration Card */}
<NeoCard spotlightColor="rgba(253, 169, 19, 0.1)" innerClassName="!p-5">
<div className="flex items-start justify-between mb-4" onClick={() => openTooltip && setOpenTooltip(null)}>
<div className="flex items-center gap-2">
<DollarSign size={18} className={NEON_GOLD} />
<h4 className="text-sm font-mono font-bold text-zinc-900 dark:text-white uppercase tracking-widest">Commission</h4>
{/* Source Badge */}
{(() => {
const src = resolvedCommission.source;
const badgeCfg = {
org: { icon: Building2, label: 'Org Default', color: 'text-sky-500 dark:text-[#00f0ff]', bg: 'bg-sky-50 dark:bg-[#00f0ff]/10', border: 'border-sky-200 dark:border-[#00f0ff]/20' },
user: { icon: UserIcon, label: 'User Override', color: 'text-purple-500 dark:text-purple-400', bg: 'bg-purple-50 dark:bg-purple-500/10', border: 'border-purple-200 dark:border-purple-500/20' },
job: { icon: Briefcase, label: 'Job Override', color: 'text-amber-600 dark:text-[#fda913]', bg: 'bg-amber-50 dark:bg-[#fda913]/10', border: 'border-amber-200 dark:border-[#fda913]/20' },
};
const b = badgeCfg[src];
const BadgeIcon = b.icon;
return (
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-widest border ${b.color} ${b.bg} ${b.border}`}>
<BadgeIcon size={10} />
{b.label}
</span>
);
})()}
</div>
<div className="flex items-center gap-1">
<button
type="button"
onClick={(e) => { e.stopPropagation(); setCommissionSettingsOpen(true); }}
className="p-1.5 rounded-lg hover:bg-amber-500/10 transition-colors group"
title="Commission Settings"
>
<Settings size={15} className="text-zinc-400 dark:text-zinc-500 group-hover:text-amber-500 dark:group-hover:text-[#fda913] transition-colors" />
</button>
<div className="relative">
<button type="button" onClick={(e) => { e.stopPropagation(); setOpenTooltip(prev => prev === 'commission' ? null : 'commission'); }} className="p-1 rounded-lg hover:bg-zinc-100 dark:hover:bg-white/10 transition-colors">
<Info size={14} className="text-zinc-400 dark:text-zinc-500" />
</button>
{openTooltip === 'commission' && (
<div className="absolute right-0 top-2 z-30 w-72 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">
<p className="font-bold text-zinc-900 dark:text-white mb-2">Commission Hierarchy</p>
<div className="space-y-1.5">
<p><span className="font-bold text-amber-600 dark:text-amber-400">Job Override:</span> Highest priority set per job</p>
<p><span className="font-bold text-purple-600 dark:text-purple-400">User Override:</span> Per-user commission settings</p>
<p><span className="font-bold text-sky-600 dark:text-[#00f0ff]">Org Default:</span> Organization-wide fallback</p>
</div>
<div className="mt-2 pt-2 border-t border-zinc-100 dark:border-white/5">
<p className="text-[10px] text-zinc-500">Priority: Job &rarr; User &rarr; Org</p>
</div>
</div>
)}
</div>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-12 gap-4 items-end">
<div className="sm:col-span-4">
<label className="text-[10px] font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 block mb-1.5">Type</label>
<select
value={activeCommissionConfig.type}
onChange={e => setCommissionConfig(prev => ({ ...(prev || { type: activeCommissionConfig.type, rate: activeCommissionConfig.rate }), type: e.target.value, rate: '' }))}
className="w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl px-4 py-2.5 text-sm text-zinc-900 dark:text-white outline-none focus:border-amber-500 dark:focus:border-amber-500 focus:ring-1 focus:ring-amber-500/30 transition-all"
>
{COMMISSION_TYPES.map(ct => (
<option key={ct.value} value={ct.value}>{ct.label}</option>
))}
</select>
</div>
<div className="sm:col-span-4">
<label className="text-[10px] font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 block mb-1.5">
{activeCommissionConfig.type === 'percent_gross' || activeCommissionConfig.type === 'percent_net' ? 'Rate (%)' : 'Amount ($)'}
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-sm text-zinc-400 dark:text-zinc-500 pointer-events-none">
{activeCommissionConfig.type === 'percent_gross' || activeCommissionConfig.type === 'percent_net' ? '%' : '$'}
</span>
<input
type="number"
step="any"
min="0"
value={activeCommissionConfig.rate}
onChange={e => setCommissionConfig(prev => ({ ...(prev || { type: activeCommissionConfig.type, rate: activeCommissionConfig.rate }), type: activeCommissionConfig.type, rate: e.target.value }))}
placeholder={activeCommissionConfig.type === 'percent_gross' || activeCommissionConfig.type === 'percent_net' ? 'e.g. 10' : 'e.g. 5000'}
className="w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl pl-8 pr-4 py-2.5 text-sm text-zinc-900 dark:text-white placeholder-zinc-400 dark:placeholder-zinc-600 outline-none focus:border-amber-500 dark:focus:border-amber-500 focus:ring-1 focus:ring-amber-500/30 transition-all"
/>
</div>
</div>
<div className="sm:col-span-4">
<label className="text-[10px] font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 block mb-1.5">Calculated Commission</label>
<div className="bg-zinc-100 dark:bg-black/40 border border-zinc-200 dark:border-white/5 rounded-xl px-4 py-2.5 flex items-center justify-between">
<span className="text-lg font-black font-mono text-amber-600 dark:text-[#fda913]">{formatCurrency(profitMetrics.commission)}</span>
</div>
</div>
</div>
<div className="mt-3 px-3 py-2 bg-zinc-50 dark:bg-black/30 rounded-lg border border-zinc-100 dark:border-white/5 flex items-center justify-between">
<p className="text-[11px] font-mono text-zinc-500 dark:text-zinc-400">
<span className="text-amber-600 dark:text-amber-500 font-bold">Formula: </span>
{profitMetrics.commissionFormula}
</p>
</div>
</NeoCard>
{/* Profit Summary Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<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">
<button type="button" onClick={(e) => { e.stopPropagation(); setOpenTooltip(prev => prev === 'gross' ? null : 'gross'); }} className="p-1 rounded-lg hover:bg-zinc-100 dark:hover:bg-white/10 transition-colors">
<Info size={14} className="text-zinc-400 dark:text-zinc-500" />
</button>
{openTooltip === 'gross' && (
<div className="absolute right-0 top-2 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">
<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)} = {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>
<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">
<button type="button" onClick={(e) => { e.stopPropagation(); setOpenTooltip(prev => prev === 'net' ? null : 'net'); }} className="p-1 rounded-lg hover:bg-zinc-100 dark:hover:bg-white/10 transition-colors">
<Info size={14} className="text-zinc-400 dark:text-zinc-500" />
</button>
{openTooltip === 'net' && (
<div className="absolute right-0 top-2 z-30 w-72 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">
<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</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.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 Received Table */}
<NeoCard spotlightColor="rgba(57, 255, 20, 0.1)" innerClassName="p-0 lg:p-0">
<div className="p-5 border-b border-zinc-200 dark:border-white/5 flex items-center justify-between">
<div className="flex items-center gap-3">
<Banknote className={NEON_GREEN} size={20} />
<h3 className="text-lg font-bold text-zinc-900 dark:text-white tracking-widest uppercase">Payments Received</h3>
<span className="text-xs font-bold bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/5 px-2 py-0.5 rounded-full text-zinc-500 dark:text-zinc-400">
{payments.length}
</span>
</div>
<button
onClick={() => setCreateModalConfig({
title: 'Record Payment', icon: Banknote, iconColor: 'text-[#39ff14]', iconBg: 'bg-[#39ff14]/10',
submitLabel: 'Save Payment', submitColor: 'bg-[#39ff14]/20 text-[#39ff14] border-[#39ff14]/30',
fields: [
{ name: 'from', label: 'From / Payer Name', type: 'text', required: true, placeholder: 'e.g. State Farm Insurance' },
{ name: 'method', label: 'Payment Method', type: 'select', options: [{ label: 'Cash', value: 'Cash' }, { label: 'Check', value: 'Check' }, { label: 'ACH', value: 'ACH' }, { label: 'Credit Card', value: 'Credit Card' }, { label: 'Wire Transfer', value: 'Wire Transfer' }], required: true },
{ name: 'amount', label: 'Amount ($)', type: 'number', required: true },
{ name: 'date', label: 'Payment Date', type: 'date', required: true },
{ name: 'refNumber', label: 'Check No. / Reference', type: 'text', placeholder: 'e.g. CHK-1234' },
{ name: 'memo', label: 'Memo / Notes', type: 'textarea' },
],
onSubmit: handleAddPayment
})}
className="bg-[#39ff14]/10 hover:bg-[#39ff14]/20 text-green-600 dark:text-[#39ff14] border border-[#39ff14]/30 px-4 py-2 rounded-lg text-xs font-bold transition flex items-center gap-2"
>
<Plus size={14} /> Record Payment
</button>
</div>
{payments.length > 0 ? (
<>
<div className="overflow-x-auto w-full hidden md:block">
<table className="w-full text-left border-collapse min-w-[800px]">
<thead className="bg-zinc-100 dark:bg-[#18181b]/80 border-b border-zinc-200 dark:border-white/10">
<tr>
<th className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400">From / Method</th>
<th className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 text-right cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors select-none" onClick={() => togglePaymentSort('amount')}>
Amount {paymentSort.field === 'amount' ? (paymentSort.dir === 'asc' ? '↑' : '↓') : ''}
</th>
<th className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors select-none" onClick={() => togglePaymentSort('date')}>
Date {paymentSort.field === 'date' ? (paymentSort.dir === 'asc' ? '↑' : '↓') : ''}
</th>
<th className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400">Check No. / Ref</th>
<th className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400">Memo</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-100 dark:divide-white/5">
{paginatedPayments.map((pay) => (
<tr key={pay.id} className="hover:bg-zinc-50 dark:hover:bg-white/5 transition-colors">
<td className="px-5 py-4">
<div className="font-bold text-sm text-zinc-900 dark:text-white">{pay.from}</div>
<span className={`inline-block mt-1 px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-widest ${
pay.method === 'ACH' ? 'text-sky-600 dark:text-[#00f0ff] bg-sky-50 dark:bg-blue-500/10 border border-sky-200 dark:border-blue-500/20' :
pay.method === 'Check' ? 'text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/20' :
pay.method === 'Credit Card' ? 'text-purple-600 dark:text-purple-400 bg-purple-50 dark:bg-purple-500/10 border border-purple-200 dark:border-purple-500/20' :
pay.method === 'Wire Transfer' ? 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/20' :
'text-zinc-600 dark:text-zinc-400 bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10'
}`}>
{pay.method}
</span>
</td>
<td className="px-5 py-4 text-right font-mono text-sm font-bold text-green-600 dark:text-[#39ff14]">
{formatCurrency(pay.amount)}
</td>
<td className="px-5 py-4 text-sm text-zinc-500 dark:text-zinc-400 font-mono">{pay.date}</td>
<td className="px-5 py-4 font-mono text-xs text-zinc-500 dark:text-zinc-400">{pay.refNumber || '—'}</td>
<td className="px-5 py-4 text-xs text-zinc-500 dark:text-zinc-400 max-w-[200px] truncate">{pay.memo || '—'}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="md:hidden p-4 space-y-3">
{paginatedPayments.map((pay) => (
<div key={pay.id} className="bg-zinc-50 dark:bg-white/[0.03] border border-zinc-100 dark:border-white/[0.06] rounded-2xl p-4 space-y-3">
<div className="flex items-start justify-between">
<div>
<p className="font-bold text-sm text-zinc-900 dark:text-white">{pay.from}</p>
<span className={`inline-block mt-1 px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-widest ${
pay.method === 'ACH' ? 'text-sky-600 dark:text-[#00f0ff] bg-sky-50 dark:bg-blue-500/10 border border-sky-200 dark:border-blue-500/20' :
pay.method === 'Check' ? 'text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/20' :
pay.method === 'Credit Card' ? 'text-purple-600 dark:text-purple-400 bg-purple-50 dark:bg-purple-500/10 border border-purple-200 dark:border-purple-500/20' :
pay.method === 'Wire Transfer' ? 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/20' :
'text-zinc-600 dark:text-zinc-400 bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10'
}`}>
{pay.method}
</span>
</div>
<span className="font-mono text-lg font-black text-green-600 dark:text-[#39ff14]">{formatCurrency(pay.amount)}</span>
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div>
<span className="text-zinc-500 dark:text-zinc-500">Date</span>
<p className="font-mono text-zinc-700 dark:text-zinc-300">{pay.date}</p>
</div>
<div>
<span className="text-zinc-500 dark:text-zinc-500">Ref #</span>
<p className="font-mono text-zinc-700 dark:text-zinc-300">{pay.refNumber || '—'}</p>
</div>
</div>
{pay.memo && (
<p className="text-xs text-zinc-500 dark:text-zinc-400 border-t border-zinc-100 dark:border-white/5 pt-2">{pay.memo}</p>
)}
</div>
))}
</div>
{totalPaymentPages > 1 && (
<div className="px-5 py-4 border-t border-zinc-200 dark:border-white/5 flex items-center justify-between">
<p className="text-xs text-zinc-500 dark:text-zinc-400 font-mono">
{paymentPage * PAYMENTS_PER_PAGE + 1}{Math.min((paymentPage + 1) * PAYMENTS_PER_PAGE, sortedPayments.length)} of {sortedPayments.length}
</p>
<div className="flex gap-2">
<button
onClick={() => setPaymentPage(p => Math.max(0, p - 1))}
disabled={paymentPage === 0}
className="p-2 rounded-lg bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-white/10 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronLeft size={14} />
</button>
<button
onClick={() => setPaymentPage(p => Math.min(totalPaymentPages - 1, p + 1))}
disabled={paymentPage >= totalPaymentPages - 1}
className="p-2 rounded-lg bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-white/10 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronRight size={14} />
</button>
</div>
</div>
)}
</>
) : (
<div className="p-16 text-center text-zinc-500">
<Banknote size={48} className="mx-auto mb-4 text-zinc-400 dark:text-zinc-600" />
<p className="font-bold text-lg text-zinc-900 dark:text-white">No payments recorded yet.</p>
<p className="text-sm mt-2 text-zinc-400">Record your first payment to start tracking.</p>
</div>
)}
</NeoCard>
</div> </div>
)} )}
@@ -982,367 +1320,6 @@ const OwnerProjectDetail = () => {
</NeoCard> </NeoCard>
)} )}
{/* PAYMENTS TAB */}
{activeTab === 'payments' && (
<div className="space-y-6 max-w-7xl mx-auto" onClick={() => openTooltip && setOpenTooltip(null)}>
{/* Financial Summary Row */}
<div className="grid grid-cols-2 sm:grid-cols-3 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">{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">{formatCurrency(profitMetrics.additionalExp)}</div>
<div className="text-[10px] uppercase font-bold tracking-wider text-zinc-500 mt-1">Addl. Expenses</div>
</SpotlightCard>
</div>
{/* Commission Configuration Card */}
<NeoCard spotlightColor="rgba(253, 169, 19, 0.1)" innerClassName="!p-5">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-2">
<DollarSign size={18} className={NEON_GOLD} />
<h4 className="text-sm font-mono font-bold text-zinc-900 dark:text-white uppercase tracking-widest">Commission</h4>
{/* Source Badge */}
{(() => {
const src = resolvedCommission.source;
const badgeCfg = {
org: { icon: Building2, label: 'Org Default', color: 'text-sky-500 dark:text-[#00f0ff]', bg: 'bg-sky-50 dark:bg-[#00f0ff]/10', border: 'border-sky-200 dark:border-[#00f0ff]/20' },
user: { icon: UserIcon, label: 'User Override', color: 'text-purple-500 dark:text-purple-400', bg: 'bg-purple-50 dark:bg-purple-500/10', border: 'border-purple-200 dark:border-purple-500/20' },
job: { icon: Briefcase, label: 'Job Override', color: 'text-amber-600 dark:text-[#fda913]', bg: 'bg-amber-50 dark:bg-[#fda913]/10', border: 'border-amber-200 dark:border-[#fda913]/20' },
};
const b = badgeCfg[src];
const BadgeIcon = b.icon;
return (
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-widest border ${b.color} ${b.bg} ${b.border}`}>
<BadgeIcon size={10} />
{b.label}
</span>
);
})()}
</div>
<div className="flex items-center gap-1">
<button
type="button"
onClick={(e) => { e.stopPropagation(); setCommissionSettingsOpen(true); }}
className="p-1.5 rounded-lg hover:bg-amber-500/10 transition-colors group"
title="Commission Settings"
>
<Settings size={15} className="text-zinc-400 dark:text-zinc-500 group-hover:text-amber-500 dark:group-hover:text-[#fda913] transition-colors" />
</button>
<div className="relative">
<button type="button" onClick={(e) => { e.stopPropagation(); setOpenTooltip(prev => prev === 'commission' ? null : 'commission'); }} className="p-1 rounded-lg hover:bg-zinc-100 dark:hover:bg-white/10 transition-colors">
<Info size={14} className="text-zinc-400 dark:text-zinc-500" />
</button>
{openTooltip === 'commission' && (
<div className="absolute right-0 top-2 z-30 w-72 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">
<p className="font-bold text-zinc-900 dark:text-white mb-2">Commission Hierarchy</p>
<div className="space-y-1.5">
<p><span className="font-bold text-amber-600 dark:text-amber-400">Job Override:</span> Highest priority set per job</p>
<p><span className="font-bold text-purple-600 dark:text-purple-400">User Override:</span> Per-user commission settings</p>
<p><span className="font-bold text-sky-600 dark:text-[#00f0ff]">Org Default:</span> Organization-wide fallback</p>
</div>
<div className="mt-2 pt-2 border-t border-zinc-100 dark:border-white/5">
<p className="text-[10px] text-zinc-500">Priority: Job &rarr; User &rarr; Org</p>
</div>
</div>
)}
</div>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-12 gap-4 items-end">
{/* Type Selector */}
<div className="sm:col-span-4">
<label className="text-[10px] font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 block mb-1.5">Type</label>
<select
value={activeCommissionConfig.type}
onChange={e => setCommissionConfig(prev => ({ ...(prev || { type: activeCommissionConfig.type, rate: activeCommissionConfig.rate }), type: e.target.value, rate: '' }))}
className="w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl px-4 py-2.5 text-sm text-zinc-900 dark:text-white outline-none focus:border-amber-500 dark:focus:border-amber-500 focus:ring-1 focus:ring-amber-500/30 transition-all"
>
{COMMISSION_TYPES.map(ct => (
<option key={ct.value} value={ct.value}>{ct.label}</option>
))}
</select>
</div>
{/* Rate / Value Input */}
<div className="sm:col-span-4">
<label className="text-[10px] font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 block mb-1.5">
{activeCommissionConfig.type === 'percent_gross' || activeCommissionConfig.type === 'percent_net' ? 'Rate (%)' : 'Amount ($)'}
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-sm text-zinc-400 dark:text-zinc-500 pointer-events-none">
{activeCommissionConfig.type === 'percent_gross' || activeCommissionConfig.type === 'percent_net' ? '%' : '$'}
</span>
<input
type="number"
step="any"
min="0"
value={activeCommissionConfig.rate}
onChange={e => setCommissionConfig(prev => ({ ...(prev || { type: activeCommissionConfig.type, rate: activeCommissionConfig.rate }), type: activeCommissionConfig.type, rate: e.target.value }))}
placeholder={activeCommissionConfig.type === 'percent_gross' || activeCommissionConfig.type === 'percent_net' ? 'e.g. 10' : 'e.g. 5000'}
className="w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl pl-8 pr-4 py-2.5 text-sm text-zinc-900 dark:text-white placeholder-zinc-400 dark:placeholder-zinc-600 outline-none focus:border-amber-500 dark:focus:border-amber-500 focus:ring-1 focus:ring-amber-500/30 transition-all"
/>
</div>
</div>
{/* Calculated Result */}
<div className="sm:col-span-4">
<label className="text-[10px] font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 block mb-1.5">Calculated Commission</label>
<div className="bg-zinc-100 dark:bg-black/40 border border-zinc-200 dark:border-white/5 rounded-xl px-4 py-2.5 flex items-center justify-between">
<span className="text-lg font-black font-mono text-amber-600 dark:text-[#fda913]">{formatCurrency(profitMetrics.commission)}</span>
</div>
</div>
</div>
{/* Live Formula Preview */}
<div className="mt-3 px-3 py-2 bg-zinc-50 dark:bg-black/30 rounded-lg border border-zinc-100 dark:border-white/5 flex items-center justify-between">
<p className="text-[11px] font-mono text-zinc-500 dark:text-zinc-400">
<span className="text-amber-600 dark:text-amber-500 font-bold">Formula: </span>
{profitMetrics.commissionFormula}
</p>
</div>
</NeoCard>
{/* 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">
<button type="button" onClick={(e) => { e.stopPropagation(); setOpenTooltip(prev => prev === 'gross' ? null : 'gross'); }} className="p-1 rounded-lg hover:bg-zinc-100 dark:hover:bg-white/10 transition-colors">
<Info size={14} className="text-zinc-400 dark:text-zinc-500" />
</button>
{openTooltip === 'gross' && (
<div className="absolute right-0 top-2 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">
<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)} = {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">
<button type="button" onClick={(e) => { e.stopPropagation(); setOpenTooltip(prev => prev === 'net' ? null : 'net'); }} className="p-1 rounded-lg hover:bg-zinc-100 dark:hover:bg-white/10 transition-colors">
<Info size={14} className="text-zinc-400 dark:text-zinc-500" />
</button>
{openTooltip === 'net' && (
<div className="absolute right-0 top-2 z-30 w-72 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">
<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)} = {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 */}
<NeoCard spotlightColor="rgba(57, 255, 20, 0.1)" innerClassName="p-0 lg:p-0">
<div className="p-5 border-b border-zinc-200 dark:border-white/5 flex items-center justify-between">
<div className="flex items-center gap-3">
<Banknote className={NEON_GREEN} size={20} />
<h3 className="text-lg font-bold text-zinc-900 dark:text-white tracking-widest uppercase">Payments Received</h3>
<span className="text-xs font-bold bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/5 px-2 py-0.5 rounded-full text-zinc-500 dark:text-zinc-400">
{payments.length}
</span>
</div>
<button
onClick={() => setCreateModalConfig({
title: 'Record Payment', icon: Banknote, iconColor: 'text-[#39ff14]', iconBg: 'bg-[#39ff14]/10',
submitLabel: 'Save Payment', submitColor: 'bg-[#39ff14]/20 text-[#39ff14] border-[#39ff14]/30',
fields: [
{ name: 'from', label: 'From / Payer Name', type: 'text', required: true, placeholder: 'e.g. State Farm Insurance' },
{ name: 'method', label: 'Payment Method', type: 'select', options: [{ label: 'Cash', value: 'Cash' }, { label: 'Check', value: 'Check' }, { label: 'ACH', value: 'ACH' }, { label: 'Credit Card', value: 'Credit Card' }, { label: 'Wire Transfer', value: 'Wire Transfer' }], required: true },
{ name: 'amount', label: 'Amount ($)', type: 'number', required: true },
{ name: 'date', label: 'Payment Date', type: 'date', required: true },
{ name: 'refNumber', label: 'Check No. / Reference', type: 'text', placeholder: 'e.g. CHK-1234' },
{ name: 'memo', label: 'Memo / Notes', type: 'textarea' },
],
onSubmit: handleAddPayment
})}
className="bg-[#39ff14]/10 hover:bg-[#39ff14]/20 text-green-600 dark:text-[#39ff14] border border-[#39ff14]/30 px-4 py-2 rounded-lg text-xs font-bold transition flex items-center gap-2"
>
<Plus size={14} /> Record Payment
</button>
</div>
{payments.length > 0 ? (
<>
{/* Desktop Table */}
<div className="overflow-x-auto w-full hidden md:block">
<table className="w-full text-left border-collapse min-w-[800px]">
<thead className="bg-zinc-100 dark:bg-[#18181b]/80 border-b border-zinc-200 dark:border-white/10">
<tr>
<th className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400">From / Method</th>
<th
className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 text-right cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors select-none"
onClick={() => togglePaymentSort('amount')}
>
Amount {paymentSort.field === 'amount' ? (paymentSort.dir === 'asc' ? '↑' : '↓') : ''}
</th>
<th
className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors select-none"
onClick={() => togglePaymentSort('date')}
>
Date {paymentSort.field === 'date' ? (paymentSort.dir === 'asc' ? '↑' : '↓') : ''}
</th>
<th className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400">Check No. / Ref</th>
<th className="px-5 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 dark:text-zinc-400">Memo</th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-100 dark:divide-white/5">
{paginatedPayments.map((pay) => (
<tr key={pay.id} className="hover:bg-zinc-50 dark:hover:bg-white/5 transition-colors">
<td className="px-5 py-4">
<div className="font-bold text-sm text-zinc-900 dark:text-white">{pay.from}</div>
<span className={`inline-block mt-1 px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-widest ${
pay.method === 'ACH' ? 'text-sky-600 dark:text-[#00f0ff] bg-sky-50 dark:bg-blue-500/10 border border-sky-200 dark:border-blue-500/20' :
pay.method === 'Check' ? 'text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/20' :
pay.method === 'Credit Card' ? 'text-purple-600 dark:text-purple-400 bg-purple-50 dark:bg-purple-500/10 border border-purple-200 dark:border-purple-500/20' :
pay.method === 'Wire Transfer' ? 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/20' :
'text-zinc-600 dark:text-zinc-400 bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10'
}`}>
{pay.method}
</span>
</td>
<td className="px-5 py-4 text-right font-mono text-sm font-bold text-green-600 dark:text-[#39ff14]">
{formatCurrency(pay.amount)}
</td>
<td className="px-5 py-4 text-sm text-zinc-500 dark:text-zinc-400 font-mono">{pay.date}</td>
<td className="px-5 py-4 font-mono text-xs text-zinc-500 dark:text-zinc-400">{pay.refNumber || '—'}</td>
<td className="px-5 py-4 text-xs text-zinc-500 dark:text-zinc-400 max-w-[200px] truncate">{pay.memo || '—'}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Mobile Card View */}
<div className="md:hidden p-4 space-y-3">
{paginatedPayments.map((pay) => (
<div key={pay.id} className="bg-zinc-50 dark:bg-white/[0.03] border border-zinc-100 dark:border-white/[0.06] rounded-2xl p-4 space-y-3">
<div className="flex items-start justify-between">
<div>
<p className="font-bold text-sm text-zinc-900 dark:text-white">{pay.from}</p>
<span className={`inline-block mt-1 px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-widest ${
pay.method === 'ACH' ? 'text-sky-600 dark:text-[#00f0ff] bg-sky-50 dark:bg-blue-500/10 border border-sky-200 dark:border-blue-500/20' :
pay.method === 'Check' ? 'text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/20' :
pay.method === 'Credit Card' ? 'text-purple-600 dark:text-purple-400 bg-purple-50 dark:bg-purple-500/10 border border-purple-200 dark:border-purple-500/20' :
pay.method === 'Wire Transfer' ? 'text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 border border-emerald-200 dark:border-emerald-500/20' :
'text-zinc-600 dark:text-zinc-400 bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10'
}`}>
{pay.method}
</span>
</div>
<span className="font-mono text-lg font-black text-green-600 dark:text-[#39ff14]">{formatCurrency(pay.amount)}</span>
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div>
<span className="text-zinc-500 dark:text-zinc-500">Date</span>
<p className="font-mono text-zinc-700 dark:text-zinc-300">{pay.date}</p>
</div>
<div>
<span className="text-zinc-500 dark:text-zinc-500">Ref #</span>
<p className="font-mono text-zinc-700 dark:text-zinc-300">{pay.refNumber || '—'}</p>
</div>
</div>
{pay.memo && (
<p className="text-xs text-zinc-500 dark:text-zinc-400 border-t border-zinc-100 dark:border-white/5 pt-2">{pay.memo}</p>
)}
</div>
))}
</div>
{/* Pagination */}
{totalPaymentPages > 1 && (
<div className="px-5 py-4 border-t border-zinc-200 dark:border-white/5 flex items-center justify-between">
<p className="text-xs text-zinc-500 dark:text-zinc-400 font-mono">
{paymentPage * PAYMENTS_PER_PAGE + 1}{Math.min((paymentPage + 1) * PAYMENTS_PER_PAGE, sortedPayments.length)} of {sortedPayments.length}
</p>
<div className="flex gap-2">
<button
onClick={() => setPaymentPage(p => Math.max(0, p - 1))}
disabled={paymentPage === 0}
className="p-2 rounded-lg bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-white/10 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronLeft size={14} />
</button>
<button
onClick={() => setPaymentPage(p => Math.min(totalPaymentPages - 1, p + 1))}
disabled={paymentPage >= totalPaymentPages - 1}
className="p-2 rounded-lg bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-white/10 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronRight size={14} />
</button>
</div>
</div>
)}
</>
) : (
<div className="p-16 text-center text-zinc-500">
<Banknote size={48} className="mx-auto mb-4 text-zinc-400 dark:text-zinc-600" />
<p className="font-bold text-lg text-zinc-900 dark:text-white">No payments recorded yet.</p>
<p className="text-sm mt-2 text-zinc-400">Record your first payment to start tracking.</p>
</div>
)}
</NeoCard>
</div>
)}
{/* RISK & ISSUES TAB */} {/* RISK & ISSUES TAB */}
{activeTab === 'risks' && ( {activeTab === 'risks' && (
<div className="space-y-6"> <div className="space-y-6">