aequatorpreis #730
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								templates/achievement.html.tera
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								templates/achievement.html.tera
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
{% import "includes/macros" as macros %}
 | 
			
		||||
{% import "includes/forms/log" as log %}
 | 
			
		||||
{% extends "base" %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <link rel="stylesheet" href="/public/table.css" />
 | 
			
		||||
    <div class="max-w-screen-lg w-full">
 | 
			
		||||
        <h1 class="h1">Abzeichen für {{ rowingbadge_year }}</h1>
 | 
			
		||||
        <div class="text-black dark:text-white">
 | 
			
		||||
            <table id="basic">
 | 
			
		||||
                <thead>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <th>Name</th>
 | 
			
		||||
                        <th>Äquatorpreis</th>
 | 
			
		||||
                        <th>Fahrtenabzeichen (FA) geschafft</th>
 | 
			
		||||
                        <th>FA - KM</th>
 | 
			
		||||
                        <th>FA - fehlende KM</th>
 | 
			
		||||
                        <th>Eintagesausfahrten</th>
 | 
			
		||||
                        <th>Mehrtagesausfahrten</th>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                    {% for person in people %}
 | 
			
		||||
                        {% set user = person.0 %}
 | 
			
		||||
                        {% set achievement = person.1 %}
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>{{ user.name }}</td>
 | 
			
		||||
                            <td>{% if achievement.new_equatorprice_this_season %}(NEU!) {% endif %}{{ achievement.curr_equatorprice_name }}  </td>
 | 
			
		||||
                            {% if achievement.rowingbadge %}
 | 
			
		||||
                                {% set badge = achievement.rowingbadge %}
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    {% if badge.achieved %}
 | 
			
		||||
                                        ja
 | 
			
		||||
                                    {% else %}
 | 
			
		||||
                                        nein
 | 
			
		||||
                                    {% endif %}
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>{{ badge.rowed_km }} / {{ badge.required_km }}</td>
 | 
			
		||||
                                <td>{{ badge.missing_km }}</td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <details>
 | 
			
		||||
                                        <summary>
 | 
			
		||||
                                            > {{ badge.single_day_trips_required_distance }} km: {{ badge.single_day_trips_over_required_distance | length }} / 2
 | 
			
		||||
                                        </summary>
 | 
			
		||||
                                        {% for log in badge.single_day_trips_over_required_distance %}
 | 
			
		||||
                                            {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, hide_type=true) }}
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                    </details>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <details>
 | 
			
		||||
                                        <summary>
 | 
			
		||||
                                            > {{ badge.multi_day_trips_required_distance }} km: {{ badge.multi_day_trips_over_required_distance | length }} / 1
 | 
			
		||||
                                        </summary>
 | 
			
		||||
                                        {% for log in badge.multi_day_trips_over_required_distance %}
 | 
			
		||||
                                            {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, hide_type=true) }}
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                    </details>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            {% else %}
 | 
			
		||||
                                <td>no birthdate of this person</td>
 | 
			
		||||
                                <td>no birthdate of this person</td>
 | 
			
		||||
                                <td>no birthdate of this person</td>
 | 
			
		||||
                                <td>no birthdate of this person</td>
 | 
			
		||||
                                <td>no birthdate of this person</td>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script src="/public/jstable.min.js"></script>
 | 
			
		||||
    <script src="/public/table.js"></script>
 | 
			
		||||
{% endblock content %}
 | 
			
		||||
@@ -173,13 +173,13 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endmacro show %}
 | 
			
		||||
{% macro show_old(log, state, allowed_to_close=false, allowed_to_edit=false, index) %}
 | 
			
		||||
{% macro show_old(log, state, allowed_to_close=false, allowed_to_edit=false, index, hide_type=false) %}
 | 
			
		||||
    <div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative"
 | 
			
		||||
         data-filterable="true"
 | 
			
		||||
         data-filter="{{ log.boat.name }} {% for rower in log.rowers %}{{ rower.name }}{% endfor %}">
 | 
			
		||||
        <details>
 | 
			
		||||
            <summary style="list-style: none;">
 | 
			
		||||
                {% if log.logtype %}
 | 
			
		||||
                {% if log.logtype and not hide_type %}
 | 
			
		||||
                    <div class="absolute top-0 right-0 bg-primary-100 rounded-bl-md text-primary-950 text-xs w-32 px-2 py-1 text-center font-bold">
 | 
			
		||||
                        {% if log.logtype == 1 %}
 | 
			
		||||
                            Wanderfahrt
 | 
			
		||||
 
 | 
			
		||||
@@ -79,25 +79,79 @@
 | 
			
		||||
                    <h2 class="h2">Persönliches</h2>
 | 
			
		||||
                    <div class="mx-2 divide-y divide-gray-200 dark:divide-primary-600">
 | 
			
		||||
                        <div class="py-3">
 | 
			
		||||
                            <h3 class="font-bold text-xl mb-3">Äquatorpreis</h3>
 | 
			
		||||
                            <h3 class="font-bold text-xl">
 | 
			
		||||
                                {% if achievements.rowingbadge and achievements.rowingbadge.achieved %}🎉{% endif %}
 | 
			
		||||
                                Fahrtenabzeichen
 | 
			
		||||
                                {% if achievements.rowingbadge %}{{ achievements.rowingbadge.year }}{% endif %}
 | 
			
		||||
                                <span><a href="http://www.rudern.at/OFFICE/Downloads/Ausschreibungen/2022/Wanderfahrten//Fahrtenabzeichen%20%C3%84quatorpreis%20und%20Danubius%202022.pdf"
 | 
			
		||||
   target="_blank"
 | 
			
		||||
   class="w-7 h-7 inline-flex align-center justify-center rounded-full bg-primary-500 ml-2">?</a></span>
 | 
			
		||||
                            </h3>
 | 
			
		||||
                            {% if achievements.rowingbadge %}
 | 
			
		||||
                                {% set badge = achievements.rowingbadge %}
 | 
			
		||||
                                <div class="mb-3">{{ badge.category }}</div>
 | 
			
		||||
                                <label for="rowingbadge" class="label">Kilometer ({{ badge.rowed_km }} / {{ badge.required_km }} km)</label>
 | 
			
		||||
                                <progress id="rowingbadge"
 | 
			
		||||
                                          class="w-full block my-3"
 | 
			
		||||
                                          value="{{ badge.rowed_km }}"
 | 
			
		||||
                                          max="{{ badge.required_km }}"></progress>
 | 
			
		||||
                                <h4 class="font-bold mt-4">Wanderfahrten</h4>
 | 
			
		||||
                                <div>Nur 1 muss erreicht werden</div>
 | 
			
		||||
                                <ol class="list-decimal ml-4 my-3">
 | 
			
		||||
                                    <li>
 | 
			
		||||
                                        {% if badge.multi_day_trips_over_required_distance | length >= 1 %}
 | 
			
		||||
                                            ✅
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                            ❌
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                        1 mehrtägige Wanderfahrt > {{ badge.multi_day_trips_required_distance }} km
 | 
			
		||||
                                    </li>
 | 
			
		||||
                                    <li>
 | 
			
		||||
                                        {% if badge.single_day_trips_over_required_distance | length >= 2 %}
 | 
			
		||||
                                            ✅
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                            ❌
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                        2 eintägige Wanderfahrten > {{ badge.single_day_trips_required_distance }} km
 | 
			
		||||
                                    </li>
 | 
			
		||||
                                </ol>
 | 
			
		||||
                                <details>
 | 
			
		||||
                                    <summary>Details zu den Wanderfahrten</summary>
 | 
			
		||||
                                    <div class="mt-3">
 | 
			
		||||
                                        {% for log in badge.single_day_trips_over_required_distance %}
 | 
			
		||||
                                            {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index) }}
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                        {% for log in badge.multi_day_trips_over_required_distance %}
 | 
			
		||||
                                            {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index) }}
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </details>
 | 
			
		||||
                            {% else %}
 | 
			
		||||
                                Wir haben leider kein Geburtsdatum von dir und können dir leider deinen heurigen Status für das Fahrtenabzeichen nicht anzeigen. Wenn du dein Geburtsdatum an <a href="mailto:it@rudernlinz.at" class="underline">it@rudernlinz.at</a> schreibst, lässt sich das ändern :-)
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="py-3">
 | 
			
		||||
                            <h3 class="font-bold text-xl mb-3">
 | 
			
		||||
                                Äquatorpreis
 | 
			
		||||
                                <span><a href="http://www.rudern.at/OFFICE/Downloads/Ausschreibungen/2022/Wanderfahrten//Fahrtenabzeichen%20%C3%84quatorpreis%20und%20Danubius%202022.pdf"
 | 
			
		||||
   target="_blank"
 | 
			
		||||
   class="w-7 h-7 inline-flex align-center justify-center rounded-full bg-primary-500 ml-2">?</a></span>
 | 
			
		||||
                            </h3>
 | 
			
		||||
                            {% set price = achievements.equatorprice %}
 | 
			
		||||
                            {% if price.level == "DONE" %}
 | 
			
		||||
                                Gratuliere, du hast alles erreicht, was es beim Äquatorpreis zu erreichen gibt.
 | 
			
		||||
                                Gratuliere, du hast alles in deinem Rudererleben erreicht, was es (beim Äquatorpreis) zu erreichen gibt.
 | 
			
		||||
                            {% else %}
 | 
			
		||||
                                <label for="equatorprice" class="label">{{ price.level }}</label>
 | 
			
		||||
                                <label for="equatorprice" class="label">{{ price.desc }} ({{ price.rowed_km }} / {{ price.required_km }} km)</label>
 | 
			
		||||
                                <progress id="equatorprice"
 | 
			
		||||
                                          class="w-full block my-3"
 | 
			
		||||
                                          value="{{ price.rowed_km }}"
 | 
			
		||||
                                          max="{{ price.required_km }}"></progress>
 | 
			
		||||
                                <details>
 | 
			
		||||
                                    <summary>Details</summary>
 | 
			
		||||
                                    Du bist insgesamt {{ price.rowed_km }} km gerudert. Um den Äquatorpreis in {{ price.level }} zu erhalten, benötigst du noch {{ price.missing_km }} um die notwendigen {{ price.required_km }} km zu erreichen.
 | 
			
		||||
                                    Du bist insgesamt {{ price.rowed_km }} km gerudert. Um den Äquatorpreis in {{ price.level }} zu erhalten, benötigst du noch {{ price.missing_km }} km um die notwendigen {{ price.required_km }} km zu erreichen.
 | 
			
		||||
                                </details>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="py-1">
 | 
			
		||||
                            <h3>Fahrtenabzeichen</h3>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
 | 
			
		||||
@@ -198,6 +252,10 @@
 | 
			
		||||
                            <a href="/admin/notification"
 | 
			
		||||
                               class="block w-100 py-2 hover:text-primary-600">Nachricht ausschreiben</a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="py-1">
 | 
			
		||||
                            <a href="/board/achievement"
 | 
			
		||||
                               class="block w-100 py-2 hover:text-primary-600">Abzeichen</a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user