Add simple search for UUID references
This commit is contained in:
parent
c8d547998c
commit
98af21d4aa
|
@ -9,7 +9,7 @@ use maud::html;
|
|||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::templates::{self, forms, TemplateConfig};
|
||||
use super::templates::{self, datalist, forms, TemplateConfig};
|
||||
use crate::manage;
|
||||
use crate::models::*;
|
||||
|
||||
|
@ -181,12 +181,24 @@ async fn list_items(pool: web::Data<PgPool>) -> actix_web::Result<impl Responder
|
|||
}
|
||||
|
||||
#[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(
|
||||
TemplateConfig {
|
||||
path: "/items/add",
|
||||
title: Some("Add Item"),
|
||||
page_title: Some(Box::new("Add Item")),
|
||||
datalists: vec![&datalist_items, &datalist_item_classes],
|
||||
..Default::default()
|
||||
},
|
||||
html! {
|
||||
|
@ -204,6 +216,7 @@ async fn add_item(form: web::Query<NewItemForm>) -> actix_web::Result<impl Respo
|
|||
title: "Class",
|
||||
required: true,
|
||||
value: form.class.map(|id| id.to_string()),
|
||||
datalist: Some(&datalist_item_classes),
|
||||
..Default::default()
|
||||
})
|
||||
(forms::InputGroup {
|
||||
|
@ -211,6 +224,7 @@ async fn add_item(form: web::Query<NewItemForm>) -> actix_web::Result<impl Respo
|
|||
name: "parent",
|
||||
title: "Parent",
|
||||
value: form.parent.map(|id| id.to_string()),
|
||||
datalist: Some(&datalist_items),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
|
@ -246,6 +260,14 @@ async fn edit_item(
|
|||
.await
|
||||
.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 mut title = item_name.to_string();
|
||||
title.push_str(" – Edit Item");
|
||||
|
@ -255,6 +277,7 @@ async fn edit_item(
|
|||
path: &format!("/item/{}/edit", item.id),
|
||||
title: Some(&title),
|
||||
page_title: Some(Box::new(item_name.clone())),
|
||||
datalists: vec![&datalist_items, &datalist_item_classes],
|
||||
..Default::default()
|
||||
},
|
||||
html! {
|
||||
|
@ -266,6 +289,7 @@ async fn edit_item(
|
|||
required: true,
|
||||
disabled: true,
|
||||
value: Some(item.id.to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
(forms::InputGroup {
|
||||
r#type: forms::InputType::Text,
|
||||
|
@ -281,6 +305,7 @@ async fn edit_item(
|
|||
title: "Class",
|
||||
required: true,
|
||||
value: Some(item.class.to_string()),
|
||||
datalist: Some(&datalist_item_classes),
|
||||
..Default::default()
|
||||
})
|
||||
(forms::InputGroup {
|
||||
|
@ -289,6 +314,7 @@ async fn edit_item(
|
|||
title: "Parent",
|
||||
value: item.parent.map(|id| id.to_string()),
|
||||
disabled: item.parent.is_none(),
|
||||
datalist: Some(&datalist_items),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use maud::html;
|
|||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::templates::{self, forms, TemplateConfig};
|
||||
use super::templates::{self, datalist, forms, TemplateConfig};
|
||||
use crate::manage;
|
||||
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")]
|
||||
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(
|
||||
TemplateConfig {
|
||||
path: "/items-classes/add",
|
||||
title: Some("Add Item Class"),
|
||||
page_title: Some(Box::new("Add Item Class")),
|
||||
datalists: vec![&datalist_item_classes],
|
||||
..Default::default()
|
||||
},
|
||||
html! {
|
||||
|
@ -183,6 +191,7 @@ async fn add_item_class(form: web::Query<NewItemClassForm>) -> actix_web::Result
|
|||
title: "Parent",
|
||||
disabled: form.parent.is_none(),
|
||||
value: form.parent.map(|id| id.to_string()),
|
||||
datalist: Some(&datalist_item_classes),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
|
@ -214,6 +223,10 @@ async fn edit_item_class(
|
|||
.await
|
||||
.map_err(error::ErrorInternalServerError)?;
|
||||
|
||||
let datalist_item_classes = datalist::item_classes(&pool)
|
||||
.await
|
||||
.map_err(error::ErrorInternalServerError)?;
|
||||
|
||||
let mut title = item_class.name.clone();
|
||||
title.push_str(" – Item Details");
|
||||
|
||||
|
@ -222,6 +235,7 @@ async fn edit_item_class(
|
|||
path: &format!("/items-class/{}/add", id),
|
||||
title: Some(&title),
|
||||
page_title: Some(Box::new(item_class.name.clone())),
|
||||
datalists: vec![&datalist_item_classes],
|
||||
..Default::default()
|
||||
},
|
||||
html! {
|
||||
|
@ -233,6 +247,7 @@ async fn edit_item_class(
|
|||
disabled: true,
|
||||
required: true,
|
||||
value: Some(item_class.id.to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
(forms::InputGroup {
|
||||
r#type: forms::InputType::Text,
|
||||
|
@ -248,6 +263,7 @@ async fn edit_item_class(
|
|||
title: "Parent",
|
||||
disabled: item_class.parent.is_none(),
|
||||
value: item_class.parent.map(|id| id.to_string()),
|
||||
datalist: Some(&datalist_item_classes),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
|
|
74
src/frontend/templates/datalist.rs
Normal file
74
src/frontend/templates/datalist.rs
Normal 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(),
|
||||
})
|
||||
}
|
|
@ -6,6 +6,8 @@ use std::fmt;
|
|||
|
||||
use maud::{html, Markup, Render};
|
||||
|
||||
use super::datalist::Datalist;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum InputType {
|
||||
Text,
|
||||
|
@ -26,6 +28,7 @@ pub struct InputGroup<'a> {
|
|||
pub required: bool,
|
||||
pub disabled: bool,
|
||||
pub value: Option<String>,
|
||||
pub datalist: Option<&'a Datalist>,
|
||||
}
|
||||
|
||||
impl Default for InputGroup<'_> {
|
||||
|
@ -37,6 +40,7 @@ impl Default for InputGroup<'_> {
|
|||
required: false,
|
||||
disabled: false,
|
||||
value: None,
|
||||
datalist: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +48,15 @@ impl Default for InputGroup<'_> {
|
|||
impl InputGroup<'_> {
|
||||
fn main_input(&self, force_required: bool) -> Markup {
|
||||
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))
|
||||
}
|
||||
}
|
||||
@if self.datalist.is_some() {
|
||||
div .form-text.datalist-hint #{ (self.name) "-datalist-hint" } { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
pub mod datalist;
|
||||
pub mod forms;
|
||||
pub mod helpers;
|
||||
|
||||
use maud::{html, Markup, Render, DOCTYPE};
|
||||
|
||||
use datalist::Datalist;
|
||||
use helpers::*;
|
||||
|
||||
const BRANDING: &str = "li7y";
|
||||
|
@ -56,6 +58,7 @@ pub struct TemplateConfig<'a> {
|
|||
pub page_actions: Vec<PageAction>,
|
||||
pub extra_css: Vec<Css<'a>>,
|
||||
pub extra_js: Vec<Js<'a>>,
|
||||
pub datalists: Vec<&'a Datalist>,
|
||||
}
|
||||
|
||||
impl Default for TemplateConfig<'_> {
|
||||
|
@ -67,6 +70,7 @@ impl Default for TemplateConfig<'_> {
|
|||
page_actions: Vec::new(),
|
||||
extra_css: Vec::new(),
|
||||
extra_js: Vec::new(),
|
||||
datalists: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +120,10 @@ pub fn base(config: TemplateConfig, content: Markup) -> Markup {
|
|||
|
||||
(footer())
|
||||
|
||||
@for datalist in config.datalists {
|
||||
(datalist)
|
||||
}
|
||||
|
||||
(Js::File("/static/vendor/bootstrap.bundle.min.js"))
|
||||
(Js::File("/static/app.js"))
|
||||
// TODO this is not the best way, but it works for now
|
||||
|
|
|
@ -13,4 +13,18 @@
|
|||
inputToggle(el)
|
||||
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))
|
||||
})
|
||||
})()
|
||||
|
|
Loading…
Reference in a new issue