summaryrefslogtreecommitdiffstats
path: root/src/pages
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/pages/associate_barcode.rs163
-rw-r--r--src/pages/buy.rs156
-rw-r--r--src/pages/create_product.rs96
-rw-r--r--src/pages/home.rs7
-rw-r--r--src/pages/inventory.rs19
-rw-r--r--src/pages/mod.rs2
6 files changed, 294 insertions, 149 deletions
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;