rename 'group' to 'team'

This commit is contained in:
Philipp Hofer 2025-04-08 18:35:59 +02:00
parent a1d5509909
commit 808525dee5
8 changed files with 184 additions and 185 deletions

View File

@ -9,11 +9,13 @@
- [x] Station - [x] Station
- [x] Route - [x] Route
- [x] Route_station - [x] Route_station
- [ ] Group - [x] Team
- [ ] Station view (rate team)
- [ ]
## Fancy features ## Fancy features
- see when a group starts going to your direction - see when a team starts going to your direction
- QR codes for groups, stations can then scan it? - QR codes for teams, stations can then scan it?
- View all groups: where have they last made contact with *any* station? - View all teams: where have they last made contact with *any* station?
- View single group: which stations are they still missing? - View single teams: which stations are they still missing?
- Rangliste - Rangliste

View File

@ -23,7 +23,7 @@ CREATE TABLE route_station (
FOREIGN KEY (station_id) REFERENCES station(id) FOREIGN KEY (station_id) REFERENCES station(id)
); );
CREATE TABLE "group" ( CREATE TABLE team (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE,
notes TEXT, notes TEXT,
@ -34,16 +34,16 @@ CREATE TABLE "group" (
FOREIGN KEY (route_id) REFERENCES route(id) FOREIGN KEY (route_id) REFERENCES route(id)
); );
CREATE TABLE group_station ( CREATE TABLE rating (
group_id INTEGER, team_id INTEGER,
station_id INTEGER, station_id INTEGER,
points INTEGER, points INTEGER,
notes TEXT, notes TEXT,
arrived_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, arrived_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at DATETIME, started_at DATETIME,
left_at DATETIME, left_at DATETIME,
PRIMARY KEY (group_id, station_id), PRIMARY KEY (team_id, station_id),
FOREIGN KEY (group_id) REFERENCES "group"(id), FOREIGN KEY (team_id) REFERENCES team(id),
FOREIGN KEY (station_id) REFERENCES station(id) FOREIGN KEY (station_id) REFERENCES station(id)
); );

View File

@ -1,21 +1,21 @@
use axum::{Router, body::Body, response::Response, routing::get}; use axum::{body::Body, response::Response, routing::get, Router};
use maud::{Markup, html}; use maud::{html, Markup};
use partials::page; use partials::page;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::sync::Arc; use std::sync::Arc;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tower_sessions::{MemoryStore, Session, SessionManagerLayer}; use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
pub(crate) mod group;
mod partials; mod partials;
pub(crate) mod route; pub(crate) mod route;
pub(crate) mod station; pub(crate) mod station;
pub(crate) mod team;
pub(crate) fn pl(amount: usize, single: &str) -> String { pub(crate) fn pl(amount: usize, single: &str, append: &str) -> String {
if amount == 1 { if amount == 1 {
single.into() single.into()
} else { } else {
format!("{single}n") format!("{single}{append}")
} }
} }
@ -101,8 +101,8 @@ async fn index(session: Session) -> Markup {
} }
} }
li { li {
a role="button" href="/group" { a role="button" href="/team" {
"Gruppen" "Teams"
} }
} }
} }
@ -120,7 +120,7 @@ pub async fn start(listener: TcpListener, db: SqlitePool) {
.route("/", get(index)) .route("/", get(index))
.nest("/station", station::routes()) .nest("/station", station::routes())
.nest("/route", route::routes()) .nest("/route", route::routes())
.nest("/group", group::routes()) .nest("/team", team::routes())
.route("/pico.css", get(serve_pico_css)) .route("/pico.css", get(serve_pico_css))
.route("/style.css", get(serve_my_css)) .route("/style.css", get(serve_my_css))
.route("/leaflet.css", get(serve_leaflet_css)) .route("/leaflet.css", get(serve_leaflet_css))

View File

@ -1,4 +1,4 @@
use crate::{group::Group, station::Station}; use crate::{station::Station, team::Team};
use axum::Router; use axum::Router;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Row, SqlitePool}; use sqlx::{FromRow, Row, SqlitePool};
@ -161,8 +161,8 @@ DROP TABLE temp_pos;",
.unwrap() .unwrap()
} }
pub(crate) async fn groups(&self, db: &SqlitePool) -> Vec<Group> { pub(crate) async fn teams(&self, db: &SqlitePool) -> Vec<Team> {
Group::all_with_route(db, self).await Team::all_with_route(db, self).await
} }
pub(crate) async fn get_next_first_station(&self, db: &SqlitePool) -> Option<Station> { pub(crate) async fn get_next_first_station(&self, db: &SqlitePool) -> Option<Station> {
@ -170,13 +170,13 @@ DROP TABLE temp_pos;",
" "
SELECT SELECT
s.id AS next_first_station_id, s.id AS next_first_station_id,
COUNT(g.id) AS group_count COUNT(g.id) AS team_count
FROM station s FROM station s
JOIN route_station rs ON s.id = rs.station_id JOIN route_station rs ON s.id = rs.station_id
LEFT JOIN 'group' g ON s.id = g.first_station_id LEFT JOIN 'team' g ON s.id = g.first_station_id
WHERE rs.route_id = {} WHERE rs.route_id = {}
GROUP BY s.id GROUP BY s.id
ORDER BY group_count ASC, s.id ASC ORDER BY team_count ASC, s.id ASC
LIMIT 1", LIMIT 1",
self.id self.id
)) ))

View File

@ -1,12 +1,12 @@
use super::Route; use super::Route;
use crate::{err, page, station::Station, succ}; use crate::{err, page, station::Station, succ};
use axum::{ use axum::{
Form, Router,
extract::State, extract::State,
response::{IntoResponse, Redirect}, response::{IntoResponse, Redirect},
routing::{get, post}, routing::{get, post},
Form, Router,
}; };
use maud::{Markup, PreEscaped, html}; use maud::{html, Markup, PreEscaped};
use serde::Deserialize; use serde::Deserialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::sync::Arc; use std::sync::Arc;
@ -25,7 +25,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
"definieren welche " "definieren welche "
a href="/station" { "Stationen" } a href="/station" { "Stationen" }
" von den " " von den "
a href="/group" { "Gruppen" } a href="/team" { "Teams" }
" in welcher Reihenfolge abgeklappert werden sollen. Wenn es verschiedene Kategorien (zB Kinder- und Erwachsenenwertung) gibt, kannst du auch mehrere Routen mit (teils) überlappenden Stationen erstellen." " in welcher Reihenfolge abgeklappert werden sollen. Wenn es verschiedene Kategorien (zB Kinder- und Erwachsenenwertung) gibt, kannst du auch mehrere Routen mit (teils) überlappenden Stationen erstellen."
} }
ol { ol {
@ -53,7 +53,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
} }
h2 { "Neue Route" } h2 { "Neue Route" }
form action="/route" method="post" { form action="/route" method="post" {
fieldset role="group" { fieldset role="team" {
input type="text" name="name" placeholder="Routenname" required; input type="text" name="name" placeholder="Routenname" required;
input type="submit" value="Neue Route"; input type="submit" value="Neue Route";
} }
@ -127,7 +127,7 @@ async fn view(
let cur_stations = route.stations(&db).await; let cur_stations = route.stations(&db).await;
let stations_not_in_route = route.stations_not_in_route(&db).await; let stations_not_in_route = route.stations_not_in_route(&db).await;
let groups = route.groups(&db).await; let teams = route.teams(&db).await;
let content = html! { let content = html! {
h1 { h1 {
@ -189,21 +189,21 @@ async fn view(
} }
} }
} }
h2 { "Gruppen mit dieser Route" } h2 { "Teams mit dieser Route" }
@if groups.is_empty() { @if teams.is_empty() {
article { article {
"Noch keine Gruppe ist dieser Route zugeteilt." "Noch keine Team ist dieser Route zugeteilt."
a role="button" href="/group" { a role="button" href="/team" {
"Zu den Gruppen" "Zu den Teams"
} }
} }
} @else { } @else {
ol { ol {
@for group in &groups { @for team in &teams {
li { li {
a href=(format!("/group/{}", group.id)) { a href=(format!("/team/{}", team.id)) {
(group.name) (team.name)
} }
} }
} }

View File

@ -1,11 +1,11 @@
use crate::{err, partials::page, station::Station, succ}; use crate::{err, partials::page, station::Station, succ};
use axum::{ use axum::{
Form, Router,
extract::State, extract::State,
response::{IntoResponse, Redirect}, response::{IntoResponse, Redirect},
routing::{get, post}, routing::{get, post},
Form, Router,
}; };
use maud::{Markup, html}; use maud::{html, Markup};
use serde::Deserialize; use serde::Deserialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::sync::Arc; use std::sync::Arc;
@ -120,7 +120,7 @@ async fn view(
td { td {
(station.pw) (station.pw)
article class="warning" { article class="warning" {
(format!("Diesen Code nur Betreuern der Station {} geben! Mit diesem Code erhält man die Berechtigung, Gruppen zu bewerten.", station.name)) (format!("Diesen Code nur Betreuern der Station {} geben! Mit diesem Code erhält man die Berechtigung, Teams zu bewerten.", station.name))
} }
} }
} }
@ -444,7 +444,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
} }
h2 { "Neue Station" } h2 { "Neue Station" }
form action="/station" method="post" { form action="/station" method="post" {
fieldset role="group" { fieldset role="team" {
input type="text" name="name" placeholder="Stationsname" required; input type="text" name="name" placeholder="Stationsname" required;
input type="submit" value="Neue Station"; input type="submit" value="Neue Station";
} }

View File

@ -7,7 +7,7 @@ use std::sync::Arc;
mod web; mod web;
#[derive(FromRow, Debug, Serialize, Deserialize)] #[derive(FromRow, Debug, Serialize, Deserialize)]
pub(crate) struct Group { pub(crate) struct Team {
pub(crate) id: i64, pub(crate) id: i64,
pub(crate) name: String, pub(crate) name: String,
notes: Option<String>, notes: Option<String>,
@ -21,10 +21,10 @@ enum CreateError {
DuplicateName(String), DuplicateName(String),
} }
impl Group { impl Team {
pub(crate) async fn all(db: &SqlitePool) -> Vec<Self> { pub(crate) async fn all(db: &SqlitePool) -> Vec<Self> {
sqlx::query_as::<_, Self>( sqlx::query_as::<_, Self>(
"SELECT id, name, notes, amount_people, first_station_id, route_id FROM 'group';", "SELECT id, name, notes, amount_people, first_station_id, route_id FROM team;",
) )
.fetch_all(db) .fetch_all(db)
.await .await
@ -33,8 +33,8 @@ impl Group {
pub(crate) async fn all_with_route(db: &SqlitePool, route: &Route) -> Vec<Self> { pub(crate) async fn all_with_route(db: &SqlitePool, route: &Route) -> Vec<Self> {
sqlx::query_as!( sqlx::query_as!(
Group, Team,
"select id, name, notes, amount_people, first_station_id, route_id from 'group' where route_id = ?;", "select id, name, notes, amount_people, first_station_id, route_id from team where route_id = ?;",
route.id route.id
) )
.fetch_all(db) .fetch_all(db)
@ -44,8 +44,8 @@ impl Group {
pub(crate) async fn all_with_first_station(db: &SqlitePool, station: &Station) -> Vec<Self> { pub(crate) async fn all_with_first_station(db: &SqlitePool, station: &Station) -> Vec<Self> {
sqlx::query_as!( sqlx::query_as!(
Group, Team,
"select id, name, notes, amount_people, first_station_id, route_id from 'group' where first_station_id = ?;", "select id, name, notes, amount_people, first_station_id, route_id from team where first_station_id = ?;",
station.id station.id
) )
.fetch_all(db) .fetch_all(db)
@ -56,7 +56,7 @@ impl Group {
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> { pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
sqlx::query_as!( sqlx::query_as!(
Self, Self,
"SELECT id, name, notes, amount_people, first_station_id, route_id FROM 'group' WHERE id = ?", "SELECT id, name, notes, amount_people, first_station_id, route_id FROM team WHERE id = ?",
id id
) )
.fetch_one(db) .fetch_one(db)
@ -65,14 +65,14 @@ impl Group {
} }
async fn create(db: &SqlitePool, name: &str, route: &Route) -> Result<(), CreateError> { async fn create(db: &SqlitePool, name: &str, route: &Route) -> Result<(), CreateError> {
// get next station id which has the lowest amount of groups to have in the first place // get next station id which has the lowest amount of teams to have in the first place
// assigned // assigned
let Some(station) = route.get_next_first_station(db).await else { let Some(station) = route.get_next_first_station(db).await else {
return Err(CreateError::NoStationForRoute); return Err(CreateError::NoStationForRoute);
}; };
sqlx::query!( sqlx::query!(
"INSERT INTO 'group'(name, route_id, first_station_id) VALUES (?, ?, ?)", "INSERT INTO team(name, route_id, first_station_id) VALUES (?, ?, ?)",
name, name,
route.id, route.id,
station.id station.id
@ -84,14 +84,14 @@ impl Group {
} }
async fn update_name(&self, db: &SqlitePool, name: &str) { async fn update_name(&self, db: &SqlitePool, name: &str) {
sqlx::query!("UPDATE 'group' SET name = ? WHERE id = ?", name, self.id) sqlx::query!("UPDATE team SET name = ? WHERE id = ?", name, self.id)
.execute(db) .execute(db)
.await .await
.unwrap(); .unwrap();
} }
async fn update_notes(&self, db: &SqlitePool, notes: &str) { async fn update_notes(&self, db: &SqlitePool, notes: &str) {
sqlx::query!("UPDATE 'group' SET notes = ? WHERE id = ?", notes, self.id) sqlx::query!("UPDATE team SET notes = ? WHERE id = ?", notes, self.id)
.execute(db) .execute(db)
.await .await
.unwrap(); .unwrap();
@ -99,7 +99,7 @@ impl Group {
async fn update_amount_people(&self, db: &SqlitePool, amount_people: i64) { async fn update_amount_people(&self, db: &SqlitePool, amount_people: i64) {
sqlx::query!( sqlx::query!(
"UPDATE 'group' SET amount_people = ? WHERE id = ?", "UPDATE team SET amount_people = ? WHERE id = ?",
amount_people, amount_people,
self.id self.id
) )
@ -114,7 +114,7 @@ impl Group {
}; };
sqlx::query!( sqlx::query!(
"UPDATE 'group' SET route_id = ?, first_station_id = ? WHERE id = ?", "UPDATE team SET route_id = ?, first_station_id = ? WHERE id = ?",
route.id, route.id,
station.id, station.id,
self.id self.id
@ -128,7 +128,7 @@ impl Group {
async fn update_first_station(&self, db: &SqlitePool, station: &Station) { async fn update_first_station(&self, db: &SqlitePool, station: &Station) {
sqlx::query!( sqlx::query!(
"UPDATE 'group' SET first_station_id = ? WHERE id = ?", "UPDATE team SET first_station_id = ? WHERE id = ?",
station.id, station.id,
self.id self.id
) )
@ -138,17 +138,14 @@ impl Group {
} }
async fn update_amount_people_reset(&self, db: &SqlitePool) { async fn update_amount_people_reset(&self, db: &SqlitePool) {
sqlx::query!( sqlx::query!("UPDATE team SET amount_people = NULL WHERE id = ?", self.id)
"UPDATE 'group' SET amount_people = NULL WHERE id = ?", .execute(db)
self.id .await
) .unwrap();
.execute(db)
.await
.unwrap();
} }
async fn delete(&self, db: &SqlitePool) -> Result<(), String> { async fn delete(&self, db: &SqlitePool) -> Result<(), String> {
sqlx::query!("DELETE FROM 'group' WHERE id = ?", self.id) sqlx::query!("DELETE FROM team WHERE id = ?", self.id)
.execute(db) .execute(db)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;

View File

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