Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/frontend/src/lib/components/ChartSkeleton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="mt-4 space-y-3">
<div class="flex items-end gap-2 px-2" style="height: 160px;">
{#each [60, 85, 45, 100, 70, 55, 90, 40] as h}
<div
class="animate-pulse flex-1 rounded-t bg-surface-2"
style="height: {h}%"
></div>
{/each}
</div>
<div class="h-3 w-full animate-pulse rounded bg-surface-2"></div>
</div>
12 changes: 9 additions & 3 deletions apps/frontend/src/lib/components/StatCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@
label,
value,
sub,
icon
icon,
href
}: {
label: string;
value: string | number;
sub?: string;
icon?: Snippet;
href?: string;
} = $props();
</script>

<div class="rounded-xl border border-border bg-surface p-5">
<svelte:element
this={href ? 'a' : 'div'}
{href}
class="block rounded-xl border border-border bg-surface p-5 {href ? 'transition-colors hover:border-accent/50' : ''}"
>
<div class="flex items-start justify-between">
<span class="text-xs font-semibold uppercase tracking-wider text-muted">{label}</span>
{#if icon}
Expand All @@ -25,4 +31,4 @@
{#if sub}
<div class="mt-1 text-xs text-muted">{sub}</div>
{/if}
</div>
</svelte:element>
22 changes: 18 additions & 4 deletions apps/frontend/src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import BarChart from '$lib/components/BarChart.svelte';
import ChartSkeleton from '$lib/components/ChartSkeleton.svelte';
import DonutChart from '$lib/components/DonutChart.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
import StatCard from '$lib/components/StatCard.svelte';
Expand All @@ -8,6 +9,13 @@

let { data }: { data: PageData } = $props();

// Data arrives via +page.ts load(); show chart skeletons until the first
// client tick confirms render (also covers slow-network navigation).
let loading = $state(true);
$effect(() => {
loading = false;
});

const sourceLabels = $derived(data.bySource.map((r) => titleize(r.source)));
const sourceData = $derived(data.bySource.map((r) => r.count));
const catLabels = $derived(data.byCategory.map((r) => r.category));
Expand All @@ -23,17 +31,19 @@
<div class="space-y-6 p-8">
<!-- Stat cards -->
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-4">
<StatCard label="Total Events" value={data.stats.total_events.toLocaleString()} sub="across all sources" />
<StatCard label="Active Sources" value={data.stats.active_sources} sub="{data.scrapers.length} scrapers registered" />
<StatCard label="Total Events" value={data.stats.total_events.toLocaleString()} sub="across all sources" href="/events" />
<StatCard label="Active Sources" value={data.scrapers.length.toLocaleString()} sub="{data.stats.active_sources} with events" href="/scrapers" />
<StatCard
label="Organizers"
value={data.stats.total_organizers.toLocaleString()}
sub="{data.stats.confirmed_organizers} confirmed · {data.stats.pending_organizers} pending"
href="/organizers"
/>
<StatCard
label="Venues"
value={data.stats.total_venues.toLocaleString()}
sub="{data.stats.verified_venues} verified"
href="/venues"
/>
</div>

Expand All @@ -42,7 +52,9 @@
<div class="rounded-xl border border-border bg-surface p-6">
<h2 class="text-base font-semibold text-heading">Events by Source</h2>
<p class="mb-4 text-sm text-muted">All time collected</p>
{#if sourceData.length}
{#if loading}
<ChartSkeleton />
{:else if sourceData.length}
<BarChart labels={sourceLabels} data={sourceData} />
{:else}
<p class="py-16 text-center text-sm text-muted">No events scraped yet.</p>
Expand All @@ -52,7 +64,9 @@
<div class="rounded-xl border border-border bg-surface p-6">
<h2 class="text-base font-semibold text-heading">Events by Category</h2>
<p class="mb-4 text-sm text-muted">Current database</p>
{#if catData.length}
{#if loading}
<ChartSkeleton />
{:else if catData.length}
<DonutChart labels={catLabels} data={catData} />
{:else}
<p class="py-16 text-center text-sm text-muted">No categorized events yet.</p>
Expand Down
Loading