use maud, make inventory work
This commit is contained in:
74
rust/Cargo.lock
generated
74
rust/Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
// ),
|
||||||
|
// ))
|
||||||
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user