app,server: Allow to edit steps
This commit is contained in:
parent
0c7e52bd8b
commit
1ea5c8aafa
4 changed files with 141 additions and 5 deletions
|
|
@ -123,3 +123,8 @@ pub struct RecipeIngredientEditRequest {
|
||||||
pub struct AddRecipeIngredientRequest {
|
pub struct AddRecipeIngredientRequest {
|
||||||
pub amount: f64,
|
pub amount: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct RecipeEditStepsRequest {
|
||||||
|
pub steps: String
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use api::{
|
use api::{
|
||||||
AddRecipeIngredientRequest, IngredientInfo, RecipeInfo, RecipeIngredientEditRequest,
|
AddRecipeIngredientRequest, IngredientInfo, RecipeEditStepsRequest, RecipeInfo,
|
||||||
RecipeRenameRequest,
|
RecipeIngredientEditRequest, RecipeRenameRequest,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::HtmlInputElement;
|
use web_sys::{HtmlInputElement, HtmlTextAreaElement};
|
||||||
use yew::{
|
use yew::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
suspense::{use_future, use_future_with_deps},
|
suspense::{use_future, use_future_with_deps},
|
||||||
|
|
@ -519,6 +519,110 @@ async fn do_delete_ig(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn do_edit_steps(
|
||||||
|
token: String,
|
||||||
|
household: Uuid,
|
||||||
|
recipe: i64,
|
||||||
|
steps: String,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let rsp = gloo_net::http::Request::patch(api!("household/{household}/recipe/{recipe}/steps"))
|
||||||
|
.json(&RecipeEditStepsRequest { steps })?
|
||||||
|
.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(Properties, PartialEq, Clone)]
|
||||||
|
struct EditStepsProps {
|
||||||
|
token: String,
|
||||||
|
household: Uuid,
|
||||||
|
recipe: i64,
|
||||||
|
steps: String,
|
||||||
|
update: Callback<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
fn EditSteps(props: &EditStepsProps) -> Html {
|
||||||
|
let steps = use_state(|| props.steps.clone());
|
||||||
|
|
||||||
|
let error = use_state(|| None::<String>);
|
||||||
|
|
||||||
|
let onchange = {
|
||||||
|
let steps = steps.clone();
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let Some(target) = e.target() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(target) = target.dyn_into::<HtmlTextAreaElement>() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
steps.set(target.value());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let on_submit = {
|
||||||
|
let steps = steps.clone();
|
||||||
|
let token = props.token.clone();
|
||||||
|
let household = props.household;
|
||||||
|
let recipe = props.recipe;
|
||||||
|
let error = error.clone();
|
||||||
|
let update = props.update.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let token = token.clone();
|
||||||
|
let steps = steps.clone();
|
||||||
|
let error = error.clone();
|
||||||
|
let update = update.clone();
|
||||||
|
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
match do_edit_steps(token.clone(), household, recipe, (*steps).clone()).await {
|
||||||
|
Ok(_) => {
|
||||||
|
let modal = bs::Modal::get_instance("#rcpEditSteps");
|
||||||
|
modal.hide();
|
||||||
|
error.set(None);
|
||||||
|
update.emit(());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error.set(Some(format!("Could not edit steps: {e}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {<>
|
||||||
|
<ModalToggleButton modal_id="rcpEditSteps" classes={classes!("btn", "btn-secondary", "mb-2")}>
|
||||||
|
{"Edit Steps"}
|
||||||
|
</ModalToggleButton>
|
||||||
|
<FormModal
|
||||||
|
id="rcpEditSteps"
|
||||||
|
fade=true
|
||||||
|
centered=true
|
||||||
|
submit_label="Edit"
|
||||||
|
title="Edit steps"
|
||||||
|
{on_submit}
|
||||||
|
>
|
||||||
|
if let Some(e) = &*error {
|
||||||
|
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||||
|
{e}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<textarea
|
||||||
|
class="form-control"
|
||||||
|
value={(*steps).clone()}
|
||||||
|
{onchange}
|
||||||
|
/>
|
||||||
|
</FormModal>
|
||||||
|
</>}
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
||||||
let recipe_render = use_state(|| 0u64);
|
let recipe_render = use_state(|| 0u64);
|
||||||
|
|
@ -626,6 +730,13 @@ fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
||||||
<li class="list-group-item">{text}</li>
|
<li class="list-group-item">{text}</li>
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
<EditSteps
|
||||||
|
token={props.token.clone()}
|
||||||
|
household={props.household}
|
||||||
|
recipe={props.id}
|
||||||
|
steps={r.steps.clone()}
|
||||||
|
update={update}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>},
|
</>},
|
||||||
Err(e) => html! {
|
Err(e) => html! {
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,10 @@ pub(crate) fn router(api_allowed: Option<HeaderValue>) -> Router<AppState> {
|
||||||
.patch(recipe::edit_name)
|
.patch(recipe::edit_name)
|
||||||
.layer(mk_service(vec![Method::GET, Method::PATCH])),
|
.layer(mk_service(vec![Method::GET, Method::PATCH])),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/household/:house_id/recipe/:recipe_id/steps",
|
||||||
|
patch(recipe::edit_step).layer(mk_service(vec![Method::PATCH])),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/household/:house_id/recipe/:recipe_id/ingredients/:iid",
|
"/household/:house_id/recipe/:recipe_id/ingredients/:iid",
|
||||||
patch(recipe::edit_ig_amount)
|
patch(recipe::edit_ig_amount)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use api::{
|
use api::{
|
||||||
AddRecipeIngredientRequest, CreateRecipeRequest, CreateRecipeResponse, EmptyResponse,
|
AddRecipeIngredientRequest, CreateRecipeRequest, CreateRecipeResponse, EmptyResponse,
|
||||||
IngredientInfo, ListRecipesResponse, RecipeInfo, RecipeIngredientEditRequest,
|
IngredientInfo, ListRecipesResponse, RecipeEditStepsRequest, RecipeInfo,
|
||||||
RecipeRenameRequest,
|
RecipeIngredientEditRequest, RecipeRenameRequest,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
|
|
@ -210,3 +210,19 @@ pub(super) async fn add_ig_request(
|
||||||
|
|
||||||
Ok(EmptyResponse {}.into())
|
Ok(EmptyResponse {}.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn edit_step(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
RecipeExtractor(recipe): RecipeExtractor,
|
||||||
|
Json(req): Json<RecipeEditStepsRequest>,
|
||||||
|
) -> JsonResult<EmptyResponse> {
|
||||||
|
let model = recipe::ActiveModel {
|
||||||
|
id: ActiveValue::Set(recipe.id),
|
||||||
|
steps: ActiveValue::Set(req.steps),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
model.update(&state.db).await?;
|
||||||
|
|
||||||
|
Ok(EmptyResponse {}.into())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue