summary refs log tree commit diff stats
path: root/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/buy.rs193
-rw-r--r--src/pages/inventory.rs54
-rw-r--r--src/pages/mod.rs3
-rw-r--r--src/pages/recipies.rs8
4 files changed, 258 insertions, 0 deletions
diff --git a/src/pages/buy.rs b/src/pages/buy.rs
new file mode 100644
index 0000000..cb4cff4
--- /dev/null
+++ b/src/pages/buy.rs
@@ -0,0 +1,193 @@
+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 reactive_stores::Store;
+use rocie_client::{
+    apis::Error,
+    models::{Product, Unit},
+};
+use uuid::Uuid;
+
+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_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?);
+            }
+
+            Ok(Some(units))
+        } else {
+            Ok(None)
+        }
+    } else {
+        Ok(None)
+    }
+}
diff --git a/src/pages/inventory.rs b/src/pages/inventory.rs
new file mode 100644
index 0000000..e5ff6ae
--- /dev/null
+++ b/src/pages/inventory.rs
@@ -0,0 +1,54 @@
+use leptos::{
+    IntoView, component,
+    prelude::{ClassAttribute, ElementChild},
+    view,
+};
+use rocie_client::models::{Product, ProductAmount, Unit};
+
+use crate::{
+    api::{get_full_product_by_id, get_products},
+    components::{async_fetch::AsyncFetch, site_header::SiteHeader},
+};
+
+#[component]
+pub fn Inventory() -> impl IntoView {
+    view! {
+        <SiteHeader logo=icondata_io::IoArrowBack back_location="/" name="Inventory" />
+
+        <ul class="flex flex-col p-2 m-2">
+            {
+                AsyncFetch! {
+                    @map_error_in_producer
+                    fetcher = get_products(),
+                    producer = render_products,
+                }
+            }
+        </ul>
+    }
+}
+
+fn render_products(products: Vec<Product>) -> impl IntoView {
+    products
+        .into_iter()
+        .map(|product| {
+            AsyncFetch! {
+                @map_error_in_producer
+                fetcher = get_full_product_by_id(product.id),
+                producer = format_full_product,
+            }
+        })
+        .collect::<Vec<_>>()
+}
+
+fn format_full_product((product, amount, unit): (Product, ProductAmount, Unit)) -> impl IntoView {
+    view! {
+        <ul class="my-3">
+            <li class="m-2">{product.name}</li>
+            <li class="m-2">
+                <span class="bg-gray-200 p-1 px-2 rounded-lg">
+                    {format!("{} {}", amount.amount.value, unit.short_name)}
+                </span>
+            </li>
+        </ul>
+    }
+}
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index 8829694..a6057cd 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -1,2 +1,5 @@
+pub mod buy;
 pub mod home;
+pub mod inventory;
 pub mod not_found;
+pub mod recipies;
diff --git a/src/pages/recipies.rs b/src/pages/recipies.rs
new file mode 100644
index 0000000..1fc9dcc
--- /dev/null
+++ b/src/pages/recipies.rs
@@ -0,0 +1,8 @@
+use leptos::{IntoView, component, view};
+
+use crate::components::site_header::SiteHeader;
+
+#[component]
+pub fn Recipies() -> impl IntoView {
+    view! { <SiteHeader logo=icondata_io::IoArrowBack back_location="/" name="Recipies" /> }
+}