notification #292
| @@ -151,3 +151,12 @@ CREATE TABLE IF NOT EXISTS "boathouse" ( | |||||||
|     CONSTRAINT unq UNIQUE (aisle, side, level) -- only 1 boat allowed to rest at each space  |     CONSTRAINT unq UNIQUE (aisle, side, level) -- only 1 boat allowed to rest at each space  | ||||||
| ); | ); | ||||||
|  |  | ||||||
|  | CREATE TABLE IF NOT EXISTS "notification" ( | ||||||
|  | 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||||
|  | 	"user_id" INTEGER NOT NULL REFERENCES user(id), | ||||||
|  | 	"message" TEXT NOT NULL, | ||||||
|  | 	"read_at" DATETIME, | ||||||
|  | 	"created_at" DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, | ||||||
|  | 	"category" TEXT NOT NULL, | ||||||
|  | 	"link" TEXT | ||||||
|  | ); | ||||||
|   | |||||||
| @@ -61,3 +61,4 @@ INSERT INTO "logbook" (boat_id, shipmaster, steering_person, shipmaster_only_ste | |||||||
| INSERT INTO "rower" (logbook_id, rower_id) VALUES(3,3); | INSERT INTO "rower" (logbook_id, rower_id) VALUES(3,3); | ||||||
| INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at) VALUES(4,'Dolle bei Position 2 fehlt', 5, '2142-12-24 15:02'); | INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at) VALUES(4,'Dolle bei Position 2 fehlt', 5, '2142-12-24 15:02'); | ||||||
| INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at, lock_boat) VALUES(5, 'TOHT', 5, '2142-12-24 15:02', 1); | INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at, lock_boat) VALUES(5, 'TOHT', 5, '2142-12-24 15:02', 1); | ||||||
|  | INSERT INTO "notification" (user_id, message, category) VALUES (1, 'This is a test notification', 'test-cat'); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ use rocket::FromForm; | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; | use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; | ||||||
|  |  | ||||||
| use super::{boat::Boat, log::Log, rower::Rower, user::User}; | use super::{boat::Boat, log::Log, notification::Notification, rower::Rower, user::User}; | ||||||
|  |  | ||||||
| #[derive(FromRow, Serialize, Clone, Debug)] | #[derive(FromRow, Serialize, Clone, Debug)] | ||||||
| pub struct Logbook { | pub struct Logbook { | ||||||
| @@ -525,6 +525,23 @@ ORDER BY departure DESC | |||||||
|             Rower::create(db, self.id, *rower) |             Rower::create(db, self.id, *rower) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| LogbookUpdateError::RowerCreateError(*rower, e.to_string()))?; |                 .map_err(|e| LogbookUpdateError::RowerCreateError(*rower, e.to_string()))?; | ||||||
|  |  | ||||||
|  |             let user = User::find_by_id_tx(db, *rower as i32).await.unwrap(); | ||||||
|  |             Notification::create_with_tx( | ||||||
|  |                 db, | ||||||
|  |                 &user, | ||||||
|  |                 &format!( | ||||||
|  |                     "Ausfahrt am {}.{}.{} nach {} ({} km)", | ||||||
|  |                     dep.day(), | ||||||
|  |                     dep.month(), | ||||||
|  |                     dep.year(), | ||||||
|  |                     log.destination, | ||||||
|  |                     log.distance_in_km | ||||||
|  |                 ), | ||||||
|  |                 "Neuer Logbucheintrag", | ||||||
|  |                 None, | ||||||
|  |             ) | ||||||
|  |             .await; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         sqlx::query!( |         sqlx::query!( | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ pub mod log; | |||||||
| pub mod logbook; | pub mod logbook; | ||||||
| pub mod logtype; | pub mod logtype; | ||||||
| pub mod mail; | pub mod mail; | ||||||
|  | pub mod notification; | ||||||
| pub mod planned_event; | pub mod planned_event; | ||||||
| pub mod role; | pub mod role; | ||||||
| pub mod rower; | pub mod rower; | ||||||
|   | |||||||
| @@ -1,36 +1,78 @@ | |||||||
| use chrono::{DateTime, Local, NaiveDateTime, TimeZone}; | use std::ops::DerefMut; | ||||||
|  |  | ||||||
|  | use chrono::NaiveDateTime; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use sqlx::{FromRow, SqlitePool}; | use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; | ||||||
|  |  | ||||||
|  | use super::user::User; | ||||||
|  |  | ||||||
| #[derive(FromRow, Debug, Serialize, Deserialize)] | #[derive(FromRow, Debug, Serialize, Deserialize)] | ||||||
| pub struct Notification { | pub struct Notification { | ||||||
|     pub id: i64, |     pub id: i64, | ||||||
|     pub user_id: i64, |     pub user_id: i64, | ||||||
|     pub message: String, |     pub message: String, | ||||||
|     pub read_at: NaiveDateTime, |     pub read_at: Option<NaiveDateTime>, | ||||||
|  |     pub created_at: NaiveDateTime, | ||||||
|     pub category: String, |     pub category: String, | ||||||
|  |     pub link: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Notification { | impl Notification { | ||||||
|     //pub async fn create(db: &SqlitePool, msg: String) -> bool { |     pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> { | ||||||
|     //    sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,) |         sqlx::query_as!(Self, "SELECT  * FROM notification WHERE id like ?", id) | ||||||
|     //        .execute(db) |             .fetch_one(db) | ||||||
|     //        .await |             .await | ||||||
|     //        .is_ok() |             .ok() | ||||||
|     //} |     } | ||||||
|  |     pub async fn create_with_tx( | ||||||
|  |         db: &mut Transaction<'_, Sqlite>, | ||||||
|  |         user: &User, | ||||||
|  |         message: &str, | ||||||
|  |         category: &str, | ||||||
|  |         link: Option<&str>, | ||||||
|  |     ) { | ||||||
|  |         sqlx::query!( | ||||||
|  |             "INSERT INTO notification(user_id, message, category, link) VALUES (?, ?, ?, ?)", | ||||||
|  |             user.id, | ||||||
|  |             message, | ||||||
|  |             category, | ||||||
|  |             link | ||||||
|  |         ) | ||||||
|  |         .execute(db.deref_mut()) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async fn for_user(db: &SqlitePool, user: &User) -> Vec<Self> { |     pub async fn create( | ||||||
|  |         db: &SqlitePool, | ||||||
|  |         user: &User, | ||||||
|  |         message: &str, | ||||||
|  |         category: &str, | ||||||
|  |         link: Option<&str>, | ||||||
|  |     ) { | ||||||
|  |         let mut tx = db.begin().await.unwrap(); | ||||||
|  |         Self::create_with_tx(&mut tx, user, message, category, link).await; | ||||||
|  |         tx.commit().await.unwrap(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn for_user(db: &SqlitePool, user: &User) -> Vec<Self> { | ||||||
|         sqlx::query_as!( |         sqlx::query_as!( | ||||||
|             Log, |             Self, | ||||||
|             " |             "SELECT * FROM notification WHERE user_id = ?", | ||||||
| SELECT id, user_id, message, read_at, category |  | ||||||
| FROM notification |  | ||||||
| WHERE user_id = {} |  | ||||||
|     ", |  | ||||||
|             user.id |             user.id | ||||||
|         ) |         ) | ||||||
|         .fetch_all(db) |         .fetch_all(db) | ||||||
|         .await |         .await | ||||||
|         .unwrap() |         .unwrap() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub async fn mark_read(self, db: &SqlitePool) { | ||||||
|  |         sqlx::query!( | ||||||
|  |             "UPDATE notification SET read_at=CURRENT_TIMESTAMP WHERE id=?", | ||||||
|  |             self.id | ||||||
|  |         ) | ||||||
|  |         .execute(db) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,21 +3,22 @@ use serde::Serialize; | |||||||
| use sqlx::SqlitePool; | use sqlx::SqlitePool; | ||||||
|  |  | ||||||
| use super::{ | use super::{ | ||||||
|  |     notification::Notification, | ||||||
|     planned_event::{PlannedEvent, Registration}, |     planned_event::{PlannedEvent, Registration}, | ||||||
|     tripdetails::TripDetails, |     tripdetails::TripDetails, | ||||||
|     triptype::TripType, |     triptype::TripType, | ||||||
|     user::CoxUser, |     user::{CoxUser, User}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #[derive(Serialize, Clone, Debug)] | #[derive(Serialize, Clone, Debug)] | ||||||
| pub struct Trip { | pub struct Trip { | ||||||
|     id: i64, |     id: i64, | ||||||
|     cox_id: i64, |     pub cox_id: i64, | ||||||
|     cox_name: String, |     cox_name: String, | ||||||
|     trip_details_id: Option<i64>, |     trip_details_id: Option<i64>, | ||||||
|     planned_starting_time: String, |     planned_starting_time: String, | ||||||
|     pub max_people: i64, |     pub max_people: i64, | ||||||
|     day: String, |     pub day: String, | ||||||
|     pub notes: Option<String>, |     pub notes: Option<String>, | ||||||
|     pub allow_guests: bool, |     pub allow_guests: bool, | ||||||
|     trip_type_id: Option<i64>, |     trip_type_id: Option<i64>, | ||||||
| @@ -43,6 +44,51 @@ impl Trip { | |||||||
|         ) |         ) | ||||||
|         .execute(db) |         .execute(db) | ||||||
|         .await; |         .await; | ||||||
|  |  | ||||||
|  |         let same_starting_datetime = TripDetails::find_by_startingdatetime( | ||||||
|  |             db, | ||||||
|  |             trip_details.day, | ||||||
|  |             trip_details.planned_starting_time, | ||||||
|  |         ) | ||||||
|  |         .await; | ||||||
|  |         if same_starting_datetime.len() > 1 { | ||||||
|  |             for notify in same_starting_datetime { | ||||||
|  |                 if notify.id != trip_details.id { | ||||||
|  |                     // notify everyone except oneself | ||||||
|  |                     if let Some(trip) = Trip::find_by_trip_details(db, notify.id).await { | ||||||
|  |                         let user = User::find_by_id(db, trip.cox_id as i32).await.unwrap(); | ||||||
|  |                         Notification::create( | ||||||
|  |                             db, | ||||||
|  |                             &user, | ||||||
|  |                             &format!( | ||||||
|  |                                 "{} hat eine Ausfahrt zur selben Zeit ({} um {}) wie du erstellt", | ||||||
|  |                                 user.name, trip.day, trip.planned_starting_time | ||||||
|  |                             ), | ||||||
|  |                             "Neue Ausfahrt zur selben Zeit".into(), | ||||||
|  |                             None, | ||||||
|  |                         ) | ||||||
|  |                         .await; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn find_by_trip_details(db: &SqlitePool, tripdetails_id: i64) -> Option<Self> { | ||||||
|  |         sqlx::query_as!( | ||||||
|  |             Self, | ||||||
|  |             " | ||||||
|  | SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, trip_details.notes, allow_guests, trip_type_id, always_show, is_locked | ||||||
|  | FROM trip  | ||||||
|  | INNER JOIN trip_details ON trip.trip_details_id = trip_details.id | ||||||
|  | INNER JOIN user ON trip.cox_id = user.id | ||||||
|  | WHERE trip_details.id=? | ||||||
|  |         ", | ||||||
|  |             tripdetails_id | ||||||
|  |         ) | ||||||
|  |         .fetch_one(db) | ||||||
|  |         .await | ||||||
|  |         .ok() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> { |     pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> { | ||||||
|   | |||||||
| @@ -46,6 +46,24 @@ WHERE id like ? | |||||||
|         .ok() |         .ok() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub async fn find_by_startingdatetime( | ||||||
|  |         db: &SqlitePool, | ||||||
|  |         day: String, | ||||||
|  |         planned_starting_time: String, | ||||||
|  |     ) -> Vec<Self> { | ||||||
|  |         sqlx::query_as!( | ||||||
|  |             Self, | ||||||
|  |             " | ||||||
|  | SELECT id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show, is_locked | ||||||
|  | FROM trip_details  | ||||||
|  | WHERE day = ? AND planned_starting_time = ? | ||||||
|  |         " | ||||||
|  |         , day, planned_starting_time | ||||||
|  |         ) | ||||||
|  |         .fetch_all(db) | ||||||
|  |         .await.unwrap() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Creates a new entry in `trip_details` and returns its id. |     /// Creates a new entry in `trip_details` and returns its id. | ||||||
|     pub async fn create(db: &SqlitePool, tripdetails: TripDetailsToAdd<'_>) -> i64 { |     pub async fn create(db: &SqlitePool, tripdetails: TripDetailsToAdd<'_>) -> i64 { | ||||||
|         let query = sqlx::query!( |         let query = sqlx::query!( | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| use sqlx::SqlitePool; | use sqlx::SqlitePool; | ||||||
|  |  | ||||||
| use super::{tripdetails::TripDetails, user::User}; | use super::{notification::Notification, trip::Trip, tripdetails::TripDetails, user::User}; | ||||||
| use crate::model::tripdetails::{Action, CoxAtTrip::Yes}; | use crate::model::tripdetails::{Action, CoxAtTrip::Yes}; | ||||||
|  |  | ||||||
| pub struct UserTrip {} | pub struct UserTrip {} | ||||||
| @@ -27,6 +27,7 @@ impl UserTrip { | |||||||
|         //TODO: Check if user sees the event (otherwise she could forge trip_details_id) |         //TODO: Check if user sees the event (otherwise she could forge trip_details_id) | ||||||
|  |  | ||||||
|         let is_cox = trip_details.user_is_cox(db, user).await; |         let is_cox = trip_details.user_is_cox(db, user).await; | ||||||
|  |         let mut name_newly_registered_person = String::new(); | ||||||
|         if user_note.is_none() { |         if user_note.is_none() { | ||||||
|             if let Yes(action) = is_cox { |             if let Yes(action) = is_cox { | ||||||
|                 match action { |                 match action { | ||||||
| @@ -47,6 +48,8 @@ impl UserTrip { | |||||||
|             .execute(db) |             .execute(db) | ||||||
|             .await |             .await | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|  |  | ||||||
|  |             name_newly_registered_person = user.name.clone(); | ||||||
|         } else { |         } else { | ||||||
|             if !trip_details.user_allowed_to_change(db, user).await { |             if !trip_details.user_allowed_to_change(db, user).await { | ||||||
|                 return Err(UserTripError::NotAllowedToAddGuest); |                 return Err(UserTripError::NotAllowedToAddGuest); | ||||||
| @@ -59,6 +62,23 @@ impl UserTrip { | |||||||
|             .execute(db) |             .execute(db) | ||||||
|             .await |             .await | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|  |  | ||||||
|  |             name_newly_registered_person = user_note.unwrap(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if let Some(trip) = Trip::find_by_trip_details(db, trip_details.id).await { | ||||||
|  |             let cox = User::find_by_id(db, trip.cox_id as i32).await.unwrap(); | ||||||
|  |             Notification::create( | ||||||
|  |                 db, | ||||||
|  |                 &cox, | ||||||
|  |                 &format!( | ||||||
|  |                     "{} hat sich für deine Ausfahrt am {} registriert", | ||||||
|  |                     name_newly_registered_person, trip.day | ||||||
|  |                 ), | ||||||
|  |                 "Registrierung bei deiner Ausfahrt", | ||||||
|  |                 None, | ||||||
|  |             ) | ||||||
|  |             .await; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|   | |||||||
| @@ -17,7 +17,10 @@ use serde::Deserialize; | |||||||
| use sqlx::SqlitePool; | use sqlx::SqlitePool; | ||||||
| use tera::Context; | use tera::Context; | ||||||
|  |  | ||||||
| use crate::model::user::{User, UserWithRoles}; | use crate::model::{ | ||||||
|  |     notification::Notification, | ||||||
|  |     user::{User, UserWithRoles}, | ||||||
|  | }; | ||||||
|  |  | ||||||
| pub(crate) mod admin; | pub(crate) mod admin; | ||||||
| mod auth; | mod auth; | ||||||
| @@ -27,6 +30,7 @@ mod cox; | |||||||
| mod ergo; | mod ergo; | ||||||
| mod log; | mod log; | ||||||
| mod misc; | mod misc; | ||||||
|  | mod notification; | ||||||
| mod planned; | mod planned; | ||||||
| mod stat; | mod stat; | ||||||
|  |  | ||||||
| @@ -43,6 +47,7 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_ | |||||||
|         context.insert("flash", &msg.into_inner()); |         context.insert("flash", &msg.into_inner()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     context.insert("notifications", &Notification::for_user(db, &user).await); | ||||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); |     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||||
|     Template::render("index", context.into_json()) |     Template::render("index", context.into_json()) | ||||||
| } | } | ||||||
| @@ -86,6 +91,7 @@ pub fn config(rocket: Rocket<Build>) -> Rocket<Build> { | |||||||
|         .mount("/log", log::routes()) |         .mount("/log", log::routes()) | ||||||
|         .mount("/planned", planned::routes()) |         .mount("/planned", planned::routes()) | ||||||
|         .mount("/ergo", ergo::routes()) |         .mount("/ergo", ergo::routes()) | ||||||
|  |         .mount("/notification", notification::routes()) | ||||||
|         .mount("/stat", stat::routes()) |         .mount("/stat", stat::routes()) | ||||||
|         .mount("/boatdamage", boatdamage::routes()) |         .mount("/boatdamage", boatdamage::routes()) | ||||||
|         .mount("/cox", cox::routes()) |         .mount("/cox", cox::routes()) | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								src/tera/notification.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/tera/notification.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | use rocket::{ | ||||||
|  |     get, | ||||||
|  |     response::{Flash, Redirect}, | ||||||
|  |     routes, Route, State, | ||||||
|  | }; | ||||||
|  | use sqlx::SqlitePool; | ||||||
|  |  | ||||||
|  | use crate::model::{notification::Notification, user::User}; | ||||||
|  |  | ||||||
|  | #[get("/<notification_id>/read")] | ||||||
|  | async fn mark_read(db: &State<SqlitePool>, user: User, notification_id: i64) -> Flash<Redirect> { | ||||||
|  |     let Some(notification) = Notification::find_by_id(db, notification_id).await else { | ||||||
|  |         return Flash::error( | ||||||
|  |             Redirect::to("/"), | ||||||
|  |             format!("Nachricht mit ID {notification_id} nicht gefunden."), | ||||||
|  |         ); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if notification.user_id == user.id { | ||||||
|  |         notification.mark_read(db).await; | ||||||
|  |         Flash::success(Redirect::to("/"), "Nachricht als gelesen markiert") | ||||||
|  |     } else { | ||||||
|  |         Flash::success( | ||||||
|  |             Redirect::to("/"), | ||||||
|  |             "Du kannst fremde Nachrichten nicht als gelesen markieren.", | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn routes() -> Vec<Route> { | ||||||
|  |     routes![mark_read] | ||||||
|  | } | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | CREATE TABLE IF NOT EXISTS "notification" ( | ||||||
|  | 	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||||
|  | 	"user_id" INTEGER NOT NULL REFERENCES user(id), | ||||||
|  | 	"message" TEXT NOT NULL, | ||||||
|  | 	"read_at" DATETIME, | ||||||
|  | 	"created_at" DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, | ||||||
|  | 	"category" TEXT NOT NULL, | ||||||
|  | 	"link" TEXT | ||||||
|  | ); | ||||||
|   | |||||||
| @@ -16,196 +16,176 @@ | |||||||
|     <header class="bg-primary-900 text-white flex justify-center p-3 fixed w-full z-10"> |     <header class="bg-primary-900 text-white flex justify-center p-3 fixed w-full z-10"> | ||||||
|         <div class="max-w-screen-xl w-full flex justify-between items-center"> |         <div class="max-w-screen-xl w-full flex justify-between items-center"> | ||||||
|             <div class="w-1/3 truncate"> |             <div class="w-1/3 truncate"> | ||||||
|                 {% if "Donau Linz" in loggedin_user.roles %} |                 <a href="/"> | ||||||
|                     <a href="/planned"> |                     Hü | ||||||
|                     {% else %} |                     {{ loggedin_user.name }} | ||||||
|                         <a href="/"> |                 </a> | ||||||
|                         {% endif %} |             </div> | ||||||
|                         Hü |             <div> | ||||||
|                         {{ loggedin_user.name }} |                 {% if "scheckbuch" in loggedin_user.roles and loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %} | ||||||
|                     </a> |                     <a href="#" | ||||||
|                 </div> |                        class="inline-flex justify-center rounded-md bg-primary-600 mx-1 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer" | ||||||
|                 <div> |                        data-sidebar="true" | ||||||
|                     <a href="https://wiki.rudernlinz.at/ruderassistent#faq" |                        data-trigger="sidebar" | ||||||
|                        target="_blank" |                        data-header="Ergo Challenge" | ||||||
|                        class="inline-flex justify-center rounded-md bg-primary-600 mx-1 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer"> |                        data-body="#mobile-menu-guest"> | ||||||
|                         {% include "includes/question-icon" %} |                         {% include "includes/book" %} | ||||||
|                         <span class="sr-only">FAQs</span> |                         <span class="sr-only">Ergo</span> | ||||||
|                     </a> |                     </a> | ||||||
|                     {% if "scheckbuch" in loggedin_user.roles and loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %} |                     <div class="hidden"> | ||||||
|                         <a href="#" |                         <div id="mobile-menu-guest"> | ||||||
|                            class="inline-flex justify-center rounded-md bg-primary-600 mx-1 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer" |                             <a href="/ergo" class="block w-100 py-2 hover:text-primary-600 border-t">Ergo</a> | ||||||
|                            data-sidebar="true" |                         </div> | ||||||
|                            data-trigger="sidebar" |                     </div> | ||||||
|                            data-header="Ergo Challenge" |                 {% endif %} | ||||||
|                            data-body="#mobile-menu-guest"> |                 {% if "scheckbuch" not in loggedin_user.roles %} | ||||||
|                             {% include "includes/book" %} |                     <a href="#" | ||||||
|                             <span class="sr-only">Ergo</span> |                        class="inline-flex justify-center rounded-md bg-primary-600 mx-1 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer" | ||||||
|                         </a> |                        data-sidebar="true" | ||||||
|                         <div class="hidden"> |                        data-trigger="sidebar" | ||||||
|                             <div id="mobile-menu-guest"> |                        data-header="Menü" | ||||||
|                                 <a href="/ergo" class="block w-100 py-2 hover:text-primary-600 border-t">Ergo</a> |                        data-body="#mobile-menu"> | ||||||
|                             </div> |                         {% include "includes/book" %} | ||||||
|                         </div> |                         <span class="sr-only">Logbuch</span> | ||||||
|                     {% endif %} |                     </a> | ||||||
|                     {% if "scheckbuch" not in loggedin_user.roles %} |                     <div class="hidden"> | ||||||
|                         <a href="#" |                         <div id="mobile-menu"> | ||||||
|                            class="inline-flex justify-center rounded-md bg-primary-600 mx-1 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer" |                             <a href="/log" class="block w-100 py-2 hover:text-primary-600">Ausfahrt eintragen</a> | ||||||
|                            data-sidebar="true" |                             <a href="/log/show" | ||||||
|                            data-trigger="sidebar" |                                class="block w-100 py-2 hover:text-primary-600 border-t">Logbuch</a> | ||||||
|                            data-header="Menü" |                             {% if "admin" in loggedin_user.roles or "Vorstand" in loggedin_user.roles %} | ||||||
|                            data-body="#mobile-menu"> |                                 <a href="/admin/user" | ||||||
|                             {% include "includes/book" %} |                                    class="block w-100 py-2 hover:text-primary-600 border-t">Userverwaltung</a> | ||||||
|                             <span class="sr-only">Logbuch</span> |                             {% endif %} | ||||||
|                         </a> |                             {% if loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %} | ||||||
|                         <div class="hidden"> |                                 <a href="/ergo" class="block w-100 py-2 hover:text-primary-600 border-t">Ergo</a> | ||||||
|                             <div id="mobile-menu"> |                             {% endif %} | ||||||
|                                 <a href="/log" class="block w-100 py-2 hover:text-primary-600">Ausfahrt eintragen</a> |                             <a href="/stat" class="block w-100 py-2 hover:text-primary-600 border-t">Statistik</a> | ||||||
|                                 <a href="/log/show" |                             <a href="/stat/boats" | ||||||
|                                    class="block w-100 py-2 hover:text-primary-600 border-t">Logbuch</a> |                                class="block w-100 py-2 hover:text-primary-600 border-t">Bootsauswertung</a> | ||||||
|                                 {% if loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %} |                             {% if "admin" in loggedin_user.roles %} | ||||||
|                                     <a href="/ergo" class="block w-100 py-2 hover:text-primary-600 border-t">Ergo</a> |                                 <a href="/admin/boat" | ||||||
|                                 {% endif %} |                                    class="block w-100 py-2 hover:text-primary-600 border-t">Boote</a> | ||||||
|                                 <a href="/stat" class="block w-100 py-2 hover:text-primary-600 border-t">Statistik</a> |                             {% endif %} | ||||||
|                                 <a href="/stat/boats" |                             <a href="/boatdamage" | ||||||
|                                    class="block w-100 py-2 hover:text-primary-600 border-t">Bootsauswertung</a> |                                class="block w-100 py-2 hover:text-primary-600 border-t">Bootsschaden</a> | ||||||
|                                 {% if "admin" in loggedin_user.roles %} |                         </div> | ||||||
|                                     <a href="/admin/boat" |                     </div> | ||||||
|                                        class="block w-100 py-2 hover:text-primary-600 border-t">Boote</a> |                 {% endif %} | ||||||
|                                 {% endif %} |                 <a href="/auth/logout" | ||||||
|                                 <a href="/boatdamage" |                    class="inline-flex justify-center rounded-md bg-gray-200 ml-1 px-3 py-2 text-sm font-semibold text-primary-950 hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer"> | ||||||
|                                    class="block w-100 py-2 hover:text-primary-600 border-t">Bootsschaden</a> |                     <svg class="inline h-4" | ||||||
|                             </div> |                          width="24" | ||||||
|                         </div> |                          height="24" | ||||||
|                     {% endif %} |                          viewbox="0 0 24 24" | ||||||
|                     {% if "admin" in loggedin_user.roles or "Vorstand" in loggedin_user.roles %} |                          fill="none" | ||||||
|                         <a href="/admin/user" |                          stroke="currentColor" | ||||||
|                            class="inline-flex justify-center rounded-md bg-primary-600 mx-1 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer"> |                          stroke-width="2" | ||||||
|                             <svg class="inline h-4" |                          stroke-linecap="round" | ||||||
|                                  width="16" |                          stroke-linejoin="round" | ||||||
|                                  height="16" |                          class="feather feather-log-out"> | ||||||
|                                  fill="currentColor" |                         <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path> | ||||||
|                                  class="bi bi-person-lines-fill" |                         <polyline points="16 17 21 12 16 7"></polyline> | ||||||
|                                  viewbox="0 0 16 16"> |                         <line x1="21" y1="12" x2="9" y2="12"></line> | ||||||
|                                 <path d="M6 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-5 6s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zM11 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm.5 2.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4zm2 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2zm0 3a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1h-2z" /> |                     </svg> | ||||||
|                             </svg> |                     <span class="sr-only">Ausloggen</span> | ||||||
|                             <span class="sr-only">Userverwaltung</span> |                 </a> | ||||||
|                         </a> |  | ||||||
|                     {% endif %} |  | ||||||
|                     <a href="/auth/logout" |  | ||||||
|                        class="inline-flex justify-center rounded-md bg-gray-200 ml-1 px-3 py-2 text-sm font-semibold text-primary-950 hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer"> |  | ||||||
|                         <svg class="inline h-4" |  | ||||||
|                              width="24" |  | ||||||
|                              height="24" |  | ||||||
|                              viewbox="0 0 24 24" |  | ||||||
|                              fill="none" |  | ||||||
|                              stroke="currentColor" |  | ||||||
|                              stroke-width="2" |  | ||||||
|                              stroke-linecap="round" |  | ||||||
|                              stroke-linejoin="round" |  | ||||||
|                              class="feather feather-log-out"> |  | ||||||
|                             <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path> |  | ||||||
|                             <polyline points="16 17 21 12 16 7"></polyline> |  | ||||||
|                             <line x1="21" y1="12" x2="9" y2="12"></line> |  | ||||||
|                         </svg> |  | ||||||
|                         <span class="sr-only">Ausloggen</span> |  | ||||||
|                     </a> |  | ||||||
|                 </div> |  | ||||||
|             </div> |             </div> | ||||||
|         </header> |  | ||||||
|         <div class="h-8"></div> |  | ||||||
|     {% endmacro header %} |  | ||||||
|     {% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false, id='', autofocus=false, wrapper_class='', pattern='', readonly=false, accept='') %} |  | ||||||
|         <div class="{{ wrapper_class }}"> |  | ||||||
|             <label for="{{ name }}" |  | ||||||
|                    class="{% if hide_label %} sr-only {% else %} text-sm text-gray-600 dark:text-white {% endif %}"> |  | ||||||
|                 {{ label }} |  | ||||||
|             </label> |  | ||||||
|             <input {% if type=='datetime-local' %}onclick='if (!this.value) setCurrentdate(this)'{% endif %} |  | ||||||
|                    {% if id %} id="{{ id }}" {% else %} id="{{ name }}" {% endif %} |  | ||||||
|                    name="{{ name }}" |  | ||||||
|                    type="{{ type }}" |  | ||||||
|                    {% if required %}required{% endif %} |  | ||||||
|                    value="{{ value }}" |  | ||||||
|                    class="input {{ class }}" |  | ||||||
|                    placeholder="{% if hide_label %}{{ label }}{% endif %}" |  | ||||||
|                    {% if min is defined %}min="{{ min }}"{% endif %} |  | ||||||
|                    {% if autofocus %}autofocus{% endif %} |  | ||||||
|                    {% if accept %}accept="{{ accept }}"{% endif %} |  | ||||||
|                    {% if pattern %}pattern="{{ pattern }}"{% endif %} |  | ||||||
|                    {% if readonly %}readonly{% endif %}> |  | ||||||
|         </div> |         </div> | ||||||
|     {% endmacro input %} |     </header> | ||||||
|     {% macro checkbox(label, name, id='', checked=false, class='', disabled=false) %} |     <div class="h-8"></div> | ||||||
|         <label for="{{ name }}{{ id }}" | {% endmacro header %} | ||||||
|                class="flex items-center cursor-pointer text-black dark:text-white hover:text-gray-900 dark:hover:text-gray-100 {{ class }}"> | {% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false, id='', autofocus=false, wrapper_class='', pattern='', readonly=false, accept='') %} | ||||||
|             <input type="checkbox" |     <div class="{{ wrapper_class }}"> | ||||||
|                    id="{{ name }}{{ id }}" |         <label for="{{ name }}" | ||||||
|                    name="{{ name }}" |                class="{% if hide_label %} sr-only {% else %} text-sm text-gray-600 dark:text-white {% endif %}"> | ||||||
|                    {% if checked %}checked{% endif %} |  | ||||||
|                    {% if disabled %}disabled{% endif %} |  | ||||||
|                    class="h-4 w-4 accent-primary-600 dark:accent-primary-200 mr-2" /> |  | ||||||
|             {{ label }} |             {{ label }} | ||||||
|         </label> |         </label> | ||||||
|     {% endmacro checkbox %} |         <input {% if type=='datetime-local' %}onclick='if (!this.value) setCurrentdate(this)'{% endif %} | ||||||
|     {% macro select(label, data, name='trip_type', default='', id='', selected_id='', display='', extras='', class='', wrapper_class='', required=false, show_seats=false, new_last_entry='') %} |                {% if id %} id="{{ id }}" {% else %} id="{{ name }}" {% endif %} | ||||||
|         <div class="{{ wrapper_class }}"> |                name="{{ name }}" | ||||||
|             <label for="{{ name }}" class="text-sm text-gray-600 dark:text-gray-100">{{ label }}</label> |                type="{{ type }}" | ||||||
|             {% if display == '' %} |                {% if required %}required{% endif %} | ||||||
|                 {% set display = ["name"] %} |                value="{{ value }}" | ||||||
|             {% endif %} |                class="input {{ class }}" | ||||||
|             <select name="{{ name }}" |                placeholder="{% if hide_label %}{{ label }}{% endif %}" | ||||||
|                     {% if id %} id="{{ id }}" {% else %} id="{{ name }}" {% endif %} |                {% if min is defined %}min="{{ min }}"{% endif %} | ||||||
|                     class="input rounded-md {{ class }}" |                {% if autofocus %}autofocus{% endif %} | ||||||
|                     {% if required %}required="required"{% endif %}> |                {% if accept %}accept="{{ accept }}"{% endif %} | ||||||
|                 {% if default %}<option selected value>{{ default }}</option>{% endif %} |                {% if pattern %}pattern="{{ pattern }}"{% endif %} | ||||||
|                 {% for d in data %} |                {% if readonly %}readonly{% endif %}> | ||||||
|                     <option value="{{ d.id }}" {% if d.id == selected_id %}selected{% endif %} {% if extras != '' %} {% for extra in extras %} {% if extra != 'on_water' and d[extra] %} data-{{ extra }}={{ d[extra] }} {% else %} {% if d[extra] %}disabled{% endif %} {% endif %} {% endfor %} {% endif %} {% if show_seats %} data-custom-properties='{"amount_seats": {{ d["amount_seats"] }}, "owner": "{{ d["owner"] }}", "default_destination": "{{ d["default_destination"] }}", "boat_in_ottensheim": {{ d["location_id"] == 2 }}}'{% endif %}> |     </div> | ||||||
|                         {% for displa in display -%} | {% endmacro input %} | ||||||
|                             {%- if d[displa] -%} | {% macro checkbox(label, name, id='', checked=false, class='', disabled=false) %} | ||||||
|                                 {{- d[displa] -}} |     <label for="{{ name }}{{ id }}" | ||||||
|                             {%- else -%} |            class="flex items-center cursor-pointer text-black dark:text-white hover:text-gray-900 dark:hover:text-gray-100 {{ class }}"> | ||||||
|                                 {{- displa -}} |         <input type="checkbox" | ||||||
|                             {%- endif -%} |                id="{{ name }}{{ id }}" | ||||||
|                         {%- endfor %} |                name="{{ name }}" | ||||||
|                     </option> |                {% if checked %}checked{% endif %} | ||||||
|                 {% endfor %} |                {% if disabled %}disabled{% endif %} | ||||||
|                 {% if new_last_entry %}<option value="-1">{{ new_last_entry }}</option>{% endif %} |                class="h-4 w-4 accent-primary-600 dark:accent-primary-200 mr-2" /> | ||||||
|             </select> |         {{ label }} | ||||||
|         </div> |     </label> | ||||||
|     {% endmacro select %} | {% endmacro checkbox %} | ||||||
|     {% macro alert(message, type, class='') %} | {% macro select(label, data, name='trip_type', default='', id='', selected_id='', display='', extras='', class='', wrapper_class='', required=false, show_seats=false, new_last_entry='') %} | ||||||
|         <div class="{{ class }} alert-{{ type }} text-white px-3 py-1 rounded-md text-center">{{ message }}</div> |     <div class="{{ wrapper_class }}"> | ||||||
|     {% endmacro alert %} |         <label for="{{ name }}" class="text-sm text-gray-600 dark:text-gray-100">{{ label }}</label> | ||||||
|     {% macro box(participants, empty_seats='', header='Freie Plätze:', text='Keine Ruderer angemeldet', bg='primary-600', color='white', trip_details_id='', allow_removing=false) %} |         {% if display == '' %} | ||||||
|         <div class="text-{{ color }} bg-{{ bg }} text-center p-1 mt-1 rounded-t-md"> |             {% set display = ["name"] %} | ||||||
|             {{ header }} |         {% endif %} | ||||||
|             {{ empty_seats }} |         <select name="{{ name }}" | ||||||
|         </div> |                 {% if id %} id="{{ id }}" {% else %} id="{{ name }}" {% endif %} | ||||||
|         <div class="p-2 border border-t-0 border-{{ bg }} mb-4 rounded-b-md"> |                 class="input rounded-md {{ class }}" | ||||||
|             {% if participants | length > 0 %} |                 {% if required %}required="required"{% endif %}> | ||||||
|                 {% for rower in participants %} |             {% if default %}<option selected value>{{ default }}</option>{% endif %} | ||||||
|                     {{ rower.name }} |             {% for d in data %} | ||||||
|                     {% if rower.is_guest %}<small class="text-gray-600 dark:text-gray-100">(Scheckbuch)</small>{% endif %} |                 <option value="{{ d.id }}" {% if d.id == selected_id %}selected{% endif %} {% if extras != '' %} {% for extra in extras %} {% if extra != 'on_water' and d[extra] %} data-{{ extra }}={{ d[extra] }} {% else %} {% if d[extra] %}disabled{% endif %} {% endif %} {% endfor %} {% endif %} {% if show_seats %} data-custom-properties='{"amount_seats": {{ d["amount_seats"] }}, "owner": "{{ d["owner"] }}", "default_destination": "{{ d["default_destination"] }}", "boat_in_ottensheim": {{ d["location_id"] == 2 }}}'{% endif %}> | ||||||
|                     {% if rower.is_real_guest %} |                     {% for displa in display -%} | ||||||
|                         <small class="text-gray-600 dark:text-gray-100">(Gast)</small> |                         {%- if d[displa] -%} | ||||||
|                         {% if allow_removing %} |                             {{- d[displa] -}} | ||||||
|                             <a href="/planned/remove/{{ trip_details_id }}/{{ rower.name }}" |                         {%- else -%} | ||||||
|                                class="btn btn-attention btn-fw">Abmelden</a> |                             {{- displa -}} | ||||||
|                         {% endif %} |                         {%- endif -%} | ||||||
|  |                     {%- endfor %} | ||||||
|  |                 </option> | ||||||
|  |             {% endfor %} | ||||||
|  |             {% if new_last_entry %}<option value="-1">{{ new_last_entry }}</option>{% endif %} | ||||||
|  |         </select> | ||||||
|  |     </div> | ||||||
|  | {% endmacro select %} | ||||||
|  | {% macro alert(message, type, class='') %} | ||||||
|  |     <div class="{{ class }} alert-{{ type }} text-white px-3 py-1 rounded-md text-center">{{ message }}</div> | ||||||
|  | {% endmacro alert %} | ||||||
|  | {% macro box(participants, empty_seats='', header='Freie Plätze:', text='Keine Ruderer angemeldet', bg='primary-600', color='white', trip_details_id='', allow_removing=false) %} | ||||||
|  |     <div class="text-{{ color }} bg-{{ bg }} text-center p-1 mt-1 rounded-t-md"> | ||||||
|  |         {{ header }} | ||||||
|  |         {{ empty_seats }} | ||||||
|  |     </div> | ||||||
|  |     <div class="p-2 border border-t-0 border-{{ bg }} mb-4 rounded-b-md"> | ||||||
|  |         {% if participants | length > 0 %} | ||||||
|  |             {% for rower in participants %} | ||||||
|  |                 {{ rower.name }} | ||||||
|  |                 {% if rower.is_guest %}<small class="text-gray-600 dark:text-gray-100">(Scheckbuch)</small>{% endif %} | ||||||
|  |                 {% if rower.is_real_guest %} | ||||||
|  |                     <small class="text-gray-600 dark:text-gray-100">(Gast)</small> | ||||||
|  |                     {% if allow_removing %} | ||||||
|  |                         <a href="/planned/remove/{{ trip_details_id }}/{{ rower.name }}" | ||||||
|  |                            class="btn btn-attention btn-fw">Abmelden</a> | ||||||
|                     {% endif %} |                     {% endif %} | ||||||
|                     <span class="hidden">(angemeldet seit |                 {% endif %} | ||||||
|                     {{ rower.registered_at }})</span> |                 <span class="hidden">(angemeldet seit | ||||||
|                     <br /> |                 {{ rower.registered_at }})</span> | ||||||
|                 {% endfor %} |                 <br /> | ||||||
|             {% else %} |             {% endfor %} | ||||||
|                 {{ text }} |         {% else %} | ||||||
|             {% endif %} |             {{ text }} | ||||||
|         </div> |         {% endif %} | ||||||
|     {% endmacro box %} |     </div> | ||||||
|     {% macro faq(question, answer) %} | {% endmacro box %} | ||||||
|         <div> | {% macro faq(question, answer) %} | ||||||
|             <h2 class="flex mb-4 text-lg font-bold text-primary-900">{{ question }}</h2> |     <div> | ||||||
|             <p class="text-primary-950">{{ answer | safe }}</p> |         <h2 class="flex mb-4 text-lg font-bold text-primary-900">{{ question }}</h2> | ||||||
|         </div> |         <p class="text-primary-950">{{ answer | safe }}</p> | ||||||
|     {% endmacro faq %} |     </div> | ||||||
|  | {% endmacro faq %} | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								templates/includes/rowing-icon.html.tera
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								templates/includes/rowing-icon.html.tera
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||||
|  | <svg width="100%" | ||||||
|  |      class="flex-shrink-0 w-6 h-6 inline-block" | ||||||
|  |      height="100%" | ||||||
|  |      viewBox="0 0 583 1024" | ||||||
|  |      version="1.1" | ||||||
|  |      xmlns="http://www.w3.org/2000/svg" | ||||||
|  |      xmlns:xlink="http://www.w3.org/1999/xlink" | ||||||
|  |      xml:space="preserve" | ||||||
|  |      xmlns:serif="http://www.serif.com/" | ||||||
|  |      style="fill-rule:evenodd; | ||||||
|  |             clip-rule:evenodd; | ||||||
|  |             stroke-linejoin:round; | ||||||
|  |             stroke-miterlimit:2"> | ||||||
|  |     <g transform="matrix(1,0,0,1,-1574,-536.199)"> | ||||||
|  |     <g transform="matrix(1,0,0,1,0,10.8235)"> | ||||||
|  |     <g transform="matrix(0.948324,0.317305,0.307947,-0.920356,-304.665,1886.18)"> | ||||||
|  |     <rect x="1838.29" y="1006.52" width="17.353" height="644.204" style="fill:white;fill-opacity:0.7;" /> | ||||||
|  |     </g> | ||||||
|  |     <path d="M1944.68,934.772C1921.09,896.523 1932.18,782.181 1974.56,655.531C1990.23,608.676 2009.04,563.787 2029.1,525.376L2156.88,568.132C2149.78,610.876 2137.79,658.046 2122.11,704.901C2079.26,832.966 2018.43,931.728 1976.52,946.409L2085.08,568.504L1944.68,934.772Z" style="fill:white;fill-opacity:0.7;" /> | ||||||
|  |     </g> | ||||||
|  |     <g transform="matrix(-1,0,0,1,3730.88,10.8235)"> | ||||||
|  |     <g transform="matrix(0.948324,0.317305,0.307947,-0.920356,-304.665,1886.18)"> | ||||||
|  |     <rect x="1838.29" y="1006.52" width="17.353" height="644.204" style="fill:white;fill-opacity:0.7;" /> | ||||||
|  |     </g> | ||||||
|  |     <path d="M1944.68,934.772C1921.09,896.523 1932.18,782.181 1974.56,655.531C1990.23,608.676 2009.04,563.787 2029.1,525.376L2156.88,568.132C2149.78,610.876 2137.79,658.046 2122.11,704.901C2079.26,832.966 2018.43,931.728 1976.52,946.409L2085.08,568.504L1944.68,934.772Z" style="fill:white;fill-opacity:0.7;" /> | ||||||
|  |     </g> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.7 KiB | 
| @@ -3,146 +3,177 @@ | |||||||
| {% block content %} | {% block content %} | ||||||
|     <div class="max-w-screen-lg w-full"> |     <div class="max-w-screen-lg w-full"> | ||||||
|         <h1 class="h1">Ruderassistent</h1> |         <h1 class="h1">Ruderassistent</h1> | ||||||
|         <div class="grid gap-3"> |         <div class="grid gap-3 my-5"> | ||||||
|  |             <div class="m-auto"> | ||||||
|  |                 <a href="/planned" | ||||||
|  |                    class="btn btn-primary flex items-center justify-center"> | ||||||
|  |                     {% include "includes/rowing-icon" %} | ||||||
|  |                     <span class="text-xl px-3">Geplante Ausfahrten</span> | ||||||
|  |                 </a> | ||||||
|  |             </div> | ||||||
|  |             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||||
|  |                  role="alert"> | ||||||
|  |                 <h2 class="h2">Nachrichten</h2> | ||||||
|  |                 <div class="divide-y"> | ||||||
|  |                     {% for notification in notifications %} | ||||||
|  |                         {% if not notification.read_at %} | ||||||
|  |                             <div class="relative flex justify-between items-center p-3"> | ||||||
|  |                                 <div class="grow me-4"> | ||||||
|  |                                     <small class="uppercase text-gray-600 dark:text-gray-100"> | ||||||
|  |                                         <strong>{{ notification.category }}</strong> • {{ notification.created_at | date(format="%d.%m.%Y %H:%M") }} | ||||||
|  |                                     </small> | ||||||
|  |                                     <div class="mt-1">{{ notification.message }}</div> | ||||||
|  |                                 </div> | ||||||
|  |                                 <div> | ||||||
|  |                                     {% if not notification.read_at %} | ||||||
|  |                                         <a href="/notification/{{ notification.id }}/read" class="inline-block"> | ||||||
|  |                                             <button class="btn btn-primary" type="button"> | ||||||
|  |                                                 ✓ | ||||||
|  |                                                 <span class="sr-only">Notification gelesen</span> | ||||||
|  |                                             </button> | ||||||
|  |                                         </a> | ||||||
|  |                                     {% endif %} | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         {% endif %} | ||||||
|  |                     {% endfor %} | ||||||
|  |                 </div> | ||||||
|  |                 <details class="py-3 bg-gray-200 dark:bg-primary-950 rounded-b-md"> | ||||||
|  |                     <summary class="px-3">Vergangene Nachrichten</summary> | ||||||
|  |                     <div class="divide-y text-sm"> | ||||||
|  |                         {% for notification in notifications %} | ||||||
|  |                             {% if notification.read_at %} | ||||||
|  |                                 <div class="p-3 relative"> | ||||||
|  |                                     <small class="uppercase text-gray-600 dark:text-gray-100"> | ||||||
|  |                                         <strong>{{ notification.category }}</strong> • {{ notification.created_at | date(format="%d.%m.%Y %H:%M") }} | ||||||
|  |                                     </small> | ||||||
|  |                                     <div class="mt-1">{{ notification.message }}</div> | ||||||
|  |                                 </div> | ||||||
|  |                             {% endif %} | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 </details> | ||||||
|  |             </div> | ||||||
|  |             {% if loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %} | ||||||
|  |                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||||
|  |                      role="alert"> | ||||||
|  |                     <h2 class="h2">Ergo</h2> | ||||||
|  |                     <div class="p-3"> | ||||||
|  |                         <ul class="list-none ms-2"> | ||||||
|  |                             <li class="py-1"> | ||||||
|  |                                 <a href="/ergo" class="block w-100 py-2 hover:text-primary-600">Ergo</a> | ||||||
|  |                             </li> | ||||||
|  |                         </ul> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             {% 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 %} | ||||||
|  |                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||||
|  |                      role="alert"> | ||||||
|  |                     <h2 class="h2">Aktives Vereinsmitglied</h2> | ||||||
|  |                     <ul class="list-none ms-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/planned" class="block w-100 py-2 hover:text-primary-600">Geplante Ausfahrten</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/log" class="block w-100 py-2 hover:text-primary-600">Ausfahrt eintragen</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/log/show" class="block w-100 py-2 hover:text-primary-600">Logbuch</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/stat" class="block w-100 py-2 hover:text-primary-600">Statistik</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/stat/boats" class="block w-100 py-2 hover:text-primary-600">Bootsauswertung</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/boatdamage" class="block w-100 py-2 hover:text-primary-600">Bootsschaden</a> | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                 </div> | ||||||
|  |             {% endif %} | ||||||
|  |             {% if "scheckbuch" 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">Scheckbuch</h2> | ||||||
|  |                     <ul class="list-none ms-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/planned" class="block w-100 py-2 hover:text-primary-600">Geplante Ausfahrten</a> | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                 </div> | ||||||
|  |             {% endif %} | ||||||
|  |             {% if "schnupper-betreuer" 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">Schnupper-Betreuer</h2> | ||||||
|  |                     <ul class="list-none ms-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/schnupper" | ||||||
|  |                                class="block w-100 py-2 hover:text-primary-600">Schnuppern</a> | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                 </div> | ||||||
|  |             {% endif %} | ||||||
|  |             {% if "Vorstand" 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">Vorstand</h2> | ||||||
|  |                     <ul class="list-none ms-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/user/fees" | ||||||
|  |                                class="block w-100 py-2 hover:text-primary-600">Übersicht User Gebühren</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/user/scheckbuch" | ||||||
|  |                                class="block w-100 py-2 hover:text-primary-600">Scheckbuch</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/user" class="block w-100 py-2 hover:text-primary-600">User</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/board/boathouse" | ||||||
|  |                                class="block w-100 py-2 hover:text-primary-600">Bootshaus</a> | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                 </div> | ||||||
|  |             {% endif %} | ||||||
|  |             {% if "admin" 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">Admin</h2> | ||||||
|  |                     <ul class="list-none ms-2 divide-y divide-gray-200 dark:divide-primary-600"> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/boat" class="block w-100 py-2 hover:text-primary-600">Boote</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/user" class="block w-100 py-2 hover:text-primary-600">User</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/mail" class="block w-100 py-2 hover:text-primary-600">Mail (beautifully layouted)</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/rss" class="block w-100 py-2 hover:text-primary-600">Logs</a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="py-1"> | ||||||
|  |                             <a href="/admin/list" class="block w-100 py-2 hover:text-primary-600">Fingerabdruck-Liste überprüfen</a> | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                 </div> | ||||||
|  |             {% endif %} | ||||||
|             <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">Allgemein</h2> |                 <h2 class="h2">Allgemein</h2> | ||||||
|                 <div class="text-sm p-3"> |                 <div class="p-3"> | ||||||
|                     <ul class="list-disc ms-2"> |                     <ul class="list-none ms-2"> | ||||||
|                         <li class="py-1"> |                         <li class="py-1"> | ||||||
|                             <a href="https://wiki.rudernlinz.at/ruderassistent#faq" |                             <a href="https://wiki.rudernlinz.at/ruderassistent#faq" | ||||||
|                                target="_blank" |                                target="_blank" | ||||||
|                                class="link-primary">FAQ (extern)</a> |                                class="block w-100 py-2 hover:text-primary-600">FAQ (extern)</a> | ||||||
|                         </li> |                         </li> | ||||||
|                     </ul> |                     </ul> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|         {% if loggedin_user.weight and loggedin_user.sex and loggedin_user.dob %} |     {% endblock content %} | ||||||
|             <div class="grid gap-3"> |  | ||||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" |  | ||||||
|                      role="alert"> |  | ||||||
|                     <h2 class="h2">Ergo</h2> |  | ||||||
|                     <div class="text-sm p-3"> |  | ||||||
|                         <ul class="list-disc ms-2"> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/ergo" class="link-primary">Ergo</a> |  | ||||||
|                             </li> |  | ||||||
|                         </ul> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         {% 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 %} |  | ||||||
|             <div class="grid gap-3"> |  | ||||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" |  | ||||||
|                      role="alert"> |  | ||||||
|                     <h2 class="h2">Aktives Vereinsmitglied</h2> |  | ||||||
|                     <div class="text-sm p-3"> |  | ||||||
|                         <ul class="list-disc ms-2"> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/planned" class="link-primary">Geplante Ausfahrten</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/log" class="link-primary">Ausfahrt eintragen</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/log/show" class="link-primary">Logbuch</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/stat" class="link-primary">Statistik</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/stat/boats" class="link-primary">Bootsauswertung</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/boatdamage" class="link-primary">Bootsschaden</a> |  | ||||||
|                             </li> |  | ||||||
|                         </ul> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         {% endif %} |  | ||||||
|         {% if "scheckbuch" in loggedin_user.roles %} |  | ||||||
|             <div class="grid gap-3"> |  | ||||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" |  | ||||||
|                      role="alert"> |  | ||||||
|                     <h2 class="h2">Scheckbuch</h2> |  | ||||||
|                     <div class="text-sm p-3"> |  | ||||||
|                         <ul class="list-disc ms-2"> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/planned" class="link-primary">Geplante Ausfahrten</a> |  | ||||||
|                             </li> |  | ||||||
|                         </ul> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         {% endif %} |  | ||||||
|         {% if "schnupper-betreuer" in loggedin_user.roles %} |  | ||||||
|             <div class="grid gap-3"> |  | ||||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" |  | ||||||
|                      role="alert"> |  | ||||||
|                     <h2 class="h2">Schnupper-Betreuer</h2> |  | ||||||
|                     <div class="text-sm p-3"> |  | ||||||
|                         <ul class="list-disc ms-2"> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/schnupper" class="link-primary">Schnuppern</a> |  | ||||||
|                             </li> |  | ||||||
|                         </ul> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         {% endif %} |  | ||||||
|         {% if "Vorstand" in loggedin_user.roles %} |  | ||||||
|             <div class="grid gap-3"> |  | ||||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" |  | ||||||
|                      role="alert"> |  | ||||||
|                     <h2 class="h2">Vorstand</h2> |  | ||||||
|                     <div class="text-sm p-3"> |  | ||||||
|                         <ul class="list-disc ms-2"> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/user/fees" class="link-primary">Übersicht User Gebühren</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/user/scheckbuch" class="link-primary">Scheckbuch</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/user" class="link-primary">User</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/board/boathouse" class="link-primary">Bootshaus</a> |  | ||||||
|                             </li> |  | ||||||
|                         </ul> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         {% endif %} |  | ||||||
|         {% if "admin" in loggedin_user.roles %} |  | ||||||
|             <div class="grid gap-3"> |  | ||||||
|                 <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" |  | ||||||
|                      role="alert"> |  | ||||||
|                     <h2 class="h2">Admin</h2> |  | ||||||
|                     <div class="text-sm p-3"> |  | ||||||
|                         <ul class="list-disc ms-2"> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/boat" class="link-primary">Boote</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/user" class="link-primary">User</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/mail" class="link-primary">Mail (beautifully layouted)</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/rss" class="link-primary">Logs</a> |  | ||||||
|                             </li> |  | ||||||
|                             <li class="py-1"> |  | ||||||
|                                 <a href="/admin/list" class="link-primary">Fingerabdruck-Liste überprüfen</a> |  | ||||||
|                             </li> |  | ||||||
|                         </ul> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         {% endif %} |  | ||||||
|     </div> |  | ||||||
| {% endblock content %} |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user