From 8eb165a4059a13615f7c49954a9877477cf8dca0 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 29 May 2024 13:58:31 +0200 Subject: [PATCH] Implement ingredient source editing --- backend/src/frontend.rs | 3 +- backend/src/frontend/home.rs | 41 +++---- backend/src/frontend/ingredients_tab.rs | 139 +++++++++++++++++++----- backend/src/index.css | 2 +- backend/src/main.rs | 4 +- foodlib/src/events.rs | 6 - foodlib/src/ingredients.rs | 40 +++++++ 7 files changed, 178 insertions(+), 57 deletions(-) diff --git a/backend/src/frontend.rs b/backend/src/frontend.rs index affdf93..2674ff2 100644 --- a/backend/src/frontend.rs +++ b/backend/src/frontend.rs @@ -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" } } } diff --git a/backend/src/frontend/home.rs b/backend/src/frontend/home.rs index a7cd951..56a2a26 100644 --- a/backend/src/frontend/home.rs +++ b/backend/src/frontend/home.rs @@ -56,26 +56,27 @@ pub async fn content(State(state): State) -> 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("
"); - 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("
"); - 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" {} + } + } + } } } diff --git a/backend/src/frontend/ingredients_tab.rs b/backend/src/frontend/ingredients_tab.rs index dcfa17a..d966721 100644 --- a/backend/src/frontend/ingredients_tab.rs +++ b/backend/src/frontend/ingredients_tab.rs @@ -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; @@ -16,6 +18,10 @@ use super::html_error; pub(crate) fn ingredients_router() -> axum::Router { 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::::login_or_redirect( Arc::new(LOGIN_URL.into()), @@ -78,6 +84,54 @@ pub async fn add_ingredient( } } +#[derive(Deserialize, Clone, PartialEq)] +struct SourceData { + store_id: i32, + weight: BigDecimal, + price: f64, + url: Option, + comment: Option, +} + +async fn update_source( + State(state): State, + Path((ingredient_id, source_id)): Path<(i32, i32)>, + form: axum::extract::Form, +) -> 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) -> Markup { let ingredients = state .db_connection @@ -98,6 +152,7 @@ pub async fn ingredients_view(State(state): State) -> Markup { } (add_ingredient_button()) + table { thead { tr class="p-2" { th class="w-1/3" { "Name" } @@ -170,53 +225,85 @@ async fn delete_ingredient_form(state: State, id: Path) -> Mark async fn sources_table(state: State, id: Path) -> 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)} @@ -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 ▼" } } } diff --git a/backend/src/index.css b/backend/src/index.css index bf2a2fd..796a3e5 100644 --- a/backend/src/index.css +++ b/backend/src/index.css @@ -1 +1 @@ -/*! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.static{position:static}.m-2{margin:.5rem}.mx-16{margin-left:4rem;margin-right:4rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-16{margin-bottom:4rem}.mb-2{margin-bottom:.5rem}.mr-4{margin-right:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.flex{display:flex}.table{display:table}.contents{display:contents}.hidden{display:none}.h-10{height:2.5rem}.h-6{height:1.5rem}.h-full{height:100%}.w-1\/3{width:33.333333%}.w-3\/4{width:75%}.w-6{width:1.5rem}.w-full{width:100%}.shrink{flex-shrink:1}.grow{flex-grow:1}.grow-0{flex-grow:0}.table-auto{table-layout:auto}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.justify-stretch{justify-content:stretch}.justify-items-center{justify-items:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-24{gap:6rem}.gap-5{gap:1.25rem}.overflow-hidden{overflow:hidden}.rounded-xl{border-radius:.75rem}.bg-blue-700{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}.bg-light-bg-light{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.bg-navbar{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}.object-center{-o-object-position:center;object-position:center}.p-2{padding:.5rem}.p-6{padding:1.5rem}.text-center{text-align:center}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-inherit{color:inherit}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}table{table-layout:fixed;border-radius:.5rem;width:100%}th{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){th{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}}th{padding:1rem}td{padding:.5rem}tr:nth-child(2n){border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){tr:nth-child(2n){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}}tr:nth-child(odd){border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){tr:nth-child(odd){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}}.text{background-color:rgb(209 213 219/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.text{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}}.text{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}@media (prefers-color-scheme:dark){.text{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}}.text{border-radius:.375rem;border-width:2px;border-style:solid;padding:.5rem}.dialog{background-color:rgb(209 213 219/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.dialog{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}}.dialog{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}@media (prefers-color-scheme:dark){.dialog{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}}.dialog{border-radius:.375rem;border-width:2px;border-style:solid;padding:.5rem}.fc-select{background-color:rgb(209 213 219/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.fc-select{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}}.fc-select{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}@media (prefers-color-scheme:dark){.fc-select{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}}.fc-select{border-radius:.375rem;border-width:2px;border-style:solid;padding:.5rem}.btn{border-radius:.25rem;padding:.5rem 1rem;font-weight:700}.btn-primary{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.btn-primary{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}}.btn-primary:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){.btn-primary:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}}.btn-success{--tw-bg-opacity:1;background-color:rgb(22 163 74/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.btn-success:hover{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity))}.btn-cancel{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.btn-cancel:hover{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity))}svg{color:red}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}.hover\:bg-blue-800:hover{--tw-bg-opacity:1;background-color:rgb(30 64 175/var(--tw-bg-opacity))}.hover\:shadow-inner:hover{--tw-shadow:inset 0 2px 4px 0 #0000000d;--tw-shadow-colored:inset 0 2px 4px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}@media (prefers-color-scheme:dark){.dark\:bg-dark-bg-dark{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark\:text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}} \ No newline at end of file +/*! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.static{position:static}.m-2{margin:.5rem}.mx-16{margin-left:4rem;margin-right:4rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-16{margin-bottom:4rem}.mb-2{margin-bottom:.5rem}.mr-4{margin-right:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.flex{display:flex}.table{display:table}.contents{display:contents}.hidden{display:none}.h-10{height:2.5rem}.h-6{height:1.5rem}.h-full{height:100%}.w-1\/3{width:33.333333%}.w-2\/3{width:66.666667%}.w-3\/4{width:75%}.w-6{width:1.5rem}.w-full{width:100%}.shrink{flex-shrink:1}.grow{flex-grow:1}.grow-0{flex-grow:0}.table-auto{table-layout:auto}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.justify-stretch{justify-content:stretch}.justify-items-center{justify-items:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-24{gap:6rem}.gap-5{gap:1.25rem}.overflow-hidden{overflow:hidden}.rounded-xl{border-radius:.75rem}.bg-blue-700{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}.bg-light-bg-light{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.bg-navbar{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}.object-center{-o-object-position:center;object-position:center}.p-2{padding:.5rem}.p-6{padding:1.5rem}.text-center{text-align:center}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-inherit{color:inherit}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}table{table-layout:fixed;border-radius:.5rem;width:100%}th{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){th{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}}th{padding:1rem}td{padding:.5rem}tr:nth-child(2n){border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){tr:nth-child(2n){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}}tr:nth-child(odd){border-radius:.5rem;--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){tr:nth-child(odd){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}}.text{background-color:rgb(209 213 219/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.text{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}}.text{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}@media (prefers-color-scheme:dark){.text{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}}.text{border-radius:.375rem;border-width:2px;border-style:solid;padding:.5rem}.dialog{background-color:rgb(209 213 219/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.dialog{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}}.dialog{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}@media (prefers-color-scheme:dark){.dialog{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}}.dialog{border-radius:.375rem;border-width:2px;border-style:solid;padding:.5rem}.fc-select{background-color:rgb(209 213 219/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.fc-select{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}}.fc-select{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}@media (prefers-color-scheme:dark){.fc-select{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}}.fc-select{border-radius:.375rem;border-width:2px;border-style:solid;padding:.5rem}.btn{border-radius:.25rem;padding:.5rem 1rem;font-weight:700}.btn-primary{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.btn-primary{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}}.btn-primary:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){.btn-primary:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity))}}.btn-success{--tw-bg-opacity:1;background-color:rgb(22 163 74/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.btn-success:hover{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity))}.btn-cancel{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.btn-cancel:hover{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity))}svg{color:red}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}.hover\:bg-blue-800:hover{--tw-bg-opacity:1;background-color:rgb(30 64 175/var(--tw-bg-opacity))}.hover\:shadow-inner:hover{--tw-shadow:inset 0 2px 4px 0 #0000000d;--tw-shadow-colored:inset 0 2px 4px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}@media (prefers-color-scheme:dark){.dark\:bg-dark-bg-dark{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark\:text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}} \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index b326a9a..67a30d5 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,4 +1,4 @@ -use std::{env, ops::Deref, sync::Arc}; +use std::{env, ops::Deref}; use fern::colors::{Color, ColoredLevelConfig}; use sqlx::postgres::PgPool; @@ -12,7 +12,7 @@ mod frontend; use axum_login::{ axum_sessions::{async_session::MemoryStore, SessionLayer}, - AuthLayer, PostgresStore, RequireAuthorizationLayer, + AuthLayer, PostgresStore, }; use rand::Rng; diff --git a/foodlib/src/events.rs b/foodlib/src/events.rs index 6c56545..cc9ba0a 100644 --- a/foodlib/src/events.rs +++ b/foodlib/src/events.rs @@ -61,12 +61,6 @@ impl Display for Place { } } -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct Store { - pub store_id: i32, - pub name: String, -} - impl FoodBase { //TODO Merge this with function below, as this currently excludes events without meals pub async fn get_events(&self) -> eyre::Result> { diff --git a/foodlib/src/ingredients.rs b/foodlib/src/ingredients.rs index f9cf884..1e9691b 100644 --- a/foodlib/src/ingredients.rs +++ b/foodlib/src/ingredients.rs @@ -266,6 +266,28 @@ impl FoodBase { Ok(ingredient.ingredient_id) } + pub async fn update_ingredient_source(&self, source: &IngredientSource) -> eyre::Result { + let source = sqlx::query!( + r#" + UPDATE ingredient_sources + SET ingredient_id = $1, store_id = $2, url = $3, package_size = $4, price = $5, unit_id = $6 + WHERE ingredient_source_id = $7 + RETURNING ingredient_source_id + "#, + source.ingredient_id, + source.store_id, + source.url, + source.package_size, + source.price, + source.unit_id, + source.ingredient_source_id + ) + .fetch_one(&*self.pg_pool) + .await?; + + Ok(source.ingredient_source_id) + } + pub async fn get_ingredients(&self) -> eyre::Result> { let records = sqlx::query_as!( Ingredient, @@ -561,6 +583,24 @@ impl FoodBase { .map(|result| result.rows_affected()) .map_err(|err| err.into()) } + + pub async fn get_stores(&self) -> eyre::Result> { + sqlx::query_as!( + Store, + r#" + SELECT * FROM stores + ORDER BY store_id + "# + ) + .fetch_all(&*self.pg_pool) + .await + .map_err(Into::into) + } +} +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Store { + pub store_id: i32, + pub name: String, } mod tests {