updates #715

Merged
philipp merged 10 commits from updates into main 2024-09-02 13:27:11 +02:00
11 changed files with 139 additions and 42 deletions
Showing only changes of commit 99a49dbec9 - Show all commits

View File

@ -575,10 +575,8 @@ ORDER BY departure DESC
return Err(LogbookUpdateError::ArrivalNotAfterDeparture); return Err(LogbookUpdateError::ArrivalNotAfterDeparture);
} }
if !boat.external { if !boat.external && boat.on_water_between(db, dep, arr).await {
if boat.on_water_between(db, dep, arr).await { return Err(LogbookUpdateError::BoatAlreadyOnWater);
return Err(LogbookUpdateError::BoatAlreadyOnWater);
};
} }
let duration_in_mins = (arr.and_utc().timestamp() - dep.and_utc().timestamp()) / 60; let duration_in_mins = (arr.and_utc().timestamp() - dep.and_utc().timestamp()) / 60;
@ -594,14 +592,13 @@ ORDER BY departure DESC
let today = Local::now().date_naive(); let today = Local::now().date_naive();
let day_diff = today - arr.date(); let day_diff = today - arr.date();
let day_diff = day_diff.num_days(); let day_diff = day_diff.num_days();
if day_diff >= 7 { if day_diff >= 7
if !user.has_role_tx(db, "admin").await && !user.has_role_tx(db, "admin").await
&& !user && !user
.has_role_tx(db, "allow-retroactive-logbookentries") .has_role_tx(db, "allow-retroactive-logbookentries")
.await .await
{ {
return Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday); return Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday);
}
} }
if day_diff < 0 && !user.has_role_tx(db, "admin").await { if day_diff < 0 && !user.has_role_tx(db, "admin").await {
return Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday); return Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday);

View File

@ -40,7 +40,6 @@ pub struct TripUpdate<'a> {
pub max_people: i32, pub max_people: i32,
pub notes: Option<&'a str>, pub notes: Option<&'a str>,
pub trip_type: Option<i64>, //TODO: Move to `TripType` pub trip_type: Option<i64>, //TODO: Move to `TripType`
pub always_show: bool,
pub is_locked: bool, pub is_locked: bool,
} }
@ -210,11 +209,10 @@ WHERE day=?
let was_already_cancelled = tripdetails.max_people == 0; let was_already_cancelled = tripdetails.max_people == 0;
sqlx::query!( sqlx::query!(
"UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, always_show = ?, is_locked = ? WHERE id = ?", "UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, is_locked = ? WHERE id = ?",
update.max_people, update.max_people,
update.notes, update.notes,
update.trip_type, update.trip_type,
update.always_show,
update.is_locked, update.is_locked,
trip_details_id trip_details_id
) )
@ -338,6 +336,20 @@ WHERE day=?
self.cox_id == user_id self.cox_id == user_id
} }
pub(crate) async fn toggle_always_show(&self, db: &SqlitePool) {
if let Some(trip_details) = self.trip_details_id {
let new_state = !self.always_show;
sqlx::query!(
"UPDATE trip_details SET always_show = ? WHERE id = ?",
new_state,
trip_details
)
.execute(db)
.await
.unwrap();
}
}
pub(crate) async fn get_pinned_for_day( pub(crate) async fn get_pinned_for_day(
db: &sqlx::Pool<sqlx::Sqlite>, db: &sqlx::Pool<sqlx::Sqlite>,
day: NaiveDate, day: NaiveDate,

View File

@ -1,5 +1,5 @@
use crate::model::user::User; use crate::model::user::User;
use chrono::NaiveDate; use chrono::{Local, NaiveDate};
use rocket::FromForm; use rocket::FromForm;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool}; use sqlx::{FromRow, SqlitePool};
@ -33,7 +33,6 @@ pub struct TripDetailsToAdd<'r> {
pub notes: Option<&'r str>, pub notes: Option<&'r str>,
pub trip_type: Option<i64>, pub trip_type: Option<i64>,
pub allow_guests: bool, pub allow_guests: bool,
pub always_show: bool,
} }
impl TripDetails { impl TripDetails {
@ -59,6 +58,24 @@ WHERE id like ?
} }
} }
pub fn date(&self) -> NaiveDate {
NaiveDate::parse_from_str(&self.day, "%Y-%m-%d").unwrap()
}
pub(crate) async fn user_sees_trip(&self, db: &SqlitePool, user: &User) -> bool {
let today = Local::now().date_naive();
let day_diff = self.date() - today;
let day_diff = day_diff.num_days();
if day_diff < 0 {
// tripdetails is in past
return false;
}
if day_diff <= user.amount_days_to_show(db).await {
return true;
}
self.always_show
}
pub async fn find_by_startingdatetime( pub async fn find_by_startingdatetime(
db: &SqlitePool, db: &SqlitePool,
day: String, day: String,
@ -142,14 +159,13 @@ WHERE day = ? AND planned_starting_time = ?
/// Creates a new entry in `trip_details` and returns its id. /// Creates a new entry in `trip_details` and returns its id.
pub async fn create(db: &SqlitePool, tripdetails: TripDetailsToAdd<'_>) -> i64 { pub async fn create(db: &SqlitePool, tripdetails: TripDetailsToAdd<'_>) -> i64 {
let query = sqlx::query!( let query = sqlx::query!(
"INSERT INTO trip_details(planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show) VALUES(?, ?, ?, ?, ?, ?, ?)" , "INSERT INTO trip_details(planned_starting_time, max_people, day, notes, allow_guests, trip_type_id) VALUES(?, ?, ?, ?, ?, ?)" ,
tripdetails.planned_starting_time, tripdetails.planned_starting_time,
tripdetails.max_people, tripdetails.max_people,
tripdetails.day, tripdetails.day,
tripdetails.notes, tripdetails.notes,
tripdetails.allow_guests, tripdetails.allow_guests,
tripdetails.trip_type, tripdetails.trip_type,
tripdetails.always_show
) )
.execute(db) .execute(db)
.await .await
@ -305,7 +321,6 @@ mod test {
notes: None, notes: None,
allow_guests: false, allow_guests: false,
trip_type: None, trip_type: None,
always_show: false
} }
) )
.await, .await,
@ -321,7 +336,6 @@ mod test {
notes: None, notes: None,
allow_guests: false, allow_guests: false,
trip_type: None, trip_type: None,
always_show: false
} }
) )
.await, .await,

View File

@ -450,6 +450,12 @@ ASKÖ Ruderverein Donau Linz", self.name),
false false
} }
pub async fn allowed_to_update_always_show_trip(&self, db: &SqlitePool) -> bool {
AllowedToUpdateTripToAlwaysBeShownUser::new(db, self.clone())
.await
.is_some()
}
pub async fn has_membership_pdf(&self, db: &SqlitePool) -> bool { pub async fn has_membership_pdf(&self, db: &SqlitePool) -> bool {
match sqlx::query_scalar!("SELECT membership_pdf FROM user WHERE id = ?", self.id) match sqlx::query_scalar!("SELECT membership_pdf FROM user WHERE id = ?", self.id)
.fetch_one(db) .fetch_one(db)
@ -879,7 +885,7 @@ ORDER BY last_access DESC
days days
} }
async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 {
if self.has_role(db, "cox").await { if self.has_role(db, "cox").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
@ -890,7 +896,7 @@ ORDER BY last_access DESC
.num_days() .num_days()
+ 1; + 1;
if days_left_in_year < 30 { 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
@ -1028,6 +1034,7 @@ special_user!(VorstandUser, +"Vorstand");
special_user!(EventUser, +"manage_events"); special_user!(EventUser, +"manage_events");
special_user!(AllowedToEditPaymentStatusUser, +"kassier", +"admin"); special_user!(AllowedToEditPaymentStatusUser, +"kassier", +"admin");
special_user!(ManageUserUser, +"admin", +"schriftfuehrer"); special_user!(ManageUserUser, +"admin", +"schriftfuehrer");
special_user!(AllowedToUpdateTripToAlwaysBeShownUser, +"admin");
#[derive(FromRow, Serialize, Deserialize, Clone, Debug)] #[derive(FromRow, Serialize, Deserialize, Clone, Debug)]
pub struct UserWithRolesAndMembershipPdf { pub struct UserWithRolesAndMembershipPdf {

View File

@ -24,6 +24,10 @@ impl UserTrip {
return Err(UserTripError::GuestNotAllowedForThisEvent); return Err(UserTripError::GuestNotAllowedForThisEvent);
} }
if !trip_details.user_sees_trip(db, user).await {
return Err(UserTripError::NotVisibleToUser);
}
//TODO: Check if user sees the event (otherwise she could forge trip_details_id) //TODO: Check if user sees the event (otherwise she could forge trip_details_id)
let is_cox = trip_details.user_is_cox(db, user).await; let is_cox = trip_details.user_is_cox(db, user).await;
@ -96,6 +100,10 @@ impl UserTrip {
return Err(UserTripDeleteError::DetailsLocked); return Err(UserTripDeleteError::DetailsLocked);
} }
if !trip_details.user_sees_trip(db, user).await {
return Err(UserTripDeleteError::NotVisibleToUser);
}
if let Some(name) = name { if let Some(name) = name {
if !trip_details.user_allowed_to_change(db, user).await { if !trip_details.user_allowed_to_change(db, user).await {
return Err(UserTripDeleteError::NotAllowedToDeleteGuest); return Err(UserTripDeleteError::NotAllowedToDeleteGuest);
@ -137,6 +145,7 @@ pub enum UserTripError {
CantRegisterAtOwnEvent, CantRegisterAtOwnEvent,
GuestNotAllowedForThisEvent, GuestNotAllowedForThisEvent,
NotAllowedToAddGuest, NotAllowedToAddGuest,
NotVisibleToUser,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -144,6 +153,7 @@ pub enum UserTripDeleteError {
DetailsLocked, DetailsLocked,
GuestNotParticipating, GuestNotParticipating,
NotAllowedToDeleteGuest, NotAllowedToDeleteGuest,
NotVisibleToUser,
} }
#[cfg(test)] #[cfg(test)]

View File

@ -374,7 +374,7 @@ async fn create_scheckbuch(
if mail.parse::<Address>().is_err() { if mail.parse::<Address>().is_err() {
return Flash::error( return Flash::error(
Redirect::to("/admin/user/scheckbuch"), Redirect::to("/admin/user/scheckbuch"),
format!("Keine gültige Mailadresse"), "Keine gültige Mailadresse".to_string(),
); );
} }
@ -383,9 +383,8 @@ async fn create_scheckbuch(
if User::find_by_name(db, name).await.is_some() { if User::find_by_name(db, name).await.is_some() {
return Flash::error( return Flash::error(
Redirect::to("/admin/user/scheckbuch"), Redirect::to("/admin/user/scheckbuch"),
format!( "Kann kein Scheckbuch erstellen, der Name wird bereits von einem User verwendet"
"Kann kein Scheckbuch erstellen, der Name wird bereits von einem User verwendet" .to_string(),
),
); );
} }
@ -418,14 +417,14 @@ async fn schnupper_to_scheckbuch(
let Some(user) = User::find_by_id(db, id).await else { let Some(user) = User::find_by_id(db, id).await else {
return Flash::error( return Flash::error(
Redirect::to("/admin/schnupper"), Redirect::to("/admin/schnupper"),
format!("user id not found"), "user id not found".to_string(),
); );
}; };
if !user.has_role(db, "schnupperant").await { if !user.has_role(db, "schnupperant").await {
return Flash::error( return Flash::error(
Redirect::to("/admin/schnupper"), Redirect::to("/admin/schnupper"),
format!("kein schnupperant..."), "kein schnupperant...".to_string(),
); );
} }

View File

@ -11,7 +11,7 @@ use crate::model::{
log::Log, log::Log,
trip::{self, CoxHelpError, Trip, TripDeleteError, TripHelpDeleteError, TripUpdateError}, trip::{self, CoxHelpError, Trip, TripDeleteError, TripHelpDeleteError, TripUpdateError},
tripdetails::{TripDetails, TripDetailsToAdd}, tripdetails::{TripDetails, TripDetailsToAdd},
user::CoxUser, user::{AllowedToUpdateTripToAlwaysBeShownUser, CoxUser},
}; };
#[post("/trip", data = "<data>")] #[post("/trip", data = "<data>")]
@ -42,7 +42,6 @@ struct EditTripForm<'r> {
max_people: i32, max_people: i32,
notes: Option<&'r str>, notes: Option<&'r str>,
trip_type: Option<i64>, trip_type: Option<i64>,
always_show: bool,
is_locked: bool, is_locked: bool,
} }
@ -60,7 +59,6 @@ async fn update(
max_people: data.max_people, max_people: data.max_people,
notes: data.notes, notes: data.notes,
trip_type: data.trip_type, trip_type: data.trip_type,
always_show: data.always_show,
is_locked: data.is_locked, is_locked: data.is_locked,
}; };
match Trip::update_own(db, &update).await { match Trip::update_own(db, &update).await {
@ -80,6 +78,23 @@ async fn update(
} }
} }
#[get("/trip/<trip_id>/toggle-always-show")]
async fn toggle_always_show(
db: &State<SqlitePool>,
trip_id: i64,
_user: AllowedToUpdateTripToAlwaysBeShownUser,
) -> Flash<Redirect> {
if let Some(trip) = Trip::find_by_id(db, trip_id).await {
trip.toggle_always_show(db).await;
Flash::success(
Redirect::to("/planned"),
"'Immer anzeigen' erfolgreich gesetzt!",
)
} else {
Flash::error(Redirect::to("/planned"), "Ausfahrt gibt's nicht")
}
}
#[get("/join/<planned_event_id>")] #[get("/join/<planned_event_id>")]
async fn join(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) -> Flash<Redirect> { async fn join(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) -> Flash<Redirect> {
if let Some(planned_event) = Event::find_by_id(db, planned_event_id).await { if let Some(planned_event) = Event::find_by_id(db, planned_event_id).await {
@ -164,7 +179,14 @@ async fn remove(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) ->
} }
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![create, join, remove, remove_trip, update] routes![
create,
join,
remove,
remove_trip,
update,
toggle_always_show
]
} }
#[cfg(test)] #[cfg(test)]

View File

@ -131,13 +131,13 @@ async fn new_blogpost(
blogpost: Form<NewBlogpostForm<'_>>, blogpost: Form<NewBlogpostForm<'_>>,
config: &State<Config>, config: &State<Config>,
) -> String { ) -> String {
if blogpost.pw == &config.wordpress_key { if blogpost.pw == config.wordpress_key {
let member = Role::find_by_name(&db, "Donau Linz").await.unwrap(); let member = Role::find_by_name(db, "Donau Linz").await.unwrap();
Notification::create_for_role( Notification::create_for_role(
db, db,
&member, &member,
&urlencoding::decode(blogpost.article_title).expect("UTF-8"), &urlencoding::decode(blogpost.article_title).expect("UTF-8"),
&format!("Neuer Blogpost"), "Neuer Blogpost",
Some(blogpost.article_url), Some(blogpost.article_url),
None, None,
) )
@ -160,9 +160,9 @@ async fn blogpost_unpublished(
blogpost: Form<BlogpostUnpublishedForm<'_>>, blogpost: Form<BlogpostUnpublishedForm<'_>>,
config: &State<Config>, config: &State<Config>,
) -> String { ) -> String {
if blogpost.pw == &config.wordpress_key { if blogpost.pw == config.wordpress_key {
Notification::delete_by_link( Notification::delete_by_link(
&db, db,
&urlencoding::decode(blogpost.article_url).expect("UTF-8"), &urlencoding::decode(blogpost.article_url).expect("UTF-8"),
) )
.await; .await;

View File

@ -37,6 +37,10 @@ async fn index(
context.insert("flash", &msg.into_inner()); context.insert("flash", &msg.into_inner());
} }
context.insert(
"allowed_to_update_always_show_trip",
&user.allowed_to_update_always_show_trip(db).await,
);
context.insert("fee", &user.fee(db).await); context.insert("fee", &user.fee(db).await);
context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await); context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await);
context.insert("days", &days); context.insert("days", &days);
@ -99,6 +103,10 @@ async fn join(
Redirect::to("/planned"), Redirect::to("/planned"),
"Du darfst keine Gäste hinzufügen.", "Du darfst keine Gäste hinzufügen.",
), ),
Err(UserTripError::NotVisibleToUser) => Flash::error(
Redirect::to("/planned"),
"Du kannst dich nicht registrieren, weil du die Ausfahrt gar nicht sehen solltest.",
),
Err(UserTripError::DetailsLocked) => Flash::error( Err(UserTripError::DetailsLocked) => Flash::error(
Redirect::to("/planned"), Redirect::to("/planned"),
"Die Bootseinteilung wurde bereits gemacht. Wenn du noch mitrudern möchtest, frag bitte bei einer angemeldeten Steuerperson nach, ob das noch möglich ist.", "Die Bootseinteilung wurde bereits gemacht. Wenn du noch mitrudern möchtest, frag bitte bei einer angemeldeten Steuerperson nach, ob das noch möglich ist.",
@ -147,6 +155,10 @@ async fn remove_guest(
Err(UserTripDeleteError::GuestNotParticipating) => { Err(UserTripDeleteError::GuestNotParticipating) => {
Flash::error(Redirect::to("/planned"), "Gast nicht angemeldet.") Flash::error(Redirect::to("/planned"), "Gast nicht angemeldet.")
} }
Err(UserTripDeleteError::NotVisibleToUser) => Flash::error(
Redirect::to("/planned"),
"Du kannst dich nicht abmelden, weil du die Ausfahrt gar nicht sehen solltest.",
),
Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error( Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error(
Redirect::to("/planned"), Redirect::to("/planned"),
"Keine Berechtigung um den Gast zu entfernen.", "Keine Berechtigung um den Gast zu entfernen.",
@ -191,6 +203,18 @@ async fn remove(
Flash::error(Redirect::to("/planned"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") Flash::error(Redirect::to("/planned"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
} }
Err(UserTripDeleteError::NotVisibleToUser) => {
Log::create(
db,
format!(
"User {} tried to unregister for not-yet-seeable trip_details.id={}",
user.name, trip_details_id
),
)
.await;
Flash::error(Redirect::to("/planned"), "Abmeldung nicht möglich, da du dieses Event eigentlich gar nicht sehen solltest...")
}
Err(_) => { Err(_) => {
panic!("Not possible to be here"); panic!("Not possible to be here");
} }

View File

@ -5,7 +5,6 @@
{{ macros::input(label='Startzeit (zB "10:00")', name='planned_starting_time', type='time', required=true) }} {{ macros::input(label='Startzeit (zB "10:00")', name='planned_starting_time', type='time', required=true) }}
{{ macros::input(label='Anzahl Ruderer (ohne Steuerperson)', name='max_people', type='number', required=true, min='0') }} {{ macros::input(label='Anzahl Ruderer (ohne Steuerperson)', name='max_people', type='number', required=true, min='0') }}
{{ macros::checkbox(label='Scheckbuch-Anmeldungen erlauben', name='allow_guests') }} {{ macros::checkbox(label='Scheckbuch-Anmeldungen erlauben', name='allow_guests') }}
{{ macros::checkbox(label='Immer anzeigen', name='always_show') }}
{{ macros::input(label='Anmerkungen', name='notes', type='input') }} {{ macros::input(label='Anmerkungen', name='notes', type='input') }}
{{ macros::select(label='Typ', data=trip_types, name='trip_type', default='Reguläre Ausfahrt') }} {{ macros::select(label='Typ', data=trip_types, name='trip_type', default='Reguläre Ausfahrt') }}
<input value="Erstellen" class="w-full btn btn-primary" type="submit" /> <input value="Erstellen" class="w-full btn btn-primary" type="submit" />

View File

@ -67,7 +67,8 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div id="{{ day.day| date(format="%Y-%m-%d") }}" class="bg-white dark:bg-primary-900 rounded-md flex justify-between flex-col shadow reset-js" <div id="{{ day.day| date(format="%Y-%m-%d") }}"
class="bg-white dark:bg-primary-900 rounded-md flex justify-between flex-col shadow reset-js"
style="min-height: 10rem" style="min-height: 10rem"
data-trips="{{ amount_trips }}" data-trips="{{ amount_trips }}"
data-month="{{ day.day| date(format='%m') }}" data-month="{{ day.day| date(format='%m') }}"
@ -346,7 +347,6 @@
<form action="/cox/trip/{{ trip.id }}" method="post" class="grid gap-3"> <form action="/cox/trip/{{ trip.id }}" method="post" class="grid gap-3">
{{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=trip.max_people, min=trip.rower | length) }} {{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=trip.max_people, min=trip.rower | length) }}
{{ macros::input(label='Anmerkungen', name='notes', type='input', value=trip.notes) }} {{ macros::input(label='Anmerkungen', name='notes', type='input', value=trip.notes) }}
{{ macros::checkbox(label='Immer anzeigen', name='always_show', id=trip.id,checked=trip.always_show) }}
{{ macros::checkbox(label='Gesperrt', name='is_locked', id=trip.id,checked=trip.is_locked) }} {{ macros::checkbox(label='Gesperrt', name='is_locked', id=trip.id,checked=trip.is_locked) }}
{{ macros::select(label='Typ', name='trip_type', data=trip_types, default='Reguläre Ausfahrt', selected_id=trip.trip_type_id) }} {{ macros::select(label='Typ', name='trip_type', data=trip_types, default='Reguläre Ausfahrt', selected_id=trip.trip_type_id) }}
<input value="Speichern" class="btn btn-primary" type="submit" /> <input value="Speichern" class="btn btn-primary" type="submit" />
@ -369,7 +369,6 @@
<form action="/cox/trip/{{ trip.id }}" method="post" class="grid"> <form action="/cox/trip/{{ trip.id }}" method="post" class="grid">
{{ macros::input(label='', name='max_people', type='hidden', value=0) }} {{ macros::input(label='', name='max_people', type='hidden', value=0) }}
{{ macros::input(label='Grund der Absage', name='notes', type='input', value='') }} {{ macros::input(label='Grund der Absage', name='notes', type='input', value='') }}
{{ macros::input(label='', name='always_show', type='hidden', value=trip.always_show) }}
{{ macros::input(label='', name='is_locked', type='hidden', value=trip.is_locked) }} {{ macros::input(label='', name='is_locked', type='hidden', value=trip.is_locked) }}
{{ macros::input(label='', name='trip_type', type='hidden', value=trip.trip_type_id) }} {{ macros::input(label='', name='trip_type', type='hidden', value=trip.trip_type_id) }}
<input value="Ausfahrt absagen" class="btn btn-alert" type="submit" /> <input value="Ausfahrt absagen" class="btn btn-alert" type="submit" />
@ -379,6 +378,20 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{# --- END Edit Form --- #} {# --- END Edit Form --- #}
{# --- START Admin Form --- #}
{% if allowed_to_update_always_show_trip %}
<div class="bg-gray-100 dark:bg-primary-900 p-3 mt-4 rounded-md">
<h3 class="text-primary-950 dark:text-white font-bold uppercase tracking-wide mb-2">Admin-Modus</h3>
<form action="/cox/trip/{{ trip.id }}/toggle-always-show"
method="get"
class="grid gap-3">
<input value="{% if trip.always_show %}Normal anzeigen{% else %}Immer anzeigen{% endif %}"
class="btn btn-primary"
type="submit" />
</form>
</div>
{% endif %}
{# --- END Admin Form --- #}
</div> </div>
</div> </div>
{# --- END Sidebar Content --- #} {# --- END Sidebar Content --- #}