Merge pull request 'single-user-edit-page' (#986) from single-user-edit-page into staging
Reviewed-on: #986
This commit was merged in pull request #986.
	This commit is contained in:
		| @@ -4,4 +4,8 @@ | ||||
|  | ||||
| .h2 { | ||||
|   @apply font-bold uppercase tracking-wide text-center rounded-t-md text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 text-lg px-3 py-3; | ||||
| } | ||||
|  | ||||
| .h3 { | ||||
|   @apply text-center text-xl uppercase tracking-wide font-bold text-primary-900 dark:text-white; | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| use std::ops::DerefMut; | ||||
|  | ||||
| use super::user::User; | ||||
| use super::{role::Role, user::User}; | ||||
| use chrono::NaiveDateTime; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; | ||||
| @@ -21,6 +21,7 @@ pub struct ActivityBuilder { | ||||
| } | ||||
|  | ||||
| impl ActivityBuilder { | ||||
|     #[must_use] | ||||
|     pub fn new(text: &str) -> Self { | ||||
|         Self { | ||||
|             text: text.into(), | ||||
| @@ -29,6 +30,7 @@ impl ActivityBuilder { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[must_use] | ||||
|     pub fn relevant_for_user(self, user: &User) -> Self { | ||||
|         Self { | ||||
|             relevant_for: format!("{}user-{};", self.relevant_for, user.id), | ||||
| @@ -36,6 +38,14 @@ impl ActivityBuilder { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[must_use] | ||||
|     pub fn relevant_for_role(self, role: &Role) -> Self { | ||||
|         Self { | ||||
|             relevant_for: format!("{}role-{};", self.relevant_for, role.id), | ||||
|             ..self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub async fn save(self, db: &SqlitePool) { | ||||
|         Activity::create(db, &self.text, &self.relevant_for, self.keep_until).await; | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| use std::{cmp::Ordering, fmt::Display, ops::DerefMut}; | ||||
|  | ||||
| use super::{activity::ActivityBuilder, user::AdminUser}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; | ||||
|  | ||||
| @@ -134,6 +135,30 @@ WHERE name like ? | ||||
|         .ok() | ||||
|     } | ||||
|  | ||||
|     pub async fn update( | ||||
|         &self, | ||||
|         db: &SqlitePool, | ||||
|         updated_by: &AdminUser, | ||||
|         formatted_name: &str, | ||||
|         desc: &str, | ||||
|     ) -> Result<(), String> { | ||||
|         sqlx::query!( | ||||
|             "UPDATE role SET formatted_name=?, desc=? WHERE id=?", | ||||
|             formatted_name, | ||||
|             desc, | ||||
|             self.id | ||||
|         ) | ||||
|         .execute(db) | ||||
|         .await | ||||
|         .map_err(|e| e.to_string())?; | ||||
|  | ||||
|         ActivityBuilder::new(&format!( | ||||
|             "{updated_by} hat Rolle {self} von {self:#?} auf FORMATTED_NAME={formatted_name}, DESC={desc} aktualisiert." | ||||
|         )).relevant_for_role(self).save(db).await; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub async fn names_from_role(&self, db: &SqlitePool) -> Vec<String> { | ||||
|         let query = format!( | ||||
|             "SELECT u.name | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| use super::{regular::ClubMember, ManageUserUser, User}; | ||||
| use super::{ManageUserUser, User, regular::ClubMember}; | ||||
| use crate::{ | ||||
|     NonEmptyString, | ||||
|     model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role}, | ||||
|     special_user, NonEmptyString, | ||||
|     special_user, | ||||
| }; | ||||
| use chrono::NaiveDate; | ||||
| use rocket::{async_trait, fs::TempFile}; | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| use std::{fmt::Display, ops::DerefMut}; | ||||
|  | ||||
| use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; | ||||
| use argon2::{Argon2, PasswordHasher, password_hash::SaltString}; | ||||
| use chrono::{Datelike, Local, NaiveDate}; | ||||
| use log::info; | ||||
| use rocket::async_trait; | ||||
| use rocket::{ | ||||
|     Request, | ||||
|     http::{Cookie, Status}, | ||||
|     request::{FromRequest, Outcome}, | ||||
|     time::{Duration, OffsetDateTime}, | ||||
|     Request, | ||||
| }; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; | ||||
|  | ||||
| use super::activity::ActivityBuilder; | ||||
| use super::{ | ||||
|     Day, | ||||
|     log::Log, | ||||
|     logbook::Logbook, | ||||
|     mail::Mail, | ||||
| @@ -23,7 +24,6 @@ use super::{ | ||||
|     role::Role, | ||||
|     stat::Stat, | ||||
|     tripdetails::TripDetails, | ||||
|     Day, | ||||
| }; | ||||
| use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD; | ||||
| use scheckbuch::ScheckbuchUser; | ||||
| @@ -512,7 +512,7 @@ ASKÖ Ruderverein Donau Linz", self.name), | ||||
|             .save(db) | ||||
|             .await; | ||||
|             return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has | ||||
|                                                                 //been deleted | ||||
|             //been deleted | ||||
|         } | ||||
|  | ||||
|         if let Some(user_pw) = user.pw.as_ref() { | ||||
| @@ -583,12 +583,12 @@ ASKÖ Ruderverein Donau Linz", self.name), | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     pub async fn delete(&self, db: &SqlitePool) { | ||||
|     pub async fn delete(&self, db: &SqlitePool, deleted_by: &ManageUserUser) { | ||||
|         sqlx::query!("UPDATE user SET deleted=1 WHERE id=?", self.id) | ||||
|             .execute(db) | ||||
|             .await | ||||
|             .unwrap(); //Okay, because we can only create a User of a valid id | ||||
|         ActivityBuilder::new(&format!("User {self} wurde gelöscht.")) | ||||
|         ActivityBuilder::new(&format!("User {self} wurde von {deleted_by} gelöscht.")) | ||||
|             .relevant_for_user(self) | ||||
|             .save(db) | ||||
|             .await; | ||||
| @@ -622,9 +622,9 @@ ASKÖ Ruderverein Donau Linz", self.name), | ||||
|     pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { | ||||
|         if self.allowed_to_steer(db).await { | ||||
|             let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok, | ||||
|                                                                                              //december | ||||
|                                                                                              //has 31 | ||||
|                                                                                              //days | ||||
|             //december | ||||
|             //has 31 | ||||
|             //days | ||||
|             let days_left_in_year = end_of_year | ||||
|                 .signed_duration_since(Local::now().date_naive()) | ||||
|                 .num_days() | ||||
| @@ -633,9 +633,9 @@ ASKÖ Ruderverein Donau Linz", self.name), | ||||
|             if days_left_in_year <= 31 { | ||||
|                 let end_of_next_year = | ||||
|                     NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok, | ||||
|                                                                                        //december | ||||
|                                                                                        //has 31 | ||||
|                                                                                        //days | ||||
|                 //december | ||||
|                 //has 31 | ||||
|                 //days | ||||
|                 end_of_next_year | ||||
|                     .signed_duration_since(Local::now().date_naive()) | ||||
|                     .num_days() | ||||
| @@ -867,8 +867,8 @@ special_user!(SteeringUser, +"cox", +"Bootsführer"); | ||||
| special_user!(AdminUser, +"admin"); | ||||
| special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied"); | ||||
| special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO: | ||||
|                                                                                        // remove -> | ||||
|                                                                                        // RegularUser | ||||
| // remove -> | ||||
| // RegularUser | ||||
| special_user!(SchnupperBetreuerUser, +"schnupper-betreuer"); | ||||
| special_user!(VorstandUser, +"admin", +"Vorstand"); | ||||
| special_user!(EventUser, +"manage_events"); | ||||
| @@ -982,17 +982,21 @@ mod test { | ||||
|     #[sqlx::test] | ||||
|     fn wrong_pw() { | ||||
|         let pool = testdb!(); | ||||
|         assert!(User::login(&pool, "admin".into(), "admi".into()) | ||||
|             .await | ||||
|             .is_err()); | ||||
|         assert!( | ||||
|             User::login(&pool, "admin".into(), "admi".into()) | ||||
|                 .await | ||||
|                 .is_err() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[sqlx::test] | ||||
|     fn wrong_username() { | ||||
|         let pool = testdb!(); | ||||
|         assert!(User::login(&pool, "admi".into(), "admin".into()) | ||||
|             .await | ||||
|             .is_err()); | ||||
|         assert!( | ||||
|             User::login(&pool, "admi".into(), "admin".into()) | ||||
|                 .await | ||||
|                 .is_err() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[sqlx::test] | ||||
| @@ -1011,9 +1015,11 @@ mod test { | ||||
|         let pool = testdb!(); | ||||
|         let user = User::find_by_id(&pool, 1).await.unwrap(); | ||||
|  | ||||
|         assert!(User::login(&pool, "admin".into(), "abc".into()) | ||||
|             .await | ||||
|             .is_err()); | ||||
|         assert!( | ||||
|             User::login(&pool, "admin".into(), "abc".into()) | ||||
|                 .await | ||||
|                 .is_err() | ||||
|         ); | ||||
|  | ||||
|         user.update_pw(&pool, "abc".into()).await; | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| use super::{ManageUserUser, User}; | ||||
| use crate::{ | ||||
|     NonEmptyString, | ||||
|     model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role}, | ||||
|     special_user, NonEmptyString, | ||||
|     special_user, | ||||
| }; | ||||
| use chrono::NaiveDate; | ||||
| use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt}; | ||||
|   | ||||
| @@ -2,12 +2,13 @@ use super::foerdernd::FoerderndUser; | ||||
| use super::regular::RegularUser; | ||||
| use super::unterstuetzend::UnterstuetzendUser; | ||||
| use super::{ManageUserUser, User}; | ||||
| use crate::NonEmptyString; | ||||
| use crate::model::activity::ActivityBuilder; | ||||
| use crate::model::role::Role; | ||||
| use crate::NonEmptyString; | ||||
| use crate::{ | ||||
|     SCHECKBUCH, | ||||
|     model::{mail::Mail, notification::Notification}, | ||||
|     special_user, SCHECKBUCH, | ||||
|     special_user, | ||||
| }; | ||||
| use chrono::NaiveDate; | ||||
| use rocket::async_trait; | ||||
|   | ||||
| @@ -4,9 +4,9 @@ use super::scheckbuch::ScheckbuchUser; | ||||
| use super::schnupperinterest::SchnupperInterestUser; | ||||
| use super::unterstuetzend::UnterstuetzendUser; | ||||
| use super::{ManageUserUser, User}; | ||||
| use crate::NonEmptyString; | ||||
| use crate::model::activity::ActivityBuilder; | ||||
| use crate::model::role::Role; | ||||
| use crate::NonEmptyString; | ||||
| use crate::{ | ||||
|     model::{mail::Mail, notification::Notification}, | ||||
|     special_user, | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| use super::scheckbuch::ScheckbuchUser; | ||||
| use super::schnupperant::SchnupperantUser; | ||||
| use super::{ManageUserUser, User}; | ||||
| use crate::NonEmptyString; | ||||
| use crate::model::activity::ActivityBuilder; | ||||
| use crate::model::role::Role; | ||||
| use crate::NonEmptyString; | ||||
| use crate::{model::notification::Notification, special_user}; | ||||
| use rocket::async_trait; | ||||
| use sqlx::SqlitePool; | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| use super::{regular::ClubMember, ManageUserUser, User}; | ||||
| use super::{ManageUserUser, User, regular::ClubMember}; | ||||
| use crate::{ | ||||
|     NonEmptyString, | ||||
|     model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role}, | ||||
|     special_user, NonEmptyString, | ||||
|     special_user, | ||||
| }; | ||||
| use chrono::NaiveDate; | ||||
| use rocket::{async_trait, fs::TempFile}; | ||||
|   | ||||
| @@ -12,6 +12,7 @@ pub mod boat; | ||||
| pub mod event; | ||||
| pub mod mail; | ||||
| pub mod notification; | ||||
| pub mod role; | ||||
| pub mod schnupper; | ||||
| pub mod user; | ||||
|  | ||||
| @@ -81,6 +82,7 @@ pub fn routes() -> Vec<Route> { | ||||
|     ret.append(&mut notification::routes()); | ||||
|     ret.append(&mut mail::routes()); | ||||
|     ret.append(&mut event::routes()); | ||||
|     ret.append(&mut role::routes()); | ||||
|     ret.append(&mut routes![rss, show_rss, show_list, list]); | ||||
|     ret | ||||
| } | ||||
|   | ||||
							
								
								
									
										64
									
								
								src/tera/admin/role.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/tera/admin/role.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| use crate::model::{ | ||||
|     role::Role, | ||||
|     user::{AdminUser, UserWithDetails, VorstandUser}, | ||||
| }; | ||||
| use rocket::{ | ||||
|     form::Form, | ||||
|     get, post, | ||||
|     request::FlashMessage, | ||||
|     response::{Flash, Redirect}, | ||||
|     routes, FromForm, Route, State, | ||||
| }; | ||||
| use rocket_dyn_templates::{tera::Context, Template}; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| #[get("/role")] | ||||
| async fn index( | ||||
|     db: &State<SqlitePool>, | ||||
|     admin: VorstandUser, | ||||
|     flash: Option<FlashMessage<'_>>, | ||||
| ) -> Template { | ||||
|     let roles = Role::all(db).await; | ||||
|  | ||||
|     let mut context = Context::new(); | ||||
|     if let Some(msg) = flash { | ||||
|         context.insert("flash", &msg.into_inner()); | ||||
|     } | ||||
|     context.insert("roles", &roles); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithDetails::from_user(admin.user, db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/role", context.into_json()) | ||||
| } | ||||
| #[derive(FromForm)] | ||||
| pub struct RoleToUpdate<'r> { | ||||
|     pub formatted_name: &'r str, | ||||
|     pub desc: &'r str, | ||||
| } | ||||
|  | ||||
| #[post("/role/<role_id>", data = "<data>")] | ||||
| async fn update( | ||||
|     db: &State<SqlitePool>, | ||||
|     data: Form<RoleToUpdate<'_>>, | ||||
|     role_id: i32, | ||||
|     admin: AdminUser, | ||||
| ) -> Flash<Redirect> { | ||||
|     let role = Role::find_by_id(db, role_id).await; | ||||
|     let Some(role) = role else { | ||||
|         return Flash::error(Redirect::to("/admin/role"), "Role does not exist!"); | ||||
|     }; | ||||
|  | ||||
|     match role | ||||
|         .update(db, &admin, &data.formatted_name, &data.desc) | ||||
|         .await | ||||
|     { | ||||
|         Ok(_) => Flash::success(Redirect::to("/admin/role"), "Rolle bearbeitet"), | ||||
|         Err(e) => Flash::error(Redirect::to("/admin/role"), e), | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn routes() -> Vec<Route> { | ||||
|     routes![index, update] | ||||
| } | ||||
| @@ -7,11 +7,11 @@ use crate::{ | ||||
|         mail::valid_mails, | ||||
|         role::Role, | ||||
|         user::{ | ||||
|             AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails, | ||||
|             UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, | ||||
|             clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member, | ||||
|             regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser, | ||||
|             schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser, | ||||
|             AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails, | ||||
|             UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, | ||||
|         }, | ||||
|     }, | ||||
|     tera::Config, | ||||
| @@ -19,6 +19,7 @@ use crate::{ | ||||
| use chrono::NaiveDate; | ||||
| use futures::future::join_all; | ||||
| use rocket::{ | ||||
|     FromForm, Request, Route, State, | ||||
|     form::Form, | ||||
|     fs::TempFile, | ||||
|     get, | ||||
| @@ -26,9 +27,9 @@ use rocket::{ | ||||
|     post, | ||||
|     request::{FlashMessage, FromRequest, Outcome}, | ||||
|     response::{Flash, Redirect}, | ||||
|     routes, FromForm, Request, Route, State, | ||||
|     routes, | ||||
| }; | ||||
| use rocket_dyn_templates::{tera::Context, Template}; | ||||
| use rocket_dyn_templates::{Template, tera::Context}; | ||||
| use sqlx::SqlitePool; | ||||
|  | ||||
| // Custom request guard to extract the Referer header | ||||
| @@ -286,7 +287,7 @@ async fn delete(db: &State<SqlitePool>, admin: ManageUserUser, user: i32) -> Fla | ||||
|     Log::create(db, format!("{} deleted user: {user:?}", admin.user.name)).await; | ||||
|     match user { | ||||
|         Some(user) => { | ||||
|             user.delete(db).await; | ||||
|             user.delete(db, &admin).await; | ||||
|             Flash::success( | ||||
|                 Redirect::to("/admin/user"), | ||||
|                 format!("Benutzer {} gelöscht", user.name), | ||||
|   | ||||
							
								
								
									
										37
									
								
								templates/admin/role.html.tera
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								templates/admin/role.html.tera
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| {% import "includes/macros" as macros %} | ||||
| {% import "includes/forms/boat" as boat %} | ||||
| {% extends "base" %} | ||||
| {% block content %} | ||||
|     <div class="max-w-screen-lg w-full dark:text-white"> | ||||
|         <h1 class="h1">Rolle</h1> | ||||
|         <div class="grid "> | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                  role="alert"> | ||||
|                 <h2 class="h2">Rolle</h2> | ||||
|                 {% for role in roles %} | ||||
|     <div data-filterable="true" | ||||
|          data-filter="{{ role.name }}" | ||||
|          class="w-full border-t"> | ||||
|         <form action="/admin/role/{{ role.id }}" | ||||
|               data-filterable="true" | ||||
|               method="post" | ||||
|               class="bg-white dark:bg-primary-900 p-4 w-full"> | ||||
|             <div class="w-full"> | ||||
|                 <input type="hidden" name="id" value="{{ role.id }}" /> | ||||
|                 <div class="font-bold mb-1 text-black dark:text-white"> | ||||
|                     {{ role.name }} | ||||
|                     <br /> | ||||
|                 </div> | ||||
|                 <div class="grid md:grid-cols-3 gap-3"> | ||||
|                     {{ macros::input(label='Formatierter Name', name='formatted_name', type='text', value=role.formatted_name) }} | ||||
|                     {{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }} | ||||
|                     <input value="Ändern" type="submit" class="w-28 btn btn-primary" /> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     {% endfor %} | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| {% endblock content %} | ||||
| @@ -8,52 +8,121 @@ | ||||
|                 <summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white"> | ||||
|                     Neue Person hinzufügen | ||||
|                 </summary> | ||||
|                 <details class="mt-5 bg-gray-200 dark:bg-primary-600 p-3 rounded-md"> | ||||
|                     <summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">Vereinsmitglied</summary> | ||||
|                     <form action="/admin/user/new/clubmember" | ||||
|                           method="post" | ||||
|                           enctype="multipart/form-data" | ||||
|                           class="grid gap-3"> | ||||
|                         <div> | ||||
|                             <label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label> | ||||
|                             <select name="membertype" id="membertype" class="input rounded-md "> | ||||
|                                 <option selected="" value="regular">Reguläres Vereinsmitglied</option> | ||||
|                                 <option value="unterstuetzend">Unterstützendes Vereinsmitglied</option> | ||||
|                                 <option value="foerdernd">Förderndes Vereinsmitglied</option> | ||||
|                             </select> | ||||
|  | ||||
|                 <div class="grid sm:grid-cols-3 gap-3 mt-3"> | ||||
|                       <button type="button" | ||||
|                               onclick="document.getElementById('add-clubuser').showModal()" | ||||
|                               class="btn btn-primary">Vereinsmitglied</button> | ||||
|                       <button type="button" | ||||
|                               onclick="document.getElementById('add-scheckbuch').showModal()" | ||||
|                               class="btn btn-dark">Scheckbuch</button> | ||||
|                       <button type="button" | ||||
|                               onclick="document.getElementById('add-schnupperkurs').showModal()" | ||||
|                               class="btn btn-dark">Schnupperkurs</button> | ||||
|  | ||||
|  | ||||
|                       </div> | ||||
|                       <dialog id="add-clubuser" | ||||
|                               class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" | ||||
|                               onclick="document.getElementById('add-clubuser').close()"> | ||||
|                           <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                               <button type="button" | ||||
|                                       onclick="document.getElementById('add-clubuser').close()" | ||||
|                                       title="Schließen" | ||||
|                                       class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> | ||||
|                                   <svg class="inline h-5 w-5" | ||||
|                                         width="16" | ||||
|                                         height="16" | ||||
|                                         fill="currentColor" | ||||
|                                         viewBox="0 0 16 16"> | ||||
|                                       <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> | ||||
|                                   </svg> | ||||
|                               </button> | ||||
|                               <div class="mt-8"> | ||||
|                               <h2 class="h3 mb-3">Neues Vereinsmitglied</h2> | ||||
|                               <form action="/admin/user/new/clubmember" | ||||
|                                     method="post" | ||||
|                                     enctype="multipart/form-data" | ||||
|                                     class="grid gap-3"> | ||||
|                                 <div> | ||||
|                                     <label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label> | ||||
|                                     <select name="membertype" id="membertype" class="input rounded-md "> | ||||
|                                         <option selected="" value="regular">Reguläres Vereinsmitglied</option> | ||||
|                                         <option value="unterstuetzend">Unterstützendes Vereinsmitglied</option> | ||||
|                                         <option value="foerdernd">Förderndes Vereinsmitglied</option> | ||||
|                                     </select> | ||||
|                                 </div> | ||||
|                                 {{ macros::input(label='Name', name='name', type="text", required=true) }} | ||||
|                                 {{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }} | ||||
|                                 {{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }} | ||||
|                                 {{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }} | ||||
|                                 {{ macros::input(label='Geburtsdatum', name='birthdate', type="date", required=true) }} | ||||
|                                 {{ macros::input(label='Telefonnummer', name='phone', type="text", required=true) }} | ||||
|                                 {{ macros::input(label='Adresse', name='address', type="text", required=true) }} | ||||
|                                 {{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }} | ||||
|                                 <input value="Neues Vereinsmitglied anlegen" | ||||
|                                       type="submit" | ||||
|                                       class="btn btn-primary" /> | ||||
|                             </form> | ||||
|                         </div> | ||||
|                         {{ macros::input(label='Name', name='name', type="text", required=true) }} | ||||
|                         {{ macros::input(label='Mailadresse', name='mail', type="email", required=true) }} | ||||
|                         {{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }} | ||||
|                         {{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }} | ||||
|                         {{ macros::input(label='Geburtsdatum', name='birthdate', type="date", required=true) }} | ||||
|                         {{ macros::input(label='Telefonnummer', name='phone', type="text", required=true) }} | ||||
|                         {{ macros::input(label='Adresse', name='address', type="text", required=true) }} | ||||
|                         {{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }} | ||||
|                         <input value="Neues Vereinsmitglied anlegen" | ||||
|                                type="submit" | ||||
|                                class="btn btn-primary" /> | ||||
|                     </form> | ||||
|                 </details> | ||||
|                 <details class="mt-5 bg-gray-200 dark:bg-primary-600 p-3 rounded-md"> | ||||
|                     <summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">Scheckbuch</summary> | ||||
|                     <form action="/admin/user/new/scheckbuch" | ||||
|                           method="post" | ||||
|                           enctype="multipart/form-data" | ||||
|                           class="grid gap-3"> | ||||
|                         {{ macros::input(label='Name', name='name', type="text", required=true) }} | ||||
|                         {{ macros::input(label='Mailadresse', name='mail', type="email", required=true) }} | ||||
|                         <input value="Neues Scheckbuch anlegen" | ||||
|                                type="submit" | ||||
|                                class="btn btn-primary" /> | ||||
|                     </form> | ||||
|                 </details> | ||||
|                 <details class="mt-5 bg-gray-200 dark:bg-primary-600 p-3 rounded-md"> | ||||
|                     <summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">Schnupperkurs</summary> | ||||
|                     <form action="/admin/user/new/schnupper" | ||||
|                         </div> | ||||
|                       </dialog>    | ||||
|   | ||||
|                       <dialog id="add-scheckbuch" | ||||
|                               class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" | ||||
|                               onclick="document.getElementById('add-scheckbuch').close()"> | ||||
|                           <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                               <button type="button" | ||||
|                                       onclick="document.getElementById('add-scheckbuch').close()" | ||||
|                                       title="Schließen" | ||||
|                                       class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> | ||||
|                                   <svg class="inline h-5 w-5" | ||||
|                                         width="16" | ||||
|                                         height="16" | ||||
|                                         fill="currentColor" | ||||
|                                         viewBox="0 0 16 16"> | ||||
|                                       <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> | ||||
|                                   </svg> | ||||
|                               </button> | ||||
|                               <div class="mt-8"> | ||||
|                                 <h2 class="h3 mb-3">Neues Scheckbuch</h2> | ||||
|                                 <form action="/admin/user/new/scheckbuch" | ||||
|                                       method="post" | ||||
|                                       enctype="multipart/form-data" | ||||
|                                       class="grid gap-3"> | ||||
|                                     {{ macros::input(label='Name', name='name', type="text", required=true) }} | ||||
|                                     {{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }} | ||||
|                                     <input value="Neues Scheckbuch anlegen" | ||||
|                                           type="submit" | ||||
|                                           class="btn btn-primary" /> | ||||
|                                 </form> | ||||
|                               </div> | ||||
|                             </div> | ||||
|                       </dialog> | ||||
|  | ||||
|                       <dialog id="add-schnupperkurs" | ||||
|                               class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" | ||||
|                               onclick="document.getElementById('add-schnupperkurs').close()"> | ||||
|                         <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                             <button type="button" | ||||
|                                     onclick="document.getElementById('add-schnupperkurs').close()" | ||||
|                                     title="Schließen" | ||||
|                                     class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3"> | ||||
|                                 <svg class="inline h-5 w-5" | ||||
|                                       width="16" | ||||
|                                       height="16" | ||||
|                                       fill="currentColor" | ||||
|                                       viewBox="0 0 16 16"> | ||||
|                                     <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path> | ||||
|                                 </svg> | ||||
|                             </button> | ||||
|                             <div class="mt-8"> | ||||
|                               <form action="/admin/user/new/schnupper" | ||||
|                           method="post" | ||||
|                           enctype="multipart/form-data" | ||||
|                           class="grid gap-3"> | ||||
|                           <h2 class="h3 mb-3">Neuer Schnupperant</h2> | ||||
|  | ||||
|                         <div> | ||||
|                             <label for="schnupper_type" class="text-sm text-gray-600 dark:text-gray-100">Typ</label> | ||||
|                             <select name="schnupper_type" id="schnupper_type" class="input rounded-md "> | ||||
| @@ -62,11 +131,13 @@ | ||||
|                             </select> | ||||
|                         </div> | ||||
|                         {{ macros::input(label='Name', name='name', type="text", required=true) }} | ||||
|                         {{ macros::input(label='Mailadresse', name='mail', type="email", required=true) }} | ||||
|                         {{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }} | ||||
|                         {{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }} | ||||
|                         <input value="Hinzufügen" type="submit" class="btn btn-primary" /> | ||||
|                     </form> | ||||
|                 </details> | ||||
|                             </div> | ||||
|                           </div> | ||||
|                     </dialog> | ||||
|             </details> | ||||
|         {% endif %} | ||||
|         <!-- START filterBar --> | ||||
|   | ||||
| @@ -132,7 +132,7 @@ | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <dialog id="change-member-type" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" | ||||
|                                     onclick="document.getElementById('change-member-type').close()"> | ||||
|                                 <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                                     <button type="button" | ||||
| @@ -235,7 +235,7 @@ | ||||
|                                         class="btn btn-primary">Zu Vereinsmitglied umwandeln</button> | ||||
|                             </div> | ||||
|                             <dialog id="call-for-action" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" | ||||
|                                     onclick="document.getElementById('call-for-action').close()"> | ||||
|                                 <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                                     <button type="button" | ||||
| @@ -318,7 +318,7 @@ | ||||
|                                         class="btn btn-primary w-full">Rolle hinzufügen</button> | ||||
|                             </div> | ||||
|                             <dialog id="role-modal" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md" | ||||
|                                     class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" | ||||
|                                     onclick="document.getElementById('role-modal').close()"> | ||||
|                                 <div onclick="event.stopPropagation();" class="p-3"> | ||||
|                                     <button type="button" | ||||
|   | ||||
| @@ -156,7 +156,7 @@ function setChoiceByLabel(choicesInstance, label) { | ||||
|     </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='') %} | ||||
| {% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false, id='', autofocus=false, wrapper_class='', pattern='', readonly=false, accept='', placeholder='') %} | ||||
|     <div class="{{ wrapper_class }}"> | ||||
|         <label for="{{ name }}" | ||||
|                class="{% if hide_label %} sr-only {% else %} text-sm text-gray-600 dark:text-white {% endif %}"> | ||||
| @@ -169,7 +169,7 @@ function setChoiceByLabel(choicesInstance, label) { | ||||
|                {% if required %}required{% endif %} | ||||
|                value="{{ value }}" | ||||
|                class="input {{ class }}" | ||||
|                placeholder="{% if hide_label %}{{ label }}{% endif %}" | ||||
|                placeholder="{% if hide_label %}{{ label }}{% endif %}{% if placeholder %}{{ placeholder }}{% endif %}" | ||||
|                {% if min is defined %}min="{{ min }}"{% endif %} | ||||
|                {% if autofocus %}autofocus{% endif %} | ||||
|                {% if accept %}accept="{{ accept }}"{% endif %} | ||||
|   | ||||
| @@ -431,6 +431,9 @@ | ||||
|                         <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/role" class="block w-100 py-2 hover:text-primary-600">Rollen</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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user