Show parent hierarchy in item list and details

This commit is contained in:
Simon Bruder 2024-07-08 22:39:54 +02:00
parent 9138865b48
commit b736eba0a0
Signed by: simon
GPG key ID: 347FF8699CDA0776
5 changed files with 76 additions and 5 deletions

View file

@ -27,6 +27,7 @@ struct ItemDetails {
req: HttpRequest, req: HttpRequest,
item: Item, item: Item,
item_class: ItemClass, item_class: ItemClass,
parents: Vec<Item>,
} }
#[get("/item/{id}")] #[get("/item/{id}")]
@ -45,10 +46,15 @@ async fn show_item(
.await .await
.map_err(error::ErrorInternalServerError)?; .map_err(error::ErrorInternalServerError)?;
let parents = manage::item::get_parents_details(&mut pool.get().await.unwrap(), item.id)
.await
.map_err(error::ErrorInternalServerError)?;
Ok(ItemDetails { Ok(ItemDetails {
req, req,
item, item,
item_class, item_class,
parents,
}) })
} }
@ -56,8 +62,12 @@ async fn show_item(
#[template(path = "item_list.html")] #[template(path = "item_list.html")]
struct ItemList { struct ItemList {
req: HttpRequest, req: HttpRequest,
items: Vec<Item>, // Both a Vec and a HashMap are used to have both the natural order,
// as well as arbitrary access capabilities.
item_list: Vec<Item>,
items: HashMap<Uuid, Item>,
item_classes: HashMap<Uuid, ItemClass>, item_classes: HashMap<Uuid, ItemClass>,
item_tree: HashMap<Uuid, Vec<Uuid>>,
} }
#[get("/items")] #[get("/items")]
@ -65,7 +75,11 @@ async fn list_items(
req: HttpRequest, req: HttpRequest,
pool: web::Data<DbPool>, pool: web::Data<DbPool>,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let items = manage::item::get_all(&mut pool.get().await.unwrap()) let item_list = manage::item::get_all(&mut pool.get().await.unwrap())
.await
.map_err(error::ErrorInternalServerError)?;
let items = manage::item::get_all_as_map(&mut pool.get().await.unwrap())
.await .await
.map_err(error::ErrorInternalServerError)?; .map_err(error::ErrorInternalServerError)?;
@ -73,10 +87,16 @@ async fn list_items(
.await .await
.map_err(error::ErrorInternalServerError)?; .map_err(error::ErrorInternalServerError)?;
let item_tree = manage::item::get_all_parents(&mut pool.get().await.unwrap())
.await
.map_err(error::ErrorInternalServerError)?;
Ok(ItemList { Ok(ItemList {
req, req,
item_list,
items, items,
item_classes, item_classes,
item_tree,
}) })
} }

View file

@ -5,6 +5,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::sql_types;
use diesel_async::pg::AsyncPgConnection; use diesel_async::pg::AsyncPgConnection;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use uuid::Uuid; use uuid::Uuid;
@ -37,6 +38,16 @@ pub async fn get_all(conn: &mut AsyncPgConnection) -> Result<Vec<Item>, diesel::
.await .await
} }
pub async fn get_all_as_map(
conn: &mut AsyncPgConnection,
) -> Result<HashMap<Uuid, Item>, diesel::result::Error> {
Ok(get_all(conn)
.await?
.into_iter()
.map(|i| (i.id, i))
.collect())
}
pub async fn update( pub async fn update(
conn: &mut AsyncPgConnection, conn: &mut AsyncPgConnection,
id: Uuid, id: Uuid,
@ -91,3 +102,23 @@ pub async fn get_all_parents(
.collect::<HashMap<Uuid, Vec<Uuid>>>() .collect::<HashMap<Uuid, Vec<Uuid>>>()
}) })
} }
pub async fn get_parents_details(
conn: &mut AsyncPgConnection,
id: Uuid,
) -> Result<Vec<Item>, diesel::result::Error> {
define_sql_function!(fn unnest(a: sql_types::Array<sql_types::Uuid>) -> sql_types::Uuid);
schema::items::table
.filter(
schema::items::id.eq_any(
schema::item_tree::table
.filter(schema::item_tree::id.eq(id))
.select(unnest(schema::item_tree::parents))
.into_boxed(),
),
)
.select(Item::as_select())
.load(conn)
.await
}

View file

@ -39,3 +39,4 @@ diesel::table! {
diesel::joinable!(items -> item_classes (class)); diesel::joinable!(items -> item_classes (class));
diesel::allow_tables_to_appear_in_same_query!(item_classes, items); diesel::allow_tables_to_appear_in_same_query!(item_classes, items);
diesel::allow_tables_to_appear_in_same_query!(items, item_tree);

View file

@ -36,10 +36,15 @@ SPDX-License-Identifier: AGPL-3.0-or-later
</tr> </tr>
<tr> <tr>
<th> <th>
Parent Parents
</th> </th>
<td> <td>
{% if let Some(parent) = item.parent %}{{ parent }}{% else %}-{% endif %} <ol class="breadcrumb mb-0">
{%- for parent in parents %}
<li class="breadcrumb-item"><a href="/item/{{ parent.id }}">{{ parent.name }}</a></li>
{%- endfor %}
<li class="breadcrumb-item active">{{ item.name }}</li>
</ol>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View file

@ -15,14 +15,28 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Class</th> <th>Class</th>
<th>Parents</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for item in items -%} {% for item in item_list -%}
{% let class = item_classes.get(item.class).unwrap() %} {% let class = item_classes.get(item.class).unwrap() %}
<tr> <tr>
<td><a href="/item/{{ item.id }}">{{ item.name }}</a></td> <td><a href="/item/{{ item.id }}">{{ item.name }}</a></td>
<td><a href="/item-class/{{ class.id }}">{{ class.name }}</a></td> <td><a href="/item-class/{{ class.id }}">{{ class.name }}</a></td>
<td>
<ol class="breadcrumb mb-0">
{%- let parents = item_tree.get(item.id).unwrap() -%}
{%- if parents.len() > 3 %}
<li class="breadcrumb-item"></li>
{%- endif %}
{%- for parent in parents.iter().rev().take(3).rev() %}
{%- let parent = items.get(parent).unwrap() %}
<li class="breadcrumb-item"><a href="/item/{{ parent.id }}">{{ parent.name }}</a></li>
{%- endfor %}
<li class="breadcrumb-item active">{{ item.name }}</li>
</ol>
</td>
</tr> </tr>
{% endfor -%} {% endfor -%}
</tbody> </tbody>