record meals that were added to list, allow delete with ingredients
ci/woodpecker/push/e2e Pipeline was successful
ci/woodpecker/push/fmt Pipeline was successful
ci/woodpecker/push/test Pipeline was successful

This commit is contained in:
2026-08-04 00:01:23 -04:00
parent dcd1203d31
commit 863d43ef8c
11 changed files with 465 additions and 33 deletions
+62 -7
View File
@@ -3,7 +3,9 @@ use pulldown_cmark::{Options, Parser, html as cmark_html};
use crate::{
domain::PresenceUser,
domain::{Category, GroceryList, Item, Meal, MealCategory, MealIngredient, Passkey, User},
domain::{
Category, GroceryList, Item, ListMeal, Meal, MealCategory, MealIngredient, Passkey, User,
},
};
pub fn login_page(error: Option<&str>, invite: Option<&str>) -> Markup {
@@ -168,7 +170,12 @@ pub fn account_page(
)
}
pub fn lists_page(user: &User, lists: &[GroceryList], csrf_token: &str) -> Markup {
pub fn lists_page(
user: &User,
lists: &[GroceryList],
categories: &[Category],
csrf_token: &str,
) -> Markup {
page(
"Your lists",
Some(user),
@@ -176,7 +183,7 @@ pub fn lists_page(user: &User, lists: &[GroceryList], csrf_token: &str) -> Marku
div class="page-heading" {
div {
p class="eyebrow" { "SHARED LISTS" }
h1 { "Grocery lists" }
h1 class="page-title" { "Grocery lists" }
p class="lede" { "Everything you need, in one place." }
}
}
@@ -215,6 +222,7 @@ pub fn lists_page(user: &User, lists: &[GroceryList], csrf_token: &str) -> Marku
button class="button button-primary" type="submit" { "Create list" }
}
}
(categories_panel(categories, csrf_token, false))
section id="sharing" class="panel sharing-panel" {
div class="panel-heading" { h2 { "Invite someone" } }
p { "Create a one-time invite link so a new person can join." }
@@ -247,7 +255,7 @@ pub fn meals_page(
div class="page-heading" {
div {
p class="eyebrow" { "MEAL LIBRARY" }
h1 { "Meals" }
h1 class="page-title" { "Meals" }
p class="lede" { "Save a meal and add its ingredients to any list." }
}
a class="button button-primary" href="/meals/new" { "New meal" }
@@ -711,6 +719,7 @@ pub fn list_page(
list: &GroceryList,
items: &[Item],
categories: &[Category],
list_meals: &[ListMeal],
presence: &[PresenceUser],
csrf_token: &str,
) -> Markup {
@@ -722,7 +731,6 @@ pub fn list_page(
a class="back-link" href="/lists" { "← All lists" }
div class="list-topbar-actions" {
span class="live-pill" { span class="live-dot" {} "Live" }
button class="button button-small add-meal-button" hx-get=(format!("/meals?picker={}", list.id)) hx-target="#meal-picker" hx-swap="innerHTML" { "+ Add meal" }
}
}
div id="meal-picker" class="meal-picker" {}
@@ -738,8 +746,8 @@ pub fn list_page(
(list_content_fragment(list, items, categories, csrf_token, false))
}
aside class="side-column" {
(list_meals_panel(list_meals, list.id, csrf_token, false))
(presence_panel(presence, false))
(categories_panel(categories, csrf_token, false))
section class="panel tip-panel" {
span class="tip-label" { "TIP" }
p { "Check items off as you go. Everyone viewing this list will see it instantly." }
@@ -1006,11 +1014,58 @@ pub fn live_list_fragments(
list: &GroceryList,
items: &[Item],
categories: &[Category],
list_meals: &[ListMeal],
csrf_token: &str,
) -> Markup {
html! {
(list_content_fragment(list, items, categories, csrf_token, true))
(categories_panel(categories, csrf_token, true))
(list_meals_panel(list_meals, list.id, csrf_token, true))
}
}
pub fn list_meals_panel(
list_meals: &[ListMeal],
list_id: i64,
csrf_token: &str,
out_of_band: bool,
) -> Markup {
let panel = html! {
div class="panel-heading" {
h2 { "Meals on this list" }
span class="count-badge" { (list_meals.len()) }
}
@if list_meals.is_empty() {
p class="muted" { "No meals added yet." }
} @else {
div class="list-meals" {
@for meal in list_meals {
div class="list-meal-row" {
span class="list-meal-icon" { "🍽" }
span class="list-meal-name" { (meal.name) }
form
hx-post=(format!("/lists/{}/meals/{}/remove", list_id, meal.id))
hx-target="#list-items"
hx-swap="outerHTML"
class="list-meal-remove"
{
input type="hidden" name="csrf" value=(csrf_token);
button type="submit" class="list-meal-remove-button" aria-label=(format!("Remove {} from list", meal.name)) { "" }
}
}
}
}
}
button class="button button-small add-meal-button" hx-get=(format!("/meals?picker={}", list_id)) hx-target="#meal-picker" hx-swap="innerHTML" { "+ Add meal" }
};
if out_of_band {
html! {
section id="list-meals-panel" class="panel list-meals-panel" hx-swap-oob="outerHTML" { (panel) }
}
} else {
html! {
section id="list-meals-panel" class="panel list-meals-panel" { (panel) }
}
}
}