78 lines
2.2 KiB
Svelte
78 lines
2.2 KiB
Svelte
<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>
|