AV: precise recolor to spec — warm dark backgrounds (#1c1815/#231d18/#26201a/#15110e) + gold #F0A623 / orange #D85A30 accents + warm text (#FBEFE0/#C9BBA8/#8a7e6e). 138 component files + 36 token swaps.
This commit is contained in:
@@ -91,7 +91,7 @@ export default function ClientsPage() {
|
||||
setForm({ company_name: '', contact_name: '', contact_email: '', contact_phone: '', billing_address: '', notes: '' });
|
||||
}
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -102,7 +102,7 @@ export default function ClientsPage() {
|
||||
<h1 className="text-xl font-bold">Clients</h1>
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 transition-colors">+ New Client</button>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 transition-colors">+ New Client</button>
|
||||
</div>
|
||||
|
||||
{/* Success Toast */}
|
||||
@@ -116,14 +116,14 @@ export default function ClientsPage() {
|
||||
{/* Filters */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{['active','inactive','all'].map(s => (
|
||||
<button key={s} onClick={() => setFilter(s)} className={`px-3 py-1 rounded text-xs capitalize ${filter === s ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888] hover:text-white'}`}>{s}</button>
|
||||
<button key={s} onClick={() => setFilter(s)} className={`px-3 py-1 rounded text-xs capitalize ${filter === s ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888] hover:text-white'}`}>{s}</button>
|
||||
))}
|
||||
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search clients…" className="ml-auto px-3 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#ffb800]" />
|
||||
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search clients…" className="ml-auto px-3 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
|
||||
{/* New Client Form */}
|
||||
{showForm && (
|
||||
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#252525] rounded-lg p-4 space-y-3">
|
||||
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3">
|
||||
<h3 className="text-sm font-semibold">New Client</h3>
|
||||
|
||||
{errors._form && (
|
||||
@@ -148,7 +148,7 @@ export default function ClientsPage() {
|
||||
type={f.key === 'contact_email' ? 'email' : 'text'}
|
||||
value={(form as any)[f.key]}
|
||||
onChange={e => handleChange(f.key, e.target.value)}
|
||||
className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors[f.key] ? 'border-red-500/60' : 'border-[#252525]'}`}
|
||||
className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors[f.key] ? 'border-red-500/60' : 'border-[#3a322b]'}`}
|
||||
/>
|
||||
{errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>}
|
||||
</div>
|
||||
@@ -156,26 +156,26 @@ export default function ClientsPage() {
|
||||
<div className="col-span-2">
|
||||
<label className="text-xs text-[#888] block mb-1">Billing Address</label>
|
||||
<input value={form.billing_address} onChange={e => handleChange('billing_address', e.target.value)}
|
||||
className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="text-xs text-[#888] block mb-1">Notes</label>
|
||||
<textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2}
|
||||
className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Client'}</button>
|
||||
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Client'}</button>
|
||||
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['Company','Contact','Email','Active Orders','Total Revenue','Status',''].map(h => (
|
||||
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
|
||||
))}
|
||||
@@ -187,17 +187,17 @@ export default function ClientsPage() {
|
||||
) : clients.length === 0 ? (
|
||||
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No clients found</td></tr>
|
||||
) : clients.map(c => (
|
||||
<tr key={c.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
|
||||
<tr key={c.id} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<td className="px-4 py-2 text-white font-medium">{c.company_name}</td>
|
||||
<td className="px-4 py-2 text-[#ccc]">{c.contact_name}</td>
|
||||
<td className="px-4 py-2 text-[#888]">{c.contact_email}</td>
|
||||
<td className="px-4 py-2 text-[#ccc]">{c.active_orders ?? 0}</td>
|
||||
<td className="px-4 py-2 text-green-400">{fmt(c.total_revenue ?? 0)}</td>
|
||||
<td className="px-4 py-2">
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${c.status === 'active' ? 'bg-green-400/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{c.status}</span>
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${c.status === 'active' ? 'bg-green-400/10 text-green-400' : 'bg-[#231d18] text-[#555]'}`}>{c.status}</span>
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
<Link href={`/admin/accounting/clients/${c.id}`} className="text-xs text-[#ffb800] hover:underline">View</Link>
|
||||
<Link href={`/admin/accounting/clients/${c.id}`} className="text-xs text-[#F0A623] hover:underline">View</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function CommissionsPage() {
|
||||
load();
|
||||
}
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -53,10 +53,10 @@ export default function CommissionsPage() {
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<select value={year} onChange={e => setYear(Number(e.target.value))} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
||||
<select value={year} onChange={e => setYear(Number(e.target.value))} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none">
|
||||
{[2024, 2025, 2026, 2027].map(y => <option key={y} value={y}>{y}</option>)}
|
||||
</select>
|
||||
<select value={filterStaff} onChange={e => setFilterStaff(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
||||
<select value={filterStaff} onChange={e => setFilterStaff(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none">
|
||||
<option value="">All Staff</option>
|
||||
{staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)}
|
||||
</select>
|
||||
@@ -68,7 +68,7 @@ export default function CommissionsPage() {
|
||||
{summary.map((s: any) => {
|
||||
const pct = s.next_threshold_cents ? Math.min(100, Math.round((s.ytd_sales_cents / s.next_threshold_cents) * 100)) : 100;
|
||||
return (
|
||||
<div key={s.staff_id} className="bg-[#111] border border-[#252525] rounded-lg p-4 space-y-2">
|
||||
<div key={s.staff_id} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-white">{s.full_name}</p>
|
||||
<span className="text-xs text-[#888]">{s.current_tier}</span>
|
||||
@@ -83,8 +83,8 @@ export default function CommissionsPage() {
|
||||
<div className="flex justify-between text-xs text-[#555] mb-1">
|
||||
<span>Tier progress</span><span>{pct}%</span>
|
||||
</div>
|
||||
<div className="h-1.5 bg-[#1a1a1a] rounded-full overflow-hidden">
|
||||
<div className="h-full bg-[#ffb800] rounded-full transition-all" style={{ width: `${pct}%` }} />
|
||||
<div className="h-1.5 bg-[#231d18] rounded-full overflow-hidden">
|
||||
<div className="h-full bg-[#F0A623] rounded-full transition-all" style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -94,10 +94,10 @@ export default function CommissionsPage() {
|
||||
</div>
|
||||
|
||||
{/* Detail table */}
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-x-auto">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['Staff','Invoice','Sale Amount','Rate','Commission','YTD at Time','Paid',''].map(h => (
|
||||
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
|
||||
))}
|
||||
@@ -109,9 +109,9 @@ export default function CommissionsPage() {
|
||||
) : commissions.length === 0 ? (
|
||||
<tr><td colSpan={8} className="px-4 py-8 text-center text-[#555] text-xs">No commission records</td></tr>
|
||||
) : commissions.map((c: any) => (
|
||||
<tr key={c.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
|
||||
<tr key={c.id} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<td className="px-3 py-2 text-white text-xs">{c.rmp_sales_staff?.full_name ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#ffb800] text-xs font-mono">{c.rmp_invoices?.invoice_number ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#F0A623] text-xs font-mono">{c.rmp_invoices?.invoice_number ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#ccc] text-xs">{fmt(c.sale_amount_cents ?? 0)}</td>
|
||||
<td className="px-3 py-2 text-[#888] text-xs">{((c.commission_rate ?? 0) * 100).toFixed(1)}%</td>
|
||||
<td className="px-3 py-2 text-green-400 text-xs">{fmt(c.commission_amount_cents ?? 0)}</td>
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function DocumentsPage() {
|
||||
|
||||
const years = Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i);
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -54,70 +54,70 @@ export default function DocumentsPage() {
|
||||
<h1 className="text-xl font-bold">Document Vault</h1>
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Upload Document</button>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Upload Document</button>
|
||||
</div>
|
||||
|
||||
{/* Year tabs */}
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
<button onClick={() => setFilterYear('')} className={`px-3 py-1 rounded text-xs ${!filterYear ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888]'}`}>All Years</button>
|
||||
<button onClick={() => setFilterYear('')} className={`px-3 py-1 rounded text-xs ${!filterYear ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888]'}`}>All Years</button>
|
||||
{years.map(y => (
|
||||
<button key={y} onClick={() => setFilterYear(String(y))} className={`px-3 py-1 rounded text-xs ${filterYear === String(y) ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888]'}`}>{y}</button>
|
||||
<button key={y} onClick={() => setFilterYear(String(y))} className={`px-3 py-1 rounded text-xs ${filterYear === String(y) ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888]'}`}>{y}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<select value={filterType} onChange={e => setFilterType(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
||||
<select value={filterType} onChange={e => setFilterType(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none">
|
||||
<option value="">All Types</option>
|
||||
{DOC_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{showForm && (
|
||||
<form onSubmit={handleCreate} className="bg-[#111] border border-[#252525] rounded-lg p-4 space-y-3">
|
||||
<form onSubmit={handleCreate} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3">
|
||||
<h3 className="text-sm font-semibold">Upload Document</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Type</label>
|
||||
<select value={form.document_type} onChange={e => setForm(p => ({ ...p, document_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none">
|
||||
<select value={form.document_type} onChange={e => setForm(p => ({ ...p, document_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none">
|
||||
{DOC_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Title *</label>
|
||||
<input required value={form.title} onChange={e => setForm(p => ({ ...p, title: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input required value={form.title} onChange={e => setForm(p => ({ ...p, title: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
{form.document_type === 'sales_agreement' && (
|
||||
<>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Person *</label>
|
||||
<input required value={form.person} onChange={e => setForm(p => ({ ...p, person: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input required value={form.person} onChange={e => setForm(p => ({ ...p, person: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Year *</label>
|
||||
<input type="number" required value={form.year} onChange={e => setForm(p => ({ ...p, year: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input type="number" required value={form.year} onChange={e => setForm(p => ({ ...p, year: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="col-span-2">
|
||||
<label className="text-xs text-[#888] block mb-1">File URL</label>
|
||||
<input value={form.file_url} onChange={e => setForm(p => ({ ...p, file_url: e.target.value }))} placeholder="https://…" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input value={form.file_url} onChange={e => setForm(p => ({ ...p, file_url: e.target.value }))} placeholder="https://…" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="text-xs text-[#888] block mb-1">Notes</label>
|
||||
<textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Save Document'}</button>
|
||||
<button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Save Document'}</button>
|
||||
<button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['Type','Title','Person','Year','Date',''].map(h => (
|
||||
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
|
||||
))}
|
||||
@@ -129,14 +129,14 @@ export default function DocumentsPage() {
|
||||
) : docs.length === 0 ? (
|
||||
<tr><td colSpan={6} className="px-4 py-8 text-center text-[#555] text-xs">No documents found</td></tr>
|
||||
) : docs.map((d: any) => (
|
||||
<tr key={d.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
|
||||
<tr key={d.id} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<td className="px-4 py-2 text-[#888] text-xs capitalize">{d.document_type?.replace(/_/g, ' ')}</td>
|
||||
<td className="px-4 py-2 text-white text-xs">{d.title}</td>
|
||||
<td className="px-4 py-2 text-[#ccc] text-xs">{d.person ?? '—'}</td>
|
||||
<td className="px-4 py-2 text-[#888] text-xs">{d.year ?? '—'}</td>
|
||||
<td className="px-4 py-2 text-[#555] text-xs">{d.uploaded_at ? new Date(d.uploaded_at).toLocaleDateString() : '—'}</td>
|
||||
<td className="px-4 py-2 flex gap-2">
|
||||
{d.file_url && <a href={d.file_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#ffb800] hover:underline">Download</a>}
|
||||
{d.file_url && <a href={d.file_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#F0A623] hover:underline">Download</a>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function ExpensesPage() {
|
||||
setForm({ vendor: '', amount: '', category: 'other', description: '', expense_date: new Date().toISOString().split('T')[0], receipt_source: 'manual' });
|
||||
}
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -95,7 +95,7 @@ export default function ExpensesPage() {
|
||||
<h1 className="text-xl font-bold">Expenses</h1>
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Log Expense</button>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Log Expense</button>
|
||||
</div>
|
||||
|
||||
{/* Success Toast */}
|
||||
@@ -107,11 +107,11 @@ export default function ExpensesPage() {
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<select value={filterCat} onChange={e => setFilterCat(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
||||
<select value={filterCat} onChange={e => setFilterCat(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none">
|
||||
<option value="">All Categories</option>
|
||||
{CATEGORIES.map(c => <option key={c} value={c}>{c.replace(/_/g, ' ')}</option>)}
|
||||
</select>
|
||||
<select value={filterReconciled} onChange={e => setFilterReconciled(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
||||
<select value={filterReconciled} onChange={e => setFilterReconciled(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none">
|
||||
<option value="">All</option>
|
||||
<option value="true">Reconciled</option>
|
||||
<option value="false">Unreconciled</option>
|
||||
@@ -119,7 +119,7 @@ export default function ExpensesPage() {
|
||||
</div>
|
||||
|
||||
{showForm && (
|
||||
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#252525] rounded-lg p-4 space-y-3">
|
||||
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3">
|
||||
<h3 className="text-sm font-semibold">Log Expense</h3>
|
||||
|
||||
{errors._form && (
|
||||
@@ -132,40 +132,40 @@ export default function ExpensesPage() {
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Vendor</label>
|
||||
<input type="text" value={form.vendor} onChange={e => handleChange('vendor', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input type="text" value={form.vendor} onChange={e => handleChange('vendor', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Amount ($) <span className="text-red-400">*</span></label>
|
||||
<input type="number" step="0.01" value={form.amount} onChange={e => handleChange('amount', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.amount ? 'border-red-500/60' : 'border-[#252525]'}`} />
|
||||
<input type="number" step="0.01" value={form.amount} onChange={e => handleChange('amount', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.amount ? 'border-red-500/60' : 'border-[#3a322b]'}`} />
|
||||
{errors.amount && <p className="mt-1 text-xs text-red-400">{errors.amount}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Date <span className="text-red-400">*</span></label>
|
||||
<input type="date" value={form.expense_date} onChange={e => handleChange('expense_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.expense_date ? 'border-red-500/60' : 'border-[#252525]'}`} />
|
||||
<input type="date" value={form.expense_date} onChange={e => handleChange('expense_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.expense_date ? 'border-red-500/60' : 'border-[#3a322b]'}`} />
|
||||
{errors.expense_date && <p className="mt-1 text-xs text-red-400">{errors.expense_date}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Category</label>
|
||||
<select value={form.category} onChange={e => handleChange('category', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none">
|
||||
<select value={form.category} onChange={e => handleChange('category', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none">
|
||||
{CATEGORIES.map(c => <option key={c} value={c}>{c.replace(/_/g, ' ')}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="text-xs text-[#888] block mb-1">Description</label>
|
||||
<input value={form.description} onChange={e => handleChange('description', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input value={form.description} onChange={e => handleChange('description', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Log Expense'}</button>
|
||||
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Log Expense'}</button>
|
||||
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-x-auto">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['Date','Vendor','Amount','Category','Source','Reconciled','Description'].map(h => (
|
||||
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
|
||||
))}
|
||||
@@ -177,7 +177,7 @@ export default function ExpensesPage() {
|
||||
) : expenses.length === 0 ? (
|
||||
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No expenses found</td></tr>
|
||||
) : expenses.map((ex: any) => (
|
||||
<tr key={ex.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
|
||||
<tr key={ex.id} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<td className="px-3 py-2 text-[#888] text-xs">{ex.expense_date}</td>
|
||||
<td className="px-3 py-2 text-white text-xs">{ex.vendor ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-red-400 text-xs">{ex.amount_cents ? fmt(ex.amount_cents) : '—'}</td>
|
||||
|
||||
@@ -10,7 +10,7 @@ const STATUS_COLORS: Record<string, string> = {
|
||||
paid: 'bg-green-400/10 text-green-400',
|
||||
pending: 'bg-yellow-400/10 text-yellow-400',
|
||||
overdue: 'bg-red-400/10 text-red-400',
|
||||
cancelled: 'bg-[#1a1a1a] text-[#555]',
|
||||
cancelled: 'bg-[#231d18] text-[#555]',
|
||||
};
|
||||
|
||||
function InvoicesContent() {
|
||||
@@ -83,7 +83,7 @@ function InvoicesContent() {
|
||||
setPayForm({ method: 'check', paid_date: new Date().toISOString().split('T')[0], wire_reference: '' });
|
||||
}
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -94,7 +94,7 @@ function InvoicesContent() {
|
||||
<h1 className="text-xl font-bold">Invoices</h1>
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ New Invoice (via Order)</Link>
|
||||
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ New Invoice (via Order)</Link>
|
||||
</div>
|
||||
|
||||
{/* Success Toast */}
|
||||
@@ -108,14 +108,14 @@ function InvoicesContent() {
|
||||
{/* Filters */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{['','pending','paid','overdue','cancelled'].map(s => (
|
||||
<button key={s} onClick={() => setFilterStatus(s)} className={`px-3 py-1 rounded text-xs capitalize ${filterStatus === s ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888] hover:text-white'}`}>{s || 'All'}</button>
|
||||
<button key={s} onClick={() => setFilterStatus(s)} className={`px-3 py-1 rounded text-xs capitalize ${filterStatus === s ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888] hover:text-white'}`}>{s || 'All'}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mark Paid Modal */}
|
||||
{markPaidId && (
|
||||
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
|
||||
<form onSubmit={handleMarkPaid} noValidate className="bg-[#111] border border-[#252525] rounded-lg p-5 w-80 space-y-3">
|
||||
<form onSubmit={handleMarkPaid} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-5 w-80 space-y-3">
|
||||
<h3 className="text-sm font-semibold">Mark Invoice as Paid</h3>
|
||||
|
||||
{payErrors._form && (
|
||||
@@ -127,35 +127,35 @@ function InvoicesContent() {
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Payment Method</label>
|
||||
<select value={payForm.method} onChange={e => handlePayChange('method', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none">
|
||||
<select value={payForm.method} onChange={e => handlePayChange('method', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none">
|
||||
{['stripe','wire','check','other'].map(m => <option key={m} value={m}>{m}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Paid Date <span className="text-red-400">*</span></label>
|
||||
<input type="date" value={payForm.paid_date} onChange={e => handlePayChange('paid_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${payErrors.paid_date ? 'border-red-500/60' : 'border-[#252525]'}`} />
|
||||
<input type="date" value={payForm.paid_date} onChange={e => handlePayChange('paid_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${payErrors.paid_date ? 'border-red-500/60' : 'border-[#3a322b]'}`} />
|
||||
{payErrors.paid_date && <p className="mt-1 text-xs text-red-400">{payErrors.paid_date}</p>}
|
||||
</div>
|
||||
{payForm.method === 'wire' && (
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Wire Reference <span className="text-red-400">*</span></label>
|
||||
<input value={payForm.wire_reference} onChange={e => handlePayChange('wire_reference', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${payErrors.wire_reference ? 'border-red-500/60' : 'border-[#252525]'}`} />
|
||||
<input value={payForm.wire_reference} onChange={e => handlePayChange('wire_reference', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${payErrors.wire_reference ? 'border-red-500/60' : 'border-[#3a322b]'}`} />
|
||||
{payErrors.wire_reference && <p className="mt-1 text-xs text-red-400">{payErrors.wire_reference}</p>}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-green-600 rounded text-xs font-medium hover:bg-green-500 disabled:opacity-50">{saving ? 'Saving…' : 'Mark Paid'}</button>
|
||||
<button type="button" onClick={handleCloseModal} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
<button type="button" onClick={handleCloseModal} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-x-auto">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['Invoice#','Client','Site','Order#','Amount','Status','Due Date','Salesperson',''].map(h => (
|
||||
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th>
|
||||
))}
|
||||
@@ -167,14 +167,14 @@ function InvoicesContent() {
|
||||
) : invoices.length === 0 ? (
|
||||
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No invoices found</td></tr>
|
||||
) : invoices.map((inv: any) => (
|
||||
<tr key={inv.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
|
||||
<td className="px-3 py-2 text-[#ffb800] font-mono text-xs">{inv.invoice_number}</td>
|
||||
<tr key={inv.id} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<td className="px-3 py-2 text-[#F0A623] font-mono text-xs">{inv.invoice_number}</td>
|
||||
<td className="px-3 py-2 text-white text-xs">{inv.rmp_clients?.company_name ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#888] text-xs capitalize">{inv.site}</td>
|
||||
<td className="px-3 py-2 text-[#888] text-xs font-mono">{inv.rmp_orders?.internal_order_number ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-green-400 text-xs">{inv.amount_cents ? fmt(inv.amount_cents) : '—'}</td>
|
||||
<td className="px-3 py-2">
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${STATUS_COLORS[inv.status] ?? 'bg-[#1a1a1a] text-[#555]'}`}>{inv.status}</span>
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${STATUS_COLORS[inv.status] ?? 'bg-[#231d18] text-[#555]'}`}>{inv.status}</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-[#888] text-xs">{inv.due_date ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#ccc] text-xs">{inv.rmp_sales_staff?.full_name ?? '—'}</td>
|
||||
@@ -195,7 +195,7 @@ function InvoicesContent() {
|
||||
|
||||
export default function InvoicesPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>}>
|
||||
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>}>
|
||||
<InvoicesContent />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -104,7 +104,7 @@ function OrdersContent() {
|
||||
setForm({ client_id: '', site: '', product_id: '', ad_unit: '', description: '', start_date: '', end_date: '', total_amount_cents: '', currency: 'USD', invoicing_schedule: 'single', next_invoice_date: '', staff_id: '', po_number: '', notes: '' });
|
||||
}
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -115,7 +115,7 @@ function OrdersContent() {
|
||||
<h1 className="text-xl font-bold">Orders</h1>
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ New Order</button>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ New Order</button>
|
||||
</div>
|
||||
|
||||
{/* Success Toast */}
|
||||
@@ -128,20 +128,20 @@ function OrdersContent() {
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex gap-2 flex-wrap items-center">
|
||||
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
||||
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none">
|
||||
<option value="">All Sites</option>
|
||||
{SITES.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
|
||||
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none">
|
||||
<option value="">All Statuses</option>
|
||||
{STATUSES.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
<input value={filterClient} onChange={e => setFilterClient(e.target.value)} placeholder="Filter by client…" className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#ffb800]" />
|
||||
<input value={filterClient} onChange={e => setFilterClient(e.target.value)} placeholder="Filter by client…" className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
|
||||
{/* New Order Form */}
|
||||
{showForm && (
|
||||
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#252525] rounded-lg p-4 space-y-3">
|
||||
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3">
|
||||
<h3 className="text-sm font-semibold">New Order</h3>
|
||||
|
||||
{errors._form && (
|
||||
@@ -154,7 +154,7 @@ function OrdersContent() {
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Client <span className="text-red-400">*</span></label>
|
||||
<select value={form.client_id} onChange={e => handleChange('client_id', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.client_id ? 'border-red-500/60' : 'border-[#252525]'}`}>
|
||||
<select value={form.client_id} onChange={e => handleChange('client_id', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.client_id ? 'border-red-500/60' : 'border-[#3a322b]'}`}>
|
||||
<option value="">Select client…</option>
|
||||
{clients.map((c: any) => <option key={c.id} value={c.id}>{c.company_name}</option>)}
|
||||
</select>
|
||||
@@ -162,7 +162,7 @@ function OrdersContent() {
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Site <span className="text-red-400">*</span></label>
|
||||
<select value={form.site} onChange={e => handleChange('site', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.site ? 'border-red-500/60' : 'border-[#252525]'}`}>
|
||||
<select value={form.site} onChange={e => handleChange('site', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.site ? 'border-red-500/60' : 'border-[#3a322b]'}`}>
|
||||
<option value="">Select site…</option>
|
||||
{SITES.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
@@ -170,67 +170,67 @@ function OrdersContent() {
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Product</label>
|
||||
<select value={form.product_id} onChange={e => handleChange('product_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]">
|
||||
<select value={form.product_id} onChange={e => handleChange('product_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]">
|
||||
<option value="">Select product…</option>
|
||||
{products.map((p: any) => <option key={p.id} value={p.id}>{p.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Ad Unit</label>
|
||||
<input value={form.ad_unit} onChange={e => handleChange('ad_unit', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input value={form.ad_unit} onChange={e => handleChange('ad_unit', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Start Date</label>
|
||||
<input type="date" value={form.start_date} onChange={e => handleChange('start_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input type="date" value={form.start_date} onChange={e => handleChange('start_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">End Date</label>
|
||||
<input type="date" value={form.end_date} onChange={e => handleChange('end_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.end_date ? 'border-red-500/60' : 'border-[#252525]'}`} />
|
||||
<input type="date" value={form.end_date} onChange={e => handleChange('end_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.end_date ? 'border-red-500/60' : 'border-[#3a322b]'}`} />
|
||||
{errors.end_date && <p className="mt-1 text-xs text-red-400">{errors.end_date}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Total Amount ($)</label>
|
||||
<input type="number" step="0.01" value={form.total_amount_cents} onChange={e => handleChange('total_amount_cents', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.total_amount_cents ? 'border-red-500/60' : 'border-[#252525]'}`} />
|
||||
<input type="number" step="0.01" value={form.total_amount_cents} onChange={e => handleChange('total_amount_cents', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.total_amount_cents ? 'border-red-500/60' : 'border-[#3a322b]'}`} />
|
||||
{errors.total_amount_cents && <p className="mt-1 text-xs text-red-400">{errors.total_amount_cents}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Invoicing Schedule</label>
|
||||
<select value={form.invoicing_schedule} onChange={e => handleChange('invoicing_schedule', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]">
|
||||
<select value={form.invoicing_schedule} onChange={e => handleChange('invoicing_schedule', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]">
|
||||
{SCHEDULES.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Next Invoice Date</label>
|
||||
<input type="date" value={form.next_invoice_date} onChange={e => handleChange('next_invoice_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input type="date" value={form.next_invoice_date} onChange={e => handleChange('next_invoice_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">Salesperson</label>
|
||||
<select value={form.staff_id} onChange={e => handleChange('staff_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]">
|
||||
<select value={form.staff_id} onChange={e => handleChange('staff_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]">
|
||||
<option value="">None</option>
|
||||
{staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-[#888] block mb-1">PO Number</label>
|
||||
<input value={form.po_number} onChange={e => handleChange('po_number', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<input value={form.po_number} onChange={e => handleChange('po_number', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-3">
|
||||
<label className="text-xs text-[#888] block mb-1">Notes</label>
|
||||
<textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
|
||||
<textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Order'}</button>
|
||||
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Order'}</button>
|
||||
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-x-auto">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['Client','PO#','Order#','Site','Product','Ad Unit','Dates','Amount','Status',''].map(h => (
|
||||
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th>
|
||||
))}
|
||||
@@ -242,20 +242,20 @@ function OrdersContent() {
|
||||
) : orders.length === 0 ? (
|
||||
<tr><td colSpan={10} className="px-4 py-8 text-center text-[#555] text-xs">No orders found</td></tr>
|
||||
) : orders.map((o: any) => (
|
||||
<tr key={o.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
|
||||
<tr key={o.id} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<td className="px-3 py-2 text-white text-xs">{o.rmp_clients?.company_name ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#888] text-xs">{o.po_number ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#ffb800] text-xs font-mono">{o.internal_order_number}</td>
|
||||
<td className="px-3 py-2 text-[#F0A623] text-xs font-mono">{o.internal_order_number}</td>
|
||||
<td className="px-3 py-2 text-[#888] text-xs capitalize">{o.site}</td>
|
||||
<td className="px-3 py-2 text-[#ccc] text-xs">{o.rmp_products?.name ?? o.ad_unit ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#888] text-xs">{o.ad_unit ?? '—'}</td>
|
||||
<td className="px-3 py-2 text-[#888] text-xs whitespace-nowrap">{o.start_date} – {o.end_date ?? '∞'}</td>
|
||||
<td className="px-3 py-2 text-green-400 text-xs">{o.total_amount_cents ? fmt(o.total_amount_cents) : '—'}</td>
|
||||
<td className="px-3 py-2">
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${o.status === 'active' ? 'bg-green-400/10 text-green-400' : o.status === 'cancelled' ? 'bg-red-400/10 text-red-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{o.status}</span>
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${o.status === 'active' ? 'bg-green-400/10 text-green-400' : o.status === 'cancelled' ? 'bg-red-400/10 text-red-400' : 'bg-[#231d18] text-[#555]'}`}>{o.status}</span>
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<Link href={`/admin/accounting/orders/${o.id}`} className="text-xs text-[#ffb800] hover:underline">View</Link>
|
||||
<Link href={`/admin/accounting/orders/${o.id}`} className="text-xs text-[#F0A623] hover:underline">View</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -269,7 +269,7 @@ function OrdersContent() {
|
||||
|
||||
export default function OrdersPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>}>
|
||||
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>}>
|
||||
<OrdersContent />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function AccountingDashboard() {
|
||||
|
||||
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-[#ffb800] border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
if (!user) return null;
|
||||
@@ -59,18 +59,18 @@ export default function AccountingDashboard() {
|
||||
<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-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">+ New Client</Link>
|
||||
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">+ New Order</Link>
|
||||
<Link href="/admin/accounting/invoices" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">+ New Invoice</Link>
|
||||
<Link href="/admin/accounting/expenses" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">+ Log Expense</Link>
|
||||
<Link href="/admin/accounting/reconciliation" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">Upload Bank CSV</Link>
|
||||
<Link href="/admin/accounting/clients" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">+ New Client</Link>
|
||||
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">+ New Order</Link>
|
||||
<Link href="/admin/accounting/invoices" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">+ New Invoice</Link>
|
||||
<Link href="/admin/accounting/expenses" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">+ Log Expense</Link>
|
||||
<Link href="/admin/accounting/reconciliation" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] 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">
|
||||
<Link key={c.key} href={c.href} className="bg-[#111] border border-[#3a322b] 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>
|
||||
@@ -78,13 +78,13 @@ export default function AccountingDashboard() {
|
||||
</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]">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-[#3a322b]">
|
||||
<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]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['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>
|
||||
))}
|
||||
@@ -94,7 +94,7 @@ export default function AccountingDashboard() {
|
||||
{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]">
|
||||
<tr key={row.site} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<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>
|
||||
@@ -107,13 +107,13 @@ export default function AccountingDashboard() {
|
||||
</div>
|
||||
|
||||
{/* Top Clients */}
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-[#252525]">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-[#3a322b]">
|
||||
<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]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['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>
|
||||
))}
|
||||
@@ -123,7 +123,7 @@ export default function AccountingDashboard() {
|
||||
{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]">
|
||||
<tr key={c.id} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<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>
|
||||
@@ -146,7 +146,7 @@ export default function AccountingDashboard() {
|
||||
{ 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-[#ffb800] transition-colors">
|
||||
<Link key={l.href} href={l.href} className="bg-[#111] border border-[#3a322b] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#F0A623] transition-colors">
|
||||
{l.label}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function ReconciliationPage() {
|
||||
load();
|
||||
}
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -59,7 +59,7 @@ export default function ReconciliationPage() {
|
||||
<h1 className="text-xl font-bold">Bank Reconciliation</h1>
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<label className={`px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 cursor-pointer ${uploading ? 'opacity-50' : ''}`}>
|
||||
<label className={`px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 cursor-pointer ${uploading ? 'opacity-50' : ''}`}>
|
||||
{uploading ? 'Uploading…' : 'Upload Bank CSV'}
|
||||
<input type="file" accept=".csv" className="hidden" onChange={handleUpload} disabled={uploading} />
|
||||
</label>
|
||||
@@ -78,10 +78,10 @@ export default function ReconciliationPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-x-auto">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['Date','Description','Amount','Type','Matched To','Reconciled','Action'].map(h => (
|
||||
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
|
||||
))}
|
||||
@@ -93,7 +93,7 @@ export default function ReconciliationPage() {
|
||||
) : transactions.length === 0 ? (
|
||||
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No transactions. Upload a CSV to begin.</td></tr>
|
||||
) : transactions.map((tx: any) => (
|
||||
<tr key={tx.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
|
||||
<tr key={tx.id} className="border-b border-[#231d18] hover:bg-[#231d18]">
|
||||
<td className="px-3 py-2 text-[#888] text-xs">{tx.transaction_date}</td>
|
||||
<td className="px-3 py-2 text-white text-xs max-w-xs truncate">{tx.description}</td>
|
||||
<td className={`px-3 py-2 text-xs font-medium ${tx.type === 'credit' ? 'text-green-400' : 'text-red-400'}`}>{fmt(Math.abs(tx.amount_cents ?? 0))}</td>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function ReportsPage() {
|
||||
|
||||
useEffect(() => { if (user) loadReport(); }, [user]);
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -40,41 +40,41 @@ export default function ReportsPage() {
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white focus:outline-none" />
|
||||
<input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white focus:outline-none" />
|
||||
<span className="text-[#555] text-xs">to</span>
|
||||
<input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white focus:outline-none" />
|
||||
<button onClick={loadReport} disabled={loadingData} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Run Report</button>
|
||||
<input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white focus:outline-none" />
|
||||
<button onClick={loadReport} disabled={loadingData} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Run Report</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loadingData ? (
|
||||
<div className="flex items-center justify-center py-20"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>
|
||||
<div className="flex items-center justify-center py-20"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>
|
||||
) : report ? (
|
||||
<div className="space-y-4">
|
||||
{/* P&L Summary */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg p-4">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-4">
|
||||
<p className="text-xs text-[#555]">Total Revenue</p>
|
||||
<p className="text-2xl font-bold text-green-400 mt-1">{fmt(report.total_revenue_cents ?? 0)}</p>
|
||||
</div>
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg p-4">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-4">
|
||||
<p className="text-xs text-[#555]">Total Expenses</p>
|
||||
<p className="text-2xl font-bold text-red-400 mt-1">{fmt(report.total_expenses_cents ?? 0)}</p>
|
||||
</div>
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg p-4">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-4">
|
||||
<p className="text-xs text-[#555]">Net Profit</p>
|
||||
<p className={`text-2xl font-bold mt-1 ${(report.net_profit_cents ?? 0) >= 0 ? 'text-blue-400' : 'text-red-400'}`}>{fmt(report.net_profit_cents ?? 0)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Revenue by Site */}
|
||||
<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">Revenue by Site</h2></div>
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-[#3a322b]"><h2 className="text-sm font-semibold">Revenue by Site</h2></div>
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr className="border-b border-[#1a1a1a]">{['Site','Revenue'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
|
||||
<thead><tr className="border-b border-[#231d18]">{['Site','Revenue'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
|
||||
<tbody>
|
||||
{(report.by_site ?? []).map((r: any) => (
|
||||
<tr key={r.site} className="border-b border-[#1a1a1a]">
|
||||
<tr key={r.site} className="border-b border-[#231d18]">
|
||||
<td className="px-4 py-2 text-white text-xs capitalize">{r.site}</td>
|
||||
<td className="px-4 py-2 text-green-400 text-xs">{fmt(r.revenue_cents ?? 0)}</td>
|
||||
</tr>
|
||||
@@ -84,13 +84,13 @@ export default function ReportsPage() {
|
||||
</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">Top 10 Clients</h2></div>
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-[#3a322b]"><h2 className="text-sm font-semibold">Top 10 Clients</h2></div>
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr className="border-b border-[#1a1a1a]">{['Client','Revenue'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
|
||||
<thead><tr className="border-b border-[#231d18]">{['Client','Revenue'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
|
||||
<tbody>
|
||||
{(report.top_clients ?? []).map((c: any) => (
|
||||
<tr key={c.id} className="border-b border-[#1a1a1a]">
|
||||
<tr key={c.id} className="border-b border-[#231d18]">
|
||||
<td className="px-4 py-2 text-white text-xs">{c.company_name}</td>
|
||||
<td className="px-4 py-2 text-green-400 text-xs">{fmt(c.revenue_cents ?? 0)}</td>
|
||||
</tr>
|
||||
@@ -100,13 +100,13 @@ export default function ReportsPage() {
|
||||
</div>
|
||||
|
||||
{/* Expenses by Category */}
|
||||
<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">Expenses by Category</h2></div>
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-[#3a322b]"><h2 className="text-sm font-semibold">Expenses by Category</h2></div>
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr className="border-b border-[#1a1a1a]">{['Category','Amount'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
|
||||
<thead><tr className="border-b border-[#231d18]">{['Category','Amount'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
|
||||
<tbody>
|
||||
{(report.by_category ?? []).map((c: any) => (
|
||||
<tr key={c.category} className="border-b border-[#1a1a1a]">
|
||||
<tr key={c.category} className="border-b border-[#231d18]">
|
||||
<td className="px-4 py-2 text-white text-xs">{c.category?.replace(/_/g, ' ')}</td>
|
||||
<td className="px-4 py-2 text-red-400 text-xs">{fmt(c.amount_cents ?? 0)}</td>
|
||||
</tr>
|
||||
|
||||
@@ -97,7 +97,7 @@ export default function StaffPage() {
|
||||
if (errors[errKey]) setErrors(p => { const n = { ...p }; delete n[errKey]; return n; });
|
||||
}
|
||||
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -108,7 +108,7 @@ export default function StaffPage() {
|
||||
<h1 className="text-xl font-bold">Sales Staff</h1>
|
||||
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]">← Accounting</Link>
|
||||
</div>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Add Salesperson</button>
|
||||
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Add Salesperson</button>
|
||||
</div>
|
||||
|
||||
{/* Success Toast */}
|
||||
@@ -120,7 +120,7 @@ export default function StaffPage() {
|
||||
)}
|
||||
|
||||
{showForm && (
|
||||
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#252525] rounded-lg p-4 space-y-3">
|
||||
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3">
|
||||
<h3 className="text-sm font-semibold">New Salesperson</h3>
|
||||
|
||||
{errors._form && (
|
||||
@@ -140,7 +140,7 @@ export default function StaffPage() {
|
||||
type={f.key === 'email' ? 'email' : 'text'}
|
||||
value={(form as any)[f.key]}
|
||||
onChange={e => handleChange(f.key, e.target.value)}
|
||||
className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors[f.key] ? 'border-red-500/60' : 'border-[#252525]'}`}
|
||||
className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors[f.key] ? 'border-red-500/60' : 'border-[#3a322b]'}`}
|
||||
/>
|
||||
{errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>}
|
||||
</div>
|
||||
@@ -149,34 +149,34 @@ export default function StaffPage() {
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-xs text-[#888]">Commission Tiers ({new Date().getFullYear()})</label>
|
||||
<button type="button" onClick={() => setTiers(t => [...t, { tier_order: t.length + 1, threshold_cents: '', rate: '' }])} className="text-xs text-[#ffb800] hover:underline">+ Add Tier</button>
|
||||
<button type="button" onClick={() => setTiers(t => [...t, { tier_order: t.length + 1, threshold_cents: '', rate: '' }])} className="text-xs text-[#F0A623] hover:underline">+ Add Tier</button>
|
||||
</div>
|
||||
{tiers.map((tier, i) => (
|
||||
<div key={i} className="flex gap-2 mb-2 items-start">
|
||||
<span className="text-xs text-[#555] w-12 pt-5">Tier {tier.tier_order}</span>
|
||||
<div className="flex-1">
|
||||
<label className="text-xs text-[#555] block mb-0.5">Threshold ($, blank = no cap)</label>
|
||||
<input type="number" step="0.01" value={tier.threshold_cents} onChange={e => handleTierChange(i, 'threshold_cents', e.target.value)} placeholder="100000" className="w-full px-2 py-1 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none" />
|
||||
<input type="number" step="0.01" value={tier.threshold_cents} onChange={e => handleTierChange(i, 'threshold_cents', e.target.value)} placeholder="100000" className="w-full px-2 py-1 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="text-xs text-[#555] block mb-0.5">Rate (%) <span className="text-red-400">*</span></label>
|
||||
<input type="number" step="0.1" value={tier.rate} onChange={e => handleTierChange(i, 'rate', e.target.value)} placeholder="25" className={`w-full px-2 py-1 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${errors[`tier_rate_${i}`] ? 'border-red-500/60' : 'border-[#252525]'}`} />
|
||||
<input type="number" step="0.1" value={tier.rate} onChange={e => handleTierChange(i, 'rate', e.target.value)} placeholder="25" className={`w-full px-2 py-1 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${errors[`tier_rate_${i}`] ? 'border-red-500/60' : 'border-[#3a322b]'}`} />
|
||||
{errors[`tier_rate_${i}`] && <p className="mt-1 text-xs text-red-400">{errors[`tier_rate_${i}`]}</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create'}</button>
|
||||
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create'}</button>
|
||||
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
|
||||
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[#1a1a1a]">
|
||||
<tr className="border-b border-[#231d18]">
|
||||
{['Name','Email','Status','Commission YTD','Outstanding',''].map(h => (
|
||||
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
|
||||
))}
|
||||
@@ -188,16 +188,16 @@ export default function StaffPage() {
|
||||
) : staff.length === 0 ? (
|
||||
<tr><td colSpan={6} className="px-4 py-8 text-center text-[#555] text-xs">No staff found</td></tr>
|
||||
) : staff.map((s: any) => (
|
||||
<tr key={s.id} className={`border-b border-[#1a1a1a] hover:bg-[#161616] ${s.status === 'terminated' ? 'opacity-50' : ''}`}>
|
||||
<tr key={s.id} className={`border-b border-[#231d18] hover:bg-[#231d18] ${s.status === 'terminated' ? 'opacity-50' : ''}`}>
|
||||
<td className="px-4 py-2 text-white">{s.full_name}</td>
|
||||
<td className="px-4 py-2 text-[#888] text-xs">{s.email}</td>
|
||||
<td className="px-4 py-2">
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${s.status === 'active' ? 'bg-green-400/10 text-green-400' : s.status === 'terminated' ? 'bg-red-400/10 text-red-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{s.status}</span>
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs ${s.status === 'active' ? 'bg-green-400/10 text-green-400' : s.status === 'terminated' ? 'bg-red-400/10 text-red-400' : 'bg-[#231d18] text-[#555]'}`}>{s.status}</span>
|
||||
</td>
|
||||
<td className="px-4 py-2 text-green-400 text-xs">{fmt(s.commission_ytd ?? 0)}</td>
|
||||
<td className="px-4 py-2 text-yellow-400 text-xs">{fmt(s.commission_outstanding ?? 0)}</td>
|
||||
<td className="px-4 py-2">
|
||||
<Link href={`/admin/accounting/staff/${s.id}`} className="text-xs text-[#ffb800] hover:underline">View</Link>
|
||||
<Link href={`/admin/accounting/staff/${s.id}`} className="text-xs text-[#F0A623] hover:underline">View</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user