Redesign overview: income vs expenses split with net-position card
Hero row is now a two-column green/red split showing Income and Expenses side-by-side, with a full-width Net Position card below that colours green or red based on the sign. Budget KPIs (Remaining, Total, Allocated) moved to a secondary row underneath. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
const income = $derived(parseFloat(data.totalIncome ?? '0'));
|
||||
const remaining = $derived(total - spent);
|
||||
const remainingPct = $derived(total > 0 ? (remaining / total) * 100 : 0);
|
||||
const net = $derived(income - spent);
|
||||
const netPositive = $derived(net >= 0);
|
||||
|
||||
const tone = $derived(remaining < 0 ? 'red' : remainingPct < 20 ? 'amber' : 'green');
|
||||
|
||||
@@ -36,11 +38,58 @@
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- KPI row -->
|
||||
<div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
||||
<div class="rounded-lg border-2 {toneRing[tone]} bg-white p-4 dark:bg-gray-800">
|
||||
<!-- Income vs Expenses (hero split) -->
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div class="rounded-lg border-2 border-emerald-300 bg-emerald-50 p-5 dark:border-emerald-700 dark:bg-emerald-900/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-emerald-600 dark:text-emerald-400">
|
||||
Income
|
||||
</p>
|
||||
<svg class="h-5 w-5 text-emerald-500 dark:text-emerald-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path d="M10 3a1 1 0 01.707.293l5 5a1 1 0 01-1.414 1.414L11 6.414V16a1 1 0 11-2 0V6.414L5.707 9.707a1 1 0 01-1.414-1.414l5-5A1 1 0 0110 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="mt-2 text-3xl font-bold text-emerald-700 dark:text-emerald-400">
|
||||
{formatCurrency(income, currency)}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-emerald-700/70 dark:text-emerald-400/70">
|
||||
Net of withholding · from confirmed sales
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border-2 border-red-300 bg-red-50 p-5 dark:border-red-700 dark:bg-red-900/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-red-600 dark:text-red-400">
|
||||
Expenses
|
||||
</p>
|
||||
<svg class="h-5 w-5 text-red-500 dark:text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path d="M10 17a1 1 0 01-.707-.293l-5-5a1 1 0 011.414-1.414L9 13.586V4a1 1 0 112 0v9.586l3.293-3.293a1 1 0 011.414 1.414l-5 5A1 1 0 0110 17z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="mt-2 text-3xl font-bold text-red-700 dark:text-red-400">
|
||||
{formatCurrency(spent, currency)}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-red-700/70 dark:text-red-400/70">
|
||||
Approved · across {data.projects.length} {data.projects.length === 1 ? 'project' : 'projects'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Net position -->
|
||||
<div class="rounded-lg border-2 {netPositive ? 'border-emerald-300 bg-emerald-50 dark:border-emerald-700 dark:bg-emerald-900/20' : 'border-red-300 bg-red-50 dark:border-red-700 dark:bg-red-900/20'} p-5 md:col-span-2">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider {netPositive ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'}">
|
||||
Net Position (Income − Expenses)
|
||||
</p>
|
||||
<p class="mt-2 text-3xl font-bold {netPositive ? 'text-emerald-700 dark:text-emerald-400' : 'text-red-700 dark:text-red-400'}">
|
||||
{netPositive ? '+' : ''}{formatCurrency(net, currency)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Budget KPIs (secondary) -->
|
||||
<div class="grid grid-cols-2 gap-3 lg:grid-cols-3">
|
||||
<div class="rounded-lg border {toneRing[tone]} bg-white p-4 dark:bg-gray-800">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
||||
Remaining
|
||||
Remaining Budget
|
||||
</p>
|
||||
<p class="mt-1 text-2xl font-bold {toneText[tone]}">
|
||||
{formatCurrency(remaining, currency)}
|
||||
@@ -63,19 +112,7 @@
|
||||
<p class="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{formatCurrency(total, currency)}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Company-wide</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
||||
Spent
|
||||
</p>
|
||||
<p class="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{formatCurrency(spent, currency)}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Across {data.projects.length} {data.projects.length === 1 ? 'project' : 'projects'}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">From account balances</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
|
||||
@@ -89,16 +126,6 @@
|
||||
{total > 0 ? ((allocated / total) * 100).toFixed(1) : '0'}% of total
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-emerald-300 bg-white p-4 dark:border-emerald-700 dark:bg-gray-800 lg:col-span-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-emerald-500 dark:text-emerald-400">
|
||||
Income (from confirmed sales)
|
||||
</p>
|
||||
<p class="mt-1 text-2xl font-bold text-emerald-700 dark:text-emerald-400">
|
||||
{formatCurrency(income, currency)}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Net of withholding tax</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
|
||||
Reference in New Issue
Block a user