make points + notes updateable
All checks were successful
CI/CD Pipeline / test (push) Successful in 3m16s
All checks were successful
CI/CD Pipeline / test (push) Successful in 3m16s
This commit is contained in:
parent
6be4f1f883
commit
17690d7e6f
@ -13,13 +13,6 @@
|
||||
- [ ] Rating view
|
||||
- [x] make plan how i want to handle station-login, then write tests!
|
||||
- [ ] simple rating entry
|
||||
- [x] "new group here" (team_id, auto ARRIVED_AT)
|
||||
- [x] create
|
||||
- [x] delete
|
||||
- [ ] if no group currently here -> ask to start
|
||||
- [x] "group started" (auto STARTED_AT)
|
||||
- [x] "group finished" (auto LEFT_AT
|
||||
- [ ] "group rated" (points, notes) // also updateable
|
||||
- [ ] improve messages, especially for `/s`
|
||||
- [ ] Highscore list
|
||||
|
||||
@ -31,3 +24,4 @@
|
||||
- Rangliste
|
||||
- How long have teams spent on average at each station?
|
||||
- Aggregated wait time (started_at - arrived_at) for each station
|
||||
- station-view: maybe add configuration to instantly start groups (no waiting possible)
|
||||
|
@ -89,6 +89,39 @@ impl Station {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn team_update(
|
||||
&self,
|
||||
db: &SqlitePool,
|
||||
team: &Team,
|
||||
points: Option<i64>,
|
||||
notes: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
let notes = match notes {
|
||||
Some(n) if n.is_empty() => None,
|
||||
Some(n) => Some(n),
|
||||
None => None,
|
||||
};
|
||||
let teams = TeamsAtStationLocation::for_station(db, self).await;
|
||||
|
||||
let waiting_teams: Vec<&Team> = teams.waiting.iter().map(|(team, _)| team).collect();
|
||||
let doing_teams: Vec<&Team> = teams.doing.iter().map(|(team, _)| team).collect();
|
||||
let finished_teams: Vec<&Team> = teams.left.iter().map(|(team, _)| team).collect();
|
||||
|
||||
if !waiting_teams.contains(&team)
|
||||
&& !doing_teams.contains(&team)
|
||||
&& !finished_teams.contains(&team)
|
||||
{
|
||||
return Err(
|
||||
"Es können nur Teams bewertet werden, die zumindest schon bei der Station sind."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
Rating::update(db, self, team, points, notes).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_team_waiting(
|
||||
&self,
|
||||
db: &SqlitePool,
|
||||
@ -200,7 +233,7 @@ impl Station {
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE rating SET left_at = NULL WHERE team_id = ? AND station_id = ?",
|
||||
"UPDATE rating SET left_at = NULL, points=NULL WHERE team_id = ? AND station_id = ?",
|
||||
team.id,
|
||||
self.id
|
||||
)
|
||||
|
@ -8,7 +8,7 @@ pub(crate) struct Rating {
|
||||
pub(crate) team_id: i64,
|
||||
pub(crate) station_id: i64,
|
||||
pub(crate) points: Option<i64>,
|
||||
notes: Option<String>,
|
||||
pub(crate) notes: Option<String>,
|
||||
arrived_at: NaiveDateTime,
|
||||
started_at: Option<NaiveDateTime>,
|
||||
left_at: Option<NaiveDateTime>,
|
||||
@ -30,6 +30,27 @@ impl Rating {
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update(
|
||||
db: &SqlitePool,
|
||||
station: &Station,
|
||||
team: &Team,
|
||||
points: Option<i64>,
|
||||
notes: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
sqlx::query!(
|
||||
"UPDATE rating SET points = ?, notes = ? WHERE station_id = ? AND team_id = ?",
|
||||
points,
|
||||
notes,
|
||||
station.id,
|
||||
team.id
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete(
|
||||
db: &SqlitePool,
|
||||
station: &Station,
|
||||
|
134
src/station.rs
134
src/station.rs
@ -54,9 +54,25 @@ async fn view(
|
||||
" (seit "
|
||||
(rating.local_time_arrived_at())
|
||||
")"
|
||||
a href=(format!("/s/{id}/{code}/remove-waiting/{}", team.id))
|
||||
onclick="return confirm('Bist du sicher, dass das Team noch nicht bei dir ist? Das kann _NICHT_ mehr rückgängig gemacht werden.');" {
|
||||
"🗑️"
|
||||
details {
|
||||
summary { "✏️" }
|
||||
article {
|
||||
form action=(format!("/s/{id}/{code}/team-update/{}", team.id)) method="post" {
|
||||
label {
|
||||
"Notizen"
|
||||
@if let Some(notes) = &rating.notes {
|
||||
input type="text" name="notes" value=(notes);
|
||||
} @else {
|
||||
input type="text" name="notes";
|
||||
}
|
||||
}
|
||||
input type="submit" value="Notizen speichern";
|
||||
}
|
||||
a href=(format!("/s/{id}/{code}/remove-waiting/{}", team.id))
|
||||
onclick="return confirm('Bist du sicher, dass das Team noch nicht bei dir ist? Das kann _NICHT_ mehr rückgängig gemacht werden.');" {
|
||||
"🗑️"
|
||||
}
|
||||
}
|
||||
}
|
||||
a href=(format!("/s/{id}/{code}/team-starting/{}", team.id)) {
|
||||
button { "Team startet" }
|
||||
@ -89,9 +105,25 @@ async fn view(
|
||||
" (seit "
|
||||
(rating.local_time_doing())
|
||||
")"
|
||||
a href=(format!("/s/{id}/{code}/remove-doing/{}", team.id))
|
||||
onclick="return confirm('Bist du sicher, dass das Team noch nicht bei dir arbeitet? Das Team wird zurück auf die Warte-Position gesetzt');" {
|
||||
"🗑️"
|
||||
details {
|
||||
summary { "✏️" }
|
||||
article {
|
||||
form action=(format!("/s/{id}/{code}/team-update/{}", team.id)) method="post" {
|
||||
label {
|
||||
"Notizen"
|
||||
@if let Some(notes) = &rating.notes {
|
||||
input type="text" name="notes" value=(notes);
|
||||
} @else {
|
||||
input type="text" name="notes";
|
||||
}
|
||||
}
|
||||
input type="submit" value="Notizen speichern";
|
||||
}
|
||||
a href=(format!("/s/{id}/{code}/remove-doing/{}", team.id))
|
||||
onclick="return confirm('Bist du sicher, dass das Team noch nicht bei dir arbeitet? Das Team wird zurück auf die Warte-Position gesetzt');" {
|
||||
"🗑️"
|
||||
}
|
||||
}
|
||||
}
|
||||
a href=(format!("/s/{id}/{code}/team-finished/{}", team.id)) {
|
||||
button { "Team fertig" }
|
||||
@ -110,10 +142,55 @@ async fn view(
|
||||
(team.name)
|
||||
" (gegangen um "
|
||||
(rating.local_time_left())
|
||||
@if let Some(points) = rating.points {
|
||||
", "
|
||||
(points)
|
||||
" Punkte"
|
||||
}
|
||||
@if let Some(notes) = &rating.notes{
|
||||
", Notizen: "
|
||||
(notes)
|
||||
}
|
||||
")"
|
||||
a href=(format!("/s/{id}/{code}/remove-left/{}", team.id))
|
||||
onclick="return confirm('Bist du sicher, dass das Team noch nicht bei dir fertig ist? Das Team wird zurück auf die Arbeits-Position gesetzt');" {
|
||||
"🗑️"
|
||||
|
||||
details open[rating.points.is_none()] {
|
||||
summary { "✏️" }
|
||||
article {
|
||||
@if rating.points.is_none() {
|
||||
article class="warning" {
|
||||
"Noch keine Punkte für diese Gruppe vergeben. Gib sie hier ein und drücke dann auf "
|
||||
em { "Speichern" }
|
||||
}
|
||||
}
|
||||
form action=(format!("/s/{id}/{code}/team-update/{}", team.id)) method="post" {
|
||||
label {
|
||||
@if let Some(points) = rating.points {
|
||||
span { (points) " Punkte" }
|
||||
input type="range" name="points" min="0" max="10" value=(points)
|
||||
onchange="if(!confirm('Du hast die Gruppe bereits bewertet. Bist du sicher, dass du deine Bewertung nochmal ändern möchtest?')) { this.value = this.defaultValue; this.previousElementSibling.textContent = this.defaultValue + ' Punkte'; }"
|
||||
oninput="this.previousElementSibling.textContent = this.value + ' Punkte'" {}
|
||||
} @else {
|
||||
span { "0 Punkte" }
|
||||
input type="range" name="points" min="0" max="10" value="0" oninput="this.previousElementSibling.textContent = this.value + ' Punkte'" {}
|
||||
}
|
||||
|
||||
}
|
||||
label {
|
||||
"Notizen"
|
||||
@if let Some(notes) = &rating.notes {
|
||||
input type="text" name="notes" value=(notes);
|
||||
} @else {
|
||||
input type="text" name="notes";
|
||||
}
|
||||
}
|
||||
input type="submit" value="Speichern";
|
||||
}
|
||||
|
||||
a href=(format!("/s/{id}/{code}/remove-left/{}", team.id))
|
||||
onclick="return confirm('Bist du sicher, dass das Team noch nicht bei dir fertig ist? Das Team wird zurück auf die Arbeits-Position gesetzt');" {
|
||||
"🗑️"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -294,6 +371,44 @@ async fn remove_left(
|
||||
Redirect::to(&format!("/s/{id}/{code}"))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TeamUpdateForm {
|
||||
points: Option<i64>,
|
||||
notes: Option<String>,
|
||||
}
|
||||
async fn team_update(
|
||||
State(db): State<Arc<SqlitePool>>,
|
||||
session: Session,
|
||||
axum::extract::Path((id, code, team_id)): axum::extract::Path<(i64, String, i64)>,
|
||||
Form(form): Form<TeamUpdateForm>,
|
||||
) -> impl IntoResponse {
|
||||
let Some(station) = Station::login(&db, id, &code).await else {
|
||||
err!(
|
||||
session,
|
||||
"Falscher Quick-Einlogg-Link. Bitte nochmal scannen oder neu eingeben."
|
||||
);
|
||||
return Redirect::to("/s/{id}/{code}");
|
||||
};
|
||||
let Some(team) = Team::find_by_id(&db, team_id).await else {
|
||||
err!(
|
||||
session,
|
||||
"Konnte das Team nicht updaten, weil ein Team mit ID {} nicht existiert",
|
||||
team_id
|
||||
);
|
||||
return Redirect::to("/s/{id}/{code}");
|
||||
};
|
||||
|
||||
match station
|
||||
.team_update(&db, &team, form.points, form.notes)
|
||||
.await
|
||||
{
|
||||
Ok(()) => succ!(session, "Team bearbeitet"),
|
||||
Err(e) => err!(session, "{e}"),
|
||||
}
|
||||
|
||||
Redirect::to(&format!("/s/{id}/{code}"))
|
||||
}
|
||||
|
||||
pub(super) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(view))
|
||||
@ -303,6 +418,7 @@ pub(super) fn routes() -> Router<AppState> {
|
||||
.route("/remove-doing/{team_id}", get(remove_doing))
|
||||
.route("/team-finished/{team_id}", get(team_finished))
|
||||
.route("/remove-left/{team_id}", get(remove_left))
|
||||
.route("/team-update/{team_id}", post(team_update))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
Loading…
x
Reference in New Issue
Block a user