feat: styling of clothing input

This commit is contained in:
Max Hohlfeld 2025-05-22 21:43:32 +02:00
parent 8d4a981055
commit 045a509daf
9 changed files with 75 additions and 44 deletions

View File

@ -1,4 +1,5 @@
use askama::Template;
use garde::Validate;
use serde::Deserialize;
use crate::models::Clothing;
@ -25,7 +26,16 @@ struct ReadClothingPartialTemplate {
c: Clothing,
}
#[derive(Deserialize)]
#[derive(Deserialize, Validate)]
struct NewOrEditClothingForm {
#[garde(length(min=3), custom(alphanumeric_or_space))]
name: String,
}
fn alphanumeric_or_space(value: &str, _context: &()) -> garde::Result {
if value.chars().all(|c| c.is_alphanumeric() || c == ' ') {
return Ok(())
} else {
return Err(garde::Error::new("Eingabe enthält unerlaubte Zeichen. Erlaubt sind Buchstaben, Zahlen und Leerzeichen."));
}
}

View File

@ -1,4 +1,5 @@
use actix_web::{web, HttpResponse, Responder};
use garde::Validate;
use sqlx::PgPool;
use crate::{
@ -17,7 +18,7 @@ pub async fn post(
path: web::Path<IdPath>,
form: web::Form<NewOrEditClothingForm>,
) -> Result<impl Responder, ApplicationError> {
if user.role == Role::Admin {
if user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
@ -25,6 +26,10 @@ pub async fn post(
return Ok(HttpResponse::NotFound().finish());
};
if let Err(e) = form.validate() {
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
};
clothing.name = form.name.to_string();
Clothing::update(pool.get_ref(), clothing.id, &clothing.name).await?;

View File

@ -1,4 +1,5 @@
use actix_web::{web, Responder};
use actix_web::{web, HttpResponse, Responder};
use garde::Validate;
use sqlx::PgPool;
use crate::{
@ -13,10 +14,14 @@ pub async fn post(
pool: web::Data<PgPool>,
form: web::Form<NewOrEditClothingForm>,
) -> Result<impl Responder, ApplicationError> {
if user.role == Role::Admin {
if user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
if let Err(e) = form.validate() {
return Ok(HttpResponse::UnprocessableEntity().body(e.to_string()));
};
let clothing = Clothing::create(pool.get_ref(), &form.name).await?;
let template = ReadClothingPartialTemplate { c: clothing };

View File

@ -95,10 +95,10 @@ pub fn init(cfg: &mut ServiceConfig) {
cfg.service(vehicle_assignment::delete::delete);
cfg.service(clothing::get_overview::get);
cfg.service(clothing::get_edit::get);
cfg.service(clothing::get_new::get);
cfg.service(clothing::get_edit::get);
cfg.service(clothing::get_read::get);
cfg.service(clothing::delete::delete);
cfg.service(clothing::post_new::post);
cfg.service(clothing::post_edit::post);
cfg.service(clothing::post_new::post);
}

View File

@ -23,7 +23,7 @@ impl Clothing {
}
pub async fn read_all(pool: &PgPool) -> Result<Vec<Clothing>> {
let records = query!("SELECT * FROM clothing;").fetch_all(pool).await?;
let records = query!("SELECT * FROM clothing ORDER by id;").fetch_all(pool).await?;
let clothing_options = records
.into_iter()

View File

@ -18,6 +18,7 @@ $primary: $crimson,
@forward "bulma/sass/components/navbar";
@forward "bulma/sass/components/message";
@forward "bulma/sass/components/dropdown";
@forward "bulma/sass/components/panel";
@forward "bulma/sass/elements/button";
@forward "bulma/sass/elements/box";

View File

@ -1,26 +1,32 @@
<li>
<form method="post" action="/clothing/{% if let Some(id) = id %}{{ id }}{% else %}new{% endif %}">
{% if id.is_none() %}<a class="panel-block is-active">{% endif %}
<form hx-post="/clothing{% if let Some(id) = id %}/{{ id }}{% endif %}" hx-target="closest a" hx-target-422="find p">
<div class="level">
<div class="level-left">
<div class="level-item">
<input class="input" type="text" {{ name|insert_value }} />
<div class="field">
<label class="label"></label>
<div class="control">
<input class="input" name="name" type="text" {{ name|insert_value|safe }}
_="on input put '' into the next <p/>" placeholder="Tuchuniform" minlength="3" />
</div>
<p class="help is-danger"></p>
</div>
</div>
<div class="level-item buttons are-small">
<button class="button">
<button class="button is-success">
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
</button>
{% if let Some(id) = id %}
<button class="button" type="button" hx-get="/clothing/{{ id }}" hx-swap="outerHTML"
hx-target="closest li">
<button class="button is-warning is-light" type="button" hx-get="/clothing/{{ id }}" hx-target="closest a">
<svg class="icon">
<use href="/static/feather-sprite.svg#x-circle" />
</svg>
</button>
{% else %}
<button class="button" type="button" _="on click remove the closest <li/>">
<button class="button is-warning" type="button" _="on click remove the closest <a/>">
<svg class="icon">
<use href="/static/feather-sprite.svg#x-circle" />
</svg>
@ -30,4 +36,5 @@
</div>
</div>
</form>
</li>
{% if id.is_none() %}
</a>{% endif %}

View File

@ -1,23 +1,21 @@
<li>
<div class="level">
<div class="level-left">
<div class="level-item">
{{ c.name }}
</div>
<div class="level">
<div class="level-left">
<div class="level-item">
{{ c.name }}
</div>
<div class="level-item buttons are-small">
<button class="button" hx-get="/clothing/edit/{{ c.id }}" hx-swap="outerHTML" hx-target="closest li">
<svg class="icon">
<use href="/static/feather-sprite.svg#edit" />
</svg>
</button>
<button class="button" hx-delete="/clothing/{{ c.id }}" hx-swap="delete"
hx-target="closest li" hx-trigger="confirmed">
<svg class="icon">
<use href="/static/feather-sprite.svg#trash-2" />
</svg>
</button>
</div>
<div class="level-item buttons are-small">
<button class="button is-success is-light" hx-get="/clothing/edit/{{ c.id }}" hx-target="closest a">
<svg class="icon">
<use href="/static/feather-sprite.svg#edit" />
</svg>
</button>
<button class="button is-danger is-light" hx-delete="/clothing/{{ c.id }}" hx-swap="delete"
hx-target="closest a" hx-trigger="confirmed">
<svg class="icon">
<use href="/static/feather-sprite.svg#trash-2" />
</svg>
</button>
</div>
</div>
</li>
</div>

View File

@ -6,17 +6,22 @@
<h3 class="title is-3">
Anzugsordnungen
</h3>
<p class="content">zur Auswahl bei der Erstellung von Events</p>
<p class="subtitle is-5">zur Auswahl bei der Erstellung von Events</p>
<div class="box">
<ul>
{% for c in clothings %}
<div class="panel p-2">
{% for c in clothings %}
<a class="panel-block is-active">
{% include "clothing_entry_read.html" %}
{% endfor %}
<li>
<button hx-get="/clothing/new" hx-swap="beforebegin">neue Anzugsordnung +</button>
</li>
</ul>
</a>
{% endfor %}
<div class="panel-block">
<button class="button is-link is-light" hx-get="/clothing/new" hx-swap="beforebegin" hx-target="closest div">
<svg class="icon">
<use href="/static/feather-sprite.svg#plus-circle" />
</svg>
<span>neue Anzugsordnung</span>
</button>
</div>
</div>
</section>