feat: event input and display

closes #20
This commit is contained in:
Max Hohlfeld 2025-05-11 22:31:27 +02:00
parent 5047530a3d
commit 4527a4dfc3
9 changed files with 465 additions and 434 deletions

View File

@ -5,200 +5,209 @@ snapshot_kind: text
--- ---
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<form method="post" action="/events/1/edit"> <form method="post" action="/events/1/edit">
<h1 class="title">Event 'Vorstellung' bearbeiten</h1> <h1 class="title">Event 'Vorstellung' bearbeiten</h1>
<input type="hidden" name="date" value="2025-01-01"> <input type="hidden" name="date" value="2025-01-01">
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"></div> <div class="field-label"></div>
<div class="field-body"> <div class="field-body">
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
<button class="button is-warning" type="button" <button class="button is-warning" type="button"
hx-put="/events/1/cancel"> hx-put="/events/1/cancel">
<svg class="icon"> <svg class="icon">
<use href="/static/feather-sprite.svg#alert-circle" /> <use href="/static/feather-sprite.svg#alert-circle" />
</svg> </svg>
<span> <span>
Als abgesagt markieren Als abgesagt markieren
</span> </span>
</button> </button>
</div> </div>
<div class="control"> <div class="control">
<button class="button is-danger" type="button" hx-delete="/events/1" hx-trigger="confirmed"> <button class="button is-danger" type="button"
<svg class="icon"> hx-delete="/events/1"
<use href="/static/feather-sprite.svg#x-circle" /> hx-trigger="confirmed">
</svg> <svg class="icon">
<span>Löschen</span> <use href="/static/feather-sprite.svg#x-circle" />
</button> </svg>
</div> <span>Löschen</span>
</button>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Veranstaltungsname</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" name="name" placeholder="Wave Gotik Treffen" required
value="Vorstellung" />
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Startzeit - Endzeit</label>
<div class="field is-horizontal"> </div>
<div class="field-label"> <div class="field-body">
<label class="label">Veranstaltungsname</label> <div class="field">
<input class="input" type="time" name="start" required
value="08:00" />
</div> </div>
<div class="field-body"> <div class="field">
<div class="field">
<div class="control"> <input class="input" type="datetime-local" name="end" required
<input class="input" name="name" placeholder="Wave Gotik Treffen" required value="Vorstellung" /> value="2025-01-01T10:00"
min="2025-01-01T00:00" max="2025-01-02T23:59" />
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Veranstaltungsort</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<div class="select is-fullwidth">
<select name="location" required >
<option value="1"
selected>
Hauptbahnhof
- (Leipzig Ost)
</option>
</select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"> <div class="field-label">
<label class="label">Startzeit - Endzeit</label> <label class="label">Wachhabender durch FF gestellt?</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field is-narrow">
<div class="field"> <div class="control">
<input class="input" type="time" id="from" name="from" required value="08:00" /> <label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntarywachhabender" value="true"
>
</label>
</div> </div>
<div class="field">
<input class="input" type="time" id="till" name="till" required value="10:00" /> </div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Führungsassistent durch FF gestellt?</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntaryfuehrungsassistent" value="true"
>
</label>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anzahl der Posten</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="number" name="amount"
min="0" max="100"
required value="2" />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anzugsordnung</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" name="clothing" placeholder="Tuchuniform" required value="Tuchuniform"
/>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"> <div class="field-label">
<label class="label">Veranstaltungsort</label> <label class="label">Anmerkung</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field is-narrow"> <div class="field">
<div class="control"> <div class="control">
<div class="select is-fullwidth"> <input class="input" name="note" />
<select name="location" required >
<option value="1" selected>Hauptbahnhof - (Leipzig Ost)
</option>
</select>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"> <div class="field-label"></div>
<label class="label">Wachhabender durch FF gestellt?</label> <div class="field-body">
</div> <div class="field is-grouped">
<div class="field-body"> <div class="control">
<div class="field is-narrow"> <button class="button is-success" >
<svg class="icon">
<div class="control"> <use href="/static/feather-sprite.svg#check-circle" />
<label class="checkbox"> </svg>
<input class="checkbox" type="checkbox" name="voluntarywachhabender" value="true" > <span>
</label> Speichern
</div> </span>
</button>
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date=2025-01-01">
<svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" />
</svg>
<span>Zurück</span>
</a>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal"> </form>
<div class="field-label">
<label class="label">Führungsassistent durch FF gestellt?</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntaryfuehrungsassistent" value="true" >
</label>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anzahl der Posten</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="number" name="amount"
min="1" max="100" required
value="2" />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anzugsordnung</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" name="clothing" placeholder="Tuchuniform" required
value="Tuchuniform" />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anmerkung</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" name="note" />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<button class="button is-success" >
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
<span>
Speichern
</span>
</button>
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date=2025-01-01">
<svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" />
</svg>
<span>Zurück</span>
</a>
</div>
</div>
</div>
</div>
</form>
</div> </div>
</section> </section>

View File

@ -10,7 +10,7 @@ use crate::utils::test_helper::{
use chrono::{NaiveDate, NaiveTime}; use chrono::{NaiveDate, NaiveTime};
use crate::{ use crate::{
endpoints::{events::NewEventTemplate, IdPath}, endpoints::{events::NewOrEditEventTemplate, IdPath},
models::{Assignment, Event, Function, Location, Role, User}, models::{Assignment, Event, Function, Location, Role, User},
utils::{ApplicationError, TemplateResponse}, utils::{ApplicationError, TemplateResponse},
}; };
@ -37,11 +37,10 @@ pub async fn get(
let assignments = Assignment::read_all_by_event(pool.get_ref(), event.id).await?; let assignments = Assignment::read_all_by_event(pool.get_ref(), event.id).await?;
let template = NewEventTemplate { let template = NewOrEditEventTemplate {
user: user.into_inner(), user: user.into_inner(),
date: event.start.date(), date: event.start.date(),
locations, locations,
event: Some(event),
amount_of_planned_posten: assignments amount_of_planned_posten: assignments
.iter() .iter()
.filter(|x| x.function == Function::Posten) .filter(|x| x.function == Function::Posten)
@ -52,6 +51,17 @@ pub async fn get(
is_wachhabender_planned: assignments is_wachhabender_planned: assignments
.iter() .iter()
.any(|x| x.function == Function::Wachhabender), .any(|x| x.function == Function::Wachhabender),
id: Some(event.id),
start: event.start.time(),
end: event.end,
name: Some(event.name),
location: Some(event.location_id),
voluntary_wachhabender: event.voluntary_wachhabender,
voluntary_fuehrungsassistent: event.voluntary_fuehrungsassistent,
amount_of_posten: Some(event.amount_of_posten),
clothing: Some(event.clothing),
canceled: event.canceled,
note: event.note,
}; };
Ok(template.to_response()?) Ok(template.to_response()?)

View File

@ -1,8 +1,9 @@
use actix_web::{web, Responder}; use actix_web::{web, Responder};
use chrono::NaiveTime;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
endpoints::{events::NewEventTemplate, NaiveDateQuery}, endpoints::{events::NewOrEditEventTemplate, NaiveDateQuery},
models::{Location, Role, User}, models::{Location, Role, User},
utils::{ApplicationError, TemplateResponse}, utils::{ApplicationError, TemplateResponse},
}; };
@ -23,14 +24,26 @@ pub async fn get(
Location::read_by_area(pool.get_ref(), user.area_id).await? Location::read_by_area(pool.get_ref(), user.area_id).await?
}; };
let template = NewEventTemplate { let template = NewOrEditEventTemplate {
user: user.into_inner(), user: user.into_inner(),
date: query.date, date: query.date,
locations, locations,
event: None,
amount_of_planned_posten: 0, amount_of_planned_posten: 0,
is_fuehrungsassistent_planned: false, is_fuehrungsassistent_planned: false,
is_wachhabender_planned: false, is_wachhabender_planned: false,
id: None,
start: NaiveTime::from_hms_opt(18, 0, 0).unwrap(),
end: query
.date
.and_time(NaiveTime::from_hms_opt(23, 00, 00).unwrap()),
name: None,
location: None,
voluntary_wachhabender: false,
voluntary_fuehrungsassistent: false,
amount_of_posten: None,
clothing: None,
canceled: false,
note: None,
}; };
Ok(template.to_response()?) Ok(template.to_response()?)

View File

@ -1,9 +1,10 @@
use chrono::Days;
use crate::filters; use crate::filters;
use chrono::NaiveDate;
use askama::Template; use askama::Template;
use chrono::{Days, NaiveDateTime};
use chrono::{NaiveDate, NaiveTime};
use serde::Deserialize;
use crate::models::{Event, Location, Role, User}; use crate::models::{Location, Role, User};
pub mod delete; pub mod delete;
pub mod get_edit; pub mod get_edit;
@ -16,12 +17,52 @@ pub mod put_cancelation;
#[derive(Template)] #[derive(Template)]
#[cfg_attr(not(test), template(path = "events/new_or_edit.html"))] #[cfg_attr(not(test), template(path = "events/new_or_edit.html"))]
#[cfg_attr(test, template(path = "events/new_or_edit.html", block = "content"))] #[cfg_attr(test, template(path = "events/new_or_edit.html", block = "content"))]
pub struct NewEventTemplate { pub struct NewOrEditEventTemplate {
user: User, user: User,
date: NaiveDate, date: NaiveDate,
locations: Vec<Location>, locations: Vec<Location>,
event: Option<Event>, id: Option<i32>,
start: NaiveTime,
end: NaiveDateTime,
name: Option<String>,
location: Option<i32>,
voluntary_wachhabender: bool,
voluntary_fuehrungsassistent: bool,
amount_of_posten: Option<i16>,
clothing: Option<String>,
canceled: bool,
note: Option<String>,
amount_of_planned_posten: usize, amount_of_planned_posten: usize,
is_fuehrungsassistent_planned: bool, is_fuehrungsassistent_planned: bool,
is_wachhabender_planned: bool, is_wachhabender_planned: bool,
} }
#[derive(Deserialize)]
pub struct NewOrEditEventForm {
name: String,
date: NaiveDate,
start: NaiveTime,
#[serde(with = "short_date_time_format")]
end: NaiveDateTime,
location: i32,
voluntarywachhabender: Option<bool>,
voluntaryfuehrungsassistent: Option<bool>,
amount: i16,
clothing: String,
note: Option<String>,
}
mod short_date_time_format {
use chrono::NaiveDateTime;
use serde::{self, Deserialize, Deserializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
where
D: Deserializer<'de>,
{
const FORMAT: &'static str = "%Y-%m-%dT%H:%M";
let s = String::deserialize(deserializer)?;
let dt = NaiveDateTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)?;
Ok(dt)
}
}

View File

@ -1,11 +1,10 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder}; use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use chrono::{Days, NaiveDateTime}; use chrono::Days;
use garde::Validate; use garde::Validate;
use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
endpoints::IdPath, endpoints::{events::NewOrEditEventForm, IdPath},
models::{ models::{
Assignment, AssignmentChangeset, Availability, Event, EventChangeset, EventContext, Assignment, AssignmentChangeset, Availability, Event, EventChangeset, EventContext,
Function, Location, Role, User, Function, Location, Role, User,
@ -14,24 +13,11 @@ use crate::{
END_OF_DAY, START_OF_DAY, END_OF_DAY, START_OF_DAY,
}; };
#[derive(Deserialize)]
pub struct EditEventForm {
name: String,
from: NaiveDateTime,
till: NaiveDateTime,
location: i32,
voluntarywachhabender: Option<bool>,
voluntaryfuehrungsassistent: Option<bool>,
amount: i16,
clothing: String,
note: Option<String>,
}
#[actix_web::post("/events/{id}/edit")] #[actix_web::post("/events/{id}/edit")]
pub async fn post( pub async fn post(
user: web::ReqData<User>, user: web::ReqData<User>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
form: web::Form<EditEventForm>, form: web::Form<NewOrEditEventForm>,
path: web::Path<IdPath>, path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> { ) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin && user.role != Role::AreaManager { if user.role != Role::Admin && user.role != Role::AreaManager {
@ -61,7 +47,7 @@ pub async fn post(
amount_of_posten: form.amount, amount_of_posten: form.amount,
clothing: form.clothing.clone(), clothing: form.clothing.clone(),
location_id: form.location, location_id: form.location,
time: (form.from, form.till), time: (form.date.and_time(form.start), form.end),
name: form.name.clone(), name: form.name.clone(),
note: form note: form
.note .note
@ -161,7 +147,7 @@ pub async fn post(
Event::update(pool.get_ref(), event.id, changeset).await?; Event::update(pool.get_ref(), event.id, changeset).await?;
let url = utils::get_return_url_for_date(&form.from.date()); let url = utils::get_return_url_for_date(&form.date);
//println!("redirecto to {url}"); //println!("redirecto to {url}");
Ok(HttpResponse::Found() Ok(HttpResponse::Found()
.insert_header((LOCATION, url.clone())) .insert_header((LOCATION, url.clone()))

View File

@ -1,32 +1,18 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder}; use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use chrono::NaiveDateTime;
use garde::Validate; use garde::Validate;
use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
endpoints::events::NewOrEditEventForm,
models::{Event, EventChangeset, Location, Role, User}, models::{Event, EventChangeset, Location, Role, User},
utils::{self, ApplicationError}, utils::{self, ApplicationError},
}; };
#[derive(Deserialize)]
pub struct NewEventForm {
name: String,
from: NaiveDateTime,
till: NaiveDateTime,
location: i32,
voluntarywachhabender: Option<bool>,
voluntaryfuehrungsassistent: Option<bool>,
amount: i16,
clothing: String,
note: Option<String>,
}
#[actix_web::post("/events/new")] #[actix_web::post("/events/new")]
pub async fn post( pub async fn post(
user: web::ReqData<User>, user: web::ReqData<User>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
form: web::Form<NewEventForm>, form: web::Form<NewOrEditEventForm>,
) -> Result<impl Responder, ApplicationError> { ) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin && user.role != Role::AreaManager { if user.role != Role::Admin && user.role != Role::AreaManager {
return Err(ApplicationError::Unauthorized); return Err(ApplicationError::Unauthorized);
@ -44,7 +30,7 @@ pub async fn post(
amount_of_posten: form.amount, amount_of_posten: form.amount,
clothing: form.clothing.clone(), clothing: form.clothing.clone(),
location_id: form.location, location_id: form.location,
time: (form.from, form.till), time: (form.date.and_time(form.start), form.end),
name: form.name.clone(), name: form.name.clone(),
note: form note: form
.note .note
@ -60,7 +46,7 @@ pub async fn post(
Event::create(pool.get_ref(), changeset).await?; Event::create(pool.get_ref(), changeset).await?;
let url = utils::get_return_url_for_date(&form.from.date()); let url = utils::get_return_url_for_date(&form.date);
//println!("redirecto to {url}"); //println!("redirecto to {url}");
Ok(HttpResponse::Found() Ok(HttpResponse::Found()
.insert_header((LOCATION, url.clone())) .insert_header((LOCATION, url.clone()))

View File

@ -74,11 +74,11 @@ pub fn show_tree(f: &UserFunction) -> askama::Result<String> {
} }
pub fn dt_f(v: &NaiveDateTime) -> askama::Result<String> { pub fn dt_f(v: &NaiveDateTime) -> askama::Result<String> {
Ok(v.format("%F").to_string()) Ok(v.format("%Y-%m-%dT%H:%M").to_string())
} }
pub fn dt_d(v: &NaiveDateTime) -> askama::Result<String> { pub fn dt_ff(v: &NaiveDateTime) -> askama::Result<String> {
Ok(v.format("%d.%m.%Y").to_string()) Ok(v.format("%d.%m.%Y %H:%M").to_string())
} }
pub fn date_d(v: &NaiveDate) -> askama::Result<String> { pub fn date_d(v: &NaiveDate) -> askama::Result<String> {

View File

@ -3,246 +3,230 @@
{% block content %} {% block content %}
<section class="section"> <section class="section">
<div class="container"> <div class="container">
{% if let Some(event) = event %} <form method="post" action="/events/{% if let Some(id) = id %}{{ id }}/edit{% else %}new{% endif %}">
<form method="post" action="/events/{{ event.id }}/edit"> {% if let Some(name) = name %}
<h1 class="title">Event '{{ event.name }}' bearbeiten</h1> <h1 class="title">Event '{{ name }}' bearbeiten</h1>
{% else %} {% else %}
<form method="post" action="/events/new"> <h1 class="title">Neues Event anlegen für den {{ date|date_d }}</h1>
<h1 class="title">Neues Event anlegen für den {{ date|date_d }}</h1> {% endif %}
{% endif %}
{% if let Some(event) = event %} <input type="hidden" name="date" value="{{ date }}">
<div class="field is-horizontal">
<div class="field-label"></div> {% if let Some(id) = id %}
<div class="field-body"> <div class="field is-horizontal">
<div class="field is-grouped"> <div class="field-label"></div>
<div class="control"> <div class="field-body">
<button class="button is-warning" type="button" <div class="field is-grouped">
hx-put="/events/{{ event.id }}/{% if event.canceled %}uncancel{% else %}cancel{% endif %}"> <div class="control">
<svg class="icon"> <button class="button is-warning" type="button"
<use href="/static/feather-sprite.svg#alert-circle" /> hx-put="/events/{{ id }}/{% if canceled %}uncancel{% else %}cancel{% endif %}">
</svg> <svg class="icon">
<span> <use href="/static/feather-sprite.svg#alert-circle" />
{% if event.canceled %}Absage zurücknehmen{% else %}Als abgesagt markieren{% endif %} </svg>
</span> <span>
</button> {% if canceled %}Absage zurücknehmen{% else %}Als abgesagt markieren{% endif %}
</div> </span>
<div class="control"> </button>
{% let delete_disabled = amount_of_planned_posten > 0 || is_wachhabender_planned || </div>
is_fuehrungsassistent_planned %} <div class="control">
<button class="button is-danger" type="button" {% let delete_disabled = amount_of_planned_posten > 0 || is_wachhabender_planned ||
hx-delete="/events/{{ event.id }}" {{ delete_disabled|cond_show("disabled") }} is_fuehrungsassistent_planned %}
hx-trigger="confirmed"> <button class="button is-danger" type="button"
<svg class="icon"> hx-delete="/events/{{ id }}" {{ delete_disabled|cond_show("disabled") }}
<use href="/static/feather-sprite.svg#x-circle" /> hx-trigger="confirmed">
</svg> <svg class="icon">
<span>Löschen</span> <use href="/static/feather-sprite.svg#x-circle" />
</button> </svg>
</div> <span>Löschen</span>
{% if delete_disabled %} </button>
<p class="help is-align-content-center"> </div>
Löschen nicht möglich, da bereits eine Planung existiert. {% if delete_disabled %}
</p> <p class="help is-align-content-center">
{% endif %} Löschen nicht möglich, da bereits eine Planung existiert.
</p>
{% endif %}
</div>
</div>
</div>
{% endif %}
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Veranstaltungsname</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" name="name" placeholder="Wave Gotik Treffen" required
{{ name|insert_value|safe }} {{ canceled|cond_show("disabled") }} />
</div> </div>
</div> </div>
</div> </div>
{% endif %} </div>
{% let disabled = event.is_some() && event.as_ref().unwrap().canceled %} <div class="field is-horizontal">
<div class="field-label">
<div class="field is-horizontal"> <label class="label">Startzeit - Endzeit</label>
<div class="field-label"> </div>
<label class="label">Veranstaltungsname</label> <div class="field-body">
<div class="field">
<input class="input" type="time" name="start" required
value="{{ start|time }}" {{ canceled|cond_show("disabled") }} />
</div> </div>
<div class="field-body"> <div class="field">
<div class="field"> {% let tomorrow = date.checked_add_days(Days::new(1)).unwrap() %}
<div class="control"> <input class="input" type="datetime-local" name="end" required
<input class="input" name="name" placeholder="Wave Gotik Treffen" required value="{{ end|dt_f }}" {{ canceled|cond_show("disabled") }}
{% if let Some(event)=event %} value="{{ event.name }}" {% endif %} min="{{ date }}T00:00" max="{{ tomorrow }}T23:59" />
{{ disabled|cond_show("disabled") }} /> </div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Veranstaltungsort</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<div class="select is-fullwidth">
<select name="location" required {{ canceled|cond_show("disabled") }}>
{% for l in locations %}
<option value="{{ l.id }}"
{{ (location.is_some() && *location.as_ref().unwrap()==l.id)|cond_show("selected") }}>
{{ l.name }}
{% if user.role == Role::Admin %} - ({{ l.area.as_ref().unwrap().name }}){% endif %}
</option>
{% endfor %}
</select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"> <div class="field-label">
<label class="label">Startzeit - Endzeit</label> <label class="label">Wachhabender durch FF gestellt?</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field is-narrow">
<input class="input" type="datetime-local" id="from" name="from" required {% let wh_disabled = id.is_some() && is_wachhabender_planned %}
value="{% if let Some(event)=event %}{{ event.start|dt_f }}{% else %}{{ date }}{% endif <div class="control">
%}" <label class="checkbox">
{{ disabled|cond_show("disabled") }} min="{{ date }}T00:00" max="{{ date }}T23:59" /> <input class="checkbox" type="checkbox" name="voluntarywachhabender" value="true"
{{ (wh_disabled || canceled)|cond_show("disabled")}}
{{ voluntary_wachhabender|cond_show("checked") }}>
</label>
</div> </div>
<div class="field"> {% if wh_disabled %}
<input class="input" type="datetime-local" id="till" name="till" required <p class="help">
value="{% if let Some(event)=event %}{{ event.end|dt_f }}{% else %}{{ date }}{% endif %}" Keine Änderung möglich, da ein Wachhabender bereits eingeplant ist. Diesen zuerst entplanen!
{{ disabled|cond_show("disabled") }} min="{{ date }}T00:00" </p>
max="{{ date.checked_add_days(Days::new(1)).unwrap() }}T23:59" /> {% endif %}
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Führungsassistent durch FF gestellt?</label>
</div>
<div class="field-body">
<div class="field is-narrow">
{% let fa_disabled = id.is_some() && is_fuehrungsassistent_planned %}
<div class="control">
<label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntaryfuehrungsassistent" value="true"
{{ (fa_disabled || canceled)|cond_show("disabled") }}
{{ voluntary_fuehrungsassistent|cond_show("checked") }}>
</label>
</div>
{% if fa_disabled %}
<p class="help">
Keine Änderung möglich, da ein Führungsassistent bereits eingeplant ist. Diesen zuerst entplanen!
</p>
{% endif %}
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anzahl der Posten</label>
</div>
<div class="field-body">
<div class="field">
{% let posten_planned = id.is_some() && amount_of_planned_posten > 0 %}
<div class="control">
<input class="input" type="number" name="amount"
min="{% if posten_planned %}{{ amount_of_planned_posten }}{% else %}0{% endif %}" max="100"
required {{ amount_of_posten|insert_value|safe }} {{ canceled|cond_show("disabled") }} />
</div>
{% if posten_planned %}
<p class="help">
Mindestens {{ amount_of_planned_posten }} Posten, da bereits diese Anzahl eingeplant ist. Zum
verringern diese erst entplanen!
</p>
{% endif %}
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anzugsordnung</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" name="clothing" placeholder="Tuchuniform" required {{ clothing|insert_value|safe }}
{{ canceled|cond_show("disabled") }} />
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"> <div class="field-label">
<label class="label">Veranstaltungsort</label> <label class="label">Anmerkung</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field is-narrow"> <div class="field">
<div class="control"> <div class="control">
<div class="select is-fullwidth"> <input class="input" name="note" {{ note|insert_value|safe }} {{ canceled|cond_show("disabled") }} />
<select name="location" required {{ disabled|cond_show("disabled") }}>
{% for location in locations %}
<option value="{{ location.id }}" {% if let Some(event)=event %}{{
(event.location_id==location.id)|cond_show("selected") }}{% endif %}>{{ location.name }}{%
if
user.role == Role::Admin %} - ({{
location.area.as_ref().unwrap().name }}){% endif %}
</option>
{% endfor %}
</select>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"> <div class="field-label"></div>
<label class="label">Wachhabender durch FF gestellt?</label> <div class="field-body">
</div> <div class="field is-grouped">
<div class="field-body"> <div class="control">
<div class="field is-narrow"> <button class="button is-success" {{ canceled|cond_show("disabled") }}>
{% let wh_disabled = event.is_some() && is_wachhabender_planned %} <svg class="icon">
<div class="control"> <use href="/static/feather-sprite.svg#check-circle" />
<label class="checkbox"> </svg>
<input class="checkbox" type="checkbox" name="voluntarywachhabender" value="true" {{ <span>
wh_disabled|cond_show("disabled")}} {% if let Some(event)=event %} {{ {% if id.is_some() %}Speichern{% else %}Erstellen{% endif %}
event.voluntary_wachhabender|cond_show("checked") }} {% endif %} {{ </span>
disabled|cond_show("disabled") </button>
}}> </div>
</label> <div class="control">
</div> <a class="button is-link is-light" hx-boost="true" href="/?date={{ date }}">
{% if wh_disabled %} <svg class="icon">
<p class="help"> <use href="/static/feather-sprite.svg#arrow-left" />
Keine Änderung möglich, da ein Wachhabender bereits eingeplant ist. Diesen zuerst entplanen! </svg>
</p> <span>Zurück</span>
{% endif %} </a>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="field is-horizontal"> </form>
<div class="field-label">
<label class="label">Führungsassistent durch FF gestellt?</label>
</div>
<div class="field-body">
<div class="field is-narrow">
{% let fa_disabled = event.is_some() && is_fuehrungsassistent_planned %}
<div class="control">
<label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntaryfuehrungsassistent" value="true" {{
fa_disabled|cond_show("disabled") }} {% if let Some(event)=event %} {{
event.voluntary_fuehrungsassistent|cond_show("checked") }} {% endif %} {{
disabled|cond_show("disabled") }}>
</label>
</div>
{% if fa_disabled %}
<p class="help">
Keine Änderung möglich, da ein Führungsassistent bereits eingeplant ist. Diesen zuerst entplanen!
</p>
{% endif %}
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anzahl der Posten</label>
</div>
<div class="field-body">
<div class="field">
{% let posten_planned = event.is_some() && amount_of_planned_posten > 0 %}
<div class="control">
<input class="input" type="number" name="amount"
min="{% if posten_planned %}{{ amount_of_planned_posten }}{% else %}0{% endif %}" max="100"
required
{% if let Some(event)=event %} value="{{ event.amount_of_posten }}" {% endif %} {{
disabled|cond_show("disabled") }} />
</div>
{% if posten_planned %}
<p class="help">
Mindestens {{ amount_of_planned_posten }} Posten, da bereits diese Anzahl eingeplant ist. Zum
verringern
diese erst entplanen!
</p>
{% endif %}
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anzugsordnung</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" name="clothing" placeholder="Tuchuniform" required {% if let Some(event)=event %}
value="{{ event.clothing }}" {% endif %} {{ disabled|cond_show("disabled") }} />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Anmerkung</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" name="note" {% if let Some(event)=event %} {{ event.note|insert_value }} {% endif
%} {{ disabled|cond_show("disabled") }} />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<button class="button is-success" {{ disabled|cond_show("disabled") }}>
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
<span>
{% if event.is_some() %}
Speichern
{% else %}
Erstellen
{% endif %}
</span>
</button>
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date={{ date }}">
<svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" />
</svg>
<span>Zurück</span>
</a>
</div>
</div>
</div>
</div>
</form>
</div> </div>
</section> </section>
{% endblock %} {% endblock %}

View File

@ -53,7 +53,7 @@
<div class="level"> <div class="level">
<div class="level-left"> <div class="level-left">
<h3 class="title is-3"> <h3 class="title is-3">
Events am {{ date.format("%d.%m.%Y") }} Events am {{ date|date_d }}
</h3> </h3>
</div> </div>
{% if user.role == Role::Admin || user.role == Role::AreaManager && (selected_area.is_none() || {% if user.role == Role::Admin || user.role == Role::AreaManager && (selected_area.is_none() ||
@ -106,7 +106,7 @@
{% endif %} {% endif %}
<div class="cell"> <div class="cell">
<p><b>Uhrzeit:</b> {{ event.start|dt_t }} Uhr - {{ event.end|dt_f }} Uhr</p> <p><b>Uhrzeit:</b> {{ event.start|dt_t }} Uhr - {{ event.end|dt_ff }} Uhr</p>
</div> </div>
<div class="cell"> <div class="cell">
@ -135,9 +135,11 @@
</div> </div>
{% endif %} {% endif %}
{% if wachhabender.is_some() || fuehrungsassistent.is_some() || posten.len() > 0 || vehicle.len() > 0 %}
<div class="cell is-col-span-2"> <div class="cell is-col-span-2">
<hr class="my-1"> <hr class="my-1">
</div> </div>
{% endif %}
{% if let Some(wh) = wachhabender %} {% if let Some(wh) = wachhabender %}
<div class="cell"> <div class="cell">
@ -178,7 +180,7 @@
<div class="level"> <div class="level">
<div class="level-left"> <div class="level-left">
<h3 class="title is-3"> <h3 class="title is-3">
Verfügbarkeiten am {{ date.format("%d.%m.%Y") }} Verfügbarkeiten am {{ date|date_d }}
</h3> </h3>
</div> </div>
{% if selected_area.is_none() || selected_area.unwrap() == user.area_id %} {% if selected_area.is_none() || selected_area.unwrap() == user.area_id %}
@ -220,7 +222,7 @@
{{ u.function|show_tree|safe }} {{ u.function|show_tree|safe }}
</td> </td>
<td> <td>
{{ availabillity.start.format("%R") }} bis {{ availabillity.end.format("%d.%m.%Y %R") }} {{ availabillity.start|dt_t }} Uhr bis {{ availabillity.end|dt_ff }} Uhr
</td> </td>
<td> <td>
{{ availabillity.comment.as_deref().unwrap_or("") }} {{ availabillity.comment.as_deref().unwrap_or("") }}