create board view
This commit is contained in:
		| @@ -31,6 +31,12 @@ impl PartialEq for Logbook { | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(crate) enum Filter { | ||||
|     SingleDayOnly, | ||||
|     MultiDazOnly, | ||||
|     None, | ||||
| } | ||||
|  | ||||
| #[derive(FromForm, Debug, Clone)] | ||||
| pub struct LogToAdd { | ||||
|     pub boat_id: i32, | ||||
| @@ -293,6 +299,49 @@ ORDER BY departure DESC | ||||
|         ret | ||||
|     } | ||||
|  | ||||
|     pub(crate) async fn completed_wanderfahrten_with_user_over_km_in_year( | ||||
|         db: &SqlitePool, | ||||
|         user: &User, | ||||
|         min_distance: i32, | ||||
|         year: i32, | ||||
|         filter: Filter, | ||||
|     ) -> Vec<LogbookWithBoatAndRowers> { | ||||
|         let logs: Vec<Logbook> = sqlx::query_as( | ||||
|                &format!(" | ||||
|     SELECT id, boat_id, shipmaster, steering_person, shipmaster_only_steering, departure, arrival, destination, distance_in_km, comments, logtype | ||||
|     FROM logbook | ||||
|     JOIN rower ON logbook.id = rower.logbook_id | ||||
|     WHERE arrival is not null AND rower_id = {} AND logtype = 1 AND distance_in_km >= {} AND arrival like '{}-%'  | ||||
|     ORDER BY arrival DESC | ||||
|             ",  user.id, min_distance, year) | ||||
|             ) | ||||
|             .fetch_all(db) | ||||
|             .await | ||||
|             .unwrap(); //TODO: fixme | ||||
|  | ||||
|         let mut ret = Vec::new(); | ||||
|         for log in logs { | ||||
|             let trip_days = log.arrival.unwrap() - log.departure; | ||||
|             let trip_days = trip_days.num_days(); | ||||
|             match filter { | ||||
|                 Filter::SingleDayOnly => { | ||||
|                     if trip_days == 0 { | ||||
|                         ret.push(LogbookWithBoatAndRowers::from(db, log).await); | ||||
|                     } | ||||
|                 } | ||||
|                 Filter::MultiDazOnly => { | ||||
|                     if trip_days > 0 { | ||||
|                         ret.push(LogbookWithBoatAndRowers::from(db, log).await); | ||||
|                     } | ||||
|                 } | ||||
|                 Filter::None => { | ||||
|                     ret.push(LogbookWithBoatAndRowers::from(db, log).await); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         ret | ||||
|     } | ||||
|  | ||||
|     pub async fn completed(db: &SqlitePool) -> Vec<LogbookWithBoatAndRowers> { | ||||
|         let year = chrono::Local::now().year(); | ||||
|         Self::completed_in_year(db, year).await | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| use serde::Serialize; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| enum Level { | ||||
| #[derive(Serialize, PartialEq, Debug)] | ||||
| pub(crate) enum Level { | ||||
|     NONE, | ||||
|     BRONZE, | ||||
|     SILVER, | ||||
|     GOLD, | ||||
| @@ -17,6 +19,7 @@ impl Level { | ||||
|             Level::GOLD => 100000, | ||||
|             Level::DIAMOND => 200000, | ||||
|             Level::DONE => 0, | ||||
|             Level::NONE => 0, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -33,26 +36,53 @@ impl Level { | ||||
|             Level::DONE | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn curr_level(km: i32) -> Self { | ||||
|         if km < Level::BRONZE.required_km() { | ||||
|             Level::NONE | ||||
|         } else if km < Level::SILVER.required_km() { | ||||
|             Level::BRONZE | ||||
|         } else if km < Level::GOLD.required_km() { | ||||
|             Level::SILVER | ||||
|         } else if km < Level::DIAMOND.required_km() { | ||||
|             Level::GOLD | ||||
|         } else { | ||||
|             Level::DIAMOND | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn desc(&self) -> &str { | ||||
|         match self { | ||||
|             Level::BRONZE => "Bronze", | ||||
|             Level::SILVER => "Silber", | ||||
|             Level::GOLD => "Gold", | ||||
|             Level::DIAMOND => "Diamant", | ||||
|             Level::DONE => "", | ||||
|             Level::NONE => "-", | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| pub(crate) struct Next { | ||||
|     level: Level, | ||||
|     desc: String, | ||||
|     missing_km: i32, | ||||
|     required_km: i32, | ||||
|     rowed_km: i32, | ||||
| } | ||||
|  | ||||
| impl Next { | ||||
|     pub(crate) fn rowed_km(km: i32) -> Self { | ||||
|         let level = Level::next_level(km); | ||||
|     pub(crate) fn new(rowed_km: i32) -> Self { | ||||
|         let level = Level::next_level(rowed_km); | ||||
|         let required_km = level.required_km(); | ||||
|         let missing_km = required_km - km; | ||||
|         let missing_km = required_km - rowed_km; | ||||
|         Self { | ||||
|             desc: level.desc().to_string(), | ||||
|             level, | ||||
|             missing_km, | ||||
|             required_km, | ||||
|             rowed_km: km, | ||||
|             rowed_km, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| use chrono::{Datelike, Local}; | ||||
| use equatorprice::Level; | ||||
| use serde::Serialize; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| @@ -8,14 +10,46 @@ pub(crate) mod rowingbadge; | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| pub(crate) struct Achievements { | ||||
|     equatorprice: equatorprice::Next, | ||||
|     pub(crate) equatorprice: equatorprice::Next, | ||||
|     pub(crate) curr_equatorprice_name: String, | ||||
|     pub(crate) new_equatorprice_this_season: bool, | ||||
|     pub(crate) rowingbadge: Option<rowingbadge::Status>, | ||||
| } | ||||
|  | ||||
| impl Achievements { | ||||
|     pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Self { | ||||
|         let rowed_km = Stat::person(db, None, user).await.rowed_km; | ||||
|         let rowed_km = Stat::total_km(db, user).await.rowed_km; | ||||
|         let rowed_km_this_season = if Local::now().month() == 1 { | ||||
|             Stat::person(db, Some(Local::now().year() - 1), user) | ||||
|                 .await | ||||
|                 .rowed_km | ||||
|                 + Stat::person(db, Some(Local::now().year()), user) | ||||
|                     .await | ||||
|                     .rowed_km | ||||
|         } else { | ||||
|             Stat::person(db, Some(Local::now().year()), user) | ||||
|                 .await | ||||
|                 .rowed_km | ||||
|         }; | ||||
|         println!( | ||||
|             "old: {}; new: {}", | ||||
|             rowed_km, | ||||
|             rowed_km - rowed_km_this_season | ||||
|         ); | ||||
|         println!( | ||||
|             "old: {:?}; new: {:?}", | ||||
|             Level::curr_level(rowed_km), | ||||
|             Level::curr_level(rowed_km - rowed_km_this_season) | ||||
|         ); | ||||
|         let new_equatorprice_this_season = | ||||
|             Level::curr_level(rowed_km) != Level::curr_level(rowed_km - rowed_km_this_season); | ||||
|         println!("{new_equatorprice_this_season:?}"); | ||||
|  | ||||
|         Self { | ||||
|             equatorprice: equatorprice::Next::rowed_km(rowed_km), | ||||
|             equatorprice: equatorprice::Next::new(rowed_km), | ||||
|             curr_equatorprice_name: equatorprice::Level::curr_level(rowed_km).desc().to_string(), | ||||
|             new_equatorprice_this_season, | ||||
|             rowingbadge: rowingbadge::Status::for_user(db, user).await, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,14 @@ | ||||
| use chrono::{Datelike, Local, NaiveDate}; | ||||
| use std::cmp; | ||||
|  | ||||
| use crate::model::user::User; | ||||
| use chrono::{Datelike, Local, NaiveDate}; | ||||
| use serde::Serialize; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| use crate::model::{ | ||||
|     logbook::{Filter, Logbook, LogbookWithBoatAndRowers}, | ||||
|     stat::Stat, | ||||
|     user::User, | ||||
| }; | ||||
|  | ||||
| enum AgeBracket { | ||||
|     Till14, | ||||
| @@ -11,6 +19,52 @@ enum AgeBracket { | ||||
|     From76, | ||||
| } | ||||
|  | ||||
| impl AgeBracket { | ||||
|     fn cat(&self) -> &str { | ||||
|         match self { | ||||
|             AgeBracket::Till14 => "Schülerinnen und Schüler bis 14 Jahre", | ||||
|             AgeBracket::From14Till18 => "Juniorinnen und Junioren, Para-Ruderer bis 18 Jahre", | ||||
|             AgeBracket::From19Till30 => "Frauen und Männer, Para-Ruderer bis 30 Jahre", | ||||
|             AgeBracket::From31Till60 => "Frauen und Männer, Para-Ruderer von 31 bis 60 Jahre", | ||||
|             AgeBracket::From61Till75 => "Frauen und Männer, Para-Ruderer von 61 bis 75 Jahre", | ||||
|             AgeBracket::From76 => "Frauen und Männer, Para-Ruderer ab 76 Jahre", | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn dist_in_km(&self) -> i32 { | ||||
|         match self { | ||||
|             AgeBracket::Till14 => 500, | ||||
|             AgeBracket::From14Till18 => 1000, | ||||
|             AgeBracket::From19Till30 => 1200, | ||||
|             AgeBracket::From31Till60 => 1000, | ||||
|             AgeBracket::From61Till75 => 800, | ||||
|             AgeBracket::From76 => 600, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn required_dist_multi_day_in_km(&self) -> i32 { | ||||
|         match self { | ||||
|             AgeBracket::Till14 => 60, | ||||
|             AgeBracket::From14Till18 => 60, | ||||
|             AgeBracket::From19Till30 => 80, | ||||
|             AgeBracket::From31Till60 => 80, | ||||
|             AgeBracket::From61Till75 => 80, | ||||
|             AgeBracket::From76 => 80, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn required_dist_single_day_in_km(&self) -> i32 { | ||||
|         match self { | ||||
|             AgeBracket::Till14 => 30, | ||||
|             AgeBracket::From14Till18 => 30, | ||||
|             AgeBracket::From19Till30 => 40, | ||||
|             AgeBracket::From31Till60 => 40, | ||||
|             AgeBracket::From61Till75 => 40, | ||||
|             AgeBracket::From76 => 40, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<&User> for AgeBracket { | ||||
|     type Error = String; | ||||
|  | ||||
| @@ -39,24 +93,71 @@ impl TryFrom<&User> for AgeBracket { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn cat(value: &AgeBracket) -> &str { | ||||
|     match value { | ||||
|         AgeBracket::Till14 => "Schülerinnen und Schüler bis 14 Jahre", | ||||
|         AgeBracket::From14Till18 => "Juniorinnen und Junioren, Para-Ruderer bis 18 Jahre", | ||||
|         AgeBracket::From19Till30 => "Frauen und Männer, Para-Ruderer bis 30 Jahre", | ||||
|         AgeBracket::From31Till60 => "Frauen und Männer, Para-Ruderer von 31 bis 60 Jahre", | ||||
|         AgeBracket::From61Till75 => "Frauen und Männer, Para-Ruderer von 61 bis 75 Jahre", | ||||
|         AgeBracket::From76 => "Frauen und Männer, Para-Ruderer ab 76 Jahre", | ||||
|     } | ||||
| #[derive(Serialize)] | ||||
| pub(crate) struct Status { | ||||
|     pub(crate) year: i32, | ||||
|     rowed_km: i32, | ||||
|     category: String, | ||||
|     required_km: i32, | ||||
|     missing_km: i32, | ||||
|     multi_day_trips_over_required_distance: Vec<LogbookWithBoatAndRowers>, | ||||
|     multi_day_trips_required_distance: i32, | ||||
|     single_day_trips_over_required_distance: Vec<LogbookWithBoatAndRowers>, | ||||
|     single_day_trips_required_distance: i32, | ||||
|     achieved: bool, | ||||
| } | ||||
|  | ||||
| fn dist_in_km(value: &AgeBracket) -> u32 { | ||||
|     match value { | ||||
|         AgeBracket::Till14 => 500, | ||||
|         AgeBracket::From14Till18 => 1000, | ||||
|         AgeBracket::From19Till30 => 1200, | ||||
|         AgeBracket::From31Till60 => 1000, | ||||
|         AgeBracket::From61Till75 => 800, | ||||
|         AgeBracket::From76 => 600, | ||||
| impl Status { | ||||
|     pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Option<Self> { | ||||
|         let Ok(agebracket) = AgeBracket::try_from(user) else { | ||||
|             return None; | ||||
|         }; | ||||
|         let category = agebracket.cat().to_string(); | ||||
|  | ||||
|         let year = if Local::now().month() == 1 { | ||||
|             Local::now().year() - 1 | ||||
|         } else { | ||||
|             Local::now().year() | ||||
|         }; | ||||
|  | ||||
|         let rowed_km = Stat::person(db, Some(year), user).await.rowed_km; | ||||
|         let required_km = agebracket.dist_in_km(); | ||||
|         let missing_km = cmp::max(required_km - rowed_km, 0); | ||||
|  | ||||
|         let single_day_trips_over_required_distance = | ||||
|             Logbook::completed_wanderfahrten_with_user_over_km_in_year( | ||||
|                 db, | ||||
|                 user, | ||||
|                 agebracket.required_dist_single_day_in_km(), | ||||
|                 year, | ||||
|                 Filter::SingleDayOnly, | ||||
|             ) | ||||
|             .await; | ||||
|         let multi_day_trips_over_required_distance = | ||||
|             Logbook::completed_wanderfahrten_with_user_over_km_in_year( | ||||
|                 db, | ||||
|                 user, | ||||
|                 agebracket.required_dist_multi_day_in_km(), | ||||
|                 year, | ||||
|                 Filter::MultiDazOnly, | ||||
|             ) | ||||
|             .await; | ||||
|  | ||||
|         let achieved = missing_km == 0 | ||||
|             && (multi_day_trips_over_required_distance.len() >= 1 | ||||
|                 || single_day_trips_over_required_distance.len() >= 2); | ||||
|  | ||||
|         Some(Self { | ||||
|             year, | ||||
|             rowed_km, | ||||
|             category, | ||||
|             required_km, | ||||
|             missing_km, | ||||
|             multi_day_trips_over_required_distance, | ||||
|             single_day_trips_over_required_distance, | ||||
|             multi_day_trips_required_distance: agebracket.required_dist_multi_day_in_km(), | ||||
|             single_day_trips_required_distance: agebracket.required_dist_single_day_in_km(), | ||||
|             achieved, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -195,6 +195,32 @@ ORDER BY rowed_km DESC, u.name; | ||||
|         }) | ||||
|         .collect() | ||||
|     } | ||||
|  | ||||
|     pub async fn total_km(db: &SqlitePool, user: &User) -> Stat { | ||||
|         //TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server) | ||||
|         let row = sqlx::query(&format!( | ||||
|             " | ||||
| SELECT u.name, CAST(SUM(l.distance_in_km) AS INTEGER) AS rowed_km  | ||||
| FROM ( | ||||
|     SELECT * FROM user  | ||||
|     WHERE id={} | ||||
| ) u | ||||
| INNER JOIN rower r ON u.id = r.rower_id | ||||
| INNER JOIN logbook l ON r.logbook_id = l.id | ||||
| WHERE l.distance_in_km IS NOT NULL; | ||||
| ", | ||||
|             user.id | ||||
|         )) | ||||
|         .fetch_one(db) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|         Stat { | ||||
|             name: row.get("name"), | ||||
|             rowed_km: row.get("rowed_km"), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn person(db: &SqlitePool, year: Option<i32>, user: &User) -> Stat { | ||||
|         let year = match year { | ||||
|             Some(year) => year, | ||||
|   | ||||
							
								
								
									
										46
									
								
								src/tera/board/achievement.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/tera/board/achievement.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| use crate::model::{ | ||||
|     personal::Achievements, | ||||
|     role::Role, | ||||
|     user::{User, UserWithDetails, VorstandUser}, | ||||
| }; | ||||
| use rocket::{get, request::FlashMessage, routes, Route, State}; | ||||
| use rocket_dyn_templates::{tera::Context, Template}; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| #[get("/achievement")] | ||||
| async fn index( | ||||
|     db: &State<SqlitePool>, | ||||
|     admin: VorstandUser, | ||||
|     flash: Option<FlashMessage<'_>>, | ||||
| ) -> Template { | ||||
|     let mut context = Context::new(); | ||||
|     if let Some(msg) = flash { | ||||
|         context.insert("flash", &msg.into_inner()); | ||||
|     } | ||||
|  | ||||
|     let role = Role::find_by_name(&db, "Donau Linz").await.unwrap(); | ||||
|     let users = User::all_with_role(&db, &role).await; | ||||
|     let mut people = Vec::new(); | ||||
|     let mut rowingbadge_year = None; | ||||
|     for user in users { | ||||
|         let achievement = Achievements::for_user(&db, &user).await; | ||||
|         if let Some(badge) = &achievement.rowingbadge { | ||||
|             rowingbadge_year = Some(badge.year); | ||||
|         } | ||||
|         people.push((user, achievement)); | ||||
|     } | ||||
|  | ||||
|     context.insert("people", &people); | ||||
|     context.insert("rowingbadge_year", &rowingbadge_year.unwrap()); | ||||
|  | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithDetails::from_user(admin.into_inner(), db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("achievement", context.into_json()) | ||||
| } | ||||
|  | ||||
| pub fn routes() -> Vec<Route> { | ||||
|     routes![index] | ||||
| } | ||||
| @@ -1,9 +1,11 @@ | ||||
| use rocket::Route; | ||||
|  | ||||
| pub mod achievement; | ||||
| pub mod boathouse; | ||||
|  | ||||
| pub fn routes() -> Vec<Route> { | ||||
|     let mut ret = Vec::new(); | ||||
|     ret.append(&mut boathouse::routes()); | ||||
|     ret.append(&mut achievement::routes()); | ||||
|     ret | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user