diff options
| -rw-r--r-- | src/api/mod.rs | 171 | ||||
| -rw-r--r-- | src/components/buy.rs | 0 | ||||
| -rw-r--r-- | src/components/container.rs | 2 | ||||
| -rw-r--r-- | src/components/inventory.rs | 43 | ||||
| -rw-r--r-- | src/components/mod.rs | 2 | ||||
| -rw-r--r-- | src/components/product_overview.rs | 25 | ||||
| -rw-r--r-- | src/components/unit_overview.rs | 21 | ||||
| -rw-r--r-- | src/lib.rs | 20 | ||||
| -rw-r--r-- | src/pages/associate_barcode.rs | 163 | ||||
| -rw-r--r-- | src/pages/buy.rs | 156 | ||||
| -rw-r--r-- | src/pages/create_product.rs | 96 | ||||
| -rw-r--r-- | src/pages/home.rs | 7 | ||||
| -rw-r--r-- | src/pages/inventory.rs | 19 | ||||
| -rw-r--r-- | src/pages/mod.rs | 2 |
14 files changed, 476 insertions, 251 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs index bc800fb..3bc870c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,103 +1,110 @@ -use leptos::{ - error::Error, - prelude::{Read, expect_context}, -}; -use reactive_stores::Store; +use leptos::error::Error; use rocie_client::{ apis::{ api_get_inventory_api::amount_by_id, api_get_product_api::{ - product_by_id, product_by_name, product_suggestion_by_name, products, + product_by_id, product_by_name, product_suggestion_by_name, products_in_storage, + products_registered, }, api_get_unit_api::unit_by_id, - api_get_unit_property_api::unit_property_by_id, + api_get_unit_property_api::{unit_properties, unit_property_by_id}, api_set_barcode_api::buy_barcode, + api_set_product_api::{associate_barcode, register_product}, configuration::Configuration, }, models::{ - BarcodeId, Product, ProductAmount, ProductId, Unit, UnitId, UnitProperty, UnitPropertyId, + Barcode, BarcodeId, Product, ProductAmount, ProductId, ProductStub, Unit, UnitId, + UnitProperty, UnitPropertyId, }, }; -use crate::{ConfigState, ConfigStateStoreFields}; +macro_rules! get_config { + () => {{ + use crate::ConfigStateStoreFields; + use leptos::prelude::Read; -pub(crate) async fn get_amount_by_id(product_id: ProductId) -> Result<ProductAmount, Error> { - let config = expect_context::<Store<ConfigState>>(); - amount_by_id(&config.config().read(), product_id) - .await - .map_err(Into::<Error>::into) -} -pub(crate) async fn get_product_by_id(product_id: ProductId) -> Result<Product, Error> { - let config = expect_context::<Store<ConfigState>>(); - product_by_id(&config.config().read(), product_id) - .await - .map_err(Into::<Error>::into) -} -pub(crate) async fn get_product_by_name( - name: String, -) -> Result< - Product, - rocie_client::apis::Error<rocie_client::apis::api_get_product_api::ProductByNameError>, -> { - let config = expect_context::<Store<ConfigState>>(); - product_by_name(&config.config().read(), &name).await -} -pub(crate) async fn get_products_by_part_name(part_name: String) -> Result<Vec<Product>, Error> { - let config = expect_context::<Store<ConfigState>>(); - product_suggestion_by_name(&config.config().read(), &part_name) - .await - .map_err(Into::<Error>::into) -} -pub(crate) async fn get_unit_by_id(unit_id: UnitId) -> Result<Unit, Error> { - let config = expect_context::<Store<ConfigState>>(); - unit_by_id(&config.config().read(), unit_id) - .await - .map_err(Into::<Error>::into) -} -pub(crate) async fn get_unit_property_by_id( - unit_id: UnitPropertyId, -) -> Result<UnitProperty, Error> { - let config = expect_context::<Store<ConfigState>>(); - unit_property_by_id(&config.config().read(), unit_id) - .await - .map_err(Into::<Error>::into) + let config = + leptos::prelude::expect_context::<reactive_stores::Store<crate::ConfigState>>(); + config.config().read() + }}; } -pub(crate) async fn get_full_product_by_id( - id: ProductId, -) -> Result<(Product, ProductAmount, Unit), Error> { - let amount = get_amount_by_id(id).await?; - let product = get_product_by_id(id).await?; - let unit = get_unit_by_id(amount.amount.unit).await?; +pub(crate) use get_config; - Ok::<_, Error>((product, amount, unit)) -} -pub(crate) async fn get_product_unit_by_id( - id: ProductId, -) -> Result<(Product, UnitProperty), Error> { - let product = get_product_by_id(id).await?; - let unit = get_unit_property_by_id(product.unit_property).await?; +macro_rules! mk_wrapper { + ( + $orig:ident($($arg_name:ident : $arg_type:ty),*) -> $output_type:ty + as $new_name:ident + ) => { + pub(crate) async fn $new_name( + $($arg_name : $arg_type),* + ) -> Result<$output_type, Error> { + let config = get_config!(); - Ok::<_, Error>((product, unit)) -} + $orig(&config, $($arg_name),*) + .await + .map_err(Into::<Error>::into) + } + }; -pub(crate) async fn get_products() -> Result<Vec<Product>, Error> { - let config = expect_context::<Store<ConfigState>>(); - products(&config.config().read()) - .await - .map_err(Into::<Error>::into) -} + ( + @treat_404_as_None + $orig:ident($($arg_name:ident : $arg_type:ty),*) -> Option<$output_type:ty> + as $new_name:ident + ) => { + pub(crate) async fn $new_name( + $($arg_name : $arg_type),* + ) -> Result<Option<$output_type>, Error> { + let config = get_config!(); -pub(crate) async fn buy_barcode_wrapper( - config: &Configuration, - barcode_number: u32, -) -> Result<(), Error> { - buy_barcode( - config, - BarcodeId { - value: barcode_number, - }, - ) - .await - .map_err(Into::<Error>::into) + match $orig(&config, $($arg_name),*).await + { + Ok(ok) => Ok::<_, leptos::error::Error>(Some(ok)), + Err(err) => match err { + rocie_client::apis::Error::ResponseError(ref response_content) => { + match response_content.status.as_u16() { + 404 => Ok(None), + _ => Err(err.into()), + } + } + err => Err(err.into()), + }, + } + } + }; + + ( + @external_config + $orig:ident(&config, $($arg_name:ident : $arg_type:ty),*) -> $output_type:ty + as $new_name:ident + ) => { + pub(crate) async fn $new_name( + config: &Configuration, + $($arg_name : $arg_type),* + ) -> Result<$output_type, Error> { + $orig(config, $($arg_name),*) + .await + .map_err(Into::<Error>::into) + } + } } + +mk_wrapper!(product_by_id(product_id: ProductId) -> Product as product_by_id_wrapped); + +mk_wrapper!(@treat_404_as_None product_by_name(name: &str) -> Option<Product> as product_by_name_404_wrapped); +mk_wrapper!(@external_config product_by_name(&config, name: &str) -> Product as product_by_name_external_wrapped); + +mk_wrapper!(product_suggestion_by_name(part_name: &str) -> Vec<Product> as product_suggestion_by_name_wrapped); + +mk_wrapper!(unit_by_id(unit_id: UnitId) -> Unit as unit_by_id_wrapped); +mk_wrapper!(unit_property_by_id(unit_id: UnitPropertyId) -> UnitProperty as unit_property_by_id_wrapped); +mk_wrapper!(unit_properties() -> Vec<UnitProperty> as unit_properties_wrapped); + +mk_wrapper!(amount_by_id(product_id: ProductId) -> ProductAmount as amount_by_id_wrapped); + +mk_wrapper!(products_registered() -> Vec<Product> as products_registered_wrapped); +mk_wrapper!(products_in_storage() -> Vec<Product> as products_in_storage_wrapped); + +mk_wrapper!(@external_config buy_barcode(&config, barcode_number: BarcodeId, times: u32) -> () as buy_barcode_external_wrapped); +mk_wrapper!(@external_config register_product(&config, product_stub: ProductStub) -> ProductId as register_product_external_wrapped); +mk_wrapper!(@external_config associate_barcode(&config, id: ProductId, barcode: Barcode) -> () as associate_barcode_external_wrapped); diff --git a/src/components/buy.rs b/src/components/buy.rs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/components/buy.rs diff --git a/src/components/container.rs b/src/components/container.rs index 83b9584..d6d2f03 100644 --- a/src/components/container.rs +++ b/src/components/container.rs @@ -31,7 +31,7 @@ pub fn Container( .into_iter() .map(|(name, path)| { view! { - <li class="bg-green-400/40 p-2 first:rounded-l-full last:rounded-r-full"> + <li class="bg-green-400/40 p-2 text-nowrap first:rounded-l-full last:rounded-r-full"> <button on:click=move |_| { use_navigate()(path, NavigateOptions::default()); }>{name}</button> diff --git a/src/components/inventory.rs b/src/components/inventory.rs new file mode 100644 index 0000000..275dd0b --- /dev/null +++ b/src/components/inventory.rs @@ -0,0 +1,43 @@ +use leptos::{IntoView, component, view}; + +use crate::{ + api::products_in_storage_wrapped, + components::{async_fetch::AsyncFetch, container::Container, icon_p::IconP}, +}; + +#[component] +pub fn Inventory() -> impl IntoView { + view! { + <Container + header="Inventory" + buttons=vec![ + (view! { <IconP icon=icondata_io::IoClipboard text="Inventory" /> }, "inventory"), + (view! { <IconP icon=icondata_io::IoPricetags text="Consume" /> }, "consume"), + (view! { <IconP icon=icondata_io::IoStorefront text="Buy" /> }, "buy"), + ] + > + { + AsyncFetch! { + @map_error_in_producer + fetcher = products_in_storage_wrapped(), + producer = |products| { + let products_num = products.len(); + let plural_s = if products_num == 1 { "" } else { "s" }; + let products_value = 2; + let products_currency = "EUR"; + + view! { + <p> + {format!( + "You have {products_num} product{plural_s} \ + in stock with a value \ + of {products_value} {products_currency}.", + )} + </p> + } + } + } + } + </Container> + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index efc4842..2c3d79a 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -10,9 +10,11 @@ pub mod input_placeholder; pub mod select_placeholder; // Specific +pub mod inventory; pub mod product_overview; pub mod recipies; pub mod site_header; +pub mod unit_overview; fn get_id() -> u32 { static ID: AtomicU32 = AtomicU32::new(0); diff --git a/src/components/product_overview.rs b/src/components/product_overview.rs index 5413dc1..bf81624 100644 --- a/src/components/product_overview.rs +++ b/src/components/product_overview.rs @@ -1,7 +1,7 @@ use leptos::{IntoView, component, view}; use crate::{ - api::get_products, + api::products_registered_wrapped, components::{async_fetch::AsyncFetch, container::Container, icon_p::IconP}, }; @@ -9,31 +9,20 @@ use crate::{ pub fn ProductOverview() -> impl IntoView { view! { <Container - header="Inventory" + header="Products" buttons=vec![ - (view! { <IconP icon=icondata_io::IoClipboard text="Inventory" /> }, "inventory"), - (view! { <IconP icon=icondata_io::IoPricetags text="Consume" /> }, "consume"), - (view! { <IconP icon=icondata_io::IoStorefront text="Buy" /> }, "buy"), + (view! { <IconP icon=icondata_io::IoClipboard text="Show products" /> }, "products"), + (view! { <IconP icon=icondata_io::IoPricetags text="Create product" /> }, "create-product"), + (view! { <IconP icon=icondata_io::IoPricetags text="Associate barcode with product" /> }, "associate-barcode-product"), ] > { AsyncFetch! { @map_error_in_producer - fetcher = get_products(), + fetcher = products_registered_wrapped(), producer = |products| { - let products_num = products.len(); - let plural_s = if products_num == 1 { "" } else { "s" }; - let products_value = 2; - let products_currency = "EUR"; - view! { - <p> - {format!( - "You have {products_num} product{plural_s} \ - in stock with a value \ - of {products_value} {products_currency}.", - )} - </p> + <p>{format!("You have {} products", products.len())}</p> } } } diff --git a/src/components/unit_overview.rs b/src/components/unit_overview.rs new file mode 100644 index 0000000..25e5675 --- /dev/null +++ b/src/components/unit_overview.rs @@ -0,0 +1,21 @@ +use leptos::{IntoView, component, view}; + +use crate::components::{container::Container, icon_p::IconP}; + +#[component] +pub fn UnitOverview() -> impl IntoView { + view! { + <Container + header="Units" + buttons=vec![ + (view! { <IconP icon=icondata_io::IoClipboard text="Show unit" /> }, "units"), + (view! { <IconP icon=icondata_io::IoClipboard text="Create unit" /> }, "create-unit"), + (view! { <IconP icon=icondata_io::IoPricetags text="Create unit property" /> }, "create-unit-property"), + ] + > + { + "You have units" + } + </Container> + } +} diff --git a/src/lib.rs b/src/lib.rs index b5ca65a..36210e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ use reactive_stores::Store; use rocie_client::apis::configuration::Configuration; use crate::pages::{ - buy::Buy, home::Home, inventory::Inventory, not_found::NotFound, recipies::Recipies, + associate_barcode::AssociateBarcode, buy::Buy, create_product::CreateProduct, home::Home, inventory::Inventory, not_found::NotFound, recipies::Recipies }; #[derive(Debug, Clone, Store)] @@ -71,6 +71,8 @@ pub fn App() -> impl IntoView { view! { <Home /> } } /> + + // Inventory <Route path=path!("/inventory") view=move || { @@ -83,12 +85,28 @@ pub fn App() -> impl IntoView { view! { <Buy /> } } /> + + // Recipes <Route path=path!("/recipies") view=move || { view! { <Recipies /> } } /> + + // Products + <Route + path=path!("/create-product") + view=move || { + view! { <CreateProduct /> } + } + /> + <Route + path=path!("/associate-barcode-product") + view=move || { + view! { <AssociateBarcode /> } + } + /> </Routes> </Router> } diff --git a/src/pages/associate_barcode.rs b/src/pages/associate_barcode.rs new file mode 100644 index 0000000..20714ff --- /dev/null +++ b/src/pages/associate_barcode.rs @@ -0,0 +1,163 @@ +use leptos::{ + IntoView, component, + prelude::{Get, Show, WriteSignal, signal}, + task::spawn_local, + view, +}; +use rocie_client::models::{Barcode, BarcodeId, Product, Unit, UnitAmount, UnitId}; +use rocie_macros::Form; +use uuid::Uuid; + +use crate::{ + api::{ + associate_barcode_external_wrapped, get_config, product_by_id_wrapped, + product_by_name_404_wrapped, product_by_name_external_wrapped, + product_suggestion_by_name_wrapped, unit_by_id_wrapped, unit_property_by_id_wrapped, + }, + components::{async_fetch::AsyncResource, banner::Banner, site_header::SiteHeader}, +}; + +#[component] +pub fn AssociateBarcode() -> impl IntoView { + let product_name_signal; + + let (errors, errors_set) = signal(None); + + let (show_units, show_units_set) = signal(false); + + view! { + <SiteHeader logo=icondata_io::IoPricetag back_location="/" name="Buy" /> + + <Show when=move || errors.get().is_some()> + <Banner text=move || errors.get().expect("Was some") /> + </Show> + + { + Form! { + on_submit = |barcode_id, product_name, amount, unit_id| { + let config = get_config!(); + + spawn_local(async move { + let output = async { + let product = product_by_name_external_wrapped(&config, &product_name).await?; + + associate_barcode_external_wrapped(&config, product.id, Barcode { + amount:UnitAmount { + unit: UnitId { value: unit_id }, + value: u32::from(amount), + }, + id: BarcodeId { value: barcode_id }, + }).await?; + + Ok::<_, leptos::error::Error>(()) + }; + + match output.await { + Ok(()) => (), + Err(err) => { + errors_set.set( + Some( + format!("Could not associate barcode: {err}") + ) + ); + }, + } + }); + }; + + <Input + name=barcode_id, + rust_type=u32, + html_type="number", + label="Barcode number", + /> + + <Input + name=product_name, + rust_type=String, + html_type="text", + label="Product Name", + reactive=product_name_signal + auto_complete=generate_suggest_products + /> + + <Show + when=move || show_units.get(), + > + <Select + name=unit_id, + rust_type=Uuid, + label="Unit", + options=AsyncResource! { + ( + product_name: Option<String> = product_name_signal(), + show_units_set: WriteSignal<bool> = show_units_set + ) -> Result<Vec<(String, String)>, leptos::error::Error> { + let units = product_unit_fetcher(product_name).await?; + + show_units_set.set(units.is_some()); + if let Some(units) = units { + Ok( + units + .into_iter() + .map(|unit| (unit.full_name_singular, unit.id.to_string())) + .collect() + ) + } else { + Ok(vec![]) + } + } + }, + /> + </Show> + + <Input + name=amount, + rust_type=u16, + html_type="number", + label="Amount" + /> + } + } + } +} + +async fn generate_suggest_products( + optional_product_name: Option<String>, +) -> Result<Option<Vec<String>>, leptos::error::Error> { + if let Some(product_name) = optional_product_name + && !product_name.is_empty() + { + let products = product_suggestion_by_name_wrapped(&product_name).await?; + Ok(Some(products.into_iter().map(|prod| prod.name).collect())) + } else { + Ok(None) + } +} + +async fn product_unit_fetcher( + optinal_product_name: Option<String>, +) -> Result<Option<Vec<Unit>>, leptos::error::Error> { + if let Some(product_name) = optinal_product_name + && !product_name.is_empty() + { + let maybe_product: Option<Product> = product_by_name_404_wrapped(&product_name).await?; + + if let Some(product) = maybe_product { + let unit_property = + unit_property_by_id_wrapped(product_by_id_wrapped(product.id).await?.unit_property) + .await?; + + let mut units = Vec::with_capacity(unit_property.units.len()); + for unit_id in unit_property.units { + units.push(unit_by_id_wrapped(unit_id).await?); + } + + Ok(Some(units)) + } else { + Ok(None) + } + } else { + Ok(None) + } +} diff --git a/src/pages/buy.rs b/src/pages/buy.rs index cb4cff4..f3335f6 100644 --- a/src/pages/buy.rs +++ b/src/pages/buy.rs @@ -1,25 +1,15 @@ use leptos::{ IntoView, component, - prelude::{Get, Read, Show, WriteSignal, expect_context, signal}, + prelude::{Get, Show, signal}, task::spawn_local, view, }; -use leptos_router::{NavigateOptions, hooks::use_navigate}; use log::info; -use reactive_stores::Store; -use rocie_client::{ - apis::Error, - models::{Product, Unit}, -}; -use uuid::Uuid; +use rocie_client::models::BarcodeId; use crate::{ - ConfigState, ConfigStateStoreFields, - api::{ - buy_barcode_wrapper, get_product_by_name, get_product_unit_by_id, - get_products_by_part_name, get_unit_by_id, - }, - components::{async_fetch::AsyncResource, banner::Banner, form::Form, site_header::SiteHeader}, + api::{buy_barcode_external_wrapped, get_config}, + components::{banner::Banner, form::Form, site_header::SiteHeader}, }; #[component] @@ -35,20 +25,20 @@ pub fn Buy() -> impl IntoView { { Form! { - on_submit = |barcode_number, amount| { - let config = expect_context::<Store<ConfigState>>(); - let config = config.config().read(); + on_submit = |barcode_number, times| { + let config = get_config!(); spawn_local(async move { - if let Err(err) = buy_barcode_wrapper(&config, barcode_number).await { + if let Err(err) = buy_barcode_external_wrapped(&config, BarcodeId { value: barcode_number }, u32::from(times)).await { let error = format!("Error in form on-submit for barcode `{barcode_number}`: {err}"); + on_submit_errored_set.set(Some(error)); } else { on_submit_errored_set.set(None); - - info!("Bought barcode {barcode_number} {amount} times"); } + + info!("Bought barcode {barcode_number} {times} times"); }); }; @@ -60,134 +50,12 @@ pub fn Buy() -> impl IntoView { /> <Input - name=amount, - rust_type=u16, - html_type="number", - label="Amount" - /> - } - } - } -} - -#[component] -pub fn AssociateBarcode() -> impl IntoView { - let product_name_signal; - - let (show_units, show_units_set) = signal(false); - - view! { - <SiteHeader logo=icondata_io::IoPricetag back_location="/" name="Buy" /> - - { - Form! { - on_submit = |product_name, amount, unit_id| { - spawn_local(async move { - let navigate = use_navigate(); - - info!("Got product barcode: {product_name} with amount: {amount}, and {unit_id}"); - - navigate("/", NavigateOptions::default()); - }); - }; - - <Input - name=product_name, - rust_type=String, - html_type="text", - label="Product Name", - reactive=product_name_signal - auto_complete=generate_suggest_products - /> - - <Show - when=move || show_units.get(), - > - <Select - name=unit_id, - rust_type=Uuid, - label="Unit", - options=AsyncResource! { - ( - product_name: Option<String> = product_name_signal(), - show_units_set: WriteSignal<bool> = show_units_set - ) -> Result<Vec<(String, String)>, leptos::error::Error> { - let units = product_unit_fetcher(product_name).await?; - - show_units_set.set(units.is_some()); - if let Some(units) = units { - Ok( - units - .into_iter() - .map(|unit| (unit.full_name_singular, unit.id.to_string())) - .collect() - ) - } else { - Ok(vec![]) - } - } - }, - /> - </Show> - - <Input - name=amount, + name=times, rust_type=u16, html_type="number", - label="Amount" + label="Times" /> } } } } - -async fn generate_suggest_products( - optional_product_name: Option<String>, -) -> Result<Option<Vec<String>>, leptos::error::Error> { - if let Some(product_name) = optional_product_name - && !product_name.is_empty() - { - let products = get_products_by_part_name(product_name).await?; - Ok(Some(products.into_iter().map(|prod| prod.name).collect())) - } else { - Ok(None) - } -} - -async fn product_unit_fetcher( - optinal_product_name: Option<String>, -) -> Result<Option<Vec<Unit>>, leptos::error::Error> { - if let Some(product_name) = optinal_product_name - && !product_name.is_empty() - { - let value: Option<Product> = { - match get_product_by_name(product_name).await { - Ok(ok) => Ok::<_, leptos::error::Error>(Some(ok)), - Err(err) => match err { - Error::ResponseError(ref response_content) => { - match response_content.status.as_u16() { - 404 => Ok(None), - _ => Err(err.into()), - } - } - err => Err(err.into()), - }, - }? - }; - - if let Some(value) = value { - let (_, unit_property) = get_product_unit_by_id(value.id).await?; - - let mut units = Vec::with_capacity(unit_property.units.len()); - for unit_id in unit_property.units { - units.push(get_unit_by_id(unit_id).await?); - } - - Ok(Some(units)) - } else { - Ok(None) - } - } else { - Ok(None) - } -} diff --git a/src/pages/create_product.rs b/src/pages/create_product.rs new file mode 100644 index 0000000..fcd3b0b --- /dev/null +++ b/src/pages/create_product.rs @@ -0,0 +1,96 @@ +use std::{convert::Infallible, str::FromStr}; + +use leptos::{ + IntoView, component, + prelude::{Get, Show, signal}, + task::spawn_local, + view, +}; +use rocie_client::models::{ProductStub, UnitPropertyId}; +use rocie_macros::Form; +use uuid::Uuid; + +use crate::{ + api::{get_config, register_product_external_wrapped, unit_properties_wrapped}, + components::{async_fetch::AsyncResource, banner::Banner, site_header::SiteHeader}, +}; + +struct OptionalString(Option<String>); + +impl FromStr for OptionalString { + type Err = Infallible; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + if s.is_empty() { + Ok(Self(None)) + } else { + Ok(Self(Some(s.to_owned()))) + } + } +} + +#[component] +pub fn CreateProduct() -> impl IntoView { + let (error_message, error_message_set) = signal(None); + + view! { + <SiteHeader logo=icondata_io::IoArrowBack back_location="/" name="Create Product" /> + + <Show when=move || error_message.get().is_some()> + <Banner text=move || error_message.get().expect("Is some") /> + </Show> + + { + Form! { + on_submit = |product_name, product_description, unit_property_id| { + let config = get_config!(); + + spawn_local(async move { + match register_product_external_wrapped(&config, ProductStub { + description: Some(product_description.0), + name: product_name, + parent: None, // TODO: Add this <2025-10-25> + unit_property: UnitPropertyId { value: unit_property_id }, + } + ).await { + Ok(_id) => {} + Err(err) => error_message_set.set(Some(format!("Failed to create product: {err}"))), + } + }); + }; + + <Input + name=product_name, + rust_type=String, + html_type="text", + label="Product Name", + /> + + <Input + name=product_description, + rust_type=OptionalString, + html_type="text", + label="Product Description" + /> + + <Select + name=unit_property_id, + rust_type=Uuid, + label="Unit property", + options=AsyncResource! { + () -> Result<Vec<(String, String)>, leptos::error::Error> { + let unit_properties = unit_properties_wrapped().await?; + + Ok( + unit_properties + .into_iter() + .map(|prop| (prop.name, prop.id.to_string())) + .collect() + ) + } + }, + /> + } + } + } +} diff --git a/src/pages/home.rs b/src/pages/home.rs index 387562e..b9dba64 100644 --- a/src/pages/home.rs +++ b/src/pages/home.rs @@ -10,7 +10,8 @@ use leptos_router::{ }; use crate::components::{ - product_overview::ProductOverview, recipies::Recipies, site_header::SiteHeader, + inventory::Inventory, product_overview::ProductOverview, recipies::Recipies, + site_header::SiteHeader, unit_overview::UnitOverview, }; #[component] @@ -43,8 +44,10 @@ pub fn Home() -> impl IntoView { <div class="flex flex-col content-start"> <SiteHeader logo=icondata_io::IoRoseSharp back_location="/" name="Rocie" /> - <ProductOverview /> + <Inventory /> <Recipies /> + <ProductOverview /> + <UnitOverview /> </div> </ErrorBoundary> } diff --git a/src/pages/inventory.rs b/src/pages/inventory.rs index e5ff6ae..b2ce4a1 100644 --- a/src/pages/inventory.rs +++ b/src/pages/inventory.rs @@ -3,10 +3,13 @@ use leptos::{ prelude::{ClassAttribute, ElementChild}, view, }; -use rocie_client::models::{Product, ProductAmount, Unit}; +use rocie_client::models::{Product, ProductAmount, ProductId, Unit}; use crate::{ - api::{get_full_product_by_id, get_products}, + api::{ + amount_by_id_wrapped, product_by_id_wrapped, products_in_storage_wrapped, + unit_by_id_wrapped, + }, components::{async_fetch::AsyncFetch, site_header::SiteHeader}, }; @@ -19,7 +22,7 @@ pub fn Inventory() -> impl IntoView { { AsyncFetch! { @map_error_in_producer - fetcher = get_products(), + fetcher = products_in_storage_wrapped(), producer = render_products, } } @@ -40,6 +43,16 @@ fn render_products(products: Vec<Product>) -> impl IntoView { .collect::<Vec<_>>() } +async fn get_full_product_by_id( + id: ProductId, +) -> Result<(Product, ProductAmount, Unit), leptos::error::Error> { + let product = product_by_id_wrapped(id).await?; + let amount = amount_by_id_wrapped(id).await?; + let unit = unit_by_id_wrapped(amount.amount.unit).await?; + + Ok((product, amount, unit)) +} + fn format_full_product((product, amount, unit): (Product, ProductAmount, Unit)) -> impl IntoView { view! { <ul class="my-3"> diff --git a/src/pages/mod.rs b/src/pages/mod.rs index a6057cd..b8a68c7 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -3,3 +3,5 @@ pub mod home; pub mod inventory; pub mod not_found; pub mod recipies; +pub mod create_product; +pub mod associate_barcode; |
