use maud, make inventory work

This commit is contained in:
2023-05-08 22:31:01 +02:00
parent 7dd39ca2a5
commit 3264af5c65
7 changed files with 483 additions and 263 deletions

74
rust/Cargo.lock generated
View File

@@ -540,6 +540,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.143" version = "0.2.143"
@@ -644,6 +650,16 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.15" version = "0.2.15"
@@ -669,6 +685,12 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "packager" name = "packager"
version = "0.1.0" version = "0.1.0"
@@ -682,6 +704,7 @@ dependencies = [
"tokio", "tokio",
"tower", "tower",
"tracing", "tracing",
"tracing-subscriber",
"uuid", "uuid",
] ]
@@ -947,6 +970,15 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"
@@ -1152,6 +1184,16 @@ dependencies = [
"syn 2.0.15", "syn 2.0.15",
] ]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.21" version = "0.3.21"
@@ -1319,6 +1361,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@@ -1392,6 +1460,12 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

View File

@@ -20,6 +20,9 @@ version = "0.4.13"
[dependencies.tracing] [dependencies.tracing]
version = "0.1.37" version = "0.1.37"
[dependencies.tracing-subscriber]
version = "0.3"
[dependencies.maud] [dependencies.maud]
version = "0.25" version = "0.25"

View File

@@ -1,25 +1,28 @@
use super::Tree; use maud::{html, Markup};
use axohtml::html;
pub struct Home { pub struct Home {
doc: Tree, doc: Markup,
} }
impl Home { impl Home {
pub fn build() -> Self { pub fn build() -> Self {
let doc = html!( let doc: Markup = html!(
<div id="home" class=["p-8", "max-w-xl"]> div id="home" class={"p-8" "max-w-xl"} {
<p><a href="/inventory">"Inventory"</a></p> p {
<p><a href="/trips">"Trips"</a></p> a href="/inventory" { "Inventory" }
</div> }
p {
a href="/trips" { "Trips" }
}
}
); );
Self { doc } Self { doc }
} }
} }
impl Into<Tree> for Home { impl Into<Markup> for Home {
fn into(self) -> Tree { fn into(self) -> Markup {
self.doc self.doc
} }
} }

View File

@@ -1,47 +1,45 @@
use super::Tree; use maud::{html, Markup};
use axohtml::{
html, text,
types::{Class, SpacedSet},
};
use crate::models::*; use crate::models::*;
use crate::State; use crate::State;
use uuid::uuid;
pub struct Inventory { pub struct Inventory {
doc: Tree, doc: Markup,
} }
impl Inventory { impl Inventory {
pub async fn build(state: State, categories: Vec<Category>) -> Result<Self, Error> { pub async fn build(state: State, categories: Vec<Category>) -> Result<Self, Error> {
let doc = html!( let doc = html!(
<div id="pkglist-item-manager"> div id="pkglist-item-manager" {
<div class=["p-8", "grid", "grid-cols-4", "gap-3"]> div ."p-8" ."grid" ."grid-cols-4" ."gap-3" {
<div class=["col-span-2"]> div ."col-span-2" {
{<InventoryCategoryList as Into<Tree>>::into(InventoryCategoryList::build(&categories).await?)} ({<InventoryCategoryList as Into<Markup>>::into(InventoryCategoryList::build(&categories).await?)})
</div> }
{if state.has_active_category { html!( div ."col-span-2" {
<div class=["col-span-2"]> h1 ."text-2xl" ."mb-5" ."text-center" { "Items" }
{<InventoryItemList as Into<Tree>>::into(InventoryItemList::build(categories.iter().find(|category| category.active).unwrap().items()).await?)} @if state.active_category_id.is_some() {
</div> ({<InventoryItemList as Into<Markup>>::into(InventoryItemList::build(categories.iter().find(|category| category.active).unwrap().items()).await?)})
)} else { }
html!(<div></div>) ({<InventoryNewItemForm as Into<Markup>>::into(InventoryNewItemForm::build(&state, &categories).await?)})
}}
</div> }
</div> }
}
); );
Ok(Self { doc }) Ok(Self { doc })
} }
} }
impl Into<Tree> for Inventory { impl Into<Markup> for Inventory {
fn into(self) -> Tree { fn into(self) -> Markup {
self.doc self.doc
} }
} }
pub struct InventoryCategoryList { pub struct InventoryCategoryList {
doc: Tree, doc: Markup,
} }
impl InventoryCategoryList { impl InventoryCategoryList {
@@ -52,94 +50,69 @@ impl InventoryCategoryList {
.max() .max()
.unwrap_or(1); .unwrap_or(1);
let cls_td_active: SpacedSet<Class> =
["border", "p-0", "m-0", "font-bold"].try_into().unwrap();
let cls_td_inactive: SpacedSet<Class> = ["border", "p-0", "m-0"].try_into().unwrap();
let cls_tr_active: SpacedSet<Class> = [
"h-10",
"hover:bg-purple-100",
"m-3",
"h-full",
"outline",
"outline-2",
"outline-indigo-600",
]
.try_into()
.unwrap();
let cls_tr_inactive: SpacedSet<Class> = ["h-10", "hover:bg-purple-100", "m-3", "h-full"]
.try_into()
.unwrap();
let doc = html!( let doc = html!(
<div> div {
<h1 class=["text-2xl", "mb-5"]>"Categories"</h1> h1 ."text-2xl" ."mb-5" ."text-center" { "Categories" }
<table class=[ table
"table", ."table"
"table-auto", ."table-auto"
"border-collapse", ."border-collapse"
"border-spacing-0", ."border-spacing-0"
"border", ."border"
"w-full", ."w-full"
]> {
<colgroup> colgroup {
<col style="width:50%"/> col style="width:50%" {}
<col style="width:50%"/> col style="width:50%" {}
</colgroup> }
<thead class=["bg-gray-200"]> thead ."bg-gray-200" {
<tr class=["h-10"]> tr ."h-10" {
<th class=["border", "p-2"]>"Name"</th> th ."border" ."p-2" { "Name" }
<th class=["border", "p-2"]>"Weight"</th> th ."border" ."p-2" { "Weight" }
</tr> }
</thead> }
<tbody> tbody {
{categories.iter().map(|category| html!( @for category in categories {
<tr tr class={@if category.active {
class={if category.active { "h-10 hover:bg-purple-100 m-3 h-full outline outline-2 outline-indigo-300"
cls_tr_active.clone() } @else {
} else { "h-10 hover:bg-purple-100 m-3 h-full"
cls_tr_inactive.clone() }} {
}}
> td
<td class=@if category.active {
class={if category.active { "border p-0 m-0 font-bold"
cls_td_active.clone() } @else {
} else { "border p-0 m-0"
cls_td_inactive.clone() } {
}} a
>
<a
id="select-category" id="select-category"
href={ href=(
format!( format!(
"/inventory/category/{id}", "/inventory/category/{id}",
id=category.id id=category.id
) )
}
class=["inline-block", "p-2", "m-0", "w-full"]
>
{text!(category.name.clone())}
</a>
</td>
<td class=["border", "p-0", "m-0"] style="position:relative;">
<a
id="select-category"
href={
format!(
"/inventory/category/{id}",
id=category.id
) )
// hx-post=(
// format!(
// "/inventory/category/{id}/items",
// id=category.id
// )
// )
// hx-swap="outerHTML"
// hx-target="#items"
."inline-block" ."p-2" ."m-0" ."w-full"
{
(category.name.clone())
} }
class=["inline-block", "p-2", "m-0", "w-full"] }
> td ."border" ."p-2" ."m-0" style="position:relative;" {
<p> p {
{text!(category.total_weight().to_string())} (category.total_weight().to_string())
</p> }
</a> div ."bg-blue-600" ."h-1.5"
<div style=(
class=["bg-blue-600", "h-1.5"]
style = {
format!( format!(
"width: {width}%;position:absolute;left:0;bottom:0;right:0;", "width: {width}%;position:absolute;left:0;bottom:0;right:0;",
width=( width=(
@@ -148,89 +121,216 @@ impl InventoryCategoryList {
* 100.0 * 100.0
) )
) )
) {}
}
}
}
tr ."h-10" ."hover:bg-purple-200" ."bg-gray-300" ."font-bold" {
td ."border" ."p-0" ."m-0" {
p ."p-2" ."m-2" { "Sum" }
}
td ."border" ."p-0" ."m-0" {
p ."p-2" ."m-2" {
(categories.iter().map(|category| category.total_weight()).sum::<u32>().to_string())
}
}
}
}
}
} }
>
</div>
</td>
</tr>
))}
<tr class=["h-10", "hover:bg-purple-200", "bg-gray-300", "font-bold"]>
<td class=["border", "p-0", "m-0"]>
<p class=["p-2", "m-2"]>"Sum"</p>
</td>
<td class=["border", "p-0", "m-0"]>
<p class=["p-2", "m-2"]>
{text!(categories.iter().map(|category| category.total_weight()).sum::<u32>().to_string())}
</p>
</td>
</tr>
</tbody>
</table>
</div>
); );
Ok(Self { doc }) Ok(Self { doc })
} }
} }
impl Into<Tree> for InventoryCategoryList { impl Into<Markup> for InventoryCategoryList {
fn into(self) -> Tree { fn into(self) -> Markup {
self.doc self.doc
} }
} }
pub struct InventoryItemList { pub struct InventoryItemList {
doc: Tree, doc: Markup,
} }
impl InventoryItemList { impl InventoryItemList {
pub async fn build(items: &Vec<Item>) -> Result<Self, Error> { pub async fn build(items: &Vec<Item>) -> Result<Self, Error> {
let biggest_item_weight: u32 = items.iter().map(|item| item.weight).max().unwrap_or(1);
let doc = html!( let doc = html!(
<div> div #items {
<h1 class=["text-2xl", "mb-5"]>"Items"</h1> @if items.is_empty() {
<table class=[ p ."text-lg" ."text-center" ."py-5" ."text-gray-400" { "[Empty]" }
"table", } @else {
"table-auto", table
"border-collapse", ."table"
"border-spacing-0", ."table-auto"
"border", ."border-collapse"
"w-full", ."border-spacing-0"
]> ."border"
<thead class=["bg-gray-200"]> ."w-full"
<tr class=["h-10"]> {
<th class=["border", "p-2"]>"Name"</th> thead ."bg-gray-200" {
<th class=["border", "p-2"]>"Weight"</th> tr ."h-10" {
</tr> th ."border" ."p-2" { "Name" }
</thead> th ."border" ."p-2" { "Weight" }
<tbody> }
{items.iter().map(|item| html!( }
<tr class=["h-10", "even:bg-gray-100", "hover:bg-purple-100"]> tbody {
<td class=["border", "p-2"]> @for item in items {
<a tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" {
class=["p-2", "w-full", "inline-block"] td ."border" ."p-0" {
href={ a
format!("/inventory/item/{id}/", id=item.id) ."p-2" ."w-full" ."inline-block"
href=(
format!("/inventory/item/{id}/", id=item.id)
) {
(item.name.clone())
}
}
td ."border" ."p-2" style="position:relative;" {
p { (item.weight.to_string()) }
div ."bg-blue-600" ."h-1.5" style=(format!("
width: {width}%;
position:absolute;
left:0;
bottom:0;
right:0;", width=(item.weight as f32 / biggest_item_weight as f32 * 100.0))) {}
}
}
}
}
}
}
} }
>
{text!(item.name.clone())}
</a>
</td>
<td class=["border", "p-2"]>
{text!(item.weight.to_string())}
</td>
</tr>
))}
</tbody>
</table>
</div>
); );
Ok(Self { doc }) Ok(Self { doc })
} }
} }
impl Into<Tree> for InventoryItemList { impl Into<Markup> for InventoryItemList {
fn into(self) -> Tree { fn into(self) -> Markup {
self.doc self.doc
} }
} }
pub struct InventoryNewItemForm {
doc: Markup,
}
impl InventoryNewItemForm {
pub async fn build(state: &State, categories: &Vec<Category>) -> Result<Self, Error> {
let doc = html!(
form
name="new-item"
id="new-item"
action="/inventory/item/"
target="_self"
method="post"
."mt-8" ."p-5" ."border-2" ."border-gray-200" {
div ."mb-5" ."flex" ."flex-row" ."items-center" {
span ."mdi" ."mdi-playlist-plus" ."text-2xl" ."mr-4" {}
p ."inline" ."text-xl" { "Add new item" }
}
div ."w-11/12" ."mx-auto" {
div ."pb-8" {
div ."flex" ."flex-row" ."justify-center" ."items-start"{
label for="item-name" .font-bold ."w-1/2" ."p-2" ."text-center" { "Name" }
span ."w-1/2" {
input type="text" id="item-name" name="name"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."rounded"
."focus:outline-none"
."focus:bg-white"
."focus:border-purple-500"
{
}
}
}
}
div ."flex" ."flex-row" ."justify-center" ."items-center" ."pb-8" {
label for="item-weight" .font-bold ."w-1/2" .text-center { "Weight" }
span ."w-1/2" {
input
type="text"
id="item-weight"
name="weight"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."border-gray-300"
."rounded"
."focus:outline-none"
."focus:bg-white"
."focus:border-purple-500"
{
}
}
}
div ."flex" ."flex-row" ."justify-center" ."items-center" ."pb-8" {
label for="item-category" .font-bold ."w-1/2" .text-center { "Category" }
span ."w-1/2" {
select
id="item-category"
name="category"
."block"
."w-full"
."p-2"
."bg-gray-50"
."border-2"
."border-gray-300"
."rounded"
."focus:outline-none"
."focus:bg-white"
."focus:border-purple-500" {
@for category in categories {
@if state.active_category_id.map_or(false, |id| id == category.id) {
option value=(category.id) selected="true" {
(category.name)
}
} @else {
option value=(category.id) {
(category.name)
}
}
}
}
}
}
input type="submit" value="Add"
."py-2"
."border-2"
."rounded"
."border-gray-300"
."mx-auto"
."w-full" {
}
}
}
);
Ok(Self { doc })
}
}
impl Into<Markup> for InventoryNewItemForm {
fn into(self) -> Markup {
self.doc
}
}
// impl InventoryItemList {
// pub fn to_string(self) -> String {
// self.doc.into_string()
// }
// }
//ItemList

View File

@@ -1,12 +1,4 @@
use axohtml::{ use maud::{html, Markup, DOCTYPE};
dom::DOMTree,
elements::FlowContent,
html,
types::{Class, SpacedSet},
unsafe_text,
};
type Tree = Box<dyn FlowContent<String>>;
pub mod home; pub mod home;
pub mod inventory; pub mod inventory;
@@ -17,7 +9,7 @@ pub use inventory::*;
pub use triplist::*; pub use triplist::*;
pub struct Root { pub struct Root {
doc: DOMTree<String>, doc: Markup,
} }
pub enum TopLevelPage { pub enum TopLevelPage {
@@ -27,60 +19,54 @@ pub enum TopLevelPage {
} }
impl Root { impl Root {
pub fn build(body: Tree, active_page: TopLevelPage) -> Self { pub fn build(body: Markup, active_page: TopLevelPage) -> Self {
let active_classes: SpacedSet<Class> =
["text-lg", "font-bold", "underline"].try_into().unwrap();
let inactive_classes: SpacedSet<Class> = ["text-lg"].try_into().unwrap();
let doc = html!( let doc = html!(
<html> (DOCTYPE)
<head> html {
<title>"Packager"</title> head {
<script src="https://unpkg.com/htmx.org@1.7.0"/> title { "Packager" }
<script src="https://cdn.tailwindcss.com"/> script src="https://unpkg.com/htmx.org@1.7.0" {}
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js" defer="true"/> script src="https://cdn.tailwindcss.com" {}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"/> script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js" defer {}
<script>{unsafe_text!(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js")))}</script> link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css";
</head> script { (include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js"))) }
<body> }
<header class=[ body {
"bg-gray-200", header
"p-5", ."bg-gray-200"
"flex", ."p-5"
"flex-row", ."flex"
"flex-nowrap", ."flex-row"
"justify-between", ."flex-nowrap"
"items-center", ."justify-between"
]> ."items-center"
<span class=["text-xl", "font-semibold"]> hx-boost="true"
<a href="/">"Packager"</a> {
</span> span ."text-xl" ."font-semibold" {
<nav class=["grow", "flex", "flex-row", "justify-center", "gap-x-6"]> a href="/" { "Packager" }
<a href="/inventory/" class={ }
match active_page { nav ."grow" ."flex" ."flex-row" ."justify-center" ."gap-x-6" {
TopLevelPage::Inventory => active_classes.clone(), a href="/inventory/" class={@match active_page {
_ => inactive_classes.clone(), TopLevelPage::Inventory => "text-lg font-bold underline",
_ => "text-lg",
}} { "Inventory" }
a href="/trips/" class={@match active_page {
TopLevelPage::Trips => "text-lg font-bold underline",
_ => "text-lg",
}} { "Trips" }
}
}
div hx-boost="true" {
(body)
}
} }
}>"Inventory"</a>
<a href="/trips/" class={
match active_page {
TopLevelPage::Trips => active_classes,
_ => inactive_classes,
} }
}>"Trips"</a>
</nav>
</header>
{body}
</body>
</html>
); );
Self { doc } Self { doc }
} }
pub fn to_string(&self) -> String { pub fn to_string(self) -> String {
let mut doc = self.doc.to_string(); self.doc.into_string()
doc.insert_str(0, "<!DOCTYPE html>\n");
doc
} }
} }

View File

@@ -1,43 +1,38 @@
use super::Tree;
use crate::models::*; use crate::models::*;
use axohtml::{html, text}; use maud::{html, Markup};
pub struct TripList { pub struct TripList {
doc: Tree, doc: Markup,
} }
impl TripList { impl TripList {
pub fn build(package_lists: Vec<Trip>) -> Self { pub fn build(package_lists: Vec<Trip>) -> Self {
let doc = html!( let doc = html!(
<table> table {
<thead> thead {
<tr> td {
<th>"ID"</th> td { "ID" }
<th>"Name"</th> td { "Name" }
</tr> }
</thead> }
<tbody> tbody {
{ @for list in package_lists {
package_lists.into_iter().map(|list| { tr {
html!( td { (list.id.to_string()) }
<tr> td { (list.name) }
<td>{text!(list.id.to_string())}</td> }
<td>{text!(list.name)}</td> }
</tr> }
)
})
} }
</tbody>
</table>
); );
Self { doc } Self { doc }
} }
} }
impl Into<Tree> for TripList { impl Into<Markup> for TripList {
fn into(self) -> Tree { fn into(self) -> Markup {
self.doc self.doc
} }
} }

View File

@@ -1,6 +1,15 @@
use axum::{extract::Path, http::StatusCode, response::Html, routing::get, Router}; #![allow(unused_imports)]
use axum::{
extract::Path,
http::StatusCode,
response::Html,
routing::{get, post},
Router,
};
use sqlx::sqlite::SqlitePoolOptions; use sqlx::sqlite::SqlitePoolOptions;
use tracing_subscriber;
use futures::TryStreamExt; use futures::TryStreamExt;
use uuid::Uuid; use uuid::Uuid;
@@ -13,25 +22,34 @@ use crate::components::*;
use crate::models::*; use crate::models::*;
pub struct State { pub struct State {
pub has_active_category: bool, pub active_category_id: Option<Uuid>,
} }
impl State { impl State {
pub fn new() -> Self { pub fn new() -> Self {
State { State {
has_active_category: false, active_category_id: None,
} }
} }
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), sqlx::Error> { async fn main() -> Result<(), sqlx::Error> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.init();
// build our application with a route // build our application with a route
let app = Router::new() let app = Router::new()
.route("/", get(root)) .route("/", get(root))
.route("/trips/", get(trips)) .route("/trips/", get(trips))
.route("/inventory/", get(inventory_inactive)) .route("/inventory/", get(inventory_inactive))
.route("/inventory/category/:id", get(inventory_active)); .route("/inventory/category/:id", get(inventory_active))
// .route(
// "/inventory/category/:id/items",
// post(htmx_inventory_category_items),
// );
;
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr); tracing::debug!("listening on {}", addr);
@@ -64,13 +82,13 @@ async fn inventory(
active_id: Option<String>, active_id: Option<String>,
) -> Result<(StatusCode, Html<String>), (StatusCode, Html<String>)> { ) -> Result<(StatusCode, Html<String>), (StatusCode, Html<String>)> {
let mut state: State = State::new(); let mut state: State = State::new();
state.has_active_category = active_id.is_some();
let active_id = active_id let active_id = active_id
.map(|id| Uuid::try_parse(&id)) .map(|id| Uuid::try_parse(&id))
.transpose() .transpose()
.map_err(|e| (StatusCode::BAD_REQUEST, Html::from(e.to_string())))?; .map_err(|e| (StatusCode::BAD_REQUEST, Html::from(e.to_string())))?;
state.active_category_id = active_id;
let pool = SqlitePoolOptions::new() let pool = SqlitePoolOptions::new()
.max_connections(5) .max_connections(5)
.connect("sqlite:///home/hannes-private/sync/items/items.sqlite") .connect("sqlite:///home/hannes-private/sync/items/items.sqlite")
@@ -145,3 +163,44 @@ async fn trips() -> Result<(StatusCode, Html<String>), (StatusCode, Html<String>
Html::from(Root::build(TripList::build(trips).into(), TopLevelPage::Trips).to_string()), Html::from(Root::build(TripList::build(trips).into(), TopLevelPage::Trips).to_string()),
)) ))
} }
// async fn htmx_inventory_category_items(
// Path(id): Path<String>,
// ) -> Result<(StatusCode, Html<String>), (StatusCode, Html<String>)> {
// let pool = SqlitePoolOptions::new()
// .max_connections(5)
// .connect("sqlite:///home/hannes-private/sync/items/items.sqlite")
// .await
// .unwrap();
// let items = sqlx::query(&format!(
// "SELECT
// i.id, i.name, i.description, i.weight, i.category_id
// FROM inventoryitemcategories AS c
// LEFT JOIN inventoryitems AS i
// ON i.category_id = c.id WHERE c.id = '{id}';",
// id = id,
// ))
// .fetch(&pool)
// .map_ok(|row| row.try_into())
// .try_collect::<Vec<Result<Item, models::Error>>>()
// .await
// // we have two error handling lines here. these are distinct errors
// // this one is the SQL error that may arise during the query
// .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Html::from(e.to_string())))?
// .into_iter()
// .collect::<Result<Vec<Item>, models::Error>>()
// // and this one is the model mapping error that may arise e.g. during
// // reading of the rows
// .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Html::from(e.to_string())))?;
// Ok((
// StatusCode::OK,
// Html::from(
// InventoryItemList::build(&items)
// .await
// .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Html::from(e.to_string())))?
// .to_string(),
// ),
// ))
// }