style: reformat checkin and home page templates
This commit is contained in:
parent
3747ab1e93
commit
f9adc849c2
2 changed files with 183 additions and 90 deletions
|
|
@ -1,12 +1,14 @@
|
||||||
{% extends "base.html" %} {% import "partials/icons.html" as icons %} {% import "partials/scan_input.html" as scan %} {% import "partials/location_search.html" as location %} {% block title %}Brewlog · Check In{% endblock %}
|
{% extends "base.html" %} {% import "partials/icons.html" as icons %} {% import
|
||||||
|
"partials/scan_input.html" as scan %} {% import "partials/location_search.html" as location %} {%
|
||||||
{% block head %}
|
block title %}Brewlog · Check In{% endblock %} {% block head %}
|
||||||
<script>
|
<script>
|
||||||
{{ location::location_search_js() }}
|
{
|
||||||
|
{
|
||||||
|
location::location_search_js()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %} {% block content %}
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section
|
<section
|
||||||
id="checkin-root"
|
id="checkin-root"
|
||||||
data-signals:_step="1"
|
data-signals:_step="1"
|
||||||
|
|
@ -35,9 +37,7 @@
|
||||||
>
|
>
|
||||||
<header class="flex flex-col gap-2">
|
<header class="flex flex-col gap-2">
|
||||||
<h1 class="text-3xl font-semibold">Check In</h1>
|
<h1 class="text-3xl font-semibold">Check In</h1>
|
||||||
<p class="max-w-2xl text-sm text-text-secondary">
|
<p class="max-w-2xl text-sm text-text-secondary">Record a cup of coffee at a cafe.</p>
|
||||||
Record a cup of coffee at a cafe.
|
|
||||||
</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Step progress -->
|
<!-- Step progress -->
|
||||||
|
|
@ -45,38 +45,51 @@
|
||||||
<!-- Background track (continuous line through dot centers) -->
|
<!-- Background track (continuous line through dot centers) -->
|
||||||
<div class="absolute bg-border" style="height: 3px; top: 8px; left: 10px; right: 10px"></div>
|
<div class="absolute bg-border" style="height: 3px; top: 8px; left: 10px; right: 10px"></div>
|
||||||
<!-- Accent overlay: first segment -->
|
<!-- Accent overlay: first segment -->
|
||||||
<div class="absolute bg-accent" style="height: 3px; top: 8px; left: 10px; right: 50%; display: none"
|
<div
|
||||||
|
class="absolute bg-accent"
|
||||||
|
style="height: 3px; top: 8px; left: 10px; right: 50%; display: none"
|
||||||
data-show="$_step > 1"
|
data-show="$_step > 1"
|
||||||
></div>
|
></div>
|
||||||
<!-- Accent overlay: second segment -->
|
<!-- Accent overlay: second segment -->
|
||||||
<div class="absolute bg-accent" style="height: 3px; top: 8px; left: 50%; right: 10px; display: none"
|
<div
|
||||||
|
class="absolute bg-accent"
|
||||||
|
style="height: 3px; top: 8px; left: 50%; right: 10px; display: none"
|
||||||
data-show="$_step > 2"
|
data-show="$_step > 2"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div class="relative flex justify-between">
|
<div class="relative flex justify-between">
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<div class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
|
<div
|
||||||
|
class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
|
||||||
data-class="{'border-accent': $_step >= 1, 'border-border': $_step < 1}"
|
data-class="{'border-accent': $_step >= 1, 'border-border': $_step < 1}"
|
||||||
></div>
|
></div>
|
||||||
<span class="mt-2 text-xs font-medium transition-colors"
|
<span
|
||||||
|
class="mt-2 text-xs font-medium transition-colors"
|
||||||
data-class="{'text-accent': $_step === 1, 'text-text': $_step > 1}"
|
data-class="{'text-accent': $_step === 1, 'text-text': $_step > 1}"
|
||||||
>Cafe</span>
|
>Cafe</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<div class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
|
<div
|
||||||
|
class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
|
||||||
data-class="{'border-accent': $_step >= 2, 'border-border': $_step < 2}"
|
data-class="{'border-accent': $_step >= 2, 'border-border': $_step < 2}"
|
||||||
></div>
|
></div>
|
||||||
<span class="mt-2 text-xs font-medium transition-colors"
|
<span
|
||||||
|
class="mt-2 text-xs font-medium transition-colors"
|
||||||
data-class="{'text-accent': $_step === 2, 'text-text': $_step > 2, 'text-text-muted': $_step < 2}"
|
data-class="{'text-accent': $_step === 2, 'text-text': $_step > 2, 'text-text-muted': $_step < 2}"
|
||||||
>Coffee</span>
|
>Coffee</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<div class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
|
<div
|
||||||
|
class="h-5 w-5 rounded-full border-[3px] bg-page transition-all"
|
||||||
data-class="{'border-accent': $_step >= 3, 'border-border': $_step < 3}"
|
data-class="{'border-accent': $_step >= 3, 'border-border': $_step < 3}"
|
||||||
></div>
|
></div>
|
||||||
<span class="mt-2 text-xs font-medium transition-colors"
|
<span
|
||||||
|
class="mt-2 text-xs font-medium transition-colors"
|
||||||
data-class="{'text-accent': $_step === 3, 'text-text-muted': $_step < 3}"
|
data-class="{'text-accent': $_step === 3, 'text-text-muted': $_step < 3}"
|
||||||
>Submit</span>
|
>Submit</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -90,13 +103,20 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
{{ icons::location("h-4 w-4 text-accent shrink-0") }}
|
{{ icons::location("h-4 w-4 text-accent shrink-0") }}
|
||||||
<span class="font-medium text-text" data-text="$_cafeName"></span>
|
<span class="font-medium text-text" data-text="$_cafeName"></span>
|
||||||
<span class="text-xs text-text-muted" data-show="$_cafeCity" data-text="$_cafeCity" style="display: none"></span>
|
<span
|
||||||
|
class="text-xs text-text-muted"
|
||||||
|
data-show="$_cafeCity"
|
||||||
|
data-text="$_cafeCity"
|
||||||
|
style="display: none"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-on:click="$_cafeId = ''; $_cafeName = ''; $_step = 1"
|
data-on:click="$_cafeId = ''; $_cafeName = ''; $_step = 1"
|
||||||
class="text-xs text-text-muted hover:text-text"
|
class="text-xs text-text-muted hover:text-text"
|
||||||
>Change</button>
|
>
|
||||||
|
Change
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Selected roast summary (shown after selection) -->
|
<!-- Selected roast summary (shown after selection) -->
|
||||||
|
|
@ -108,13 +128,20 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
{{ icons::bag("h-4 w-4 text-accent shrink-0") }}
|
{{ icons::bag("h-4 w-4 text-accent shrink-0") }}
|
||||||
<span class="font-medium text-text" data-text="$_roastName"></span>
|
<span class="font-medium text-text" data-text="$_roastName"></span>
|
||||||
<span class="text-xs text-text-muted" data-show="$_roasterName" data-text="$_roasterName" style="display: none"></span>
|
<span
|
||||||
|
class="text-xs text-text-muted"
|
||||||
|
data-show="$_roasterName"
|
||||||
|
data-text="$_roasterName"
|
||||||
|
style="display: none"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-on:click="$_roastId = ''; $_roastName = ''; $_roasterName = ''; $_scanSuccess = ''; $_step = 2"
|
data-on:click="$_roastId = ''; $_roastName = ''; $_roasterName = ''; $_scanSuccess = ''; $_step = 2"
|
||||||
class="text-xs text-text-muted hover:text-text"
|
class="text-xs text-text-muted hover:text-text"
|
||||||
>Change</button>
|
>
|
||||||
|
Change
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error display -->
|
<!-- Error display -->
|
||||||
|
|
@ -128,15 +155,10 @@
|
||||||
<!-- Step 1: Cafe selection -->
|
<!-- Step 1: Cafe selection -->
|
||||||
<div class="mt-4" data-show="$_step === 1" style="display: none">
|
<div class="mt-4" data-show="$_step === 1" style="display: none">
|
||||||
<div class="rounded-lg border bg-surface p-5">
|
<div class="rounded-lg border bg-surface p-5">
|
||||||
<div data-show="!$_reviewingCafe">
|
<div data-show="!$_reviewingCafe">{{ location::location_search("$_error") }}</div>
|
||||||
{{ location::location_search("$_error") }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Review form for new cafe from Foursquare -->
|
<!-- Review form for new cafe from Foursquare -->
|
||||||
<div
|
<div data-show="$_reviewingCafe" style="display: none">
|
||||||
data-show="$_reviewingCafe"
|
|
||||||
style="display: none"
|
|
||||||
>
|
|
||||||
<h3 class="text-base font-semibold text-text mb-1">Confirm cafe details</h3>
|
<h3 class="text-base font-semibold text-text mb-1">Confirm cafe details</h3>
|
||||||
<p class="text-sm text-text-secondary mb-3">Review and edit the details before saving.</p>
|
<p class="text-sm text-text-secondary mb-3">Review and edit the details before saving.</p>
|
||||||
<div class="grid gap-4 sm:grid-cols-2">
|
<div class="grid gap-4 sm:grid-cols-2">
|
||||||
|
|
@ -154,7 +176,12 @@
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1 text-sm">
|
<label class="flex flex-col gap-1 text-sm">
|
||||||
<span class="text-text">Website</span>
|
<span class="text-text">Website</span>
|
||||||
<input type="url" data-bind:_cafe-website class="input-field" placeholder="https://…" />
|
<input
|
||||||
|
type="url"
|
||||||
|
data-bind:_cafe-website
|
||||||
|
class="input-field"
|
||||||
|
placeholder="https://…"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex items-center justify-end gap-2">
|
<div class="mt-4 flex items-center justify-end gap-2">
|
||||||
|
|
@ -179,13 +206,19 @@
|
||||||
{% if !cafe_options.is_empty() %}
|
{% if !cafe_options.is_empty() %}
|
||||||
<div class="mt-3 border-t pt-3" data-show="!$_reviewingCafe">
|
<div class="mt-3 border-t pt-3" data-show="!$_reviewingCafe">
|
||||||
<p class="mb-2 text-xs text-text-muted">Or choose a saved cafe:</p>
|
<p class="mb-2 text-xs text-text-muted">Or choose a saved cafe:</p>
|
||||||
<searchable-select name="saved_cafe_id" placeholder="Type to search saved cafes…"
|
<searchable-select
|
||||||
|
name="saved_cafe_id"
|
||||||
|
placeholder="Type to search saved cafes…"
|
||||||
data-on:change="$_cafeId = evt.detail.value; $_cafeName = evt.detail.display; $_cafeCity = evt.detail.data.city; $_cafeCountry = ''; $_cafeLat = 0; $_cafeLng = 0; $_cafeWebsite = ''; $_step = 2"
|
data-on:change="$_cafeId = evt.detail.value; $_cafeName = evt.detail.display; $_cafeCity = evt.detail.data.city; $_cafeCountry = ''; $_cafeLat = 0; $_cafeLng = 0; $_cafeWebsite = ''; $_step = 2"
|
||||||
>
|
>
|
||||||
{% for cafe in cafe_options %}
|
{% for cafe in cafe_options %}
|
||||||
<button type="button" value="{{ cafe.id }}" data-display="{{ cafe.name }}"
|
<button
|
||||||
|
type="button"
|
||||||
|
value="{{ cafe.id }}"
|
||||||
|
data-display="{{ cafe.name }}"
|
||||||
data-city="{{ cafe.city }}"
|
data-city="{{ cafe.city }}"
|
||||||
class="w-full px-3 py-2 text-left text-sm hover:bg-surface-alt transition">
|
class="w-full px-3 py-2 text-left text-sm hover:bg-surface-alt transition"
|
||||||
|
>
|
||||||
<span class="font-medium text-text">{{ cafe.name }}</span>
|
<span class="font-medium text-text">{{ cafe.name }}</span>
|
||||||
<span class="ml-2 text-xs text-text-muted">{{ cafe.city }}</span>
|
<span class="ml-2 text-xs text-text-muted">{{ cafe.city }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -202,13 +235,18 @@
|
||||||
<h3 class="text-base font-semibold text-text mb-3">What are you drinking?</h3>
|
<h3 class="text-base font-semibold text-text mb-3">What are you drinking?</h3>
|
||||||
|
|
||||||
<div data-show="!$_scanSuccess" class="mb-4">
|
<div data-show="!$_scanSuccess" class="mb-4">
|
||||||
<p class="text-sm text-text-secondary mb-3" data-show="!$_extracting">Scan a bag to identify the coffee, or select from your existing roasts below.</p>
|
<p class="text-sm text-text-secondary mb-3" data-show="!$_extracting">
|
||||||
|
Scan a bag to identify the coffee, or select from your existing roasts below.
|
||||||
|
</p>
|
||||||
|
|
||||||
<form id="checkin-scan-form"
|
<form
|
||||||
|
id="checkin-scan-form"
|
||||||
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/scan', {contentType: 'form'})"
|
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/scan', {contentType: 'form'})"
|
||||||
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_roastName = $_scanSuccess; $_step = 3 } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Scan failed. Please try again.' }"
|
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_roastName = $_scanSuccess; $_step = 3 } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Scan failed. Please try again.' }"
|
||||||
>
|
>
|
||||||
{{ scan::scan_input("checkin-scan-form", "checkin-image", "$_extracting", "$_extractError", "Describe the coffee…", "Scanning…") }}
|
{{ scan::scan_input("checkin-scan-form", "checkin-image", "$_extracting",
|
||||||
|
"$_extractError", "Describe a coffee bag to fetch details automatically…", "Scanning…")
|
||||||
|
}}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -223,13 +261,19 @@
|
||||||
{% if !roast_options.is_empty() %}
|
{% if !roast_options.is_empty() %}
|
||||||
<div class="border-t pt-3">
|
<div class="border-t pt-3">
|
||||||
<p class="text-xs text-text-muted mb-2">Or select an existing roast:</p>
|
<p class="text-xs text-text-muted mb-2">Or select an existing roast:</p>
|
||||||
<searchable-select name="roast_id" placeholder="Type to search existing roasts…"
|
<searchable-select
|
||||||
|
name="roast_id"
|
||||||
|
placeholder="Type to search existing roasts…"
|
||||||
data-on:change="$_roastId = evt.detail.value; $_roastName = evt.detail.display; $_roasterName = evt.detail.data.roaster; $_step = 3"
|
data-on:change="$_roastId = evt.detail.value; $_roastName = evt.detail.display; $_roasterName = evt.detail.data.roaster; $_step = 3"
|
||||||
>
|
>
|
||||||
{% for roast in roast_options %}
|
{% for roast in roast_options %}
|
||||||
<button type="button" value="{{ roast.id }}" data-display="{{ roast.name }}"
|
<button
|
||||||
|
type="button"
|
||||||
|
value="{{ roast.id }}"
|
||||||
|
data-display="{{ roast.name }}"
|
||||||
data-roaster="{{ roast.roaster_name }}"
|
data-roaster="{{ roast.roaster_name }}"
|
||||||
class="w-full px-3 py-2 text-left text-sm hover:bg-surface-alt transition">
|
class="w-full px-3 py-2 text-left text-sm hover:bg-surface-alt transition"
|
||||||
|
>
|
||||||
<span class="font-medium text-text">{{ roast.name }}</span>
|
<span class="font-medium text-text">{{ roast.name }}</span>
|
||||||
<span class="ml-2 text-xs text-text-muted">{{ roast.roaster_name }}</span>
|
<span class="ml-2 text-xs text-text-muted">{{ roast.roaster_name }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -247,11 +291,17 @@
|
||||||
<dl class="flex flex-col gap-3 text-sm mb-6">
|
<dl class="flex flex-col gap-3 text-sm mb-6">
|
||||||
<div class="flex justify-between gap-2">
|
<div class="flex justify-between gap-2">
|
||||||
<dt class="font-medium text-text-muted">Cafe</dt>
|
<dt class="font-medium text-text-muted">Cafe</dt>
|
||||||
<dd class="text-right text-text" data-text="$_cafeCity ? $_cafeName + ', ' + $_cafeCity : $_cafeName"></dd>
|
<dd
|
||||||
|
class="text-right text-text"
|
||||||
|
data-text="$_cafeCity ? $_cafeName + ', ' + $_cafeCity : $_cafeName"
|
||||||
|
></dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between gap-2">
|
<div class="flex justify-between gap-2">
|
||||||
<dt class="font-medium text-text-muted">Coffee</dt>
|
<dt class="font-medium text-text-muted">Coffee</dt>
|
||||||
<dd class="text-right text-text" data-text="$_roasterName ? $_roastName + ' (' + $_roasterName + ')' : $_roastName"></dd>
|
<dd
|
||||||
|
class="text-right text-text"
|
||||||
|
data-text="$_roasterName ? $_roastName + ' (' + $_roasterName + ')' : $_roastName"
|
||||||
|
></dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,29 @@
|
||||||
{% extends "base.html" %} {% import "partials/bag_card.html" as bag_card %} {% import "partials/icons.html" as icons %} {% import "partials/scan_input.html" as scan %} {% block title %}Brewlog{% endblock %}
|
{% extends "base.html" %} {% import "partials/bag_card.html" as bag_card %} {% import
|
||||||
|
"partials/icons.html" as icons %} {% import "partials/scan_input.html" as scan %} {% block title
|
||||||
{% block head %}
|
%}Brewlog{% endblock %} {% block head %} {% if is_authenticated %}
|
||||||
{% if is_authenticated %}
|
|
||||||
<script>
|
<script>
|
||||||
const closeBag = async (bagId, cardEl) => {
|
const closeBag = async (bagId, cardEl) => {
|
||||||
if (!confirm('Close this bag? This will mark it as finished.')) return;
|
if (!confirm("Close this bag? This will mark it as finished.")) return
|
||||||
try {
|
try {
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split("T")[0]
|
||||||
const resp = await fetch(`/api/v1/bags/${bagId}`, {
|
const resp = await fetch(`/api/v1/bags/${bagId}`, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
credentials: 'same-origin',
|
credentials: "same-origin",
|
||||||
body: JSON.stringify({ closed: true, remaining: 0.0, finished_at: today }),
|
body: JSON.stringify({ closed: true, remaining: 0.0, finished_at: today }),
|
||||||
});
|
})
|
||||||
if (!resp.ok) throw new Error(`Server returned ${resp.status}`);
|
if (!resp.ok) throw new Error(`Server returned ${resp.status}`)
|
||||||
cardEl.remove();
|
cardEl.remove()
|
||||||
const grid = document.getElementById('open-bags-grid');
|
const grid = document.getElementById("open-bags-grid")
|
||||||
if (grid && grid.children.length === 0) {
|
if (grid && grid.children.length === 0) {
|
||||||
document.getElementById('open-bags-section')?.remove();
|
document.getElementById("open-bags-section")?.remove()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(`Failed to close bag: ${e.message}`);
|
alert(`Failed to close bag: ${e.message}`)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %} {% endblock %} {% block content %}
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<!-- Scan Bag -->
|
<!-- Scan Bag -->
|
||||||
{% if is_authenticated %}
|
{% if is_authenticated %}
|
||||||
<section
|
<section
|
||||||
|
|
@ -53,11 +49,13 @@
|
||||||
>
|
>
|
||||||
<!-- Input: photo or text (shown when not yet extracted) -->
|
<!-- Input: photo or text (shown when not yet extracted) -->
|
||||||
<div data-show="!$_scanExtracted" class="rounded-lg border bg-surface p-5">
|
<div data-show="!$_scanExtracted" class="rounded-lg border bg-surface p-5">
|
||||||
<form id="scan-extract-form"
|
<form
|
||||||
|
id="scan-extract-form"
|
||||||
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/extract-bag-scan', {contentType: 'form'})"
|
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/extract-bag-scan', {contentType: 'form'})"
|
||||||
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_scanExtracted = true; document.getElementById('scan-extract-form').reset() } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Extraction failed. Please try again.' }"
|
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_scanExtracted = true; document.getElementById('scan-extract-form').reset() } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Extraction failed. Please try again.' }"
|
||||||
>
|
>
|
||||||
{{ scan::scan_input("scan-extract-form", "scan-image", "$_extracting", "$_extractError", "Describe the coffee bag…", "Waiting for response…") }}
|
{{ scan::scan_input("scan-extract-form", "scan-image", "$_extracting", "$_extractError",
|
||||||
|
"Describe a coffee bag to fetch details automatically…", "Waiting for response…") }}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -79,7 +77,9 @@
|
||||||
<section data-signals:_brewing="false">
|
<section data-signals:_brewing="false">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h2 class="text-lg font-semibold text-text">Recent Brews</h2>
|
<h2 class="text-lg font-semibold text-text">Recent Brews</h2>
|
||||||
<a href="/data?type=brews" class="text-sm text-accent hover:text-accent-hover font-medium">View all brews →</a>
|
<a href="/data?type=brews" class="text-sm text-accent hover:text-accent-hover font-medium"
|
||||||
|
>View all brews →</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 items-start gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 items-start gap-4">
|
||||||
{% for brew in recent_brews %}
|
{% for brew in recent_brews %}
|
||||||
|
|
@ -89,20 +89,31 @@
|
||||||
<span class="block truncate font-semibold text-text">{{ brew.roast_name }}</span>
|
<span class="block truncate font-semibold text-text">{{ brew.roast_name }}</span>
|
||||||
<p class="truncate text-sm text-text-muted">{{ brew.roaster_name }}</p>
|
<p class="truncate text-sm text-text-muted">{{ brew.roaster_name }}</p>
|
||||||
</div>
|
</div>
|
||||||
<time class="text-xs text-text-muted whitespace-nowrap shrink-0">{{ brew.relative_date_label }}</time>
|
<time class="text-xs text-text-muted whitespace-nowrap shrink-0"
|
||||||
|
>{{ brew.relative_date_label }}</time
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 space-y-0.5 text-sm">
|
<div class="mt-2 space-y-0.5 text-sm">
|
||||||
<p class="truncate"><span class="font-medium text-text">{{ brew.coffee_weight }} · {{ brew.water_volume }} · {{ brew.water_temp }}</span> <span class="text-text-muted">· {{ brew.ratio }}</span></p>
|
<p class="truncate">
|
||||||
<p class="truncate"><span class="font-medium text-text">{{ brew.grind_setting }}</span> <span class="text-text-muted">· {{ brew.grinder_name }}</span></p>
|
<span class="font-medium text-text"
|
||||||
<p class="truncate"><span class="font-medium text-text">{{ brew.brewer_name }}</span>{% if let Some(fp) = brew.filter_paper_name %} <span class="text-text-muted">· {{ fp }}</span>{% endif %}</p>
|
>{{ brew.coffee_weight }} · {{ brew.water_volume }} · {{ brew.water_temp }}</span
|
||||||
|
>
|
||||||
|
<span class="text-text-muted">· {{ brew.ratio }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="truncate">
|
||||||
|
<span class="font-medium text-text">{{ brew.grind_setting }}</span>
|
||||||
|
<span class="text-text-muted">· {{ brew.grinder_name }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="truncate">
|
||||||
|
<span class="font-medium text-text">{{ brew.brewer_name }}</span>{% if let Some(fp) =
|
||||||
|
brew.filter_paper_name %} <span class="text-text-muted">· {{ fp }}</span>{% endif %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 pt-3 border-t flex items-center justify-between gap-3">
|
<div class="mt-3 pt-3 border-t flex items-center justify-between gap-3">
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
{% if !brew.quick_notes.is_empty() %}
|
{% if !brew.quick_notes.is_empty() %} {% for qn in brew.quick_notes %}
|
||||||
{% for qn in brew.quick_notes %}
|
|
||||||
<span class="{{ qn.pill_class }}">{{ qn.label }}</span>
|
<span class="{{ qn.pill_class }}">{{ qn.label }}</span>
|
||||||
{% endfor %}
|
{% endfor %} {% else %}
|
||||||
{% else %}
|
|
||||||
<span class="pill pill-muted">No Notes</span>
|
<span class="pill pill-muted">No Notes</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -144,12 +155,12 @@
|
||||||
<section id="open-bags-section">
|
<section id="open-bags-section">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h2 class="text-lg font-semibold text-text">Open Bags</h2>
|
<h2 class="text-lg font-semibold text-text">Open Bags</h2>
|
||||||
<a href="/data?type=bags" class="text-sm text-accent hover:text-accent-hover font-medium">View all bags →</a>
|
<a href="/data?type=bags" class="text-sm text-accent hover:text-accent-hover font-medium"
|
||||||
|
>View all bags →</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div id="open-bags-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div id="open-bags-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{% for bag in open_bags %}
|
{% for bag in open_bags %} {{ bag_card::card(bag, is_authenticated) }} {% endfor %}
|
||||||
{{ bag_card::card(bag, is_authenticated) }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
@ -159,16 +170,27 @@
|
||||||
<section>
|
<section>
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h2 class="text-lg font-semibold text-text">Recent Activity</h2>
|
<h2 class="text-lg font-semibold text-text">Recent Activity</h2>
|
||||||
<a href="/timeline" class="text-sm text-accent hover:text-accent-hover font-medium">View full timeline →</a>
|
<a href="/timeline" class="text-sm text-accent hover:text-accent-hover font-medium"
|
||||||
|
>View full timeline →</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
{% for event in recent_events %}
|
{% for event in recent_events %}
|
||||||
<div class="rounded-lg border bg-surface px-4 py-3 flex items-center justify-between gap-3">
|
<div class="rounded-lg border bg-surface px-4 py-3 flex items-center justify-between gap-3">
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<span class="pill pill-muted w-28 shrink-0 justify-center whitespace-nowrap">{{ event.kind_label }}</span>
|
<span class="pill pill-muted w-28 shrink-0 justify-center whitespace-nowrap"
|
||||||
<p class="min-w-0 truncate text-sm"><a href="{{ event.link }}" class="font-medium text-text hover:text-accent">{{ event.title }}</a>{% if let Some(sub) = event.subtitle %} <span class="text-xs text-text-muted italic">· {{ sub }}</span>{% endif %}</p>
|
>{{ event.kind_label }}</span
|
||||||
|
>
|
||||||
|
<p class="min-w-0 truncate text-sm">
|
||||||
|
<a href="{{ event.link }}" class="font-medium text-text hover:text-accent"
|
||||||
|
>{{ event.title }}</a
|
||||||
|
>{% if let Some(sub) = event.subtitle %}
|
||||||
|
<span class="text-xs text-text-muted italic">· {{ sub }}</span>{% endif %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<time class="text-xs text-text-muted whitespace-nowrap shrink-0">{{ event.relative_date_label }}</time>
|
<time class="text-xs text-text-muted whitespace-nowrap shrink-0"
|
||||||
|
>{{ event.relative_date_label }}</time
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -181,37 +203,58 @@
|
||||||
<h2 class="text-lg font-semibold text-text">Stats</h2>
|
<h2 class="text-lg font-semibold text-text">Stats</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-7">
|
<div class="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-7">
|
||||||
<a href="/data?type=brews" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
|
<a
|
||||||
|
href="/data?type=brews"
|
||||||
|
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
|
||||||
|
>
|
||||||
{{ icons::beaker("h-6 w-6 text-accent") }}
|
{{ icons::beaker("h-6 w-6 text-accent") }}
|
||||||
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.brews }}</span>
|
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.brews }}</span>
|
||||||
<span class="text-xs text-text-muted">Brews</span>
|
<span class="text-xs text-text-muted">Brews</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/data?type=roasts" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
|
<a
|
||||||
|
href="/data?type=roasts"
|
||||||
|
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
|
||||||
|
>
|
||||||
{{ icons::fire("h-6 w-6 text-accent") }}
|
{{ icons::fire("h-6 w-6 text-accent") }}
|
||||||
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasts }}</span>
|
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasts }}</span>
|
||||||
<span class="text-xs text-text-muted">Roasts</span>
|
<span class="text-xs text-text-muted">Roasts</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/data?type=roasters" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
|
<a
|
||||||
|
href="/data?type=roasters"
|
||||||
|
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
|
||||||
|
>
|
||||||
{{ icons::building("h-6 w-6 text-accent") }}
|
{{ icons::building("h-6 w-6 text-accent") }}
|
||||||
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasters }}</span>
|
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasters }}</span>
|
||||||
<span class="text-xs text-text-muted">Roasters</span>
|
<span class="text-xs text-text-muted">Roasters</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/data?type=bags" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
|
<a
|
||||||
|
href="/data?type=bags"
|
||||||
|
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
|
||||||
|
>
|
||||||
{{ icons::bag("h-6 w-6 text-accent") }}
|
{{ icons::bag("h-6 w-6 text-accent") }}
|
||||||
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.bags }}</span>
|
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.bags }}</span>
|
||||||
<span class="text-xs text-text-muted">Bags</span>
|
<span class="text-xs text-text-muted">Bags</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/data?type=cups" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
|
<a
|
||||||
|
href="/data?type=cups"
|
||||||
|
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
|
||||||
|
>
|
||||||
{{ icons::cup("h-6 w-6 text-accent") }}
|
{{ icons::cup("h-6 w-6 text-accent") }}
|
||||||
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cups }}</span>
|
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cups }}</span>
|
||||||
<span class="text-xs text-text-muted">Cups</span>
|
<span class="text-xs text-text-muted">Cups</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/data?type=cafes" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
|
<a
|
||||||
|
href="/data?type=cafes"
|
||||||
|
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
|
||||||
|
>
|
||||||
{{ icons::location("h-6 w-6 text-accent") }}
|
{{ icons::location("h-6 w-6 text-accent") }}
|
||||||
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cafes }}</span>
|
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cafes }}</span>
|
||||||
<span class="text-xs text-text-muted">Cafes</span>
|
<span class="text-xs text-text-muted">Cafes</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="/data?type=gear" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
|
<a
|
||||||
|
href="/data?type=gear"
|
||||||
|
class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"
|
||||||
|
>
|
||||||
{{ icons::wrench("h-6 w-6 text-accent") }}
|
{{ icons::wrench("h-6 w-6 text-accent") }}
|
||||||
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.gear }}</span>
|
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.gear }}</span>
|
||||||
<span class="text-xs text-text-muted">Gear</span>
|
<span class="text-xs text-text-muted">Gear</span>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue