Merge pull request 'single-user-edit-page' (#990) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 14m43s
CI/CD Pipeline / deploy-staging (push) Successful in 13m5s
CI/CD Pipeline / deploy-main (push) Has been skipped

Reviewed-on: #990
This commit is contained in:
philipp 2025-05-06 10:14:55 +02:00
commit e3fc756b3f
11 changed files with 126 additions and 99 deletions

View File

@ -11,6 +11,10 @@
@apply text-white hover:text-primary-100 underline; @apply text-white hover:text-primary-100 underline;
} }
&-black {
@apply text-black hover:text-primary-950 dark:text-white hover:dark:text-primary-300 underline;
}
&-no-underline { &-no-underline {
@apply no-underline; @apply no-underline;
} }

View File

@ -115,7 +115,7 @@ test("Cox can start and finish trip", async ({ page }, testInfo) => {
await page.getByPlaceholder("Passwort").press("Enter"); await page.getByPlaceholder("Passwort").press("Enter");
await page.goto("/log/show"); await page.goto("/log/show");
await page.getByText('(cox2)').click(); await page.getByRole('link', { name: 'Joe' }).nth(1).click();
page.once("dialog", (dialog) => { page.once("dialog", (dialog) => {
dialog.accept().catch(() => {}); dialog.accept().catch(() => {});
}); });
@ -208,7 +208,6 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
await page.getByRole('link', { name: 'Logbuch' }).click(); await page.getByRole('link', { name: 'Logbuch' }).click();
await expect(page.locator('body')).toContainText('Joe'); await expect(page.locator('body')).toContainText('Joe');
await expect(page.locator('body')).toContainText('(cox2)');
await expect(page.locator('body')).toContainText('Ottensheim (25 km)'); await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2'); await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2');
@ -225,7 +224,7 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => {
await page.getByPlaceholder("Passwort").press("Enter"); await page.getByPlaceholder("Passwort").press("Enter");
await page.goto("/log/show"); await page.goto("/log/show");
await page.getByText('(cox2)').click(); await page.getByRole('link', { name: 'Joe' }).nth(1).click();
page.once("dialog", (dialog) => { page.once("dialog", (dialog) => {
dialog.accept().catch(() => {}); dialog.accept().catch(() => {});
}); });
@ -286,7 +285,6 @@ test("Cox can start and finish trip with cox steering only", async ({ page }, te
await page.goto('/log/show'); await page.goto('/log/show');
await expect(page.locator('body')).toContainText('cox_only_steering_boat'); await expect(page.locator('body')).toContainText('cox_only_steering_boat');
await expect(page.locator('body')).toContainText('(cox2 - handgesteuert)');
await expect(page.locator('body')).toContainText('Ottensheim (25 km)'); await expect(page.locator('body')).toContainText('Ottensheim (25 km)');
@ -302,7 +300,7 @@ test("Cox can start and finish trip with cox steering only", async ({ page }, te
await page.getByPlaceholder("Passwort").press("Enter"); await page.getByPlaceholder("Passwort").press("Enter");
await page.goto("/log/show"); await page.goto("/log/show");
await page.getByText('(cox2 - handgesteuert)').click(); await page.getByRole("link", { name: "cox_only_steering_boat" }).click();
page.once("dialog", (dialog) => { page.once("dialog", (dialog) => {
dialog.accept().catch(() => {}); dialog.accept().catch(() => {});
}); });
@ -371,7 +369,7 @@ test("Kiosk can start and finish trip in one stop", async ({ page }, testInfo) =
await page.getByPlaceholder("Passwort").press("Enter"); await page.getByPlaceholder("Passwort").press("Enter");
await page.goto("/log/show"); await page.goto("/log/show");
await page.getByText('(cox2)').click(); await page.getByRole('link', { name: 'Joe' }).nth(1).click();
page.once("dialog", (dialog) => { page.once("dialog", (dialog) => {
dialog.accept().catch(() => {}); dialog.accept().catch(() => {});
}); });

View File

@ -414,12 +414,14 @@ impl User {
.await .await
.unwrap(); .unwrap();
ActivityBuilder::new(&format!( if !role.hide_in_lists && role.cluster.is_none() {
"{updated_by} hat die Rolle {role} von {self} entfernt." ActivityBuilder::new(&format!(
)) "{updated_by} hat die Rolle {role} von {self} entfernt."
.relevant_for_user(self) ))
.save(db) .relevant_for_user(self)
.await; .save(db)
.await;
}
Ok(()) Ok(())
} }
@ -499,7 +501,7 @@ impl User {
) )
})?; })?;
if !role.hide_in_lists { if !role.hide_in_lists && role.cluster.is_none() {
ActivityBuilder::new(&format!( ActivityBuilder::new(&format!(
"{updated_by} hat die Rolle '{role}' dem Benutzer {self} hinzugefügt." "{updated_by} hat die Rolle '{role}' dem Benutzer {self} hinzugefügt."
)) ))

View File

@ -1,21 +1,20 @@
use std::{fmt::Display, ops::DerefMut}; use std::{fmt::Display, ops::DerefMut};
use argon2::{Argon2, PasswordHasher, password_hash::SaltString}; use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
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,
@ -24,6 +23,7 @@ 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() {
@ -577,10 +577,6 @@ ASKÖ Ruderverein Donau Linz", self.name),
.execute(db) .execute(db)
.await .await
.unwrap(); //Okay, because we can only create a User of a valid id .unwrap(); //Okay, because we can only create a User of a valid id
ActivityBuilder::new(&format!("User {self} hat sich eingeloggt."))
.relevant_for_user(self)
.save(db)
.await;
} }
pub async fn delete(&self, db: &SqlitePool, deleted_by: &ManageUserUser) { pub async fn delete(&self, db: &SqlitePool, deleted_by: &ManageUserUser) {
@ -622,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()
@ -633,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()
@ -867,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");
@ -982,21 +978,17 @@ mod test {
#[sqlx::test] #[sqlx::test]
fn wrong_pw() { fn wrong_pw() {
let pool = testdb!(); let pool = testdb!();
assert!( assert!(User::login(&pool, "admin".into(), "admi".into())
User::login(&pool, "admin".into(), "admi".into()) .await
.await .is_err());
.is_err()
);
} }
#[sqlx::test] #[sqlx::test]
fn wrong_username() { fn wrong_username() {
let pool = testdb!(); let pool = testdb!();
assert!( assert!(User::login(&pool, "admi".into(), "admin".into())
User::login(&pool, "admi".into(), "admin".into()) .await
.await .is_err());
.is_err()
);
} }
#[sqlx::test] #[sqlx::test]
@ -1015,11 +1007,9 @@ 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!( assert!(User::login(&pool, "admin".into(), "abc".into())
User::login(&pool, "admin".into(), "abc".into()) .await
.await .is_err());
.is_err()
);
user.update_pw(&pool, "abc".into()).await; user.update_pw(&pool, "abc".into()).await;

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,7 +19,6 @@ 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,
@ -27,9 +26,9 @@ use rocket::{
post, post,
request::{FlashMessage, FromRequest, Outcome}, request::{FlashMessage, FromRequest, Outcome},
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes, FromForm, Request, Route, State,
}; };
use rocket_dyn_templates::{Template, tera::Context}; use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool; use sqlx::SqlitePool;
// Custom request guard to extract the Referer header // Custom request guard to extract the Referer header
@ -133,6 +132,12 @@ async fn view(
format!("User mit ID {} gibts ned", user), format!("User mit ID {} gibts ned", user),
)); ));
}; };
if user.name == "Externe Steuerperson" {
return Err(Flash::error(
Redirect::to("/admin/user"),
"Diese besondere Person kannst du dir leider nicht anschauen, mein lieber neugieriger Ruderant!"
));
}
let member = Member::from(db, user.clone()).await; let member = Member::from(db, user.clone()).await;
let fee = user.fee(db).await; let fee = user.fee(db).await;

View File

@ -1,5 +1,4 @@
use rocket::{ use rocket::{
FromForm, Request, Route, State,
form::Form, form::Form,
get, get,
http::{Cookie, CookieJar}, http::{Cookie, CookieJar},
@ -9,11 +8,13 @@ use rocket::{
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes,
time::{Duration, OffsetDateTime}, time::{Duration, OffsetDateTime},
FromForm, Request, Route, State,
}; };
use rocket_dyn_templates::{Template, context, tera}; use rocket_dyn_templates::{context, tera, Template};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::{ use crate::model::{
activity::ActivityBuilder,
log::Log, log::Log,
user::{LoginError, User}, user::{LoginError, User},
}; };
@ -82,13 +83,12 @@ async fn login(
cookies.add_private(Cookie::new("loggedin_user", format!("{}", user.id))); cookies.add_private(Cookie::new("loggedin_user", format!("{}", user.id)));
Log::create( ActivityBuilder::new(&format!(
db, "{user} hat sich eingeloggt (User-Agent: {})",
format!( agent.0
"Succ login of {} with this useragent: {}", ))
login.name, agent.0 .relevant_for_user(&user)
), .save(db)
)
.await; .await;
// Check for redirect_url cookie and redirect accordingly // Check for redirect_url cookie and redirect accordingly

View File

@ -1,7 +1,6 @@
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},
@ -10,8 +9,9 @@ use rocket::{
response::{Flash, Redirect}, response::{Flash, Redirect},
routes, routes,
time::{Duration, OffsetDateTime}, time::{Duration, OffsetDateTime},
Request, Route, State,
}; };
use rocket_dyn_templates::{Template, context}; use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tera::Context; use tera::Context;
@ -110,10 +110,13 @@ async fn index(
#[get("/show", rank = 3)] #[get("/show", rank = 3)]
async fn show(db: &State<SqlitePool>, user: DonauLinzUser) -> Template { async fn show(db: &State<SqlitePool>, user: DonauLinzUser) -> Template {
let logs = Logbook::completed(db).await; let logs = Logbook::completed(db).await;
let boats = Boat::all(db).await;
let users = User::all(db).await;
let logtypes = LogType::all(db).await;
Template::render( Template::render(
"log.completed", "log.completed",
context!(logs, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await), context!(logs, boats, users, logtypes, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await),
) )
} }
@ -582,7 +585,7 @@ mod test {
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::logbook::Logbook; use crate::model::logbook::Logbook;
use crate::tera::{User, log::Boat}; use crate::tera::{log::Boat, User};
use crate::testdb; use crate::testdb;
#[sqlx::test] #[sqlx::test]

View File

@ -12,13 +12,13 @@
<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> </div>

View File

@ -4,11 +4,13 @@
{% block content %} {% block content %}
<div class="max-w-screen-lg w-full"> <div class="max-w-screen-lg w-full">
{% if "admin" in loggedin_user.roles or "Vorstand" in loggedin_user.roles %} {% if "admin" in loggedin_user.roles or "Vorstand" in loggedin_user.roles %}
<a href="/admin/user" class="link link-primary link-no-underline">&larr; Userverwaltung</a> <div class="mb-5 lg:mb-0">
<a href="/admin/user" class="link link-primary link-no-underline">&larr; Userverwaltung</a>
</div>
{% endif %} {% endif %}
<h1 class="h1">{{ user.name }}</h1> <h1 class="h1">{{ user.name }}</h1>
<div class="grid sm:grid-cols-2 gap-3"> <div class="grid sm:grid-cols-2 gap-8 my-8">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<h2 class="h2"> <h2 class="h2">
Grunddaten Grunddaten
<br /> <br />
@ -53,7 +55,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<h2 class="h2"> <h2 class="h2">
Mitgliedschaft Mitgliedschaft
<br /> <br />
@ -119,12 +121,12 @@
</div> </div>
{% if allowed_to_edit %} {% if allowed_to_edit %}
<div class="py-3"> <div class="py-3">
<div class="mt-3 text-right"> <div class="text-right">
<button type="button" <button type="button"
onclick="document.getElementById('change-member-type').showModal()" onclick="document.getElementById('change-member-type').showModal()"
class="btn btn-dark">Mitgliedsstatus ändern</button> class="btn btn-dark">Mitgliedsstatus ändern</button>
<a href="/admin/user/{{ user.id }}/delete" <a href="/admin/user/{{ user.id }}/delete"
class="btn btn-alert" class="btn btn-alert mt-3"
onclick="return confirm('Ist {{ user.name }} wirklich aus dem Verein ausgetreten?');"> onclick="return confirm('Ist {{ user.name }} wirklich aus dem Verein ausgetreten?');">
{% include "includes/delete-icon" %} {% include "includes/delete-icon" %}
Mitglied ist ausgetreten Mitglied ist ausgetreten
@ -285,7 +287,7 @@
</div> </div>
</div> </div>
{% if is_clubmember %} {% if is_clubmember %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<h2 class="h2">Rollen</h2> <h2 class="h2">Rollen</h2>
<div> <div>
<ul class="divide-y divide-gray-200 dark:divide-primary-60 w-full"> <ul class="divide-y divide-gray-200 dark:divide-primary-60 w-full">
@ -363,7 +365,7 @@
</div> </div>
{% endif %} {% endif %}
{% if supposed_to_pay %} {% if supposed_to_pay %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<h2 class="h2">💸-Beitrag</h2> <h2 class="h2">💸-Beitrag</h2>
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600">
<div class="py-3"> <div class="py-3">
@ -400,13 +402,13 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<h2 class="h2">Aktivitäten</h2> <h2 class="h2">Aktivitäten</h2>
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> <div class="mx-3 max-h-60 overflow-y-scroll">
<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>{{ activity.created_at | date(format="%d. %m. %Y") }}: {{ activity.text }}</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 %}
@ -414,13 +416,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"> <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<h2 class="h2">Ergo-Challenge</h2> <h2 class="h2">Ergo-Challenge</h2>
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> <div class="mx-3">
<div class="py-3"> <div class="grid gap-3 pb-3 mt-3">
{{ macros::input(label='DOB', name='dob', type="text", value=user.dob, readonly=allowed_to_edit == false) }} {{ macros::inputgroup(label='DOB', name='dob', type="text", value=user.dob, readonly=allowed_to_edit == false) }}
{{ macros::input(label='Weight (kg)', name='weight', type="text", value=user.weight, readonly=allowed_to_edit == false) }} {{ macros::inputgroup(label='Weight (kg)', name='weight', type="text", value=user.weight, readonly=allowed_to_edit == false) }}
{{ macros::input(label='Sex', name='sex', type="text", value=user.sex, readonly=allowed_to_edit == false) }} {{ macros::inputgroup(label='Sex', name='sex', type="text", value=user.sex, readonly=allowed_to_edit == false) }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -183,8 +183,6 @@
<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 %}">
<details>
<summary style="list-style: none;">
{% 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 %}
@ -199,7 +197,15 @@
</div> </div>
{% endif %} {% endif %}
<div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}> <div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
<strong class="text-black dark:text-white">{{ log.boat.name }}</strong> {% 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 -}} <small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
{% if log.shipmaster_only_steering %} {% if log.shipmaster_only_steering %}
- handgesteuert - handgesteuert
@ -252,35 +258,52 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
</summary> {% if allowed_to_edit %}
{% if allowed_to_edit %} <dialog id="change-{{ log.id }}"
<form action="/log/update" method="post"> 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="id" value="{{ log.id }}" />
<input type="hidden" name="boat_id" value="{{ log.boat_id }}" /> <input type="hidden" name="boat_id" value="{{ log.boat_id }}" />
<input type="hidden" name="shipmaster" value="{{ log.shipmaster }}" /> <input type="hidden" name="shipmaster" value="{{ log.shipmaster }}" />
<input type="hidden" <input type="hidden"
name="steering_person" name="steering_person"
value="{{ log.steering_person }}" /> value="{{ log.steering_person }}" />
Handgesteuert: {{ macros::checkbox(label='Handgesteuert', name='shipmaster_only_steering', id=log.shipmaster_only_steering,checked=log.shipmaster_only_steering) }}
<input type="checkbox" <input type="datetime-local" class="input rounded-md" name="departure" value="{{ log.departure }}" />
name="shipmaster_only_steering" <input type="datetime-local" class="input rounded-md" name="arrival" value="{{ log.arrival }}" />
{% if log.shipmaster_only_steering %}checked="checked"{% endif %} />
<input type="datetime-local" name="departure" value="{{ log.departure }}" />
<input type="datetime-local" name="arrival" value="{{ log.arrival }}" />
<input type="hidden" name="destination" value="{{ log.destination }}" /> <input type="hidden" name="destination" value="{{ log.destination }}" />
<input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" /> <input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" />
<input type="hidden" name="comments" value="{{ log.comments }}" /> <input type="hidden" name="comments" value="{{ log.comments }}" />
<input type="hidden" name="logtype" value="{{ log.logtype }}" /> <input type="hidden" name="logtype" value="{{ log.logtype }}" />
<input type="submit" value="Updaten" /> <input type="submit" class="btn btn-primary" value="Updaten" />
</form> </form>
<a href="/log/{{ log.id }}/delete" <a href="/log/{{ log.id }}/delete"
class="w-28 btn btn-alert" class="w-28 btn btn-alert mt-3"
onclick="return confirm('Willst du diesen Logbucheintrag wirklich löschen?');"> onclick="return confirm('Willst du diesen Logbucheintrag wirklich löschen?');">
{% include "includes/delete-icon" %} {% include "includes/delete-icon" %}
Löschen Löschen
</a> </a>
{% endif %} </div>
</details> </div>
</dialog>
{% endif %}
</div> </div>
{% endmacro show_old %} {% endmacro show_old %}
{% macro home(log) %} {% macro home(log) %}

View File

@ -26,7 +26,7 @@
{% for log in logs %} {% for log in logs %}
{% set_global allowed_to_edit = false %} {% set_global allowed_to_edit = false %}
{% if loggedin_user %} {% if loggedin_user %}
{% if "Vorstand" in loggedin_user.roles %} {% if "Vorstand" in loggedin_user.roles or "admin" in loggedin_user.roles %}
{% set_global allowed_to_edit = true %} {% set_global allowed_to_edit = true %}
{% endif %} {% endif %}
{% endif %} {% endif %}