summaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-09-30 09:15:56 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-09-30 09:15:56 +0200
commitd0263ce46160cd4152c67381fab2ee557f3aa483 (patch)
treeb24a725d71b984e466ff478fbb4449111beeacac /src/components
parentchore: Second version (diff)
downloadweb-client-d0263ce46160cd4152c67381fab2ee557f3aa483.zip
feat(treewide): Switch to tailwindcss and add more components
Diffstat (limited to '')
-rw-r--r--src/components/async_fetch.rs50
-rw-r--r--src/components/container.rs47
-rw-r--r--src/components/icon_p.rs18
-rw-r--r--src/components/inventory.rs55
-rw-r--r--src/components/mod.rs6
-rw-r--r--src/components/product_overview.rs71
-rw-r--r--src/components/recipies.rs12
-rw-r--r--src/components/side_header.rs22
-rw-r--r--src/components/site_header.rs29
9 files changed, 225 insertions, 85 deletions
diff --git a/src/components/async_fetch.rs b/src/components/async_fetch.rs
new file mode 100644
index 0000000..7105c6f
--- /dev/null
+++ b/src/components/async_fetch.rs
@@ -0,0 +1,50 @@
+macro_rules! AsyncFetch {
+ (fetcher = $fetcher:block producer = |$bound_variable:pat_param| $producer:block) => {{
+ use leptos::{
+ prelude::{ElementChild, LocalResource, Suspend, Transition},
+ view,
+ };
+
+ view! {
+ <Transition fallback=|| {
+ view! { <p>"Loading..."</p> }
+ }>
+ {move || Suspend::new(async move {
+ let resource = { LocalResource::new(move || $fetcher) };
+ resource
+ .await
+ .map(|$bound_variable| $producer)
+ })}
+ </Transition>
+ }
+ }};
+}
+pub(crate) use AsyncFetch;
+
+// #[component]
+// pub fn AsyncFetch<P, V, T, Fut>(
+// fetcher: impl Fn() -> Fut + 'static + Send + Sync,
+// producer: P,
+// ) -> impl IntoView
+// where
+// V: IntoView + 'static,
+// P: Fn(T) -> V + 'static + Send + Sync,
+// Fut: Future<Output = Result<T, Error>> + 'static,
+// T: 'static,
+// LocalResource<Result<T, Error>>: IntoFuture<Output = Result<T, Error>> + Send,
+// {
+// view! {
+// <Transition fallback=|| {
+// view! { <p>"Loading..."</p> }
+// }>
+// { || Suspend::new(async {
+// let value_resource = LocalResource::new( || fetcher());
+// value_resource
+// .await
+// .map(|value| {
+// producer(value)
+// })
+// })}
+// </Transition>
+// }
+// }
diff --git a/src/components/container.rs b/src/components/container.rs
index cf7aa5a..7a4a64f 100644
--- a/src/components/container.rs
+++ b/src/components/container.rs
@@ -1,34 +1,35 @@
use leptos::{
IntoView, component,
- prelude::{Children, ClassAttribute, ElementChild},
+ prelude::{Children, ClassAttribute, ElementChild, OnAttribute},
view,
};
-use leptos_meta::Style;
+use leptos_router::{NavigateOptions, hooks::use_navigate};
#[component]
-pub fn Container(header: impl IntoView, children: Children) -> impl IntoView {
+pub fn Container(
+ header: impl IntoView,
+ buttons: Vec<(impl IntoView, &'static str)>,
+ children: Children,
+) -> impl IntoView {
view! {
- <Style>
- "
- .rocie-container {
- border-width: 0.1rem;
- border-style: solid;
- border-color: gray;
- border-radius: 15%;
-
- padding: 0.2rem;
-
- text-align: left;
- }
- .rocie-container-header {
- font-size: 1.5rem;
- }
- "
- </Style>
-
- <div class="rocie-container">
- <p class="rocie-container-header">{header}</p>
+ <div class="p-4 mt-4 mr-4 ml-4 md-2 rounded-lg border-gray-600 border">
+ <p class="text-lg text-bold">{header}</p>
{children()}
+
+ <ul class="flex flex-row gap-1 pt-2 overflow-x-auto">
+ {buttons
+ .into_iter()
+ .map(|(name, path)| {
+ view! {
+ <li class="bg-green-400/40 p-2 first:rounded-l-full last:rounded-r-full">
+ <button on:click=move |_| {
+ use_navigate()(path, NavigateOptions::default());
+ }>{name}</button>
+ </li>
+ }
+ })
+ .collect::<Vec<_>>()}
+ </ul>
</div>
}
}
diff --git a/src/components/icon_p.rs b/src/components/icon_p.rs
new file mode 100644
index 0000000..372e280
--- /dev/null
+++ b/src/components/icon_p.rs
@@ -0,0 +1,18 @@
+use leptos::{
+ IntoView, component,
+ prelude::{ClassAttribute, ElementChild, Signal},
+ view,
+};
+use leptos_icons::Icon;
+
+#[component]
+pub fn IconP(#[prop(into)] icon: Signal<icondata_core::Icon>, text: &'static str) -> impl IntoView {
+ view! {
+ <div class="flex justify-evenly items-center">
+ <div class="mr-1">
+ <Icon icon=icon />
+ </div>
+ <p>{text}</p>
+ </div>
+ }
+}
diff --git a/src/components/inventory.rs b/src/components/inventory.rs
new file mode 100644
index 0000000..5855b33
--- /dev/null
+++ b/src/components/inventory.rs
@@ -0,0 +1,55 @@
+use leptos::{
+ IntoView, component,
+ prelude::{ClassAttribute, ElementChild},
+ view,
+};
+
+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! {
+ fetcher = {get_products()}
+ producer = |products| {
+ products
+ .into_iter()
+ .map(|product| {
+ view! {
+ {AsyncFetch! {
+ fetcher = {get_full_product_by_id(product.id)}
+ producer = |(product, amount, unit)| {
+ 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>
+ }
+ }
+ }}
+ }
+ })
+ .collect::<Vec<_>>()
+ }
+ }
+ }
+ </ul>
+ }
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
index 85f9671..7174ad8 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -1,6 +1,10 @@
// Generic
+pub mod async_fetch;
pub mod container;
+pub mod icon_p;
// Specific
+pub mod inventory;
pub mod product_overview;
-pub mod side_header;
+pub mod recipies;
+pub mod site_header;
diff --git a/src/components/product_overview.rs b/src/components/product_overview.rs
index 4e95335..ae2eaf2 100644
--- a/src/components/product_overview.rs
+++ b/src/components/product_overview.rs
@@ -1,47 +1,40 @@
-use std::sync::Arc;
+use leptos::{IntoView, component, view};
-use leptos::{
- IntoView, component,
- prelude::{ElementChild, Set, signal},
- task::spawn_local,
- view,
+use crate::{
+ api::get_products,
+ components::{async_fetch::AsyncFetch, container::Container, icon_p::IconP},
};
-use rocie_client::apis::{api_get_product_api::products, configuration::Configuration};
-
-use crate::components::container::Container;
#[component]
-pub fn ProductOverview(config: Arc<Configuration>) -> impl IntoView {
- let (read_status, write_status) = signal("Loading..".to_owned());
-
- {
- let local_config = Arc::clone(&config);
-
- spawn_local(async move {
- let products = products(&local_config).await;
-
- write_status.set(
- products
- .as_ref()
- .map(move |products| {
- let products_num = products.len();
- let plural_s = if products_num == 1 { "" } else { "s" };
- let products_value = 2;
- let products_currency = "EUR";
- format!(
- "You have {products_num} product{plural_s} \
- in stock with a value \
- of {products_value} {products_currency}.",
- )
- })
- .unwrap(),
- );
- });
- }
-
+pub fn ProductOverview() -> impl IntoView {
view! {
- <Container header="Inventory">
- <p>{read_status}</p>
+ <Container
+ header="Inventory"
+ buttons=vec![
+ (view! { <IconP icon=icondata_io::IoClipboardOutline text="Inventory" /> }, "inventory"),
+ (view! { <IconP icon=icondata_io::IoPricetags text="Consume" /> }, "consume"),
+ (view! { <IconP icon=icondata_io::IoStorefront text="Buy" /> }, "buy"),
+ ]
+ >
+ {AsyncFetch!(
+ fetcher = {get_products()}
+ 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/recipies.rs b/src/components/recipies.rs
new file mode 100644
index 0000000..1bd3a0d
--- /dev/null
+++ b/src/components/recipies.rs
@@ -0,0 +1,12 @@
+use leptos::{IntoView, component, prelude::ElementChild, view};
+
+use crate::components::container::Container;
+
+#[component]
+pub fn Recipies() -> impl IntoView {
+ view! {
+ <Container header="Recipies" buttons=vec![("Mealplan", "mealplan")]>
+ <p>"You have 0 recipies."</p>
+ </Container>
+ }
+}
diff --git a/src/components/side_header.rs b/src/components/side_header.rs
deleted file mode 100644
index 9cd6777..0000000
--- a/src/components/side_header.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use leptos::prelude::{AddAnyAttr, ElementChild, IntoView, StyleAttribute, component, view};
-use leptos_router::{NavigateOptions, hooks::use_navigate};
-use thaw::{Flex, FlexJustify, LayoutHeader};
-
-#[component]
-pub fn SiteHeader() -> impl IntoView {
- let navigate = use_navigate();
-
- view! {
- <LayoutHeader
- attr:style="padding: 20px;"
- on:click=move |_| {
- navigate("/", NavigateOptions::default());
- }
- >
- <Flex justify=FlexJustify::SpaceAround>
- <img src="/logo.svg" style="width: 36px" />
- <h3>"Rocie"</h3>
- </Flex>
- </LayoutHeader>
- }
-}
diff --git a/src/components/site_header.rs b/src/components/site_header.rs
new file mode 100644
index 0000000..65f7137
--- /dev/null
+++ b/src/components/site_header.rs
@@ -0,0 +1,29 @@
+use icondata_core::Icon as DataIcon;
+use leptos::prelude::{ClassAttribute, ElementChild, IntoView, OnAttribute, component, view};
+use leptos_icons::Icon;
+use leptos_router::{NavigateOptions, hooks::use_navigate};
+
+#[component]
+pub fn SiteHeader(
+ logo: DataIcon,
+ back_location: &'static str,
+ name: &'static str,
+ #[prop(default = None)] menu: Option<String>,
+) -> impl IntoView {
+ let navigate = use_navigate();
+
+ view! {
+ <div
+ class="flex flex-row justify-between items-center p-2 px-4 w-full h-1/12 bg-gray-100"
+ on:click=move |_| {
+ navigate(back_location, NavigateOptions::default());
+ }
+ >
+ <div class="max-w-14">
+ <Icon icon=logo />
+ </div>
+ <p class="text-xl">{name}</p>
+ <div>{menu}</div>
+ </div>
+ }
+}