This commit is contained in:
Philipp Hofer 2025-05-06 13:40:33 +02:00
parent 374fed9e3b
commit 5fb9e0fbba
9 changed files with 293 additions and 283 deletions

View File

@ -1,20 +1,21 @@
use std::{fmt::Display, ops::DerefMut}; 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 chrono::{Datelike, Local, NaiveDate};
use log::info; use log::info;
use rocket::async_trait; use rocket::async_trait;
use rocket::{ use rocket::{
Request,
http::{Cookie, Status}, http::{Cookie, Status},
request::{FromRequest, Outcome}, request::{FromRequest, Outcome},
time::{Duration, OffsetDateTime}, time::{Duration, OffsetDateTime},
Request,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::activity::ActivityBuilder; use super::activity::ActivityBuilder;
use super::{ use super::{
Day,
log::Log, log::Log,
logbook::Logbook, logbook::Logbook,
mail::Mail, mail::Mail,
@ -23,7 +24,6 @@ use super::{
role::Role, role::Role,
stat::Stat, stat::Stat,
tripdetails::TripDetails, tripdetails::TripDetails,
Day,
}; };
use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD; use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD;
use scheckbuch::ScheckbuchUser; use scheckbuch::ScheckbuchUser;
@ -512,7 +512,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
.save(db) .save(db)
.await; .await;
return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
//been deleted //been deleted
} }
if let Some(user_pw) = user.pw.as_ref() { if let Some(user_pw) = user.pw.as_ref() {
@ -618,9 +618,9 @@ ASKÖ Ruderverein Donau Linz", self.name),
pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 {
if self.allowed_to_steer(db).await { if self.allowed_to_steer(db).await {
let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok, let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok,
//december //december
//has 31 //has 31
//days //days
let days_left_in_year = end_of_year let days_left_in_year = end_of_year
.signed_duration_since(Local::now().date_naive()) .signed_duration_since(Local::now().date_naive())
.num_days() .num_days()
@ -629,9 +629,9 @@ ASKÖ Ruderverein Donau Linz", self.name),
if days_left_in_year <= 31 { if days_left_in_year <= 31 {
let end_of_next_year = let end_of_next_year =
NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok, NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok,
//december //december
//has 31 //has 31
//days //days
end_of_next_year end_of_next_year
.signed_duration_since(Local::now().date_naive()) .signed_duration_since(Local::now().date_naive())
.num_days() .num_days()
@ -863,8 +863,8 @@ special_user!(SteeringUser, +"cox", +"Bootsführer");
special_user!(AdminUser, +"admin"); special_user!(AdminUser, +"admin");
special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied"); special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied");
special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO: special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO:
// remove -> // remove ->
// RegularUser // RegularUser
special_user!(SchnupperBetreuerUser, +"schnupper-betreuer"); special_user!(SchnupperBetreuerUser, +"schnupper-betreuer");
special_user!(VorstandUser, +"admin", +"Vorstand"); special_user!(VorstandUser, +"admin", +"Vorstand");
special_user!(EventUser, +"manage_events"); special_user!(EventUser, +"manage_events");
@ -978,17 +978,21 @@ mod test {
#[sqlx::test] #[sqlx::test]
fn wrong_pw() { fn wrong_pw() {
let pool = testdb!(); let pool = testdb!();
assert!(User::login(&pool, "admin".into(), "admi".into()) assert!(
.await User::login(&pool, "admin".into(), "admi".into())
.is_err()); .await
.is_err()
);
} }
#[sqlx::test] #[sqlx::test]
fn wrong_username() { fn wrong_username() {
let pool = testdb!(); let pool = testdb!();
assert!(User::login(&pool, "admi".into(), "admin".into()) assert!(
.await User::login(&pool, "admi".into(), "admin".into())
.is_err()); .await
.is_err()
);
} }
#[sqlx::test] #[sqlx::test]
@ -1007,9 +1011,11 @@ mod test {
let pool = testdb!(); let pool = testdb!();
let user = User::find_by_id(&pool, 1).await.unwrap(); let user = User::find_by_id(&pool, 1).await.unwrap();
assert!(User::login(&pool, "admin".into(), "abc".into()) assert!(
.await User::login(&pool, "admin".into(), "abc".into())
.is_err()); .await
.is_err()
);
user.update_pw(&pool, "abc".into()).await; user.update_pw(&pool, "abc".into()).await;

View File

@ -3,13 +3,14 @@ use crate::model::{
user::{AdminUser, UserWithDetails, VorstandUser}, user::{AdminUser, UserWithDetails, VorstandUser},
}; };
use rocket::{ use rocket::{
FromForm, Route, State,
form::Form, form::Form,
get, post, get, post,
request::FlashMessage, request::FlashMessage,
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, FromForm, Route, State, routes,
}; };
use rocket_dyn_templates::{tera::Context, Template}; use rocket_dyn_templates::{Template, tera::Context};
use sqlx::SqlitePool; use sqlx::SqlitePool;
#[get("/role")] #[get("/role")]

View File

@ -7,11 +7,11 @@ use crate::{
mail::valid_mails, mail::valid_mails,
role::Role, role::Role,
user::{ user::{
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member, clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member,
regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser, regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser,
schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser, schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser,
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
}, },
}, },
tera::Config, tera::Config,
@ -19,6 +19,7 @@ use crate::{
use chrono::NaiveDate; use chrono::NaiveDate;
use futures::future::join_all; use futures::future::join_all;
use rocket::{ use rocket::{
FromForm, Request, Route, State,
form::Form, form::Form,
fs::TempFile, fs::TempFile,
get, get,
@ -26,9 +27,9 @@ use rocket::{
post, post,
request::{FlashMessage, FromRequest, Outcome}, request::{FlashMessage, FromRequest, Outcome},
response::{Flash, Redirect}, 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; use sqlx::SqlitePool;
// Custom request guard to extract the Referer header // Custom request guard to extract the Referer header
@ -135,7 +136,7 @@ async fn view(
if user.name == "Externe Steuerperson" { if user.name == "Externe Steuerperson" {
return Err(Flash::error( return Err(Flash::error(
Redirect::to("/admin/user"), Redirect::to("/admin/user"),
"Diese besondere Person kannst du dir leider nicht anschauen, mein lieber neugieriger Ruderant!" "Diese besondere Person kannst du dir leider nicht anschauen, mein lieber neugieriger Ruderant!",
)); ));
} }

View File

@ -1,4 +1,5 @@
use rocket::{ use rocket::{
FromForm, Request, Route, State,
form::Form, form::Form,
get, get,
http::{Cookie, CookieJar}, http::{Cookie, CookieJar},
@ -8,9 +9,8 @@ use rocket::{
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes,
time::{Duration, OffsetDateTime}, time::{Duration, OffsetDateTime},
FromForm, Request, Route, State,
}; };
use rocket_dyn_templates::{context, tera, Template}; use rocket_dyn_templates::{Template, context, tera};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::{ use crate::model::{

View File

@ -1,6 +1,7 @@
use std::net::IpAddr; use std::net::IpAddr;
use rocket::{ use rocket::{
Request, Route, State,
form::Form, form::Form,
get, get,
http::{Cookie, CookieJar}, http::{Cookie, CookieJar},
@ -9,9 +10,8 @@ use rocket::{
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes,
time::{Duration, OffsetDateTime}, time::{Duration, OffsetDateTime},
Request, Route, State,
}; };
use rocket_dyn_templates::{context, Template}; use rocket_dyn_templates::{Template, context};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tera::Context; use tera::Context;
@ -585,7 +585,7 @@ mod test {
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::logbook::Logbook; use crate::model::logbook::Logbook;
use crate::tera::{log::Boat, User}; use crate::tera::{User, log::Boat};
use crate::testdb; use crate::testdb;
#[sqlx::test] #[sqlx::test]

View File

@ -9,28 +9,28 @@
role="alert"> role="alert">
<h2 class="h2">Rolle</h2> <h2 class="h2">Rolle</h2>
{% for role in roles %} {% for role in roles %}
<div data-filterable="true" <div data-filterable="true"
data-filter="{{ role.name }}" data-filter="{{ role.name }}"
class="w-full border-t"> class="w-full border-t">
<form action="/admin/role/{{ role.id }}" <form action="/admin/role/{{ role.id }}"
data-filterable="true" data-filterable="true"
method="post" method="post"
class="bg-white dark:bg-primary-900 p-4 w-full"> class="bg-white dark:bg-primary-900 p-4 w-full">
<div class="w-full"> <div class="w-full">
<input type="hidden" name="id" value="{{ role.id }}" /> <input type="hidden" name="id" value="{{ role.id }}" />
<div class="font-bold mb-1 text-black dark:text-white"> <div class="font-bold mb-1 text-black dark:text-white">
{{ role.name }} {{ role.name }}
<br /> <br />
</div> </div>
<div class="grid md:grid-cols-3 gap-3"> <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='Formatierter Name', name='formatted_name', type='text', value=role.formatted_name) }}
{{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }} {{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }}
<input value="Ändern" type="submit" class="w-28 btn btn-primary" /> <input value="Ändern" type="submit" class="w-28 btn btn-primary" />
</div> </div>
</div> </div>
</form> </form>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,42 +8,39 @@
<summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white"> <summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">
Neue Person hinzufügen Neue Person hinzufügen
</summary> </summary>
<div class="grid sm:grid-cols-3 gap-3 mt-3"> <div class="grid sm:grid-cols-3 gap-3 mt-3">
<button type="button" <button type="button"
onclick="document.getElementById('add-clubuser').showModal()" onclick="document.getElementById('add-clubuser').showModal()"
class="btn btn-primary">🥳 Vereinsmitglied</button> class="btn btn-primary">🥳 Vereinsmitglied</button>
<button type="button" <button type="button"
onclick="document.getElementById('add-scheckbuch').showModal()" onclick="document.getElementById('add-scheckbuch').showModal()"
class="btn btn-dark">🧑‍🏫 Scheckbuch</button> class="btn btn-dark">🧑‍🏫 Scheckbuch</button>
<button type="button" <button type="button"
onclick="document.getElementById('add-schnupperkurs').showModal()" onclick="document.getElementById('add-schnupperkurs').showModal()"
class="btn btn-dark">👨‍🎓 Schnupperkurs</button> class="btn btn-dark">👨‍🎓 Schnupperkurs</button>
</div>
<dialog id="add-clubuser"
</div> class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
<dialog id="add-clubuser" onclick="document.getElementById('add-clubuser').close()">
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" <div onclick="event.stopPropagation();" class="p-3">
onclick="document.getElementById('add-clubuser').close()"> <button type="button"
<div onclick="event.stopPropagation();" class="p-3"> onclick="document.getElementById('add-clubuser').close()"
<button type="button" title="Schließen"
onclick="document.getElementById('add-clubuser').close()" 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">
title="Schließen" <svg class="inline h-5 w-5"
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"> width="16"
<svg class="inline h-5 w-5" height="16"
width="16" fill="currentColor"
height="16" viewBox="0 0 16 16">
fill="currentColor" <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>
viewBox="0 0 16 16"> </svg>
<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> </button>
</svg> <div class="mt-8">
</button> <h2 class="h3 mb-3">Neues Vereinsmitglied</h2>
<div class="mt-8"> <form action="/admin/user/new/clubmember"
<h2 class="h3 mb-3">Neues Vereinsmitglied</h2> method="post"
<form action="/admin/user/new/clubmember" enctype="multipart/form-data"
method="post" class="grid gap-3">
enctype="multipart/form-data"
class="grid gap-3">
<div> <div>
<label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label> <label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label>
<select name="membertype" id="membertype" class="input rounded-md "> <select name="membertype" id="membertype" class="input rounded-md ">
@ -61,83 +58,80 @@
{{ macros::input(label='Adresse', name='address', 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) }} {{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }}
<input value="Neues Vereinsmitglied anlegen" <input value="Neues Vereinsmitglied anlegen"
type="submit" type="submit"
class="btn btn-primary" /> class="btn btn-primary" />
</form> </form>
</div> </div>
</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> </div>
</dialog>
<dialog id="add-scheckbuch" <dialog id="add-schnupperkurs"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('add-scheckbuch').close()"> onclick="document.getElementById('add-schnupperkurs').close()">
<div onclick="event.stopPropagation();" class="p-3"> <div onclick="event.stopPropagation();" class="p-3">
<button type="button" <button type="button"
onclick="document.getElementById('add-scheckbuch').close()" onclick="document.getElementById('add-schnupperkurs').close()"
title="Schließen" 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"> 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" <svg class="inline h-5 w-5"
width="16" width="16"
height="16" height="16"
fill="currentColor" fill="currentColor"
viewBox="0 0 16 16"> 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> <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> </svg>
</button> </button>
<div class="mt-8"> <div class="mt-8">
<h2 class="h3 mb-3">Neues Scheckbuch</h2> <form action="/admin/user/new/schnupper"
<form action="/admin/user/new/scheckbuch" method="post"
method="post" enctype="multipart/form-data"
enctype="multipart/form-data" class="grid gap-3">
class="grid gap-3"> <h2 class="h3 mb-3">Neuer Schnupperant</h2>
{{ macros::input(label='Name', name='name', type="text", required=true) }} <div>
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }} <label for="schnupper_type" class="text-sm text-gray-600 dark:text-gray-100">Typ</label>
<input value="Neues Scheckbuch anlegen" <select name="schnupper_type" id="schnupper_type" class="input rounded-md ">
type="submit" <option value="schnupperInterested">Interessiert am Schnupperkurs</option>
class="btn btn-primary" /> <option value="schnupperant">Fixe Schnupperkurs-Anmeldung</option>
</form> </select>
</div> </div>
</div> {{ macros::input(label='Name', name='name', type="text", required=true) }}
</dialog> {{ 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") }}
<dialog id="add-schnupperkurs" <input value="Hinzufügen" type="submit" class="btn btn-primary" />
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" </form>
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 ">
<option value="schnupperInterested">Interessiert am Schnupperkurs</option>
<option value="schnupperant">Fixe Schnupperkurs-Anmeldung</option>
</select>
</div> </div>
{{ macros::input(label='Name', name='name', type="text", required=true) }} </div>
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }} </dialog>
{{ 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>
</div>
</div>
</dialog>
</details> </details>
{% endif %} {% endif %}
<!-- START filterBar --> <!-- START filterBar -->

View File

@ -385,9 +385,11 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% if "paid" in user.roles %} {% if "paid" in user.roles %}
✅ {% for key, value in member %}
{% for key, value in member %}
{% if loop.first %}{{ key }}{% endif %} {% if loop.first %}{{ key }}{% endif %}
{% endfor %} hat schon bezahlt {% endfor %}
hat schon bezahlt
{% else %} {% else %}
{% for key, value in member %} {% for key, value in member %}
@ -406,7 +408,9 @@
<div class="py-3"> <div class="py-3">
<ul class="list-disc ms-4"> <ul class="list-disc ms-4">
{% for activity in activities %} {% for activity in activities %}
<li><strong>{{ activity.created_at | date(format="%d. %m. %Y") }}:</strong> <small>{{ activity.text }}</small></li> <li>
<strong>{{ activity.created_at | date(format="%d. %m. %Y") }}:</strong> <small>{{ activity.text }}</small>
</li>
{% else %} {% else %}
<li>Noch keine Aktivität... Stay tuned 😆</li> <li>Noch keine Aktivität... Stay tuned 😆</li>
{% endfor %} {% endfor %}

View File

@ -183,126 +183,130 @@
<div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative" <div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative"
data-filterable="true" data-filterable="true"
data-filter="{{ log.boat.name }} {% for rower in log.rowers %}{{ rower.name }}{% endfor %}"> data-filter="{{ log.boat.name }} {% for rower in log.rowers %}{{ rower.name }}{% endfor %}">
{% if log.logtype and not hide_type %} {% if log.logtype and not hide_type %}
<div class="absolute top-0 right-0 bg-primary-100 rounded-bl-md text-primary-950 text-xs w-32 px-2 py-1 text-center font-bold"> <div class="absolute top-0 right-0 bg-primary-100 rounded-bl-md text-primary-950 text-xs w-32 px-2 py-1 text-center font-bold">
{% if log.logtype == 1 %} {% if log.logtype == 1 %}
Wanderfahrt Wanderfahrt
{% else %} {% else %}
{% if log.logtype == 2 %} {% if log.logtype == 2 %}
Regatta Regatta
{% else %}
{{ log.logtype }}
{% endif %}
{% endif %}
</div>
{% endif %}
<div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
{% if allowed_to_edit %}
<a href="#"
onclick="document.getElementById('change-{{ log.id }}').showModal()"
class="link link-black font-bold">{{ log.boat.name }}</a>
{% else %}
<strong class="text-black dark:text-white">
{{ log.boat.name }}
</strong>
{% endif %}
<small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
{% if log.shipmaster_only_steering %}
- handgesteuert
{%- endif -%}
)</small>
<small class="block text-gray-600 dark:text-gray-100">
{% if state == "completed" and log.departure | date(format='%d.%m.%Y') == log.arrival | date(format='%d.%m.%Y') %}
{{ log.departure | date(format='%d.%m.%Y') }}
({{ log.departure | date(format='%H:%M') }}
-
{{ log.arrival | date(format='%H:%M') }})
{% else %}
{{ log.departure | date(format='%d.%m.%Y (%H:%M)') }}
{% if state == "completed" %}
-
{{ log.arrival | date(format='%d.%m.%Y (%H:%M)') }}
{% endif %}
{% endif %}
</small>
{% set amount_rowers = log.rowers | length %}
{% set amount_guests = log.boat.amount_seats - amount_rowers %}
{% if allowed_to_close and state == "on_water" %}
{{ log::home(log=log) }}
{% else %} {% else %}
<div class="text-black dark:text-white"> {{ log.logtype }}
{{ log.destination }} {% endif %}
{% if state == "completed" %} {% endif %}
<small class="text-gray-600 dark:text-gray-100">({{ log.distance_in_km }} </div>
km)</small> {% endif %}
{% endif %} <div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
{% if log.comments %}<span class="text-sm italic">- "{{ log.comments }}"</span>{% endif %}
</div>
{% if amount_guests > 0 or log.rowers | length > 0 %}
{% if not log.boat.amount_seats == 1 %}
<div class="text-sm text-gray-600 dark:text-gray-100">
Ruderer:
{% for rower in log.rowers -%}
{{ rower.name }}
{%- if rower.id == log.steering_user.id and rower.id != log.shipmaster_user.id %}
(Steuerperson){%- endif -%}
{%- if not loop.last or amount_guests > 0 and not log.boat.external %},{% endif %}
{% endfor -%}
{% if amount_guests > 0 and not log.boat.external %}
Gäste
<small class="text-gray-600 dark:text-gray-100">(ohne Account)</small>:
{{ amount_guests }}
{% endif %}
</div>
{% endif %}
{% endif %}
{% endif %}
</div>
{% if allowed_to_edit %} {% if allowed_to_edit %}
<dialog id="change-{{ log.id }}" <a href="#"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md" onclick="document.getElementById('change-{{ log.id }}').showModal()"
onclick="document.getElementById('change-{{ log.id }}').close()"> class="link link-black font-bold">{{ log.boat.name }}</a>
<div onclick="event.stopPropagation();" class="p-3"> {% else %}
<button type="button" <strong class="text-black dark:text-white">{{ log.boat.name }}</strong>
onclick="document.getElementById('change-{{ log.id }}').close()" {% endif %}
title="Schließen" <small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
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"> {% if log.shipmaster_only_steering %}
<svg class="inline h-5 w-5" - handgesteuert
width="16" {%- endif -%}
height="16" )</small>
fill="currentColor" <small class="block text-gray-600 dark:text-gray-100">
viewBox="0 0 16 16"> {% if state == "completed" and log.departure | date(format='%d.%m.%Y') == log.arrival | date(format='%d.%m.%Y') %}
<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> {{ log.departure | date(format='%d.%m.%Y') }}
</svg> ({{ log.departure | date(format='%H:%M') }}
</button> -
<div class="mt-8"> {{ log.arrival | date(format='%H:%M') }})
<h2 class="h3">Eintrag '{{ log.boat.name }}' ändern </h2> {% else %}
<p class="text-center mb-3">{{ log.id }}</p> {{ log.departure | date(format='%d.%m.%Y (%H:%M)') }}
<form action="/log/update" method="post" class="grid gap-3"> {% if state == "completed" %}
<input type="hidden" name="id" value="{{ log.id }}" /> -
<input type="hidden" name="boat_id" value="{{ log.boat_id }}" /> {{ log.arrival | date(format='%d.%m.%Y (%H:%M)') }}
<input type="hidden" name="shipmaster" value="{{ log.shipmaster }}" /> {% endif %}
<input type="hidden" {% endif %}
name="steering_person" </small>
value="{{ log.steering_person }}" /> {% set amount_rowers = log.rowers | length %}
{{ macros::checkbox(label='Handgesteuert', name='shipmaster_only_steering', id=log.shipmaster_only_steering,checked=log.shipmaster_only_steering) }} {% set amount_guests = log.boat.amount_seats - amount_rowers %}
<input type="datetime-local" class="input rounded-md" name="departure" value="{{ log.departure }}" /> {% if allowed_to_close and state == "on_water" %}
<input type="datetime-local" class="input rounded-md" name="arrival" value="{{ log.arrival }}" /> {{ log::home(log=log) }}
<input type="hidden" name="destination" value="{{ log.destination }}" /> {% else %}
<input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" /> <div class="text-black dark:text-white">
<input type="hidden" name="comments" value="{{ log.comments }}" /> {{ log.destination }}
<input type="hidden" name="logtype" value="{{ log.logtype }}" /> {% if state == "completed" %}
<input type="submit" class="btn btn-primary" value="Updaten" /> <small class="text-gray-600 dark:text-gray-100">({{ log.distance_in_km }}
</form> km)</small>
<a href="/log/{{ log.id }}/delete" {% endif %}
class="w-28 btn btn-alert mt-3" {% if log.comments %}<span class="text-sm italic">- "{{ log.comments }}"</span>{% endif %}
onclick="return confirm('Willst du diesen Logbucheintrag wirklich löschen?');"> </div>
{% include "includes/delete-icon" %} {% if amount_guests > 0 or log.rowers | length > 0 %}
Löschen {% if not log.boat.amount_seats == 1 %}
</a> <div class="text-sm text-gray-600 dark:text-gray-100">
Ruderer:
{% for rower in log.rowers -%}
{{ rower.name }}
{%- if rower.id == log.steering_user.id and rower.id != log.shipmaster_user.id %}
(Steuerperson){%- endif -%}
{%- if not loop.last or amount_guests > 0 and not log.boat.external %},{% endif %}
{% endfor -%}
{% if amount_guests > 0 and not log.boat.external %}
Gäste
<small class="text-gray-600 dark:text-gray-100">(ohne Account)</small>:
{{ amount_guests }}
{% endif %}
</div>
{% endif %}
{% endif %}
{% endif %}
</div>
{% if allowed_to_edit %}
<dialog id="change-{{ log.id }}"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('change-{{ log.id }}').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('change-{{ log.id }}').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">Eintrag '{{ log.boat.name }}' ändern</h2>
<p class="text-center mb-3">{{ log.id }}</p>
<form action="/log/update" method="post" class="grid gap-3">
<input type="hidden" name="id" value="{{ log.id }}" />
<input type="hidden" name="boat_id" value="{{ log.boat_id }}" />
<input type="hidden" name="shipmaster" value="{{ log.shipmaster }}" />
<input type="hidden"
name="steering_person"
value="{{ log.steering_person }}" />
{{ macros::checkbox(label='Handgesteuert', name='shipmaster_only_steering', id=log.shipmaster_only_steering,checked=log.shipmaster_only_steering) }}
<input type="datetime-local"
class="input rounded-md"
name="departure"
value="{{ log.departure }}" />
<input type="datetime-local"
class="input rounded-md"
name="arrival"
value="{{ log.arrival }}" />
<input type="hidden" name="destination" value="{{ log.destination }}" />
<input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" />
<input type="hidden" name="comments" value="{{ log.comments }}" />
<input type="hidden" name="logtype" value="{{ log.logtype }}" />
<input type="submit" class="btn btn-primary" value="Updaten" />
</form>
<a href="/log/{{ log.id }}/delete"
class="w-28 btn btn-alert mt-3"
onclick="return confirm('Willst du diesen Logbucheintrag wirklich löschen?');">
{% include "includes/delete-icon" %}
Löschen
</a>
</div> </div>
</div> </div>
</dialog> </dialog>
{% endif %} {% endif %}
</div> </div>
{% endmacro show_old %} {% endmacro show_old %}