use super::{CreateError, Team}; use crate::{admin::route::Route, admin::station::Station, err, partials::page, pl, succ}; use axum::{ extract::State, response::{IntoResponse, Redirect}, routing::{get, post}, Form, Router, }; use maud::{html, Markup, PreEscaped}; use serde::Deserialize; use sqlx::SqlitePool; use std::sync::Arc; use tower_sessions::Session; #[derive(Deserialize)] struct CreateForm { name: String, route_id: i64, } async fn create( State(db): State>, session: Session, Form(form): Form, ) -> impl IntoResponse { let Some(route) = Route::find_by_id(&db, form.route_id).await else { err!( session, "Team mit {} konnte nicht erstellt werden, da keine Route mit ID {} existiert", form.name, form.route_id ); return Redirect::to("/admin/team"); }; match Team::create(&db, &form.name, &route).await { Ok(()) => succ!(session, "Team '{}' erfolgreich erstellt!", form.name), Err(CreateError::DuplicateName(e)) => err!( session, "Team '{}' konnte _NICHT_ erstellt werden, da es bereits ein Team mit diesem Namen gibt ({e})!", form.name ), Err(CreateError::NoStationForRoute) => err!( session, "Team '{}' konnte _NICHT_ erstellt werden, da in der angegebenen Route '{}' noch keine Stationen vorkommen", form.name, route.name ), } Redirect::to("/admin/team") } async fn delete( State(db): State>, session: Session, axum::extract::Path(id): axum::extract::Path, ) -> impl IntoResponse { let Some(team) = Team::find_by_id(&db, id).await else { err!( session, "Team mit ID {id} konnte nicht gelöscht werden, da sie nicht existiert" ); return Redirect::to("/admin/team"); }; match team.delete(&db).await { Ok(()) => succ!(session, "Team '{}' erfolgreich gelöscht!", team.name), Err(e) => err!( session, "Team '{}' kann nicht gelöscht werden, da sie bereits verwendet wird. ({e})", team.name ), } Redirect::to("/admin/team") } async fn view( State(db): State>, session: Session, axum::extract::Path(id): axum::extract::Path, ) -> Result { let Some(team) = Team::find_by_id(&db, id).await else { err!( session, "Team mit ID {id} konnte nicht geöffnet werden, da sie nicht existiert" ); return Err(Redirect::to("/admin/team")); }; let first_station = team.first_station(&db).await; let routes = Route::all(&db).await; let stations = team.route(&db).await.stations(&db).await; // maybe switch to maud-display impl of team let content = html! { h1 { a href="/admin/team" { "↩️" } "Team " (team.name) } article { details { summary { "Teamnamen bearbeiten ✏️" } form action=(format!("/admin/team/{}/name", team.id)) method="post" { input type="text" name="name" value=(team.name) required; input type="submit" value="Speichern"; } } } table { tbody { tr { th scope="row" { "Notizen" }; td { @match &team.notes { Some(notes) => { (notes) details { summary { "✏️" } form action=(format!("/admin/team/{}/notes", team.id)) method="post" { textarea name="notes" required rows="10" { (notes) }; input type="submit" value="Speichern"; } } }, None => details { summary { "Neue Notiz hinzufügen" } form action=(format!("/admin/team/{}/notes", team.id)) method="post" { textarea name="notes" required rows="10" {}; input type="submit" value="Speichern"; } } } } } tr { th scope="row" { "Anzahl Teammitglieder" }; td { @match team.amount_people { Some(amount) => (amount), None => "?", } details { summary { "✏️" } form action=(format!("/admin/team/{}/amount-people", team.id)) method="post" { input type="number" name="amount_people" min="0" max="10"; input type="submit" value="Speichern"; } a href=(format!("/admin/team/{}/amount-people-reset", team.id)) { button class="error" { em data-tooltip="Ich weiß noch nicht wv. Personen dieses Team beherbergt." { "?" } } } } } } tr { th scope="row" { "Route" }; td { a href=(format!("/admin/route/{}", &team.route(&db).await.id)) { (&team.route(&db).await.name) } @if routes.len() > 1 { details { summary { "✏️" } form action=(format!("/admin/team/{}/update-route", team.id)) method="post" { select name="route_id" aria-label="Route auswählen" required { @for route in &routes { @if route.id != team.route(&db).await.id { option value=(route.id) { (route.name) } } } } input type="submit" value="Team speichern"; } } } } } tr { th scope="row" { "Erste Station" article { "Die erste Station wird beim Anlegen eines Team automatisch an diejenige Station vergeben, die aktuell am wenigsten Startteams hat. Diese Zuteilung kannst du hier auch manuell verändern." } }; td { a href=(format!("/admin/station/{}", first_station.id)) { (first_station.name) } @if stations.len() > 1 { details { summary { "✏️" } form action=(format!("/admin/team/{}/update-first-station", team.id)) method="post" { select name="first_station_id" aria-label="Station auswählen" required { @for station in &stations { @if station.id != first_station.id { option value=(station.id) { (station.name) @let amount_start_teams = Team::all_with_first_station(&db, station).await.len(); @if amount_start_teams > 0 { (format!(" (schon {amount_start_teams} {})", pl(amount_start_teams, "Startteam", "s"))) } } } } } input type="submit" value="Station speichern"; } } } } } } } }; Ok(page(content, session, true).await) } #[derive(Deserialize)] struct UpdateNameForm { name: String, } async fn update_name( State(db): State>, session: Session, axum::extract::Path(id): axum::extract::Path, Form(form): Form, ) -> impl IntoResponse { let Some(team) = Team::find_by_id(&db, id).await else { err!( session, "Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert" ); return Redirect::to("/admin/team"); }; team.update_name(&db, &form.name).await; succ!( session, "Team '{}' heißt ab sofort '{}'.", team.name, form.name ); Redirect::to(&format!("/admin/team/{id}")) } #[derive(Deserialize)] struct UpdateNotesForm { notes: String, } async fn update_notes( State(db): State>, session: Session, axum::extract::Path(id): axum::extract::Path, Form(form): Form, ) -> impl IntoResponse { let Some(team) = Team::find_by_id(&db, id).await else { err!( session, "Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert" ); return Redirect::to("/admin/team"); }; team.update_notes(&db, &form.notes).await; succ!( session, "Notizen für das Team '{}' wurden erfolgreich bearbeitet!", team.name ); Redirect::to(&format!("/admin/team/{id}")) } #[derive(Deserialize)] struct UpdateAmountPeopleForm { amount_people: i64, } async fn update_amount_people( State(db): State>, session: Session, axum::extract::Path(id): axum::extract::Path, Form(form): Form, ) -> impl IntoResponse { let Some(team) = Team::find_by_id(&db, id).await else { err!( session, "Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert" ); return Redirect::to("/admin/team"); }; team.update_amount_people(&db, form.amount_people).await; succ!( session, "Anzahl an Mitglieder für das Team '{}' wurden erfolgreich bearbeitet!", team.name ); Redirect::to(&format!("/admin/team/{id}")) } #[derive(Deserialize)] struct UpdateRouteForm { route_id: i64, } async fn update_route( State(db): State>, session: Session, axum::extract::Path(id): axum::extract::Path, Form(form): Form, ) -> impl IntoResponse { // TODO: move sanity checks into mod.rs let Some(team) = Team::find_by_id(&db, id).await else { err!( session, "Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert" ); return Redirect::to("/admin/team"); }; let Some(route) = Route::find_by_id(&db, form.route_id).await else { err!( session, "Konnte Team mit ID {id} konnte nicht zur Route mit ID {} bearbeiten, da diese Route nicht existiert.", form.route_id ); return Redirect::to(&format!("/admin/team/{id}")); }; match team.update_route(&db, &route).await { Ok(new_first_station_name) => succ!( session, "Team '{}' läuft ab sofort die Route '{}'. Auch die erste Station hat sich dadurch auf {} verändert!!", team.name, route.name, new_first_station_name ), Err(()) => err!( session, "Konnte für das Team {} nicht die Route {} auswählen, da Route {} keine Stationen zugeteilt hat.", team.name, route.name, route.name ), } Redirect::to(&format!("/admin/team/{id}")) } #[derive(Deserialize)] struct UpdateFirstStationForm { first_station_id: i64, } async fn update_first_station( State(db): State>, session: Session, axum::extract::Path(id): axum::extract::Path, Form(form): Form, ) -> impl IntoResponse { let Some(team) = Team::find_by_id(&db, id).await else { err!( session, "Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert" ); return Redirect::to("/admin/team"); }; let Some(station) = Station::find_by_id(&db, form.first_station_id).await else { err!( session, "Konnte die erste Station (ID={}) des Teams mit ID {} nicht bearbeiten, da diese Station nicht existiert.", form.first_station_id, team.id ); return Redirect::to(&format!("/admin/team/{id}")); }; if !station.is_in_route(&db, &team.route(&db).await).await { err!( session, "Konnte Station {} nicht dem Team {} hinzufügen, weil dieses Team bei Route {} und nicht bei Route {} mitläuft.", station.name, team.name, team.route(&db).await.name, team.name ); return Redirect::to(&format!("/admin/team/{id}")); } team.update_first_station(&db, &station).await; succ!( session, "Erste Station des Teams {} ist ab sofort {}", team.name, station.name ); Redirect::to(&format!("/admin/team/{id}")) } async fn update_amount_people_reset( State(db): State>, session: Session, axum::extract::Path(id): axum::extract::Path, ) -> impl IntoResponse { let Some(team) = Team::find_by_id(&db, id).await else { err!( session, "Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert" ); return Redirect::to("/admin/team"); }; team.update_amount_people_reset(&db).await; succ!( session, "Anzahl an Mitglieder für das Team '{}' wurden erfolgreich bearbeitet!", team.name ); Redirect::to(&format!("/admin/team/{id}")) } async fn index(State(db): State>, session: Session) -> Markup { let teams = Team::all(&db).await; let routes = Route::all(&db).await; let content = html! { h1 { a href="/admin/" { "↩️" } "Teams" } article { em { "Teams " } "sind eine Menge an Personen, die verschiedene " a href="/admin/station" { "Stationen" } " ablaufen. Welche Stationen, entscheidet sich je nachdem, welcher " a href="/admin/route" { "Route" } " sie zugewiesen sind." } ol { @for team in &teams{ li { a href=(format!("/admin/team/{}", team.id)){ (team.name) } a href=(format!("/admin/team/{}/delete", team.id)) onclick="return confirm('Bist du sicher, dass das Team gelöscht werden soll? Das kann _NICHT_ mehr rückgängig gemacht werden.');" { "🗑️" } } } } @if teams.is_empty() { article class="warning" { "Es gibt noch keine Teams. " @if !routes.is_empty() { "Das kannst du hier ändern ⤵️" } } } h2 { "Neues Team" } @if routes.is_empty() { article class="error" { (PreEscaped("Bevor du ein Team erstellen kannst, musst du zumindest eine Route erstellen, die das Team gehen kann → ")) a role="button" href="/admin/route" { "Team erstellen" } } } @else { form action="/admin/team" method="post" { @if routes.len() == 1 { fieldset role="team" { input type="text" name="name" placeholder="Teamnamen" required; input type="hidden" name="route_id" value=(routes[0].id) ; input type="submit" value="Neues Team"; } } @else { input type="text" name="name" placeholder="Teamnamen" required; select name="route_id" aria-label="Route auswählen" required { @for route in &routes { option value=(route.id) { (route.name) } } } input type="submit" value="Neues Team"; } } } }; page(content, session, false).await } pub(super) fn routes() -> Router> { Router::new() .route("/", get(index)) .route("/", post(create)) .route("/{id}", get(view)) .route("/{id}/delete", get(delete)) .route("/{id}/name", post(update_name)) .route("/{id}/notes", post(update_notes)) .route("/{id}/amount-people", post(update_amount_people)) .route("/{id}/amount-people-reset", get(update_amount_people_reset)) .route("/{id}/update-route", post(update_route)) .route("/{id}/update-first-station", post(update_first_station)) }