Add simple search for UUID references

This commit is contained in:
Simon Bruder 2024-07-12 15:44:11 +02:00
parent 1427ed9bf4
commit e40b5ba3bd
Signed by: simon
GPG key ID: 347FF8699CDA0776
6 changed files with 158 additions and 5 deletions

View file

@ -9,7 +9,7 @@ use maud::html;
use sqlx::PgPool; use sqlx::PgPool;
use uuid::Uuid; use uuid::Uuid;
use super::templates::{self, forms, TemplateConfig}; use super::templates::{self, datalist, forms, TemplateConfig};
use crate::manage; use crate::manage;
use crate::models::*; use crate::models::*;
@ -181,12 +181,24 @@ async fn list_items(pool: web::Data<PgPool>) -> actix_web::Result<impl Responder
} }
#[get("/items/add")] #[get("/items/add")]
async fn add_item(form: web::Query<NewItemForm>) -> actix_web::Result<impl Responder> { async fn add_item(
pool: web::Data<PgPool>,
form: web::Query<NewItemForm>,
) -> actix_web::Result<impl Responder> {
let datalist_items = datalist::items(&pool)
.await
.map_err(error::ErrorInternalServerError)?;
let datalist_item_classes = datalist::item_classes(&pool)
.await
.map_err(error::ErrorInternalServerError)?;
Ok(templates::base( Ok(templates::base(
TemplateConfig { TemplateConfig {
path: "/items/add", path: "/items/add",
title: Some("Add Item"), title: Some("Add Item"),
page_title: Some(Box::new("Add Item")), page_title: Some(Box::new("Add Item")),
datalists: vec![&datalist_items, &datalist_item_classes],
..Default::default() ..Default::default()
}, },
html! { html! {
@ -204,6 +216,7 @@ async fn add_item(form: web::Query<NewItemForm>) -> actix_web::Result<impl Respo
title: "Class", title: "Class",
required: true, required: true,
value: form.class.map(|id| id.to_string()), value: form.class.map(|id| id.to_string()),
datalist: Some(&datalist_item_classes),
..Default::default() ..Default::default()
}) })
(forms::InputGroup { (forms::InputGroup {
@ -211,6 +224,7 @@ async fn add_item(form: web::Query<NewItemForm>) -> actix_web::Result<impl Respo
name: "parent", name: "parent",
title: "Parent", title: "Parent",
value: form.parent.map(|id| id.to_string()), value: form.parent.map(|id| id.to_string()),
datalist: Some(&datalist_items),
..Default::default() ..Default::default()
}) })
@ -246,6 +260,14 @@ async fn edit_item(
.await .await
.map_err(error::ErrorInternalServerError)?; .map_err(error::ErrorInternalServerError)?;
let datalist_items = datalist::items(&pool)
.await
.map_err(error::ErrorInternalServerError)?;
let datalist_item_classes = datalist::item_classes(&pool)
.await
.map_err(error::ErrorInternalServerError)?;
let item_name = templates::helpers::ItemName::new(&item, &item_class); let item_name = templates::helpers::ItemName::new(&item, &item_class);
let mut title = item_name.to_string(); let mut title = item_name.to_string();
title.push_str(" Edit Item"); title.push_str(" Edit Item");
@ -255,6 +277,7 @@ async fn edit_item(
path: &format!("/item/{}/edit", item.id), path: &format!("/item/{}/edit", item.id),
title: Some(&title), title: Some(&title),
page_title: Some(Box::new(item_name.clone())), page_title: Some(Box::new(item_name.clone())),
datalists: vec![&datalist_items, &datalist_item_classes],
..Default::default() ..Default::default()
}, },
html! { html! {
@ -266,6 +289,7 @@ async fn edit_item(
required: true, required: true,
disabled: true, disabled: true,
value: Some(item.id.to_string()), value: Some(item.id.to_string()),
..Default::default()
}) })
(forms::InputGroup { (forms::InputGroup {
r#type: forms::InputType::Text, r#type: forms::InputType::Text,
@ -281,6 +305,7 @@ async fn edit_item(
title: "Class", title: "Class",
required: true, required: true,
value: Some(item.class.to_string()), value: Some(item.class.to_string()),
datalist: Some(&datalist_item_classes),
..Default::default() ..Default::default()
}) })
(forms::InputGroup { (forms::InputGroup {
@ -289,6 +314,7 @@ async fn edit_item(
title: "Parent", title: "Parent",
value: item.parent.map(|id| id.to_string()), value: item.parent.map(|id| id.to_string()),
disabled: item.parent.is_none(), disabled: item.parent.is_none(),
datalist: Some(&datalist_items),
..Default::default() ..Default::default()
}) })

View file

@ -7,7 +7,7 @@ use maud::html;
use sqlx::PgPool; use sqlx::PgPool;
use uuid::Uuid; use uuid::Uuid;
use super::templates::{self, forms, TemplateConfig}; use super::templates::{self, datalist, forms, TemplateConfig};
use crate::manage; use crate::manage;
use crate::models::*; use crate::models::*;
@ -159,12 +159,20 @@ async fn list_item_classes(pool: web::Data<PgPool>) -> actix_web::Result<impl Re
} }
#[get("/item-classes/add")] #[get("/item-classes/add")]
async fn add_item_class(form: web::Query<NewItemClassForm>) -> actix_web::Result<impl Responder> { async fn add_item_class(
pool: web::Data<PgPool>,
form: web::Query<NewItemClassForm>,
) -> actix_web::Result<impl Responder> {
let datalist_item_classes = datalist::item_classes(&pool)
.await
.map_err(error::ErrorInternalServerError)?;
Ok(templates::base( Ok(templates::base(
TemplateConfig { TemplateConfig {
path: "/items-classes/add", path: "/items-classes/add",
title: Some("Add Item Class"), title: Some("Add Item Class"),
page_title: Some(Box::new("Add Item Class")), page_title: Some(Box::new("Add Item Class")),
datalists: vec![&datalist_item_classes],
..Default::default() ..Default::default()
}, },
html! { html! {
@ -183,6 +191,7 @@ async fn add_item_class(form: web::Query<NewItemClassForm>) -> actix_web::Result
title: "Parent", title: "Parent",
disabled: form.parent.is_none(), disabled: form.parent.is_none(),
value: form.parent.map(|id| id.to_string()), value: form.parent.map(|id| id.to_string()),
datalist: Some(&datalist_item_classes),
..Default::default() ..Default::default()
}) })
@ -214,6 +223,10 @@ async fn edit_item_class(
.await .await
.map_err(error::ErrorInternalServerError)?; .map_err(error::ErrorInternalServerError)?;
let datalist_item_classes = datalist::item_classes(&pool)
.await
.map_err(error::ErrorInternalServerError)?;
let mut title = item_class.name.clone(); let mut title = item_class.name.clone();
title.push_str(" Item Details"); title.push_str(" Item Details");
@ -222,6 +235,7 @@ async fn edit_item_class(
path: &format!("/items-class/{}/add", id), path: &format!("/items-class/{}/add", id),
title: Some(&title), title: Some(&title),
page_title: Some(Box::new(item_class.name.clone())), page_title: Some(Box::new(item_class.name.clone())),
datalists: vec![&datalist_item_classes],
..Default::default() ..Default::default()
}, },
html! { html! {
@ -233,6 +247,7 @@ async fn edit_item_class(
disabled: true, disabled: true,
required: true, required: true,
value: Some(item_class.id.to_string()), value: Some(item_class.id.to_string()),
..Default::default()
}) })
(forms::InputGroup { (forms::InputGroup {
r#type: forms::InputType::Text, r#type: forms::InputType::Text,
@ -248,6 +263,7 @@ async fn edit_item_class(
title: "Parent", title: "Parent",
disabled: item_class.parent.is_none(), disabled: item_class.parent.is_none(),
value: item_class.parent.map(|id| id.to_string()), value: item_class.parent.map(|id| id.to_string()),
datalist: Some(&datalist_item_classes),
..Default::default() ..Default::default()
}) })

View file

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use maud::{html, Markup, Render};
use sqlx::PgPool;
use super::helpers::ItemName;
use crate::manage;
pub struct Datalist {
name: String,
options: Vec<DatalistOption>,
}
impl Datalist {
pub fn name(&self) -> &str {
&self.name
}
}
impl Render for Datalist {
fn render(&self) -> Markup {
html! {
datalist #{ (self.name) "-datalist" } {
@for option in &self.options {
(option)
}
}
}
}
}
pub struct DatalistOption {
value: String,
text: Box<dyn Render>,
}
impl Render for DatalistOption {
fn render(&self) -> Markup {
html! { option value=(self.value) { (self.text) } }
}
}
pub async fn items(pool: &PgPool) -> Result<Datalist, sqlx::Error> {
let items = manage::item::get_all(pool).await?;
let item_classes = manage::item_class::get_all_as_map(pool).await?;
Ok(Datalist {
name: "items".to_string(),
options: items
.iter()
.map(|i| DatalistOption {
value: i.id.to_string(),
text: Box::new(ItemName::new(i, item_classes.get(&i.class).unwrap())),
})
.collect(),
})
}
pub async fn item_classes(pool: &PgPool) -> Result<Datalist, sqlx::Error> {
Ok(Datalist {
name: "item-classes".to_string(),
options: manage::item_class::get_all(pool)
.await?
.into_iter()
.map(|ic| DatalistOption {
value: ic.id.to_string(),
text: Box::new(ic.name),
})
.collect(),
})
}

View file

@ -6,6 +6,8 @@ use std::fmt;
use maud::{html, Markup, Render}; use maud::{html, Markup, Render};
use super::datalist::Datalist;
#[derive(Clone)] #[derive(Clone)]
pub enum InputType { pub enum InputType {
Text, Text,
@ -26,6 +28,7 @@ pub struct InputGroup<'a> {
pub required: bool, pub required: bool,
pub disabled: bool, pub disabled: bool,
pub value: Option<String>, pub value: Option<String>,
pub datalist: Option<&'a Datalist>,
} }
impl Default for InputGroup<'_> { impl Default for InputGroup<'_> {
@ -37,6 +40,7 @@ impl Default for InputGroup<'_> {
required: false, required: false,
disabled: false, disabled: false,
value: None, value: None,
datalist: None,
} }
} }
} }
@ -44,7 +48,15 @@ impl Default for InputGroup<'_> {
impl InputGroup<'_> { impl InputGroup<'_> {
fn main_input(&self, force_required: bool) -> Markup { fn main_input(&self, force_required: bool) -> Markup {
html! { html! {
input .form-control #(self.name) name={ (self.name) } type={ (self.r#type) } required[self.required || force_required] disabled[self.disabled] value=[self.value.clone()]; input
.form-control
#(self.name)
name={ (self.name) }
type={ (self.r#type) }
required[self.required || force_required]
disabled[self.disabled]
value=[self.value.clone()]
list=[self.datalist.map(|dl| format!("{}-datalist", dl.name()))];
} }
} }
} }
@ -64,6 +76,9 @@ impl Render for InputGroup<'_> {
(self.main_input(true)) (self.main_input(true))
} }
} }
@if self.datalist.is_some() {
div .form-text.datalist-hint #{ (self.name) "-datalist-hint" } { }
}
} }
} }
} }

View file

@ -2,11 +2,13 @@
// //
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
pub mod datalist;
pub mod forms; pub mod forms;
pub mod helpers; pub mod helpers;
use maud::{html, Markup, Render, DOCTYPE}; use maud::{html, Markup, Render, DOCTYPE};
use datalist::Datalist;
use helpers::*; use helpers::*;
const BRANDING: &str = "li7y"; const BRANDING: &str = "li7y";
@ -56,6 +58,7 @@ pub struct TemplateConfig<'a> {
pub page_actions: Vec<PageAction>, pub page_actions: Vec<PageAction>,
pub extra_css: Vec<Css<'a>>, pub extra_css: Vec<Css<'a>>,
pub extra_js: Vec<Js<'a>>, pub extra_js: Vec<Js<'a>>,
pub datalists: Vec<&'a Datalist>,
} }
impl Default for TemplateConfig<'_> { impl Default for TemplateConfig<'_> {
@ -67,6 +70,7 @@ impl Default for TemplateConfig<'_> {
page_actions: Vec::new(), page_actions: Vec::new(),
extra_css: Vec::new(), extra_css: Vec::new(),
extra_js: Vec::new(), extra_js: Vec::new(),
datalists: Vec::new(),
} }
} }
} }
@ -116,6 +120,10 @@ pub fn base(config: TemplateConfig, content: Markup) -> Markup {
(footer()) (footer())
@for datalist in config.datalists {
(datalist)
}
(Js::File("/static/vendor/bootstrap.bundle.min.js")) (Js::File("/static/vendor/bootstrap.bundle.min.js"))
(Js::File("/static/app.js")) (Js::File("/static/app.js"))
// TODO this is not the best way, but it works for now // TODO this is not the best way, but it works for now

View file

@ -13,4 +13,18 @@
inputToggle(el) inputToggle(el)
el.addEventListener("change", e => inputToggle(e.target)) el.addEventListener("change", e => inputToggle(e.target))
}) })
const datalistHint = (input, hint) => {
const selected = input.list.querySelector(`option[value="${input.value}"]`);
if (selected === null)
hint.innerText = ""
else {
hint.innerHTML = selected.innerHTML
}
}
Array.from(document.getElementsByClassName("datalist-hint")).forEach(hint => {
const input = hint.parentElement.querySelector("input[list]")
datalistHint(input, hint)
input.addEventListener("input", _ => datalistHint(input, hint))
})
})() })()