app,server: Allow to display a recipe
This commit is contained in:
parent
f3788e31a9
commit
1a0ffb2d89
5 changed files with 159 additions and 4 deletions
|
|
@ -101,3 +101,10 @@ pub struct CreateRecipeResponse {
|
|||
pub struct ListRecipesResponse {
|
||||
pub recipes: Vec<(i64, String)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct RecipeInfo {
|
||||
pub name: String,
|
||||
pub steps: Vec<String>,
|
||||
pub ingredients: Vec<(i64, IngredientInfo, f64)>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ fn switch(route: Route) -> Html {
|
|||
},
|
||||
Route::Recipe { id } => html! {
|
||||
<GlobalStateRedirector {route}>
|
||||
{format!("RECIPE {id}")}
|
||||
<recipe::RecipeViewer {id} />
|
||||
</GlobalStateRedirector>
|
||||
},
|
||||
Route::SearchRecipe => html!{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use api::RecipeInfo;
|
||||
use uuid::Uuid;
|
||||
use yew::{prelude::*, suspense::use_future};
|
||||
use yew_router::prelude::*;
|
||||
|
|
@ -34,7 +35,7 @@ fn RecipeListInner(props: &RecipeListProps) -> HtmlResult {
|
|||
Ok(match &*state {
|
||||
Ok(l) => html! {
|
||||
<div class="container text-center">
|
||||
<div class="row row-cols-2 row-cols-sm-2 row-cols-md-4 g-2 mb-1">
|
||||
<div class="row row-cols-2 row-cols-sm-2 row-cols-md-4 g-2 mb-2">
|
||||
{for l.recipes.iter().map(|(id, name)| html!{
|
||||
<div class="col" key={*id}>
|
||||
<div class="p-3 border rounded border-light-subtle h-100">
|
||||
|
|
@ -82,3 +83,89 @@ pub fn RecipeList() -> Html {
|
|||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Properties)]
|
||||
pub struct RecipeViewerProps {
|
||||
pub id: i64,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Properties)]
|
||||
struct RecipeViewerInnerProps {
|
||||
id: i64,
|
||||
token: String,
|
||||
household: Uuid,
|
||||
}
|
||||
|
||||
async fn fetch_recipe(token: String, household: Uuid, id: i64) -> anyhow::Result<RecipeInfo> {
|
||||
let rsp = gloo_net::http::Request::get(api!("household/{household}/recipe/{id}"))
|
||||
.header("Authorization", &format!("Bearer {token}"))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !rsp.ok() {
|
||||
let body = rsp.text().await.unwrap_or_default();
|
||||
anyhow::bail!("Could not get recipe (code={}): {body}", rsp.status());
|
||||
}
|
||||
|
||||
Ok(rsp.json().await?)
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
||||
let recipe = use_future(|| fetch_recipe(props.token.clone(), props.household, props.id))?;
|
||||
|
||||
Ok(match &*recipe {
|
||||
Ok(r) => html! {<>
|
||||
<h1>{&r.name}</h1>
|
||||
<hr />
|
||||
<div class="text-start">
|
||||
<h2>{"Ingredients"}</h2>
|
||||
<ul class="list-group">
|
||||
{for r.ingredients.iter().map(|(id, info, amount)| html!{
|
||||
<li key={*id} class="list-group-item">
|
||||
{format!("{amount}{} {}", info.unit.as_deref().unwrap_or(""), info.name)}
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="text-start">
|
||||
<h2>{"Steps"}</h2>
|
||||
<ul class="list-group list-group-flush">
|
||||
{for r.steps.iter().map(|text| html!{
|
||||
<li class="list-group-item">{text}</li>
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</>},
|
||||
Err(e) => html! {
|
||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||
{format!("Error fetching recipe: {e}")}
|
||||
</div>
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn RecipeViewer(props: &RecipeViewerProps) -> Html {
|
||||
let global_state = use_state(RegaladeGlobalState::get);
|
||||
let fallback = html! {
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">{"Loading ..."}</span>
|
||||
</div>
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class="d-flex align-items-center justify-content-center w-100">
|
||||
<div class={classes!("container", "text-center", "rounded", "border", "pt-2", "m-2")}>
|
||||
<Suspense {fallback}>
|
||||
<RecipeViewerInner
|
||||
id={props.id}
|
||||
token={global_state.token.token.clone()}
|
||||
household={global_state.household.id}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,4 +222,8 @@ pub(crate) fn router(api_allowed: Option<HeaderValue>) -> Router<AppState> {
|
|||
.get(recipe::list_recipes)
|
||||
.layer(mk_service(vec![Method::POST, Method::GET])),
|
||||
)
|
||||
.route(
|
||||
"/household/:house_id/recipe/:recipe_id",
|
||||
get(recipe::fetch_recipe).layer(mk_service(vec![Method::GET])),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
use api::{CreateRecipeRequest, CreateRecipeResponse, ListRecipesResponse};
|
||||
use axum::{extract::State, Json};
|
||||
use api::{
|
||||
CreateRecipeRequest, CreateRecipeResponse, IngredientInfo, ListRecipesResponse, RecipeInfo,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
Json,
|
||||
};
|
||||
use sea_orm::{prelude::*, ActiveValue, TransactionTrait};
|
||||
|
||||
use crate::entity::{ingredient, prelude::*, recipe, recipe_ingerdients, recipe_steps};
|
||||
|
|
@ -77,3 +82,55 @@ pub(super) async fn list_recipes(
|
|||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub(super) struct RecipeId {
|
||||
recipe_id: i64,
|
||||
}
|
||||
|
||||
pub(super) async fn fetch_recipe(
|
||||
AuthorizedHousehold(household): AuthorizedHousehold,
|
||||
State(state): State<AppState>,
|
||||
Path(RecipeId { recipe_id }): Path<RecipeId>,
|
||||
) -> JsonResult<RecipeInfo> {
|
||||
let Some(recipe) = household
|
||||
.find_related(Recipe)
|
||||
.filter(recipe::Column::Id.eq(recipe_id))
|
||||
.one(&state.db).await? else {
|
||||
return Err(RouteError::RessourceNotFound);
|
||||
};
|
||||
|
||||
let steps = recipe
|
||||
.find_related(RecipeSteps)
|
||||
.all(&state.db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|m| m.text)
|
||||
.collect();
|
||||
|
||||
let recipe_ingredients = recipe.find_related(Ingredient).all(&state.db).await?;
|
||||
|
||||
let mut ingredients = Vec::new();
|
||||
|
||||
for ingredient in recipe_ingredients {
|
||||
ingredients.push((
|
||||
ingredient.id,
|
||||
IngredientInfo {
|
||||
name: ingredient.name,
|
||||
unit: ingredient.unit,
|
||||
},
|
||||
RecipeIngerdients::find_by_id((recipe.id, ingredient.id))
|
||||
.one(&state.db)
|
||||
.await?
|
||||
.expect("Ingredient should exist as it was fetched")
|
||||
.amount,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(RecipeInfo {
|
||||
name: recipe.name,
|
||||
steps,
|
||||
ingredients,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue