Files
avbeat-com/src/app/admin/accounting/page.tsx

158 lines
8.0 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import { useAuth } from '@/contexts/AuthContext';
import { useRouter } from 'next/navigation';
const STAT_CARDS = [
{ key: 'revenue_ytd', label: 'Revenue YTD', color: 'text-green-400', bg: 'bg-green-400/10', href: '/admin/accounting/invoices' },
{ key: 'expenses_ytd', label: 'Expenses YTD', color: 'text-red-400', bg: 'bg-red-400/10', href: '/admin/accounting/expenses' },
{ key: 'net_profit', label: 'Net Profit YTD', color: 'text-blue-400', bg: 'bg-blue-400/10', href: '/admin/accounting/reports' },
{ key: 'outstanding', label: 'Outstanding Invoices', color: 'text-yellow-400', bg: 'bg-yellow-400/10', href: '/admin/accounting/invoices?status=pending' },
{ key: 'unreconciled', label: 'Unreconciled Transactions', color: 'text-orange-400', bg: 'bg-orange-400/10', href: '/admin/accounting/reconciliation' },
{ key: 'commissions_outstanding', label: 'Commissions Outstanding', color: 'text-purple-400', bg: 'bg-purple-400/10', href: '/admin/accounting/commissions' },
];
function fmt(cents: number) {
return '$' + (cents / 100).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
export default function AccountingDashboard() {
const { user, loading } = useAuth();
const router = useRouter();
const [stats, setStats] = useState<Record<string, number>>({});
const [siteRevenue, setSiteRevenue] = useState<any[]>([]);
const [topClients, setTopClients] = useState<any[]>([]);
const [loadingData, setLoadingData] = useState(true);
useEffect(() => {
if (!loading && !user) router.push('/login');
}, [user, loading, router]);
useEffect(() => {
if (!user) return;
fetch('/api/accounting/dashboard')
.then(r => r.json())
.then(d => {
setStats(d.stats ?? {});
setSiteRevenue(d.siteRevenue ?? []);
setTopClients(d.topClients ?? []);
})
.catch(() => {})
.finally(() => setLoadingData(false));
}, [user]);
if (loading || loadingData) return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
</div>
);
if (!user) return null;
return (
<div className="min-h-screen bg-[#0a0a0a] text-white p-6">
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-white">Accounting Dashboard</h1>
<p className="text-[#888] text-sm mt-1">Relevant Media Properties, LLC</p>
</div>
<div className="flex gap-2 flex-wrap">
<Link href="/admin/accounting/clients" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">+ New Client</Link>
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">+ New Order</Link>
<Link href="/admin/accounting/invoices" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">+ New Invoice</Link>
<Link href="/admin/accounting/expenses" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">+ Log Expense</Link>
<Link href="/admin/accounting/reconciliation" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">Upload Bank CSV</Link>
</div>
</div>
{/* Stat Cards */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
{STAT_CARDS.map(c => (
<Link key={c.key} href={c.href} className="bg-[#111] border border-[#252525] rounded-lg p-4 hover:border-[#333] transition-colors">
<p className={`text-lg font-bold ${c.color}`}>{fmt(stats[c.key] ?? 0)}</p>
<p className="text-xs text-[#888] mt-1">{c.label}</p>
</Link>
))}
</div>
{/* Site Revenue Table */}
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#252525]">
<h2 className="text-sm font-semibold text-white">Revenue by Site</h2>
</div>
<table className="w-full text-sm">
<thead>
<tr className="border-b border-[#1a1a1a]">
{['Site','Revenue MTD','Revenue YTD','Outstanding','Active Orders'].map(h => (
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))}
</tr>
</thead>
<tbody>
{siteRevenue.length === 0 ? (
<tr><td colSpan={5} className="px-4 py-6 text-center text-[#555] text-xs">No data yet</td></tr>
) : siteRevenue.map((row: any) => (
<tr key={row.site} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
<td className="px-4 py-2 text-white font-medium capitalize">{row.site}</td>
<td className="px-4 py-2 text-green-400">{fmt(row.revenue_mtd ?? 0)}</td>
<td className="px-4 py-2 text-green-400">{fmt(row.revenue_ytd ?? 0)}</td>
<td className="px-4 py-2 text-yellow-400">{fmt(row.outstanding ?? 0)}</td>
<td className="px-4 py-2 text-[#ccc]">{row.active_orders ?? 0}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Top Clients */}
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#252525]">
<h2 className="text-sm font-semibold text-white">Top 5 Clients YTD</h2>
</div>
<table className="w-full text-sm">
<thead>
<tr className="border-b border-[#1a1a1a]">
{['Client','Revenue YTD','Active Orders'].map(h => (
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))}
</tr>
</thead>
<tbody>
{topClients.length === 0 ? (
<tr><td colSpan={3} className="px-4 py-6 text-center text-[#555] text-xs">No data yet</td></tr>
) : topClients.map((c: any) => (
<tr key={c.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
<td className="px-4 py-2 text-white">{c.company_name}</td>
<td className="px-4 py-2 text-green-400">{fmt(c.revenue_ytd ?? 0)}</td>
<td className="px-4 py-2 text-[#ccc]">{c.active_orders ?? 0}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Sub-nav links */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
{[
{ label: 'Clients', href: '/admin/accounting/clients' },
{ label: 'Orders', href: '/admin/accounting/orders' },
{ label: 'Invoices', href: '/admin/accounting/invoices' },
{ label: 'Staff & Commissions', href: '/admin/accounting/staff' },
{ label: 'Expenses', href: '/admin/accounting/expenses' },
{ label: 'Reconciliation', href: '/admin/accounting/reconciliation' },
{ label: 'Commissions', href: '/admin/accounting/commissions' },
{ label: 'Reports', href: '/admin/accounting/reports' },
{ label: 'Documents', href: '/admin/accounting/documents' },
].map(l => (
<Link key={l.href} href={l.href} className="bg-[#111] border border-[#252525] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#3b82f6] transition-colors">
{l.label}
</Link>
))}
</div>
</div>
</div>
);
}