This commit is contained in:
2023-08-29 21:34:00 +02:00
parent 71c2611d63
commit 20c52b8b7d
8 changed files with 667 additions and 182 deletions

View File

@@ -9,3 +9,11 @@ window.onload = function() {
function is_positive_integer(val) { function is_positive_integer(val) {
return /^\d+$/.test(val); return /^\d+$/.test(val);
} }
function inventory_new_item_check_input() {
return document.getElementById('new-item-name').value.length != 0
&& is_positive_integer(document.getElementById('new-item-weight').value)
}
function check_weight() {
return document.getElementById('new-item-weight').validity.valid;
}

View File

@@ -63,22 +63,44 @@
/* ON inner.category_id = category.id */ /* ON inner.category_id = category.id */
/* WHERE category.id = '1293c6b6-eef5-4269-bf10-a1ac20549dac' */ /* WHERE category.id = '1293c6b6-eef5-4269-bf10-a1ac20549dac' */
SELECT /* SELECT */
trip.id AS id, /* trip.id AS id, */
trip.name AS name, /* trip.name AS name, */
CAST (date_start AS TEXT) date_start, /* CAST (date_start AS TEXT) date_start, */
CAST (date_end AS TEXT) date_end, /* CAST (date_end AS TEXT) date_end, */
state, /* state, */
location, /* location, */
temp_min, /* temp_min, */
temp_max, /* temp_max, */
comment, /* comment, */
SUM(i_item.weight) AS total_weight /* SUM(i_item.weight) AS total_weight */
FROM trips AS trip /* FROM trips AS trip */
INNER JOIN trips_items AS t_item /* INNER JOIN trips_items AS t_item */
ON t_item.trip_id = trip.id /* ON t_item.trip_id = trip.id */
INNER JOIN inventory_items AS i_item /* INNER JOIN inventory_items AS i_item */
ON t_item.item_id = i_item.id /* ON t_item.item_id = i_item.id */
WHERE /* WHERE */
trip.id = '0535193c-7b47-4ba4-bca5-40e54c15c2d0' /* trip.id = '0535193c-7b47-4ba4-bca5-40e54c15c2d0' */
AND t_item.pick = true /* AND t_item.pick = true */
/* UPDATE trips_items */
/* SET pick = true */
/* WHERE trip_id = '0535193c-7b47-4ba4-bca5-40e54c15c2d0' */
/* AND item_id = '2418ab2d-4e11-4a68-8761-265d442742f6' */
/* RETURNING */
/* trips_items.item_id */
SELECT *
FROM inventory_items
WHERE id = (
UPDATE trips_items
SET pick = true
WHERE trip_id = '0535193c-7b47-4ba4-bca5-40e54c15c2d0'
AND item_id = '2418ab2d-4e11-4a68-8761-265d442742f6'
RETURNING
trips_items.item_id
/* SELECT trips_items.item_id */
/* FROM trips_items */
/* WHERE trip_id = '0535193c-7b47-4ba4-bca5-40e54c15c2d0' */
/* AND item_id = '2418ab2d-4e11-4a68-8761-265d442742f6' */
)

View File

@@ -5,13 +5,327 @@ pub struct Home;
impl Home { impl Home {
pub fn build() -> Markup { pub fn build() -> Markup {
html!( html!(
div id="home" class={"p-8" "max-w-xl"} { div
p { id="home"
a href="/inventory/" { "Inventory" } hx-boost="true"
."p-8"
."flex"
."flex-col"
."gap-8"
."flex-nowrap"
{
h1
."text-2xl"
."m-auto"
."my-4"
{
"Welcome!"
} }
p { section
a href="/trips/" { "Trips" } ."border-2"
."border-gray-200"
."rounded-md"
."flex"
."flex-row"
{
a
href="/inventory/"
hx-boost="true"
."p-8"
."w-1/5"
."flex"
."hover:bg-gray-200"
{
span
."m-auto"
."text-xl"
{ "Inventory" }
}
div
."p-8"
."w-4/5"
."flex"
."flex-col"
."gap-3"
{
p {
"The inventory contains all the items that you own."
}
p {
"It is effectively a list of items, sectioned into
arbitrary categories"
}
p {
"Each item has some important data attached to it,
like its weight"
}
}
} }
section
."border-2"
."border-gray-200"
."rounded-md"
."flex"
."flex-row"
{
a
href="/trips/"
hx-boost="true"
."p-8"
."w-1/5"
."flex"
."hover:bg-gray-200"
{
span
."m-auto"
."text-xl"
{ "Trips" }
}
div
."p-8"
."w-4/5"
."flex"
."flex-col"
."gap-6"
{
div
."flex"
."flex-col"
."gap-3"
{
p {
"Trips is where it gets interesting, as you can put
your inventory to good use"
}
p {
r#"With trips, you record any trips you plan to do. A
"trip" can be anything you want it to be. Anything
from a multi-week hike, a high altitude mountaineering
tour or just a visit to the library. Whenever it makes
sense to do some planning, creating a trip makes sense."#
}
p {
"Each trip has some metadata attached to it, like start-
and end dates or the expected temperature."
}
}
div
."flex"
."flex-col"
."gap-3"
{
div
."flex"
."flex-row"
."gap-2"
."items-center"
."justify-start"
{
span
."mdi"
."mdi-pound"
."text-lg"
."text-gray-300"
{}
h3 ."text-lg" {
"States"
}
}
p {
"One of the most important parts of each trip is
its " em{"state"} ", which determines certain
actions on the trip and can have the following values:"
}
table
."table"
."table-auto"
."border-collapse"
{
tr
."border-b-2"
."last:border-b-0"
{
td ."py-2" ."pr-4" ."border-r-2" {
"Init"
}
td ."py-2" ."w-full" ."pl-4" {
"The new trip was just created"
}
}
tr
."border-b-2"
."last:border-b-0"
{
td ."py-2" ."pr-4" ."border-r-2" {
"Planning"
}
td ."py-2" ."w-full" ."pl-4" {
"Now, you actually start planning the trip.
Setting the location, going through your
items to decide what to take with you."
}
}
tr
."border-b-2"
."last:border-b-0"
{
td ."py-2" ."pr-4" ."border-r-2" {
"Planned"
}
td ."py-2" ."w-full" ."pl-4" {
"You are done with the planning. It's time
to pack up your stuff and get going."
}
}
tr
."border-b-2"
."last:border-b-0"
{
td ."py-2" ."pr-4" ."border-r-2" {
"Active"
}
td ."py-2" ."w-full" ."pl-4" {
"The trip is finally underway!"
}
}
tr
."border-b-2"
."last:border-b-0"
{
td ."py-2" ."pr-4" ."border-r-2" {
"Review"
}
td ."py-2" ."w-full" ."pl-4" {
div
."flex"
."flex-col"
."gap-2"
{
p {
"You returned from your trip. It may make
sense to take a look back and see what
went well and what went not so well."
}
p {
"Anything you missed? Any items that you
took with you that turned out to be useless?
Record it and you will remember on your next
trip"
}
}
}
}
tr
."border-b-2"
."last:border-b-0"
{
td ."py-2" ."pr-4" ."border-r-2" {
"Done"
}
td ."py-2" ."w-full" ."pl-4" {
"Your review is done and the trip can be laid to rest"
}
}
}
}
div
."flex"
."flex-col"
."gap-3"
{
div
."flex"
."flex-row"
."gap-2"
."items-center"
."justify-start"
{
span
."mdi"
."mdi-pound"
."text-lg"
."text-gray-300"
{}
h3 ."text-lg" {
"Items"
}
}
p {
"Of course, you can use items defined in your
inventory in your trips"
}
p {
"Generally, all items are available to you in
the same way as the inventory. For each item,
there are two specific states for the trip: An
item can be " em{"picked"} ", which means that
you plan to take it on the trip, and it can
be " em{"packed"} ", which means that you actually
packed it into your bag (and therefore, you cannot
forget it any more)"
}
}
div
."flex"
."flex-col"
."gap-3"
{
div
."flex"
."flex-row"
."gap-2"
."items-center"
."justify-start"
{
span
."mdi"
."mdi-pound"
."text-lg"
."text-gray-300"
{}
h3 ."text-lg" {
"Types & Presets"
}
}
p {
"Often, you will take a certain set of items to
certain trips. Whenever you plan to sleep outdoors,
it makes sense to take your sleeping bag and mat
with you"
}
p {
"To reflect this, you can attach " em {"types"} " "
"to your trips. Types define arbitrary characteristics
about a trip and reference a certain set of items."
}
p {
"Here are some examples of types that might make sense:"
}
ul
."list-disc"
."list-inside"
{
li {
r#""Biking": Make sure to pack your helmet and
some repair tools"#
}
li {
r#""Climbing": You certainly don't want to forget
your climbing shoes"#
}
li {
r#""Rainy": Pack a rain jacket and some waterproof
shoes"#
}
}
p {
"Types are super flexible, it's up to you how to use
them"
}
}
}
}
} }
) )
} }

View File

@@ -89,7 +89,7 @@ impl InventoryCategoryList {
id="select-category" id="select-category"
href={ href={
"/inventory/category/" "/inventory/category/"
(category.id) (category.id) "/"
} }
hx-post={ hx-post={
"/inventory/categories/" "/inventory/categories/"
@@ -152,6 +152,7 @@ impl InventoryItemList {
name="edit-item" name="edit-item"
id="edit-item" id="edit-item"
action={"/inventory/item/" (edit_item_id) "/edit"} action={"/inventory/item/" (edit_item_id) "/edit"}
hx-boost="true"
target="_self" target="_self"
method="post" method="post"
{} {}
@@ -230,12 +231,13 @@ impl InventoryItemList {
."h-full" ."h-full"
{ {
a a
href=(format!("/inventory/item/{id}/cancel", id = item.id))
hx-boost="true"
."aspect-square" ."aspect-square"
."flex" ."flex"
."w-full" ."w-full"
."h-full" ."h-full"
."p-0" ."p-0"
href=(format!("/inventory/item/{id}/cancel", id = item.id))
{ {
span span
."m-auto" ."m-auto"
@@ -253,10 +255,11 @@ impl InventoryItemList {
."p-2" ."w-full" ."inline-block" ."p-2" ."w-full" ."inline-block"
href=( href=(
format!("/inventory/item/{id}/", id=item.id) format!("/inventory/item/{id}/", id=item.id)
) { )
hx-boost="true"
(item.name.clone()) {
} (item.name.clone())
}
} }
td ."border" ."p-2" style="position:relative;" { td ."border" ."p-2" style="position:relative;" {
p { (item.weight.to_string()) } p { (item.weight.to_string()) }
@@ -276,10 +279,11 @@ impl InventoryItemList {
."h-full" ."h-full"
{ {
a a
href=(format!("?edit_item={id}", id = item.id))
hx-boost="true"
."aspect-square" ."aspect-square"
."flex" ."flex"
."w-full" ."w-full"
href=(format!("?edit_item={id}", id = item.id))
{ {
span ."m-auto" ."mdi" ."mdi-pencil" ."text-xl" {} span ."m-auto" ."mdi" ."mdi-pencil" ."text-xl" {}
} }
@@ -293,10 +297,11 @@ impl InventoryItemList {
."h-full" ."h-full"
{ {
a a
href=(format!("/inventory/item/{id}/delete", id = item.id))
hx-boost="true"
."aspect-square" ."aspect-square"
."flex" ."flex"
."w-full" ."w-full"
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" {}
} }
@@ -317,17 +322,6 @@ pub struct InventoryNewItemFormName;
impl InventoryNewItemFormName { impl InventoryNewItemFormName {
pub fn build(value: Option<&str>, error: bool) -> Markup { pub fn build(value: Option<&str>, error: bool) -> Markup {
html!( html!(
script {
(PreEscaped("
function inventory_new_item_check_input() {
return document.getElementById('new-item-name').value.length != 0
&& is_positive_integer(document.getElementById('new-item-weight').value)
}
function check_weight() {
return document.getElementById('new-item-weight').validity.valid;
}
"))
}
div div
."grid" ."grid"
."grid-cols-[2fr,3fr]" ."grid-cols-[2fr,3fr]"
@@ -376,17 +370,6 @@ pub struct InventoryNewItemFormWeight;
impl InventoryNewItemFormWeight { impl InventoryNewItemFormWeight {
pub fn build() -> Markup { pub fn build() -> Markup {
html!( html!(
script {
(PreEscaped("
function inventory_new_item_check_input() {
return document.getElementById('new-item-name').value.length != 0
&& is_positive_integer(document.getElementById('new-item-weight').value)
}
function check_weight() {
return document.getElementById('new-item-weight').validity.valid;
}
"))
}
div div
."grid" ."grid"
."grid-cols-[2fr,3fr]" ."grid-cols-[2fr,3fr]"
@@ -468,17 +451,6 @@ pub struct InventoryNewItemForm;
impl InventoryNewItemForm { impl InventoryNewItemForm {
pub fn build(active_category: Option<&Category>, categories: &Vec<Category>) -> Markup { pub fn build(active_category: Option<&Category>, categories: &Vec<Category>) -> Markup {
html!( html!(
script {
(PreEscaped("
function inventory_new_item_check_input() {
return document.getElementById('new-item-name').value.length != 0
&& is_positive_integer(document.getElementById('new-item-weight').value)
}
function check_weight() {
return document.getElementById('new-item-weight').validity.valid;
}
"))
}
form form
x-data="{ x-data="{
save_active: inventory_new_item_check_input(), save_active: inventory_new_item_check_input(),

View File

@@ -10,6 +10,7 @@ pub use trip::*;
pub struct Root; pub struct Root;
#[derive(PartialEq, Eq)]
pub enum TopLevelPage { pub enum TopLevelPage {
Inventory, Inventory,
Trips, Trips,
@@ -36,25 +37,30 @@ impl Root {
{ {
header header
#header #header
."h-full" ."h-16"
."bg-gray-200" ."bg-gray-200"
."p-5"
."flex" ."flex"
."flex-row" ."flex-row"
."flex-nowrap" ."flex-nowrap"
."justify-between" ."justify-between"
."items-center" ."items-stretch"
{ {
span a
."text-xl" #home
."font-semibold" hx-boost="true"
href="/"
."flex" ."flex"
."flex-row" ."flex-row"
."items-center" ."items-center"
."gap-3" ."gap-3"
."px-5"
."hover:bg-gray-300"
{ {
img ."h-12" src="/assets/luggage.svg" {} img ."h-12" src="/assets/luggage.svg" {}
a #home href="/" { "Packager" } span
."text-xl"
."font-semibold"
{ "Packager" }
} }
nav nav
."grow" ."grow"
@@ -62,22 +68,44 @@ impl Root {
."flex-row" ."flex-row"
."justify-center" ."justify-center"
."gap-x-10" ."gap-x-10"
."content-stretch" ."items-stretch"
{ {
a href="/inventory/" a
href="/inventory/"
hx-boost="true"
#header-link-inventory #header-link-inventory
."px-5"
."flex"
."h-full" ."h-full"
."text-lg" ."text-lg"
."font-bold"[matches!(active_page, TopLevelPage::Inventory)] ."hover:bg-gray-300"
."underline"[matches!(active_page, TopLevelPage::Inventory)]
{ "Inventory" } // invisible top border to fix alignment
a href="/trips/" ."border-t-gray-200"[active_page == &TopLevelPage::Inventory]
."hover:border-t-gray-300"[active_page == &TopLevelPage::Inventory]
."border-b-gray-500"[active_page == &TopLevelPage::Inventory]
."border-y-4"[active_page == &TopLevelPage::Inventory]
."font-bold"[active_page == &TopLevelPage::Inventory]
{ span ."m-auto" ."font-semibold" { "Inventory" }}
a
href="/trips/"
hx-boost="true"
#header-link-trips #header-link-trips
."px-5"
."flex"
."h-full" ."h-full"
."text-lg" ."text-lg"
."font-bold"[matches!(active_page, TopLevelPage::Trips)] ."hover:bg-gray-300"
."underline"[matches!(active_page, TopLevelPage::Trips)]
{ "Trips" } // invisible top border to fix alignment
."border-t-gray-200"[active_page == &TopLevelPage::Trips]
."hover:border-t-gray-300"[active_page == &TopLevelPage::Trips]
."border-gray-500"[active_page == &TopLevelPage::Trips]
."border-y-4"[active_page == &TopLevelPage::Trips]
."font-bold"[active_page == &TopLevelPage::Trips]
{ span ."m-auto" ."font-semibold" { "Trips" }}
} }
} }
(body) (body)

View File

@@ -9,7 +9,9 @@ use serde_variant::to_variant_name;
use crate::ClientState; use crate::ClientState;
pub struct TripManager; pub struct TripManager;
pub mod packagelist;
pub mod types; pub mod types;
pub use types::*; pub use types::*;
impl TripManager { impl TripManager {
@@ -105,8 +107,14 @@ impl TripTableRow {
pub fn build(trip_id: Uuid, value: impl maud::Render) -> Markup { pub fn build(trip_id: Uuid, value: impl maud::Render) -> Markup {
html!( html!(
td ."border" ."p-0" ."m-0" { td ."border" ."p-0" ."m-0" {
a ."inline-block" ."p-2" ."m-0" ."w-full" a
href=(format!("/trips/{id}/", id=trip_id)) href={"/trips/" (trip_id) "/"}
hx-boost="true"
."inline-block"
."p-2"
."m-0"
."w-full"
{ (value) } { (value) }
} }
) )
@@ -123,6 +131,7 @@ impl NewTrip {
action="/trips/" action="/trips/"
target="_self" target="_self"
method="post" method="post"
hx-boost="true"
."p-5" ."border-2" ."border-gray-200" ."p-5" ."border-2" ."border-gray-200"
{ {
div ."mb-5" ."flex" ."flex-row" ."trips-center" { div ."mb-5" ."flex" ."flex-row" ."trips-center" {
@@ -225,93 +234,116 @@ impl Trip {
div div
."flex" ."flex"
."flex-row" ."flex-row"
."items-stretch" ."justify-between"
."gap-x-5"
{ {
a div
href="/trips/"
."text-sm"
."text-gray-500"
."flex" ."flex"
."flex-row"
."items-stretch"
."gap-x-5"
{ {
div a
."m-auto" href="/trips/"
hx-boost="true"
."text-sm"
."text-gray-500"
."flex"
{ {
span div
."mdi" ."m-auto"
."mdi-arrow-left" {
{} span
"back" ."mdi"
."mdi-arrow-left"
{}
"back"
}
}
div ."flex" ."flex-row" ."items-center" ."gap-x-3" {
@if trip_edit_attribute.as_ref().map_or(false, |a| *a == TripAttribute::Name) {
form
id="edit-trip"
action=(format!("edit/{}/submit", to_variant_name(&TripAttribute::Name).unwrap()))
hx-boost="true"
target="_self"
method="post"
{
div
."flex"
."flex-row"
."items-center"
."gap-x-3"
."items-stretch"
{
input
."bg-blue-200"
."w-full"
."text-2xl"
."font-semibold"
type=(<InputType as Into<&'static str>>::into(InputType::Text))
name="new-value"
form="edit-trip"
value=(trip.name)
{}
a
href="."
hx-boost="true"
."bg-red-200"
."hover:bg-red-300"
."w-8"
."flex"
{
span
."mdi"
."mdi-cancel"
."text-xl"
."m-auto"
{}
}
button
type="submit"
form="edit-trip"
."bg-green-200"
."hover:bg-green-300"
."w-8"
{
span
."mdi"
."mdi-content-save"
."text-xl"
{}
}
}
}
} @else {
h1 ."text-2xl" { (trip.name) }
span {
a
href={"?edit=" (to_variant_name(&TripAttribute::Name).unwrap())}
hx-boost="true"
{
span
."mdi"
."mdi-pencil"
."text-xl"
."opacity-50"
{}
}
}
}
} }
} }
div ."flex" ."flex-row" ."items-center" ."gap-x-3" { a
@if trip_edit_attribute.as_ref().map_or(false, |a| *a == TripAttribute::Name) { href={"/trips/" (trip.id) "/packagelist/"}
form hx-boost="true"
id="edit-trip" ."p-2"
action=(format!("edit/{}/submit", to_variant_name(&TripAttribute::Name).unwrap())) ."border-2"
target="_self" ."border-gray-500"
method="post" ."rounded-md"
{ ."bg-blue-200"
div ."hover:bg-blue-200"
."flex" {
."flex-row" "Show Package List"
."items-center"
."gap-x-3"
."items-stretch"
{
input
."bg-blue-200"
."w-full"
."text-2xl"
."font-semibold"
type=(<InputType as Into<&'static str>>::into(InputType::Text))
name="new-value"
form="edit-trip"
value=(trip.name)
{}
a
href="."
."bg-red-200"
."hover:bg-red-300"
."w-8"
."flex"
{
span
."mdi"
."mdi-cancel"
."text-xl"
."m-auto"
{}
}
button
type="submit"
form="edit-trip"
."bg-green-200"
."hover:bg-green-300"
."w-8"
{
span
."mdi"
."mdi-content-save"
."text-xl"
{}
}
}
}
} @else {
h1 ."text-2xl" { (trip.name) }
span {
a href=(format!("?edit={}", to_variant_name(&TripAttribute::Name).unwrap()))
{
span
."mdi"
."mdi-pencil"
."text-xl"
."opacity-50"
{}
}
}
}
} }
} }
(TripInfo::build(trip_edit_attribute, trip)) (TripInfo::build(trip_edit_attribute, trip))
@@ -339,6 +371,7 @@ 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()) ))
hx-boost="true"
target="_self" target="_self"
method="post" method="post"
{} {}
@@ -377,11 +410,12 @@ impl TripInfoRow {
."h-full" ."h-full"
{ {
a a
href="." // strips query parameters
hx-boost="true"
."flex" ."flex"
."w-full" ."w-full"
."h-full" ."h-full"
."p-0" ."p-0"
href="." // strips query parameters
{ {
span span
."m-auto" ."m-auto"
@@ -426,10 +460,11 @@ impl TripInfoRow {
."h-full" ."h-full"
{ {
a a
.flex href={ "?edit=" (to_variant_name(&attribute_key).unwrap()) }
hx-boost="true"
."flex"
."w-full" ."w-full"
."h-full" ."h-full"
href={ "?edit=" (to_variant_name(&attribute_key).unwrap()) }
{ {
span span
."m-auto" ."m-auto"
@@ -649,7 +684,10 @@ impl TripInfo {
."justify-start" ."justify-start"
{ {
@for triptype in active_triptypes { @for triptype in active_triptypes {
a href=(format!("type/{}/remove", triptype.id)) { a
href={"type/" (triptype.id) "/remove"}
hx-boost="true"
{
li li
."border" ."border"
."rounded-2xl" ."rounded-2xl"
@@ -679,7 +717,10 @@ impl TripInfo {
."justify-start" ."justify-start"
{ {
@for triptype in inactive_triptypes { @for triptype in inactive_triptypes {
a href=(format!("type/{}/add", triptype.id)) { a
href={"type/" (triptype.id) "/add"}
hx-boost="true"
{
li li
."border" ."border"
."rounded-2xl" ."rounded-2xl"
@@ -703,7 +744,9 @@ impl TripInfo {
} }
} }
} }
a href="/trips/types/" a
href="/trips/types/"
hx-boost="true"
."text-sm" ."text-sm"
."text-gray-500" ."text-gray-500"
."mr-2" ."mr-2"
@@ -742,6 +785,7 @@ impl TripComment {
form form
id="edit-comment" id="edit-comment"
action="comment/submit" action="comment/submit"
hx-boost="true"
target="_self" target="_self"
method="post" method="post"
{} {}
@@ -1118,6 +1162,7 @@ impl TripItemListRow {
href=( href=(
format!("/inventory/item/{id}/", id=item.item.id) format!("/inventory/item/{id}/", id=item.item.id)
) )
hx-boost="true"
{ {
(item.item.name.clone()) (item.item.name.clone())
} }

View File

@@ -32,6 +32,7 @@ impl TypeList {
."hidden" ."hidden"
id="edit-trip-type" id="edit-trip-type"
action={ (trip_type.id) "/edit/name/submit" } action={ (trip_type.id) "/edit/name/submit" }
hx-boost="true"
target="_self" target="_self"
method="post" method="post"
{} {}
@@ -56,6 +57,7 @@ impl TypeList {
{ {
a a
href="." href="."
hx-boost="true"
."bg-red-200" ."bg-red-200"
."hover:bg-red-300" ."hover:bg-red-300"
."w-8" ."w-8"
@@ -96,10 +98,11 @@ impl TypeList {
."w-8" ."w-8"
{ {
a a
href={ "?edit=" (trip_type.id) }
hx-boost="true"
.flex .flex
."w-full" ."w-full"
."h-full" ."h-full"
href={ "?edit=" (trip_type.id) }
{ {
span span
."m-auto" ."m-auto"
@@ -119,6 +122,7 @@ impl TypeList {
action="/trips/types/" action="/trips/types/"
target="_self" target="_self"
method="post" method="post"
hx-boost="true"
."mt-8" ."p-5" ."border-2" ."border-gray-200" ."mt-8" ."p-5" ."border-2" ."border-gray-200"
{ {
div ."mb-5" ."flex" ."flex-row" { div ."mb-5" ."flex" ."flex-row" {

View File

@@ -166,13 +166,21 @@ async fn main() -> Result<(), sqlx::Error> {
.nest( .nest(
"/trips/", "/trips/",
Router::new() Router::new()
.route("/", get(trips)) .route("/", get(trips).post(trip_create))
.route("/types/", get(trips_types).post(trip_type_create)) .route("/types/", get(trips_types).post(trip_type_create))
.route("/types/:id/edit/name/submit", post(trips_types_edit_name)) .route("/types/:id/edit/name/submit", post(trips_types_edit_name))
.route("/", post(trip_create))
.route("/:id/", get(trip)) .route("/:id/", get(trip))
.route("/:id/comment/submit", post(trip_comment_set)) .route("/:id/comment/submit", post(trip_comment_set))
.route("/:id/categories/:id/select", post(trip_category_select)) .route("/:id/categories/:id/select", post(trip_category_select))
.route("/:id/packagelist/", get(trip_packagelist))
.route(
"/:id/packagelist/item/:id/pack",
post(trip_item_packagelist_set_pack_htmx),
)
.route(
"/:id/packagelist/item/:id/unpack",
post(trip_item_packagelist_set_unpack_htmx),
)
.route("/:id/state/:id", post(trip_state_set)) .route("/:id/state/:id", post(trip_state_set))
.route("/:id/total_weight", get(trip_total_weight_htmx)) .route("/:id/total_weight", get(trip_total_weight_htmx))
.route("/:id/type/:id/add", get(trip_type_add)) .route("/:id/type/:id/add", get(trip_type_add))
@@ -199,18 +207,15 @@ async fn main() -> Result<(), sqlx::Error> {
"/inventory/", "/inventory/",
Router::new() Router::new()
.route("/", get(inventory_inactive)) .route("/", get(inventory_inactive))
.route("/category/", post(inventory_category_create))
.route("/item/:id/", get(inventory_item))
.route("/categories/:id/select", post(inventory_category_select)) .route("/categories/:id/select", post(inventory_category_select))
.route("/item/", post(inventory_item_create)) .route("/category/", post(inventory_category_create))
.route("/item/name/validate", post(inventory_item_validate_name))
.route("/category/:id/", get(inventory_active)) .route("/category/:id/", get(inventory_active))
.route("/item/", post(inventory_item_create))
.route("/item/:id/", get(inventory_item))
.route("/item/:id/cancel", get(inventory_item_cancel))
.route("/item/:id/delete", get(inventory_item_delete)) .route("/item/:id/delete", get(inventory_item_delete))
.route("/item/:id/edit", post(inventory_item_edit)) .route("/item/:id/edit", post(inventory_item_edit))
.route("/item/:id/cancel", get(inventory_item_cancel)), // .route( .route("/item/name/validate", post(inventory_item_validate_name)),
// "/inventory/category/:id/items",
// post(htmx_inventory_category_items),
// );
) )
.fallback(|| async { (StatusCode::NOT_FOUND, "not found") }) .fallback(|| async { (StatusCode::NOT_FOUND, "not found") })
.with_state(state); .with_state(state);
@@ -1777,7 +1782,7 @@ async fn inventory_category_select(
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert::<HeaderName>( headers.insert::<HeaderName>(
HtmxResponseHeaders::PushUrl.into(), HtmxResponseHeaders::PushUrl.into(),
format!("/inventory/category/{category_id}") format!("/inventory/category/{category_id}/")
.parse() .parse()
.unwrap(), .unwrap(),
); );
@@ -1792,3 +1797,90 @@ async fn inventory_category_select(
), ),
)) ))
} }
async fn trip_packagelist(
State(state): State<AppState>,
Path(trip_id): Path<Uuid>,
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
let mut trip = models::Trip::find(&state.database_pool, trip_id)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
)
})?
.ok_or((
StatusCode::NOT_FOUND,
ErrorPage::build(&format!("trip with id {trip_id} not found")),
))?;
trip.load_categories(&state.database_pool)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&e.to_string()),
)
})?;
Ok((
StatusCode::OK,
Root::build(
&components::packagelist::TripPackageList::build(&trip),
&TopLevelPage::None,
),
))
}
async fn trip_item_packagelist_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?;
let item = models::TripItem::find(&state.database_pool, trip_id, item_id)
.await
.map_err(|error| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&error.to_string()),
)
})?
.ok_or((
StatusCode::NOT_FOUND,
ErrorPage::build(&format!("an item with id {item_id} does not exist")),
))?;
Ok((
StatusCode::OK,
components::packagelist::TripPackageListRow::build(trip_id, &item),
))
}
async fn trip_item_packagelist_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?;
// note that this cannot fail due to a missing item, as trip_item_set_state would already
// return 404. but error handling cannot hurt ;)
let item = models::TripItem::find(&state.database_pool, trip_id, item_id)
.await
.map_err(|error| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ErrorPage::build(&error.to_string()),
)
})?
.ok_or((
StatusCode::NOT_FOUND,
ErrorPage::build(&format!("an item with id {item_id} does not exist")),
))?;
Ok((
StatusCode::OK,
components::packagelist::TripPackageListRow::build(trip_id, &item),
))
}