dev/finalFixes #81

Merged
tom_trgr merged 14 commits from dev/finalFixes into main 2026-03-01 10:11:45 +01:00
4 changed files with 220 additions and 154 deletions
Showing only changes of commit 2d8747c971 - Show all commits

View File

@@ -36,6 +36,7 @@ type Booking struct {
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
CounterId int `json:"counter_id"` CounterId int `json:"counter_id"`
BookingType BookingType `json:"anwesenheit_typ"` BookingType BookingType `json:"anwesenheit_typ"`
Valid bool `json:"valid"`
} }
type IDatabase interface { type IDatabase interface {
@@ -252,12 +253,13 @@ func (b *Booking) Update(nb Booking) {
func checkLastBooking(b Booking) bool { func checkLastBooking(b Booking) bool {
var check_in_out int var check_in_out int
slog.Info("Checking with timestamp:", "timestamp", b.Timestamp.String()) slog.Info("Checking with timestamp:", "timestamp", b.Timestamp.String())
stmt, err := DB.Prepare((`SELECT check_in_out FROM "anwesenheit" WHERE "card_uid" = $1 AND "timestamp"::DATE <= $2::DATE ORDER BY "timestamp" DESC LIMIT 1;`)) stmt, err := DB.Prepare((`SELECT check_in_out FROM "anwesenheit" WHERE "card_uid" = $1 AND "timestamp" <= $2 ORDER BY "timestamp" DESC LIMIT 1;`))
if err != nil { if err != nil {
log.Fatalf("Error preparing query: %v", err) log.Fatalf("Error preparing query: %v", err)
return false return false
} }
err = stmt.QueryRow(b.CardUID, b.Timestamp).Scan(&check_in_out) err = stmt.QueryRow(b.CardUID, b.Timestamp).Scan(&check_in_out)
slog.Info("Checking last bookings check_in_out", "Check", check_in_out)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return true return true
} }

View File

@@ -186,97 +186,148 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
var workSec, pauseSec float64 var workSec, pauseSec float64
qStr, err := DB.Prepare(` qStr, err := DB.Prepare(`
WITH all_days AS ( WITH
SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date), all_days AS (
normalized_bookings AS ( SELECT
SELECT * generate_series(
FROM ( $2 ::DATE,
SELECT $3 ::DATE - INTERVAL '1 day',
a.card_uid, INTERVAL '1 day'
a.timestamp, )::DATE AS work_date
a.timestamp::DATE AS work_date, ),
a.check_in_out, all_bookings AS (
a.counter_id, SELECT
a.anwesenheit_typ, a.card_uid,
sat.anwesenheit_name AS anwesenheit_typ_name, a.timestamp,
LAG(a.check_in_out) OVER ( a.timestamp::DATE AS work_date,
PARTITION BY a.card_uid, a.timestamp::DATE a.check_in_out,
ORDER BY a.timestamp a.counter_id,
) AS prev_check a.anwesenheit_typ,
FROM anwesenheit a sat.anwesenheit_name AS anwesenheit_typ_name,
LEFT JOIN s_anwesenheit_typen sat LAG(a.check_in_out) OVER (
ON a.anwesenheit_typ = sat.anwesenheit_id PARTITION BY
WHERE a.card_uid = $1 a.card_uid,
AND a.timestamp::DATE >= $2 a.timestamp::DATE
AND a.timestamp::DATE <= $3 ORDER BY
) t a.timestamp
WHERE prev_check IS NULL OR prev_check <> check_in_out ) AS prev_check,
), LAG(a.timestamp) OVER (
ordered_bookings AS ( PARTITION BY
SELECT a.card_uid,
*, a.timestamp::DATE
LAG(timestamp) OVER ( ORDER BY
PARTITION BY card_uid, work_date a.timestamp
ORDER BY timestamp ) AS prev_timestamp
) AS prev_timestamp FROM
FROM normalized_bookings anwesenheit a
) LEFT JOIN s_anwesenheit_typen sat ON a.anwesenheit_typ = sat.anwesenheit_id
SELECT WHERE
d.work_date, a.card_uid = $1
COALESCE(MIN(b.timestamp), NOW()) AS time_from, AND a.timestamp::DATE >= $2::DATE
COALESCE(MAX(b.timestamp), NOW()) AS time_to, AND a.timestamp::DATE <= $3::DATE
COALESCE( ),
EXTRACT(EPOCH FROM SUM( normalized_bookings AS (
CASE SELECT
WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 254) *
THEN b.timestamp - b.prev_timestamp FROM
ELSE INTERVAL '0' all_bookings
END WHERE
)), 0 prev_check IS NULL
) AS total_work_seconds, OR prev_check <> check_in_out
COALESCE( )
EXTRACT(EPOCH FROM SUM( SELECT
CASE d.work_date,
WHEN b.prev_check IN (2, 4, 254) AND b.check_in_out IN (1, 3) COALESCE(MIN(b.timestamp), NOW()) AS time_from,
THEN b.timestamp - b.prev_timestamp COALESCE(MAX(b.timestamp), NOW()) AS time_to,
ELSE INTERVAL '0' EXTRACT(
END EPOCH
)), 0 FROM
) AS total_pause_seconds, SUM(
COALESCE(jsonb_agg(jsonb_build_object( CASE
'check_in_out', b.check_in_out, WHEN b.prev_check IN (1, 3)
'timestamp', b.timestamp, AND b.check_in_out IN (2, 4, 254) THEN b.timestamp - b.prev_timestamp
'counter_id', b.counter_id, ELSE INTERVAL '0'
'anwesenheit_typ', b.anwesenheit_typ, END
'anwesenheit_typ', jsonb_build_object( )
'anwesenheit_id', b.anwesenheit_typ, ) AS total_work_seconds,
'anwesenheit_name', b.anwesenheit_typ_name EXTRACT(
) EPOCH
) ORDER BY b.timestamp), '[]'::jsonb) AS bookings FROM
FROM all_days d SUM(
LEFT JOIN ordered_bookings b ON d.work_date = b.work_date CASE
GROUP BY d.work_date WHEN b.prev_check IN (2, 4, 254)
ORDER BY d.work_date ASC;`) AND b.check_in_out IN (1, 3) THEN b.timestamp - b.prev_timestamp
ELSE INTERVAL '0'
END
)
) AS total_pause_seconds,
jsonb_agg(
jsonb_build_object(
'check_in_out',
b.check_in_out,
'valid',
coalesce(b.check_in_out != b.prev_check, true),
'timestamp',
b.timestamp,
'counter_id',
b.counter_id,
'anwesenheit_typ',
jsonb_build_object(
'anwesenheit_id',
b.anwesenheit_typ,
'anwesenheit_name',
b.anwesenheit_typ_name
)
)
ORDER BY
b.timestamp
) FILTER (
WHERE
b.card_uid IS NOT NULL
) AS bookings
FROM
all_days d
LEFT JOIN all_bookings b ON b.work_date = d.work_date
GROUP BY
d.work_date;
`)
// qStr, err := DB.Prepare(` // qStr, err := DB.Prepare(`
// WITH all_days AS ( // WITH all_days AS (
// SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date), // SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date),
// ordered_bookings AS ( // normalized_bookings AS (
// SELECT // SELECT *
// a.timestamp::DATE AS work_date, // FROM (
// a.timestamp, // SELECT
// a.check_in_out, // a.card_uid,
// a.counter_id, // a.timestamp,
// a.anwesenheit_typ, // a.timestamp::DATE AS work_date,
// sat.anwesenheit_name AS anwesenheit_typ_name, // a.check_in_out,
// LAG(a.timestamp) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_timestamp, // a.counter_id,
// LAG(a.check_in_out) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_check // a.anwesenheit_typ,
// FROM anwesenheit a // sat.anwesenheit_name AS anwesenheit_typ_name,
// LEFT JOIN s_anwesenheit_typen sat ON a.anwesenheit_typ = sat.anwesenheit_id // LAG(a.check_in_out) OVER (
// WHERE a.card_uid = $1 // PARTITION BY a.card_uid, a.timestamp::DATE
// AND a.timestamp::DATE >= $2 // ORDER BY a.timestamp
// AND a.timestamp::DATE <= $3 // ) AS prev_check
// ) // FROM anwesenheit a
// LEFT JOIN s_anwesenheit_typen sat
// ON a.anwesenheit_typ = sat.anwesenheit_id
// WHERE a.card_uid = $1
// AND a.timestamp::DATE >= $2
// AND a.timestamp::DATE <= $3
// ) t
// WHERE prev_check IS NULL OR prev_check <> check_in_out
// ),
// ordered_bookings AS (
// SELECT
// *,
// LAG(timestamp) OVER (
// PARTITION BY card_uid, work_date
// ORDER BY timestamp
// ) AS prev_timestamp
// FROM normalized_bookings
// )
// SELECT // SELECT
// d.work_date, // d.work_date,
// COALESCE(MIN(b.timestamp), NOW()) AS time_from, // COALESCE(MIN(b.timestamp), NOW()) AS time_from,
@@ -330,26 +381,29 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
var workDay WorkDay var workDay WorkDay
var bookings []byte var bookings []byte
if err := rows.Scan(&workDay.Day, &workDay.TimeFrom, &workDay.TimeTo, &workSec, &pauseSec, &bookings); err != nil { if err := rows.Scan(&workDay.Day, &workDay.TimeFrom, &workDay.TimeTo, &workSec, &pauseSec, &bookings); err != nil {
log.Println("Error scanning row!", err) slog.Error("Error scanning row!", "Error", err)
return workDays return workDays
} }
workDay.workTime = time.Duration(workSec * float64(time.Second)) workDay.workTime = time.Duration(workSec * float64(time.Second))
workDay.pauseTime = time.Duration(pauseSec * float64(time.Second)) workDay.pauseTime = time.Duration(pauseSec * float64(time.Second))
err = json.Unmarshal(bookings, &workDay.Bookings) if bookings != nil {
if err != nil { err = json.Unmarshal(bookings, &workDay.Bookings)
log.Println("Error parsing bookings JSON!", err) if err != nil {
return nil slog.Error("Error parsing bookings JSON!", "Error", err, "Json", bookings)
return nil
}
} }
// better empty day handling // better empty day handling
if len(workDay.Bookings) == 1 && workDay.Bookings[0].CounterId == 0 { // if len(workDay.Bookings) == 1 && workDay.Bookings[0].CounterId == 0 {
workDay.Bookings = []Booking{} // workDay.Bookings = []Booking{}
} // }
if len(workDay.Bookings) >= 1 || !helper.IsWeekend(workDay.Date()) { if len(workDay.Bookings) >= 1 || !helper.IsWeekend(workDay.Date()) {
workDays = append(workDays, workDay) workDays = append(workDays, workDay)
} }
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
log.Println("Error in workday rows!", err) slog.Error("Error in workday rows!", "Error", err)
return workDays return workDays
} }
return workDays return workDays

View File

@@ -205,9 +205,15 @@
.top-0 { .top-0 {
top: calc(var(--spacing) * 0); top: calc(var(--spacing) * 0);
} }
.top-1 {
top: calc(var(--spacing) * 1);
}
.top-1\/2 { .top-1\/2 {
top: calc(1/2 * 100%); top: calc(1/2 * 100%);
} }
.top-2 {
top: calc(var(--spacing) * 2);
}
.top-2\.5 { .top-2\.5 {
top: calc(var(--spacing) * 2.5); top: calc(var(--spacing) * 2.5);
} }
@@ -217,9 +223,15 @@
.right-1 { .right-1 {
right: calc(var(--spacing) * 1); right: calc(var(--spacing) * 1);
} }
.right-2 {
right: calc(var(--spacing) * 2);
}
.right-2\.5 { .right-2\.5 {
right: calc(var(--spacing) * 2.5); right: calc(var(--spacing) * 2.5);
} }
.left-1 {
left: calc(var(--spacing) * 1);
}
.left-1\/2 { .left-1\/2 {
left: calc(1/2 * 100%); left: calc(1/2 * 100%);
} }
@@ -350,6 +362,9 @@
.block { .block {
display: block; display: block;
} }
.contents {
display: contents;
}
.flex { .flex {
display: flex; display: flex;
} }
@@ -383,6 +398,9 @@
.h-2 { .h-2 {
height: calc(var(--spacing) * 2); height: calc(var(--spacing) * 2);
} }
.h-3 {
height: calc(var(--spacing) * 3);
}
.h-3\.5 { .h-3\.5 {
height: calc(var(--spacing) * 3.5); height: calc(var(--spacing) * 3.5);
} }
@@ -407,6 +425,9 @@
.w-2 { .w-2 {
width: calc(var(--spacing) * 2); width: calc(var(--spacing) * 2);
} }
.w-3 {
width: calc(var(--spacing) * 3);
}
.w-3\.5 { .w-3\.5 {
width: calc(var(--spacing) * 3.5); width: calc(var(--spacing) * 3.5);
} }
@@ -416,6 +437,9 @@
.w-5 { .w-5 {
width: calc(var(--spacing) * 5); width: calc(var(--spacing) * 5);
} }
.w-9 {
width: calc(var(--spacing) * 9);
}
.w-9\/10 { .w-9\/10 {
width: calc(9/10 * 100%); width: calc(9/10 * 100%);
} }
@@ -428,6 +452,9 @@
.w-full { .w-full {
width: 100%; width: 100%;
} }
.flex-shrink {
flex-shrink: 1;
}
.flex-shrink-0 { .flex-shrink-0 {
flex-shrink: 0; flex-shrink: 0;
} }
@@ -443,10 +470,21 @@
.basis-\[content\] { .basis-\[content\] {
flex-basis: content; flex-basis: content;
} }
.border-collapse {
border-collapse: collapse;
}
.-translate-x-1 {
--tw-translate-x: calc(var(--spacing) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-x-1\/2 { .-translate-x-1\/2 {
--tw-translate-x: calc(calc(1/2 * 100%) * -1); --tw-translate-x: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
} }
.-translate-y-1 {
--tw-translate-y: calc(var(--spacing) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 { .-translate-y-1\/2 {
--tw-translate-y: calc(calc(1/2 * 100%) * -1); --tw-translate-y: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -457,33 +495,21 @@
.cursor-pointer { .cursor-pointer {
cursor: pointer; cursor: pointer;
} }
.resize {
resize: both;
}
.scroll-m-2 { .scroll-m-2 {
scroll-margin: calc(var(--spacing) * 2); scroll-margin: calc(var(--spacing) * 2);
} }
.appearance-none { .appearance-none {
appearance: none; appearance: none;
} }
.break-after-page {
break-after: page;
}
.auto-rows-min {
grid-auto-rows: min-content;
}
.grid-cols-2 { .grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.grid-cols-5 { .grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr)); grid-template-columns: repeat(5, minmax(0, 1fr));
} }
.grid-cols-\[3fr_2fr_2fr_2fr_3fr_3fr_3fr\] {
grid-template-columns: 3fr 2fr 2fr 2fr 3fr 3fr 3fr;
}
.grid-cols-subgrid {
grid-template-columns: subgrid;
}
.grid-rows-6 {
grid-template-rows: repeat(6, minmax(0, 1fr));
}
.flex-col { .flex-col {
flex-direction: column; flex-direction: column;
} }
@@ -534,11 +560,6 @@
border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
} }
} }
.divide-neutral-300 {
:where(& > :not(:last-child)) {
border-color: var(--color-neutral-300);
}
}
.justify-self-end { .justify-self-end {
justify-self: flex-end; justify-self: flex-end;
} }
@@ -565,18 +586,10 @@
border-style: var(--tw-border-style); border-style: var(--tw-border-style);
border-width: 0px; border-width: 0px;
} }
.border-r-0 {
border-right-style: var(--tw-border-style);
border-right-width: 0px;
}
.border-r-1 { .border-r-1 {
border-right-style: var(--tw-border-style); border-right-style: var(--tw-border-style);
border-right-width: 1px; border-right-width: 1px;
} }
.border-b-0 {
border-bottom-style: var(--tw-border-style);
border-bottom-width: 0px;
}
.border-dashed { .border-dashed {
--tw-border-style: dashed; --tw-border-style: dashed;
border-style: dashed; border-style: dashed;
@@ -587,9 +600,6 @@
.border-neutral-500 { .border-neutral-500 {
border-color: var(--color-neutral-500); border-color: var(--color-neutral-500);
} }
.border-neutral-600 {
border-color: var(--color-neutral-600);
}
.border-slate-800 { .border-slate-800 {
border-color: var(--color-slate-800); border-color: var(--color-slate-800);
} }
@@ -614,15 +624,15 @@
.bg-red-600 { .bg-red-600 {
background-color: var(--color-red-600); background-color: var(--color-red-600);
} }
.mask-repeat {
mask-repeat: repeat;
}
.p-1 { .p-1 {
padding: calc(var(--spacing) * 1); padding: calc(var(--spacing) * 1);
} }
.p-2 { .p-2 {
padding: calc(var(--spacing) * 2); padding: calc(var(--spacing) * 2);
} }
.p-8 {
padding: calc(var(--spacing) * 8);
}
.px-3 { .px-3 {
padding-inline: calc(var(--spacing) * 3); padding-inline: calc(var(--spacing) * 3);
} }
@@ -635,10 +645,6 @@
.text-center { .text-center {
text-align: center; text-align: center;
} }
.text-2xl {
font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
}
.text-sm { .text-sm {
font-size: var(--text-sm); font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height)); line-height: var(--tw-leading, var(--text-sm--line-height));
@@ -654,15 +660,15 @@
.whitespace-nowrap { .whitespace-nowrap {
white-space: nowrap; white-space: nowrap;
} }
.\!text-red-500 {
color: var(--color-red-500) !important;
}
.text-accent { .text-accent {
color: var(--color-accent); color: var(--color-accent);
} }
.text-black { .text-black {
color: var(--color-black); color: var(--color-black);
} }
.text-neutral-300 {
color: var(--color-neutral-300);
}
.text-neutral-500 { .text-neutral-500 {
color: var(--color-neutral-500); color: var(--color-neutral-500);
} }
@@ -690,9 +696,16 @@
.uppercase { .uppercase {
text-transform: uppercase; text-transform: uppercase;
} }
.underline {
text-decoration-line: underline;
}
.opacity-0 { .opacity-0 {
opacity: 0%; opacity: 0%;
} }
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.filter { .filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
} }
@@ -719,18 +732,6 @@
-webkit-user-select: none; -webkit-user-select: none;
user-select: none; user-select: none;
} }
.\*\:text-center {
:is(& > *) {
text-align: center;
}
}
.\*\:not-print\:p-2 {
:is(& > *) {
@media not print {
padding: calc(var(--spacing) * 2);
}
}
}
.group-hover\:text-black { .group-hover\:text-black {
&:is(:where(.group):hover *) { &:is(:where(.group):hover *) {
@media (hover: hover) { @media (hover: hover) {
@@ -1024,7 +1025,7 @@
border-width: 1px; border-width: 1px;
border-color: var(--color-neutral-800); border-color: var(--color-neutral-800);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
transition-timing-function: var( --tw-ease, var(--default-transition-timing-function) ); transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration)); transition-duration: var(--tw-duration, var(--default-transition-duration));
} }
input.btn, select.btn { input.btn, select.btn {
@@ -1138,6 +1139,11 @@
syntax: "*"; syntax: "*";
inherits: false; inherits: false;
} }
@property --tw-outline-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-blur { @property --tw-blur {
syntax: "*"; syntax: "*";
inherits: false; inherits: false;
@@ -1210,6 +1216,7 @@
--tw-border-style: solid; --tw-border-style: solid;
--tw-divide-y-reverse: 0; --tw-divide-y-reverse: 0;
--tw-font-weight: initial; --tw-font-weight: initial;
--tw-outline-style: solid;
--tw-blur: initial; --tw-blur: initial;
--tw-brightness: initial; --tw-brightness: initial;
--tw-contrast: initial; --tw-contrast: initial;

View File

@@ -77,10 +77,13 @@ templ newBookingComponent(d models.IWorkDay) {
templ bookingComponent(booking models.Booking) { templ bookingComponent(booking models.Booking) {
<div> <div>
<p class="text-neutral-500 edit-box"> <p class={ "text-neutral-500 edit-box", templ.KV("text-red-500", !booking.Valid) }>
<span class="text-black group-[.edit]:hidden inline">{ booking.Timestamp.Format("15:04") }</span> <span class="text-black group-[.edit]:hidden inline">{ booking.Timestamp.Format("15:04") }</span>
<input disabled name={ "booking_" + strconv.Itoa(booking.CounterId) } type="time" value={ booking.Timestamp.Format("15:04") } class="text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer"/> <input disabled name={ "booking_" + strconv.Itoa(booking.CounterId) } type="time" value={ booking.Timestamp.Format("15:04") } class="text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer"/>
{ booking.GetBookingType() } { booking.GetBookingType() }
if !booking.Valid {
fehlerhafte Buchung, wird nicht zur Berechnung verwendet!
}
</p> </p>
if booking.IsSubmittedAndChecked() { if booking.IsSubmittedAndChecked() {
<p>submitted</p> <p>submitted</p>
@@ -89,10 +92,10 @@ templ bookingComponent(booking models.Booking) {
} }
templ workdayComponent(workDay *models.WorkDay) { templ workdayComponent(workDay *models.WorkDay) {
if len(workDay.Bookings) < 1 { if workDay.IsEmpty() && !workDay.IsKurzArbeit() {
<p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p> <p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p>
} else { } else {
if workDay.IsKurzArbeit() && len(workDay.Bookings) > 0 { if workDay.IsKurzArbeit() {
@absenceComponent(workDay.GetKurzArbeit(), true) @absenceComponent(workDay.GetKurzArbeit(), true)
} }
for _, booking := range workDay.Bookings { for _, booking := range workDay.Bookings {