feat: cancelation of events

This commit is contained in:
Max Hohlfeld 2025-01-16 18:07:34 +01:00
parent 290c7bcc43
commit 0bb5f54f16
10 changed files with 210 additions and 31 deletions

View File

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE event SET canceled = $1 WHERE id = $2;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Bool",
"Int4"
]
},
"nullable": []
},
"hash": "7bc06d40e0e7f43f73861bb5fa9fe954aea7c821abf785b68d0731fcaf0f4845"
}

View File

@ -12,6 +12,37 @@ snapshot_kind: text
<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-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<button class="button is-warning" type="button"
hx-put="/events/1/cancel">
<svg class="icon">
<use href="/static/feather-sprite.svg#alert-circle" />
</svg>
<span>
Als abgesagt markieren
</span>
</button>
</div>
<div class="control">
<button class="button is-danger" type="button">
<svg class="icon">
<use href="/static/feather-sprite.svg#x-circle" />
</svg>
<span>Löschen</span>
</button>
</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">Veranstaltungsname</label> <label class="label">Veranstaltungsname</label>
@ -19,7 +50,7 @@ snapshot_kind: text
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input class="input" name="name" placeholder="Wave Gotik Treffen" required value="Vorstellung" /> <input class="input" name="name" placeholder="Wave Gotik Treffen" required value="Vorstellung" />
</div> </div>
</div> </div>
</div> </div>
@ -32,10 +63,10 @@ snapshot_kind: text
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
<input class="input" type="time" id="from" name="from" required value="08:00" /> <input class="input" type="time" id="from" name="from" required value="08:00" />
</div> </div>
<div class="field"> <div class="field">
<input class="input" type="time" id="till" name="till" required value="10:00" /> <input class="input" type="time" id="till" name="till" required value="10:00" />
</div> </div>
</div> </div>
</div> </div>
@ -48,10 +79,9 @@ snapshot_kind: text
<div class="field is-narrow"> <div class="field is-narrow">
<div class="control"> <div class="control">
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select name="location" required> <select name="location" required >
<option value="1">Hauptbahnhof - (Leipzig Ost) <option value="1" selected>Hauptbahnhof - (Leipzig Ost)
selected
</option> </option>
</select> </select>
@ -70,7 +100,7 @@ snapshot_kind: text
<div class="control"> <div class="control">
<label class="checkbox"> <label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntarywachhabender" value="true" > <input class="checkbox" type="checkbox" name="voluntarywachhabender" value="true" >
</label> </label>
</div> </div>
@ -87,7 +117,7 @@ snapshot_kind: text
<div class="control"> <div class="control">
<label class="checkbox"> <label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntaryfuehrungsassistent" value="true" > <input class="checkbox" type="checkbox" name="voluntaryfuehrungsassistent" value="true" >
</label> </label>
</div> </div>
@ -105,7 +135,7 @@ snapshot_kind: text
<div class="control"> <div class="control">
<input class="input" type="number" name="amount" <input class="input" type="number" name="amount"
min="1" max="100" required min="1" max="100" required
value="2" /> value="2" />
</div> </div>
</div> </div>
@ -120,7 +150,7 @@ snapshot_kind: text
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input class="input" name="clothing" placeholder="Tuchuniform" required <input class="input" name="clothing" placeholder="Tuchuniform" required
value="Tuchuniform" /> value="Tuchuniform" />
</div> </div>
</div> </div>
</div> </div>
@ -133,7 +163,7 @@ snapshot_kind: text
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input class="input" name="note" /> <input class="input" name="note" />
</div> </div>
</div> </div>
</div> </div>
@ -144,10 +174,24 @@ snapshot_kind: text
<div class="field-body"> <div class="field-body">
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
<input class="button is-link" type="submit" value="Erstellen"> <button class="button is-success" >
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
<span>
Speichern
</span>
</button>
</div> </div>
<div class="control"> <div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date=2025-01-01">Zurück</a> <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>

View File

@ -9,6 +9,7 @@ pub mod get_new;
pub mod get_plan; pub mod get_plan;
pub mod post_new; pub mod post_new;
pub mod post_edit; pub mod post_edit;
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"))]

View File

@ -0,0 +1,56 @@
use actix_http::header::LOCATION;
use actix_web::{web, HttpResponse, Responder};
use sqlx::PgPool;
use crate::{
endpoints::IdPath,
models::{Event, Role, User},
utils::{self, ApplicationError},
};
#[actix_web::put("/events/{id}/cancel")]
pub async fn put_cancel(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> {
handle_set_event_cancelation_to(user, pool, path, true).await
}
#[actix_web::put("/events/{id}/uncancel")]
pub async fn put_uncancel(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> {
handle_set_event_cancelation_to(user, pool, path, false).await
}
async fn handle_set_event_cancelation_to(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
cancelation_state: bool,
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin && user.role != Role::AreaManager {
return Err(ApplicationError::Unauthorized);
}
let Some(event) = Event::read_by_id_including_location(pool.get_ref(), path.id).await? else {
return Ok(HttpResponse::NotFound().finish());
};
if event.canceled != cancelation_state {
if user.role != Role::Admin && user.area_id != event.location.as_ref().unwrap().area_id {
return Ok(HttpResponse::BadRequest().body("Can't use location outside of your area"));
}
Event::update_cancelation(pool.get_ref(), event.id, cancelation_state).await?;
}
let url = utils::get_return_url_for_date(&event.date);
println!("redirecto to {url}");
Ok(HttpResponse::Ok()
.insert_header(("HX-LOCATION", url))
.finish())
}

View File

@ -56,6 +56,8 @@ pub fn init(cfg: &mut ServiceConfig) {
cfg.service(availability::post_new::post); cfg.service(availability::post_new::post);
cfg.service(availability::post_update::post); cfg.service(availability::post_update::post);
cfg.service(events::put_cancelation::put_cancel);
cfg.service(events::put_cancelation::put_uncancel);
cfg.service(events::get_new::get); cfg.service(events::get_new::get);
cfg.service(events::post_new::post); cfg.service(events::post_new::post);
cfg.service(events::get_plan::get); cfg.service(events::get_plan::get);

View File

@ -158,4 +158,10 @@ impl Event {
Ok(()) Ok(())
} }
pub async fn update_cancelation(pool: &PgPool, id: i32, canceled: bool) -> Result<()> {
query!("UPDATE event SET canceled = $1 WHERE id = $2;", canceled, id).execute(pool).await?;
Ok(())
}
} }

View File

@ -51,7 +51,7 @@ impl Location {
let locations = records let locations = records
.iter() .iter()
.map(|lr| Location { .map(|lr| Location {
id: lr.id, id: lr.locationid,
name: lr.name.to_string(), name: lr.name.to_string(),
area_id: lr.areaid, area_id: lr.areaid,
area: Some(Area { area: Some(Area {

View File

@ -27,6 +27,7 @@ $primary: $crimson,
@forward "bulma/sass/elements/table"; @forward "bulma/sass/elements/table";
@forward "bulma/sass/elements/title"; @forward "bulma/sass/elements/title";
@forward "bulma/sass/elements/delete"; @forward "bulma/sass/elements/delete";
@forward "bulma/sass/elements/notification";
@forward "bulma/sass/form"; @forward "bulma/sass/form";

View File

@ -13,6 +13,37 @@
<input type="hidden" name="date" value="{{ date }}"> <input type="hidden" name="date" value="{{ date }}">
{% if let Some(event) = event %}
<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-warning" type="button"
hx-put="/events/{{ event.id }}/{% if event.canceled %}uncancel{% else %}cancel{% endif %}">
<svg class="icon">
<use href="/static/feather-sprite.svg#alert-circle" />
</svg>
<span>
{% if event.canceled %}Absage zurücknehmen{% else %}Als abgesagt markieren{% endif %}
</span>
</button>
</div>
<div class="control">
<button class="button is-danger" type="button">
<svg class="icon">
<use href="/static/feather-sprite.svg#x-circle" />
</svg>
<span>Löschen</span>
</button>
</div>
</div>
</div>
</div>
{% endif %}
{% let disabled = event.is_some() && event.as_ref().unwrap().canceled %}
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label"> <div class="field-label">
<label class="label">Veranstaltungsname</label> <label class="label">Veranstaltungsname</label>
@ -21,7 +52,7 @@
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input class="input" name="name" placeholder="Wave Gotik Treffen" required {% if let Some(event)=event <input class="input" name="name" placeholder="Wave Gotik Treffen" required {% if let Some(event)=event
%} value="{{ event.name }}" {% endif %} /> %} value="{{ event.name }}" {% endif %} {{ disabled|cond_show("disabled") }} />
</div> </div>
</div> </div>
</div> </div>
@ -35,11 +66,11 @@
{% let f = "%H:%M" %} {% let f = "%H:%M" %}
<div class="field"> <div class="field">
<input class="input" type="time" id="from" name="from" required value="{% if let Some(event)=event <input class="input" type="time" id="from" name="from" required value="{% if let Some(event)=event
%}{{ event.start_time.format(f) }}{% else %}0:00{% endif %}" /> %}{{ event.start_time.format(f) }}{% else %}0:00{% endif %}" {{ disabled|cond_show("disabled") }} />
</div> </div>
<div class="field"> <div class="field">
<input class="input" type="time" id="till" name="till" required value="{% if let Some(event)=event <input class="input" type="time" id="till" name="till" required value="{% if let Some(event)=event
%}{{ event.end_time.format(f) }}{% else %}23:59{% endif %}" /> %}{{ event.end_time.format(f) }}{% else %}23:59{% endif %}" {{ disabled|cond_show("disabled") }} />
</div> </div>
</div> </div>
</div> </div>
@ -52,12 +83,12 @@
<div class="field is-narrow"> <div class="field is-narrow">
<div class="control"> <div class="control">
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select name="location" required> <select name="location" required {{ disabled|cond_show("disabled") }}>
{% for location in locations %} {% for location in locations %}
<option value="{{ location.id }}">{{ location.name }}{% if user.role == Role::Admin %} - ({{ <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 %} location.area.as_ref().unwrap().name }}){% endif %}
{% if let Some(event) = event %}{{ (event.location_id == location.id)|cond_show("selected") }}{%
endif %}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
@ -78,7 +109,8 @@
<label class="checkbox"> <label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntarywachhabender" value="true" {{ <input class="checkbox" type="checkbox" name="voluntarywachhabender" value="true" {{
wh_disabled|cond_show("disabled")}} {% if let Some(event)=event %} {{ wh_disabled|cond_show("disabled")}} {% if let Some(event)=event %} {{
event.voluntary_wachhabender|cond_show("checked") }} {% endif %}> event.voluntary_wachhabender|cond_show("checked") }} {% endif %} {{ disabled|cond_show("disabled")
}}>
</label> </label>
</div> </div>
{% if wh_disabled %} {% if wh_disabled %}
@ -101,7 +133,8 @@
<label class="checkbox"> <label class="checkbox">
<input class="checkbox" type="checkbox" name="voluntaryfuehrungsassistent" value="true" {{ <input class="checkbox" type="checkbox" name="voluntaryfuehrungsassistent" value="true" {{
fa_disabled|cond_show("disabled") }} {% if let Some(event)=event %} {{ fa_disabled|cond_show("disabled") }} {% if let Some(event)=event %} {{
event.voluntary_fuehrungsassistent|cond_show("checked") }} {% endif %}> event.voluntary_fuehrungsassistent|cond_show("checked") }} {% endif %} {{
disabled|cond_show("disabled") }}>
</label> </label>
</div> </div>
{% if fa_disabled %} {% if fa_disabled %}
@ -123,11 +156,13 @@
<div class="control"> <div class="control">
<input class="input" type="number" name="amount" <input class="input" type="number" name="amount"
min="{% if posten_planned %}{{ amount_of_planned_posten }}{% else %}1{% endif %}" max="100" required min="{% if posten_planned %}{{ amount_of_planned_posten }}{% else %}1{% endif %}" max="100" required
{% if let Some(event)=event %} value="{{ event.amount_of_posten }}" {% endif %} /> {% if let Some(event)=event %} value="{{ event.amount_of_posten }}" {% endif %} {{
disabled|cond_show("disabled") }} />
</div> </div>
{% if posten_planned %} {% if posten_planned %}
<p class="help"> <p class="help">
Mindestens {{ amount_of_planned_posten }} Posten, da bereits diese Anzahl eingeplant ist. Zum verringern Mindestens {{ amount_of_planned_posten }} Posten, da bereits diese Anzahl eingeplant ist. Zum
verringern
diese erst entplanen! diese erst entplanen!
</p> </p>
{% endif %} {% endif %}
@ -143,7 +178,7 @@
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input class="input" name="clothing" placeholder="Tuchuniform" required {% if let Some(event)=event %} <input class="input" name="clothing" placeholder="Tuchuniform" required {% if let Some(event)=event %}
value="{{ event.clothing }}" {% endif %} /> value="{{ event.clothing }}" {% endif %} {{ disabled|cond_show("disabled") }} />
</div> </div>
</div> </div>
</div> </div>
@ -157,7 +192,7 @@
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<input class="input" name="note" {% if let Some(event)=event %} {{ event.note|insert_value }} {% endif <input class="input" name="note" {% if let Some(event)=event %} {{ event.note|insert_value }} {% endif
%} /> %} {{ disabled|cond_show("disabled") }} />
</div> </div>
</div> </div>
</div> </div>
@ -168,10 +203,26 @@
<div class="field-body"> <div class="field-body">
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
<input class="button is-link" type="submit" value="Erstellen"> <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>
<div class="control"> <div class="control">
<a class="button is-link is-light" hx-boost="true" href="/?date={{ date }}">Zurück</a> <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>
</div> </div>

View File

@ -96,11 +96,14 @@
</svg> </svg>
<span>Bearbeiten</span> <span>Bearbeiten</span>
</a> </a>
<a href="" class="button is-warning">als abgesagt markieren</a>
</div> </div>
{% endif %} {% endif %}
{% if event.canceled %}<b>Veranstaltung abgesagt!</b>{% endif %} {% if event.canceled %}
<div class="cell is-col-span-2 notification is-warning is-light">
<b>Veranstaltung abgesagt!</b>
</div>
{% endif %}
<div class="cell"> <div class="cell">
<p><b>Uhrzeit:</b> {{ event.start_time.format("%R") }} Uhr - {{ event.end_time.format("%R") }} Uhr</p> <p><b>Uhrzeit:</b> {{ event.start_time.format("%R") }} Uhr - {{ event.end_time.format("%R") }} Uhr</p>