properly end station run; every team gets a station
This commit is contained in:
@@ -15,6 +15,13 @@ login_succ: "Erfolgreich eingeloggt als %{name}"
|
||||
user_id_nonexisting: "User mit ID %{id} gibts ned"
|
||||
person: "Person"
|
||||
people: "Personen"
|
||||
end_run: "Stationslauf beenden"
|
||||
restart_run: "Stationslauf wieder aufnehmen"
|
||||
confirm_end_run: "Willst du den Stationslauf wirklich beenden?"
|
||||
confirm_restart_run: "Willst du den Stationslauf wirklich wieder aufnehmen?"
|
||||
run_ended: "Stationslauf erfolgreich beendet"
|
||||
run_restarted: "Stationslauf erfolgreich wieder aufgenommen"
|
||||
come_home_with_these_groups: "Gruppen mitnehmen"
|
||||
|
||||
#
|
||||
# ######
|
||||
@@ -105,8 +112,10 @@ confirm_station_cancel_team_finished: "Bist du sicher, dass das Team noch nicht
|
||||
#
|
||||
station: "Station"
|
||||
stations: "Stationen"
|
||||
go_to_stations: "Zu den Stationen"
|
||||
crewless_station: "Station ohne Stationsbetreuer"
|
||||
station_create: "Station erstellen"
|
||||
no_stations_yet: "Es gibt noch keine Stationen."
|
||||
stations_expl_without_first_word: "sind festgelegte Orte mit spezifischen Aufgaben."
|
||||
station_warning_not_assigned_route: "Noch keiner Route zugeordnet" # should be short -> tooltip
|
||||
station_confirm_deletion: "Bist du sicher, dass die Station gelöscht werden soll? Das kann _NICHT_ mehr rückgängig gemacht werden."
|
||||
|
||||
@@ -30,8 +30,10 @@ CREATE TABLE IF NOT EXISTS team (
|
||||
notes TEXT,
|
||||
amount_people INTEGER,
|
||||
first_station_id INTEGER NOT NULL,
|
||||
last_station_id INTEGER,
|
||||
route_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (first_station_id) REFERENCES station(id),
|
||||
FOREIGN KEY (last_station_id) REFERENCES station(id),
|
||||
FOREIGN KEY (route_id) REFERENCES route(id)
|
||||
);
|
||||
|
||||
@@ -49,15 +51,14 @@ CREATE TABLE IF NOT EXISTS rating (
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
pw TEXT NOT NULL,
|
||||
require_new_password_code TEXT
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
pw TEXT NOT NULL,
|
||||
require_new_password_code TEXT
|
||||
);
|
||||
|
||||
create table if not exists "tower_sessions" (
|
||||
id text primary key not null,
|
||||
data blob not null,
|
||||
expiry_date integer not null
|
||||
CREATE TABLE IF NOT EXISTS tower_sessions (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
expiry_date INTEGER NOT NULL
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
use crate::{AppState, auth::Backend, models::rating::Rating, page};
|
||||
use axum::{Router, extract::State, routing::get};
|
||||
use crate::{auth::Backend, models::rating::Rating, page, suc, AppState, Station};
|
||||
use axum::{
|
||||
extract::State,
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use axum_login::login_required;
|
||||
use maud::{Markup, html};
|
||||
use maud::{html, Markup};
|
||||
use rand::{
|
||||
distr::{Distribution, Uniform},
|
||||
rng,
|
||||
@@ -9,6 +14,7 @@ use rand::{
|
||||
use route::Route;
|
||||
use sqlx::SqlitePool;
|
||||
use std::sync::Arc;
|
||||
use team::Team;
|
||||
use tower_sessions::Session;
|
||||
|
||||
pub(crate) mod route;
|
||||
@@ -107,7 +113,29 @@ async fn highscore(State(db): State<Arc<SqlitePool>>, session: Session) -> Marku
|
||||
page(content, session, false).await
|
||||
}
|
||||
|
||||
async fn index(session: Session) -> Markup {
|
||||
#[derive(PartialEq)]
|
||||
pub enum RunStatus {
|
||||
NoStationsYet,
|
||||
Active,
|
||||
HasEnded,
|
||||
}
|
||||
|
||||
impl RunStatus {
|
||||
pub async fn curr(db: &SqlitePool) -> Self {
|
||||
let stations = Station::all(db).await;
|
||||
if stations.is_empty() {
|
||||
return RunStatus::NoStationsYet;
|
||||
}
|
||||
|
||||
if station::some_team_has_last_station_id(db).await {
|
||||
return RunStatus::HasEnded;
|
||||
}
|
||||
RunStatus::Active
|
||||
}
|
||||
}
|
||||
|
||||
async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
|
||||
let status = RunStatus::curr(&db).await;
|
||||
let content = html! {
|
||||
nav {
|
||||
ul {
|
||||
@@ -147,16 +175,75 @@ async fn index(session: Session) -> Markup {
|
||||
(t!("admins"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@match status {
|
||||
RunStatus::NoStationsYet => {
|
||||
(t!("no_stations_yet"))
|
||||
(t!("change_that_below"))
|
||||
a role="button" href="/admin/station" {
|
||||
(t!("go_to_stations"))
|
||||
}
|
||||
},
|
||||
RunStatus::Active => {
|
||||
a href="/admin/end-run" onclick=(format!("return confirm('{}');", t!("confirm_end_run"))) {
|
||||
button style="background-color: red;" {
|
||||
(t!("end_run"))
|
||||
}
|
||||
}
|
||||
},
|
||||
RunStatus::HasEnded => {
|
||||
@let stations = Station::all(&db).await;
|
||||
a href="/admin/restart-run" onclick=(format!("return confirm('{}');", t!("confirm_restart_run"))) {
|
||||
button style="background-color: red;" {
|
||||
(t!("restart_run"))
|
||||
}
|
||||
}
|
||||
table {
|
||||
thead {
|
||||
tr {
|
||||
th { (t!("stations")) }
|
||||
th { (t!("come_home_with_these_groups")) }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
@for station in stations {
|
||||
tr {
|
||||
td { (station) }
|
||||
td {
|
||||
ol {
|
||||
@for team in Team::all_with_last_station(&db, &station).await {
|
||||
li { (team) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
page(content, session, false).await
|
||||
}
|
||||
|
||||
async fn end_run(State(db): State<Arc<SqlitePool>>, session: Session) -> impl IntoResponse {
|
||||
Team::end_run(&db).await;
|
||||
suc!(session, t!("run_ended"));
|
||||
Redirect::to("/admin")
|
||||
}
|
||||
async fn restart_run(State(db): State<Arc<SqlitePool>>, session: Session) -> impl IntoResponse {
|
||||
Team::restart_run(&db).await;
|
||||
suc!(session, t!("run_restarted"));
|
||||
Redirect::to("/admin")
|
||||
}
|
||||
|
||||
pub(super) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/highscore", get(highscore))
|
||||
.route("/end-run", get(end_run))
|
||||
.route("/restart-run", get(restart_run))
|
||||
.nest("/station", station::routes())
|
||||
.nest("/route", route::routes())
|
||||
.nest("/team", team::routes())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AppState,
|
||||
admin::{station::Station, team::Team},
|
||||
AppState,
|
||||
};
|
||||
use axum::Router;
|
||||
use futures::future::join_all;
|
||||
@@ -236,6 +236,18 @@ DROP TABLE temp_pos;",
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn next_station(&self, db: &SqlitePool, target_station: &Station) -> Option<Station> {
|
||||
let stations = Station::all(db).await;
|
||||
for station in stations {
|
||||
if let Some(prev_station) = self.prev_station(db, &station).await {
|
||||
if &prev_station == target_station {
|
||||
return Some(station);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn prev_station(&self, db: &SqlitePool, station: &Station) -> Option<Station> {
|
||||
if station.crewless() {
|
||||
return None;
|
||||
|
||||
@@ -28,6 +28,12 @@ pub(crate) struct Station {
|
||||
pub(crate) lng: Option<f64>,
|
||||
}
|
||||
|
||||
impl PartialEq for Station {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Station {
|
||||
fn render(&self) -> Markup {
|
||||
html! {
|
||||
@@ -406,7 +412,7 @@ impl Station {
|
||||
|
||||
pub(crate) async fn teams(&self, db: &SqlitePool) -> Vec<Team> {
|
||||
sqlx::query_as::<_, Team>(
|
||||
"SELECT DISTINCT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.route_id
|
||||
"SELECT DISTINCT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.last_station_id, t.route_id
|
||||
FROM team t
|
||||
JOIN route_station rs ON t.route_id = rs.route_id
|
||||
WHERE rs.station_id = ?
|
||||
@@ -420,7 +426,7 @@ ORDER BY LOWER(t.name);",
|
||||
|
||||
pub(crate) async fn left_teams(&self, db: &SqlitePool) -> Vec<Team> {
|
||||
sqlx::query_as::<_, Team>(
|
||||
"SELECT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.route_id
|
||||
"SELECT t.id, t.name, t.notes, t.amount_people, t.first_station_id, t.last_station_id, t.route_id
|
||||
FROM team t
|
||||
JOIN rating r ON t.id = r.team_id
|
||||
WHERE r.station_id = ?
|
||||
@@ -470,6 +476,14 @@ AND r.left_at IS NOT NULL;",
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn some_team_has_last_station_id(db: &SqlitePool) -> bool {
|
||||
sqlx::query_scalar!("SELECT 1 FROM team WHERE last_station_id IS NOT NULL")
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub struct TeamOnTheWay {
|
||||
pub(crate) team: Team,
|
||||
pub(crate) left: String,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::{
|
||||
AppState,
|
||||
admin::{route::Route, station::Station},
|
||||
models::rating::Rating,
|
||||
AppState,
|
||||
};
|
||||
use axum::Router;
|
||||
use chrono::{DateTime, Local, NaiveDateTime, Utc};
|
||||
use maud::{html, Markup, Render};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, SqlitePool};
|
||||
|
||||
@@ -17,26 +18,33 @@ pub(crate) struct Team {
|
||||
pub(crate) notes: Option<String>,
|
||||
pub(crate) amount_people: Option<i64>,
|
||||
first_station_id: i64,
|
||||
last_station_id: Option<i64>,
|
||||
route_id: i64,
|
||||
}
|
||||
|
||||
impl Render for Team {
|
||||
fn render(&self) -> Markup {
|
||||
html! {
|
||||
a href=(format!("/admin/team/{}", self.id)){
|
||||
(self.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub(crate) struct LastContactTeam {
|
||||
team_id: i64,
|
||||
team_name: String,
|
||||
station_id: i64,
|
||||
station_name: String,
|
||||
team: Team,
|
||||
station: Option<Station>,
|
||||
last_contact_time: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
impl LastContactTeam {
|
||||
pub(crate) async fn all_sort_missing(db: &SqlitePool) -> Vec<Self> {
|
||||
sqlx::query_as::<_, Self>(
|
||||
let rows = sqlx::query_as::<_, (i64, i64, Option<NaiveDateTime>)>(
|
||||
"SELECT
|
||||
t.id AS team_id,
|
||||
t.name AS team_name,
|
||||
last_contact.station_id AS station_id,
|
||||
s.name AS station_name,
|
||||
last_contact.last_contact_time AS last_contact_time
|
||||
FROM
|
||||
team t
|
||||
@@ -58,7 +66,16 @@ ORDER BY
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let mut ret = Vec::new();
|
||||
for (team_id, station_id, last_contact_time) in rows {
|
||||
ret.push(LastContactTeam {
|
||||
team: Team::find_by_id(db, team_id).await.expect("db constraints"),
|
||||
station: Station::find_by_id(db, station_id).await,
|
||||
last_contact_time,
|
||||
});
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn local_last_contact(&self) -> Option<DateTime<Local>> {
|
||||
@@ -78,7 +95,7 @@ enum CreateError {
|
||||
impl Team {
|
||||
pub(crate) async fn all(db: &SqlitePool) -> Vec<Self> {
|
||||
sqlx::query_as::<_, Self>(
|
||||
"SELECT id, name, notes, amount_people, first_station_id, route_id FROM team ORDER BY name;",
|
||||
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team ORDER BY name;",
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
@@ -88,7 +105,7 @@ impl Team {
|
||||
pub(crate) async fn all_with_route(db: &SqlitePool, route: &Route) -> Vec<Self> {
|
||||
sqlx::query_as!(
|
||||
Team,
|
||||
"SELECT id, name, notes, amount_people, first_station_id, route_id FROM team WHERE route_id = ?;",
|
||||
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team WHERE route_id = ?;",
|
||||
route.id
|
||||
)
|
||||
.fetch_all(db)
|
||||
@@ -99,7 +116,18 @@ impl Team {
|
||||
pub(crate) async fn all_with_first_station(db: &SqlitePool, station: &Station) -> Vec<Self> {
|
||||
sqlx::query_as!(
|
||||
Team,
|
||||
"select id, name, notes, amount_people, first_station_id, route_id from team where first_station_id = ?;",
|
||||
"select id, name, notes, amount_people, first_station_id, last_station_id, route_id from team where first_station_id = ?;",
|
||||
station.id
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) async fn all_with_last_station(db: &SqlitePool, station: &Station) -> Vec<Self> {
|
||||
sqlx::query_as!(
|
||||
Team,
|
||||
"select id, name, notes, amount_people, first_station_id, last_station_id, route_id from team where last_station_id = ?;",
|
||||
station.id
|
||||
)
|
||||
.fetch_all(db)
|
||||
@@ -110,7 +138,7 @@ impl Team {
|
||||
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT id, name, notes, amount_people, first_station_id, route_id FROM team WHERE id = ?",
|
||||
"SELECT id, name, notes, amount_people, first_station_id, last_station_id, route_id FROM team WHERE id = ?",
|
||||
id
|
||||
)
|
||||
.fetch_one(db)
|
||||
@@ -144,6 +172,17 @@ impl Team {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn update_end_station(&self, db: &SqlitePool, station: &Station) {
|
||||
sqlx::query!(
|
||||
"UPDATE team SET last_station_id = ? WHERE id = ?",
|
||||
station.id,
|
||||
self.id
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn update_notes(&self, db: &SqlitePool, notes: &str) {
|
||||
sqlx::query!("UPDATE team SET notes = ? WHERE id = ?", notes, self.id)
|
||||
.execute(db)
|
||||
@@ -191,6 +230,17 @@ impl Team {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn update_last_station(&self, db: &SqlitePool, station: &Station) {
|
||||
sqlx::query!(
|
||||
"UPDATE team SET last_station_id = ? WHERE id = ?",
|
||||
station.id,
|
||||
self.id
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn update_amount_people_reset(&self, db: &SqlitePool) {
|
||||
sqlx::query!("UPDATE team SET amount_people = NULL WHERE id = ?", self.id)
|
||||
.execute(db)
|
||||
@@ -212,6 +262,14 @@ impl Team {
|
||||
.expect("db constraints")
|
||||
}
|
||||
|
||||
pub async fn last_station(&self, db: &SqlitePool) -> Option<Station> {
|
||||
if let Some(last_station_id) = self.last_station_id {
|
||||
Station::find_by_id(db, last_station_id).await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn route(&self, db: &SqlitePool) -> Route {
|
||||
Route::find_by_id(db, self.route_id)
|
||||
.await
|
||||
@@ -234,6 +292,63 @@ impl Team {
|
||||
.await
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub(crate) async fn end_station(&self, db: &SqlitePool) -> Station {
|
||||
match LastContactTeam::all_sort_missing(db)
|
||||
.await
|
||||
.into_iter()
|
||||
.find(|last_contact_team| &last_contact_team.team == self)
|
||||
{
|
||||
Some(last_contact_team) => {
|
||||
if let Some(station) = last_contact_team.station {
|
||||
// Team already made some contact with a station
|
||||
match Rating::find_by_team_and_station(db, self, &station).await {
|
||||
Some(rating) => {
|
||||
if rating.left_at.is_none() {
|
||||
rating.station(db).await
|
||||
} else {
|
||||
let next_station = self
|
||||
.route(db)
|
||||
.await
|
||||
.next_station(db, &station)
|
||||
.await
|
||||
.unwrap();
|
||||
if Rating::find_by_team_and_station(db, self, &next_station)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
station // last station for team
|
||||
} else {
|
||||
next_station
|
||||
}
|
||||
}
|
||||
}
|
||||
None => self.first_station(db).await,
|
||||
}
|
||||
} else {
|
||||
// Team has made no contact yet -> next station should be the first one
|
||||
self.first_station(db).await
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn end_run(db: &SqlitePool) {
|
||||
// set `last_station_id` to the next station where `left_at` is not null
|
||||
let teams = Team::all(db).await;
|
||||
for team in teams {
|
||||
let end_station = team.end_station(db).await;
|
||||
team.update_end_station(db, &end_station).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn restart_run(db: &SqlitePool) {
|
||||
sqlx::query!("UPDATE team SET last_station_id = null")
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn routes() -> Router<AppState> {
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
use super::{CreateError, LastContactTeam, Team};
|
||||
use crate::{
|
||||
AppState,
|
||||
admin::{route::Route, station::Station},
|
||||
err,
|
||||
models::rating::Rating,
|
||||
partials::page,
|
||||
pl, succ,
|
||||
pl, succ, AppState,
|
||||
};
|
||||
use axum::{
|
||||
Form, Router,
|
||||
extract::State,
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{get, post},
|
||||
Form, Router,
|
||||
};
|
||||
use maud::{Markup, PreEscaped, html};
|
||||
use maud::{html, Markup, PreEscaped};
|
||||
use serde::Deserialize;
|
||||
use sqlx::SqlitePool;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
@@ -271,6 +270,7 @@ async fn view(
|
||||
return Err(Redirect::to("/admin/team"));
|
||||
};
|
||||
let first_station = team.first_station(&db).await;
|
||||
let last_station = team.last_station(&db).await;
|
||||
let routes = Route::all(&db).await;
|
||||
|
||||
let stations = team.route(&db).await.crewful_stations(&db).await;
|
||||
@@ -399,6 +399,35 @@ async fn view(
|
||||
}
|
||||
}
|
||||
}
|
||||
@if let Some(last_station) = last_station {
|
||||
tr {
|
||||
th scope="row" {
|
||||
"Letzte Station"
|
||||
};
|
||||
td {
|
||||
a href=(format!("/admin/station/{}", last_station.id)) {
|
||||
(last_station.name)
|
||||
}
|
||||
@if stations.len() > 1 {
|
||||
details {
|
||||
summary { "✏️" }
|
||||
form action=(format!("/admin/team/{}/update-last-station", team.id)) method="post" {
|
||||
select name="last_station_id" aria-label="Station auswählen" required {
|
||||
@for station in &stations {
|
||||
@if station.id != last_station.id {
|
||||
option value=(station.id) {
|
||||
(station.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
input type="submit" value="Station speichern";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -613,6 +642,61 @@ async fn update_first_station(
|
||||
Redirect::to(&format!("/admin/team/{id}"))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateLastStationForm {
|
||||
last_station_id: i64,
|
||||
}
|
||||
async fn update_last_station(
|
||||
State(db): State<Arc<SqlitePool>>,
|
||||
session: Session,
|
||||
axum::extract::Path(id): axum::extract::Path<i64>,
|
||||
Form(form): Form<UpdateLastStationForm>,
|
||||
) -> 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.last_station_id).await else {
|
||||
err!(
|
||||
session,
|
||||
"Konnte die letzte Station (ID={}) des Teams mit ID {} nicht bearbeiten, da diese Station nicht existiert.",
|
||||
form.last_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_last_station(&db, &station).await;
|
||||
|
||||
succ!(
|
||||
session,
|
||||
"Letzte 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,
|
||||
@@ -659,8 +743,8 @@ async fn lost(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
|
||||
@for lost in &losts {
|
||||
tr {
|
||||
td {
|
||||
a href=(format!("/admin/team/{}", lost.team_id)) {
|
||||
(lost.team_name)
|
||||
a href=(format!("/admin/team/{}", lost.team.id)) {
|
||||
(lost.team.name)
|
||||
}
|
||||
}
|
||||
td {
|
||||
@@ -671,8 +755,12 @@ async fn lost(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
|
||||
}
|
||||
}
|
||||
td {
|
||||
a href=(format!("/admin/station/{}", lost.station_id)) {
|
||||
(lost.station_name)
|
||||
@if let Some(station) = &lost.station {
|
||||
a href=(format!("/admin/station/{}", station.id)) {
|
||||
(station.name)
|
||||
}
|
||||
}@else{
|
||||
"Noch nicht gesehen"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -782,4 +870,5 @@ pub(super) fn routes() -> Router<AppState> {
|
||||
.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))
|
||||
.route("/{id}/update-last-station", post(update_last_station))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Station, admin::team::Team};
|
||||
use crate::{admin::team::Team, Station};
|
||||
use chrono::{DateTime, Local, NaiveDateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, SqlitePool};
|
||||
@@ -49,6 +49,12 @@ impl Rating {
|
||||
.expect("db constraints")
|
||||
}
|
||||
|
||||
pub(crate) async fn station(&self, db: &SqlitePool) -> Station {
|
||||
Station::find_by_id(db, self.station_id)
|
||||
.await
|
||||
.expect("db constraints")
|
||||
}
|
||||
|
||||
pub(crate) async fn for_station(db: &SqlitePool, station: &Station) -> Vec<Self> {
|
||||
sqlx::query_as::<_, Self>("SELECT team_id, station_id, points, notes, arrived_at, started_at, left_at FROM rating WHERE station_id = ?;")
|
||||
.bind(station.id)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::{
|
||||
admin::team::Team, er, err, models::rating::TeamsAtStationLocation, partials, suc, AppState,
|
||||
Station,
|
||||
admin::{team::Team, RunStatus},
|
||||
er, err,
|
||||
models::rating::TeamsAtStationLocation,
|
||||
partials, suc, AppState, Station,
|
||||
};
|
||||
use axum::{
|
||||
extract::State,
|
||||
@@ -30,6 +32,7 @@ async fn view(
|
||||
|
||||
let teams = TeamsAtStationLocation::for_station(&db, &station).await;
|
||||
let teams_on_the_way = station.teams_on_the_way(&db).await;
|
||||
let status = RunStatus::curr(&db).await;
|
||||
|
||||
let content = html! {
|
||||
h1 {
|
||||
@@ -126,6 +129,20 @@ async fn view(
|
||||
(t!("n_teams_should_come_to_station", amount=teams.total_teams))
|
||||
}
|
||||
progress value=(teams.total_teams-teams.not_yet_here.len() as i64) max=(teams.total_teams) {}
|
||||
@if status == RunStatus::HasEnded {
|
||||
@let teams_to_take_home = Team::all_with_last_station(&db, &station).await;
|
||||
@if !teams_to_take_home.is_empty() {
|
||||
"Bitte nimm folgende Teams mit heim:"
|
||||
ol {
|
||||
@for team in teams_to_take_home {
|
||||
li { (team.name) }
|
||||
}
|
||||
}
|
||||
}@else {
|
||||
"Du musst keine Teams mit heim nehmen"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@for team in teams_on_the_way {
|
||||
article {
|
||||
|
||||
Reference in New Issue
Block a user