more oob experiments
This commit is contained in:
@@ -30,6 +30,7 @@ impl Root {
|
||||
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" {}
|
||||
script { (PreEscaped(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js")))) }
|
||||
meta name="htmx-config" content=r#"{"useTemplateFragments":true}"# {}
|
||||
}
|
||||
body
|
||||
hx-boost="true"
|
||||
|
||||
@@ -745,12 +745,14 @@ impl TripCategoryListRow {
|
||||
pub fn build(
|
||||
category: &TripCategory,
|
||||
active: bool,
|
||||
has_new_items: bool,
|
||||
biggest_category_weight: i64,
|
||||
htmx_swap: bool,
|
||||
) -> Markup {
|
||||
let has_new_items = category.items.as_ref().unwrap().iter().any(|item| item.new);
|
||||
html!(
|
||||
tr
|
||||
id={"category-" (category.category.id)}
|
||||
hx-swap-oob=[htmx_swap.then_some("outerHTML")]
|
||||
."h-10"
|
||||
."hover:bg-purple-100"
|
||||
."m-3"
|
||||
@@ -872,9 +874,8 @@ impl TripCategoryList {
|
||||
}
|
||||
tbody {
|
||||
@for category in trip.categories() {
|
||||
@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);
|
||||
(TripCategoryListRow::build(category, active, has_new_items, biggest_category_weight))
|
||||
(TripCategoryListRow::build(category, active, biggest_category_weight,false))
|
||||
}
|
||||
tr ."h-10" ."hover:bg-purple-200" ."bg-gray-300" ."font-bold" {
|
||||
td ."border" ."p-0" ."m-0" {
|
||||
@@ -922,8 +923,8 @@ impl TripItemList {
|
||||
{
|
||||
thead ."bg-gray-200" {
|
||||
tr ."h-10" {
|
||||
th ."border" ."p-2" { "Take?" }
|
||||
th ."border" ."p-2" { "Packed?" }
|
||||
th ."border" ."p-2" {}
|
||||
th ."border" ."p-2" {}
|
||||
th ."border" ."p-2" ."w-1/2" { "Name" }
|
||||
th ."border" ."p-2" ."w-1/4" { "Weight" }
|
||||
}
|
||||
|
||||
187
rust/src/main.rs
187
rust/src/main.rs
@@ -114,54 +114,53 @@ async fn main() -> Result<(), sqlx::Error> {
|
||||
.route("/favicon.svg", get(icon_handler))
|
||||
.route("/assets/luggage.svg", get(icon_handler))
|
||||
.route("/", get(root))
|
||||
.route("/trips/", get(trips))
|
||||
.route("/trips/types/", get(trips_types).post(trip_type_create))
|
||||
.route(
|
||||
"/trips/types/:id/edit/name/submit",
|
||||
post(trips_types_edit_name),
|
||||
.nest(
|
||||
"/trips/",
|
||||
Router::new()
|
||||
.route("/", get(trips))
|
||||
.route("/trips/types/", get(trips_types).post(trip_type_create))
|
||||
.route("/types/:id/edit/name/submit", post(trips_types_edit_name))
|
||||
.route("/", post(trip_create))
|
||||
.route("/:id/", get(trip))
|
||||
.route("/:id/comment/submit", post(trip_comment_set))
|
||||
.route("/:id/state/:id", post(trip_state_set))
|
||||
.route("/:id/type/:id/add", get(trip_type_add))
|
||||
.route("/:id/type/:id/remove", get(trip_type_remove))
|
||||
.route("/:id/edit/:attribute/submit", post(trip_edit_attribute))
|
||||
.route(
|
||||
"/:id/items/:id/pick",
|
||||
get(trip_item_set_pick).post(trip_item_set_pick_htmx),
|
||||
)
|
||||
.route(
|
||||
"/:id/items/:id/unpick",
|
||||
get(trip_item_set_unpick).post(trip_item_set_unpick_htmx),
|
||||
)
|
||||
.route(
|
||||
"/:id/items/:id/pack",
|
||||
get(trip_item_set_pack).post(trip_item_set_pack_htmx),
|
||||
)
|
||||
.route(
|
||||
"/:id/items/:id/unpack",
|
||||
get(trip_item_set_unpack).post(trip_item_set_unpack_htmx),
|
||||
),
|
||||
)
|
||||
.route("/trips/", post(trip_create))
|
||||
.route("/trips/:id/", get(trip))
|
||||
.route("/trips/:id/comment/submit", post(trip_comment_set))
|
||||
.route("/trips/:id/state/:id", post(trip_state_set))
|
||||
.route("/trips/:id/type/:id/add", get(trip_type_add))
|
||||
.route("/trips/:id/type/:id/remove", get(trip_type_remove))
|
||||
.route(
|
||||
"/trips/:id/edit/:attribute/submit",
|
||||
post(trip_edit_attribute),
|
||||
.nest(
|
||||
"/inventory/",
|
||||
Router::new()
|
||||
.route("/", get(inventory_inactive))
|
||||
.route("/category/", post(inventory_category_create))
|
||||
.route("/item/:id/", get(inventory_item))
|
||||
.route("/item/", post(inventory_item_create))
|
||||
.route("/item/name/validate", post(inventory_item_validate_name))
|
||||
.route("/category/:id/", get(inventory_active))
|
||||
.route("/item/:id/delete", get(inventory_item_delete))
|
||||
.route("/item/:id/edit", post(inventory_item_edit))
|
||||
.route("/item/:id/cancel", get(inventory_item_cancel)), // .route(
|
||||
// "/inventory/category/:id/items",
|
||||
// post(htmx_inventory_category_items),
|
||||
// );
|
||||
)
|
||||
.route(
|
||||
"/trips/:id/items/:id/pick",
|
||||
get(trip_item_set_pick).post(trip_item_set_pick_htmx),
|
||||
)
|
||||
.route(
|
||||
"/trips/:id/items/:id/unpick",
|
||||
get(trip_item_set_unpick).post(trip_item_set_unpick_htmx),
|
||||
)
|
||||
.route(
|
||||
"/trips/:id/items/:id/pack",
|
||||
get(trip_item_set_pack).post(trip_item_set_pack_htmx),
|
||||
)
|
||||
.route(
|
||||
"/trips/:id/items/:id/unpack",
|
||||
get(trip_item_set_unpack).post(trip_item_set_unpack_htmx),
|
||||
)
|
||||
.route("/inventory/", get(inventory_inactive))
|
||||
.route("/inventory/category/", post(inventory_category_create))
|
||||
.route("/inventory/item/:id/", get(inventory_item))
|
||||
.route("/inventory/item/", post(inventory_item_create))
|
||||
.route(
|
||||
"/inventory/item/name/validate",
|
||||
post(inventory_item_validate_name),
|
||||
)
|
||||
.route("/inventory/category/:id/", get(inventory_active))
|
||||
.route("/inventory/item/:id/delete", get(inventory_item_delete))
|
||||
.route("/inventory/item/:id/edit", post(inventory_item_edit))
|
||||
.route("/inventory/item/:id/cancel", get(inventory_item_cancel))
|
||||
// .route(
|
||||
// "/inventory/category/:id/items",
|
||||
// post(htmx_inventory_category_items),
|
||||
// );
|
||||
.fallback(|| async { (StatusCode::NOT_FOUND, "not found") })
|
||||
.with_state(state);
|
||||
|
||||
let args = Args::parse();
|
||||
@@ -996,6 +995,65 @@ async fn trip_item_set_state(
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)),
|
||||
)
|
||||
})?;
|
||||
|
||||
let item_row = 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()),
|
||||
)
|
||||
})?,
|
||||
);
|
||||
|
||||
let category = TripCategory::find(&state.database_pool, trip_id, item.item.category_id)
|
||||
.map_err(|error| {
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
ErrorPage::build(&error.to_string()),
|
||||
)
|
||||
})
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
ErrorPage::build(&format!(
|
||||
"category with id {} not found",
|
||||
item.item.category_id
|
||||
)),
|
||||
)
|
||||
})?;
|
||||
|
||||
let category_row = components::trip::TripCategoryListRow::build(&category, true, 0, true);
|
||||
|
||||
Ok(html!((item_row)(category_row)))
|
||||
}
|
||||
|
||||
async fn trip_item_set_pick(
|
||||
State(state): State<AppState>,
|
||||
Path((trip_id, item_id)): Path<(Uuid, Uuid)>,
|
||||
@@ -1056,43 +1114,6 @@ 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)>,
|
||||
|
||||
@@ -214,6 +214,114 @@ impl TripCategory {
|
||||
.map(|item| item.item.weight)
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub async fn find(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
trip_id: Uuid,
|
||||
category_id: Uuid,
|
||||
) -> Result<Option<TripCategory>, Error> {
|
||||
let mut category: Option<TripCategory> = None;
|
||||
|
||||
let trip_id_param = trip_id.to_string();
|
||||
let category_id_param = category_id.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
SELECT
|
||||
category.id as category_id,
|
||||
category.name as category_name,
|
||||
category.description AS category_description,
|
||||
inner.trip_id AS trip_id,
|
||||
inner.item_id AS item_id,
|
||||
inner.item_name AS item_name,
|
||||
inner.item_description AS item_description,
|
||||
inner.item_weight AS item_weight,
|
||||
inner.item_is_picked AS item_is_picked,
|
||||
inner.item_is_packed AS item_is_packed,
|
||||
inner.item_is_new AS item_is_new
|
||||
FROM inventory_items_categories AS category
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
trip.trip_id AS trip_id,
|
||||
category.id as category_id,
|
||||
category.name as category_name,
|
||||
category.description as category_description,
|
||||
item.id as item_id,
|
||||
item.name as item_name,
|
||||
item.description as item_description,
|
||||
item.weight as item_weight,
|
||||
trip.pick as item_is_picked,
|
||||
trip.pack as item_is_packed,
|
||||
trip.new as item_is_new
|
||||
FROM trips_items as trip
|
||||
INNER JOIN inventory_items as item
|
||||
ON item.id = trip.item_id
|
||||
INNER JOIN inventory_items_categories as category
|
||||
ON category.id = item.category_id
|
||||
WHERE trip.trip_id = ?
|
||||
) AS inner
|
||||
ON inner.category_id = category.id
|
||||
WHERE category.id = ?
|
||||
",
|
||||
trip_id_param,
|
||||
category_id_param
|
||||
)
|
||||
.fetch(pool)
|
||||
.map_ok(|row| -> Result<(), Error> {
|
||||
match &category {
|
||||
Some(_) => (),
|
||||
None => {
|
||||
category = Some(TripCategory {
|
||||
category: Category {
|
||||
id: Uuid::try_parse(&row.category_id)?,
|
||||
name: row.category_name,
|
||||
description: row.category_description,
|
||||
items: None,
|
||||
},
|
||||
items: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
match row.item_id {
|
||||
None => {
|
||||
// we have an empty (unused) category which has NULL values
|
||||
// for the item_id column
|
||||
category.as_mut().unwrap().items = Some(vec![]);
|
||||
category.as_mut().unwrap().category.items = Some(vec![]);
|
||||
}
|
||||
Some(item_id) => {
|
||||
let item = TripItem {
|
||||
item: Item {
|
||||
id: Uuid::try_parse(&item_id)?,
|
||||
name: row.item_name.unwrap(),
|
||||
description: row.item_description,
|
||||
weight: row.item_weight.unwrap(),
|
||||
category_id: category.as_ref().unwrap().category.id,
|
||||
},
|
||||
picked: row.item_is_picked.unwrap(),
|
||||
packed: row.item_is_packed.unwrap(),
|
||||
new: row.item_is_new.unwrap(),
|
||||
};
|
||||
|
||||
match &mut category.as_mut().unwrap().items {
|
||||
None => category.as_mut().unwrap().items = Some(vec![item]),
|
||||
Some(ref mut items) => items.push(item),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.try_collect::<Vec<Result<(), Error>>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect::<Result<(), Error>>()?;
|
||||
|
||||
// this may be None if there are no results (which
|
||||
// means that the category was not found)
|
||||
Ok(category)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -658,6 +766,34 @@ pub struct DbInventoryItemsRow {
|
||||
}
|
||||
|
||||
impl<'a> Category {
|
||||
// pub async fn find(
|
||||
// pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
// id: Uuid,
|
||||
// ) -> Result<Option<Category>, Error> {
|
||||
// let id_param = id.to_string();
|
||||
// let item: Result<Result<Category, Error>, sqlx::Error> = sqlx::query_as!(
|
||||
// DbCategoryRow,
|
||||
// "SELECT
|
||||
// id,
|
||||
// name,
|
||||
// description
|
||||
// FROM inventory_items_categories AS category
|
||||
// WHERE category.id = ?",
|
||||
// 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 fn items(&'a self) -> &'a Vec<Item> {
|
||||
self.items
|
||||
.as_ref()
|
||||
@@ -787,22 +923,18 @@ impl Item {
|
||||
|
||||
pub async fn get_category_max_weight(
|
||||
pool: &sqlx::Pool<sqlx::Sqlite>,
|
||||
id: Uuid,
|
||||
category_id: Uuid,
|
||||
) -> Result<i64, Error> {
|
||||
let id_param = id.to_string();
|
||||
let category_id_param = category_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 = ?
|
||||
)
|
||||
WHERE category_id = ?
|
||||
",
|
||||
id_param
|
||||
category_id_param
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.map_ok(|row| {
|
||||
|
||||
Reference in New Issue
Block a user