609 lines
20 KiB
Rust
609 lines
20 KiB
Rust
use super::{CreateError, LastContactTeam, Team};
|
|
use crate::{
|
|
admin::{route::Route, station::Station},
|
|
err,
|
|
partials::page,
|
|
pl, succ, AppState,
|
|
};
|
|
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<Arc<SqlitePool>>,
|
|
session: Session,
|
|
Form(form): Form<CreateForm>,
|
|
) -> 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");
|
|
};
|
|
|
|
let id = match Team::create(&db, &form.name, &route).await {
|
|
Ok(id) => {
|
|
succ!(session, "Team '{}' erfolgreich erstellt!", form.name);
|
|
id
|
|
}
|
|
Err(CreateError::DuplicateName(e)) => {
|
|
err!(
|
|
session,
|
|
"Team '{}' konnte _NICHT_ erstellt werden, da es bereits ein Team mit diesem Namen gibt ({e})!",
|
|
form.name
|
|
);
|
|
|
|
return Redirect::to("/admin/team");
|
|
}
|
|
Err(CreateError::NoStationForRoute) => {
|
|
err!(
|
|
session,
|
|
"Team '{}' konnte _NICHT_ erstellt werden, da in der angegebenen Route '{}' noch keine Stationen vorkommen",
|
|
form.name,
|
|
route.name
|
|
);
|
|
|
|
return Redirect::to("/admin/team");
|
|
}
|
|
};
|
|
|
|
Redirect::to(&format!("/admin/team/{}", id))
|
|
}
|
|
|
|
async fn delete(
|
|
State(db): State<Arc<SqlitePool>>,
|
|
session: Session,
|
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
|
) -> 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<Arc<SqlitePool>>,
|
|
session: Session,
|
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
|
) -> Result<Markup, impl IntoResponse> {
|
|
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<Arc<SqlitePool>>,
|
|
session: Session,
|
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
|
Form(form): Form<UpdateNameForm>,
|
|
) -> 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<Arc<SqlitePool>>,
|
|
session: Session,
|
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
|
Form(form): Form<UpdateNotesForm>,
|
|
) -> 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<Arc<SqlitePool>>,
|
|
session: Session,
|
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
|
Form(form): Form<UpdateAmountPeopleForm>,
|
|
) -> 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<Arc<SqlitePool>>,
|
|
session: Session,
|
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
|
Form(form): Form<UpdateRouteForm>,
|
|
) -> 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<Arc<SqlitePool>>,
|
|
session: Session,
|
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
|
Form(form): Form<UpdateFirstStationForm>,
|
|
) -> 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<Arc<SqlitePool>>,
|
|
session: Session,
|
|
axum::extract::Path(id): axum::extract::Path<i64>,
|
|
) -> 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 lost(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
|
|
let losts = LastContactTeam::all_sort_missing(&db).await;
|
|
|
|
let content = html! {
|
|
h1 {
|
|
a href="/admin/team" { "↩️" }
|
|
"Teams: Letzter Kontakt"
|
|
}
|
|
div class="overflow-auto" {
|
|
table {
|
|
thead {
|
|
tr {
|
|
td { "Gruppe" }
|
|
td { "Uhrzeit" }
|
|
td { "Station" }
|
|
}
|
|
}
|
|
tbody {
|
|
@for lost in &losts {
|
|
tr {
|
|
td {
|
|
a href=(format!("/admin/team/{}", lost.team_id)) {
|
|
(lost.team_name)
|
|
}
|
|
}
|
|
td {
|
|
@if let Some(time) = lost.local_last_contact() {
|
|
(time)
|
|
}@else{
|
|
"No contact yet"
|
|
}
|
|
}
|
|
td {
|
|
a href=(format!("/admin/station/{}", lost.station_id)) {
|
|
(lost.station_name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
page(content, session, false).await
|
|
}
|
|
|
|
async fn index(State(db): State<Arc<SqlitePool>>, 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."
|
|
}
|
|
article {
|
|
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" {
|
|
"Route erstellen"
|
|
}
|
|
}
|
|
} @else {
|
|
form action="/admin/team" method="post" {
|
|
@if routes.len() == 1 {
|
|
fieldset role="group" {
|
|
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";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
a href="/admin/team/lost" {
|
|
button class="outline" {
|
|
"Hab ich eine Gruppe verloren? 😳"
|
|
}
|
|
}
|
|
@for route in &routes {
|
|
h2 { (route.name) }
|
|
ol {
|
|
@for team in &route.teams(&db).await{
|
|
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 ⤵️"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
page(content, session, false).await
|
|
}
|
|
|
|
pub(super) fn routes() -> Router<AppState> {
|
|
Router::new()
|
|
.route("/", get(index))
|
|
.route("/", post(create))
|
|
.route("/lost", get(lost))
|
|
.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))
|
|
}
|