add boatdamages functionality
This commit is contained in:
		| @@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS "user" ( | ||||
| 	"is_cox" boolean NOT NULL DEFAULT FALSE, | ||||
| 	"is_admin" boolean NOT NULL DEFAULT FALSE, | ||||
| 	"is_guest" boolean NOT NULL DEFAULT TRUE, | ||||
| 	"is_tech" boolean NOT NULL DEFAULT FALSE, | ||||
| 	"deleted" boolean NOT NULL DEFAULT FALSE, | ||||
| 	"last_access" DATETIME | ||||
| ); | ||||
| @@ -112,11 +113,11 @@ CREATE TABLE IF NOT EXISTS "boat_damage" ( | ||||
| 	"boat_id" INTEGER NOT NULL REFERENCES boat(id),  | ||||
| 	"desc" text not null, | ||||
| 	"user_id_created" INTEGER NOT NULL REFERENCES user(id),  | ||||
| 	"created_at" text not null default CURRENT_TIMESTAMP, | ||||
| 	"created_at" datetime not null default CURRENT_TIMESTAMP, | ||||
| 	"user_id_fixed" INTEGER REFERENCES user(id),  -- none: not fixed yet | ||||
| 	"fixed_at" text, | ||||
| 	"fixed_at" datetime, | ||||
| 	"user_id_verified" INTEGER REFERENCES user(id),  | ||||
| 	"verified_at" text, | ||||
| 	"verified_at" datetime, | ||||
| 	"lock_boat" boolean not null default false -- if true: noone can use the boat  | ||||
| ); | ||||
|  | ||||
|   | ||||
							
								
								
									
										167
									
								
								src/model/boatdamage.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/model/boatdamage.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | ||||
| use crate::model::{boat::Boat, user::User}; | ||||
| use chrono::NaiveDateTime; | ||||
| use rocket::serde::{Deserialize, Serialize}; | ||||
| use rocket::FromForm; | ||||
| use sqlx::{FromRow, SqlitePool}; | ||||
|  | ||||
| #[derive(FromRow, Debug, Serialize, Deserialize)] | ||||
| pub struct BoatDamage { | ||||
|     pub id: i64, | ||||
|     pub boat_id: i64, | ||||
|     pub desc: String, | ||||
|     pub user_id_created: i64, | ||||
|     pub created_at: NaiveDateTime, | ||||
|     pub user_id_fixed: Option<i64>, | ||||
|     pub fixed_at: Option<NaiveDateTime>, | ||||
|     pub user_id_verified: Option<i64>, | ||||
|     pub verified_at: Option<NaiveDateTime>, | ||||
|     pub lock_boat: bool, | ||||
| } | ||||
|  | ||||
| #[derive(FromRow, Debug, Serialize, Deserialize)] | ||||
| pub struct BoatDamageWithDetails { | ||||
|     #[serde(flatten)] | ||||
|     boat_damage: BoatDamage, | ||||
|     user_created: User, | ||||
|     user_fixed: Option<User>, | ||||
|     user_verified: Option<User>, | ||||
|     boat: Boat, | ||||
| } | ||||
|  | ||||
| pub struct BoatDamageToAdd<'r> { | ||||
|     pub boat_id: i64, | ||||
|     pub desc: &'r str, | ||||
|     pub user_id_created: i32, | ||||
|     pub lock_boat: bool, | ||||
| } | ||||
|  | ||||
| #[derive(FromForm)] | ||||
| pub struct BoatDamageFixed<'r> { | ||||
|     pub desc: &'r str, | ||||
|     pub user_id_fixed: i32, | ||||
| } | ||||
|  | ||||
| #[derive(FromForm)] | ||||
| pub struct BoatDamageVerified<'r> { | ||||
|     pub desc: &'r str, | ||||
|     pub user_id_verified: i32, | ||||
| } | ||||
|  | ||||
| impl BoatDamage { | ||||
|     pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> { | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             "SELECT id, boat_id, desc, user_id_created, created_at, user_id_fixed, fixed_at, user_id_verified, verified_at, lock_boat  | ||||
|              FROM boat_damage | ||||
|              WHERE id like ?", | ||||
|             id | ||||
|         ) | ||||
|         .fetch_one(db) | ||||
|         .await | ||||
|         .ok() | ||||
|     } | ||||
|  | ||||
|     pub async fn all(db: &SqlitePool) -> Vec<BoatDamageWithDetails> { | ||||
|         let boatdamages = sqlx::query_as!( | ||||
|             BoatDamage, | ||||
|             " | ||||
| SELECT id, boat_id, desc, user_id_created, created_at, user_id_fixed, fixed_at, user_id_verified, verified_at, lock_boat  | ||||
| FROM boat_damage | ||||
| ORDER BY created_at DESC | ||||
|         " | ||||
|         ) | ||||
|         .fetch_all(db) | ||||
|         .await | ||||
|         .unwrap(); //TODO: fixme | ||||
|  | ||||
|         let mut res = Vec::new(); | ||||
|         for boat_damage in boatdamages { | ||||
|             let user_fixed = match boat_damage.user_id_fixed { | ||||
|                 Some(id) => { | ||||
|                     let user = User::find_by_id(db, id as i32).await; | ||||
|                     Some(user.unwrap()) | ||||
|                 } | ||||
|                 None => None, | ||||
|             }; | ||||
|             let user_verified = match boat_damage.user_id_verified { | ||||
|                 Some(id) => { | ||||
|                     let user = User::find_by_id(db, id as i32).await; | ||||
|                     Some(user.unwrap()) | ||||
|                 } | ||||
|                 None => None, | ||||
|             }; | ||||
|  | ||||
|             res.push(BoatDamageWithDetails { | ||||
|                 boat: Boat::find_by_id(db, boat_damage.boat_id as i32) | ||||
|                     .await | ||||
|                     .unwrap(), | ||||
|                 user_created: User::find_by_id(db, boat_damage.user_id_created as i32) | ||||
|                     .await | ||||
|                     .unwrap(), | ||||
|                 user_fixed, | ||||
|                 user_verified, | ||||
|                 boat_damage, | ||||
|             }) | ||||
|         } | ||||
|         res | ||||
|     } | ||||
|  | ||||
|     pub async fn create(db: &SqlitePool, boatdamage: BoatDamageToAdd<'_>) -> Result<(), String> { | ||||
|         sqlx::query!( | ||||
|             "INSERT INTO boat_damage(boat_id, desc, user_id_created, lock_boat) VALUES (?,?,?, ?)", | ||||
|             boatdamage.boat_id, | ||||
|             boatdamage.desc, | ||||
|             boatdamage.user_id_created, | ||||
|             boatdamage.lock_boat | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .map_err(|e| e.to_string())?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub async fn fixed(&self, db: &SqlitePool, boat: BoatDamageFixed<'_>) -> Result<(), String> { | ||||
|         sqlx::query!( | ||||
|             "UPDATE boat_damage SET desc=?, user_id_fixed=?, fixed_at=CURRENT_TIMESTAMP WHERE id=?", | ||||
|             boat.desc, | ||||
|             boat.user_id_fixed, | ||||
|             self.id | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .map_err(|e| e.to_string())?; | ||||
|  | ||||
|         let user = User::find_by_id(db, boat.user_id_fixed).await.unwrap(); | ||||
|         if user.is_tech { | ||||
|             return self | ||||
|                 .verified( | ||||
|                     db, | ||||
|                     BoatDamageVerified { | ||||
|                         desc: boat.desc, | ||||
|                         user_id_verified: user.id as i32, | ||||
|                     }, | ||||
|                 ) | ||||
|                 .await; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub async fn verified( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         boat: BoatDamageVerified<'_>, | ||||
|     ) -> Result<(), String> { | ||||
|         //TODO: Check if user is allowed to verify | ||||
|  | ||||
|         sqlx::query!( | ||||
|             "UPDATE boat_damage SET desc=?, user_id_verified=?, verified_at=CURRENT_TIMESTAMP WHERE id=?", | ||||
|             boat.desc, | ||||
|             boat.user_id_verified, | ||||
|             self.id | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await.map_err(|e| e.to_string())?; | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| @@ -8,6 +8,7 @@ use self::{ | ||||
| }; | ||||
|  | ||||
| pub mod boat; | ||||
| pub mod boatdamage; | ||||
| pub mod location; | ||||
| pub mod log; | ||||
| pub mod logbook; | ||||
|   | ||||
| @@ -14,7 +14,7 @@ impl Rower { | ||||
|         sqlx::query_as!( | ||||
|             User, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech | ||||
| FROM user | ||||
| WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?) | ||||
|         ", | ||||
|   | ||||
| @@ -23,6 +23,7 @@ pub struct User { | ||||
|     pub is_cox: bool, | ||||
|     pub is_admin: bool, | ||||
|     pub is_guest: bool, | ||||
|     pub is_tech: bool, | ||||
|     pub deleted: bool, | ||||
|     pub last_access: Option<chrono::NaiveDateTime>, | ||||
| } | ||||
| @@ -41,6 +42,7 @@ pub enum LoginError { | ||||
|     NotLoggedIn, | ||||
|     NotAnAdmin, | ||||
|     NotACox, | ||||
|     NotATech, | ||||
|     NoPasswordSet(User), | ||||
|     DeserializationError, | ||||
| } | ||||
| @@ -73,7 +75,7 @@ impl User { | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech | ||||
| FROM user  | ||||
| WHERE id like ? | ||||
|         ", | ||||
| @@ -88,7 +90,7 @@ WHERE id like ? | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech | ||||
| FROM user  | ||||
| WHERE name like ? | ||||
|         ", | ||||
| @@ -130,7 +132,7 @@ WHERE name like ? | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech | ||||
| FROM user | ||||
| WHERE deleted = 0 | ||||
| ORDER BY last_access DESC | ||||
| @@ -145,7 +147,7 @@ ORDER BY last_access DESC | ||||
|         sqlx::query_as!( | ||||
|             Self, | ||||
|             " | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access | ||||
| SELECT id, name, pw, is_cox, is_admin, is_guest, deleted, last_access, is_tech | ||||
| FROM user | ||||
| WHERE deleted = 0 AND is_cox=true | ||||
| ORDER BY last_access DESC | ||||
| @@ -167,12 +169,20 @@ ORDER BY last_access DESC | ||||
|         .is_ok() | ||||
|     } | ||||
|  | ||||
|     pub async fn update(&self, db: &SqlitePool, is_cox: bool, is_admin: bool, is_guest: bool) { | ||||
|     pub async fn update( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         is_cox: bool, | ||||
|         is_admin: bool, | ||||
|         is_guest: bool, | ||||
|         is_tech: bool, | ||||
|     ) { | ||||
|         sqlx::query!( | ||||
|             "UPDATE user SET is_cox = ?, is_admin = ?, is_guest = ? where id = ?", | ||||
|             "UPDATE user SET is_cox = ?, is_admin = ?, is_guest = ?, is_tech = ? where id = ?", | ||||
|             is_cox, | ||||
|             is_admin, | ||||
|             is_guest, | ||||
|             is_tech, | ||||
|             self.id | ||||
|         ) | ||||
|         .execute(db) | ||||
| @@ -325,6 +335,46 @@ impl<'r> FromRequest<'r> for User { | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct TechUser { | ||||
|     pub(crate) user: User, | ||||
| } | ||||
|  | ||||
| impl Deref for TechUser { | ||||
|     type Target = User; | ||||
|  | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         &self.user | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<User> for TechUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     fn try_from(user: User) -> Result<Self, Self::Error> { | ||||
|         if user.is_tech { | ||||
|             Ok(TechUser { user }) | ||||
|         } else { | ||||
|             Err(LoginError::NotATech) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait] | ||||
| impl<'r> FromRequest<'r> for TechUser { | ||||
|     type Error = LoginError; | ||||
|  | ||||
|     async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { | ||||
|         match User::from_request(req).await { | ||||
|             Outcome::Success(user) => match user.try_into() { | ||||
|                 Ok(user) => Outcome::Success(user), | ||||
|                 Err(_) => Outcome::Failure((Status::Unauthorized, LoginError::NotACox)), | ||||
|             }, | ||||
|             Outcome::Failure(f) => Outcome::Failure(f), | ||||
|             Outcome::Forward(f) => Outcome::Forward(f), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct CoxUser { | ||||
|     user: User, | ||||
| } | ||||
| @@ -469,7 +519,7 @@ mod test { | ||||
|         let pool = testdb!(); | ||||
|  | ||||
|         let user = User::find_by_id(&pool, 1).await.unwrap(); | ||||
|         user.update(&pool, false, false, false).await; | ||||
|         user.update(&pool, false, false, false, false).await; | ||||
|  | ||||
|         let user = User::find_by_id(&pool, 1).await.unwrap(); | ||||
|         assert_eq!(user.is_admin, false); | ||||
|   | ||||
| @@ -63,6 +63,7 @@ struct UserEditForm { | ||||
|     is_guest: bool, | ||||
|     is_cox: bool, | ||||
|     is_admin: bool, | ||||
|     is_tech: bool, | ||||
| } | ||||
|  | ||||
| #[post("/user", data = "<data>")] | ||||
| @@ -76,10 +77,10 @@ async fn update( | ||||
|         return Flash::error( | ||||
|             Redirect::to("/admin/user"), | ||||
|             format!("User with ID {} does not exist!", data.id), | ||||
|             ) | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     user.update(db, data.is_cox, data.is_admin, data.is_guest) | ||||
|     user.update(db, data.is_cox, data.is_admin, data.is_guest, data.is_tech) | ||||
|         .await; | ||||
|  | ||||
|     Flash::success(Redirect::to("/admin/user"), "Successfully updated user") | ||||
|   | ||||
							
								
								
									
										114
									
								
								src/tera/boatdamage.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/tera/boatdamage.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| use rocket::{ | ||||
|     form::Form, | ||||
|     get, post, | ||||
|     request::FlashMessage, | ||||
|     response::{Flash, Redirect}, | ||||
|     routes, FromForm, Route, State, | ||||
| }; | ||||
| use rocket_dyn_templates::Template; | ||||
| use sqlx::SqlitePool; | ||||
| use tera::Context; | ||||
|  | ||||
| use crate::model::{ | ||||
|     boat::Boat, | ||||
|     boatdamage::{BoatDamage, BoatDamageFixed, BoatDamageToAdd, BoatDamageVerified}, | ||||
|     user::{CoxUser, TechUser, User}, | ||||
| }; | ||||
|  | ||||
| #[get("/")] | ||||
| async fn index(db: &State<SqlitePool>, flash: Option<FlashMessage<'_>>, user: User) -> Template { | ||||
|     let boatdamages = BoatDamage::all(db).await; | ||||
|     let boats = Boat::all(db).await; | ||||
|  | ||||
|     let mut context = Context::new(); | ||||
|     if let Some(msg) = flash { | ||||
|         context.insert("flash", &msg.into_inner()); | ||||
|     } | ||||
|  | ||||
|     context.insert("boatdamages", &boatdamages); | ||||
|     context.insert("boats", &boats); | ||||
|     context.insert("loggedin_user", &user); | ||||
|  | ||||
|     Template::render("boatdamages", context.into_json()) | ||||
| } | ||||
|  | ||||
| #[derive(FromForm)] | ||||
| pub struct FormBoatDamageToAdd<'r> { | ||||
|     pub boat_id: i64, | ||||
|     pub desc: &'r str, | ||||
|     pub lock_boat: bool, | ||||
| } | ||||
|  | ||||
| #[post("/", data = "<data>")] | ||||
| async fn create<'r>( | ||||
|     db: &State<SqlitePool>, | ||||
|     data: Form<FormBoatDamageToAdd<'r>>, | ||||
|     coxuser: CoxUser, | ||||
| ) -> Flash<Redirect> { | ||||
|     let boatdamage_to_add = BoatDamageToAdd { | ||||
|         boat_id: data.boat_id, | ||||
|         desc: data.desc, | ||||
|         lock_boat: data.lock_boat, | ||||
|         user_id_created: coxuser.id as i32, | ||||
|     }; | ||||
|     match BoatDamage::create(db, boatdamage_to_add).await { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to("/boatdamage"), | ||||
|             "Ausfahrt erfolgreich hinzugefügt", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to("/boatdamage"), format!("Fehler: {e}")), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm)] | ||||
| pub struct FormBoatDamageFixed<'r> { | ||||
|     pub desc: &'r str, | ||||
| } | ||||
|  | ||||
| #[post("/<boatdamage_id>/fixed", data = "<data>")] | ||||
| async fn fixed<'r>( | ||||
|     db: &State<SqlitePool>, | ||||
|     data: Form<FormBoatDamageFixed<'r>>, | ||||
|     boatdamage_id: i32, | ||||
|     coxuser: CoxUser, | ||||
| ) -> Flash<Redirect> { | ||||
|     let boatdamage = BoatDamage::find_by_id(db, boatdamage_id).await.unwrap(); //TODO: Fix | ||||
|     let boatdamage_fixed = BoatDamageFixed { | ||||
|         desc: data.desc, | ||||
|         user_id_fixed: coxuser.id as i32, | ||||
|     }; | ||||
|     match boatdamage.fixed(db, boatdamage_fixed).await { | ||||
|         Ok(_) => Flash::success(Redirect::to("/boatdamage"), "Successfully fixed the boat."), | ||||
|         Err(e) => Flash::error(Redirect::to("/boatdamage"), format!("Error: {e}")), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(FromForm)] | ||||
| pub struct FormBoatDamageVerified<'r> { | ||||
|     pub desc: &'r str, | ||||
| } | ||||
|  | ||||
| #[post("/<boatdamage_id>/verified", data = "<data>")] | ||||
| async fn verified<'r>( | ||||
|     db: &State<SqlitePool>, | ||||
|     data: Form<FormBoatDamageFixed<'r>>, | ||||
|     boatdamage_id: i32, | ||||
|     techuser: TechUser, | ||||
| ) -> Flash<Redirect> { | ||||
|     let boatdamage = BoatDamage::find_by_id(db, boatdamage_id).await.unwrap(); //TODO: Fix | ||||
|     let boatdamage_verified = BoatDamageVerified { | ||||
|         desc: data.desc, | ||||
|         user_id_verified: techuser.id as i32, | ||||
|     }; | ||||
|     match boatdamage.verified(db, boatdamage_verified).await { | ||||
|         Ok(_) => Flash::success( | ||||
|             Redirect::to("/boatdamage"), | ||||
|             "Successfully verified the boat.", | ||||
|         ), | ||||
|         Err(e) => Flash::error(Redirect::to("/boatdamage"), format!("Error: {e}")), | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn routes() -> Vec<Route> { | ||||
|     routes![index, create, fixed, verified] | ||||
| } | ||||
| @@ -21,6 +21,7 @@ use crate::model::{ | ||||
|  | ||||
| mod admin; | ||||
| mod auth; | ||||
| mod boatdamage; | ||||
| mod cox; | ||||
| mod log; | ||||
| mod misc; | ||||
| @@ -120,6 +121,7 @@ pub fn config(rocket: Rocket<Build>) -> Rocket<Build> { | ||||
|         .mount("/auth", auth::routes()) | ||||
|         .mount("/log", log::routes()) | ||||
|         .mount("/stat", stat::routes()) | ||||
|         .mount("/boatdamage", boatdamage::routes()) | ||||
|         .mount("/cox", cox::routes()) | ||||
|         .mount("/admin", admin::routes()) | ||||
|         .mount("/", misc::routes()) | ||||
|   | ||||
| @@ -51,6 +51,7 @@ | ||||
| 						<div class="grid md:grid-cols-3"> | ||||
| 							{{ macros::checkbox(label='Gast', name='is_guest', id=loop.index , checked=user.is_guest) }} | ||||
| 							{{ macros::checkbox(label='Steuerberechtigter', name='is_cox', id=loop.index , checked=user.is_cox) }} | ||||
| 							{{ macros::checkbox(label='Technical', name='is_tech', id=loop.index , checked=user.is_tech) }} | ||||
| 							{{ macros::checkbox(label='Admin', name='is_admin', id=loop.index , checked=user.is_admin) }} | ||||
| 						</div> | ||||
| 						{% if user.pw %} | ||||
|   | ||||
							
								
								
									
										59
									
								
								templates/boatdamages.html.tera
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								templates/boatdamages.html.tera
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| {% import "includes/macros" as macros %} | ||||
| {% import "includes/forms/log" as log %} | ||||
|  | ||||
| {% extends "base" %} | ||||
|  | ||||
| {% block content %} | ||||
|  | ||||
| <div class="w-full"> | ||||
|   <h1 class="h1">Bootschäden</h1> | ||||
|  | ||||
|   {% if flash %} | ||||
|     {{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }} | ||||
|   {% endif %} | ||||
|   <h1>Neuen Schaden eintragen</h1> | ||||
|   <form action="/boatdamage" method="post"> | ||||
|   	{{ log::boat_select(only_ones=false) }} | ||||
|   	{{ macros::input(label='Beschreibung des Schadens', name='desc', type='text', required=true) }} | ||||
| 	<input type="submit" value="Schaden eintragen" /> | ||||
|   </form> | ||||
|  | ||||
|    | ||||
|  | ||||
|   <hr /> | ||||
|  | ||||
|  | ||||
|   {% for boatdamage in boatdamages %} | ||||
|   	Boat: {{ boatdamage.boat.name }} | ||||
| 	Desc: {{ boatdamage.desc }} | ||||
| 	Schaden eingetragen von {{ boatdamage.user_created.name }} am/um {{ boatdamage.created_at | date(format='%d. %m. %Y %H:%M') }} | ||||
| 	{% if boatdamage.is_lock %} | ||||
| 		Boot gesperrt | ||||
| 	{% endif %} | ||||
| 	{% if boatdamage.fixed_at %} | ||||
| 		Repariert von {{ boatdamage.user_fixed.name }} am/um {{ boatdamage.fixed_at | date(format='%d. %m. %Y %H:%M') }} | ||||
| 	{% elif loggedin_user.is_cox %} | ||||
| 		<form action="/boatdamage/{{ boatdamage.id }}/fixed" method="post"> | ||||
| 			<input type="text" name="desc" value="{{ boatdamage.desc }}" /> | ||||
| 			{% if loggedin_user.is_tech %} | ||||
| 				<input type="submit" value="Ich hab's repariert + verifiziert" /> | ||||
| 			{% else %} | ||||
| 				<input type="submit" value="Ich hab's repariert" /> | ||||
| 			{% endif %} | ||||
| 		</form> | ||||
| 	{% endif %} | ||||
| 	{% if boatdamage.verified_at %} | ||||
| 		Verifziert von {{ boatdamage.user_verified.name }} am/um {{ boatdamage.verified_at | date(format='%d. %m. %Y %H:%M') }} | ||||
| 	{% elif loggedin_user.is_tech and boatdamage.fixed_at %} | ||||
| 		<form action="/boatdamage/{{ boatdamage.id }}/verified" method="post"> | ||||
| 			<input type="text" name="desc" value="{{ boatdamage.desc }}" /> | ||||
| 			<input type="submit" value="Ich hab's verifiziert" /> | ||||
| 		</form> | ||||
| 	{% endif %} | ||||
| 	<hr /> | ||||
|   {% endfor %} | ||||
|  | ||||
| </div> | ||||
|  | ||||
| {% include "dynamics/sidebar" %} | ||||
| {% endblock content%} | ||||
| @@ -13,6 +13,13 @@ | ||||
| 					{% include "includes/question-icon" %} | ||||
| 					<span class="sr-only">FAQs</span> | ||||
| 				</a> | ||||
| 				{% if loggedin_user.is_admin%} | ||||
| 					<a href="/boatdamage" 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"> | ||||
| 						Bootsschaden	 | ||||
| 						<span class="sr-only">Bootsschaden</span> | ||||
| 					</a> | ||||
| 					 | ||||
| 				{% endif %} | ||||
| 				{% if loggedin_user.is_admin %} | ||||
| 					<a href="/stat" 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"> | ||||
| 						STATS | ||||
|   | ||||
		Reference in New Issue
	
	Block a user