diff --git a/kitchen/src/cli.rs b/kitchen/src/cli.rs index 6d35ec9..89f7e9e 100644 --- a/kitchen/src/cli.rs +++ b/kitchen/src/cli.rs @@ -86,7 +86,7 @@ pub fn output_ingredients_list(rs: Vec) { for r in rs { acc.accumulate_from(&r); } - for (_, i) in acc.ingredients() { + for (_, (i, _)) in acc.ingredients() { print!("{}", i.amt.normalize()); println!(" {}", i.name); } @@ -99,7 +99,7 @@ pub fn output_ingredients_csv(rs: Vec) { } let out = std::io::stdout(); let mut writer = csv::Writer::from_writer(out); - for (_, i) in acc.ingredients() { + for (_, (i, _)) in acc.ingredients() { writer .write_record(&[format!("{}", i.amt.normalize()), i.name]) .expect("Failed to write csv."); diff --git a/recipes/src/lib.rs b/recipes/src/lib.rs index 71d654b..a44ba4f 100644 --- a/recipes/src/lib.rs +++ b/recipes/src/lib.rs @@ -14,7 +14,7 @@ pub mod parse; pub mod unit; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use chrono::NaiveDate; @@ -49,7 +49,7 @@ impl Mealplan { } /// A Recipe with a title, description, and a series of steps. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] pub struct Recipe { pub title: String, pub desc: Option, @@ -92,11 +92,14 @@ impl Recipe { let mut acc = IngredientAccumulator::new(); acc.accumulate_from(&self); acc.ingredients() + .into_iter() + .map(|(k, v)| (k, v.0)) + .collect() } } pub struct IngredientAccumulator { - inner: BTreeMap, + inner: BTreeMap)>, } impl IngredientAccumulator { @@ -110,26 +113,31 @@ impl IngredientAccumulator { for i in r.steps.iter().map(|s| s.ingredients.iter()).flatten() { let key = i.key(); if !self.inner.contains_key(&key) { - self.inner.insert(key, i.clone()); + let mut set = BTreeSet::new(); + set.insert(r.title.clone()); + self.inner.insert(key, (i.clone(), set)); } else { - let amt = match (self.inner[&key].amt, i.amt) { + let amt = match (self.inner[&key].0.amt, i.amt) { (Volume(rvm), Volume(lvm)) => Volume(lvm + rvm), (Count(lqty), Count(rqty)) => Count(lqty + rqty), (Weight(lqty), Weight(rqty)) => Weight(lqty + rqty), _ => unreachable!(), }; - self.inner.get_mut(&key).map(|i| i.amt = amt); + self.inner.get_mut(&key).map(|(i, set)| { + i.amt = amt; + set.insert(r.title.clone()); + }); } } } - pub fn ingredients(self) -> BTreeMap { + pub fn ingredients(self) -> BTreeMap)> { self.inner } } /// A Recipe step. It has the time for the step if there is one, instructions, and an ingredients /// list. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] pub struct Step { pub prep_time: Option, pub instructions: String, @@ -172,7 +180,7 @@ pub struct IngredientKey(String, Option, String); /// Ingredient in a recipe. The `name` and `form` fields with the measurement type /// uniquely identify an ingredient. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct Ingredient { pub id: Option, // TODO(jwall): use uuid instead? pub name: String, diff --git a/recipes/src/unit.rs b/recipes/src/unit.rs index 6f38162..03115a1 100644 --- a/recipes/src/unit.rs +++ b/recipes/src/unit.rs @@ -26,7 +26,7 @@ use std::{ use num_rational::Ratio; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialOrd, Eq, Ord)] /// Volume Measurements for ingredients in a recipe. pub enum VolumeMeasure { // Imperial volume measurements. US. @@ -225,7 +225,7 @@ impl Display for VolumeMeasure { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialOrd, Eq, Ord)] pub enum WeightMeasure { Gram(Quantity), Kilogram(Quantity), @@ -338,7 +338,7 @@ impl Display for WeightMeasure { use WeightMeasure::{Gram, Kilogram, Oz, Pound}; -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] /// Measurements in a Recipe with associated units for them. pub enum Measure { /// Volume measurements as meter cubed base unit @@ -447,7 +447,7 @@ impl Display for Measure { } /// Represents a Quantity for an ingredient of a recipe. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, Ord)] pub enum Quantity { /// Whole or non fractional quantities of an ingredient in a recipe. Whole(u32), diff --git a/web/src/components/shopping_list.rs b/web/src/components/shopping_list.rs index 6e06275..4f0537c 100644 --- a/web/src/components/shopping_list.rs +++ b/web/src/components/shopping_list.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::service::AppService; use std::collections::HashMap; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; use recipes::{Ingredient, IngredientKey}; use sycamore::{context::use_context, prelude::*}; @@ -33,7 +33,7 @@ pub fn shopping_list() -> View { .iter() .map(|(k, v)| (k.clone(), v.clone())) .filter(|(k, _v)| !filtered_keys.get().contains(k)) - .collect::>() + .collect::))>>() })); let table_view = Signal::new(View::empty()); create_effect( @@ -44,15 +44,17 @@ pub fn shopping_list() -> View { tr { th { " Quantity " } th { " Ingredient " } + th { " Recipes " } } tbody {Indexed(IndexedProps{ iterable: ingredients.clone(), - template: cloned!((filtered_keys, modified_amts) => move |(k, i)| { + template: cloned!((filtered_keys, modified_amts) => move |(k, (i, rs))| { let mut modified_amt_set = (*modified_amts.get()).clone(); let amt = modified_amt_set.entry(k.clone()).or_insert(Signal::new(format!("{}", i.amt.normalize()))).clone(); modified_amts.set(modified_amt_set); let name = i.name; let form = i.form.map(|form| format!("({})", form)).unwrap_or_default(); + let names = rs.iter().fold(String::new(), |acc, s| format!("{}{},", acc, s)).trim_end_matches(",").to_owned(); view! { tr { td { input(bind:value=amt.clone(), class="ingredient-count-sel", type="text") } @@ -60,7 +62,8 @@ pub fn shopping_list() -> View { let mut keyset = (*filtered_keys.get()).clone(); keyset.insert(k.clone()); filtered_keys.set(keyset); - })) " " (name) " " (form) } + })) " " (name) " " (form) "" } + td { (names) } } } }), diff --git a/web/src/service.rs b/web/src/service.rs index 6b9745d..fb9fc84 100644 --- a/web/src/service.rs +++ b/web/src/service.rs @@ -11,7 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use crate::{console_debug, console_error, console_log}; @@ -124,7 +124,7 @@ impl AppService { self.recipes.get().get(idx).map(|(_, r)| r.clone()) } - pub fn get_shopping_list(&self) -> BTreeMap { + pub fn get_shopping_list(&self) -> BTreeMap)> { let mut acc = IngredientAccumulator::new(); let recipe_counts = self.menu_list.get(); for (idx, count) in recipe_counts.iter() {