From 3a0e91bda1e93afa33dd182c2e820c94b3e94593 Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Sat, 25 Oct 2025 02:15:31 +0200 Subject: feat(treewide): Add further buttons The register product > associate barcode > buy barcode workflow is now usable. The only missing features for an MVP are unit and unit property creation. --- src/pages/associate_barcode.rs | 163 +++++++++++++++++++++++++++++++++++++++++ src/pages/buy.rs | 156 +++------------------------------------ src/pages/create_product.rs | 96 ++++++++++++++++++++++++ src/pages/home.rs | 7 +- src/pages/inventory.rs | 19 ++++- src/pages/mod.rs | 2 + 6 files changed, 294 insertions(+), 149 deletions(-) create mode 100644 src/pages/associate_barcode.rs create mode 100644 src/pages/create_product.rs (limited to 'src/pages') 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! { + + + + + + + { + 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}") + ) + ); + }, + } + }); + }; + + + + + + + + } + } + } +} + +async fn generate_suggest_products( + optional_product_name: Option, +) -> Result>, 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, +) -> Result>, leptos::error::Error> { + if let Some(product_name) = optinal_product_name + && !product_name.is_empty() + { + let maybe_product: Option = 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::>(); - 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 { /> - } - } - } -} - -#[component] -pub fn AssociateBarcode() -> impl IntoView { - let product_name_signal; - - let (show_units, show_units_set) = signal(false); - - view! { - - - { - 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()); - }); - }; - - - - - } } } } - -async fn generate_suggest_products( - optional_product_name: Option, -) -> Result>, 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, -) -> Result>, leptos::error::Error> { - if let Some(product_name) = optinal_product_name - && !product_name.is_empty() - { - let value: Option = { - 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); + +impl FromStr for OptionalString { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + 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! { + + + + + + + { + 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}"))), + } + }); + }; + + + + + +