add more stuff
This commit is contained in:
@@ -3,6 +3,10 @@ name = "packager"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 0
|
||||||
|
lto = "off"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4.3.0"
|
version = "4.3.0"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ CREATE TABLE "inventory_products" (
|
|||||||
UNIQUE (name)
|
UNIQUE (name)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE "inventory_items_tmp"
|
CREATE TABLE "inventory_items_tmp" (
|
||||||
id VARCHAR(36) NOT NULL,
|
id VARCHAR(36) NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
@@ -16,8 +16,12 @@ CREATE TABLE "inventory_items_tmp"
|
|||||||
product_id VARCHAR(36),
|
product_id VARCHAR(36),
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
FOREIGN KEY (category_id) REFERENCES inventory_items_categories(id)
|
FOREIGN KEY (category_id) REFERENCES inventory_items_categories(id)
|
||||||
FOREIGN KEY (product_id) REFERENCES inventory_products(id);
|
FOREIGN KEY (product_id) REFERENCES inventory_products(id)
|
||||||
)
|
);
|
||||||
|
|
||||||
|
INSERT INTO inventory_items_tmp SELECT *, NULL as product_id FROM inventory_items;
|
||||||
|
|
||||||
|
/* DROP TABLE inventory_items; */
|
||||||
|
|
||||||
|
/* ALTER TABLE "inventory_items_tmp" RENAME TO inventory_items; */
|
||||||
|
|
||||||
ALTER TABLE "inventory_items"
|
|
||||||
FOREIGN KEY (product_id) REFERENCES inventory_products(id);
|
|
||||||
|
|||||||
0
rust/migrations/20230523212341_finalize.sql
Normal file
0
rust/migrations/20230523212341_finalize.sql
Normal file
3
rust/migrations/20230523221247_finalize2.sql
Normal file
3
rust/migrations/20230523221247_finalize2.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DROP TABLE inventory_items;
|
||||||
|
|
||||||
|
ALTER TABLE "inventory_items_tmp" RENAME TO inventory_items;
|
||||||
@@ -120,6 +120,48 @@
|
|||||||
},
|
},
|
||||||
"query": "INSERT INTO trips_to_trips_types\n (trip_id, trip_type_id) VALUES (?, ?)"
|
"query": "INSERT INTO trips_to_trips_types\n (trip_id, trip_type_id) VALUES (?, ?)"
|
||||||
},
|
},
|
||||||
|
"53c45b9447118c8b448df6836e68a38ff78d0b7f4d5344a1e5064d31e70396b3": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "weight",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category_id",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n id,\n name,\n weight,\n description,\n category_id\n FROM inventory_items AS item\n WHERE item.id = ?"
|
||||||
|
},
|
||||||
"68304c19a0bee12c0b3ce9740d53389620b20e47973b41975678dbd13bd30c7f": {
|
"68304c19a0bee12c0b3ce9740d53389620b20e47973b41975678dbd13bd30c7f": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -344,6 +386,24 @@
|
|||||||
},
|
},
|
||||||
"query": "INSERT INTO trips\n (id, name, date_start, date_end, state)\n VALUES\n (?, ?, ?, ?, ?)"
|
"query": "INSERT INTO trips\n (id, name, date_start, date_end, state)\n VALUES\n (?, ?, ?, ?, ?)"
|
||||||
},
|
},
|
||||||
|
"9c4c2eb24747c3d8157ad17bacbe074d906d2891e8c68160becd8e10d045cfbc": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "weight",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT COALESCE(MAX(i_item.weight), 0) as weight\n FROM inventory_items_categories as category\n INNER JOIN inventory_items as i_item\n ON i_item.category_id = category.id\n WHERE category_id = (\n SELECT category_id\n FROM inventory_items\n WHERE inventory_items.id = ?\n )\n "
|
||||||
|
},
|
||||||
"a81bcbeb11260e3b4363e19c26b71b489e326b08bfacb6e11b4c4fc068dc7806": {
|
"a81bcbeb11260e3b4363e19c26b71b489e326b08bfacb6e11b4c4fc068dc7806": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -384,7 +444,17 @@
|
|||||||
},
|
},
|
||||||
"query": "DELETE FROM inventory_items\n WHERE id = ?"
|
"query": "DELETE FROM inventory_items\n WHERE id = ?"
|
||||||
},
|
},
|
||||||
"b916db63913aa222cef4552dffdcda0f26f16612fbb4c1e839bfd0162888fdc3": {
|
"ded3be1c8894a64e3b5f749461db7261d9224abb8a54da980db8262733d08205": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO trips_types\n (id, name)\n VALUES\n (?, ?)"
|
||||||
|
},
|
||||||
|
"efcf56aacc622556fc10220edb57ea69822eed50cdf9ef54bc48a7fb04d4ee9a": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -411,6 +481,36 @@
|
|||||||
"name": "category_id",
|
"name": "category_id",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category_name",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category_description",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "product_id",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "product_name",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "product_description",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "product_comment",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nullable": [
|
"nullable": [
|
||||||
@@ -418,23 +518,19 @@
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 1
|
"Right": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"query": "SELECT * FROM inventory_items AS item\n WHERE item.id = ?"
|
"query": "SELECT\n item.id AS id,\n item.name AS name,\n item.description AS description,\n weight,\n category.id AS category_id,\n category.name AS category_name,\n category.description AS category_description,\n product.id AS product_id,\n product.name AS product_name,\n product.description AS product_description,\n product.comment AS product_comment\n FROM inventory_items AS item\n INNER JOIN inventory_items_categories as category\n ON item.category_id = category.id\n LEFT JOIN inventory_products AS product\n ON item.product_id = product.id\n WHERE item.id = ?"
|
||||||
},
|
|
||||||
"ded3be1c8894a64e3b5f749461db7261d9224abb8a54da980db8262733d08205": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "INSERT INTO trips_types\n (id, name)\n VALUES\n (?, ?)"
|
|
||||||
},
|
},
|
||||||
"f2038d75ff5ff10d4baeb30b9dc4cc1c991da1facdb1f05e16f271372eee0c7a": {
|
"f2038d75ff5ff10d4baeb30b9dc4cc1c991da1facdb1f05e16f271372eee0c7a": {
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -524,6 +620,84 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT\n category.id as category_id,\n category.name as category_name,\n category.description AS category_description,\n inner.trip_id AS trip_id,\n inner.item_id AS item_id,\n inner.item_name AS item_name,\n inner.item_description AS item_description,\n inner.item_weight AS item_weight,\n inner.item_is_picked AS item_is_picked,\n inner.item_is_packed AS item_is_packed,\n inner.item_is_new AS item_is_new\n FROM inventory_items_categories AS category\n LEFT JOIN (\n SELECT\n trip.trip_id AS trip_id,\n category.id as category_id,\n category.name as category_name,\n category.description as category_description,\n item.id as item_id,\n item.name as item_name,\n item.description as item_description,\n item.weight as item_weight,\n trip.pick as item_is_picked,\n trip.pack as item_is_packed,\n trip.new as item_is_new\n FROM trips_items as trip\n INNER JOIN inventory_items as item\n ON item.id = trip.item_id\n INNER JOIN inventory_items_categories as category\n ON category.id = item.category_id\n WHERE trip.trip_id = ?\n ) AS inner\n ON inner.category_id = category.id\n "
|
"query": "\n SELECT\n category.id as category_id,\n category.name as category_name,\n category.description AS category_description,\n inner.trip_id AS trip_id,\n inner.item_id AS item_id,\n inner.item_name AS item_name,\n inner.item_description AS item_description,\n inner.item_weight AS item_weight,\n inner.item_is_picked AS item_is_picked,\n inner.item_is_packed AS item_is_packed,\n inner.item_is_new AS item_is_new\n FROM inventory_items_categories AS category\n LEFT JOIN (\n SELECT\n trip.trip_id AS trip_id,\n category.id as category_id,\n category.name as category_name,\n category.description as category_description,\n item.id as item_id,\n item.name as item_name,\n item.description as item_description,\n item.weight as item_weight,\n trip.pick as item_is_picked,\n trip.pack as item_is_packed,\n trip.new as item_is_new\n FROM trips_items as trip\n INNER JOIN inventory_items as item\n ON item.id = trip.item_id\n INNER JOIN inventory_items_categories as category\n ON category.id = item.category_id\n WHERE trip.trip_id = ?\n ) AS inner\n ON inner.category_id = category.id\n "
|
||||||
},
|
},
|
||||||
|
"f3fd58ae5e462c354d76fcfce1e86ffcdab80d507cc6599adebcdaea8bb2bc6f": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "picked",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "packed",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "new",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "weight",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category_id",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT\n t_item.item_id AS id,\n t_item.pick AS picked,\n t_item.pack AS packed,\n t_item.new AS new,\n i_item.name AS name,\n i_item.description AS description,\n i_item.weight AS weight,\n i_item.category_id AS category_id\n FROM trips_items AS t_item\n INNER JOIN inventory_items AS i_item\n ON i_item.id = t_item.item_id\n WHERE t_item.item_id = ?\n AND t_item.trip_id = ?\n "
|
||||||
|
},
|
||||||
|
"f9d080a5b8710c7d6a497bb1f5cf4839ad1589fd7d6a06d3faf1163d6981d8a0": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "weight",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT COALESCE(SUM(i_item.weight), 0) as weight\n FROM inventory_items_categories as category\n INNER JOIN inventory_items as i_item\n ON i_item.category_id = category.id\n INNER JOIN trips_items as t_item\n ON i_item.id = t_item.item_id\n WHERE category_id = ?\n AND t_item.pick = 1\n "
|
||||||
|
},
|
||||||
"ff260eef6f95a3c1f8e2f822808ac250925dc0971b9bddd9015b8b24643357c9": {
|
"ff260eef6f95a3c1f8e2f822808ac250925dc0971b9bddd9015b8b24643357c9": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use maud::{html, Markup, PreEscaped};
|
use maud::{html, Markup, PreEscaped};
|
||||||
|
|
||||||
|
use crate::models;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use crate::ClientState;
|
use crate::ClientState;
|
||||||
use uuid::{uuid, Uuid};
|
use uuid::{uuid, Uuid};
|
||||||
@@ -221,7 +222,8 @@ impl InventoryItemList {
|
|||||||
."m-auto"
|
."m-auto"
|
||||||
."mdi"
|
."mdi"
|
||||||
."mdi-content-save"
|
."mdi-content-save"
|
||||||
."text-xl";
|
."text-xl"
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
td
|
td
|
||||||
@@ -243,7 +245,8 @@ impl InventoryItemList {
|
|||||||
."m-auto"
|
."m-auto"
|
||||||
."mdi"
|
."mdi"
|
||||||
."mdi-cancel"
|
."mdi-cancel"
|
||||||
."text-xl";
|
."text-xl"
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +285,7 @@ impl InventoryItemList {
|
|||||||
."w-full"
|
."w-full"
|
||||||
href=(format!("?edit_item={id}", id = item.id))
|
href=(format!("?edit_item={id}", id = item.id))
|
||||||
{
|
{
|
||||||
span ."m-auto" ."mdi" ."mdi-pencil" ."text-xl";
|
span ."m-auto" ."mdi" ."mdi-pencil" ."text-xl" {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
td
|
td
|
||||||
@@ -299,7 +302,7 @@ impl InventoryItemList {
|
|||||||
."w-full"
|
."w-full"
|
||||||
href=(format!("/inventory/item/{id}/delete", id = item.id))
|
href=(format!("/inventory/item/{id}/delete", id = item.id))
|
||||||
{
|
{
|
||||||
span ."m-auto" ."mdi" ."mdi-delete" ."text-xl";
|
span ."m-auto" ."mdi" ."mdi-delete" ."text-xl" {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,7 +351,7 @@ impl InventoryNewItemFormName {
|
|||||||
."focus:bg-white"
|
."focus:bg-white"
|
||||||
."focus:border-purple-500"[!error]
|
."focus:border-purple-500"[!error]
|
||||||
value=[value]
|
value=[value]
|
||||||
;
|
{}
|
||||||
@if error {
|
@if error {
|
||||||
div
|
div
|
||||||
."col-start-2"
|
."col-start-2"
|
||||||
@@ -549,3 +552,45 @@ impl InventoryNewCategoryForm {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct InventoryItem;
|
||||||
|
|
||||||
|
impl InventoryItem {
|
||||||
|
pub fn build(_state: &ClientState, item: &models::InventoryItem) -> Markup {
|
||||||
|
html!(
|
||||||
|
div ."p-8" {
|
||||||
|
table
|
||||||
|
."table"
|
||||||
|
."table-auto"
|
||||||
|
."border-collapse"
|
||||||
|
."border-spacing-0"
|
||||||
|
."border"
|
||||||
|
."w-full"
|
||||||
|
{
|
||||||
|
tbody {
|
||||||
|
tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" {
|
||||||
|
td ."border" ."p-2" { "Name" }
|
||||||
|
td ."border" ."p-2" { (item.name) }
|
||||||
|
}
|
||||||
|
tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" {
|
||||||
|
td ."border" ."p-2" { "Description" }
|
||||||
|
td ."border" ."p-2" { (item.description.clone().unwrap_or("".to_string())) }
|
||||||
|
}
|
||||||
|
tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" {
|
||||||
|
td ."border" ."p-2" { "Weight" }
|
||||||
|
td ."border" ."p-2" { (item.weight.to_string()) }
|
||||||
|
}
|
||||||
|
tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" {
|
||||||
|
td ."border" ."p-2" { "Category" }
|
||||||
|
td ."border" ."p-2" { (item.category.name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@match item.product {
|
||||||
|
Some(ref product) => p { "this item is part of product" (product.name) },
|
||||||
|
None => p { "this item is not part of a product" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ impl Root {
|
|||||||
script src="https://unpkg.com/alpinejs@3.12.1" defer {}
|
script src="https://unpkg.com/alpinejs@3.12.1" defer {}
|
||||||
script src="https://cdn.tailwindcss.com" {}
|
script src="https://cdn.tailwindcss.com" {}
|
||||||
script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js" defer {}
|
script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js" defer {}
|
||||||
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css";
|
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" {}
|
||||||
link rel="shortcut icon" type="image/svg+xml" href="/favicon.svg";
|
link rel="shortcut icon" type="image/svg+xml" href="/favicon.svg" {}
|
||||||
script { (PreEscaped(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js")))) }
|
script { (PreEscaped(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js")))) }
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
@@ -53,7 +53,7 @@ impl Root {
|
|||||||
."items-center"
|
."items-center"
|
||||||
."gap-3"
|
."gap-3"
|
||||||
{
|
{
|
||||||
img ."h-12" src="/assets/luggage.svg";
|
img ."h-12" src="/assets/luggage.svg" {}
|
||||||
a #home href="/" { "Packager" }
|
a #home href="/" { "Packager" }
|
||||||
}
|
}
|
||||||
nav
|
nav
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ impl Trip {
|
|||||||
name="new-value"
|
name="new-value"
|
||||||
form="edit-trip"
|
form="edit-trip"
|
||||||
value=(trip.name)
|
value=(trip.name)
|
||||||
;
|
{}
|
||||||
a
|
a
|
||||||
href="."
|
href="."
|
||||||
."bg-red-200"
|
."bg-red-200"
|
||||||
@@ -250,7 +250,7 @@ impl Trip {
|
|||||||
."mdi-cancel"
|
."mdi-cancel"
|
||||||
."text-xl"
|
."text-xl"
|
||||||
."m-auto"
|
."m-auto"
|
||||||
;
|
{}
|
||||||
}
|
}
|
||||||
button
|
button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -263,7 +263,7 @@ impl Trip {
|
|||||||
."mdi"
|
."mdi"
|
||||||
."mdi-content-save"
|
."mdi-content-save"
|
||||||
."text-xl"
|
."text-xl"
|
||||||
;
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ impl Trip {
|
|||||||
."mdi-pencil"
|
."mdi-pencil"
|
||||||
."text-xl"
|
."text-xl"
|
||||||
."opacity-50"
|
."opacity-50"
|
||||||
;
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,10 +308,9 @@ impl TripInfoRow {
|
|||||||
name="edit-trip"
|
name="edit-trip"
|
||||||
id="edit-trip"
|
id="edit-trip"
|
||||||
action=(format!("edit/{key}/submit", key=(to_variant_name(&attribute_key).unwrap()) ))
|
action=(format!("edit/{key}/submit", key=(to_variant_name(&attribute_key).unwrap()) ))
|
||||||
htmx-push-url="true"
|
|
||||||
target="_self"
|
target="_self"
|
||||||
method="post"
|
method="post"
|
||||||
;
|
{}
|
||||||
}
|
}
|
||||||
tr .h-full {
|
tr .h-full {
|
||||||
@if edit {
|
@if edit {
|
||||||
@@ -324,7 +323,7 @@ impl TripInfoRow {
|
|||||||
name="new-value"
|
name="new-value"
|
||||||
form="edit-trip"
|
form="edit-trip"
|
||||||
value=(value.map_or(String::new(), |v| v.to_string()))
|
value=(value.map_or(String::new(), |v| v.to_string()))
|
||||||
;
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
td
|
td
|
||||||
@@ -347,7 +346,8 @@ impl TripInfoRow {
|
|||||||
."m-auto"
|
."m-auto"
|
||||||
."mdi"
|
."mdi"
|
||||||
."mdi-cancel"
|
."mdi-cancel"
|
||||||
."text-xl";
|
."text-xl"
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
td
|
td
|
||||||
@@ -370,7 +370,8 @@ impl TripInfoRow {
|
|||||||
."m-auto"
|
."m-auto"
|
||||||
."mdi"
|
."mdi"
|
||||||
."mdi-content-save"
|
."mdi-content-save"
|
||||||
."text-xl";
|
."text-xl"
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
@@ -395,7 +396,8 @@ impl TripInfoRow {
|
|||||||
."m-auto"
|
."m-auto"
|
||||||
."mdi"
|
."mdi"
|
||||||
."mdi-pencil"
|
."mdi-pencil"
|
||||||
."text-xl";
|
."text-xl"
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,7 +495,8 @@ impl TripInfo {
|
|||||||
."m-auto"
|
."m-auto"
|
||||||
."mdi"
|
."mdi"
|
||||||
."mdi-step-backward"
|
."mdi-step-backward"
|
||||||
."text-xl";
|
."text-xl"
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,7 +528,8 @@ impl TripInfo {
|
|||||||
."m-auto"
|
."m-auto"
|
||||||
."mdi"
|
."mdi"
|
||||||
."mdi-step-forward"
|
."mdi-step-forward"
|
||||||
."text-xl";
|
."text-xl"
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,7 +662,7 @@ impl TripComment {
|
|||||||
action="comment/submit"
|
action="comment/submit"
|
||||||
target="_self"
|
target="_self"
|
||||||
method="post"
|
method="post"
|
||||||
;
|
{}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/48460773
|
// https://stackoverflow.com/a/48460773
|
||||||
textarea
|
textarea
|
||||||
@@ -689,7 +693,7 @@ impl TripComment {
|
|||||||
."gap-2"
|
."gap-2"
|
||||||
."items-center"
|
."items-center"
|
||||||
{
|
{
|
||||||
span ."mdi" ."mdi-content-save" ."text-xl";
|
span ."mdi" ."mdi-content-save" ."text-xl" {}
|
||||||
span { "Save" }
|
span { "Save" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -735,6 +739,105 @@ impl TripItems {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TripCategoryListRow;
|
||||||
|
|
||||||
|
impl TripCategoryListRow {
|
||||||
|
pub fn build(
|
||||||
|
category: &TripCategory,
|
||||||
|
active: bool,
|
||||||
|
has_new_items: bool,
|
||||||
|
biggest_category_weight: i64,
|
||||||
|
) -> Markup {
|
||||||
|
html!(
|
||||||
|
tr
|
||||||
|
id={"category-" (category.category.id)}
|
||||||
|
."h-10"
|
||||||
|
."hover:bg-purple-100"
|
||||||
|
."m-3"
|
||||||
|
."h-full"
|
||||||
|
."outline"[active]
|
||||||
|
."outline-2"[active]
|
||||||
|
."outline-indigo-300"[active]
|
||||||
|
{
|
||||||
|
|
||||||
|
td
|
||||||
|
|
||||||
|
."border"
|
||||||
|
."m-0"
|
||||||
|
|
||||||
|
{
|
||||||
|
div
|
||||||
|
."p-0"
|
||||||
|
."flex"
|
||||||
|
."flex-row"
|
||||||
|
."items-center"
|
||||||
|
."group"
|
||||||
|
{
|
||||||
|
a
|
||||||
|
id="select-category"
|
||||||
|
href=(
|
||||||
|
format!(
|
||||||
|
"?category={id}",
|
||||||
|
id=category.category.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
."inline-block"
|
||||||
|
."p-2"
|
||||||
|
."m-0"
|
||||||
|
."w-full"
|
||||||
|
."grow"
|
||||||
|
."font-bold"[active]
|
||||||
|
{
|
||||||
|
(category.category.name.clone())
|
||||||
|
}
|
||||||
|
@if has_new_items {
|
||||||
|
div
|
||||||
|
."mr-2"
|
||||||
|
."flex"
|
||||||
|
."flex-row"
|
||||||
|
."items-center"
|
||||||
|
{
|
||||||
|
p
|
||||||
|
."hidden"
|
||||||
|
."group-hover:inline"
|
||||||
|
."text-sm"
|
||||||
|
."text-gray-500"
|
||||||
|
."grow"
|
||||||
|
{
|
||||||
|
"new items"
|
||||||
|
}
|
||||||
|
span
|
||||||
|
."mdi"
|
||||||
|
."mdi-exclamation-thick"
|
||||||
|
."text-xl"
|
||||||
|
."text-yellow-400"
|
||||||
|
."grow-0"
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td ."border" ."m-0" ."p-2" style="position:relative;" {
|
||||||
|
p {
|
||||||
|
(category.total_picked_weight().to_string())
|
||||||
|
}
|
||||||
|
div ."bg-blue-600" ."h-1.5"
|
||||||
|
style=(
|
||||||
|
format!(
|
||||||
|
"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
||||||
|
width=(
|
||||||
|
(category.total_picked_weight() as f64)
|
||||||
|
/ (biggest_category_weight as f64)
|
||||||
|
* 100.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TripCategoryList;
|
pub struct TripCategoryList;
|
||||||
|
|
||||||
impl TripCategoryList {
|
impl TripCategoryList {
|
||||||
@@ -771,90 +874,7 @@ impl TripCategoryList {
|
|||||||
@for category in trip.categories() {
|
@for category in trip.categories() {
|
||||||
@let has_new_items = category.items.as_ref().unwrap().iter().any(|item| item.new);
|
@let has_new_items = category.items.as_ref().unwrap().iter().any(|item| item.new);
|
||||||
@let active = state.active_category_id.map_or(false, |id| category.category.id == id);
|
@let active = state.active_category_id.map_or(false, |id| category.category.id == id);
|
||||||
tr
|
(TripCategoryListRow::build(category, active, has_new_items, biggest_category_weight))
|
||||||
."h-10"
|
|
||||||
."hover:bg-purple-100"
|
|
||||||
."m-3"
|
|
||||||
."h-full"
|
|
||||||
."outline"[active]
|
|
||||||
."outline-2"[active]
|
|
||||||
."outline-indigo-300"[active]
|
|
||||||
{
|
|
||||||
|
|
||||||
td
|
|
||||||
|
|
||||||
."border"
|
|
||||||
."m-0"
|
|
||||||
|
|
||||||
{
|
|
||||||
div
|
|
||||||
."p-0"
|
|
||||||
."flex"
|
|
||||||
."flex-row"
|
|
||||||
."items-center"
|
|
||||||
."group"
|
|
||||||
{
|
|
||||||
a
|
|
||||||
id="select-category"
|
|
||||||
href=(
|
|
||||||
format!(
|
|
||||||
"?category={id}",
|
|
||||||
id=category.category.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
."inline-block"
|
|
||||||
."p-2"
|
|
||||||
."m-0"
|
|
||||||
."w-full"
|
|
||||||
."grow"
|
|
||||||
."font-bold"[active]
|
|
||||||
{
|
|
||||||
(category.category.name.clone())
|
|
||||||
}
|
|
||||||
@if has_new_items {
|
|
||||||
div
|
|
||||||
."mr-2"
|
|
||||||
."flex"
|
|
||||||
."flex-row"
|
|
||||||
."items-center"
|
|
||||||
{
|
|
||||||
p
|
|
||||||
."hidden"
|
|
||||||
."group-hover:inline"
|
|
||||||
."text-sm"
|
|
||||||
."text-gray-500"
|
|
||||||
."grow"
|
|
||||||
{
|
|
||||||
"new items"
|
|
||||||
}
|
|
||||||
span
|
|
||||||
."mdi"
|
|
||||||
."mdi-exclamation-thick"
|
|
||||||
."text-xl"
|
|
||||||
."text-yellow-400"
|
|
||||||
."grow-0"
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td ."border" ."m-0" ."p-2" style="position:relative;" {
|
|
||||||
p {
|
|
||||||
(category.total_picked_weight().to_string())
|
|
||||||
}
|
|
||||||
div ."bg-blue-600" ."h-1.5"
|
|
||||||
style=(
|
|
||||||
format!(
|
|
||||||
"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
|
||||||
width=(
|
|
||||||
(category.total_picked_weight() as f64)
|
|
||||||
/ (biggest_category_weight as f64)
|
|
||||||
* 100.0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tr ."h-10" ."hover:bg-purple-200" ."bg-gray-300" ."font-bold" {
|
tr ."h-10" ."hover:bg-purple-200" ."bg-gray-300" ."font-bold" {
|
||||||
td ."border" ."p-0" ."m-0" {
|
td ."border" ."p-0" ."m-0" {
|
||||||
@@ -894,7 +914,7 @@ impl TripItemList {
|
|||||||
table
|
table
|
||||||
."table"
|
."table"
|
||||||
."table-auto"
|
."table-auto"
|
||||||
.table-fixed
|
."table-fixed"
|
||||||
."border-collapse"
|
."border-collapse"
|
||||||
."border-spacing-0"
|
."border-spacing-0"
|
||||||
."border"
|
."border"
|
||||||
@@ -910,86 +930,7 @@ impl TripItemList {
|
|||||||
}
|
}
|
||||||
tbody {
|
tbody {
|
||||||
@for item in items {
|
@for item in items {
|
||||||
tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" {
|
(TripItemListRow::build(trip.id, item, biggest_item_weight))
|
||||||
td {
|
|
||||||
a
|
|
||||||
href={
|
|
||||||
"/trip/" (trip.id)
|
|
||||||
"/items/" (item.item.id)
|
|
||||||
"/" (if item.picked { "unpick" } else { "pick" }) }
|
|
||||||
."inline-block"
|
|
||||||
."p-2"
|
|
||||||
."m-0"
|
|
||||||
."w-full"
|
|
||||||
."justify-center"
|
|
||||||
."content-center"
|
|
||||||
."flex"
|
|
||||||
{
|
|
||||||
input
|
|
||||||
type="checkbox"
|
|
||||||
checked[item.picked]
|
|
||||||
autocomplete="off"
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
a
|
|
||||||
href={
|
|
||||||
"/trip/" (trip.id)
|
|
||||||
"/items/" (item.item.id)
|
|
||||||
"/" (if item.packed { "unpack" } else { "pack" }) }
|
|
||||||
."inline-block"
|
|
||||||
."p-2"
|
|
||||||
."m-0"
|
|
||||||
."w-full"
|
|
||||||
."justify-center"
|
|
||||||
."content-center"
|
|
||||||
."flex"
|
|
||||||
{
|
|
||||||
input
|
|
||||||
type="checkbox"
|
|
||||||
checked[item.packed]
|
|
||||||
autocomplete="off"
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td ."border" ."p-0" {
|
|
||||||
div
|
|
||||||
."flex"
|
|
||||||
."flex-row"
|
|
||||||
."items-center"
|
|
||||||
{
|
|
||||||
a
|
|
||||||
."p-2" ."w-full" ."inline-block"
|
|
||||||
href=(
|
|
||||||
format!("/inventory/item/{id}/", id=item.item.id)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
(item.item.name.clone())
|
|
||||||
}
|
|
||||||
@if item.new {
|
|
||||||
div ."mr-2" {
|
|
||||||
span
|
|
||||||
."mdi"
|
|
||||||
."mdi-exclamation-thick"
|
|
||||||
."text-xl"
|
|
||||||
."text-yellow-400"
|
|
||||||
."grow-0"
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td ."border" ."p-2" style="position:relative;" {
|
|
||||||
p { (item.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.item.weight as f64) / (biggest_item_weight as f64) * 100.0))) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -997,3 +938,147 @@ impl TripItemList {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TripItemListRow;
|
||||||
|
|
||||||
|
impl TripItemListRow {
|
||||||
|
pub fn build(trip_id: Uuid, item: &models::TripItem, biggest_item_weight: i64) -> Markup {
|
||||||
|
html!(
|
||||||
|
tr ."h-10" {
|
||||||
|
td
|
||||||
|
."border"
|
||||||
|
."p-0"
|
||||||
|
{
|
||||||
|
a
|
||||||
|
href={
|
||||||
|
"/trip/" (trip_id)
|
||||||
|
"/items/" (item.item.id)
|
||||||
|
"/" (if item.picked { "unpick" } else { "pick" }) }
|
||||||
|
hx-post={
|
||||||
|
"/trip/" (trip_id)
|
||||||
|
"/items/" (item.item.id)
|
||||||
|
"/" (if item.picked { "unpick" } else { "pick" }) }
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
."inline-block"
|
||||||
|
."p-2"
|
||||||
|
."m-0"
|
||||||
|
."w-full"
|
||||||
|
."justify-center"
|
||||||
|
."content-center"
|
||||||
|
."flex"
|
||||||
|
."bg-green-200"[item.picked]
|
||||||
|
."hover:bg-green-100"[!item.picked]
|
||||||
|
."hover:bg-red-100"[item.picked]
|
||||||
|
{
|
||||||
|
@if item.picked {
|
||||||
|
span
|
||||||
|
."mdi"
|
||||||
|
."mdi-clipboard-text-outline"
|
||||||
|
."text-2xl"
|
||||||
|
{}
|
||||||
|
} @else {
|
||||||
|
span
|
||||||
|
."mdi"
|
||||||
|
."mdi-clipboard-text-off-outline"
|
||||||
|
."text-2xl"
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td
|
||||||
|
."border"
|
||||||
|
."p-0"
|
||||||
|
{
|
||||||
|
@if item.picked {
|
||||||
|
a
|
||||||
|
href={
|
||||||
|
"/trip/" (trip_id)
|
||||||
|
"/items/" (item.item.id)
|
||||||
|
"/" (if item.packed { "unpack" } else { "pack" }) }
|
||||||
|
hx-post={
|
||||||
|
"/trip/" (trip_id)
|
||||||
|
"/items/" (item.item.id)
|
||||||
|
"/" (if item.packed { "unpack" } else { "pack" }) }
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
."inline-block"
|
||||||
|
."p-2"
|
||||||
|
."m-0"
|
||||||
|
."w-full"
|
||||||
|
."justify-center"
|
||||||
|
."content-center"
|
||||||
|
."flex"
|
||||||
|
."bg-green-200"[item.packed]
|
||||||
|
."hover:bg-green-100"[!item.packed]
|
||||||
|
."hover:bg-red-100"[item.packed]
|
||||||
|
{
|
||||||
|
@if item.packed {
|
||||||
|
span
|
||||||
|
."mdi"
|
||||||
|
."mdi-bag-personal-outline"
|
||||||
|
."text-2xl"
|
||||||
|
{}
|
||||||
|
} @else {
|
||||||
|
span
|
||||||
|
."mdi"
|
||||||
|
."mdi-bag-personal-off-outline"
|
||||||
|
."text-2xl"
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
div
|
||||||
|
."flex"
|
||||||
|
."justify-center"
|
||||||
|
."items-center"
|
||||||
|
{
|
||||||
|
span
|
||||||
|
."mdi"
|
||||||
|
."mdi-bag-personal-outline"
|
||||||
|
."text-2xl"
|
||||||
|
."text-gray-300"
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td ."border" ."p-0" {
|
||||||
|
div
|
||||||
|
."flex"
|
||||||
|
."flex-row"
|
||||||
|
."items-center"
|
||||||
|
{
|
||||||
|
a
|
||||||
|
."p-2" ."w-full" ."inline-block"
|
||||||
|
href=(
|
||||||
|
format!("/inventory/item/{id}/", id=item.item.id)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
(item.item.name.clone())
|
||||||
|
}
|
||||||
|
@if item.new {
|
||||||
|
div ."mr-2" {
|
||||||
|
span
|
||||||
|
."mdi"
|
||||||
|
."mdi-exclamation-thick"
|
||||||
|
."text-xl"
|
||||||
|
."text-yellow-400"
|
||||||
|
."grow-0"
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td ."border" ."p-2" style="position:relative;" {
|
||||||
|
p { (item.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.item.weight as f64) / (biggest_item_weight as f64) * 100.0))) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ impl TypeList {
|
|||||||
name="new-value"
|
name="new-value"
|
||||||
form="edit-trip-type"
|
form="edit-trip-type"
|
||||||
value=(trip_type.name)
|
value=(trip_type.name)
|
||||||
;
|
{}
|
||||||
}
|
}
|
||||||
div
|
div
|
||||||
."flex"
|
."flex"
|
||||||
@@ -66,7 +66,7 @@ impl TypeList {
|
|||||||
."mdi-cancel"
|
."mdi-cancel"
|
||||||
."text-xl"
|
."text-xl"
|
||||||
."m-auto"
|
."m-auto"
|
||||||
;
|
{}
|
||||||
}
|
}
|
||||||
button
|
button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -79,7 +79,7 @@ impl TypeList {
|
|||||||
."mdi"
|
."mdi"
|
||||||
."mdi-content-save"
|
."mdi-content-save"
|
||||||
."text-xl"
|
."text-xl"
|
||||||
;
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
@@ -105,7 +105,8 @@ impl TypeList {
|
|||||||
."m-auto"
|
."m-auto"
|
||||||
."mdi"
|
."mdi"
|
||||||
."mdi-pencil"
|
."mdi-pencil"
|
||||||
."text-xl";
|
."text-xl"
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
148
rust/src/main.rs
148
rust/src/main.rs
@@ -9,6 +9,8 @@ use axum::{
|
|||||||
Form, Router,
|
Form, Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use maud::html;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use serde_variant::to_variant_name;
|
use serde_variant::to_variant_name;
|
||||||
@@ -128,12 +130,25 @@ async fn main() -> Result<(), sqlx::Error> {
|
|||||||
"/trip/:id/edit/:attribute/submit",
|
"/trip/:id/edit/:attribute/submit",
|
||||||
post(trip_edit_attribute),
|
post(trip_edit_attribute),
|
||||||
)
|
)
|
||||||
.route("/trip/:id/items/:id/pick", get(trip_item_set_pick))
|
.route(
|
||||||
.route("/trip/:id/items/:id/unpick", get(trip_item_set_unpick))
|
"/trip/:id/items/:id/pick",
|
||||||
.route("/trip/:id/items/:id/pack", get(trip_item_set_pack))
|
get(trip_item_set_pick).post(trip_item_set_pick_htmx),
|
||||||
.route("/trip/:id/items/:id/unpack", get(trip_item_set_unpack))
|
)
|
||||||
|
.route(
|
||||||
|
"/trip/:id/items/:id/unpick",
|
||||||
|
get(trip_item_set_unpick).post(trip_item_set_unpick_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/trip/:id/items/:id/pack",
|
||||||
|
get(trip_item_set_pack).post(trip_item_set_pack_htmx),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/trip/:id/items/:id/unpack",
|
||||||
|
get(trip_item_set_unpack).post(trip_item_set_unpack_htmx),
|
||||||
|
)
|
||||||
.route("/inventory/", get(inventory_inactive))
|
.route("/inventory/", get(inventory_inactive))
|
||||||
.route("/inventory/category/", post(inventory_category_create))
|
.route("/inventory/category/", post(inventory_category_create))
|
||||||
|
.route("/inventory/item/:id/", get(inventory_item))
|
||||||
.route("/inventory/item/", post(inventory_item_create))
|
.route("/inventory/item/", post(inventory_item_create))
|
||||||
.route(
|
.route(
|
||||||
"/inventory/item/name/validate",
|
"/inventory/item/name/validate",
|
||||||
@@ -1007,6 +1022,14 @@ async fn trip_item_set_pick(
|
|||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn trip_item_set_pick_htmx(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
||||||
|
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
||||||
|
trip_item_set_state(&state, trip_id, item_id, TripItemStateKey::Pick, true).await?;
|
||||||
|
Ok((StatusCode::OK, trip_row(&state, trip_id, item_id).await?))
|
||||||
|
}
|
||||||
|
|
||||||
async fn trip_item_set_unpick(
|
async fn trip_item_set_unpick(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
||||||
@@ -1033,6 +1056,51 @@ async fn trip_item_set_unpick(
|
|||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn trip_row(
|
||||||
|
state: &AppState,
|
||||||
|
trip_id: Uuid,
|
||||||
|
item_id: Uuid,
|
||||||
|
) -> Result<Markup, (StatusCode, Markup)> {
|
||||||
|
let item: TripItem = TripItem::find(&state.database_pool, trip_id, item_id)
|
||||||
|
.await
|
||||||
|
.map_err(|error| {
|
||||||
|
(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
ErrorPage::build(&error.to_string()),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
(
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
ErrorPage::build(&format!(
|
||||||
|
"item with id {} not found for trip {}",
|
||||||
|
item_id, trip_id
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(components::trip::TripItemListRow::build(
|
||||||
|
trip_id,
|
||||||
|
&item,
|
||||||
|
Item::get_category_max_weight(&state.database_pool, item.item.category_id)
|
||||||
|
.await
|
||||||
|
.map_err(|error| {
|
||||||
|
(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
ErrorPage::build(&error.to_string()),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn trip_item_set_unpick_htmx(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
||||||
|
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
||||||
|
trip_item_set_state(&state, trip_id, item_id, TripItemStateKey::Pick, false).await?;
|
||||||
|
Ok((StatusCode::OK, trip_row(&state, trip_id, item_id).await?))
|
||||||
|
}
|
||||||
|
|
||||||
async fn trip_item_set_pack(
|
async fn trip_item_set_pack(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
||||||
@@ -1059,6 +1127,14 @@ async fn trip_item_set_pack(
|
|||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn trip_item_set_pack_htmx(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
||||||
|
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
||||||
|
trip_item_set_state(&state, trip_id, item_id, TripItemStateKey::Pack, true).await?;
|
||||||
|
Ok((StatusCode::OK, trip_row(&state, trip_id, item_id).await?))
|
||||||
|
}
|
||||||
|
|
||||||
async fn trip_item_set_unpack(
|
async fn trip_item_set_unpack(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
||||||
@@ -1085,6 +1161,14 @@ async fn trip_item_set_unpack(
|
|||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn trip_item_set_unpack_htmx(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
||||||
|
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
||||||
|
trip_item_set_state(&state, trip_id, item_id, TripItemStateKey::Pack, false).await?;
|
||||||
|
Ok((StatusCode::OK, trip_row(&state, trip_id, item_id).await?))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct NewCategory {
|
struct NewCategory {
|
||||||
#[serde(rename = "new-category-name")]
|
#[serde(rename = "new-category-name")]
|
||||||
@@ -1339,3 +1423,59 @@ async fn trips_types_edit_name(
|
|||||||
Ok(Redirect::to("/trips/types/"))
|
Ok(Redirect::to("/trips/types/"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn inventory_item(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
|
||||||
|
let id_param = id.to_string();
|
||||||
|
let item: models::InventoryItem = query_as!(
|
||||||
|
DbInventoryItemRow,
|
||||||
|
"SELECT
|
||||||
|
item.id AS id,
|
||||||
|
item.name AS name,
|
||||||
|
item.description AS description,
|
||||||
|
weight,
|
||||||
|
category.id AS category_id,
|
||||||
|
category.name AS category_name,
|
||||||
|
category.description AS category_description,
|
||||||
|
product.id AS product_id,
|
||||||
|
product.name AS product_name,
|
||||||
|
product.description AS product_description,
|
||||||
|
product.comment AS product_comment
|
||||||
|
FROM inventory_items AS item
|
||||||
|
INNER JOIN inventory_items_categories as category
|
||||||
|
ON item.category_id = category.id
|
||||||
|
LEFT JOIN inventory_products AS product
|
||||||
|
ON item.product_id = product.id
|
||||||
|
WHERE item.id = ?",
|
||||||
|
id_param,
|
||||||
|
)
|
||||||
|
.fetch_one(&state.database_pool)
|
||||||
|
.map_ok(|row| row.try_into())
|
||||||
|
.await
|
||||||
|
.map_err(|e: sqlx::Error| match e {
|
||||||
|
sqlx::Error::RowNotFound => (
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
ErrorPage::build(&format!("item with id {} not found", id)),
|
||||||
|
),
|
||||||
|
_ => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
),
|
||||||
|
})?
|
||||||
|
.map_err(|e: Error| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ErrorPage::build(&e.to_string()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
StatusCode::OK,
|
||||||
|
Root::build(
|
||||||
|
&components::InventoryItem::build(&state.client_state, &item),
|
||||||
|
&TopLevelPage::Inventory,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
@@ -224,6 +224,79 @@ pub struct TripItem {
|
|||||||
pub new: bool,
|
pub new: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DbTripsItemsRow {
|
||||||
|
picked: bool,
|
||||||
|
packed: bool,
|
||||||
|
new: bool,
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
weight: i64,
|
||||||
|
description: Option<String>,
|
||||||
|
category_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DbTripsItemsRow> for TripItem {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(row: DbTripsItemsRow) -> Result<Self, Self::Error> {
|
||||||
|
Ok(TripItem {
|
||||||
|
picked: row.picked,
|
||||||
|
packed: row.packed,
|
||||||
|
new: row.new,
|
||||||
|
item: Item {
|
||||||
|
id: Uuid::try_parse(&row.id)?,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
weight: row.weight,
|
||||||
|
category_id: Uuid::try_parse(&row.category_id)?,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TripItem {
|
||||||
|
pub async fn find(
|
||||||
|
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||||
|
trip_id: Uuid,
|
||||||
|
item_id: Uuid,
|
||||||
|
) -> Result<Option<Self>, Error> {
|
||||||
|
let item_id_param = item_id.to_string();
|
||||||
|
let trip_id_param = trip_id.to_string();
|
||||||
|
let item: Result<Result<TripItem, Error>, sqlx::Error> = sqlx::query_as!(
|
||||||
|
DbTripsItemsRow,
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
t_item.item_id AS id,
|
||||||
|
t_item.pick AS picked,
|
||||||
|
t_item.pack AS packed,
|
||||||
|
t_item.new AS new,
|
||||||
|
i_item.name AS name,
|
||||||
|
i_item.description AS description,
|
||||||
|
i_item.weight AS weight,
|
||||||
|
i_item.category_id AS category_id
|
||||||
|
FROM trips_items AS t_item
|
||||||
|
INNER JOIN inventory_items AS i_item
|
||||||
|
ON i_item.id = t_item.item_id
|
||||||
|
WHERE t_item.item_id = ?
|
||||||
|
AND t_item.trip_id = ?
|
||||||
|
",
|
||||||
|
item_id_param,
|
||||||
|
trip_id_param,
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.map_ok(|row| row.try_into())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match item {
|
||||||
|
Err(e) => match e {
|
||||||
|
sqlx::Error::RowNotFound => Ok(None),
|
||||||
|
_ => Err(e.into()),
|
||||||
|
},
|
||||||
|
Ok(v) => Ok(Some(v?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DbTripRow {
|
pub struct DbTripRow {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -652,7 +725,13 @@ impl Item {
|
|||||||
let id_param = id.to_string();
|
let id_param = id.to_string();
|
||||||
let item: Result<Result<Item, Error>, sqlx::Error> = sqlx::query_as!(
|
let item: Result<Result<Item, Error>, sqlx::Error> = sqlx::query_as!(
|
||||||
DbInventoryItemsRow,
|
DbInventoryItemsRow,
|
||||||
"SELECT * FROM inventory_items AS item
|
"SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
weight,
|
||||||
|
description,
|
||||||
|
category_id
|
||||||
|
FROM inventory_items AS item
|
||||||
WHERE item.id = ?",
|
WHERE item.id = ?",
|
||||||
id_param,
|
id_param,
|
||||||
)
|
)
|
||||||
@@ -705,6 +784,69 @@ impl Item {
|
|||||||
Ok(v) => Ok(Some(v?)),
|
Ok(v) => Ok(Some(v?)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_category_max_weight(
|
||||||
|
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||||
|
id: Uuid,
|
||||||
|
) -> Result<i64, Error> {
|
||||||
|
let id_param = id.to_string();
|
||||||
|
let weight: Result<i64, sqlx::Error> = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT COALESCE(MAX(i_item.weight), 0) as weight
|
||||||
|
FROM inventory_items_categories as category
|
||||||
|
INNER JOIN inventory_items as i_item
|
||||||
|
ON i_item.category_id = category.id
|
||||||
|
WHERE category_id = (
|
||||||
|
SELECT category_id
|
||||||
|
FROM inventory_items
|
||||||
|
WHERE inventory_items.id = ?
|
||||||
|
)
|
||||||
|
",
|
||||||
|
id_param
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.map_ok(|row| {
|
||||||
|
// convert to i64 because that the default integer type, but looks
|
||||||
|
// like COALESCE return i32?
|
||||||
|
//
|
||||||
|
// We can be certain that the row exists, as we COALESCE it
|
||||||
|
row.weight.unwrap() as i64
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(weight?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn _get_category_total_picked_weight(
|
||||||
|
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||||
|
category_id: Uuid,
|
||||||
|
) -> Result<i64, Error> {
|
||||||
|
let category_id_param = category_id.to_string();
|
||||||
|
let weight: Result<i64, sqlx::Error> = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT COALESCE(SUM(i_item.weight), 0) as weight
|
||||||
|
FROM inventory_items_categories as category
|
||||||
|
INNER JOIN inventory_items as i_item
|
||||||
|
ON i_item.category_id = category.id
|
||||||
|
INNER JOIN trips_items as t_item
|
||||||
|
ON i_item.id = t_item.item_id
|
||||||
|
WHERE category_id = ?
|
||||||
|
AND t_item.pick = 1
|
||||||
|
",
|
||||||
|
category_id_param
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.map_ok(|row| {
|
||||||
|
// convert to i64 because that the default integer type, but looks
|
||||||
|
// like COALESCE return i32?
|
||||||
|
//
|
||||||
|
// We can be certain that the row exists, as we COALESCE it
|
||||||
|
row.weight.unwrap() as i64
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(weight?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DbTripsTypesRow {
|
pub struct DbTripsTypesRow {
|
||||||
@@ -733,3 +875,63 @@ impl TryFrom<DbTripsTypesRow> for TripsType {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Product {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InventoryItem {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub weight: i64,
|
||||||
|
pub category: Category,
|
||||||
|
pub product: Option<Product>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DbInventoryItemRow {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub weight: i64,
|
||||||
|
pub category_id: String,
|
||||||
|
pub category_name: String,
|
||||||
|
pub category_description: Option<String>,
|
||||||
|
pub product_id: Option<String>,
|
||||||
|
pub product_name: Option<String>,
|
||||||
|
pub product_description: Option<String>,
|
||||||
|
pub product_comment: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DbInventoryItemRow> for InventoryItem {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(row: DbInventoryItemRow) -> Result<Self, Self::Error> {
|
||||||
|
Ok(InventoryItem {
|
||||||
|
id: Uuid::try_parse(&row.id)?,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
weight: row.weight,
|
||||||
|
category: Category {
|
||||||
|
id: Uuid::try_parse(&row.category_id)?,
|
||||||
|
name: row.category_name,
|
||||||
|
description: row.category_description,
|
||||||
|
items: None,
|
||||||
|
},
|
||||||
|
product: row
|
||||||
|
.product_id
|
||||||
|
.map(|id| -> Result<Product, Error> {
|
||||||
|
Ok(Product {
|
||||||
|
id: Uuid::try_parse(&id)?,
|
||||||
|
name: row.product_name.unwrap(),
|
||||||
|
description: row.product_description,
|
||||||
|
comment: row.product_comment,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user