56 lines
1.9 KiB
Svelte
56 lines
1.9 KiB
Svelte
<script lang="ts">
|
|
import ChartNoAxesColumnIncreasingIcon from '@lucide/svelte/icons/chart-no-axes-column-increasing';
|
|
import * as Card from '$lib/components/ui/card/index.js';
|
|
import * as Empty from '$lib/components/ui/empty/index.js';
|
|
import type { PageData } from './$types';
|
|
|
|
let {
|
|
records,
|
|
formatMoney
|
|
}: {
|
|
records: PageData['billingTrend'];
|
|
formatMoney: (value: unknown) => string;
|
|
} = $props();
|
|
|
|
const maxValue = $derived(Math.max(...records.map((record) => record.totalGbp), 0));
|
|
const hasValues = $derived(records.some((record) => record.totalGbp > 0));
|
|
</script>
|
|
|
|
<Card.Root>
|
|
<Card.Header>
|
|
<Card.Title>Billing trend</Card.Title>
|
|
<Card.Description>Issued invoice totals over the last six months.</Card.Description>
|
|
</Card.Header>
|
|
<Card.Content>
|
|
{#if hasValues}
|
|
<div class="grid h-52 grid-cols-6 items-end gap-3" aria-label="Billing trend chart">
|
|
{#each records as record (record.month)}
|
|
<div class="flex h-full min-w-0 flex-col justify-end gap-2">
|
|
<div class="flex min-h-0 flex-1 items-end">
|
|
<div
|
|
class="w-full rounded-t-md bg-primary/75"
|
|
style:height={`${Math.max((record.totalGbp / maxValue) * 100, record.totalGbp > 0 ? 4 : 0)}%`}
|
|
title={`${record.label}: ${formatMoney(record.totalGbp)}`}
|
|
></div>
|
|
</div>
|
|
<div class="min-w-0 text-center">
|
|
<div class="truncate text-xs font-medium">{formatMoney(record.totalGbp)}</div>
|
|
<div class="truncate text-xs text-muted-foreground">{record.label}</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<Empty.Root class="min-h-52 border">
|
|
<Empty.Media variant="icon"><ChartNoAxesColumnIncreasingIcon /></Empty.Media>
|
|
<Empty.Header>
|
|
<Empty.Title>No issued invoices yet</Empty.Title>
|
|
<Empty.Description
|
|
>Billing totals will appear once invoices are generated.</Empty.Description
|
|
>
|
|
</Empty.Header>
|
|
</Empty.Root>
|
|
{/if}
|
|
</Card.Content>
|
|
</Card.Root>
|