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

@@ -162,6 +162,24 @@
},
"query": "SELECT\n id,\n name,\n weight,\n description,\n category_id\n FROM inventory_items AS item\n WHERE item.id = ?"
},
"5fd2b986fcaafe93e816f9ed665b6319d120a3987dc5adca1e3c8634203f7533": {
"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 "
},
"68304c19a0bee12c0b3ce9740d53389620b20e47973b41975678dbd13bd30c7f": {
"describe": {
"columns": [],
@@ -386,24 +404,6 @@
},
"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": {
"describe": {
"columns": [
@@ -444,6 +444,114 @@
},
"query": "DELETE FROM inventory_items\n WHERE id = ?"
},
"cc1ad49669cff7f89975abfab3d0a8caef2e3978c826e1877db91c05a7f9d00d": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "description",
"ordinal": 2,
"type_info": "Text"
}
],
"nullable": [
false,
false,
true
],
"parameters": {
"Right": 1
}
},
"query": "SELECT\n id,\n name,\n description\n FROM inventory_items_categories AS category\n WHERE category.id = ?"
},
"d0562ad92782a6ad6080c0535749c4a0a28fa78a17698933bce670db057e2628": {
"describe": {
"columns": [
{
"name": "category_id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "category_name",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "category_description",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "trip_id",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "item_id",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "item_name",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "item_description",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "item_weight",
"ordinal": 7,
"type_info": "Int64"
},
{
"name": "item_is_picked",
"ordinal": 8,
"type_info": "Bool"
},
{
"name": "item_is_packed",
"ordinal": 9,
"type_info": "Bool"
},
{
"name": "item_is_new",
"ordinal": 10,
"type_info": "Bool"
}
],
"nullable": [
false,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true
],
"parameters": {
"Right": 2
}
},
"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 WHERE category.id = ?\n "
},
"ded3be1c8894a64e3b5f749461db7261d9224abb8a54da980db8262733d08205": {
"describe": {
"columns": [],

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