more oob experiments

This commit is contained in:
2023-08-29 21:34:00 +02:00
parent 58bf5bd044
commit c1f16ce035
5 changed files with 377 additions and 114 deletions

View File

@@ -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"

View File

@@ -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" }
}

View File

@@ -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)>,

View File

@@ -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| {