app: Allow to create ingredients in the recipe creator
This commit is contained in:
parent
ce29b7b75d
commit
725c2ff3be
2 changed files with 192 additions and 8 deletions
|
|
@ -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<CreateIngredientResponse> {
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -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<f64>,
|
||||
pub ig_select: Option<AttrValue>,
|
||||
#[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::<String>);
|
||||
|
||||
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<RecipeIngredient>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn RecipeCreateIngredient(props: &RecipeCreateIngredientProps) -> Html {
|
||||
let error = use_state(|| None::<String>);
|
||||
|
||||
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::<HtmlInputElement>().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! {
|
||||
<FormModal
|
||||
id="newRcpCreateIg"
|
||||
fade=true
|
||||
centered=true
|
||||
submit_label="Create & Add"
|
||||
title="Create & Add ingredient"
|
||||
{on_submit}
|
||||
>
|
||||
if let Some(e) = &*error {
|
||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||
{e}
|
||||
</div>
|
||||
}
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="form-control"
|
||||
id="newRcpCreateIgNameInp"
|
||||
placeholder={"Name"}
|
||||
value={name.to_string()}
|
||||
onchange={on_name_change}
|
||||
/>
|
||||
<label for="newRcpCreateIgNameInp">{"Ingredient Name"}</label>
|
||||
</div>
|
||||
<div class="form-floating my-1">
|
||||
<input
|
||||
class="form-control"
|
||||
id="newRcpCreateIgUnitInp"
|
||||
placeholder={"Unit"}
|
||||
value={unit.to_string()}
|
||||
onchange={on_unit_change}
|
||||
/>
|
||||
<label for="newRcpCreateIgUnitInp">{"Ingredient Unit"}</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="1"
|
||||
id="newRcpCreateIgAmountInp"
|
||||
placeholder={"Amount"}
|
||||
value={amount.to_string()}
|
||||
onchange={on_amount_change}
|
||||
/>
|
||||
<label for="newRcpCreateIgAmountInp">{"Ingredient Amount"}</label>
|
||||
</div>
|
||||
</FormModal>
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||
{e}
|
||||
</div>
|
||||
}
|
||||
<RecipeCreateIngredient
|
||||
token={props.token.clone()}
|
||||
household={props.household}
|
||||
{on_ig_add}
|
||||
/>
|
||||
<Suspense {fallback}>
|
||||
<IngredientSelectBase
|
||||
token={props.token.clone()}
|
||||
|
|
@ -236,10 +416,14 @@ fn IngredientSelect(props: &IngredientSelectProps) -> Html {
|
|||
{on_amount_change}
|
||||
amount={*amount}
|
||||
ig_select={(*selected_ig).as_ref().map(|(_, info)| AttrValue::from(info.name.clone()))}
|
||||
refresh={*ingredient_refresh}
|
||||
>
|
||||
<button class="btn btn-primary" {onclick}>
|
||||
<button class="btn btn-primary me-1" {onclick}>
|
||||
{"Add"}
|
||||
</button>
|
||||
<button class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#newRcpCreateIg">
|
||||
{"Create"}
|
||||
</button>
|
||||
</IngredientSelectBase>
|
||||
</Suspense>
|
||||
</>}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue