feat: build dashboard overview UI
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<script lang="ts">
|
||||
import BanknoteIcon from '@lucide/svelte/icons/banknote';
|
||||
import BriefcaseBusinessIcon from '@lucide/svelte/icons/briefcase-business';
|
||||
import Building2Icon from '@lucide/svelte/icons/building-2';
|
||||
import CalendarClockIcon from '@lucide/svelte/icons/calendar-clock';
|
||||
import FileWarningIcon from '@lucide/svelte/icons/file-warning';
|
||||
import UsersIcon from '@lucide/svelte/icons/users';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let {
|
||||
metrics,
|
||||
monthLabel,
|
||||
formatMoney
|
||||
}: {
|
||||
metrics: PageData['metrics'];
|
||||
monthLabel: string;
|
||||
formatMoney: (value: unknown) => string;
|
||||
} = $props();
|
||||
|
||||
const cards = $derived([
|
||||
{
|
||||
label: 'Active clients',
|
||||
value: metrics.activeClients,
|
||||
detail: 'Unarchived client records',
|
||||
icon: UsersIcon
|
||||
},
|
||||
{
|
||||
label: 'Active contracts',
|
||||
value: metrics.activeContracts,
|
||||
detail: `${formatMoney(metrics.monthlyLicenseFeesGbp)} monthly license fees`,
|
||||
icon: BriefcaseBusinessIcon
|
||||
},
|
||||
{
|
||||
label: 'Upcoming bookings',
|
||||
value: metrics.upcomingBookings,
|
||||
detail: 'Future bookings excluding cancelled',
|
||||
icon: CalendarClockIcon
|
||||
},
|
||||
{
|
||||
label: 'Past-due invoices',
|
||||
value: metrics.overdueInvoices,
|
||||
detail: formatMoney(metrics.overdueInvoicesGbp),
|
||||
icon: FileWarningIcon
|
||||
},
|
||||
{
|
||||
label: `${monthLabel} billing`,
|
||||
value: formatMoney(metrics.monthlyBillingGbp),
|
||||
detail: 'Issued invoice total',
|
||||
icon: BanknoteIcon
|
||||
},
|
||||
{
|
||||
label: 'Room occupancy',
|
||||
value: `${metrics.occupancyPercent}%`,
|
||||
detail: `${metrics.allocatedRooms} of ${metrics.totalRooms} rooms allocated`,
|
||||
icon: Building2Icon
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<section class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{#each cards as card (card.label)}
|
||||
{@const Icon = card.icon}
|
||||
<Card.Root size="sm">
|
||||
<Card.Header>
|
||||
<Card.Title class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Icon class="size-4 text-foreground" />
|
||||
{card.label}
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="text-2xl font-semibold tracking-tight">{card.value}</div>
|
||||
<p class="mt-1 text-xs text-muted-foreground">{card.detail}</p>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
{/each}
|
||||
</section>
|
||||
Reference in New Issue
Block a user