Show item classes as tree
This commit is contained in:
parent
ae4e583c2d
commit
413a02cdaa
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH RECURSIVE item_class_children AS (\n SELECT\n item_classes.id,\n array_remove(array_agg(children.id), NULL) AS \"children\"\n FROM item_classes\n LEFT JOIN item_classes AS \"children\"\n ON item_classes.id = children.parent\n GROUP BY item_classes.id\n ),\n cte AS (\n SELECT\n item_classes.id,\n item_classes.name,\n item_class_children.children,\n 0 AS \"reverse_level\"\n FROM item_classes\n JOIN item_class_children\n ON item_classes.id = item_class_children.id\n WHERE item_class_children.children = '{}'\n\n UNION\n\n SELECT\n item_classes.id,\n item_classes.name,\n item_class_children.children,\n cte.reverse_level + 1\n FROM item_classes\n JOIN item_class_children\n ON item_classes.id = item_class_children.id\n JOIN cte\n ON cte.id = ANY (item_class_children.children)\n )\n SELECT\n id AS \"id!\",\n name AS \"name!\",\n children AS \"children!\"\n FROM cte\n GROUP BY id, name, children\n ORDER BY max(reverse_level)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id!",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "name!",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "children!",
|
||||
"type_info": "UuidArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "689a5177bdbc4ac788579a47fe033eb6b9639a356fe7ec543691e385ffef51e7"
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use sqlx::query;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -13,6 +15,12 @@ pub struct ItemClassListEntry {
|
|||
pub parent: Option<ItemClassPreview>,
|
||||
}
|
||||
|
||||
pub struct ItemClassTreeElement {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub children: Vec<ItemClassTreeElement>,
|
||||
}
|
||||
|
||||
impl ItemClassRepository {
|
||||
pub async fn list(&self) -> sqlx::Result<Vec<ItemClassListEntry>> {
|
||||
query!(
|
||||
|
@ -33,4 +41,77 @@ impl ItemClassRepository {
|
|||
.fetch_all(&self.pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn tree(&self) -> sqlx::Result<Vec<ItemClassTreeElement>> {
|
||||
let mut mappings: HashMap<Uuid, ItemClassTreeElement> = HashMap::new();
|
||||
|
||||
for row in query!(
|
||||
r##"WITH RECURSIVE item_class_children AS (
|
||||
SELECT
|
||||
item_classes.id,
|
||||
array_remove(array_agg(children.id), NULL) AS "children"
|
||||
FROM item_classes
|
||||
LEFT JOIN item_classes AS "children"
|
||||
ON item_classes.id = children.parent
|
||||
GROUP BY item_classes.id
|
||||
),
|
||||
cte AS (
|
||||
SELECT
|
||||
item_classes.id,
|
||||
item_classes.name,
|
||||
item_class_children.children,
|
||||
0 AS "reverse_level"
|
||||
FROM item_classes
|
||||
JOIN item_class_children
|
||||
ON item_classes.id = item_class_children.id
|
||||
WHERE item_class_children.children = '{}'
|
||||
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
item_classes.id,
|
||||
item_classes.name,
|
||||
item_class_children.children,
|
||||
cte.reverse_level + 1
|
||||
FROM item_classes
|
||||
JOIN item_class_children
|
||||
ON item_classes.id = item_class_children.id
|
||||
JOIN cte
|
||||
ON cte.id = ANY (item_class_children.children)
|
||||
)
|
||||
SELECT
|
||||
id AS "id!",
|
||||
name AS "name!",
|
||||
children AS "children!"
|
||||
FROM cte
|
||||
GROUP BY id, name, children
|
||||
ORDER BY max(reverse_level)"##
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await?
|
||||
{
|
||||
let mut children = if row.children.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
row.children
|
||||
.iter()
|
||||
.map(|id| mappings.remove(id).unwrap())
|
||||
.collect()
|
||||
};
|
||||
children.sort_by(|this, other| this.name.cmp(&other.name));
|
||||
|
||||
mappings.insert(
|
||||
row.id,
|
||||
ItemClassTreeElement {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
children,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let mut item_classes = mappings.into_values().collect::<Vec<_>>();
|
||||
item_classes.sort_by(|this, other| this.name.cmp(&other.name));
|
||||
Ok(item_classes)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use uuid::Uuid;
|
|||
|
||||
pub use add::{ItemClassAddForm, ItemClassAddFormPrefilled};
|
||||
pub use edit::ItemClassEditForm;
|
||||
pub use list::ItemClassTreeElement;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ItemClassRepository {
|
||||
|
|
|
@ -6,7 +6,6 @@ use actix_identity::Identity;
|
|||
use actix_web::{error, get, web, Responder};
|
||||
use maud::html;
|
||||
|
||||
use crate::database::item_classes::ItemClassPreview;
|
||||
use crate::database::ItemClassRepository;
|
||||
use crate::frontend::templates::{
|
||||
self,
|
||||
|
@ -24,7 +23,7 @@ async fn get(
|
|||
user: Identity,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let item_classes = item_class_repo
|
||||
.list()
|
||||
.tree()
|
||||
.await
|
||||
.map_err(error::ErrorInternalServerError)?;
|
||||
|
||||
|
@ -47,26 +46,9 @@ async fn get(
|
|||
..Default::default()
|
||||
},
|
||||
html! {
|
||||
table .table {
|
||||
thead {
|
||||
tr {
|
||||
th { "Name" }
|
||||
th { "Parents" }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
ul {
|
||||
@for item_class in item_classes {
|
||||
tr {
|
||||
td { (ItemClassPreview::new(item_class.id, item_class.name)) }
|
||||
td {
|
||||
@if let Some(parent) = item_class.parent {
|
||||
(parent)
|
||||
} @else {
|
||||
"-"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(item_class)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,7 +7,11 @@ use std::fmt::{self, Display};
|
|||
use maud::{html, Markup, Render};
|
||||
|
||||
use crate::database::items::ItemPreview;
|
||||
use crate::database::{item_classes::ItemClassPreview, item_events::ItemEvent, items::ItemName};
|
||||
use crate::database::{
|
||||
item_classes::{ItemClassPreview, ItemClassTreeElement},
|
||||
item_events::ItemEvent,
|
||||
items::ItemName,
|
||||
};
|
||||
|
||||
impl Render for ItemClassPreview {
|
||||
fn render(&self) -> Markup {
|
||||
|
@ -68,3 +72,20 @@ impl Render for ItemPreview {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ItemClassTreeElement {
|
||||
fn render(&self) -> Markup {
|
||||
html! {
|
||||
li {
|
||||
(ItemClassPreview::new(self.id, self.name.clone()))
|
||||
@if !self.children.is_empty() {
|
||||
ul {
|
||||
@for child in &self.children {
|
||||
(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue