mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Rudimentary shopping list table.
This commit is contained in:
parent
1b969f9355
commit
1a188f45aa
@ -167,7 +167,7 @@ impl Step {
|
|||||||
|
|
||||||
/// Unique identifier for an Ingredient. Ingredients are identified by name, form,
|
/// Unique identifier for an Ingredient. Ingredients are identified by name, form,
|
||||||
/// and measurement type. (Volume, Count, Weight)
|
/// and measurement type. (Volume, Count, Weight)
|
||||||
#[derive(PartialEq, PartialOrd, Eq, Ord)]
|
#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)]
|
||||||
pub struct IngredientKey(String, Option<String>, String);
|
pub struct IngredientKey(String, Option<String>, String);
|
||||||
|
|
||||||
/// Ingredient in a recipe. The `name` and `form` fields with the measurement type
|
/// Ingredient in a recipe. The `name` and `form` fields with the measurement type
|
||||||
|
@ -15,8 +15,33 @@ use crate::console_log;
|
|||||||
use crate::service::AppService;
|
use crate::service::AppService;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use recipes::{Ingredient, IngredientAccumulator, IngredientKey};
|
||||||
use sycamore::{context::use_context, prelude::*};
|
use sycamore::{context::use_context, prelude::*};
|
||||||
|
|
||||||
|
struct RecipeCheckBoxProps {
|
||||||
|
i: usize,
|
||||||
|
title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component(RecipeCheckBox<G>)]
|
||||||
|
fn recipe_check_box(props: RecipeCheckBoxProps) -> View<G> {
|
||||||
|
let app_service = use_context::<AppService>();
|
||||||
|
// This is total hack but it works around the borrow issues with
|
||||||
|
// the `view!` macro.
|
||||||
|
let i = props.i;
|
||||||
|
let id_as_str = Rc::new(format!("{}", i));
|
||||||
|
let id_cloned = id_as_str.clone();
|
||||||
|
let id_cloned_2 = id_as_str.clone();
|
||||||
|
view! {
|
||||||
|
input(type="checkbox", name="recipe_id", value=id_as_str.clone(), on:click=move |_| {
|
||||||
|
let mut app_service = app_service.clone();
|
||||||
|
console_log!("clicked checkbox for id {}", id_cloned);
|
||||||
|
app_service.add_recipe_by_index(i);
|
||||||
|
})
|
||||||
|
label(for=id_cloned_2) { (props.title) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[component(RecipeSelector<G>)]
|
#[component(RecipeSelector<G>)]
|
||||||
pub fn recipe_selector() -> View<G> {
|
pub fn recipe_selector() -> View<G> {
|
||||||
let app_service = use_context::<AppService>();
|
let app_service = use_context::<AppService>();
|
||||||
@ -28,16 +53,9 @@ pub fn recipe_selector() -> View<G> {
|
|||||||
Keyed(KeyedProps{
|
Keyed(KeyedProps{
|
||||||
iterable: titles,
|
iterable: titles,
|
||||||
template: |(i, title)| {
|
template: |(i, title)| {
|
||||||
// This is total hack but it works around the borrow issues with
|
|
||||||
// the `view!` macro.
|
|
||||||
let id_as_str = Rc::new(format!("{}", i));
|
|
||||||
let id_cloned = id_as_str.clone();
|
|
||||||
let id_cloned_2 = id_as_str.clone();
|
|
||||||
view! {
|
view! {
|
||||||
input(type="checkbox", name="recipe_id", value=id_as_str.clone(), on:click=move |_| {
|
RecipeCheckBox(RecipeCheckBoxProps{i: i, title: title})
|
||||||
console_log!("clicked checkbox for id {}", id_cloned);
|
}
|
||||||
})
|
|
||||||
label(for=id_cloned_2) { (title) } }
|
|
||||||
},
|
},
|
||||||
key: |(i, title)| (*i, title.clone()),
|
key: |(i, title)| (*i, title.clone()),
|
||||||
})
|
})
|
||||||
@ -45,12 +63,48 @@ pub fn recipe_selector() -> View<G> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component(MenuView<G>)]
|
#[component(ShoppingList<G>)]
|
||||||
|
fn shopping_list() -> View<G> {
|
||||||
|
let app_service = use_context::<AppService>();
|
||||||
|
let ingredients = create_memo(move || {
|
||||||
|
let mut acc = IngredientAccumulator::new();
|
||||||
|
for r in app_service.get_menu_list().get().iter() {
|
||||||
|
acc.accumulate_from(r);
|
||||||
|
}
|
||||||
|
acc.ingredients()
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
|
.collect::<Vec<(IngredientKey, Ingredient)>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
table(class="shopping_list") {
|
||||||
|
tr {
|
||||||
|
th { "Quantity" }
|
||||||
|
th { "Ingredient" }
|
||||||
|
}
|
||||||
|
Indexed(IndexedProps{
|
||||||
|
iterable: ingredients,
|
||||||
|
template: |(_k, i)| {
|
||||||
|
view! {
|
||||||
|
tr {
|
||||||
|
td { (i.amt) }
|
||||||
|
td { (i.name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component(ShoppingView<G>)]
|
||||||
pub fn shopping_view() -> View<G> {
|
pub fn shopping_view() -> View<G> {
|
||||||
view! {
|
view! {
|
||||||
h1 {
|
h1 {
|
||||||
"Select your recipes"
|
"Select your recipes"
|
||||||
}
|
}
|
||||||
RecipeSelector()
|
RecipeSelector()
|
||||||
|
ShoppingList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{console_debug, console_error};
|
use crate::{console_debug, console_error};
|
||||||
|
|
||||||
use reqwasm::http;
|
use reqwasm::http;
|
||||||
@ -22,12 +24,14 @@ use recipes::{parse, Recipe};
|
|||||||
pub struct AppService {
|
pub struct AppService {
|
||||||
// TODO(jwall): Should each Recipe also be a Signal?
|
// TODO(jwall): Should each Recipe also be a Signal?
|
||||||
recipes: Signal<Vec<(usize, Recipe)>>,
|
recipes: Signal<Vec<(usize, Recipe)>>,
|
||||||
|
menu_list: Signal<Vec<Recipe>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppService {
|
impl AppService {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
recipes: Signal::new(Vec::new()),
|
recipes: Signal::new(Vec::new()),
|
||||||
|
menu_list: Signal::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +64,16 @@ impl AppService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_menu_list(&self) -> Signal<Vec<Recipe>> {
|
||||||
|
self.menu_list.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_recipe_by_index(&mut self, i: usize) {
|
||||||
|
let mut v = (*self.menu_list.get()).clone();
|
||||||
|
v.push(self.recipes.get()[i].1.clone());
|
||||||
|
self.menu_list.set(v);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_recipes(&self) -> Signal<Vec<(usize, Recipe)>> {
|
pub fn get_recipes(&self) -> Signal<Vec<(usize, Recipe)>> {
|
||||||
self.recipes.clone()
|
self.recipes.clone()
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
// Copyright 2022 Jeremy Wall
|
// Copyright 2022 Jeremy Wall
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -45,7 +43,7 @@ fn route_switch<G: Html>(route: ReadSignal<AppRoutes>) -> View<G> {
|
|||||||
RecipeView(*idx)
|
RecipeView(*idx)
|
||||||
},
|
},
|
||||||
AppRoutes::Menu => view! {
|
AppRoutes::Menu => view! {
|
||||||
"TODO!!"
|
ShoppingView()
|
||||||
},
|
},
|
||||||
AppRoutes::NotFound => view! {
|
AppRoutes::NotFound => view! {
|
||||||
"NotFound"
|
"NotFound"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user