Skip to content

Commit

Permalink
Implement ingredient source editing
Browse files Browse the repository at this point in the history
  • Loading branch information
TrueDoctor committed May 29, 2024
1 parent f98bf7d commit 8eb165a
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 57 deletions.
3 changes: 1 addition & 2 deletions backend/src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ pub fn html_error(reason: &str, redirect: &str) -> Markup {
class="flex flex-col items-center justify-center text-red-500" {
div {
h1 { "Error" }
p { "Failed to delete recipe" }
p { (reason) }
button class="btn btn-primary" hx-get="/recipes" hx-target="#content" { "Back" }
button class="btn btn-primary" hx-get=(redirect) hx-target="#content" { "Back" }
}
}

Expand Down
41 changes: 21 additions & 20 deletions backend/src/frontend/home.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,27 @@ pub async fn content(State(state): State<MyAppState>) -> Markup {
}

pub fn navbar() -> Markup {
{
extern crate alloc;
extern crate maud;
let mut __maud_output = alloc::string::String::with_capacity(1288usize);
__maud_output.push_str("<div class=\"\n flex items-center justify-between flex-wrap \n bg-navbar text-white \n mx-16 my-4 \n rounded-xl shadow-xl overflow-hidden\n \">");
maud::macro_private::render_to!(
&navbutton("Ingredients", "/ingredients"),
&mut __maud_output
);
maud::macro_private::render_to!(&navbutton("Recipes", "/recipes"), &mut __maud_output);
maud::macro_private::render_to!(&navbutton("Events", "/events"), &mut __maud_output);
maud::macro_private::render_to!(
&navbutton("Inventories", "/inventories"),
&mut __maud_output
);
maud::macro_private::render_to!(&navbutton("Stores", "/stores"), &mut __maud_output);
__maud_output.push_str("<a hx-get=\"");
maud::macro_private::render_to!(&LOGIN_URL, &mut __maud_output);
__maud_output.push_str("\" hx-target=\"#content\" class=\"flex flex-col items-center\n transition ease-in-out transition duration-200 \n rounded-xl p-6\n hover:shadow-inner hover:bg-blue-800\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\" class=\"h-6 w-6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M217.9 105.9L340.7 228.7c7.2 7.2 11.3 17.1 11.3 27.3s-4.1 20.1-11.3 27.3L217.9 406.1c-6.4 6.4-15 9.9-24 9.9c-18.7 0-33.9-15.2-33.9-33.9l0-62.1L32 320c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l128 0 0-62.1c0-18.7 15.2-33.9 33.9-33.9c9 0 17.6 3.6 24 9.9zM352 416l64 0c17.7 0 32-14.3 32-32l0-256c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64 0c53 0 96 43 96 96l0 256c0 53-43 96-96 96l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32z\"></path></svg></a></div>");
maud::PreEscaped(__maud_output)
html! {
div class="
flex items-center justify-between flex-wrap
bg-navbar text-white
mx-16 my-4
rounded-xl shadow-xl overflow-hidden
" {
(navbutton("Ingredients", "/ingredients"))
(navbutton("Recipes", "/recipes"))
(navbutton("Events", "/events"))
(navbutton("Inventories", "/inventories"))
(navbutton("Stores", "/stores"))
a hx-get=("/auth/login") hx-target="#content" class="flex flex-col items-center
transition ease-in-out transition duration-200
rounded-xl p-6
hover:shadow-inner hover:bg-blue-800" {
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="h-6 w-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" {
path d="M217.9 105.9L340.7 228.7c7.2 7.2 11.3 17.1 11.3 27.3s-4.1 20.1-11.3 27.3L217.9 406.1c-6.4 6.4-15 9.9-24 9.9c-18.7 0-33.9-15.2-33.9-33.9l0-62.1L32 320c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l128 0 0-62.1c0-18.7 15.2-33.9 33.9-33.9c9 0 17.6 3.6 24 9.9zM352 416l64 0c17.7 0 32-14.3 32-32l0-256c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l64 0c53 0 96 43 96 96l0 256c0 53-43 96-96 96l-64 0c-17.7 0-32-14.3-32-32s14.3-32 32-32z" {}
}
}
}
}
}

Expand Down
139 changes: 113 additions & 26 deletions backend/src/frontend/ingredients_tab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use std::sync::Arc;
use axum::extract::{Form, Path, State};
use axum::response::{IntoResponse, Response};
use axum_login::RequireAuthorizationLayer;
use foodlib::{Ingredient, IngredientSource, User};
use bigdecimal::BigDecimal;
use foodlib::{Ingredient, IngredientSource, Store, User};
use maud::{html, Markup};
use serde::Deserialize;
use sqlx::postgres::types::PgMoney;

use crate::MyAppState;

Expand All @@ -16,6 +18,10 @@ use super::html_error;
pub(crate) fn ingredients_router() -> axum::Router<MyAppState> {
axum::Router::new()
.route("/", axum::routing::post(add_ingredient))
.route(
"/sources/:ingredient/:source",
axum::routing::post(update_source),
)
.route("/:id", axum::routing::delete(delete))
.route_layer(RequireAuthorizationLayer::<i64, User>::login_or_redirect(
Arc::new(LOGIN_URL.into()),
Expand Down Expand Up @@ -78,6 +84,54 @@ pub async fn add_ingredient(
}
}

#[derive(Deserialize, Clone, PartialEq)]
struct SourceData {
store_id: i32,
weight: BigDecimal,
price: f64,
url: Option<String>,
comment: Option<String>,
}

async fn update_source(
State(state): State<MyAppState>,
Path((ingredient_id, source_id)): Path<(i32, i32)>,
form: axum::extract::Form<SourceData>,
) -> Markup {
let ingredient = form.0;
if source_id == -1 {
match state
.add_ingredient_source(
ingredient_id,
ingredient.store_id,
ingredient.weight,
PgMoney((ingredient.price * 100.) as i64),
ingredient.comment,
0,
)
.await
{
Err(_) => html_error("Failed to add ingredient source", "/ingredients"),
Ok(_) => sources_table(State(state), Path(ingredient_id)).await,
}
} else {
let source = IngredientSource {
ingredient_source_id: source_id,
ingredient_id,
store_id: ingredient.store_id,
package_size: ingredient.weight,
price: PgMoney((ingredient.price * 100.) as i64),
unit_id: 0,
url: ingredient.url,
comment: ingredient.comment,
};
match state.update_ingredient_source(&source).await {
Err(_) => html_error("Failed to update ingredient source", "/ingredients"),
Ok(_) => sources_table(State(state), Path(ingredient_id)).await,
}
}
}

pub async fn ingredients_view(State(state): State<MyAppState>) -> Markup {
let ingredients = state
.db_connection
Expand All @@ -98,6 +152,7 @@ pub async fn ingredients_view(State(state): State<MyAppState>) -> Markup {

}
(add_ingredient_button())

table {
thead { tr class="p-2" {
th class="w-1/3" { "Name" }
Expand Down Expand Up @@ -170,53 +225,85 @@ async fn delete_ingredient_form(state: State<MyAppState>, id: Path<i32>) -> Mark

async fn sources_table(state: State<MyAppState>, id: Path<i32>) -> Markup {
let Ok(sources) = state.get_ingredient_sources(Some(id.0)).await else {
return html_error("Failed to fetch ingredient_sources", "ingredients");
return html_error("Failed to fetch ingredient_sources", "/ingredients");
};

let Ok(stores) = state.get_stores().await else {
return html_error("Failed to fetch stores", "/ingredients");
};

let Ok(ingredient) = state.get_ingredient(id.0).await else {
return html_error("Failed to fetch ingredient", "/ingredients");
};

let dummy_ingredient = IngredientSource::default();
html! {
tr {
td colspan="6" {
dialog open="true" class="w-2/3 dialog" id="popup"{
h1 {(ingredient.name)}
table {
thead { tr {
th { "ID" }
th { "Store" }
th { "Weight" }
th { "Price" }
th { "Url" }
thead {
tr {
th { "Store" }
th { "Weight" }
th { "Price" }
th { "Url" }
th {}
}
}
tbody {
(format_ingredient_source(&dummy_ingredient))
(format_ingredient_source(&dummy_ingredient, &stores, id.0))
@for source in sources {
(format_ingredient_source(&source))
(format_ingredient_source(&source, &stores, id.0))
}
}
}
}
}
button class="btn btn-cancel" hx-swap="delete" hx-target="#popup" hx-get="/" {"Close"}
}
}
}

fn format_ingredient_source(source: &IngredientSource) -> Markup {
fn format_ingredient_source(
source: &IngredientSource,
stores: &[Store],
ingredient_id: i32,
) -> Markup {
let button = |text, id| {
html! {
button class="btn btn-primary" hx-post=(format!("/ingredients/{id}")) hx-include="closest tr" { (text) }
button class="btn btn-primary" hx-target="#popup" hx-swap="outerHTML" hx-post=(format!("/ingredients/sources/{ingredient_id}/{id}")) hx-include="closest tr" { (text) }
}
};

let (id, button) = if source.ingredient_source_id == -1 {
("New".into(), button("Add", -1))
let button = if source.ingredient_source_id == -1 {
button("Add", -1)
} else {
(
source.ingredient_source_id.to_string(),
button("Save", source.ingredient_source_id),
)
button("Save", source.ingredient_source_id)
};
let option = |store: &Store, source_store| match store.store_id == source_store {
false => html! {
option
label=(store.name)
value=(store.store_id) {
(store.name)
}
},
true => html! {
option
label=(store.name)
value=(store.store_id)
selected {(store.name)}
},
};
html! {
tr {
td { (&id) }
td {input class="text" name="store_id" value=(source.store_id); }
td {input class="text" name="package_size" value=(source.package_size);}
td {
label for="stores" style="display:none" { "Pick a Store" }
select name="store_id" id="stores" required="true" class="text" {
@for store in stores {
(option(store, source.store_id))
}
}
}
td {input class="text" name="weight" value=(source.package_size);}
td {input class="text" name="price" type="number" value=(&(source.price.0 as f64 / 100.));}
td {input class="text" name="url" value=(source.url.clone().unwrap_or_default());}
td {(button)}
Expand Down Expand Up @@ -245,7 +332,7 @@ fn format_ingredient(ingredient: &Ingredient) -> Markup {
button class="btn btn-primary"
hx-get=(format!("/ingredients/sources/{}", ingredient.ingredient_id))
hx-swap="afterend"
hx-target=(format!("#ingredient-{}", ingredient.ingredient_id))
// hx-target=(format!("#ingredient-{}", ingredient.ingredient_id))
{ "Sources ▼" }
}
}
Expand Down
Loading

0 comments on commit 8eb165a

Please sign in to comment.