This commit is contained in:
2023-05-12 00:31:08 +02:00
parent 38fc0d2f83
commit 04abb53f4a
5 changed files with 168 additions and 20 deletions

View File

@@ -9,10 +9,10 @@ impl Home {
let doc: Markup = html!( let doc: Markup = html!(
div id="home" class={"p-8" "max-w-xl"} { div id="home" class={"p-8" "max-w-xl"} {
p { p {
a href="/inventory" { "Inventory" } a href="/inventory/" { "Inventory" }
} }
p { p {
a href="/trips" { "Trips" } a href="/trips/" { "Trips" }
} }
} }
); );

View File

@@ -162,6 +162,15 @@ impl InventoryItemList {
@if items.is_empty() { @if items.is_empty() {
p ."text-lg" ."text-center" ."py-5" ."text-gray-400" { "[Empty]" } p ."text-lg" ."text-center" ."py-5" ."text-gray-400" { "[Empty]" }
} @else { } @else {
@if let Some(edit_item) = edit_item {
form
name="edit-item"
id="edit-item"
action=(format!("/inventory/item/{edit_item}/edit"))
target="_self"
method="post"
{}
}
table table
."table" ."table"
."table-auto" ."table-auto"
@@ -179,7 +188,36 @@ impl InventoryItemList {
tbody { tbody {
@for item in items { @for item in items {
@if edit_item.map_or(false, |edit_item| edit_item == item.id) { @if edit_item.map_or(false, |edit_item| edit_item == item.id) {
tr { td { (item.name.clone()) " is being edited" }} tr ."h-10" {
td ."border" ."p-2" ."bg-blue-100" {
input ."w-full"
type="text"
id="edit-item-name"
name="edit-item-name"
form="edit-item"
value=(item.name)
{}
}
td ."border" ."p-2" ."bg-blue-100" {
input ."w-full"
type="number"
id="edit-item-weight"
name="edit-item-weight"
form="edit-item"
value=(item.weight)
{}
}
td ."border" ."p-2" ."bg-green-100" {
button type="submit" form="edit-item" {
span ."mdi" ."mdi-content-save" ."text-xl" {}
}
}
td ."border" ."p-2" ."bg-red-100" {
a href=(format!("/inventory/item/{id}/cancel", id = item.id)) {
span ."mdi" ."mdi-cancel" ."text-xl" {}
}
}
}
} @else { } @else {
tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" { tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" {
td ."border" ."p-0" { td ."border" ."p-0" {

View File

@@ -56,7 +56,8 @@ impl Root {
}} { "Trips" } }} { "Trips" }
} }
} }
div hx-boost="true" { // div hx-boost="true" {
div {
(body) (body)
} }
} }

View File

@@ -13,11 +13,12 @@ use sqlx::{
error::DatabaseError, error::DatabaseError,
query, query,
sqlite::{SqliteConnectOptions, SqliteError, SqlitePoolOptions}, sqlite::{SqliteConnectOptions, SqliteError, SqlitePoolOptions},
Pool, Sqlite, Pool, Row, Sqlite,
}; };
use serde::Deserialize; use serde::Deserialize;
use futures::TryFutureExt;
use futures::TryStreamExt; use futures::TryStreamExt;
use uuid::{uuid, Uuid}; use uuid::{uuid, Uuid};
@@ -85,6 +86,8 @@ async fn main() -> Result<(), sqlx::Error> {
.route("/inventory/item/", post(inventory_item_create)) .route("/inventory/item/", post(inventory_item_create))
.route("/inventory/category/:id", get(inventory_active)) .route("/inventory/category/:id", get(inventory_active))
.route("/inventory/item/:id/delete", get(inventory_item_delete)) .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( // .route(
// "/inventory/category/:id/items", // "/inventory/category/:id/items",
// post(htmx_inventory_category_items), // post(htmx_inventory_category_items),
@@ -293,7 +296,7 @@ async fn inventory_item_delete(
headers: HeaderMap, headers: HeaderMap,
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
) -> Result<Redirect, (StatusCode, String)> { ) -> Result<Redirect, (StatusCode, String)> {
query( let results = query(
"DELETE FROM inventoryitems "DELETE FROM inventoryitems
WHERE id = ?", WHERE id = ?",
) )
@@ -302,21 +305,28 @@ async fn inventory_item_delete(
.await .await
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?; .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
Ok(Redirect::to( if results.rows_affected() == 0 {
headers Err((
.get("referer") StatusCode::NOT_FOUND,
.ok_or(( format!("item with id {id} not found", id = id),
StatusCode::BAD_REQUEST, ))
"no referer header found".to_string(), } else {
))? Ok(Redirect::to(
.to_str() headers
.map_err(|e| { .get("referer")
( .ok_or((
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
format!("referer could not be converted: {}", e), "no referer header found".to_string(),
) ))?
})?, .to_str()
)) .map_err(|e| {
(
StatusCode::BAD_REQUEST,
format!("referer could not be converted: {}", e),
)
})?,
))
}
} }
// async fn htmx_inventory_category_items( // async fn htmx_inventory_category_items(
@@ -360,3 +370,44 @@ async fn inventory_item_delete(
// ), // ),
// )) // ))
// } // }
#[derive(Deserialize)]
struct EditItem {
#[serde(rename = "edit-item-name")]
name: String,
#[serde(rename = "edit-item-weight")]
weight: u32,
}
async fn inventory_item_edit(
State(state): State<AppState>,
Path(id): Path<Uuid>,
Form(edit_item): Form<EditItem>,
) -> Result<Redirect, (StatusCode, String)> {
let id = Item::update(&state.database_pool, id, &edit_item.name, edit_item.weight)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((
StatusCode::NOT_FOUND,
format!("item with id {id} not found", id = id),
))?;
Ok(Redirect::to(&format!("/inventory/category/{id}", id = id)))
}
async fn inventory_item_cancel(
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<Redirect, (StatusCode, String)> {
let id = Item::find(&state.database_pool, id)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((
StatusCode::NOT_FOUND,
format!("item with id {id} not found", id = id),
))?;
Ok(Redirect::to(&format!(
"/inventory/category/{id}",
id = id.category_id
)))
}

View File

@@ -6,6 +6,7 @@ use uuid::Uuid;
use sqlx::sqlite::SqlitePoolOptions; use sqlx::sqlite::SqlitePoolOptions;
use futures::TryFutureExt;
use futures::TryStreamExt; use futures::TryStreamExt;
pub enum Error { pub enum Error {
@@ -164,3 +165,60 @@ impl TryFrom<SqliteRow> for Item {
}) })
} }
} }
impl Item {
pub async fn find(pool: &sqlx::Pool<sqlx::Sqlite>, id: Uuid) -> Result<Option<Item>, Error> {
let item: Result<Result<Item, Error>, sqlx::Error> = sqlx::query(
"SELECT * FROM inventoryitems AS item
WHERE item.id = ?",
)
.bind(id.to_string())
.fetch_one(pool)
.map_ok(std::convert::TryInto::try_into)
.await;
match item {
Err(e) => match e {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(e.into()),
},
Ok(v) => Ok(Some(v?)),
}
}
pub async fn update(
pool: &sqlx::Pool<sqlx::Sqlite>,
id: Uuid,
name: &str,
weight: u32,
) -> Result<Option<Uuid>, Error> {
let id: Result<Result<Uuid, Error>, sqlx::Error> = sqlx::query(
"UPDATE inventoryitems AS item
SET
name = ?,
weight = ?
WHERE item.id = ?
RETURNING inventoryitems.category_id AS id
",
)
.bind(name)
.bind(weight)
.bind(id.to_string())
.fetch_one(pool)
.map_ok(|row| {
let id: &str = row.try_get("id")?;
let uuid: Result<Uuid, uuid::Error> = Uuid::try_parse(id);
let uuid: Result<Uuid, Error> = uuid.map_err(|e| e.into());
uuid
})
.await;
match id {
Err(e) => match e {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(e.into()),
},
Ok(v) => Ok(Some(v?)),
}
}
}