feat(ui): add empty states for stats and data sections
Show blurred placeholder cards with "No stats yet" / "No data yet" labels when no data exists, instead of hiding the sections entirely.
This commit is contained in:
parent
ffee36ea41
commit
f5e319ce03
3 changed files with 95 additions and 2 deletions
|
|
@ -117,6 +117,10 @@ pub(crate) async fn stats_page(
|
||||||
.collect();
|
.collect();
|
||||||
let max_grinder_weight = cached.brewing_summary.max_grinder_weight;
|
let max_grinder_weight = cached.brewing_summary.max_grinder_weight;
|
||||||
|
|
||||||
|
let has_data = cached.consumption.brews_all_time > 0
|
||||||
|
|| cached.roast_summary.unique_origins > 0
|
||||||
|
|| !cached.brewing_summary.brewer_counts.is_empty();
|
||||||
|
|
||||||
let template = StatsPageTemplate {
|
let template = StatsPageTemplate {
|
||||||
nav_active: "stats",
|
nav_active: "stats",
|
||||||
is_authenticated,
|
is_authenticated,
|
||||||
|
|
@ -137,6 +141,7 @@ pub(crate) async fn stats_page(
|
||||||
consumption_30d_weight,
|
consumption_30d_weight,
|
||||||
consumption_all_time_weight,
|
consumption_all_time_weight,
|
||||||
cache_age,
|
cache_age,
|
||||||
|
has_data,
|
||||||
};
|
};
|
||||||
|
|
||||||
render_html(template).map(IntoResponse::into_response)
|
render_html(template).map(IntoResponse::into_response)
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,6 @@ is_authenticated %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
{% if !stat_cards.is_empty() %}
|
|
||||||
<section>
|
<section>
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-5">
|
||||||
<h2 class="text-lg font-semibold text-text">Stats</h2>
|
<h2 class="text-lg font-semibold text-text">Stats</h2>
|
||||||
|
|
@ -266,6 +265,7 @@ is_authenticated %}
|
||||||
>View all stats →</a
|
>View all stats →</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
{% if !stat_cards.is_empty() %}
|
||||||
<chip-scroll class="relative block">
|
<chip-scroll class="relative block">
|
||||||
<button type="button" aria-label="Scroll left"
|
<button type="button" aria-label="Scroll left"
|
||||||
class="hidden absolute -left-4 top-1/2 z-10 -translate-y-1/2 items-center justify-center rounded-full border bg-surface p-1.5 text-text-muted shadow-sm transition hover:text-text"
|
class="hidden absolute -left-4 top-1/2 z-10 -translate-y-1/2 items-center justify-center rounded-full border bg-surface p-1.5 text-text-muted shadow-sm transition hover:text-text"
|
||||||
|
|
@ -289,8 +289,23 @@ is_authenticated %}
|
||||||
{{ icons::chevron_right("h-5 w-5") }}
|
{{ icons::chevron_right("h-5 w-5") }}
|
||||||
</button>
|
</button>
|
||||||
</chip-scroll>
|
</chip-scroll>
|
||||||
|
{% else %}
|
||||||
|
<div class="relative">
|
||||||
|
<div class="grid grid-flow-col auto-cols-[minmax(45vw,max-content)] md:auto-cols-[minmax(200px,max-content)] gap-3 overflow-hidden blur-[2px] select-none pointer-events-none" aria-hidden="true">
|
||||||
|
{% for _ in 0..6 %}
|
||||||
|
<div class="flex flex-col items-center gap-1 rounded-lg border p-4">
|
||||||
|
<div class="h-6 w-6 rounded-full bg-surface-alt"></div>
|
||||||
|
<div class="h-5 w-16 rounded bg-surface-alt"></div>
|
||||||
|
<div class="h-4 w-20 rounded bg-surface-alt"></div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-0 z-10 flex items-center justify-center">
|
||||||
|
<span class="text-lg font-semibold text-text-muted">No stats yet</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Data Cards -->
|
<!-- Data Cards -->
|
||||||
<section>
|
<section>
|
||||||
|
|
@ -300,6 +315,7 @@ is_authenticated %}
|
||||||
>View all data →</a
|
>View all data →</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
{% if !stats.is_empty() %}
|
||||||
<chip-scroll class="relative block">
|
<chip-scroll class="relative block">
|
||||||
<button type="button" aria-label="Scroll left"
|
<button type="button" aria-label="Scroll left"
|
||||||
class="hidden absolute -left-4 top-1/2 z-10 -translate-y-1/2 items-center justify-center rounded-full border bg-surface p-1.5 text-text-muted shadow-sm transition hover:text-text"
|
class="hidden absolute -left-4 top-1/2 z-10 -translate-y-1/2 items-center justify-center rounded-full border bg-surface p-1.5 text-text-muted shadow-sm transition hover:text-text"
|
||||||
|
|
@ -364,5 +380,21 @@ is_authenticated %}
|
||||||
{{ icons::chevron_right("h-5 w-5") }}
|
{{ icons::chevron_right("h-5 w-5") }}
|
||||||
</button>
|
</button>
|
||||||
</chip-scroll>
|
</chip-scroll>
|
||||||
|
{% else %}
|
||||||
|
<div class="relative">
|
||||||
|
<div class="grid grid-flow-col auto-cols-[45vw] md:auto-cols-[200px] gap-3 overflow-hidden blur-[2px] select-none pointer-events-none" aria-hidden="true">
|
||||||
|
{% for _ in 0..6 %}
|
||||||
|
<div class="flex flex-col items-center gap-1 rounded-lg border p-4">
|
||||||
|
<div class="h-6 w-6 rounded-full bg-surface-alt"></div>
|
||||||
|
<div class="h-5 w-8 rounded bg-surface-alt"></div>
|
||||||
|
<div class="h-4 w-16 rounded bg-surface-alt"></div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-0 z-10 flex items-center justify-center">
|
||||||
|
<span class="text-lg font-semibold text-text-muted">No data yet</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<p class="max-w-2xl text-sm text-text-secondary">Aggregated coffee data across origins, consumption, and brewing.</p>
|
<p class="max-w-2xl text-sm text-text-secondary">Aggregated coffee data across origins, consumption, and brewing.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{% if has_data %}
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
{% include "partials/tab_bar.html" %}
|
{% include "partials/tab_bar.html" %}
|
||||||
|
|
||||||
|
|
@ -120,4 +121,59 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
{% else %}
|
||||||
|
<div class="relative">
|
||||||
|
<div class="flex flex-col gap-8 blur-[2px] select-none pointer-events-none" aria-hidden="true">
|
||||||
|
<!-- Placeholder: Roast Stats -->
|
||||||
|
<section>
|
||||||
|
<div class="mb-5"><div class="h-5 w-28 rounded bg-surface-alt"></div></div>
|
||||||
|
<div class="grid gap-3 sm:grid-cols-3">
|
||||||
|
{% for _ in 0..3 %}
|
||||||
|
<div class="flex flex-col items-center gap-1 rounded-lg border p-4">
|
||||||
|
<div class="h-6 w-6 rounded-full bg-surface-alt"></div>
|
||||||
|
<div class="h-5 w-10 rounded bg-surface-alt"></div>
|
||||||
|
<div class="h-4 w-16 rounded bg-surface-alt"></div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Placeholder: Consumption -->
|
||||||
|
<section>
|
||||||
|
<div class="mb-5"><div class="h-5 w-32 rounded bg-surface-alt"></div></div>
|
||||||
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||||
|
{% for _ in 0..4 %}
|
||||||
|
<div class="flex flex-col items-center gap-1 rounded-lg border p-4">
|
||||||
|
<div class="h-6 w-6 rounded-full bg-surface-alt"></div>
|
||||||
|
<div class="h-5 w-12 rounded bg-surface-alt"></div>
|
||||||
|
<div class="h-4 w-20 rounded bg-surface-alt"></div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!-- Placeholder: Brewing charts -->
|
||||||
|
<section>
|
||||||
|
<div class="mb-5"><div class="h-5 w-24 rounded bg-surface-alt"></div></div>
|
||||||
|
<div class="grid gap-5 md:grid-cols-2">
|
||||||
|
{% for _ in 0..2 %}
|
||||||
|
<div class="rounded-lg border bg-surface p-5">
|
||||||
|
<div class="h-4 w-28 rounded bg-surface-alt mb-4"></div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{% for _ in 0..4 %}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-28 h-3 rounded bg-surface-alt"></div>
|
||||||
|
<div class="flex-1 h-5 rounded bg-surface-alt"></div>
|
||||||
|
<div class="w-6 h-3 rounded bg-surface-alt"></div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-0 z-10 flex items-center justify-center">
|
||||||
|
<span class="text-lg font-semibold text-text-muted">No stats yet</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue