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 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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
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 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" } { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
})
|
||||||
})()
|
})()
|
||||||
|
|
Loading…
Reference in a new issue