feat(brews): pre-select bag when clicking Brew on homepage bag card

- Add initial-value attribute to searchable-select web component
- Update bag card Brew link to pass bag_id query parameter to add page
- Thread bag_id through route handler and template to pre-select the bag
This commit is contained in:
Jon Seager 2026-02-06 10:53:42 +00:00
parent 0030f87eed
commit 4a7b054de1
No known key found for this signature in database
5 changed files with 16 additions and 2 deletions

View file

@ -48,6 +48,7 @@ const ADD_TABS: &[Tab] = &[
pub(crate) struct AddQuery { pub(crate) struct AddQuery {
#[serde(rename = "type", default = "default_type")] #[serde(rename = "type", default = "default_type")]
entity_type: String, entity_type: String,
bag_id: Option<String>,
} }
fn default_type() -> String { fn default_type() -> String {
@ -100,6 +101,7 @@ pub(crate) async fn add_page(
cafe_options, cafe_options,
defaults: brew_form.defaults, defaults: brew_form.defaults,
quick_note_options: brew_form.quick_note_options, quick_note_options: brew_form.quick_note_options,
pre_select_bag_id: query.bag_id,
}; };
render_html(template).map(IntoResponse::into_response) render_html(template).map(IntoResponse::into_response)

View file

@ -172,6 +172,7 @@ pub struct AddTemplate {
pub cafe_options: Vec<CafeOptionView>, pub cafe_options: Vec<CafeOptionView>,
pub defaults: BrewDefaultsView, pub defaults: BrewDefaultsView,
pub quick_note_options: Vec<QuickNoteView>, pub quick_note_options: Vec<QuickNoteView>,
pub pre_select_bag_id: Option<String>,
} }
pub fn render_template<T: Template>(template: T) -> Result<String, askama::Error> { pub fn render_template<T: Template>(template: T) -> Result<String, askama::Error> {

View file

@ -47,6 +47,17 @@ customElements.define("searchable-select", class extends HTMLElement {
this.appendChild(searchWrap); this.appendChild(searchWrap);
this.appendChild(selectedWrap); this.appendChild(selectedWrap);
const initialValue = this.getAttribute("initial-value");
if (initialValue) {
const match = buttons.find((btn) => btn.value === initialValue);
if (match) {
hidden.value = match.value;
display.textContent = match.dataset.display;
searchWrap.classList.add("hidden");
selectedWrap.classList.remove("hidden");
}
}
search.addEventListener("input", () => { search.addEventListener("input", () => {
const q = search.value.toLowerCase(); const q = search.value.toLowerCase();
options.classList.toggle("hidden", !q); options.classList.toggle("hidden", !q);

View file

@ -257,7 +257,7 @@
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-1 text-sm"> <div class="flex flex-col gap-1 text-sm">
<span class="text-text">Bag *</span> <span class="text-text">Bag *</span>
<searchable-select name="bag_id" placeholder="Type to search bags&hellip;"> <searchable-select name="bag_id" placeholder="Type to search bags&hellip;"{% if let Some(bag_id) = pre_select_bag_id %} initial-value="{{ bag_id }}"{% endif %}>
{% for bag in bag_options %} {% for bag in bag_options %}
<button type="button" value="{{ bag.id }}" data-display="{{ bag.roast_name }}" <button type="button" value="{{ bag.id }}" data-display="{{ bag.roast_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">

View file

@ -11,7 +11,7 @@
</p> </p>
{% if is_authenticated %} {% if is_authenticated %}
<div class="mt-3 pt-3 border-t flex gap-3"> <div class="mt-3 pt-3 border-t flex gap-3">
<a href="/data?type=brews" class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"> <a href="/add?type=brew&bag_id={{ bag.id }}" class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover">
{% call icons::plus_circle("h-4 w-4") %} {% call icons::plus_circle("h-4 w-4") %}
Brew Brew
</a> </a>