diff --git a/app/src/ingredients.rs b/app/src/ingredients.rs index 1c69760..9ace850 100644 --- a/app/src/ingredients.rs +++ b/app/src/ingredients.rs @@ -1,4 +1,4 @@ -use api::{CreateIngredientRequest, EditIngredientRequest, IngredientInfo}; +use api::{CreateIngredientRequest, EditIngredientRequest, IngredientInfo, CreateIngredientResponse}; use itertools::Itertools; use uuid::Uuid; use wasm_bindgen::JsCast; @@ -240,12 +240,12 @@ fn IngredientList(props: &IngredientListProps) -> HtmlResult { }) } -async fn do_add_ingredient( +pub async fn do_add_ingredient( token: String, household: Uuid, name: String, unit: String, -) -> anyhow::Result<()> { +) -> anyhow::Result { let rsp = gloo_net::http::Request::post(api!("household/{household}/ingredients")) .header("Authorization", &format!("Bearer {token}")) .json(&CreateIngredientRequest { @@ -263,7 +263,7 @@ async fn do_add_ingredient( } } - Ok(()) + Ok(rsp.json().await?) } #[function_component] diff --git a/app/src/recipe_creator.rs b/app/src/recipe_creator.rs index 4001f5a..f3737b8 100644 --- a/app/src/recipe_creator.rs +++ b/app/src/recipe_creator.rs @@ -4,12 +4,12 @@ use api::{CreateRecipeRequest, CreateRecipeResponse, IngredientInfo}; use uuid::Uuid; use wasm_bindgen::JsCast; use web_sys::{HtmlInputElement, HtmlTextAreaElement}; -use yew::{prelude::*, suspense::use_future}; +use yew::{prelude::*, suspense::use_future_with_deps}; use yew_router::prelude::use_navigator; use crate::{ api, - bootstrap::{ModalBody, ModalFooter, ModalToggleButton, TitledModal}, + bootstrap::{bs, FormModal, ModalBody, ModalFooter, ModalToggleButton, TitledModal}, RegaladeGlobalState, Route, }; @@ -44,11 +44,16 @@ pub(super) struct IngredientSelectBaseProps { pub children: Children, pub amount: Option, pub ig_select: Option, + #[prop_or_default] + pub refresh: u64, } #[function_component] pub(super) fn IngredientSelectBase(props: &IngredientSelectBaseProps) -> HtmlResult { - let ingredients = use_future(|| fetch_ingredients(props.token.clone(), props.household))?; + let ingredients = use_future_with_deps( + |_| fetch_ingredients(props.token.clone(), props.household), + props.refresh, + )?; let unit = use_state(|| None::); let input_value_h = use_state(|| { @@ -165,6 +170,166 @@ pub(super) fn IngredientSelectBase(props: &IngredientSelectBaseProps) -> HtmlRes } } +#[derive(PartialEq, Clone, Properties)] +struct RecipeCreateIngredientProps { + token: String, + household: Uuid, + on_ig_add: Callback, +} + +#[function_component] +fn RecipeCreateIngredient(props: &RecipeCreateIngredientProps) -> Html { + let error = use_state(|| None::); + + let amount = use_state(|| 1); + let name = use_state(String::new); + let unit = use_state(String::new); + + let get_target = |e: Event| match e.target() { + None => None, + Some(e) => e.dyn_into::().ok(), + }; + + let on_name_change = { + let name = name.clone(); + let error = error.clone(); + Callback::from(move |e| { + let Some(tgt) = get_target(e) else { + return; + }; + + let value = tgt.value(); + + if value.is_empty() { + error.set(Some("Name can't be empty".into())); + return; + } + + name.set(value); + }) + }; + + let on_unit_change = { + let unit = unit.clone(); + Callback::from(move |e| { + let Some(tgt) = get_target(e) else { + return; + }; + + unit.set(tgt.value()); + }) + }; + + let on_amount_change = { + let amount = amount.clone(); + Callback::from(move |e| { + let Some(tgt) = get_target(e) else { + return; + }; + + if !tgt.report_validity() { + return; + } + + amount.set(tgt.value().parse().expect("amount not a number")); + }) + }; + + let on_submit = { + let name = name.clone(); + let unit = unit.clone(); + let amount = amount.clone(); + let token = props.token.clone(); + let household = props.household; + let error = error.clone(); + let on_ig_add = props.on_ig_add.clone(); + Callback::from(move |_| { + let fut = super::ingredients::do_add_ingredient( + token.clone(), + household, + name.to_string(), + unit.to_string(), + ); + + let error = error.clone(); + let info = IngredientInfo { + name: name.to_string(), + unit: (!unit.is_empty()).then(|| unit.to_string()), + }; + let amount = amount.clone(); + let on_ig_add = on_ig_add.clone(); + + wasm_bindgen_futures::spawn_local(async move { + match fut.await { + Ok(rsp) => { + on_ig_add.emit(RecipeIngredient { + id: rsp.id, + info, + amount: *amount as f64, + }); + error.set(None); + + let modal = bs::Modal::get_instance("#newRcpCreateIg"); + modal.hide(); + } + Err(e) => { + error.set(Some(format!("Could not add ingredient: {e}"))); + } + } + }); + }) + }; + + html! { + + if let Some(e) = &*error { + + } +
+ + +
+
+ + +
+
+ + +
+
+ } +} + #[derive(PartialEq, Properties, Clone)] struct IngredientSelectProps { token: String, @@ -222,12 +387,27 @@ fn IngredientSelect(props: &IngredientSelectProps) -> Html { }) }; + let ingredient_refresh = use_state(|| 0u64); + let on_ig_add = { + let on_select = props.onselect.clone(); + let ingredient_refresh = ingredient_refresh.clone(); + Callback::from(move |info| { + on_select.emit(info); + ingredient_refresh.set(ingredient_refresh.wrapping_add(1)); + }) + }; + html! {<> if let Some(e) = &*error { } + Html { {on_amount_change} amount={*amount} ig_select={(*selected_ig).as_ref().map(|(_, info)| AttrValue::from(info.name.clone()))} + refresh={*ingredient_refresh} > - + }