app,server: Allow to edit the rating
This commit is contained in:
parent
5ef000dec0
commit
d614029931
4 changed files with 167 additions and 15 deletions
|
|
@ -1,6 +1,6 @@
|
|||
use api::{
|
||||
AddRecipeIngredientRequest, IngredientInfo, RecipeEditStepsRequest, RecipeInfo,
|
||||
RecipeIngredientEditRequest, RecipeRenameRequest,
|
||||
AddRecipeIngredientRequest, IngredientInfo, RecipeEditRating, RecipeEditStepsRequest,
|
||||
RecipeInfo, RecipeIngredientEditRequest, RecipeRenameRequest,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use uuid::Uuid;
|
||||
|
|
@ -19,6 +19,21 @@ use crate::{
|
|||
RegaladeGlobalState, Route,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
struct RecipeRatingProps {
|
||||
rating: u8,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn RecipeRating(props: &RecipeRatingProps) -> Html {
|
||||
let rating = (props.rating + 1).min(3);
|
||||
html! {
|
||||
<div aria-label={format!("Rating: {rating}")}>
|
||||
{ for (0..rating).map(|_| html!{<i aria-hidden="true" class="bi-star-fill" />}) }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_all_recipes(
|
||||
token: String,
|
||||
household: Uuid,
|
||||
|
|
@ -50,7 +65,7 @@ fn RecipeListInner(props: &RecipeListProps) -> HtmlResult {
|
|||
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-2">
|
||||
{for l.recipes.iter().sorted_by_key(|(_, name, _)| name).map(|(id, name, _)| html!{
|
||||
{for l.recipes.iter().sorted_by_key(|(_, name, _)| name).map(|(id, name, rating)| html!{
|
||||
<div class="col" key={*id}>
|
||||
<div class="p-3 border rounded border-light-subtle h-100">
|
||||
<Link<Route>
|
||||
|
|
@ -64,6 +79,7 @@ fn RecipeListInner(props: &RecipeListProps) -> HtmlResult {
|
|||
>
|
||||
{name}
|
||||
</Link<Route>>
|
||||
<RecipeRating {rating} />
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
|
|
@ -623,19 +639,119 @@ fn EditSteps(props: &EditStepsProps) -> Html {
|
|||
</>}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Properties)]
|
||||
struct RecipeRatingProps {
|
||||
async fn do_edit_rating(
|
||||
token: String,
|
||||
household: Uuid,
|
||||
recipe: i64,
|
||||
rating: u8,
|
||||
) -> anyhow::Result<()> {
|
||||
let rsp = gloo_net::http::Request::patch(api!("household/{household}/recipe/{recipe}/rating"))
|
||||
.json(&RecipeEditRating { rating })?
|
||||
.header("Authorization", &format!("Bearer {token}"))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !rsp.ok() {
|
||||
let body = rsp.text().await.unwrap_or_default();
|
||||
anyhow::bail!("Could not edit rating (code={}): {body}", rsp.status());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq, Clone)]
|
||||
struct EditRatingProps {
|
||||
token: String,
|
||||
household: Uuid,
|
||||
recipe: i64,
|
||||
rating: u8,
|
||||
update: Callback<()>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn RecipeRating(props: &RecipeRatingProps) -> Html {
|
||||
let rating = (props.rating + 1).min(3);
|
||||
html! {
|
||||
<div aria-label={format!("Rating: {rating}")}>
|
||||
{ for (0..rating).map(|_| html!{<i aria-hidden="true" class="bi-star-fill" />}) }
|
||||
</div>
|
||||
}
|
||||
fn EditRating(props: &EditRatingProps) -> Html {
|
||||
let rating = use_state(|| props.rating);
|
||||
|
||||
let error = use_state(|| None::<String>);
|
||||
|
||||
let onchange = {
|
||||
let rating = rating.clone();
|
||||
Callback::from(move |e: Event| {
|
||||
let Some(target) = e.target() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(target) = target.dyn_into::<HtmlInputElement>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !target.report_validity() {
|
||||
return;
|
||||
}
|
||||
|
||||
rating.set(target.value().parse().expect("invalid number"));
|
||||
})
|
||||
};
|
||||
let on_submit = {
|
||||
let rating = rating.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 rating = rating.clone();
|
||||
let error = error.clone();
|
||||
let update = update.clone();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match do_edit_rating(token.clone(), household, recipe, *rating - 1).await {
|
||||
Ok(_) => {
|
||||
let modal = bs::Modal::get_instance("#rcpEditRating");
|
||||
modal.hide();
|
||||
error.set(None);
|
||||
update.emit(());
|
||||
}
|
||||
Err(e) => {
|
||||
error.set(Some(format!("Could not edit rating: {e}")));
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
html! {<>
|
||||
<ModalToggleButton modal_id="rcpEditRating" classes={classes!("btn", "btn-secondary")}>
|
||||
{"Edit Rating"}
|
||||
</ModalToggleButton>
|
||||
<FormModal
|
||||
id="rcpEditRating"
|
||||
fade=true
|
||||
centered=true
|
||||
submit_label="Edit"
|
||||
title="Edit rating"
|
||||
{on_submit}
|
||||
>
|
||||
if let Some(e) = &*error {
|
||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||
{e}
|
||||
</div>
|
||||
}
|
||||
<div class="form-floating">
|
||||
<input
|
||||
type="number"
|
||||
max="3"
|
||||
min="1"
|
||||
class="form-control"
|
||||
id="rcpEditRatingInp"
|
||||
value={(*rating).to_string()}
|
||||
{onchange}
|
||||
/>
|
||||
<label for="rcpEditRatingInp">{"Rating"}</label>
|
||||
</div>
|
||||
</FormModal>
|
||||
</>}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
|
|
@ -686,8 +802,19 @@ fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
|||
name={r.name.clone()}
|
||||
update={update.clone()}
|
||||
/>
|
||||
<div class="mt-2">
|
||||
<RecipeRating rating={r.rating}/>
|
||||
<div class="container row mt-2">
|
||||
<div class="col-8">
|
||||
<RecipeRating rating={r.rating} />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<EditRating
|
||||
token={props.token.clone()}
|
||||
recipe={props.id}
|
||||
household={props.household}
|
||||
rating={r.rating + 1}
|
||||
update={update.clone()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
if let Some(e) = &*error {
|
||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue