app,server: Allow to add ingredients to existing recipes
This commit is contained in:
parent
6004520fb9
commit
27f9295aa3
5 changed files with 319 additions and 69 deletions
|
|
@ -1,4 +1,7 @@
|
|||
use api::{RecipeInfo, RecipeIngredientEditRequest, RecipeRenameRequest};
|
||||
use api::{
|
||||
AddRecipeIngredientRequest, IngredientInfo, RecipeInfo, RecipeIngredientEditRequest,
|
||||
RecipeRenameRequest,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use uuid::Uuid;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
|
@ -12,6 +15,7 @@ use yew_router::prelude::*;
|
|||
use crate::{
|
||||
api,
|
||||
bootstrap::{bs, ConfirmDangerModal, FormModal, ModalToggleButton},
|
||||
recipe_creator::IngredientSelectBase,
|
||||
RegaladeGlobalState, Route,
|
||||
};
|
||||
|
||||
|
|
@ -350,6 +354,150 @@ fn EditIngredient(props: &EditIngredientProps) -> Html {
|
|||
</>}
|
||||
}
|
||||
|
||||
async fn do_add_ingredient_recipe(
|
||||
token: String,
|
||||
household: Uuid,
|
||||
recipe: i64,
|
||||
ingredient: i64,
|
||||
amount: f64,
|
||||
) -> anyhow::Result<()> {
|
||||
let rsp = gloo_net::http::Request::put(api!(
|
||||
"household/{household}/recipe/{recipe}/ingredients/{ingredient}"
|
||||
))
|
||||
.json(&AddRecipeIngredientRequest { amount })?
|
||||
.header("Authorization", &format!("Bearer {token}"))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !rsp.ok() {
|
||||
let body = rsp.text().await.unwrap_or_default();
|
||||
anyhow::bail!("Could not get recipes (code={}): {body}", rsp.status());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
struct AddIngredientProps {
|
||||
token: String,
|
||||
household: Uuid,
|
||||
recipe: i64,
|
||||
update: Callback<()>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn AddIngredientInner(props: &AddIngredientProps) -> HtmlResult {
|
||||
let error = use_state(|| None::<String>);
|
||||
|
||||
let amount = use_state(|| None::<f64>);
|
||||
let selected_ig = use_state(|| None::<(i64, IngredientInfo)>);
|
||||
|
||||
let s_ig = selected_ig.clone();
|
||||
let am = amount.clone();
|
||||
let err = error.clone();
|
||||
let token = props.token.clone();
|
||||
let household = props.household;
|
||||
let recipe = props.recipe;
|
||||
let update = props.update.clone();
|
||||
let on_submit = Callback::from(move |_| match &*s_ig {
|
||||
&Some((id, _)) => match &*am {
|
||||
&Some(amount) => {
|
||||
let fut = do_add_ingredient_recipe(token.clone(), household, recipe, id, amount);
|
||||
|
||||
let am = am.clone();
|
||||
let s_ig = s_ig.clone();
|
||||
let err = err.clone();
|
||||
let update = update.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match fut.await {
|
||||
Ok(_) => {
|
||||
err.set(None);
|
||||
am.set(None);
|
||||
s_ig.set(None);
|
||||
update.emit(());
|
||||
|
||||
let modal = bs::Modal::get_instance("#rcpEditNewIg");
|
||||
modal.hide();
|
||||
}
|
||||
Err(e) => {
|
||||
err.set(Some(format!("Could not add ingredient: {e}")));
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
None => {
|
||||
err.set(Some("Amount can't be empty".into()));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
err.set(Some("Ingredient does not exist".into()));
|
||||
}
|
||||
});
|
||||
|
||||
let on_ig_change = {
|
||||
let selected_ig = selected_ig.clone();
|
||||
Callback::from(move |v| {
|
||||
selected_ig.set(v);
|
||||
})
|
||||
};
|
||||
let on_amount_change = {
|
||||
let amount = amount.clone();
|
||||
Callback::from(move |v| {
|
||||
amount.set(Some(v));
|
||||
})
|
||||
};
|
||||
|
||||
Ok({
|
||||
html! {<>
|
||||
<FormModal
|
||||
id="rcpEditNewIg"
|
||||
fade=true
|
||||
centered=true
|
||||
submit_label="Add"
|
||||
{on_submit}
|
||||
title="Add ingredient"
|
||||
>
|
||||
if let Some(e) = &*error {
|
||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||
{e}
|
||||
</div>
|
||||
}
|
||||
<IngredientSelectBase
|
||||
token={props.token.clone()}
|
||||
household={props.household}
|
||||
{on_ig_change}
|
||||
{on_amount_change}
|
||||
amount={*amount}
|
||||
ig_select={(*selected_ig).as_ref().map(|(_, info)| AttrValue::from(info.name.clone()))}
|
||||
/>
|
||||
</FormModal>
|
||||
<ModalToggleButton modal_id="rcpEditNewIg" classes={classes!("btn", "btn-secondary")}>
|
||||
{"Add Ingredient"}
|
||||
</ModalToggleButton>
|
||||
</>}
|
||||
})
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn AddIngredient(props: &AddIngredientProps) -> Html {
|
||||
let fallback = html! {
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">{"Loading ..."}</span>
|
||||
</div>
|
||||
};
|
||||
|
||||
html! {<>
|
||||
<Suspense {fallback}>
|
||||
<AddIngredientInner
|
||||
token={props.token.clone()}
|
||||
household={props.household}
|
||||
recipe={props.recipe}
|
||||
update={props.update.clone()}
|
||||
/>
|
||||
</Suspense>
|
||||
</>}
|
||||
}
|
||||
|
||||
async fn do_delete_ig(
|
||||
token: String,
|
||||
household: Uuid,
|
||||
|
|
@ -427,7 +575,7 @@ fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
|||
<hr />
|
||||
<div class="text-start">
|
||||
<h2>{"Ingredients"}</h2>
|
||||
<ul class="list-group">
|
||||
<ul class="list-group mb-2">
|
||||
{for r.ingredients.iter().map(|(id, info, amount)| {
|
||||
let delete_modal_id = format!("rcpRmIg{id}");
|
||||
html!{
|
||||
|
|
@ -463,6 +611,12 @@ fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
|||
</li>
|
||||
}})}
|
||||
</ul>
|
||||
<AddIngredient
|
||||
token={props.token.clone()}
|
||||
household={props.household}
|
||||
recipe={props.id}
|
||||
update={update.clone()}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="text-start">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue