2024-07-07 13:48:31 +02:00
|
|
|
|
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
|
|
2024-07-11 01:12:34 +02:00
|
|
|
|
use actix_web::{error, get, post, web, Responder};
|
|
|
|
|
use maud::html;
|
2024-07-07 13:48:31 +02:00
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
2024-07-11 01:12:34 +02:00
|
|
|
|
use super::templates::{self, forms, TemplateConfig};
|
2024-07-07 13:48:31 +02:00
|
|
|
|
use crate::manage;
|
|
|
|
|
use crate::models::*;
|
|
|
|
|
use crate::DbPool;
|
|
|
|
|
|
2024-07-11 01:12:34 +02:00
|
|
|
|
const FORM_ENSURE_PARENT: templates::helpers::Js = templates::helpers::Js::Inline(
|
|
|
|
|
r#"
|
|
|
|
|
(() => {
|
|
|
|
|
document.getElementById("type").addEventListener("change", e => {
|
|
|
|
|
let parentInput = document.getElementById("parent")
|
|
|
|
|
switch (e.target.value) {
|
|
|
|
|
case "generic":
|
|
|
|
|
parentInput.disabled = true
|
|
|
|
|
parentInput.value = ""
|
|
|
|
|
break
|
|
|
|
|
case "specific":
|
|
|
|
|
parentInput.disabled = false
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
console.error("invalid type!")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})()"#,
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-07 13:48:31 +02:00
|
|
|
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
|
|
|
|
cfg.service(show_item_class)
|
|
|
|
|
.service(list_item_classes)
|
|
|
|
|
.service(add_item_class)
|
|
|
|
|
.service(add_item_class_post)
|
|
|
|
|
.service(edit_item_class)
|
|
|
|
|
.service(edit_item_class_post);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/item-class/{id}")]
|
|
|
|
|
async fn show_item_class(
|
|
|
|
|
pool: web::Data<DbPool>,
|
|
|
|
|
path: web::Path<Uuid>,
|
|
|
|
|
) -> actix_web::Result<impl Responder> {
|
|
|
|
|
let id = path.into_inner();
|
|
|
|
|
|
2024-07-07 14:55:05 +02:00
|
|
|
|
let item_class = manage::item_class::get(&mut pool.clone().get().await.unwrap(), id)
|
|
|
|
|
.await
|
2024-07-07 13:48:31 +02:00
|
|
|
|
.map_err(error::ErrorInternalServerError)?;
|
|
|
|
|
|
2024-07-07 14:55:05 +02:00
|
|
|
|
// TODO: Once async closures are stable, use map_or on item_class.parent instead
|
|
|
|
|
let parent = match item_class.parent {
|
|
|
|
|
Some(id) => manage::item_class::get(&mut pool.get().await.unwrap(), id)
|
|
|
|
|
.await
|
|
|
|
|
.map(Some)
|
|
|
|
|
.map_err(error::ErrorInternalServerError)?,
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
2024-07-07 13:48:31 +02:00
|
|
|
|
|
2024-07-11 01:12:34 +02:00
|
|
|
|
let mut title = item_class.name.clone();
|
|
|
|
|
title.push_str(" – Item Details");
|
2024-07-07 13:48:31 +02:00
|
|
|
|
|
2024-07-11 01:12:34 +02:00
|
|
|
|
Ok(templates::base(
|
|
|
|
|
TemplateConfig {
|
|
|
|
|
path: &format!("/item-class/{}", item_class.id),
|
|
|
|
|
title: Some(&title),
|
|
|
|
|
page_title: Some(Box::new(item_class.name.clone())),
|
|
|
|
|
page_actions: vec![
|
|
|
|
|
(templates::helpers::PageAction {
|
|
|
|
|
href: &format!("/item-class/{}/edit", item_class.id),
|
|
|
|
|
name: "Edit",
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
html! {
|
|
|
|
|
table .table {
|
|
|
|
|
tr {
|
|
|
|
|
th { "UUID" }
|
|
|
|
|
td { (item_class.id) }
|
|
|
|
|
}
|
|
|
|
|
tr {
|
|
|
|
|
th { "Name" }
|
|
|
|
|
td { (item_class.name) }
|
|
|
|
|
}
|
|
|
|
|
tr {
|
|
|
|
|
th { "Type" }
|
|
|
|
|
td { (item_class.r#type) }
|
|
|
|
|
}
|
|
|
|
|
@if let Some(parent) = parent {
|
|
|
|
|
tr {
|
|
|
|
|
th { "Parent" }
|
|
|
|
|
td { a href={ "/item-class/" (parent.id) } { (parent.name) } }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
))
|
2024-07-07 13:48:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/item-classes")]
|
2024-07-11 01:12:34 +02:00
|
|
|
|
async fn list_item_classes(pool: web::Data<DbPool>) -> actix_web::Result<impl Responder> {
|
2024-07-08 20:54:57 +02:00
|
|
|
|
let item_classes = manage::item_class::get_all_as_map(&mut pool.get().await.unwrap())
|
2024-07-07 14:55:05 +02:00
|
|
|
|
.await
|
2024-07-07 13:48:31 +02:00
|
|
|
|
.map_err(error::ErrorInternalServerError)?;
|
|
|
|
|
|
2024-07-11 01:12:34 +02:00
|
|
|
|
Ok(templates::base(
|
|
|
|
|
TemplateConfig {
|
|
|
|
|
path: "/item-classes",
|
|
|
|
|
title: Some("Item Class List"),
|
|
|
|
|
page_title: Some(Box::new("Item Class List")),
|
|
|
|
|
page_actions: vec![
|
|
|
|
|
(templates::helpers::PageAction {
|
|
|
|
|
href: "/item-classes/add",
|
|
|
|
|
name: "Add",
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
html! {
|
|
|
|
|
table .table {
|
|
|
|
|
thead {
|
|
|
|
|
tr {
|
|
|
|
|
th { "Name" }
|
|
|
|
|
th { "Parents" }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tbody {
|
|
|
|
|
@for item_class in item_classes.values() {
|
|
|
|
|
tr {
|
|
|
|
|
td { a href={ "/item-class/" (item_class.id) } { (item_class.name) } }
|
|
|
|
|
td {
|
|
|
|
|
@if let Some(parent) = item_class.parent {
|
|
|
|
|
@let parent = item_classes.get(&parent).unwrap();
|
|
|
|
|
a href={ "/item-class/" (parent.id) } { (parent.name) }
|
|
|
|
|
} @else {
|
|
|
|
|
"-"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
))
|
2024-07-07 13:48:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/item-classes/add")]
|
2024-07-11 01:12:34 +02:00
|
|
|
|
async fn add_item_class() -> actix_web::Result<impl Responder> {
|
|
|
|
|
Ok(templates::base(
|
|
|
|
|
TemplateConfig {
|
|
|
|
|
path: "/items-classes/add",
|
|
|
|
|
title: Some("Add Item Class"),
|
|
|
|
|
page_title: Some(Box::new("Add Item Class")),
|
|
|
|
|
extra_js: vec![FORM_ENSURE_PARENT],
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
html! {
|
|
|
|
|
form method="POST" {
|
|
|
|
|
(forms::InputGroup {
|
|
|
|
|
r#type: forms::InputType::Text,
|
|
|
|
|
name: "name",
|
|
|
|
|
title: "Name",
|
|
|
|
|
required: true,
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
|
|
|
|
// TODO: drop type in favour of determining it on whether parent is set
|
|
|
|
|
.mb-3 {
|
|
|
|
|
label .form-label for="type" { "Type" }
|
|
|
|
|
select .form-select #type name="type" required {
|
|
|
|
|
@for variant in ItemClassType::VARIANTS {
|
|
|
|
|
option { (variant) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.mb-3 {
|
|
|
|
|
label .form-label for="parent" { "Parent" }
|
|
|
|
|
input .form-control #parent type="text" name="parent" disabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
button .btn.btn-primary type="submit" { "Add" }
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
))
|
2024-07-07 13:48:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[post("/item-classes/add")]
|
|
|
|
|
async fn add_item_class_post(
|
|
|
|
|
data: web::Form<NewItemClass>,
|
|
|
|
|
pool: web::Data<DbPool>,
|
|
|
|
|
) -> actix_web::Result<impl Responder> {
|
2024-07-07 14:55:05 +02:00
|
|
|
|
let item = manage::item_class::add(&mut pool.get().await.unwrap(), data.into_inner())
|
|
|
|
|
.await
|
|
|
|
|
.map_err(error::ErrorInternalServerError)?;
|
2024-07-07 13:48:31 +02:00
|
|
|
|
Ok(web::Redirect::to("/item-class/".to_owned() + &item.id.to_string()).see_other())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[get("/item-class/{id}/edit")]
|
|
|
|
|
async fn edit_item_class(
|
|
|
|
|
pool: web::Data<DbPool>,
|
|
|
|
|
path: web::Path<Uuid>,
|
|
|
|
|
) -> actix_web::Result<impl Responder> {
|
|
|
|
|
let id = path.into_inner();
|
|
|
|
|
|
2024-07-07 14:55:05 +02:00
|
|
|
|
let item_class = manage::item_class::get(&mut pool.get().await.unwrap(), id)
|
|
|
|
|
.await
|
2024-07-07 13:48:31 +02:00
|
|
|
|
.map_err(error::ErrorInternalServerError)?;
|
|
|
|
|
|
2024-07-11 01:12:34 +02:00
|
|
|
|
let mut title = item_class.name.clone();
|
|
|
|
|
title.push_str(" – Item Details");
|
|
|
|
|
|
|
|
|
|
Ok(templates::base(
|
|
|
|
|
TemplateConfig {
|
|
|
|
|
path: &format!("/items-class/{}/add", id),
|
|
|
|
|
title: Some(&title),
|
|
|
|
|
page_title: Some(Box::new(item_class.name.clone())),
|
|
|
|
|
extra_js: vec![FORM_ENSURE_PARENT],
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
html! {
|
|
|
|
|
form method="POST" {
|
|
|
|
|
(forms::InputGroup {
|
|
|
|
|
r#type: forms::InputType::Text,
|
|
|
|
|
name: "id",
|
|
|
|
|
title: "UUID",
|
|
|
|
|
disabled: true,
|
|
|
|
|
required: true,
|
|
|
|
|
value: Some(item_class.id.to_string().as_str()),
|
|
|
|
|
})
|
|
|
|
|
(forms::InputGroup {
|
|
|
|
|
r#type: forms::InputType::Text,
|
|
|
|
|
name: "name",
|
|
|
|
|
title: "Name",
|
|
|
|
|
required: true,
|
|
|
|
|
value: Some(&item_class.name),
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
|
|
|
|
// TODO: drop type in favour of determining it on whether parent is set
|
|
|
|
|
.mb-3 {
|
|
|
|
|
label .form-label for="type" { "Type" }
|
|
|
|
|
select .form-select #type name="type" required {
|
|
|
|
|
@for variant in ItemClassType::VARIANTS {
|
|
|
|
|
option selected[variant == item_class.r#type] { (variant) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.mb-3 {
|
|
|
|
|
label .form-label for="parent" { "Parent" }
|
|
|
|
|
input .form-control #parent type="text" name="parent" disabled[item_class.parent.is_none()] value=[item_class.parent];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
button .btn.btn-primary type="submit" { "Edit" }
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
))
|
2024-07-07 13:48:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[post("/item-class/{id}/edit")]
|
|
|
|
|
async fn edit_item_class_post(
|
|
|
|
|
pool: web::Data<DbPool>,
|
|
|
|
|
path: web::Path<Uuid>,
|
|
|
|
|
data: web::Form<NewItemClass>,
|
|
|
|
|
) -> actix_web::Result<impl Responder> {
|
|
|
|
|
let id = path.into_inner();
|
|
|
|
|
|
2024-07-07 14:55:05 +02:00
|
|
|
|
let item_class =
|
|
|
|
|
manage::item_class::update(&mut pool.get().await.unwrap(), id, data.into_inner())
|
|
|
|
|
.await
|
|
|
|
|
.map_err(error::ErrorInternalServerError)?;
|
2024-07-07 13:48:31 +02:00
|
|
|
|
|
|
|
|
|
Ok(web::Redirect::to("/item-class/".to_owned() + &item_class.id.to_string()).see_other())
|
|
|
|
|
}
|