refactor: new and edit availability

This commit is contained in:
Max Hohlfeld 2024-07-02 23:23:10 +02:00
parent d0c0b2aa38
commit 016e690dbd
11 changed files with 263 additions and 369 deletions

View File

@ -15,7 +15,7 @@ pub async fn delete(
if let Ok(availabillity_in_db) = Availabillity::read_by_id(pool.get_ref(), path.id).await {
if availabillity_in_db.user_id == user.id {
if let Ok(_) = Availabillity::delete(pool.get_ref(), availabillity_in_db.id).await {
return HttpResponse::NoContent().finish();
return HttpResponse::Ok().finish();
}
}
}

View File

@ -1,25 +1,31 @@
use actix_web::{web, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use chrono::NaiveDate;
use serde::Deserialize;
use crate::{endpoints::NaiveDateQuery, models::{Role, User}};
use crate::endpoints::availability::NewOrEditAvailabilityTemplate;
use crate::models::User;
#[derive(Template)]
#[template(path = "availability/new.html")]
struct AvailabillityNewTemplate {
user: User,
#[derive(Deserialize)]
struct AvailabilityNewQuery {
#[serde(rename(deserialize = "wholeday"))]
whole_day: Option<bool>,
date: NaiveDate,
}
#[actix_web::get("/availabillity/new")]
pub async fn get(
user: web::ReqData<User>,
query: web::Query<NaiveDateQuery>,
query: web::Query<AvailabilityNewQuery>,
) -> impl Responder {
let template = AvailabillityNewTemplate {
let template = NewOrEditAvailabilityTemplate {
user: user.into_inner(),
date: query.date,
whole_day: query.whole_day.unwrap_or(true),
id: None,
start_time: None,
end_time: None,
comment: None,
};
template.to_response()

View File

@ -1,45 +1,46 @@
use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use chrono::NaiveTime;
use serde::Deserialize;
use sqlx::PgPool;
use crate::{
endpoints::{availability::AvailabillityEditTemplate, IdPath},
endpoints::{availability::NewOrEditAvailabilityTemplate, IdPath},
models::{Availabillity, User},
};
#[derive(Deserialize)]
struct EditAvailabilityQuery {
#[serde(rename(deserialize = "wholeday"))]
whole_day: Option<bool>,
}
#[actix_web::get("/availabillity/edit/{id}")]
pub async fn get(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
query: web::Query<EditAvailabilityQuery>,
) -> impl Responder {
if let Ok(availabillity) = Availabillity::read_by_id(pool.get_ref(), path.id).await {
if availabillity.user_id == user.id {
let start_time = availabillity
.start_time
.unwrap_or(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
.format("%R")
.to_string();
.and_then(|d| Some(d.format("%R").to_string()));
let end_time = availabillity
.end_time
.unwrap_or(NaiveTime::from_hms_opt(23, 59, 0).unwrap())
.format("%R")
.to_string();
.and_then(|d| Some(d.format("%R").to_string()));
let has_time = availabillity.start_time.is_some() && availabillity.end_time.is_some();
let comment = availabillity.comment.unwrap_or(String::new());
let template = AvailabillityEditTemplate {
let template = NewOrEditAvailabilityTemplate {
user: user.into_inner(),
date: availabillity.date,
id: path.id,
start_time,
end_time,
has_time,
comment,
whole_day: query.whole_day.unwrap_or(!has_time),
id: Some(path.id),
start_time: start_time.as_deref(),
end_time: end_time.as_deref(),
comment: availabillity.comment.as_deref(),
};
return template.to_response();

View File

@ -1,23 +1,24 @@
use askama::Template;
use chrono::NaiveDate;
use crate::models::{User,Role};
use crate::filters;
use crate::models::{Role, User};
pub mod delete;
pub mod get_new;
pub mod get_update;
pub mod get_overview;
pub mod get_update;
pub mod post_new;
pub mod post_update;
#[derive(Template)]
#[template(path = "availability/edit.html")]
struct AvailabillityEditTemplate {
#[template(path = "availability/new_or_edit.html")]
struct NewOrEditAvailabilityTemplate<'a> {
user: User,
date: NaiveDate,
id: i32,
start_time: String,
end_time: String,
has_time: bool,
comment: String,
whole_day: bool,
id: Option<i32>,
start_time: Option<&'a str>,
end_time: Option<&'a str>,
comment: Option<&'a str>,
}

View File

@ -40,6 +40,12 @@ pub async fn post(
.finish();
}
}
if !has_changed {
return HttpResponse::Found()
.insert_header((LOCATION, "/"))
.finish();
}
}
}

View File

@ -11,3 +11,15 @@ pub fn show_area_query(a: &Option<i32>, first: bool) -> ::askama::Result<String>
return Ok(String::new());
}
}
pub fn cond_show(show: &bool, text: &str) -> askama::Result<String> {
return if *show {
Ok(String::from(text))
} else {
Ok(String::new())
}
}
pub fn invert(b: &bool) -> askama::Result<bool> {
return Ok(!b);
}

View File

@ -1,117 +0,0 @@
{% extends "nav.html" %}
{% block content %}
<section class="section">
<div class="container">
<form method="post" action="/availabillity/edit/{{ id }}">
<h1 class="title">Bearbeite Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
<input type="hidden" name="date" value="{{ date }}">
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Dauer</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<label class="radio">
{% if has_time %}
<input type="radio" id="wholeDay" name="hasTime">
{% else %}
<input type="radio" id="wholeDay" name="hasTime" checked>
{% endif %}
ganztägig
<label class="radio">
{% if has_time %}
<input type="radio" id="partDay" name="hasTime" checked>
{% else %}
<input type="radio" id="partDay" name="hasTime">
{% endif %}
zeitweise
</label>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Von - Bis</label>
</div>
<div class="field-body">
<div class="field">
<input class="input" type="time" id="from" name="from" value="{{ start_time }}" disabled>
</div>
<div class="field">
<input class="input" type="time" id="till" name="till" value="{{ end_time }}" disabled>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Kommentar</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<textarea class="textarea" name="comment" placeholder="nur Posten, nur Wachhabender, etc..">{{ comment }}</textarea>
</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">
<input class="button is-link" type="submit" value="Speichern">
</div>
<div class="control">
<a class="button is-link is-light" href="/?date={{ date }}">Zurück</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
<script>
const wholeDay = document.getElementById("wholeDay");
const partDay = document.getElementById("partDay");
const from = document.getElementById("from");
const till = document.getElementById("till");
let lastFrom = null;
let lastTill = null;
wholeDay.addEventListener("click", (event) => {
from.disabled = true
till.disabled = true
lastFrom = from.value;
lastTill = till.value;
from.value = null;
till.value = null;
});
partDay.addEventListener("click", (event) => {
from.disabled = false
till.disabled = false
if (lastFrom != null) {
from.value = lastFrom;
}
if (lastTill != null) {
till.value = lastTill;
}
});
</script>
{% endblock %}

View File

@ -1,109 +0,0 @@
{% extends "nav.html" %}
{% block content %}
<section class="section">
<div class="container">
<form method="post" action="/availabillity/new">
<h1 class="title">Neue Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
<input type="hidden" name="date" value="{{ date }}">
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Dauer</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<label class="radio">
<input type="radio" id="wholeDay" name="hasTime" checked>
ganztägig
<label class="radio">
<input type="radio" id="partDay" name="hasTime">
zeitweise
</label>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Von - Bis</label>
</div>
<div class="field-body">
<div class="field">
<input class="input" type="time" id="from" name="from" value="00:00" disabled>
</div>
<div class="field">
<input class="input" type="time" id="till" name="till" value="23:59" disabled>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Kommentar</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<textarea class="textarea" name="comment" placeholder="nur Posten, nur Wachhabender, etc.."></textarea>
</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">
<input class="button is-link" type="submit" value="Erstellen">
</div>
<div class="control">
<a class="button is-link is-light" href="/?date={{ date }}">Zurück</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
<script>
const wholeDay = document.getElementById("wholeDay");
const partDay = document.getElementById("partDay");
const from = document.getElementById("from");
const till = document.getElementById("till");
let lastFrom = null;
let lastTill = null;
wholeDay.addEventListener("click", (event) => {
from.disabled = true
till.disabled = true
lastFrom = from.value;
lastTill = till.value;
from.value = null;
till.value = null;
});
partDay.addEventListener("click", (event) => {
from.disabled = false
till.disabled = false
if (lastFrom != null) {
from.value = lastFrom;
}
if (lastTill != null) {
till.value = lastTill;
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,98 @@
{% extends "nav.html" %}
{% block content %}
<section class="section">
<div class="container">
{% if id.is_some() %}
<form method="post" action="/availabillity/edit/{{ id.unwrap() }}">
<h1 class="title">Bearbeite Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
{% else %}
<form method="post" action="/availabillity/new">
<h1 class="title">Neue Vefügbarkeit für den {{ date.format("%d.%m.%Y") }}</h1>
{% endif %}
<input type="hidden" name="date" value="{{ date }}">
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Dauer</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<label class="radio">
{% if id.is_some() %}
<input type="radio" name="hasTime" hx-get="/availabillity/edit/{{ id.unwrap() }}?wholeday=true"
hx-target="closest body" {{ whole_day|cond_show("checked") }} />
{% else %}
<input type="radio" name="hasTime" hx-get="/availabillity/new?date={{ date }}&wholeday=true"
hx-target="closest body" {{ whole_day|cond_show("checked") }} />
{% endif %}
ganztägig
<label class="radio">
{% if id.is_some() %}
<input type="radio" name="hasTime" hx-get="/availabillity/edit/{{ id.unwrap() }}?wholeday=false"
hx-target="closest body" {{ whole_day|invert|cond_show("checked") }} />
{% else %}
<input type="radio" name="hasTime" hx-get="/availabillity/new?date={{ date }}&wholeday=false"
hx-target="closest body" {{ whole_day|invert|cond_show("checked") }} />
{% endif %}
zeitweise
</label>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Von - Bis</label>
</div>
<div class="field-body">
<div class="field">
<input class="input" type="time" id="from" name="from" value='{{ start_time.unwrap_or("00:00") }}' {{
whole_day|cond_show("disabled") }} {{ whole_day|invert|cond_show("required") }}>
</div>
<div class="field">
<input class="input" type="time" id="till" name="till" value='{{ end_time.unwrap_or("23:59") }}' {{
whole_day|cond_show("disabled") }} {{ whole_day|invert|cond_show("required") }}>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Kommentar</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<textarea class="textarea" name="comment" placeholder="nur Posten, nur Wachhabender, etc..">{{
comment.unwrap_or("") }}</textarea>
</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">
{% if id.is_some() %}
<input class="button is-link" type="submit" value="Bearbeiten">
{% else %}
<input class="button is-link" type="submit" value="Erstellen">
{% endif %}
</div>
<div class="control">
<a class="button is-link is-light" href="/?date={{ date }}" hx-boost="true">Zurück</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
{% endblock %}

View File

@ -9,110 +9,108 @@
<input type="hidden" name="date" value="{{ date }}">
<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" />
</div>
</div>
</div>
<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 />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Startzeit - Endzeit</label>
</div>
<div class="field-body">
<div class="field">
<input class="input" type="time" id="from" name="from" value="00:00">
</div>
<div class="field">
<input class="input" type="time" id="till" name="till" value="23:59">
</div>
</div>
<div class="field-label">
<label class="label">Startzeit - Endzeit</label>
</div>
<div class="field-body">
<div class="field">
<input class="input" type="time" id="from" name="from" value="00:00" required>
</div>
<div class="field">
<input class="input" type="time" id="till" name="till" value="23:59" required>
</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">
{% for location in locations %}
<option value="{{ location.id }}">{{ location.name }}{% if user.role == Role::Admin %} - ({{ location.area.as_ref().unwrap().name }}){% endif %}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<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>
{% for location in locations %}
<option value="{{ location.id }}">{{ location.name }}{% if user.role == Role::Admin %} - ({{
location.area.as_ref().unwrap().name }}){% endif %}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Wachhabender 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="voluntarywachhabender" value="true">
</label>
</div>
</div>
</div>
<div class="field-label">
<label class="label">Wachhabender 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="voluntarywachhabender" 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"/>
</div>
</div>
</div>
<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 />
</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" />
</div>
</div>
</div>
<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 />
</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">
<input class="button is-link" type="submit" value="Erstellen">
</div>
<div class="control">
<a class="button is-link is-light" href="/locations">Zurück</a>
</div>
</div>
</div>
<div class="field-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<input class="button is-link" type="submit" value="Erstellen">
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date={{ date }}">Zurück</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
<script>
</script>
{% endblock %}

View File

@ -63,7 +63,14 @@
{% if (user.role == Role::Admin || user.role == Role::AreaManager) && (selected_area.is_none() ||
selected_area.unwrap() == user.area_id) %}
<div class="level-right">
<a class="button" href="/events/new?date={{ date }}">Neues Event für diesen Tag</a>
<a class="button is-link is-light" hx-boost="true" href="/events/new?date={{ date }}">
<span class="icon">
<svg class="feather">
<use href="/static/feather-sprite.svg#plus-circle" />
</svg>
</span>
<span>Neues Event für diesen Tag</span>
</a>
</div>
{% endif %}
</div>
@ -80,7 +87,8 @@
<h5 class="title is-5 level-left">{{ event.name }}</h5>
<span class="level-right">
{% if user.role == Role::AreaManager || user.role == Role::Admin %}
<a href="/assignments/new?event={{ event.id }}" class="button is-primary level-item">Planen</a>
<a href="/assignments/new?event={{ event.id }}" hx-boost="true"
class="button is-primary level-item">Planen</a>
<a href="" class="button is-primary-light level-item">bearbeiten</a>
<a href="" class="button is-warning level-item">als abgesagt markieren</a>
{% endif %}
@ -105,10 +113,16 @@
Verfügbarkeiten am {{ date.format("%d.%m.%Y") }}
</h3>
</div>
{% if (user.role == Role::Admin || user.role == Role::AreaManager) && (selected_area.is_none() ||
selected_area.unwrap() == user.area_id) %}
{% if selected_area.is_none() || selected_area.unwrap() == user.area_id %}
<div class="level-right">
<a class="button" href="/availabillity/new?date={{ date }}">Neue Verfügbarkeit für diesen Tag</a>
<a class="button is-link is-light" hx-boost="true" href="/availabillity/new?date={{ date }}">
<span class="icon">
<svg class="feather">
<use href="/static/feather-sprite.svg#plus-circle" />
</svg>
</span>
<span>Neue Verfügbarkeit für diesen Tag</span>
</a>
</div>
{% endif %}
</div>
@ -132,7 +146,7 @@
<tbody>
{% for availabillity in availabillities %}
{% let u = availabillity.user.as_ref().unwrap() %}
<tr id="availabillity-{{ availabillity.id }}">
<tr>
<td>{{ u.name }}</td>
<td>
{% match u.function %}
@ -157,8 +171,10 @@
<td>
{% if availabillity.user_id == user.id %}
<div class="buttons is-right">
<a class="button is-link" href="/availabillity/edit/{{ availabillity.id }}">Bearbeiten</a>
<button class="button is-danger" name="delete-availabillity">Löschen</button>
<a class="button is-link" hx-boost="true"
href="/availabillity/edit/{{ availabillity.id }}">Bearbeiten</a>
<button class="button is-danger" hx-delete="/availabillity/delete/{{ availabillity.id }}"
hx-target="closest tr" hx-swap="delete">Löschen</button>
</div>
{% endif %}
</td>
@ -172,22 +188,4 @@
</div>
</section>
</div>
<script>
document.getElementsByName("delete-availabillity")
.forEach(ele => ele.addEventListener("click", (event) => {
const id = event.target.closest("tr").id.split('-')[1];
event.target.classList.add("is-loading");
fetch(`/availabillity/delete/${id}`, {method: "DELETE"})
.then(response => {
if (response.status == 204) {
document.getElementById(`availabillity-${id}`).remove()
} else {
event.target.classList.remove("is-loading");
console.log("Fehler beim Löschen.")
}
});
}));
</script>
{% endblock %}