feature: crewless stations; Fixes #2
This commit is contained in:
parent
fb867ff6f2
commit
f5a2901b16
@ -45,6 +45,11 @@ async fn highscore(State(db): State<Arc<SqlitePool>>, session: Session) -> Marku
|
|||||||
td {
|
td {
|
||||||
a href=(format!("/admin/station/{}", station.id)){
|
a href=(format!("/admin/station/{}", station.id)){
|
||||||
(station.name)
|
(station.name)
|
||||||
|
@if station.crewless() {
|
||||||
|
em data-tooltip="Station ohne Stationsbetreuer" data-placement="bottom" {
|
||||||
|
small { "🤖" }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,22 @@ DROP TABLE temp_pos;",
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn crewless_stations(&self, db: &SqlitePool) -> Vec<Station> {
|
||||||
|
self.stations(db)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter(|s| s.crewless())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn crewful_stations(&self, db: &SqlitePool) -> Vec<Station> {
|
||||||
|
self.stations(db)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter(|s| !s.crewless())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
async fn stations_not_in_route(&self, db: &SqlitePool) -> Vec<Station> {
|
async fn stations_not_in_route(&self, db: &SqlitePool) -> Vec<Station> {
|
||||||
// TODO: switch to macro
|
// TODO: switch to macro
|
||||||
sqlx::query_as::<_, Station>(
|
sqlx::query_as::<_, Station>(
|
||||||
@ -200,6 +216,7 @@ DROP TABLE temp_pos;",
|
|||||||
JOIN route_station rs ON s.id = rs.station_id
|
JOIN route_station rs ON s.id = rs.station_id
|
||||||
LEFT JOIN 'team' 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 = {}
|
||||||
|
AND (s.amount_people != 0 OR s.amount_people is NULL)
|
||||||
GROUP BY s.id
|
GROUP BY s.id
|
||||||
ORDER BY team_count ASC, s.id ASC
|
ORDER BY team_count ASC, s.id ASC
|
||||||
LIMIT 1",
|
LIMIT 1",
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use super::Route;
|
use super::Route;
|
||||||
use crate::{AppState, admin::station::Station, err, page, succ};
|
use crate::{admin::station::Station, err, page, succ, AppState};
|
||||||
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;
|
||||||
@ -162,6 +162,11 @@ async fn view(
|
|||||||
@for (idx, station) in cur_stations.into_iter().enumerate() {
|
@for (idx, station) in cur_stations.into_iter().enumerate() {
|
||||||
li {
|
li {
|
||||||
(station.name)
|
(station.name)
|
||||||
|
@if station.crewless() {
|
||||||
|
em data-tooltip="Station ohne Stationsbetreuer" {
|
||||||
|
small { "🤖" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@if idx > 0 {
|
@if idx > 0 {
|
||||||
a href=(format!("/admin/route/{}/move-station-higher/{}", route.id, station.id)){
|
a href=(format!("/admin/route/{}/move-station-higher/{}", route.id, station.id)){
|
||||||
em data-tooltip=(format!("{} nach vor reihen", station.name)) {
|
em data-tooltip=(format!("{} nach vor reihen", station.name)) {
|
||||||
|
@ -16,7 +16,7 @@ pub(crate) struct Station {
|
|||||||
pub(crate) id: i64,
|
pub(crate) id: i64,
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
notes: Option<String>,
|
notes: Option<String>,
|
||||||
amount_people: Option<i64>,
|
pub(crate) amount_people: Option<i64>,
|
||||||
last_login: Option<NaiveDateTime>, // TODO use proper timestamp (NaiveDateTime?)
|
last_login: Option<NaiveDateTime>, // TODO use proper timestamp (NaiveDateTime?)
|
||||||
pub(crate) pw: String,
|
pub(crate) pw: String,
|
||||||
pub(crate) ready: bool,
|
pub(crate) ready: bool,
|
||||||
@ -45,6 +45,13 @@ impl Station {
|
|||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn crewless(&self) -> bool {
|
||||||
|
if let Some(amount_people) = self.amount_people {
|
||||||
|
return amount_people == 0;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn login(db: &SqlitePool, id: i64, code: &str) -> Option<Self> {
|
pub async fn login(db: &SqlitePool, id: i64, code: &str) -> Option<Self> {
|
||||||
let station = sqlx::query_as!(
|
let station = sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
|
@ -126,6 +126,7 @@ async fn view(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@if !station.crewless() {
|
||||||
tr {
|
tr {
|
||||||
th scope="row" { "Stations-Link" };
|
th scope="row" { "Stations-Link" };
|
||||||
td {
|
td {
|
||||||
@ -138,6 +139,7 @@ async fn view(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
tr {
|
tr {
|
||||||
th scope="row" { "Anzahl Stationsbetreuer" };
|
th scope="row" { "Anzahl Stationsbetreuer" };
|
||||||
td {
|
td {
|
||||||
@ -161,6 +163,7 @@ async fn view(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@if !station.crewless() {
|
||||||
tr {
|
tr {
|
||||||
th scope="row" { "Letzter Zugriff eines Stationsbetreuers" };
|
th scope="row" { "Letzter Zugriff eines Stationsbetreuers" };
|
||||||
td {
|
td {
|
||||||
@ -172,6 +175,7 @@ async fn view(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@if !ratings.is_empty() {
|
@if !ratings.is_empty() {
|
||||||
h2 { "Bewertungen" }
|
h2 { "Bewertungen" }
|
||||||
@ -672,6 +676,11 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
|
|||||||
a href=(format!("/admin/station/{}", station.id)){
|
a href=(format!("/admin/station/{}", station.id)){
|
||||||
(station.name)
|
(station.name)
|
||||||
}
|
}
|
||||||
|
@if station.crewless() {
|
||||||
|
em data-tooltip="Station ohne Stationsbetreuer" {
|
||||||
|
small { "🤖" }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
em data-tooltip=(format!("{}/{} Teams (davon {} wartend + {} aktiv)", status.total_teams-status.not_yet_here.len() as i64, status.total_teams, status.waiting.len(), status.doing.len())) {
|
em data-tooltip=(format!("{}/{} Teams (davon {} wartend + {} aktiv)", status.total_teams-status.not_yet_here.len() as i64, status.total_teams, status.waiting.len(), status.doing.len())) {
|
||||||
|
@ -95,29 +95,14 @@ async fn delete(
|
|||||||
Redirect::to("/admin/team")
|
Redirect::to("/admin/team")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn quick(
|
async fn quick(db: Arc<SqlitePool>, team: &Team, stations: Vec<Station>, redirect: &str) -> Markup {
|
||||||
State(db): State<Arc<SqlitePool>>,
|
html! {
|
||||||
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 stations = team.route(&db).await.stations(&db).await;
|
|
||||||
|
|
||||||
// maybe switch to maud-display impl of team
|
|
||||||
let content = html! {
|
|
||||||
h1 {
|
h1 {
|
||||||
a href=(format!("/admin/team/{}", team.id)) { "↩️" }
|
a href=(format!("/admin/team/{}", team.id)) { "↩️" }
|
||||||
"Bewertungen Team " (team.name)
|
"Bewertungen Team " (team.name)
|
||||||
}
|
}
|
||||||
form action=(format!("/admin/team/{}/quick", team.id)) method="post" {
|
form action=(format!("/admin/team/{}/quick", team.id)) method="post" {
|
||||||
|
input type="hidden" name="redirect" value=(redirect);
|
||||||
table {
|
table {
|
||||||
thead {
|
thead {
|
||||||
tr {
|
tr {
|
||||||
@ -155,12 +140,53 @@ async fn quick(
|
|||||||
}
|
}
|
||||||
input type="submit" value="Speichern";
|
input type="submit" value="Speichern";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn quick_crewless(
|
||||||
|
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 stations: Vec<Station> = team.route(&db).await.crewless_stations(&db).await;
|
||||||
|
|
||||||
|
let content = quick(db, &team, stations, "/crewless").await;
|
||||||
|
|
||||||
|
Ok(page(content, session, true).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn quick_all(
|
||||||
|
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 stations = team.route(&db).await.stations(&db).await;
|
||||||
|
|
||||||
|
let content = quick(db, &team, stations, "").await;
|
||||||
|
|
||||||
Ok(page(content, session, true).await)
|
Ok(page(content, session, true).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct QuickUpdate {
|
struct QuickUpdate {
|
||||||
|
redirect: String,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
fields: HashMap<String, String>,
|
fields: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
@ -228,7 +254,7 @@ async fn quick_post(
|
|||||||
succ!(session, "Erfolgreich {amount_succ} Bewertungen eingetragen");
|
succ!(session, "Erfolgreich {amount_succ} Bewertungen eingetragen");
|
||||||
}
|
}
|
||||||
|
|
||||||
Redirect::to(&format!("/admin/team/{id}/quick"))
|
Redirect::to(&format!("/admin/team/{id}/quick{}", form.redirect))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn view(
|
async fn view(
|
||||||
@ -247,7 +273,7 @@ async fn view(
|
|||||||
let first_station = team.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 = team.route(&db).await.stations(&db).await;
|
let stations = team.route(&db).await.crewful_stations(&db).await;
|
||||||
|
|
||||||
// maybe switch to maud-display impl of team
|
// maybe switch to maud-display impl of team
|
||||||
let content = html! {
|
let content = html! {
|
||||||
@ -378,9 +404,13 @@ async fn view(
|
|||||||
}
|
}
|
||||||
a href=(format!("/admin/team/{}/quick", team.id)){
|
a href=(format!("/admin/team/{}/quick", team.id)){
|
||||||
button {
|
button {
|
||||||
"Stations-Bewertungen für Team "
|
"Bewertungen"
|
||||||
(team.name)
|
}
|
||||||
" eingeben"
|
}
|
||||||
|
hr;
|
||||||
|
a href=(format!("/admin/team/{}/quick/crewless", team.id)){
|
||||||
|
button {
|
||||||
|
"Unbemannte Bewertungen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -743,7 +773,8 @@ pub(super) fn routes() -> Router<AppState> {
|
|||||||
.route("/lost", get(lost))
|
.route("/lost", get(lost))
|
||||||
.route("/{id}", get(view))
|
.route("/{id}", get(view))
|
||||||
.route("/{id}/delete", get(delete))
|
.route("/{id}/delete", get(delete))
|
||||||
.route("/{id}/quick", get(quick))
|
.route("/{id}/quick", get(quick_all))
|
||||||
|
.route("/{id}/quick/crewless", get(quick_crewless))
|
||||||
.route("/{id}/quick", post(quick_post))
|
.route("/{id}/quick", post(quick_post))
|
||||||
.route("/{id}/name", post(update_name))
|
.route("/{id}/name", post(update_name))
|
||||||
.route("/{id}/notes", post(update_notes))
|
.route("/{id}/notes", post(update_notes))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user