aequatorpreis #729
@@ -25,6 +25,7 @@ pub mod logbook;
 | 
				
			|||||||
pub mod logtype;
 | 
					pub mod logtype;
 | 
				
			||||||
pub mod mail;
 | 
					pub mod mail;
 | 
				
			||||||
pub mod notification;
 | 
					pub mod notification;
 | 
				
			||||||
 | 
					pub mod personal;
 | 
				
			||||||
pub mod role;
 | 
					pub mod role;
 | 
				
			||||||
pub mod rower;
 | 
					pub mod rower;
 | 
				
			||||||
pub mod stat;
 | 
					pub mod stat;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										58
									
								
								src/model/personal/equatorprice.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/model/personal/equatorprice.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					use serde::Serialize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize)]
 | 
				
			||||||
 | 
					enum Level {
 | 
				
			||||||
 | 
					    BRONZE,
 | 
				
			||||||
 | 
					    SILVER,
 | 
				
			||||||
 | 
					    GOLD,
 | 
				
			||||||
 | 
					    DIAMOND,
 | 
				
			||||||
 | 
					    DONE,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Level {
 | 
				
			||||||
 | 
					    fn required_km(&self) -> i32 {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Level::BRONZE => 40000,
 | 
				
			||||||
 | 
					            Level::SILVER => 80000,
 | 
				
			||||||
 | 
					            Level::GOLD => 100000,
 | 
				
			||||||
 | 
					            Level::DIAMOND => 200000,
 | 
				
			||||||
 | 
					            Level::DONE => 0,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn next_level(km: i32) -> Self {
 | 
				
			||||||
 | 
					        if km < Level::BRONZE.required_km() {
 | 
				
			||||||
 | 
					            Level::BRONZE
 | 
				
			||||||
 | 
					        } else if km < Level::SILVER.required_km() {
 | 
				
			||||||
 | 
					            Level::SILVER
 | 
				
			||||||
 | 
					        } else if km < Level::GOLD.required_km() {
 | 
				
			||||||
 | 
					            Level::GOLD
 | 
				
			||||||
 | 
					        } else if km < Level::DIAMOND.required_km() {
 | 
				
			||||||
 | 
					            Level::BRONZE
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Level::DONE
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize)]
 | 
				
			||||||
 | 
					pub(crate) struct Next {
 | 
				
			||||||
 | 
					    level: Level,
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					        let required_km = level.required_km();
 | 
				
			||||||
 | 
					        let missing_km = required_km - km;
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            level,
 | 
				
			||||||
 | 
					            missing_km,
 | 
				
			||||||
 | 
					            required_km,
 | 
				
			||||||
 | 
					            rowed_km: km,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/model/personal/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/model/personal/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					use serde::Serialize;
 | 
				
			||||||
 | 
					use sqlx::SqlitePool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{stat::Stat, user::User};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) mod equatorprice;
 | 
				
			||||||
 | 
					pub(crate) mod rowingbadge;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize)]
 | 
				
			||||||
 | 
					pub(crate) struct Achievements {
 | 
				
			||||||
 | 
					    equatorprice: equatorprice::Next,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Achievements {
 | 
				
			||||||
 | 
					    pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Self {
 | 
				
			||||||
 | 
					        let rowed_km = Stat::person(db, None, user).await.rowed_km;
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            equatorprice: equatorprice::Next::rowed_km(rowed_km),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								src/model/personal/rowingbadge.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/model/personal/rowingbadge.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					use chrono::{Datelike, Local, NaiveDate};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::model::user::User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum AgeBracket {
 | 
				
			||||||
 | 
					    Till14,
 | 
				
			||||||
 | 
					    From14Till18,
 | 
				
			||||||
 | 
					    From19Till30,
 | 
				
			||||||
 | 
					    From31Till60,
 | 
				
			||||||
 | 
					    From61Till75,
 | 
				
			||||||
 | 
					    From76,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TryFrom<&User> for AgeBracket {
 | 
				
			||||||
 | 
					    type Error = String;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn try_from(value: &User) -> Result<Self, Self::Error> {
 | 
				
			||||||
 | 
					        if let Some(birthdate) = value.birthdate.clone() {
 | 
				
			||||||
 | 
					            let today = Local::now().date_naive();
 | 
				
			||||||
 | 
					            let birthdate = NaiveDate::parse_from_str(&birthdate, "%Y-%m-%d").unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let age = today.year() - birthdate.year();
 | 
				
			||||||
 | 
					            if age <= 14 {
 | 
				
			||||||
 | 
					                Ok(AgeBracket::Till14)
 | 
				
			||||||
 | 
					            } else if age <= 18 {
 | 
				
			||||||
 | 
					                Ok(AgeBracket::From14Till18)
 | 
				
			||||||
 | 
					            } else if age <= 30 {
 | 
				
			||||||
 | 
					                Ok(AgeBracket::From19Till30)
 | 
				
			||||||
 | 
					            } else if age <= 60 {
 | 
				
			||||||
 | 
					                Ok(AgeBracket::From31Till60)
 | 
				
			||||||
 | 
					            } else if age <= 75 {
 | 
				
			||||||
 | 
					                Ok(AgeBracket::From61Till75)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                Ok(AgeBracket::From76)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Err("User has no birthdate".to_string())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -24,71 +24,6 @@ use crate::{
 | 
				
			|||||||
    SCHECKBUCH, STUDENT_OR_PUPIL, UNTERSTUETZEND,
 | 
					    SCHECKBUCH, STUDENT_OR_PUPIL, UNTERSTUETZEND,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod aequatorpreis {
 | 
					 | 
				
			||||||
    use chrono::{Datelike, Local, NaiveDate};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    use super::User;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    enum AgeBracket {
 | 
					 | 
				
			||||||
        Till14,
 | 
					 | 
				
			||||||
        From14Till18,
 | 
					 | 
				
			||||||
        From19Till30,
 | 
					 | 
				
			||||||
        From31Till60,
 | 
					 | 
				
			||||||
        From61Till75,
 | 
					 | 
				
			||||||
        From76,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    impl TryFrom<&User> for AgeBracket {
 | 
					 | 
				
			||||||
        type Error = String;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fn try_from(value: &User) -> Result<Self, Self::Error> {
 | 
					 | 
				
			||||||
            if let Some(birthdate) = value.birthdate.clone() {
 | 
					 | 
				
			||||||
                let today = Local::now().date_naive();
 | 
					 | 
				
			||||||
                let birthdate = NaiveDate::parse_from_str(&birthdate, "%Y-%m-%d").unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let age = today.year() - birthdate.year();
 | 
					 | 
				
			||||||
                if age <= 14 {
 | 
					 | 
				
			||||||
                    Ok(AgeBracket::Till14)
 | 
					 | 
				
			||||||
                } else if age <= 18 {
 | 
					 | 
				
			||||||
                    Ok(AgeBracket::From14Till18)
 | 
					 | 
				
			||||||
                } else if age <= 30 {
 | 
					 | 
				
			||||||
                    Ok(AgeBracket::From19Till30)
 | 
					 | 
				
			||||||
                } else if age <= 60 {
 | 
					 | 
				
			||||||
                    Ok(AgeBracket::From31Till60)
 | 
					 | 
				
			||||||
                } else if age <= 75 {
 | 
					 | 
				
			||||||
                    Ok(AgeBracket::From61Till75)
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    Ok(AgeBracket::From76)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                Err("User has no birthdate".to_string())
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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",
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(FromRow, Serialize, Deserialize, Clone, Debug, Eq, Hash, PartialEq)]
 | 
					#[derive(FromRow, Serialize, Deserialize, Clone, Debug, Eq, Hash, PartialEq)]
 | 
				
			||||||
pub struct User {
 | 
					pub struct User {
 | 
				
			||||||
    pub id: i64,
 | 
					    pub id: i64,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,7 @@ use crate::{
 | 
				
			|||||||
    model::{
 | 
					    model::{
 | 
				
			||||||
        logbook::Logbook,
 | 
					        logbook::Logbook,
 | 
				
			||||||
        notification::Notification,
 | 
					        notification::Notification,
 | 
				
			||||||
 | 
					        personal::Achievements,
 | 
				
			||||||
        role::Role,
 | 
					        role::Role,
 | 
				
			||||||
        user::{User, UserWithDetails},
 | 
					        user::{User, UserWithDetails},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -62,6 +63,7 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_
 | 
				
			|||||||
        context.insert("last_trips", &last_trips);
 | 
					        context.insert("last_trips", &last_trips);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context.insert("achievements", &Achievements::for_user(db, &user).await);
 | 
				
			||||||
    context.insert("notifications", &Notification::for_user(db, &user).await);
 | 
					    context.insert("notifications", &Notification::for_user(db, &user).await);
 | 
				
			||||||
    context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await);
 | 
					    context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await);
 | 
				
			||||||
    context.insert("costs_scheckbuch", &SCHECKBUCH);
 | 
					    context.insert("costs_scheckbuch", &SCHECKBUCH);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,6 +74,32 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
            {% if "Donau Linz" in loggedin_user.roles and "Unterstützend" not in loggedin_user.roles and "Förderndes Mitglied" not in loggedin_user.roles %}
 | 
					            {% if "Donau Linz" in loggedin_user.roles and "Unterstützend" not in loggedin_user.roles and "Förderndes Mitglied" not in loggedin_user.roles %}
 | 
				
			||||||
 | 
					                <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
 | 
				
			||||||
 | 
					                     role="alert">
 | 
				
			||||||
 | 
					                    <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>
 | 
				
			||||||
 | 
					                            {% set price = achievements.equatorprice %}
 | 
				
			||||||
 | 
					                            {% if price.level == "DONE" %}
 | 
				
			||||||
 | 
					                                Gratuliere, du hast alles erreicht, was es beim Äquatorpreis zu erreichen gibt.
 | 
				
			||||||
 | 
					                            {% else %}
 | 
				
			||||||
 | 
					                                <label for="equatorprice" class="label">{{ price.level }}</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.
 | 
				
			||||||
 | 
					                                </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"
 | 
					                <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
 | 
				
			||||||
                     role="alert">
 | 
					                     role="alert">
 | 
				
			||||||
                    <h2 class="h2">Aktives Vereinsmitglied</h2>
 | 
					                    <h2 class="h2">Aktives Vereinsmitglied</h2>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user