Merge branch 'staging' into 'main'

Staging

See merge request PhilippHofer/rot!40
This commit is contained in:
PhilippHofer 2023-10-24 06:31:21 +00:00
commit 9087fa9453
13 changed files with 120 additions and 62 deletions

View File

@ -28,6 +28,7 @@ pub struct BoatDamageWithDetails {
user_fixed: Option<User>, user_fixed: Option<User>,
user_verified: Option<User>, user_verified: Option<User>,
boat: Boat, boat: Boat,
verified: bool,
} }
#[derive(Debug)] #[derive(Debug)]
@ -102,6 +103,7 @@ ORDER BY created_at DESC
.await .await
.unwrap(), .unwrap(),
user_fixed, user_fixed,
verified: user_verified.is_some(),
user_verified, user_verified,
boat_damage, boat_damage,
}); });

View File

@ -33,7 +33,7 @@ WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?)
//TODO: Check if rower is allowed to row //TODO: Check if rower is allowed to row
sqlx::query!( sqlx::query!(
"INSERT INTO rower(logbook_id, rower_id) VALUES (?,?)", "INSERT INTO rower(logbook_id, rower_id) VALUES (?,?);",
logbook_id, logbook_id,
rower_id rower_id
) )

View File

@ -43,7 +43,7 @@ async fn delete(db: &State<SqlitePool>, _admin: AdminUser, boat: i32) -> Flash<R
boat.delete(db).await; boat.delete(db).await;
Flash::success( Flash::success(
Redirect::to("/admin/boat"), Redirect::to("/admin/boat"),
format!("Sucessfully deleted boat {}", boat.name), format!("Boot {} gelöscht", boat.name),
) )
} }
None => Flash::error(Redirect::to("/admin/boat"), "Boat does not exist"), None => Flash::error(Redirect::to("/admin/boat"), "Boat does not exist"),
@ -63,7 +63,7 @@ async fn update(
}; };
match boat.update(db, data.into_inner()).await { match boat.update(db, data.into_inner()).await {
Ok(_) => Flash::success(Redirect::to("/admin/boat"), "Successfully updated boat"), Ok(_) => Flash::success(Redirect::to("/admin/boat"), "Boot bearbeitet"),
Err(e) => Flash::error(Redirect::to("/admin/boat"), e), Err(e) => Flash::error(Redirect::to("/admin/boat"), e),
} }
} }
@ -75,7 +75,7 @@ async fn create(
_admin: AdminUser, _admin: AdminUser,
) -> Flash<Redirect> { ) -> Flash<Redirect> {
match Boat::create(db, data.into_inner()).await { match Boat::create(db, data.into_inner()).await {
Ok(_) => Flash::success(Redirect::to("/admin/boat"), "Successfully created boat"), Ok(_) => Flash::success(Redirect::to("/admin/boat"), "Boot hinzugefügt"),
Err(e) => Flash::error(Redirect::to("/admin/boat"), e), Err(e) => Flash::error(Redirect::to("/admin/boat"), e),
} }
} }
@ -153,7 +153,7 @@ mod test {
.get("_flash") .get("_flash")
.expect("Expected flash cookie"); .expect("Expected flash cookie");
assert_eq!(flash_cookie.value(), "7:successSuccessfully updated boat"); assert_eq!(flash_cookie.value(), "7:successBoot bearbeitet");
let boat = Boat::find_by_id(&db, 1).await.unwrap(); let boat = Boat::find_by_id(&db, 1).await.unwrap();
assert_eq!(boat.name, "Haichiii"); assert_eq!(boat.name, "Haichiii");
@ -267,7 +267,7 @@ mod test {
.get("_flash") .get("_flash")
.expect("Expected flash cookie"); .expect("Expected flash cookie");
assert_eq!(flash_cookie.value(), "7:successSuccessfully created boat"); assert_eq!(flash_cookie.value(), "7:successBoot hinzugefügt");
Boat::find_by_name(&db, "completely-new-boat".into()) Boat::find_by_name(&db, "completely-new-boat".into())
.await .await

View File

@ -36,7 +36,7 @@ async fn create(
PlannedEvent::create(db, data.name, data.planned_amount_cox, trip_details).await; PlannedEvent::create(db, data.name, data.planned_amount_cox, trip_details).await;
Flash::success(Redirect::to("/"), "Successfully planned the event") Flash::success(Redirect::to("/"), "Event hinzugefügt")
} }
//TODO: add constraints (e.g. planned_amount_cox > 0) //TODO: add constraints (e.g. planned_amount_cox > 0)
@ -79,7 +79,7 @@ async fn delete(db: &State<SqlitePool>, id: i64, _admin: AdminUser) -> Flash<Red
match PlannedEvent::find_by_id(db, id).await { match PlannedEvent::find_by_id(db, id).await {
Some(planned_event) => { Some(planned_event) => {
planned_event.delete(db).await; planned_event.delete(db).await;
Flash::success(Redirect::to("/"), "Successfully deleted the event") Flash::success(Redirect::to("/"), "Event gelöscht")
} }
None => Flash::error(Redirect::to("/"), "PlannedEvent does not exist"), None => Flash::error(Redirect::to("/"), "PlannedEvent does not exist"),
} }
@ -127,10 +127,7 @@ mod test {
.get("_flash") .get("_flash")
.expect("Expected flash cookie"); .expect("Expected flash cookie");
assert_eq!( assert_eq!(flash_cookie.value(), "7:successEvent gelöscht");
flash_cookie.value(),
"7:successSuccessfully deleted the event"
);
let event = PlannedEvent::find_by_id(&db, 1).await; let event = PlannedEvent::find_by_id(&db, 1).await;
assert_eq!(event, None); assert_eq!(event, None);
@ -265,10 +262,7 @@ mod test {
.get("_flash") .get("_flash")
.expect("Expected flash cookie"); .expect("Expected flash cookie");
assert_eq!( assert_eq!(flash_cookie.value(), "7:successEvent hinzugefügt");
flash_cookie.value(),
"7:successSuccessfully planned the event"
);
let event = PlannedEvent::find_by_id(&db, 2).await.unwrap(); let event = PlannedEvent::find_by_id(&db, 2).await.unwrap();
assert_eq!(event.name, "my-cool-new-event"); assert_eq!(event.name, "my-cool-new-event");

View File

@ -35,7 +35,7 @@ async fn resetpw(db: &State<SqlitePool>, _admin: AdminUser, user: i32) -> Flash<
user.reset_pw(db).await; user.reset_pw(db).await;
Flash::success( Flash::success(
Redirect::to("/admin/user"), Redirect::to("/admin/user"),
format!("Successfully reset pw of {}", user.name), format!("Passwort von {} zurückgesetzt", user.name),
) )
} }
None => Flash::error(Redirect::to("/admin/user"), "User does not exist"), None => Flash::error(Redirect::to("/admin/user"), "User does not exist"),
@ -50,7 +50,7 @@ async fn delete(db: &State<SqlitePool>, _admin: AdminUser, user: i32) -> Flash<R
user.delete(db).await; user.delete(db).await;
Flash::success( Flash::success(
Redirect::to("/admin/user"), Redirect::to("/admin/user"),
format!("Sucessfully deleted user {}", user.name), format!("Benutzer {} gelöscht", user.name),
) )
} }
None => Flash::error(Redirect::to("/admin/user"), "User does not exist"), None => Flash::error(Redirect::to("/admin/user"), "User does not exist"),

View File

@ -11,7 +11,6 @@ use rocket::{
FromForm, Request, Route, State, FromForm, Request, Route, State,
}; };
use rocket_dyn_templates::{context, tera, Template}; use rocket_dyn_templates::{context, tera, Template};
use serde_json::json;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::model::{ use crate::model::{

View File

@ -26,7 +26,7 @@ async fn index_kiosk(
) -> Template { ) -> Template {
let boatdamages = BoatDamage::all(db).await; let boatdamages = BoatDamage::all(db).await;
let boats = Boat::all(db).await; let boats = Boat::all(db).await;
let coxes = User::cox(db).await; let user = User::all(db).await;
let mut context = Context::new(); let mut context = Context::new();
if let Some(msg) = flash { if let Some(msg) = flash {
@ -35,7 +35,8 @@ async fn index_kiosk(
context.insert("boatdamages", &boatdamages); context.insert("boatdamages", &boatdamages);
context.insert("boats", &boats); context.insert("boats", &boats);
context.insert("coxes", &coxes); context.insert("user", &user);
context.insert("show_kiosk_header", &true);
Template::render("boatdamages", context.into_json()) Template::render("boatdamages", context.into_json())
} }
@ -132,7 +133,7 @@ async fn fixed<'r>(
user_id_fixed: coxuser.id as i32, user_id_fixed: coxuser.id as i32,
}; };
match boatdamage.fixed(db, boatdamage_fixed).await { match boatdamage.fixed(db, boatdamage_fixed).await {
Ok(_) => Flash::success(Redirect::to("/boatdamage"), "Successfully fixed the boat."), Ok(_) => Flash::success(Redirect::to("/boatdamage"), "Bootsschaden behoben."),
Err(e) => Flash::error(Redirect::to("/boatdamage"), format!("Error: {e}")), Err(e) => Flash::error(Redirect::to("/boatdamage"), format!("Error: {e}")),
} }
} }
@ -157,7 +158,7 @@ async fn verified<'r>(
match boatdamage.verified(db, boatdamage_verified).await { match boatdamage.verified(db, boatdamage_verified).await {
Ok(_) => Flash::success( Ok(_) => Flash::success(
Redirect::to("/boatdamage"), Redirect::to("/boatdamage"),
"Successfully verified the boat.", "Bootsschaden verifiziert",
), ),
Err(e) => Flash::error(Redirect::to("/boatdamage"), format!("Error: {e}")), Err(e) => Flash::error(Redirect::to("/boatdamage"), format!("Error: {e}")),
} }

View File

@ -90,7 +90,7 @@ async fn show(db: &State<SqlitePool>, user: User) -> Template {
async fn show_kiosk(db: &State<SqlitePool>, _kiosk: KioskCookie) -> Template { async fn show_kiosk(db: &State<SqlitePool>, _kiosk: KioskCookie) -> Template {
let logs = Logbook::completed(db).await; let logs = Logbook::completed(db).await;
Template::render("log.completed", context!(logs)) Template::render("log.completed", context!(logs, show_kiosk_header: true))
} }
#[get("/kiosk/ekrv2019/<loc>")] #[get("/kiosk/ekrv2019/<loc>")]
@ -148,6 +148,7 @@ async fn kiosk(
context.insert("logtypes", &logtypes); context.insert("logtypes", &logtypes);
context.insert("on_water", &on_water); context.insert("on_water", &on_water);
context.insert("distances", &distances); context.insert("distances", &distances);
context.insert("show_kiosk_header", &true);
Template::render("kiosk", context.into_json()) Template::render("kiosk", context.into_json())
} }
@ -207,11 +208,11 @@ async fn home_logbook(
}; };
match logbook.home(db, user, data.into_inner()).await { match logbook.home(db, user, data.into_inner()).await {
Ok(_) => Flash::success(Redirect::to("/log"), "Successfully updated log"), Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt korrekt eingetragen"),
Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")), Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")),
Err(_) => Flash::error( Err(_) => Flash::error(
Redirect::to("/log"), Redirect::to("/log"),
format!("Logbook with ID {} could not be updated!", logbook_id), format!("Eintrag {} konnte nicht abgesendet werden!", logbook_id),
), ),
} }
} }
@ -245,14 +246,43 @@ async fn home(
home_logbook(db, data, logbook_id, &user).await home_logbook(db, data, logbook_id, &user).await
} }
#[get("/<logbook_id>/delete")] #[get("/<logbook_id>/delete", rank = 2)]
async fn delete(db: &State<SqlitePool>, logbook_id: i32, user: User) -> Flash<Redirect> { async fn delete(db: &State<SqlitePool>, logbook_id: i32, user: User) -> Flash<Redirect> {
let logbook = Logbook::find_by_id(db, logbook_id).await; let logbook = Logbook::find_by_id(db, logbook_id).await;
if let Some(logbook) = logbook { if let Some(logbook) = logbook {
match logbook.delete(db, &user).await { match logbook.delete(db, &user).await {
Ok(_) => Flash::success( Ok(_) => Flash::success(
Redirect::to("/log"), Redirect::to("/log"),
format!("Logbook with ID {} successfully deleted!", logbook_id), format!("Eintrag {} gelöscht!", logbook_id),
),
Err(LogbookDeleteError::NotYourEntry) => Flash::error(
Redirect::to("/log"),
"Du hast nicht die Berechtigung, den Eintrag zu löschen!",
),
}
} else {
Flash::error(
Redirect::to("/log"),
format!("Logbook with ID {} could not be found!", logbook_id),
)
}
}
#[get("/<logbook_id>/delete")]
async fn delete_kiosk(
db: &State<SqlitePool>,
logbook_id: i32,
_kiosk: KioskCookie,
) -> Flash<Redirect> {
let logbook = Logbook::find_by_id(db, logbook_id).await;
if let Some(logbook) = logbook {
let cox = User::find_by_id(db, logbook.shipmaster as i32)
.await
.unwrap();
match logbook.delete(db, &cox).await {
Ok(_) => Flash::success(
Redirect::to("/log"),
format!("Eintrag {} gelöscht!", logbook_id),
), ),
Err(LogbookDeleteError::NotYourEntry) => Flash::error( Err(LogbookDeleteError::NotYourEntry) => Flash::error(
Redirect::to("/log"), Redirect::to("/log"),
@ -278,7 +308,8 @@ pub fn routes() -> Vec<Route> {
new_kiosk, new_kiosk,
show, show,
show_kiosk, show_kiosk,
delete delete,
delete_kiosk
] ]
} }
@ -471,7 +502,10 @@ mod test {
.get("_flash") .get("_flash")
.expect("Expected flash cookie"); .expect("Expected flash cookie");
assert_eq!(flash_cookie.value(), "7:successSuccessfully updated log"); assert_eq!(
flash_cookie.value(),
"7:successAusfahrt korrekt eingetragen"
);
} }
//Kiosk mode //Kiosk mode
@ -606,7 +640,10 @@ mod test {
.get("_flash") .get("_flash")
.expect("Expected flash cookie"); .expect("Expected flash cookie");
assert_eq!(flash_cookie.value(), "7:successSuccessfully updated log"); assert_eq!(
flash_cookie.value(),
"7:successAusfahrt korrekt eingetragen"
);
} }
#[sqlx::test] #[sqlx::test]
@ -796,7 +833,7 @@ mod test {
assert_eq!( assert_eq!(
flash_cookie.value(), flash_cookie.value(),
"5:errorLogbook with ID 1 could not be updated!" "5:errorEintrag 1 konnte nicht abgesendet werden!"
); );
} }
@ -843,7 +880,10 @@ mod test {
.get("_flash") .get("_flash")
.expect("Expected flash cookie"); .expect("Expected flash cookie");
assert_eq!(flash_cookie.value(), "7:successSuccessfully updated log"); assert_eq!(
flash_cookie.value(),
"7:successAusfahrt korrekt eingetragen"
);
} }
async fn cant_start_trip( async fn cant_start_trip(

View File

@ -26,7 +26,7 @@ async fn index_kiosk(db: &State<SqlitePool>, _kiosk: KioskCookie) -> Template {
let stat = Stat::get_rowed_km(db).await; let stat = Stat::get_rowed_km(db).await;
let kiosk = true; let kiosk = true;
Template::render("stat", context!(stat, kiosk)) Template::render("stat", context!(stat, kiosk, show_kiosk_header: true))
} }
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {

View File

@ -12,9 +12,24 @@
<body class="bg-gray-100"> <body class="bg-gray-100">
{% if loggedin_user %} {% if loggedin_user %}
{{ macros::header(loggedin_user=loggedin_user) }} {{ macros::header(loggedin_user=loggedin_user) }}
{% endif %} {% endif %}
{% if show_kiosk_header %}
<header class="bg-primary-900 text-white flex justify-between px-4 py-3 w-full z-10 ">
<div>
<a href="/">
</a>
</div>
<div>
<a href="/log" class="px-2">Ausfahrt eintragen</a>
<a href="/log/show" class="px-2">Logbuch</a>
<a href="/stat" class="px-2">Statistik</a>
<a href="/boatdamage" class="px-2">Bootsschaden</a>
</div>
</header>
{% endif %}
<div class="flex min-h-screen {%if not loggedin_user %} items-center {% else %} items-start {% endif %} justify-center px-4 py-12 sm:px-6 lg:px-8"> {% block content %}{% endblock content %} <div class="flex min-h-screen {%if not loggedin_user and not show_kiosk_header %} items-center {% else %} items-start {% endif %} justify-center px-4 py-12 sm:px-6 lg:px-8"> {% block content %}{% endblock content %}
</div> </div>
{% if loggedin_user %} {% if loggedin_user %}

View File

@ -27,7 +27,7 @@
<form action="/boatdamage" method="post" class="grid gap-3"> <form action="/boatdamage" method="post" class="grid gap-3">
{{ log::boat_select(only_ones=false, id='boat') }} {{ log::boat_select(only_ones=false, id='boat') }}
{% if not loggedin_user %} {% if not loggedin_user %}
{{ macros::select(label='Gemeldet von', data=coxes, name='user_id') }} {{ macros::select(label='Gemeldet von', data=user, name='user_id') }}
{% endif %} {% endif %}
{{ macros::input(label='Beschreibung des Schadens', name='desc', type='text', required=true, wrapper_class='col-span-4') }} {{ macros::input(label='Beschreibung des Schadens', name='desc', type='text', required=true, wrapper_class='col-span-4') }}
<div class="col-span-4"> <div class="col-span-4">
@ -45,9 +45,9 @@
<div id="filter-result-js" class="bg-gray-200 text-primary-950 pb-3 px-3 text-right"></div> <div id="filter-result-js" class="bg-gray-200 text-primary-950 pb-3 px-3 text-right"></div>
{% for boatdamage in boatdamages %} {% for boatdamage in boatdamages | sort(attribute="verified") %}
<div data-filterable="true" data-filter="{{ boatdamage.boat.name }} {{ boatdamage.user_created.name }}" class="w-full border-t bg-white p-3 {% if boatdamage.verified_at %} opacity-50 {% endif %}"> <div data-filterable="true" data-filter="{{ boatdamage.boat.name }} {{ boatdamage.user_created.name }}" class="w-full border-t bg-white p-3 {% if boatdamage.verified_at %} opacity-50 {% endif %}">
<div> <div class="w-full">
<strong>{{ boatdamage.created_at | date(format='%d.%m.%Y') }} <span class="font-normal text-gray-600">({{ boatdamage.boat.name }})</span></strong>{% if boatdamage.boat.damage %}<small class="block text-gray-600">(Boot gesperrt)</small>{% endif %} <strong>{{ boatdamage.created_at | date(format='%d.%m.%Y') }} <span class="font-normal text-gray-600">({{ boatdamage.boat.name }})</span></strong>{% if boatdamage.boat.damage %}<small class="block text-gray-600">(Boot gesperrt)</small>{% endif %}
<div>{{ boatdamage.desc }}</div> <div>{{ boatdamage.desc }}</div>
<small class="block text-gray-600"> <small class="block text-gray-600">
@ -58,12 +58,12 @@
<small class="block text-gray-600">Repariert von {{ boatdamage.user_fixed.name }} am/um {{ boatdamage.fixed_at | date(format='%d.%m.%Y (%H:%M)') }}</small> <small class="block text-gray-600">Repariert von {{ boatdamage.user_fixed.name }} am/um {{ boatdamage.fixed_at | date(format='%d.%m.%Y (%H:%M)') }}</small>
{% else %} {% else %}
{% if loggedin_user.is_cox %} {% if loggedin_user.is_cox %}
<form action="/boatdamage/{{ boatdamage.id }}/fixed" method="post" class="mt-3"> <form action="/boatdamage/{{ boatdamage.id }}/fixed" method="post" class="flex justify-between mt-3">
<input type="text" name="desc" value="{{ boatdamage.desc }}" /> <input type="text" name="desc" value="{{ boatdamage.desc }}" class="grow input rounded-s" />
{% if loggedin_user.is_tech %} {% if loggedin_user.is_tech %}
<input type="submit" class="btn btn-primary" value="Repariert und verifiziert" /> <input type="submit" class="btn btn-primary" style="border-top-left-radius: 0; border-bottom-left-radius: 0;" value="Repariert und verifiziert" />
{% else %} {% else %}
<input type="submit" class="btn btn-primary" value="Repariert" /> <input type="submit" class="btn btn-primary" style="border-top-left-radius: 0; border-bottom-left-radius: 0;" value="Repariert" />
{% endif %} {% endif %}
</form> </form>
{% endif %} {% endif %}
@ -73,9 +73,9 @@
<small class="block text-gray-600">Verifziert von {{ boatdamage.user_verified.name }} am/um {{ boatdamage.verified_at | date(format='%d.%m.%Y (%H:%M)') }}</small> <small class="block text-gray-600">Verifziert von {{ boatdamage.user_verified.name }} am/um {{ boatdamage.verified_at | date(format='%d.%m.%Y (%H:%M)') }}</small>
{% else %} {% else %}
{% if loggedin_user.is_tech and boatdamage.fixed_at %} {% if loggedin_user.is_tech and boatdamage.fixed_at %}
<form action="/boatdamage/{{ boatdamage.id }}/verified" method="post" class="mt-3"> <form action="/boatdamage/{{ boatdamage.id }}/verified" method="post" class="flex justify-between mt-3">
<input type="text" name="desc" value="{{ boatdamage.desc }}" /> <input type="text" name="desc" value="{{ boatdamage.desc }}" class="grow input rounded-s"/>
<input type="submit" class="btn btn-dark" value="Verifiziert" /> <input type="submit" class="btn btn-dark" style="border-top-left-radius: 0; border-bottom-left-radius: 0;" value="Verifiziert" />
</form> </form>
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@ -162,25 +162,31 @@
</div> </div>
{% endif %} {% endif %}
<div id="log{{ log.id }}"> <div id="log{{ log.id }}">
{% if log.destination %} {% if log.destination %}
{{ log.destination }} {{ log.destination }}
{% endif %} {% endif %}
{% for user in users %} {% for user in users %}
{% if user.id == log.shipmaster %} {% if user.id == log.shipmaster %}
<p> <p>
<strong>{{ user.name }}</strong> <strong>{{ user.name }}</strong>
</p> </p>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% for rower in log.rowers %} {% for rower in log.rowers %}
<p>{{ rower.name }}</p> <p>{{ rower.name }}</p>
{% endfor %} {% endfor %}
{% if allowed_to_close and state == "on_water" %} {% set amount_rowers = log.rowers | length %}
<a href="/log/{{ log.id }}/delete" onclick="return confirm('Willst du diesen Eintrag wirklich löschen? Die Daten gehen verloren :O');">LÖSCHEN</a> {% set amount_guests = log.boat.amount_seats - amount_rowers -1 %}
{% if amount_guests > 0 %}
Gäste (ohne Account): {{ amount_guests }}
{% endif %} {% endif %}
{% if allowed_to_close and state == "on_water" %}
<a href="/log/{{ log.id }}/delete" class="btn btn-alert w-full absolute bottom-0 left-0" style="border-radius: 0;" onclick="return confirm('Willst du diesen Eintrag wirklich löschen? Die Daten gehen verloren');">Löschen</a>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -235,8 +241,7 @@
{{ rower.name }}{% if not loop.last or amount_guests > 0 %}, {% endif %} {{ rower.name }}{% if not loop.last or amount_guests > 0 %}, {% endif %}
{% endfor %} {% endfor %}
{% if amount_guests > 0 %} {% if amount_guests > 0 %}
{{ amount_guests }} Gäste (ohne Account): {{ amount_guests }}
Gäste (ohne Account)
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}

View File

@ -9,7 +9,9 @@
<h1 class="h1">Logbuch</h1> <h1 class="h1">Logbuch</h1>
{% if flash %} {% if flash %}
{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }} <div class="pt-3 max-w-lg m-auto">
{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}
</div>
{% endif %} {% endif %}
<div class="w-full grid md:grid-cols-5 gap-3 mt-5"> <div class="w-full grid md:grid-cols-5 gap-3 mt-5">