summaryrefslogtreecommitdiffstats
path: root/src/components/buy.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-10-23 01:36:39 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-10-23 01:36:39 +0200
commit7bff22756beec82b4a1470e2d325b706dc56e5f2 (patch)
tree1566965125cfd5fbd73d654e9ee6ca8256301411 /src/components/buy.rs
parentfeat(form): Re-write the form macro as a proc macro (diff)
downloadweb-client-7bff22756beec82b4a1470e2d325b706dc56e5f2.zip
feat(buy): Provide basic buy interface
Diffstat (limited to '')
-rw-r--r--src/components/buy.rs210
1 files changed, 181 insertions, 29 deletions
diff --git a/src/components/buy.rs b/src/components/buy.rs
index 6d9402e..cb4cff4 100644
--- a/src/components/buy.rs
+++ b/src/components/buy.rs
@@ -1,41 +1,193 @@
-use leptos::{IntoView, component, view};
+use leptos::{
+ IntoView, component,
+ prelude::{Get, Read, Show, WriteSignal, expect_context, signal},
+ task::spawn_local,
+ view,
+};
+use leptos_router::{NavigateOptions, hooks::use_navigate};
use log::info;
-use rocie_client::models::UnitId;
+use reactive_stores::Store;
+use rocie_client::{
+ apis::Error,
+ models::{Product, Unit},
+};
use uuid::Uuid;
-use crate::components::{form::Form, site_header::SiteHeader};
+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},
+};
#[component]
pub fn Buy() -> impl IntoView {
+ let (on_submit_errored, on_submit_errored_set) = signal(None);
+
+ view! {
+ <SiteHeader logo=icondata_io::IoPricetag back_location="/" name="Buy" />
+
+ <Show when=move || on_submit_errored.get().is_some()>
+ <Banner text=move || on_submit_errored.get().expect("Should be some") />
+ </Show>
+
+ {
+ Form! {
+ on_submit = |barcode_number, amount| {
+ let config = expect_context::<Store<ConfigState>>();
+ let config = config.config().read();
+
+ spawn_local(async move {
+ if let Err(err) = buy_barcode_wrapper(&config, barcode_number).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");
+ }
+
+ });
+ };
+
+ <Input
+ name=barcode_number,
+ rust_type=u32,
+ html_type="number",
+ label="Barcode Number",
+ />
+
+ <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_barcode, amount, unit_id| {
- info!("Got product barcode: {product_barcode} with amount: {amount}, {unit_id}");
- };
+ {
+ 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,
+ 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 = 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?);
+ }
- <Input
- name=product_barcode,
- rust_type=u32,
- html_type="number",
- label="Product Barcode"
- />
- <Select
- name=unit_id,
- rust_type=Uuid,
- label="Unit",
- options=[
- ("Kilogram", Uuid::new_v4()),
- ("Gram", Uuid::new_v4())
- ]
- />
- <Input
- name=amount,
- rust_type=u16,
- html_type="number",
- label="Amount"
- />
- }}
+ Ok(Some(units))
+ } else {
+ Ok(None)
+ }
+ } else {
+ Ok(None)
}
}