39 Commits

Author SHA1 Message Date
fdda0ea669 moved migrations
All checks were successful
Tests / Run Go Tests (push) Successful in 2m7s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 2m45s
2026-01-18 22:50:51 +01:00
c10ab98997 fixed problem, where migrate could not connect to db
Some checks failed
Arbeitszeitmessung Deploy / Build Webserver (push) Failing after 1m14s
Tests / Run Go Tests (push) Successful in 1m48s
2026-01-18 22:42:07 +01:00
8dc8c4eed3 working to fix orangepi db connect
Some checks failed
Tests / Run Go Tests (push) Failing after 30s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 2m11s
2026-01-18 21:34:11 +01:00
3f49da49b6 ad hoc fix
All checks were successful
Tests / Run Go Tests (push) Successful in 2m24s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 2m52s
2026-01-18 20:43:38 +01:00
18b2cbc074 Merge pull request 'dev/fix-70' (#71) from dev/fix-70 into main
Some checks failed
Tests / Run Go Tests (push) Failing after 48s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 3m35s
Reviewed-on: #71
2026-01-18 18:11:00 +01:00
560c539b19 fixed minor bugs + added loggin middleware
All checks were successful
Tests / Run Go Tests (push) Successful in 1m26s
2026-01-18 00:07:54 +01:00
502955d32f added migrations back + removed distracting log message 2026-01-17 22:53:35 +01:00
cfd77ae28d fixed #70 + made db script ignore double bookings
Some checks failed
Tests / Run Go Tests (push) Failing after 2m0s
2026-01-17 22:25:25 +01:00
1daf4db167 fixed premission problem after making migrations executed by go
All checks were successful
Tests / Run Go Tests (push) Successful in 1m44s
2026-01-17 21:41:46 +01:00
3322f7e9bc updated install script with cron jobs
All checks were successful
Tests / Run Go Tests (push) Successful in 1m35s
2026-01-07 19:55:00 +01:00
4b9824c714 fixed sonarqube errors
All checks were successful
Tests / Run Go Tests (push) Successful in 2m28s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 3m15s
2026-01-05 12:19:40 +01:00
7ac6c5f9b8 Merge pull request 'dev/pdf' (#69) from dev/pdf into main
Some checks failed
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 4m43s
Tests / Run Go Tests (push) Failing after 3m44s
Reviewed-on: #69
2026-01-05 12:14:46 +01:00
f9fc3d91d1 improved logging + fixed error from log folder
Some checks failed
Tests / Run Go Tests (push) Failing after 3m7s
2026-01-05 12:13:49 +01:00
f0de9961dc removed doc-creator 2026-01-05 12:13:24 +01:00
4ded8632e5 fixed overtime calc issue
Some checks failed
Arbeitszeitmessung Deploy / Build Document Creator (push) Successful in 2m30s
Tests / Run Go Tests (push) Failing after 2m35s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 3m19s
2026-01-05 00:39:00 +01:00
b2af48463c updated git action to build right image
Some checks failed
Arbeitszeitmessung Deploy / Build Document Creator (push) Successful in 2m11s
Tests / Run Go Tests (push) Failing after 2m46s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 3m5s
2026-01-05 00:02:33 +01:00
0b72147e02 Merge pull request 'version 2.0.0 rc' (#68) from dev/feiertage into main
Some checks failed
Tests / Run Go Tests (push) Failing after 2m29s
Arbeitszeitmessung Deploy / Build Document Creator (push) Successful in 4m1s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 4m4s
Reviewed-on: #68
2026-01-04 23:56:04 +01:00
d1b46cf894 fixed sonarqube errors
Some checks failed
Tests / Run Go Tests (push) Failing after 1m33s
2026-01-04 23:53:16 +01:00
7c45ef6112 minor fixes in pdf generator + new booking input select
Some checks failed
Tests / Run Go Tests (push) Failing after 3m30s
Arbeitszeitmessung Deploy / Build Webserver (push) Successful in 4m33s
Arbeitszeitmessung Deploy / Build Document Creator (push) Successful in 4m32s
2026-01-04 23:30:09 +01:00
2b17eb6854 updates for presentation + starting update for newbooking component 2026-01-02 22:24:17 +01:00
7fae75be75 small refactor
Some checks failed
Tests / Run Go Tests (push) Failing after 1m26s
2025-12-24 23:35:19 +01:00
b7de3ade65 fixed #65, #67
Some checks failed
Tests / Run Go Tests (push) Failing after 1m31s
2025-12-24 23:20:57 +01:00
855cd6f34f working compound days + working public holidays
Some checks failed
Tests / Run Go Tests (push) Failing after 44s
2025-12-24 01:38:16 +01:00
3439fff841 added feiertage to database + endpoint to insert every year 2025-12-23 12:21:03 +01:00
f562ef2a33 added public holidays + updated templ to v0.3.960
Some checks failed
Tests / Run Go Tests (push) Failing after 1m33s
2025-12-19 09:15:58 +01:00
tom
177fbdeb3f adding feiertage in db
Some checks failed
Tests / Run Go Tests (push) Failing after 2m11s
2025-12-16 07:02:52 +01:00
tom
82eb8018a6 updated pdf renderer to support zipped output
Some checks failed
Tests / Run Go Tests (push) Failing after 1m47s
2025-12-16 07:02:17 +01:00
tom
0eb4878c90 updated pdf form to send user to pdf generator
All checks were successful
Tests / Run Go Tests (push) Successful in 3m28s
Arbeitszeitmessung Deploy / Build Go Image and Upload (push) Successful in 4m24s
2025-12-12 15:07:57 +01:00
tom
c7f8595474 updated github workflow for document creator
All checks were successful
Tests / Run Go Tests (push) Successful in 1m55s
2025-12-12 14:28:49 +01:00
fcec748293 Merge pull request 'refactor + added kurzarbeit to pdf export' (#66) from dev/broken into dev/pdf
All checks were successful
Tests / Run Go Tests (push) Successful in 1m57s
Reviewed-on: #66
2025-12-12 14:17:16 +01:00
tom
a1b225478a fixed sonarqube issue
All checks were successful
Tests / Run Go Tests (push) Successful in 1m45s
2025-12-12 14:13:24 +01:00
588bf908c6 fixed tests
Some checks failed
Tests / Run Go Tests (push) Failing after 1m37s
2025-12-12 12:58:41 +01:00
76b23133d0 fixed #61, #62 refactored getTime variants
Some checks failed
Tests / Run Go Tests (push) Failing after 1m20s
2025-12-12 12:26:40 +01:00
1ccc19b85c removed and refactored virtual and real worktime 2025-12-12 06:31:03 +01:00
f73c2b1a96 tried to add untertags krank
Some checks failed
Tests / Run Go Tests (push) Failing after 2m3s
2025-12-05 15:03:44 +01:00
a6ea625e8f upped test coverage of helpers
Some checks failed
Tests / Run Go Tests (push) Failing after 1m27s
2025-12-03 00:38:26 +01:00
386f11ec7e fixed #63 all scheduled tasks are now under route /auto and will require api key in the future
Some checks failed
Tests / Run Go Tests (push) Failing after 1m32s
2025-12-03 00:20:11 +01:00
6c0a8bca64 added tests
All checks were successful
Tests / Run Go Tests (push) Successful in 2m5s
2025-12-02 17:10:17 +01:00
6e238c4532 update badge
All checks were successful
Tests / Run Go Tests (push) Successful in 1m28s
2025-12-02 16:57:10 +01:00
88 changed files with 3693 additions and 2230 deletions

View File

@@ -4,55 +4,14 @@ on:
push: push:
tags: tags:
- "*" - "*"
branches:
- main
- dev/main
jobs: jobs:
testing: webserver:
name: Run Go Tests name: Build Webserver
runs-on: ubuntu-latest runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: password
POSTGRES_DB: arbeitszeitmessung
env:
POSTGRES_HOST: postgres
POSTGRES_USER: root
POSTGRES_PASSWORD: password
POSTGRES_DB: arbeitszeitmessung
POSTGRES_PORT: 5432
RUNNER_TOOL_CACHE: /toolcache # Runner Tool Cache
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup go
uses: actions/setup-go@v5
with:
go-version-file: Backend/go.mod
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-go
with:
patterns: |
go.mod
go.sum
- name: cache go
id: cache-go
uses: actions/cache@v4
with:
path: |-
/go_path
/go_cache
key: arbeitszeitmessung-${{ steps.hash-go.outputs.hash }}
restore-keys: |-
arbeitszeitmessung-
- name: Run Go Tests
run: cd Backend && go test ./...
build:
name: Build Go Image and Upload
runs-on: ubuntu-latest
needs: [testing]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -66,12 +25,49 @@ jobs:
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: git.letsstein.de/tom/arbeitszeitmessung-webserver
tags: |
type=raw,value=latest
type=pep440,pattern={{version}}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
context: Backend context: Backend
tags: | tags: ${{ steps.meta.outputs.tags }}
git.letsstein.de/tom/arbeitszeitmessung:latest # document-creator:
git.letsstein.de/tom/arbeitszeitmessung:${{ github.ref_name }} # name: Build Document Creator
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# - name: Login to GitHub Container Registry
# uses: docker/login-action@v3
# with:
# registry: git.letsstein.de
# username: ${{ gitea.actor }}
# password: ${{ secrets.REGISTRY_TOKEN }}
# - name: Set up QEMU
# uses: docker/setup-qemu-action@v3
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
# - name: Extract metadata (tags, labels) for Docker
# id: meta
# uses: docker/metadata-action@v5
# with:
# images: git.letsstein.de/tom/arbeitszeitmessung-doc-creator
# tags: |
# type=raw,value=latest
# type=pep440,pattern={{version}}
# - name: Build and push
# uses: docker/build-push-action@v6
# with:
# platforms: linux/amd64,linux/arm64
# push: true
# context: DocumentCreator
# tags: ${{ steps.meta.outputs.tags }}

1
.gitignore vendored
View File

@@ -39,3 +39,4 @@ node_modules
atlas.hcl atlas.hcl
.scannerwork .scannerwork
Backend/logs Backend/logs
.worktime.txt

View File

@@ -14,9 +14,13 @@ COPY . .
RUN go build -o server . RUN go build -o server .
FROM alpine:3.22 FROM alpine:3.22
RUN apk add --no-cache tzdata RUN apk add --no-cache tzdata typst
WORKDIR /app WORKDIR /app
COPY --from=build /app/server /app/server COPY --from=build /app/server /app/server
COPY migrations /app/migrations
COPY doc /doc
COPY /static /app/static COPY /static /app/static
ENTRYPOINT ["./server"] ENTRYPOINT ["./server"]

View File

@@ -2,5 +2,16 @@ test:
mkdir -p .test mkdir -p .test
go test ./... -coverprofile=.test/coverage.out -json > .test/report.json go test ./... -coverprofile=.test/coverage.out -json > .test/report.json
scan: # scan:
sonar-scanner -Dsonar.token=sqa_ca8394c93a728d6cff96703955288d8902c15200 # sonar-scanner -Dsonar.token=sqa_ca8394c93a728d6cff96703955288d8902c15200
# complete live run
live:
make -j2 live/templ live/tailwindcss
live/templ:
templ generate --watch --proxy="http://localhost:8080" --cmd="go run ." --open-browser=false
live/tailwindcss:
npx --yes tailwindcss -i ./src/main.css -o ./static/css/styles.css --watch
#--minify

View File

@@ -5,7 +5,12 @@ import (
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"database/sql" "database/sql"
"fmt" "fmt"
"log/slog"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
@@ -16,6 +21,45 @@ func OpenDatabase() (models.IDatabase, error) {
dbPassword := helper.GetEnv("POSTGRES_API_PASS", "password") dbPassword := helper.GetEnv("POSTGRES_API_PASS", "password")
dbTz := helper.GetEnv("TZ", "Europe/Berlin") dbTz := helper.GetEnv("TZ", "Europe/Berlin")
connStr := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable&TimeZone=%s", dbUser, dbPassword, dbHost, dbName, dbTz) connStr := fmt.Sprintf(
"host=%s user=%s dbname=%s password=%s sslmode=disable TimeZone=%s",
dbHost, dbUser, dbName, dbPassword, dbTz)
return sql.Open("postgres", connStr) return sql.Open("postgres", connStr)
} }
func Migrate() error {
dbHost := helper.GetEnv("POSTGRES_HOST", "localhost")
dbName := helper.GetEnv("POSTGRES_DB", "arbeitszeitmessung")
dbPassword := helper.GetEnv("POSTGRES_PASSWORD", "password")
dbTz := helper.GetEnv("TZ", "Europe/Berlin")
connStr := fmt.Sprintf(
"host=%s user=%s dbname=%s password=%s sslmode=disable TimeZone=%s",
dbHost, "migrate", dbName, dbPassword, dbTz)
db, err := sql.Open("postgres", connStr)
if err != nil {
return err
}
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
return err
}
m, err := migrate.NewWithDatabaseInstance("file:///app/migrations", "postgres", driver)
if err != nil {
return err
}
slog.Info("Connected to database. Running migrations now.")
// Migrate all the way up ...
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
return err
}
slog.Info("Finished migrations starting webserver.")
return nil
}

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -11,7 +11,7 @@
columns: (3fr, .65fr), columns: (3fr, .65fr),
align: left + horizon, align: left + horizon,
inset: .5em, inset: .5em,
[#meta.EmployeeName -- #meta.TimeRange], grid.cell(rowspan: 2)[#image("static/logo.png")], [#meta.EmployeeName -- #meta.TimeRange], grid.cell(rowspan: 2)[#image("/static/logo.png")],
[Arbeitszeitrechnung maschinell erstellt am #meta.CurrentTimestamp], [Arbeitszeitrechnung maschinell erstellt am #meta.CurrentTimestamp],
) )
]) ])
@@ -37,42 +37,47 @@
[Zeitraum: #meta.TimeRange] [Zeitraum: #meta.TimeRange]
table( table(
columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1.25fr), columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, .875fr, 1.25fr),
fill: (x, y) => fill: (x, y) =>
if y == 0 { oklch(87%, 0, 0deg) }, if y == 0 { oklch(87%, 0, 0deg) },
table-header( table-header(
[Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Pause], [Überstunden] [Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Kurzarbeit], [Pause], [Überstunden]
), ),
.. for day in days { .. for day in days {
( (
[#day.Date], [#day.Date],
if day.DayParts.len() == 0{ if day.DayParts.len() == 0{
table.cell(colspan: 3)[Keine Buchungen] table.cell(colspan: 3)[Keine Buchungen]
}else if not day.DayParts.first().IsWorkDay{ }else if day.DayParts.len() == 1 and not day.DayParts.first().IsWorkDay{
table.cell(colspan: 3)[#day.DayParts.first().WorkType] table.cell(colspan: 3)[#day.DayParts.first().WorkType]
} }
else { else {
table.cell(colspan: 3, inset: 0em)[ table.cell(colspan: 3, inset: 0em)[
#table( #table(
columns: (1fr, 1fr, 1fr), columns: (1fr, 1fr, 1fr),
.. for Zeit in day.DayParts { .. for Zeit in day.DayParts {
( (
[#Zeit.BookingFrom], if Zeit.IsWorkDay{
[#Zeit.BookingTo], (
[#Zeit.WorkType], table.cell()[#Zeit.BookingFrom],
table.cell()[#Zeit.BookingTo],
table.cell()[#Zeit.WorkType],
)
}else{
(table.cell(colspan: 3)[#Zeit.WorkType],)
}
) )
}, },
) )
] ]
}, },
[#day.Worktime], [#day.Worktime],
[#day.Kurzarbeit],
[#day.Pausetime], [#day.Pausetime],
[#day.Overtime], [#day.Overtime],
) )
if day.IsFriday { if day.IsFriday {
( table.cell(colspan: 7, fill: oklch(87%, 0, 0deg))[Wochenende], ) // note the trailing comma ( table.cell(colspan: 8, fill: oklch(87%, 0, 0deg))[Wochenende], ) // note the trailing comma
} }
} }
) )
@@ -84,9 +89,9 @@
stroke: none, stroke: none,
table.hline(start: 0, end: 2, stroke: stroke(dash:"dashed", thickness:.5pt)), table.hline(start: 0, end: 2, stroke: stroke(dash:"dashed", thickness:.5pt)),
[Arbeitszeit :], table.cell(align: left)[#meta.WorkTime], [Arbeitszeit :], table.cell(align: left)[#meta.WorkTime],
[Kurzarbeit :], table.cell(align: left)[#meta.Kurzarbeit],
[Überstunden :], table.cell(align: left)[#meta.Overtime], [Überstunden :], table.cell(align: left)[#meta.Overtime],
[Überstunden :],table.cell(align: left)[#meta.OvertimeTotal], [Überstunden lfd. :],table.cell(align: left)[#meta.OvertimeTotal],
table.hline(start: 0, end: 2), table.hline(start: 0, end: 2),
) )
} }

View File

@@ -0,0 +1,43 @@
package endpoints
import (
"arbeitszeitmessung/helper/paramParser"
"arbeitszeitmessung/models"
"log/slog"
"net/http"
"time"
"github.com/wlbr/feiertage"
)
func FeiertagsHandler(w http.ResponseWriter, r *http.Request) {
pp := paramParser.New(r.URL.Query())
slog.Debug("Generating Holidays")
from := pp.ParseTimestampFallback("from", "2006", time.Now().AddDate(-1, 0, -time.Now().YearDay()))
to := pp.ParseTimestampFallback("to", "2006", time.Now().AddDate(0, 0, -time.Now().YearDay()+1))
var publicHolidays map[string]models.PublicHoliday = make(map[string]models.PublicHoliday)
yearDelta := to.Year() - from.Year()
var holidays = feiertage.Sachsen(to.Year(), true)
for _, f := range holidays.Feiertage {
publicHolidays[f.Time.Format(time.DateOnly)] = models.NewHolidayFromFeiertag(f)
}
repeatingHolidays, err := models.GetRepeatingHolidays(from, to.AddDate(0, 0, -1))
if err != nil {
slog.Warn("Error getting holidays", slog.Any("Error", err))
}
slog.Debug("Found repeating Holidays", "num", len(repeatingHolidays), "from", from, "to", to, "yeardelta", yearDelta)
for _, day := range repeatingHolidays {
day.Time = day.Time.AddDate(yearDelta, 0, 0)
publicHolidays[day.Date().Format(time.DateOnly)] = day
}
slog.Debug("Added repeating holidays", "num", len(holidays.Feiertage))
for _, feiertag := range publicHolidays {
slog.Debug("Found holiday", "holiday", feiertag)
if err := feiertag.Insert(); err != nil {
slog.Warn("Error inserting Feiertag", slog.Any("Error", err))
}
}
}

View File

@@ -0,0 +1,82 @@
package endpoints
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/helper/paramParser"
"arbeitszeitmessung/models"
"encoding/json"
"errors"
"log/slog"
"net/http"
"time"
)
func KurzarbeitFillHandler(w http.ResponseWriter, r *http.Request) {
helper.SetCors(w)
switch r.Method {
case "GET":
fillKurzarbeit(r, w)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func fillKurzarbeit(r *http.Request, w http.ResponseWriter) {
bookingTypeKurzarbeit, err := getKurzarbeitBookingType()
if err != nil {
slog.Info("Error getting BookingType Kurzarbeit %v\n", slog.Any("Error", err))
}
users, err := models.GetAllUsers()
if err != nil {
slog.Info("Error getting user list %v\n", slog.Any("Error", err))
}
pp := paramParser.New(r.URL.Query())
startDate := pp.ParseTimestampFallback("date", time.DateOnly, time.Now())
var kurzarbeitAdded int
for _, user := range users {
days := models.GetDays(user, startDate, startDate.AddDate(0, 0, 1), false)
if len(days) == 0 {
continue
}
day := days[len(days)-1]
if !day.IsKurzArbeit() || !day.IsWorkDay() {
continue
}
if day.GetWorktime(user, models.WorktimeBaseDay, false) >= day.GetWorktime(user, models.WorktimeBaseDay, true) {
continue
}
worktimeKurzarbeit := day.GetWorktime(user, models.WorktimeBaseDay, true) - day.GetWorktime(user, models.WorktimeBaseDay, false)
if wDay, ok := day.(*models.WorkDay); !ok || len(wDay.Bookings) == 0 {
continue
}
workday, _ := day.(*models.WorkDay)
lastBookingTime := workday.Bookings[len(workday.Bookings)-1].Timestamp
kurzarbeitBegin := (*models.Booking).New(nil, user.CardUID, 0, 1, bookingTypeKurzarbeit.Id)
kurzarbeitEnd := (*models.Booking).New(nil, user.CardUID, 0, 2, bookingTypeKurzarbeit.Id)
kurzarbeitBegin.Timestamp = lastBookingTime.Add(time.Minute)
kurzarbeitEnd.Timestamp = lastBookingTime.Add(worktimeKurzarbeit)
kurzarbeitBegin.InsertWithTimestamp()
kurzarbeitEnd.InsertWithTimestamp()
kurzarbeitAdded += 1
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(kurzarbeitAdded)
}
func getKurzarbeitBookingType() (models.BookingType, error) {
for _, bookingType := range models.GetBookingTypesCached() {
if bookingType.Name == "Kurzarbeit" {
return bookingType, nil
}
}
return models.BookingType{}, errors.New("No Booking Type found")
}

View File

@@ -20,8 +20,8 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) {
} }
func autoLogout(w http.ResponseWriter) { func autoLogout(w http.ResponseWriter) {
users, err := (*models.User).GetAll(nil) users, err := models.GetAllUsers()
var logged_out_users []models.User var loggedOutUsers []models.User
if err != nil { if err != nil {
fmt.Printf("Error getting user list %v\n", err) fmt.Printf("Error getting user list %v\n", err)
} }
@@ -31,7 +31,7 @@ func autoLogout(w http.ResponseWriter) {
if err != nil { if err != nil {
fmt.Printf("Error logging out user %v\n", err) fmt.Printf("Error logging out user %v\n", err)
} else { } else {
logged_out_users = append(logged_out_users, user) loggedOutUsers = append(loggedOutUsers, user)
log.Printf("Automaticaly logged out user %s, %s ", user.Name, user.Vorname) log.Printf("Automaticaly logged out user %s, %s ", user.Name, user.Vorname)
} }
} }
@@ -39,6 +39,6 @@ func autoLogout(w http.ResponseWriter) {
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(logged_out_users) json.NewEncoder(w).Encode(loggedOutUsers)
} }

View File

@@ -4,6 +4,7 @@ import (
"arbeitszeitmessung/helper" "arbeitszeitmessung/helper"
"arbeitszeitmessung/helper/paramParser" "arbeitszeitmessung/helper/paramParser"
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"archive/zip"
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
@@ -15,18 +16,27 @@ import (
) )
const DE_DATE string = "02.01.2006" const DE_DATE string = "02.01.2006"
const FILE_YEAR_MONTH string = "2006_01"
func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, error) { func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, error) {
var typstDays []typstDay var typstDays []typstDay
for _, day := range days { for _, day := range days {
var thisTypstDay typstDay var thisTypstDay typstDay
work, pause, overtime := day.GetTimesVirtual(u, models.WorktimeBaseWeek) work, pause, overtime := day.GetTimes(u, models.WorktimeBaseDay, false)
workVirtual := day.GetWorktime(u, models.WorktimeBaseDay, true)
overtime = workVirtual - u.ArbeitszeitProWocheFrac(0.2)
thisTypstDay.Date = day.Date().Format(DE_DATE) thisTypstDay.Date = day.Date().Format(DE_DATE)
thisTypstDay.Worktime = helper.FormatDurationFill(work, true) thisTypstDay.Worktime = helper.FormatDurationFill(workVirtual, true)
thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true) thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true)
thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true) thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true)
thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday
if workVirtual > work {
thisTypstDay.Kurzarbeit = helper.FormatDurationFill(workVirtual-work, true)
} else {
thisTypstDay.Kurzarbeit = helper.FormatDurationFill(0, true)
}
thisTypstDay.DayParts = convertDayToTypstDayParts(day, u) thisTypstDay.DayParts = convertDayToTypstDayParts(day, u)
typstDays = append(typstDays, thisTypstDay) typstDays = append(typstDays, thisTypstDay)
} }
@@ -35,17 +45,22 @@ func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, erro
func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDayPart { func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDayPart {
var typstDayParts []typstDayPart var typstDayParts []typstDayPart
if day.IsWorkDay() { switch day.Type() {
case models.DayTypeWorkday:
workDay, _ := day.(*models.WorkDay) workDay, _ := day.(*models.WorkDay)
for i := 0; i < len(workDay.Bookings); i += 2 { for i := 0; i < len(workDay.Bookings); i += 2 {
var typstDayPart typstDayPart var typstDayPart typstDayPart
typstDayPart.BookingFrom = workDay.Bookings[i].Timestamp.Format("15:04") typstDayPart.BookingFrom = workDay.Bookings[i].Timestamp.Format("15:04")
typstDayPart.BookingTo = workDay.Bookings[i+1].Timestamp.Format("15:04") if i+1 < len(workDay.Bookings) {
typstDayPart.BookingTo = workDay.Bookings[i+1].Timestamp.Format("15:04")
} else {
typstDayPart.BookingTo = workDay.Bookings[i].Timestamp.Format("15:04")
}
typstDayPart.WorkType = workDay.Bookings[i].BookingType.Name typstDayPart.WorkType = workDay.Bookings[i].BookingType.Name
typstDayPart.IsWorkDay = true typstDayPart.IsWorkDay = true
typstDayParts = append(typstDayParts, typstDayPart) typstDayParts = append(typstDayParts, typstDayPart)
} }
if day.IsKurzArbeit() { if day.IsKurzArbeit() && len(workDay.Bookings) > 0 {
tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user) tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user)
typstDayParts = append(typstDayParts, typstDayPart{ typstDayParts = append(typstDayParts, typstDayPart{
BookingFrom: tsFrom.Format("15:04"), BookingFrom: tsFrom.Format("15:04"),
@@ -54,46 +69,16 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay
IsWorkDay: true, IsWorkDay: true,
}) })
} }
} else { case models.DayTypeCompound:
absentDay, _ := day.(*models.Absence) for _, c := range day.(*models.CompoundDay).DayParts {
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name}) typstDayParts = append(typstDayParts, convertDayToTypstDayParts(c, user)...)
}
default:
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: day.ToString()})
} }
return typstDayParts return typstDayParts
} }
func renderPDF(days []typstDay, metadata typstMetadata) (bytes.Buffer, error) {
var markup bytes.Buffer
var output bytes.Buffer
if err := typst.InjectValues(&markup, map[string]any{"meta": metadata, "days": days}); err != nil {
return output, err
}
// Import the template and invoke the template function with the custom data.
// Show is used to replace the current document with whatever content the template function in `template.typ` returns.
markup.WriteString(`
#import "templates/abrechnung.typ": abrechnung
#show: doc => abrechnung(meta, days)
`)
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
// f, err := os.Create("output.pdf")
// if err != nil {
// log.Panicf("Failed to create output file: %v.", err)
// }
// defer f.Close()
//
// typstCLI := typst.CLI{}
typstCLI := typst.DockerExec{
ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
}
if err := typstCLI.Compile(&markup, &output, nil); err != nil {
return output, err
}
return output, nil
}
func PDFCreateController(w http.ResponseWriter, r *http.Request) { func PDFCreateController(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r) helper.RequiresLogin(Session, w, r)
switch r.Method { switch r.Method {
@@ -105,108 +90,191 @@ func PDFCreateController(w http.ResponseWriter, r *http.Request) {
} }
pp := paramParser.New(r.URL.Query()) pp := paramParser.New(r.URL.Query())
startDate := pp.ParseTimestampFallback("start_date", time.DateOnly, time.Now()) startDate := pp.ParseTimestampFallback("start_date", time.DateOnly, time.Now())
var members []models.User = make([]models.User, 0) personalNumbers := pp.ParseIntListFallback("employe_list", ",", make([]int, 0))
output, err := createReports(user, members, startDate)
employes, err := models.GetUserByPersonalNrMulti(personalNumbers)
if err != nil { if err != nil {
slog.Warn("Could not create pdf report", slog.Any("Error", err)) slog.Warn("Error getting employes!", slog.Any("Error", err))
return
} }
w.Header().Set("Content-type", "application/pdf") n := 0
output.WriteTo(w) for _, e := range employes {
w.WriteHeader(200) if user.IsSuperior(e) {
employes[n] = e
n++
}
}
employes = employes[:n]
reportData := createReports(employes, startDate)
switch pp.ParseStringFallback("output", "render") {
case "render":
output, err := renderPDFSingle(reportData)
if err != nil {
slog.Warn("Could not create pdf report", slog.Any("Error", err))
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-type", "application/pdf")
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=Monatsabrechnung_%s", startDate.Format(FILE_YEAR_MONTH)))
output.WriteTo(w)
w.WriteHeader(http.StatusOK)
case "download":
pdfReports, err := renderPDFMulti(reportData)
if err != nil {
slog.Warn("Could not create pdf report", slog.Any("Error", err))
w.WriteHeader(http.StatusInternalServerError)
}
output, err := zipPfd(pdfReports, &reportData)
if err != nil {
slog.Warn("Could not create pdf report", slog.Any("Error", err))
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-type", "application/zip")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachement; filename=Monatsabrechnung_%s", startDate.Format(FILE_YEAR_MONTH)))
output.WriteTo(w)
w.WriteHeader(http.StatusOK)
}
default: default:
http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed) http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed)
} }
} }
func createReports(user models.User, employes []models.User, startDate time.Time) (bytes.Buffer, error) { func createReports(employes []models.User, startDate time.Time) []typstData {
if startDate.Day() > 1 { startDate = helper.GetFirstOfMonth(startDate)
startDate = startDate.AddDate(0, 0, -(startDate.Day() - 1))
}
endDate := startDate.AddDate(0, 1, -1) endDate := startDate.AddDate(0, 1, -1)
return createEmployeReport(user, startDate, endDate)
var employeData []typstData
for _, employee := range employes {
if data, err := createEmployeReport(employee, startDate, endDate); err != nil {
slog.Warn("Error when creating employeReport", slog.Any("user", employee), slog.Any("error", err))
} else {
employeData = append(employeData, data)
}
}
return employeData
} }
func createEmployeReport(employee models.User, startDate, endDate time.Time) (bytes.Buffer, error) { func createEmployeReport(employee models.User, startDate, endDate time.Time) (typstData, error) {
targetHours := (employee.ArbeitszeitProWoche() / 5) * time.Duration(helper.GetWorkingDays(startDate, endDate)) publicHolidays, err := models.GetHolidaysFromTo(startDate, endDate)
workingDays := models.GetDays(employee, startDate, endDate, false) targetHoursThisMonth := employee.ArbeitszeitProWocheFrac(.2) * time.Duration(helper.GetWorkingDays(startDate, endDate)-len(publicHolidays))
workDaysThisMonth := models.GetDays(employee, startDate, endDate.AddDate(0, 0, 1), false)
slog.Debug("Baseline Working hours", "targetHours", targetHours.Hours()) slog.Debug("Baseline Working hours", "targetHours", targetHoursThisMonth.Hours())
var actualHours time.Duration var workHours, kurzarbeitHours time.Duration
for _, day := range workingDays { for _, day := range workDaysThisMonth {
actualHours += day.TimeWorkVirtual(employee) tmpvirtualHours := day.GetWorktime(employee, models.WorktimeBaseDay, true)
tmpactualHours := day.GetWorktime(employee, models.WorktimeBaseDay, false)
if day.IsKurzArbeit() && tmpvirtualHours > tmpactualHours {
slog.Debug("Adding kurzarbeit to workday", "day", day.Date())
kurzarbeitHours += tmpvirtualHours - tmpactualHours
}
workHours += tmpvirtualHours
} }
worktimeBalance := actualHours - targetHours worktimeBalance := workHours - targetHoursThisMonth
typstDays, err := convertDaysToTypst(workingDays, employee) typstDays, err := convertDaysToTypst(workDaysThisMonth, employee)
if err != nil { if err != nil {
log.Panicf("Failed to convert days!") slog.Warn("Failed to convert to days", slog.Any("error", err))
return typstData{}, err
} }
metadata := typstMetadata{ metadata := typstMetadata{
EmployeeName: fmt.Sprintf("%s %s", employee.Vorname, employee.Name), EmployeeName: fmt.Sprintf("%s %s", employee.Vorname, employee.Name),
TimeRange: fmt.Sprintf("%s - %s", startDate.Format(DE_DATE), endDate.Format(DE_DATE)), TimeRange: fmt.Sprintf("%s - %s", startDate.Format(DE_DATE), endDate.Format(DE_DATE)),
Overtime: helper.FormatDurationFill(worktimeBalance, true), Overtime: helper.FormatDurationFill(worktimeBalance, true),
WorkTime: helper.FormatDurationFill(actualHours, true), WorkTime: helper.FormatDurationFill(workHours, true),
Kurzarbeit: helper.FormatDurationFill(kurzarbeitHours, true),
OvertimeTotal: "", OvertimeTotal: "",
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"), CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
} }
return typstData{Meta: metadata, Days: typstDays, FileName: fmt.Sprintf("%s_%s.pdf", startDate.Format(FILE_YEAR_MONTH), employee.Name)}, nil
return renderPDF(typstDays, metadata)
} }
func PDFHandler(w http.ResponseWriter, r *http.Request) { func renderPDFSingle(data []typstData) (bytes.Buffer, error) {
helper.RequiresLogin(Session, w, r) var markup bytes.Buffer
startDate := time.Now() var output bytes.Buffer
if startDate.Day() > 1 { typstCLI := typst.CLI{
startDate = startDate.AddDate(0, 0, -(startDate.Day() - 1)) WorkingDirectory: "/doc/",
} // ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
endDate := startDate.AddDate(0, 1, -1)
user, err := models.GetUserFromSession(Session, r.Context())
if err != nil {
log.Println("Error getting user!")
} }
//TODO: only accepted weeks if err := typst.InjectValues(&markup, map[string]any{"data": data}); err != nil {
return output, err
weeks := models.GetDays(user, startDate, endDate, false)
var aggregatedOvertime, aggregatedWorkTime time.Duration
for _, day := range weeks {
aggregatedOvertime += day.TimeOvertimeReal(user)
aggregatedWorkTime += day.TimeWorkVirtual(user)
} }
typstDays, err := convertDaysToTypst(weeks, user) // Import the template and invoke the template function with the custom data.
if err != nil { // Show is used to replace the current document with whatever content the template function in `template.typ` returns.
log.Panicf("Failed to convert days!") markup.WriteString(`
#import "templates/abrechnung.typ": abrechnung
#for d in data {
abrechnung(d.Meta, d.Days)
}
`)
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
if err := typstCLI.Compile(&markup, &output, nil); err != nil {
return output, err
} }
metadata := typstMetadata{ return output, nil
EmployeeName: fmt.Sprintf("%s %s", user.Vorname, user.Name), }
TimeRange: fmt.Sprintf("%s - %s", startDate.Format(DE_DATE), endDate.Format(DE_DATE)),
Overtime: helper.FormatDurationFill(aggregatedOvertime, true), func renderPDFMulti(data []typstData) ([]bytes.Buffer, error) {
WorkTime: helper.FormatDurationFill(aggregatedWorkTime, true), var outputMulti []bytes.Buffer
OvertimeTotal: "",
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"), typstRender := typst.CLI{
WorkingDirectory: "/doc/",
// ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
} }
output, err := renderPDF(typstDays, metadata) for _, d := range data {
if err != nil { var markup bytes.Buffer
slog.Warn("Could not create pdf report", slog.Any("Error", err)) var outputSingle bytes.Buffer
if err := typst.InjectValues(&markup, map[string]any{"meta": d.Meta, "days": d.Days}); err != nil {
return outputMulti, err
}
markup.WriteString(`
#import "templates/abrechnung.typ": abrechnung
#abrechnung(meta, days)
`)
if err := typstRender.Compile(&markup, &outputSingle, nil); err != nil {
return outputMulti, err
}
outputMulti = append(outputMulti, outputSingle)
}
return outputMulti, nil
}
func zipPfd(pdfReports []bytes.Buffer, reportData *[]typstData) (bytes.Buffer, error) {
var zipOutput bytes.Buffer
zipWriter := zip.NewWriter(&zipOutput)
for index, report := range pdfReports {
zipFile, err := zipWriter.Create((*reportData)[index].FileName)
if err != nil {
fmt.Println(err)
}
_, err = zipFile.Write(report.Bytes())
if err != nil {
fmt.Println(err)
}
} }
w.Header().Set("Content-type", "application/pdf") // Make sure to check the error on Close.
output.WriteTo(w) err := zipWriter.Close()
w.WriteHeader(200) return zipOutput, err
} }
type typstMetadata struct { type typstMetadata struct {
TimeRange string `json:"time-range"` TimeRange string `json:"time-range"`
EmployeeName string `json:"employee-name"` EmployeeName string `json:"employee-name"`
WorkTime string `json:"worktime"` WorkTime string `json:"worktime"`
Kurzarbeit string `json:"kurzarbeit"`
Overtime string `json:"overtime"` Overtime string `json:"overtime"`
OvertimeTotal string `json:"overtime-total"` OvertimeTotal string `json:"overtime-total"`
CurrentTimestamp string `json:"current-timestamp"` CurrentTimestamp string `json:"current-timestamp"`
@@ -220,10 +288,17 @@ type typstDayPart struct {
} }
type typstDay struct { type typstDay struct {
Date string `json:"date"` Date string `json:"date"`
DayParts []typstDayPart `json:"day-parts"` DayParts []typstDayPart `json:"day-parts"`
Worktime string `json:"worktime"` Worktime string `json:"worktime"`
Pausetime string `json:"pausetime"` Pausetime string `json:"pausetime"`
Overtime string `json:"overtime"` Overtime string `json:"overtime"`
IsFriday bool `json:"is-weekend"` Kurzarbeit string `json:"kurzarbeit"`
IsFriday bool `json:"is-weekend"`
}
type typstData struct {
Meta typstMetadata `json:"meta"`
Days []typstDay `json:"days"`
FileName string
} }

View File

@@ -7,6 +7,7 @@ import (
"errors" "errors"
"log" "log"
"net/http" "net/http"
"time"
) )
// Relevant for arduino inputs -> creates new Booking from get and put method // Relevant for arduino inputs -> creates new Booking from get and put method
@@ -36,6 +37,7 @@ func createBooking(w http.ResponseWriter, r *http.Request) {
} }
booking := (*models.Booking).FromUrlParams(nil, r.URL.Query()) booking := (*models.Booking).FromUrlParams(nil, r.URL.Query())
booking.Timestamp = time.Now()
if booking.Verify() { if booking.Verify() {
err := booking.Insert() err := booking.Insert()
if errors.Is(models.SameBookingError{}, err) { if errors.Is(models.SameBookingError{}, err) {

View File

@@ -37,8 +37,18 @@ func AbsencHandler(w http.ResponseWriter, r *http.Request) {
helper.SetCors(w) helper.SetCors(w)
switch r.Method { switch r.Method {
case http.MethodPost: case http.MethodPost:
err := updateAbsence(r) r.ParseForm()
var err error
switch r.FormValue("action") {
case "insert":
err = updateAbsence(r)
case "delete":
err = deleteAbsence(r)
default:
slog.Warn("No action found!")
}
if err != nil { if err != nil {
slog.Warn("Error handling absence route ", "error", err)
http.Error(w, "Internal error", http.StatusInternalServerError) http.Error(w, "Internal error", http.StatusInternalServerError)
return return
} }
@@ -84,7 +94,7 @@ func getBookings(w http.ResponseWriter, r *http.Request) {
if day.Date().Before(lastSub) { if day.Date().Before(lastSub) {
continue continue
} }
aggregatedOvertime += day.TimeOvertimeReal(user) aggregatedOvertime += day.GetOvertime(user, models.WorktimeBaseDay, true)
} }
if reportedOvertime, err := user.GetReportedOvertime(); err == nil { if reportedOvertime, err := user.GetReportedOvertime(); err == nil {
user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute) user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute)
@@ -104,6 +114,21 @@ func getBookings(w http.ResponseWriter, r *http.Request) {
templates.TimePage([]models.WorkDay{}, lastSub).Render(ctx, w) templates.TimePage([]models.WorkDay{}, lastSub).Render(ctx, w)
} }
func deleteAbsence(r *http.Request) error {
r.ParseForm()
pp := paramParser.New(r.Form)
counterId, err := pp.ParseInt("aw_id")
if err != nil {
return err
}
absence, err := models.GetAbsenceById(counterId)
if err != nil {
return err
}
return absence.Delete()
}
func updateBooking(w http.ResponseWriter, r *http.Request) { func updateBooking(w http.ResponseWriter, r *http.Request) {
r.ParseForm() r.ParseForm()
pp := paramParser.New(r.Form) pp := paramParser.New(r.Form)
@@ -135,19 +160,13 @@ func updateBooking(w http.ResponseWriter, r *http.Request) {
newBooking := (*models.Booking).New(nil, user.CardUID, 0, int16(check_in_out), 1) newBooking := (*models.Booking).New(nil, user.CardUID, 0, int16(check_in_out), 1)
newBooking.Timestamp = timestamp newBooking.Timestamp = timestamp
err = newBooking.InsertWithTimestamp() if newBooking.Verify() {
if err != nil { err = newBooking.InsertWithTimestamp()
log.Printf("Error inserting booking %v -> %v\n", newBooking, err) if err != nil {
log.Printf("Error inserting booking %v -> %v\n", newBooking, err)
}
} }
case "change": case "change":
// absenceType, err := strconv.Atoi(r.FormValue("absence"))
// if err != nil {
// log.Println("Error parsing absence type.", err)
// absenceType = 0
// }
// if absenceType != 0 {
// createAbsence(absenceType, user, loc, r)
// }
for index, possibleBooking := range r.PostForm { for index, possibleBooking := range r.PostForm {
if len(index) > 7 && index[:7] == "booking" { if len(index) > 7 && index[:7] == "booking" {
booking_id, err := strconv.Atoi(index[8:]) booking_id, err := strconv.Atoi(index[8:])

View File

@@ -4,10 +4,12 @@ go 1.24.7
require github.com/lib/pq v1.10.9 require github.com/lib/pq v1.10.9
require github.com/a-h/templ v0.3.943 require github.com/a-h/templ v0.3.960
require github.com/alexedwards/scs/v2 v2.8.0 require github.com/alexedwards/scs/v2 v2.8.0
require github.com/wlbr/feiertage v1.17.0
require ( require (
github.com/Dadido3/go-typst v0.8.0 github.com/Dadido3/go-typst v0.8.0
github.com/golang-migrate/migrate/v4 v4.18.3 github.com/golang-migrate/migrate/v4 v4.18.3

View File

@@ -4,10 +4,14 @@ github.com/Dadido3/go-typst v0.8.0 h1:uTLYprhkrBjwsCXRRuyYUFL0fpYHa2kIYoOB/CGqVN
github.com/Dadido3/go-typst v0.8.0/go.mod h1:QYis9sT70u65kn1SkFfyPRmHsPxgoxWbAixwfPReOZA= github.com/Dadido3/go-typst v0.8.0/go.mod h1:QYis9sT70u65kn1SkFfyPRmHsPxgoxWbAixwfPReOZA=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/a-h/templ v0.3.943 h1:o+mT/4yqhZ33F3ootBiHwaY4HM5EVaOJfIshvd5UNTY= github.com/a-h/templ v0.3.960 h1:trshEpGa8clF5cdI39iY4ZrZG8Z/QixyzEyUnA7feTM=
github.com/a-h/templ v0.3.943/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= github.com/a-h/templ v0.3.960/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw= github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -33,6 +37,8 @@ github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4
github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -40,6 +46,7 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@@ -59,9 +66,21 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/smasher164/xid v0.1.2 h1:erplXSdBRIIw+MrwjJ/m8sLN2XY16UGzpTA0E2Ru6HA= github.com/smasher164/xid v0.1.2 h1:erplXSdBRIIw+MrwjJ/m8sLN2XY16UGzpTA0E2Ru6HA=
github.com/smasher164/xid v0.1.2/go.mod h1:tgivm8CQl19fH1c5y+8F4mA+qY6n2i6qDRBlY/6nm+I= github.com/smasher164/xid v0.1.2/go.mod h1:tgivm8CQl19fH1c5y+8F4mA+qY6n2i6qDRBlY/6nm+I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/wlbr/feiertage v1.17.0 h1:AEck/iUQu19iU0xNEoSQTeSTGXF1Ju0tbAwEi/Lmwqk=
github.com/wlbr/feiertage v1.17.0/go.mod h1:TVZgmSZgGW/jSxexZ56qdlR6cDj+F/FO8bkw8U6kYxM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
@@ -72,19 +91,65 @@ go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt3
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -5,6 +5,7 @@ import (
"log/slog" "log/slog"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -12,6 +13,29 @@ type ParamsParser struct {
urlParams url.Values urlParams url.Values
} }
func (p ParamsParser) ParseStringListFallback(key string, delimiter string, fallback []string) []string {
if !p.urlParams.Has(key) {
return fallback
}
paramList := p.urlParams.Get(key)
list := strings.Split(paramList, delimiter)
return list
}
func (p ParamsParser) ParseIntListFallback(key string, delimiter string, fallback []int) []int {
if !p.urlParams.Has(key) {
return fallback
}
paramList := p.urlParams[key]
parsedList := make([]int, 0)
for _, item := range paramList {
if parsedItem, err := strconv.Atoi(item); err == nil {
parsedList = append(parsedList, parsedItem)
}
}
return parsedList
}
type NoValueError struct { type NoValueError struct {
Key string Key string
} }
@@ -59,7 +83,7 @@ func (p *ParamsParser) ParseStringFallback(key string, fallback string) string {
return p.urlParams.Get(key) return p.urlParams.Get(key)
} }
func (p *ParamsParser) ParseString(key string, fallback string) (string, error) { func (p *ParamsParser) ParseString(key string) (string, error) {
if !p.urlParams.Has(key) { if !p.urlParams.Has(key) {
return "", &NoValueError{Key: key} return "", &NoValueError{Key: key}
} }

View File

@@ -0,0 +1,47 @@
package helper
import "testing"
func TestGetFirst(t *testing.T) {
tests := []struct {
name string
a any
b any
want any
}{
{"ints", 10, 20, 10},
{"strings", "first", "second", "first"},
{"mixed", "abc", 123, "abc"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetFirst(tt.a, tt.b)
if got != tt.want {
t.Errorf("GetFirst(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want)
}
})
}
}
func TestGetSecond(t *testing.T) {
tests := []struct {
name string
a any
b any
want any
}{
{"ints", 10, 20, 20},
{"strings", "first", "second", "second"},
{"mixed", "abc", 123, 123},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetSecond(tt.a, tt.b)
if got != tt.want {
t.Errorf("GetSecond(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want)
}
})
}
}

View File

@@ -16,6 +16,13 @@ func GetMonday(ts time.Time) time.Time {
return ts return ts
} }
func GetFirstOfMonth(ts time.Time) time.Time {
if ts.Day() > 1 {
return ts.AddDate(0, 0, -(ts.Day() - 1))
}
return ts
}
func IsWeekend(ts time.Time) bool { func IsWeekend(ts time.Time) bool {
return ts.Weekday() == time.Saturday || ts.Weekday() == time.Sunday return ts.Weekday() == time.Saturday || ts.Weekday() == time.Sunday
} }

View File

@@ -8,13 +8,44 @@ import (
func TestGetMonday(t *testing.T) { func TestGetMonday(t *testing.T) {
isMonday, err := time.Parse(time.DateOnly, "2025-07-14") isMonday, err := time.Parse(time.DateOnly, "2025-07-14")
isSunday, err := time.Parse(time.DateOnly, "2025-07-20")
notMonday, err := time.Parse(time.DateOnly, "2025-07-16") notMonday, err := time.Parse(time.DateOnly, "2025-07-16")
if err != nil || isMonday.Equal(notMonday) { if err != nil || isMonday.Equal(notMonday) {
t.Errorf("U stupid? %e", err) t.Errorf("U stupid? %e", err)
} }
if GetMonday(isMonday) != isMonday || GetMonday(notMonday) != isMonday { if GetMonday(isMonday) != isMonday {
t.Error("Wrong date conversion!") t.Error("Wrong date conversion!")
} }
if GetMonday(notMonday) != isMonday {
t.Error("Wrong date conversion (notMonday)!")
}
if GetMonday(isSunday) != isMonday {
t.Error("Wrong date conversion (isSunday)!")
}
}
func TestFormatDurationFill(t *testing.T) {
testCases := []struct {
name string
duration time.Duration
fill bool
}{
{"2h", time.Duration(120 * time.Minute), true},
{"30min", time.Duration(30 * time.Minute), true},
{"1h 30min", time.Duration(90 * time.Minute), true},
{"-1h 30min", time.Duration(-90 * time.Minute), true},
{"0min", 0, true},
{"", 0, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if FormatDurationFill(tc.duration, tc.fill) != tc.name {
t.Error("Format missmatch in Formatduration.")
}
})
}
} }
func TestFormatDuration(t *testing.T) { func TestFormatDuration(t *testing.T) {
@@ -22,10 +53,6 @@ func TestFormatDuration(t *testing.T) {
name string name string
duration time.Duration duration time.Duration
}{ }{
{"2h", time.Duration(120 * time.Minute)},
{"30min", time.Duration(30 * time.Minute)},
{"1h 30min", time.Duration(90 * time.Minute)},
{"-1h 30min", time.Duration(-90 * time.Minute)},
{"", 0}, {"", 0},
} }
for _, tc := range testCases { for _, tc := range testCases {
@@ -37,6 +64,29 @@ func TestFormatDuration(t *testing.T) {
} }
} }
func TestIsSameDate(t *testing.T) {
testCases := []struct {
dateA string
dateB string
result bool
}{
{"2025-12-01 00:00:00", "2025-12-01 00:00:00", true},
{"2025-12-03 00:00:00", "2025-12-02 00:00:00", false},
{"2025-12-03 23:45:00", "2025-12-03 00:00:00", true},
{"2025-12-04 24:12:00", "2025-12-04 00:12:00", false},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("IsSameDateTest: %s date", tc.dateA), func(t *testing.T) {
dateA, _ := time.Parse(time.DateTime, tc.dateA)
dateB, _ := time.Parse(time.DateTime, tc.dateB)
if IsSameDate(dateA, dateB) != tc.result {
t.Errorf("Is SameDate did not match! Result %t", IsSameDate(dateA, dateB))
}
})
}
}
func TestGetWorkingDays(t *testing.T) { func TestGetWorkingDays(t *testing.T) {
testCases := []struct { testCases := []struct {
start string start string
@@ -82,3 +132,41 @@ func TestFormatGermanDayOfWeek(t *testing.T) {
}) })
} }
} }
func TestGetKW(t *testing.T) {
tests := []struct {
name string
date time.Time
want int
}{
{
name: "First week of year",
date: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC), // Monday
want: 1,
},
{
name: "Middle of year",
date: time.Date(2023, 6, 15, 0, 0, 0, 0, time.UTC),
want: 24,
},
{
name: "Last week of year",
date: time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC),
want: 52,
},
{
name: "ISO week crossing into next year",
date: time.Date(2020, 12, 31, 0, 0, 0, 0, time.UTC),
want: 53,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetKW(tt.date)
if got != tt.want {
t.Errorf("GetKW(%v) = %d, want %d", tt.date, got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,26 @@
package helper
import (
"fmt"
"testing"
)
func TestBoolToInt(t *testing.T) {
testCases := []struct {
value bool
res int
res8 int8
}{
{true, 1, 1},
{false, 0, 0},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("BoolToInt value: %t", tc.value), func(t *testing.T) {
if BoolToInt(tc.value) != tc.res || BoolToInt8(tc.value) != tc.res8 {
t.Error("How could you... mess up bool to int")
}
})
}
}

112
Backend/helper/web_test.go Normal file
View File

@@ -0,0 +1,112 @@
package helper
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/alexedwards/scs/v2"
)
func TestSetCors_WhenNoCorsTrue(t *testing.T) {
os.Setenv("NO_CORS", "true")
defer os.Unsetenv("NO_CORS")
rr := httptest.NewRecorder()
SetCors(rr)
h := rr.Header()
if h.Get("Access-Control-Allow-Origin") != "*" {
t.Errorf("expected Access-Control-Allow-Origin to be '*', got %q", h.Get("Access-Control-Allow-Origin"))
}
if h.Get("Access-Control-Allow-Methods") != "*" {
t.Errorf("expected Access-Control-Allow-Methods to be '*', got %q", h.Get("Access-Control-Allow-Methods"))
}
if h.Get("Access-Control-Allow-Headers") != "*" {
t.Errorf("expected Access-Control-Allow-Headers to be '*', got %q", h.Get("Access-Control-Allow-Headers"))
}
}
func TestSetCors_WhenNoCorsFalse(t *testing.T) {
os.Setenv("NO_CORS", "false")
defer os.Unsetenv("NO_CORS")
rr := httptest.NewRecorder()
SetCors(rr)
h := rr.Header()
if h.Get("Access-Control-Allow-Origin") != "" ||
h.Get("Access-Control-Allow-Methods") != "" ||
h.Get("Access-Control-Allow-Headers") != "" {
t.Errorf("CORS headers should not be set when NO_CORS=false")
}
}
func TestRequiresLogin_DebugMode_NoRedirect(t *testing.T) {
os.Setenv("GO_ENV", "debug")
defer os.Unsetenv("GO_ENV")
session := scs.New()
req := httptest.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
RequiresLogin(session, rr, req)
if rr.Result().StatusCode == http.StatusSeeOther {
t.Errorf("expected no redirect in debug mode")
}
}
// func TestRequiresLogin_UserExists_NoRedirect(t *testing.T) {
// os.Setenv("GO_ENV", "production")
// defer os.Unsetenv("GO_ENV")
// session := scs.New()
// req := httptest.NewRequest("GET", "/", nil)
// ctx, err := session.Load(req.Context(), "")
// if err != nil {
// t.Fatalf("session load error: %v", err)
// }
// ctx = session.Put(ctx, "user", "123")
// req = req.WithContext(context.WithValue(ctx, "session", session))
// rr := httptest.NewRecorder()
// yourpkg.RequiresLogin(session, rr, req)
// if rr.Result().StatusCode == http.StatusSeeOther {
// t.Errorf("expected no redirect when user exists")
// }
// }
// func TestRequiresLogin_NoUser_Redirects(t *testing.T) {
// os.Setenv("GO_ENV", "production")
// defer os.Unsetenv("GO_ENV")
// session := scs.New()
// req := httptest.NewRequest("GET", "/", nil)
// req = req.WithContext(context.WithValue(req.Context(), "session", session))
// rr := httptest.NewRecorder()
// RequiresLogin(session, rr, req)
// if rr.Result().StatusCode != http.StatusSeeOther {
// t.Errorf("expected redirect when user does not exist, got %d", rr.Result().StatusCode)
// }
// location := rr.Result().Header.Get("Location")
// if location != "/user/login" {
// t.Errorf("expected redirect to /user/login, got %q", location)
// }
// }

View File

@@ -5,6 +5,7 @@ import (
"arbeitszeitmessung/helper" "arbeitszeitmessung/helper"
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"context" "context"
"database/sql"
"log/slog" "log/slog"
"net/http" "net/http"
"os" "os"
@@ -17,7 +18,16 @@ import (
func main() { func main() {
var err error var err error
var logLevel slog.LevelVar var logLevel slog.LevelVar
logLevel.Set(slog.LevelWarn) switch helper.GetEnv("LOG_LEVEL", "warn") {
case "debug":
logLevel.Set(slog.LevelDebug)
case "info":
logLevel.Set(slog.LevelInfo)
case "warn":
logLevel.Set(slog.LevelWarn)
case "error":
logLevel.Set(slog.LevelError)
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: &logLevel})) logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: &logLevel}))
slog.SetDefault(logger) slog.SetDefault(logger)
@@ -35,6 +45,15 @@ func main() {
models.DB, err = OpenDatabase() models.DB, err = OpenDatabase()
if err != nil { if err != nil {
slog.Error("Error while opening the database", "Error", err) slog.Error("Error while opening the database", "Error", err)
return
}
defer models.DB.(*sql.DB).Close()
err = Migrate()
if err != nil {
slog.Error("Failed to migrate the database to newest version", "Error", err)
return
} }
fs := http.FileServer(http.Dir("./static")) fs := http.FileServer(http.Dir("./static"))
@@ -46,7 +65,9 @@ func main() {
server.HandleFunc("/time/new", endpoints.TimeCreateHandler) server.HandleFunc("/time/new", endpoints.TimeCreateHandler)
server.Handle("/absence", ParamsMiddleware(endpoints.AbsencHandler)) server.Handle("/absence", ParamsMiddleware(endpoints.AbsencHandler))
server.Handle("/time", ParamsMiddleware(endpoints.TimeHandler)) server.Handle("/time", ParamsMiddleware(endpoints.TimeHandler))
server.HandleFunc("/logout", endpoints.LogoutHandler) server.HandleFunc("/auto/logout", endpoints.LogoutHandler)
server.HandleFunc("/auto/kurzarbeit", endpoints.KurzarbeitFillHandler)
server.HandleFunc("/auto/feiertage", endpoints.FeiertagsHandler)
server.HandleFunc("/user/{action}", endpoints.UserHandler) server.HandleFunc("/user/{action}", endpoints.UserHandler)
// server.HandleFunc("/user/login", endpoints.LoginHandler) // server.HandleFunc("/user/login", endpoints.LoginHandler)
// server.HandleFunc("/user/settings", endpoints.UserSettingsHandler) // server.HandleFunc("/user/settings", endpoints.UserSettingsHandler)
@@ -59,6 +80,8 @@ func main() {
serverSessionMiddleware := endpoints.Session.LoadAndSave(server) serverSessionMiddleware := endpoints.Session.LoadAndSave(server)
serverSessionMiddleware = loggingMiddleware(serverSessionMiddleware)
// starting the http server // starting the http server
slog.Info("Server is running at http://localhost:8080") slog.Info("Server is running at http://localhost:8080")
slog.Error("Error starting Server", "Error", http.ListenAndServe(":8080", serverSessionMiddleware)) slog.Error("Error starting Server", "Error", http.ListenAndServe(":8080", serverSessionMiddleware))
@@ -68,7 +91,24 @@ func ParamsMiddleware(next http.HandlerFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query() queryParams := r.URL.Query()
ctx := context.WithValue(r.Context(), "urlParams", queryParams) ctx := context.WithValue(r.Context(), "urlParams", queryParams)
slog.Debug("ParamsMiddleware added urlParams", slog.Any("urlParams", queryParams)) if len(queryParams) > 0 {
slog.Debug("ParamsMiddleware added urlParams", slog.Any("urlParams", queryParams))
}
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Log the method and the requested URL
slog.Info("Started request", slog.String("Method", r.Method), slog.String("Path", r.URL.Path))
// Call the next handler in the chain
next.ServeHTTP(w, r)
// Log how long it took
slog.Info("Completet Request", slog.String("Time", time.Since(start).String()))
})
}

View File

@@ -1,3 +1,11 @@
ALTER DEFAULT PRIVILEGES FOR ROLE migrate
IN SCHEMA public
GRANT SELECT ON TABLES TO app_base;
ALTER DEFAULT PRIVILEGES FOR ROLE migrate
IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO app_base;
-- create "abwesenheit" table -- create "abwesenheit" table
CREATE TABLE "abwesenheit" ( CREATE TABLE "abwesenheit" (
"counter_id" bigserial NOT NULL, "counter_id" bigserial NOT NULL,
@@ -6,6 +14,7 @@ CREATE TABLE "abwesenheit" (
"datum" timestamptz NULL DEFAULT (now())::date, "datum" timestamptz NULL DEFAULT (now())::date,
PRIMARY KEY ("counter_id") PRIMARY KEY ("counter_id")
); );
-- create "anwesenheit" table -- create "anwesenheit" table
CREATE TABLE "anwesenheit" ( CREATE TABLE "anwesenheit" (
"counter_id" bigserial NOT NULL, "counter_id" bigserial NOT NULL,
@@ -55,3 +64,6 @@ CREATE TABLE "wochen_report" (
PRIMARY KEY ("id"), PRIMARY KEY ("id"),
CONSTRAINT "wochen_report_personal_nummer_woche_start_key" UNIQUE ("personal_nummer", "woche_start") CONSTRAINT "wochen_report_personal_nummer_woche_start_key" UNIQUE ("personal_nummer", "woche_start")
); );
GRANT INSERT, UPDATE ON abwesenheit, anwesenheit, wochen_report, user_password TO app_base;
GRANT DELETE ON abwesenheit to app_base;

View File

@@ -3,8 +3,3 @@
DROP FUNCTION update_zuletzt_geandert; DROP FUNCTION update_zuletzt_geandert;
DROP TRIGGER IF EXISTS pass_hash_update ON user_password; DROP TRIGGER IF EXISTS pass_hash_update ON user_password;
-- revert: Adds crypto extension
DROP EXTENSION IF EXISTS pgcrypto;

View File

@@ -17,5 +17,3 @@ FOR EACH ROW
EXECUTE FUNCTION update_zuletzt_geandert(); EXECUTE FUNCTION update_zuletzt_geandert();
-- Adds crypto extension -- Adds crypto extension
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -0,0 +1,6 @@
-- reverse: create index "feiertage_unique_pro_jahr" to table: "s_feiertage"
DROP INDEX "feiertage_unique_pro_jahr";
-- reverse: create "s_feiertage" table
DROP TABLE "s_feiertage";
-- reverse: set comment to column: "arbeitszeit_equivalent" on table: "s_abwesenheit_typen"
COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; 1=Arbeitszeit auffüllen; 2=Arbeitszeit austauschen';

View File

@@ -0,0 +1,15 @@
-- set comment to column: "arbeitszeit_equivalent" on table: "s_abwesenheit_typen"
COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; -1=Arbeitszeit auffüllen; <=1 - 100 => Arbeitszeit pro Tag prozentual';
-- create "s_feiertage" table
CREATE TABLE "s_feiertage" (
"counter_id" serial NOT NULL,
"datum" date NOT NULL,
"name" character varying(100) NOT NULL,
"wiederholen" smallint NOT NULL DEFAULT 0,
"arbeitszeit_equivalent" smallint NOT NULL DEFAULT 100,
PRIMARY KEY ("counter_id")
);
-- create index "feiertage_unique_pro_jahr" to table: "s_feiertage"
CREATE UNIQUE INDEX "feiertage_unique_pro_jahr" ON "s_feiertage" ((EXTRACT(year FROM datum)), "name");
GRANT INSERT, UPDATE ON s_feiertage TO app_base;

View File

@@ -1,4 +1,4 @@
h1:gE7ikkZS7bQbedAjVspQKftSo5ODij2eiQGWbfQEYmI= h1:1lrLZOm9nGe6v1/TrR1Ij8LBRDCY2igXwwUB+XqEIrc=
20250901201159_initial.up.sql h1:Mb1RlVdFvcxqU9HrSK6oNeURqFa3O4KzB3rDa+6+3gc= 20250901201159_initial.up.sql h1:Mb1RlVdFvcxqU9HrSK6oNeURqFa3O4KzB3rDa+6+3gc=
20250901201250_control_tables.up.sql h1:a5LATgR/CRiC4GsqxkJ94TyJOxeTcW74eCnodIy+c1E= 20250901201250_control_tables.up.sql h1:a5LATgR/CRiC4GsqxkJ94TyJOxeTcW74eCnodIy+c1E=
20250901201710_triggers_extension.up.sql h1:z9b6Hk9btE2Ns4mU7B16HjvYBP6EEwHAXVlvPpkn978= 20250901201710_triggers_extension.up.sql h1:z9b6Hk9btE2Ns4mU7B16HjvYBP6EEwHAXVlvPpkn978=
@@ -7,3 +7,4 @@ h1:gE7ikkZS7bQbedAjVspQKftSo5ODij2eiQGWbfQEYmI=
20250904114004_intervals.up.sql h1:gDdN8cJ4xH1vQhAbbhqD5lwdyEO1N9EIqEYkmWGiWIU= 20250904114004_intervals.up.sql h1:gDdN8cJ4xH1vQhAbbhqD5lwdyEO1N9EIqEYkmWGiWIU=
20250916093608_kurzarbeit.up.sql h1:yDAAMLyUXz6b7+MI6XK/HZMPzutKoT2NNNOCjFaqSts= 20250916093608_kurzarbeit.up.sql h1:yDAAMLyUXz6b7+MI6XK/HZMPzutKoT2NNNOCjFaqSts=
20251013212224_buchungs_array.up.sql h1:mbhvnwMUkEFFQQ41NC47auqxbtvNkztziWvpLDFm6tA= 20251013212224_buchungs_array.up.sql h1:mbhvnwMUkEFFQQ41NC47auqxbtvNkztziWvpLDFm6tA=
20251217215955_feiertage.up.sql h1:PipbYvfL8YtsidgbJ3oEHYrmiNzffQ7veyaGAxINltE=

View File

@@ -22,6 +22,11 @@ type Absence struct {
DateTo time.Time DateTo time.Time
} }
// IsEmpty implements [IWorkDay].
func (a *Absence) IsEmpty() bool {
return false
}
func NewAbsence(card_uid string, abwesenheit_typ int, datum time.Time) (Absence, error) { func NewAbsence(card_uid string, abwesenheit_typ int, datum time.Time) (Absence, error) {
if abwesenheit_typ < 0 { if abwesenheit_typ < 0 {
return Absence{ return Absence{
@@ -45,83 +50,57 @@ func (a *Absence) Date() time.Time {
return a.Day.Truncate(24 * time.Hour) return a.Day.Truncate(24 * time.Hour)
} }
func (a *Absence) Type() DayType {
return DayTypeAbsence
}
func (a *Absence) IsMultiDay() bool { func (a *Absence) IsMultiDay() bool {
return !a.DateFrom.Equal(a.DateTo) return !a.DateFrom.Equal(a.DateTo)
} }
func (a *Absence) GetWorktimeReal(u User, base WorktimeBase) time.Duration { func (a *Absence) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
if a.AbwesenheitTyp.WorkTime <= 1 { switch base {
case WorktimeBaseDay:
if a.AbwesenheitTyp.WorkTime <= 0 && includeKurzarbeit {
return u.ArbeitszeitProTagFrac(1)
} else if a.AbwesenheitTyp.WorkTime <= 0 {
return 0
}
return u.ArbeitszeitProTagFrac(float32(a.AbwesenheitTyp.WorkTime) / 100)
case WorktimeBaseWeek:
if a.AbwesenheitTyp.WorkTime <= 0 && includeKurzarbeit {
return u.ArbeitszeitProTagFrac(0.2)
} else if a.AbwesenheitTyp.WorkTime <= 0 {
return 0
}
return u.ArbeitszeitProWocheFrac(0.2 * float32(a.AbwesenheitTyp.WorkTime) / 100)
}
return 0
}
func (a *Absence) GetPausetime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
return 0
}
func (a *Absence) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
if a.AbwesenheitTyp.WorkTime > 0 {
return 0 return 0
} }
switch base { switch base {
case WorktimeBaseDay: case WorktimeBaseDay:
return u.ArbeitszeitProTag() return -u.ArbeitszeitProTagFrac(1)
case WorktimeBaseWeek: case WorktimeBaseWeek:
return u.ArbeitszeitProWoche() / 5 return -u.ArbeitszeitProWocheFrac(0.2)
}
return 0
}
func (a *Absence) GetPausetimeReal(u User, base WorktimeBase) time.Duration {
return 0
}
func (a *Absence) GetOvertimeReal(u User, base WorktimeBase) time.Duration {
if a.AbwesenheitTyp.WorkTime > 1 {
return 0
}
switch base {
case WorktimeBaseDay:
return -u.ArbeitszeitProTag()
case WorktimeBaseWeek:
return -u.ArbeitszeitProWoche() / 5
} }
return 0 return 0
} }
func (a *Absence) GetWorktimeVirtual(u User, base WorktimeBase) time.Duration { func (a *Absence) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work, pause, overtime time.Duration) {
return a.GetWorktimeReal(u, base) return a.GetWorktime(u, base, includeKurzarbeit), a.GetPausetime(u, base, includeKurzarbeit), a.GetOvertime(u, base, includeKurzarbeit)
}
func (a *Absence) GetPausetimeVirtual(u User, base WorktimeBase) time.Duration {
return a.GetPausetimeReal(u, base)
}
func (a *Absence) GetOvertimeVirtual(u User, base WorktimeBase) time.Duration {
return a.GetOvertimeReal(u, base)
}
func (a *Absence) GetTimesReal(u User, base WorktimeBase) (work, pause, overtime time.Duration) {
return a.GetWorktimeReal(u, base), a.GetPausetimeReal(u, base), a.GetOvertimeReal(u, base)
}
func (a *Absence) GetTimesVirtual(u User, base WorktimeBase) (work, pause, overtime time.Duration) {
return a.GetWorktimeVirtual(u, base), a.GetPausetimeVirtual(u, base), a.GetOvertimeVirtual(u, base)
}
func (a *Absence) TimeWorkVirtual(u User) time.Duration {
return a.TimeWorkReal(u)
}
func (a *Absence) TimeWorkReal(u User) time.Duration {
if a.AbwesenheitTyp.WorkTime > 1 {
return time.Duration(u.ArbeitszeitPerTag * float32(time.Hour)).Round(time.Minute)
}
return 0
}
func (a *Absence) TimePauseReal(u User) (work, pause time.Duration) {
return 0, 0
}
func (a *Absence) TimeOvertimeReal(u User) time.Duration {
if a.AbwesenheitTyp.WorkTime > 1 {
return 0
}
return -u.ArbeitszeitProTag()
} }
func (a *Absence) ToString() string { func (a *Absence) ToString() string {
return "Abwesenheit" return a.AbwesenheitTyp.Name
} }
func (a *Absence) IsWorkDay() bool { func (a *Absence) IsWorkDay() bool {
@@ -133,7 +112,7 @@ func (a *Absence) IsKurzArbeit() bool {
} }
func (a *Absence) GetDayProgress(u User) int8 { func (a *Absence) GetDayProgress(u User) int8 {
return 100 return a.AbwesenheitTyp.WorkTime
} }
func (a *Absence) RequiresAction() bool { func (a *Absence) RequiresAction() bool {
@@ -315,3 +294,12 @@ func GetAbsenceTypeById(absenceTypeId int8) (AbsenceType, error) {
} }
return absenceType, nil return absenceType, nil
} }
func (a *Absence) Delete() error {
qStr, err := DB.Prepare("DELETE from abwesenheit WHERE counter_id = $1;")
if err != nil {
return err
}
_, err = qStr.Exec(a.CounterId)
return err
}

View File

@@ -0,0 +1,92 @@
package models_test
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"testing"
"time"
)
var testAbsence = models.Absence{
Day: CatchError(time.Parse(time.DateOnly, "2025-01-01")),
AbwesenheitTyp: models.AbsenceType{},
DateFrom: CatchError(time.Parse(time.DateOnly, "2025-01-01")),
DateTo: CatchError(time.Parse(time.DateOnly, "2025-01-03")),
}
var testKurzarbeit = models.AbsenceType{
Name: "Kurzarbeit",
WorkTime: -1,
}
var testUrlaub = models.AbsenceType{
Name: "Urlaub",
WorkTime: 100,
}
var testUrlaubUntertags = models.AbsenceType{
Name: "Urlaub untertags",
WorkTime: 50,
}
func TestCalcRealWorkTimeDayAbsence(t *testing.T) {
testCases := []struct {
absenceType models.AbsenceType
expectedTime time.Duration
}{
{
absenceType: testUrlaub,
expectedTime: time.Hour * 8,
},
{
absenceType: testUrlaubUntertags,
expectedTime: time.Hour * 4,
},
{
absenceType: testKurzarbeit,
expectedTime: 0,
},
}
for _, tc := range testCases {
t.Run("Calc Absence Worktime: "+tc.absenceType.Name, func(t *testing.T) {
var testCase = testAbsence
testCase.AbwesenheitTyp = tc.absenceType
workTime := testCase.GetWorktime(testUser, models.WorktimeBaseDay, false)
if workTime != tc.expectedTime {
t.Errorf("Calc Worktime Default not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
}
})
}
}
func TestCalcRealWorkTimeWeekAbsence(t *testing.T) {
testCases := []struct {
absenceType models.AbsenceType
expectedTime time.Duration
}{
{
absenceType: testUrlaub,
expectedTime: time.Hour * 7,
},
{
absenceType: testUrlaubUntertags,
expectedTime: time.Hour*3 + time.Minute*30,
},
{
absenceType: testKurzarbeit,
expectedTime: 0,
},
}
for _, tc := range testCases {
t.Run("Calc Absence Worktime: "+tc.absenceType.Name, func(t *testing.T) {
var testCase = testAbsence
testCase.AbwesenheitTyp = tc.absenceType
workTime := testCase.GetWorktime(testUser, models.WorktimeBaseWeek, false)
if workTime != tc.expectedTime {
t.Errorf("Calc Worktime Default not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
}
})
}
}

View File

@@ -128,6 +128,9 @@ func (b *Booking) InsertWithTimestamp() error {
if b.Timestamp.IsZero() { if b.Timestamp.IsZero() {
return b.Insert() return b.Insert()
} }
if !checkLastBooking(*b) {
return SameBookingError{}
}
stmt, err := DB.Prepare((`INSERT INTO anwesenheit (card_uid, geraet_id, check_in_out, anwesenheit_typ, timestamp) VALUES ($1, $2, $3, $4, $5) RETURNING counter_id`)) stmt, err := DB.Prepare((`INSERT INTO anwesenheit (card_uid, geraet_id, check_in_out, anwesenheit_typ, timestamp) VALUES ($1, $2, $3, $4, $5) RETURNING counter_id`))
if err != nil { if err != nil {
return err return err
@@ -242,12 +245,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
stmt, err := DB.Prepare((`SELECT check_in_out FROM "anwesenheit" WHERE "card_uid" = $1 ORDER BY "timestamp" DESC LIMIT 1;`)) 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;`))
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).Scan(&check_in_out) err = stmt.QueryRow(b.CardUID, b.Timestamp).Scan(&check_in_out)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return true return true
} }

View File

@@ -10,36 +10,36 @@ var testBookingType = models.BookingType{
Name: "Büro", Name: "Büro",
} }
var testBookings8hrs = []models.Booking{models.Booking{ var testBookings8hrs = []models.Booking{{
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 1, CheckInOut: 1,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
BookingType: testBookingType, BookingType: testBookingType,
}, models.Booking{ }, {
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 2, CheckInOut: 2,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:00")),
BookingType: testBookingType, BookingType: testBookingType,
}} }}
var testBookings6hrs = []models.Booking{models.Booking{ var testBookings6hrs = []models.Booking{{
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 1, CheckInOut: 1,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
BookingType: testBookingType, BookingType: testBookingType,
}, models.Booking{ }, {
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 2, CheckInOut: 2,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 14:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 14:00")),
BookingType: testBookingType, BookingType: testBookingType,
}} }}
var testBookings10hrs = []models.Booking{models.Booking{ var testBookings10hrs = []models.Booking{{
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 1, CheckInOut: 1,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
BookingType: testBookingType, BookingType: testBookingType,
}, models.Booking{ }, {
CardUID: "aaaa-aaaa", CardUID: "aaaa-aaaa",
CheckInOut: 2, CheckInOut: 2,
Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 18:00")), Timestamp: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 18:00")),

View File

@@ -0,0 +1,109 @@
package models
import (
"log/slog"
"time"
)
type CompoundDay struct {
Day time.Time
DayParts []IWorkDay
}
func NewCompondDay(date time.Time, dayParts ...IWorkDay) *CompoundDay {
return &CompoundDay{Day: date, DayParts: dayParts}
}
func (c *CompoundDay) AddDayPart(dayPart IWorkDay) {
c.DayParts = append(c.DayParts, dayPart)
}
func (c *CompoundDay) GetWorkDay() WorkDay {
workday, ok := c.DayParts[0].(*WorkDay)
if ok {
return *workday
}
return WorkDay{}
}
// IsEmpty implements [IWorkDay].
func (c *CompoundDay) IsEmpty() bool {
return len(c.DayParts) == 0
}
// Date implements [IWorkDay].
func (c *CompoundDay) Date() time.Time {
return c.Day
}
// GetDayProgress implements [IWorkDay].
func (c *CompoundDay) GetDayProgress(u User) int8 {
var dayProcess int8
for _, day := range c.DayParts {
dayProcess += day.GetDayProgress(u)
}
return dayProcess
}
// GetOvertime implements [IWorkDay].
func (c *CompoundDay) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
work := c.GetWorktime(u, base, includeKurzarbeit)
var targetHours time.Duration
switch base {
case WorktimeBaseDay:
targetHours = u.ArbeitszeitProTagFrac(1)
case WorktimeBaseWeek:
targetHours = u.ArbeitszeitProWocheFrac(.2)
}
return (work - targetHours).Round(time.Minute)
}
// GetPausetime implements [IWorkDay].
func (c *CompoundDay) GetPausetime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
var pausetime time.Duration
for _, day := range c.DayParts {
pausetime += day.GetPausetime(u, base, includeKurzarbeit)
}
return pausetime
}
// GetTimes implements [IWorkDay].
func (c *CompoundDay) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work time.Duration, pause time.Duration, overtime time.Duration) {
return c.GetWorktime(u, base, includeKurzarbeit), c.GetPausetime(u, base, includeKurzarbeit), c.GetOvertime(u, base, includeKurzarbeit)
}
// GetWorktime implements [IWorkDay].
func (c *CompoundDay) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
var worktime time.Duration
for _, day := range c.DayParts {
worktime += day.GetWorktime(u, base, includeKurzarbeit)
slog.Info("Calc worktime for day", "day", day, "worktime", worktime.String())
}
return worktime
}
// IsKurzArbeit implements [IWorkDay].
func (c *CompoundDay) IsKurzArbeit() bool {
return false
}
// IsWorkDay implements [IWorkDay].
func (c *CompoundDay) IsWorkDay() bool {
return true
}
// RequiresAction implements [IWorkDay].
func (c *CompoundDay) RequiresAction() bool {
return false
}
// ToString implements [IWorkDay].
func (c *CompoundDay) ToString() string {
return "Compound Day"
}
// Type implements [IWorkDay].
func (c *CompoundDay) Type() DayType {
return DayTypeCompound
}

View File

@@ -0,0 +1,88 @@
package models
import (
"log/slog"
"time"
)
type IWorkDay interface {
Date() time.Time
ToString() string
Type() DayType
IsWorkDay() bool
IsKurzArbeit() bool
GetDayProgress(User) int8
RequiresAction() bool
GetWorktime(User, WorktimeBase, bool) time.Duration
GetPausetime(User, WorktimeBase, bool) time.Duration
GetTimes(User, WorktimeBase, bool) (work, pause, overtime time.Duration)
GetOvertime(User, WorktimeBase, bool) time.Duration
IsEmpty() bool
}
type DayType int
const (
DayTypeWorkday DayType = 1
DayTypeAbsence DayType = 2
DayTypeHoliday DayType = 3
DayTypeCompound DayType = 4
)
func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay {
var allDays map[string]IWorkDay = make(map[string]IWorkDay)
workdays := GetWorkDays(user, tsFrom, tsTo)
absences, err := GetAbsencesByCardUID(user.CardUID, tsFrom, tsTo)
if err != nil {
slog.Warn("Error gettings absences!", slog.Any("Error", err))
return nil
}
holidays, err := GetHolidaysFromTo(tsFrom, tsTo)
if err != nil {
slog.Warn("Error getting holidays!", slog.Any("Error", err))
return nil
}
for _, day := range workdays {
allDays[day.Date().Format(time.DateOnly)] = &day
}
for _, absentDay := range absences {
// Check if there is already a day
existingDay, ok := allDays[absentDay.Date().Format(time.DateOnly)]
switch {
case absentDay.AbwesenheitTyp.WorkTime < 0:
if workDay, ok := allDays[absentDay.Date().Format(time.DateOnly)].(*WorkDay); ok {
workDay.kurzArbeit = true
workDay.kurzArbeitAbsence = absentDay
}
case ok && !existingDay.IsEmpty():
allDays[absentDay.Date().Format(time.DateOnly)] = NewCompondDay(absentDay.Date(), existingDay, &absentDay)
default:
allDays[absentDay.Date().Format(time.DateOnly)] = &absentDay
}
}
for _, holiday := range holidays {
existingDay, ok := allDays[holiday.Date().Format(time.DateOnly)]
if !ok {
allDays[holiday.Date().Format(time.DateOnly)] = &holiday
continue
}
slog.Info("Existing Day", "day", existingDay)
switch {
case existingDay.Type() == DayTypeCompound:
allDays[holiday.Date().Format(time.DateOnly)].(*CompoundDay).AddDayPart(&holiday)
case existingDay.Type() != DayTypeCompound && !existingDay.IsEmpty():
allDays[holiday.Date().Format(time.DateOnly)] = NewCompondDay(holiday.Date(), existingDay, &holiday)
default:
allDays[holiday.Date().Format(time.DateOnly)] = &holiday
}
slog.Debug("Logging Holiday: ", slog.String("HolidayName", allDays[holiday.Date().Format(time.DateOnly)].ToString()), slog.Any("Overtime", holiday.GetOvertime(user, WorktimeBaseDay, false).String()), "wokrtie", float32(holiday.worktime)/100)
}
sortedDays := sortDays(allDays, orderedForward)
return sortedDays
}

View File

@@ -0,0 +1,137 @@
package models
import (
"time"
"github.com/wlbr/feiertage"
)
// type PublicHoliday feiertage.Feiertag
type PublicHoliday struct {
feiertage.Feiertag
repeat int8
worktime int8
}
// IsEmpty implements [IWorkDay].
func (p *PublicHoliday) IsEmpty() bool {
return false
}
func NewHolidayFromFeiertag(f feiertage.Feiertag) PublicHoliday {
return PublicHoliday{
Feiertag: f,
repeat: 0,
worktime: 100,
}
}
func GetHolidaysFromTo(tsFrom, tsTo time.Time) ([]PublicHoliday, error) {
var publicHolidays []PublicHoliday
qStr, err := DB.Prepare(`SELECT datum, name, wiederholen, arbeitszeit_equivalent FROM s_feiertage WHERE datum::DATE >= $1::DATE AND datum::DATE <= $2::DATE;`)
if err != nil {
return publicHolidays, err
}
rows, err := qStr.Query(tsFrom, tsTo)
if err != nil {
return publicHolidays, err
}
defer rows.Close()
for rows.Next() {
var publicHoliday PublicHoliday
if err := rows.Scan(&publicHoliday.Time, &publicHoliday.Text, &publicHoliday.repeat, &publicHoliday.worktime); err != nil {
return publicHolidays, err
}
publicHolidays = append(publicHolidays, publicHoliday)
}
return publicHolidays, nil
}
func GetRepeatingHolidays(tsFrom, tsTo time.Time) ([]PublicHoliday, error) {
var publicHolidays []PublicHoliday
qStr, err := DB.Prepare(`SELECT datum, name, wiederholen, arbeitszeit_equivalent FROM s_feiertage WHERE wiederholen = 1 AND datum::DATE >= $1::DATE AND datum::DATE <= $2::DATE;`)
if err != nil {
return publicHolidays, err
}
rows, err := qStr.Query(tsFrom, tsTo)
if err != nil {
return publicHolidays, err
}
defer rows.Close()
for rows.Next() {
var publicHoliday PublicHoliday
if err := rows.Scan(&publicHoliday.Time, &publicHoliday.Text, &publicHoliday.repeat, &publicHoliday.worktime); err != nil {
return publicHolidays, err
}
publicHolidays = append(publicHolidays, publicHoliday)
}
return publicHolidays, nil
}
func (p *PublicHoliday) Insert() error {
qStr, err := DB.Prepare(`INSERT INTO s_feiertage(name, datum, wiederholen, arbeitszeit_equivalent) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING;`)
if err != nil {
return err
}
_, err = qStr.Exec(p.Text, p.Time, p.repeat, p.worktime)
return err
}
func (p *PublicHoliday) Type() DayType {
return DayTypeHoliday
}
// Interface implementation
func (p *PublicHoliday) Date() time.Time {
return p.Time
}
func (p *PublicHoliday) ToString() string {
return p.Text
}
func (p *PublicHoliday) IsWorkDay() bool {
return false
}
func (p *PublicHoliday) IsKurzArbeit() bool {
return false
}
func (p *PublicHoliday) GetDayProgress(User) int8 {
return p.worktime
}
func (p *PublicHoliday) RequiresAction() bool {
return false
}
func (p *PublicHoliday) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
switch base {
case WorktimeBaseDay:
return u.ArbeitszeitProTagFrac(float32(p.worktime) / 100)
case WorktimeBaseWeek:
return u.ArbeitszeitProWocheFrac(float32(p.worktime) / 500)
}
return 0
}
func (p *PublicHoliday) GetPausetime(User, WorktimeBase, bool) time.Duration {
return 0
}
func (p *PublicHoliday) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
switch base {
case WorktimeBaseDay:
return u.ArbeitszeitProTagFrac(float32(p.worktime)/100) - u.ArbeitszeitProTagFrac(1)
case WorktimeBaseWeek:
return u.ArbeitszeitProWocheFrac(float32(p.worktime)/500) - u.ArbeitszeitProWocheFrac(0.2)
}
return 0
}
func (p *PublicHoliday) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work, pause, overtime time.Duration) {
return p.GetWorktime(u, base, includeKurzarbeit), 0, 0
}

View File

@@ -7,9 +7,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"log/slog"
"time" "time"
"github.com/alexedwards/scs/v2" "github.com/alexedwards/scs/v2"
"github.com/lib/pq"
) )
type User struct { type User struct {
@@ -57,11 +59,37 @@ func (u *User) GetReportedOvertime() (time.Duration, error) {
return overtime, nil return overtime, nil
} }
func GetAllUsers() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname,arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten;`))
var users []User
if err != nil {
return users, err
}
defer qStr.Close()
rows, err := qStr.Query()
if err != nil {
return users, err
}
defer rows.Close()
for rows.Next() {
var user User
if err := rows.Scan(&user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche); err != nil {
log.Println("Error creating user!", err)
continue
}
users = append(users, user)
}
if err = rows.Err(); err != nil {
return users, nil
}
return users, nil
}
func (u *User) GetAll() ([]User, error) { func (u *User) GetAll() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`)) qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`))
var users []User var users []User
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err)
return users, err return users, err
} }
defer qStr.Close() defer qStr.Close()
@@ -85,13 +113,21 @@ func (u *User) GetAll() ([]User, error) {
return users, nil return users, nil
} }
// Returns the worktime per day rounded to minutes
func (u *User) ArbeitszeitProTag() time.Duration { func (u *User) ArbeitszeitProTag() time.Duration {
return time.Duration(u.ArbeitszeitPerTag * float32(time.Hour)).Round(time.Minute) return u.ArbeitszeitProTagFrac(1)
}
// Returns the worktime per day rounded to minutes
func (u *User) ArbeitszeitProTagFrac(fraction float32) time.Duration {
return time.Duration(u.ArbeitszeitPerTag * float32(time.Hour) * fraction).Round(time.Minute)
} }
func (u *User) ArbeitszeitProWoche() time.Duration { func (u *User) ArbeitszeitProWoche() time.Duration {
return time.Duration(u.ArbeitszeitPerWoche * float32(time.Hour)).Round(time.Minute) return u.ArbeitszeitProWocheFrac(1)
}
func (u *User) ArbeitszeitProWocheFrac(fraction float32) time.Duration {
return time.Duration(u.ArbeitszeitPerWoche * float32(time.Hour) * fraction).Round(time.Minute)
} }
// Returns true if there is a booking 1 for today -> meaning the user is at work // Returns true if there is a booking 1 for today -> meaning the user is at work
@@ -99,7 +135,7 @@ func (u *User) ArbeitszeitProWoche() time.Duration {
func (u *User) CheckAnwesenheit() bool { func (u *User) CheckAnwesenheit() bool {
qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp"::date = now()::date ORDER BY "timestamp" DESC LIMIT 1;`)) qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp"::date = now()::date ORDER BY "timestamp" DESC LIMIT 1;`))
if err != nil { if err != nil {
fmt.Printf("Error preparing query statement %v\n", err) slog.Debug("Error preparing query statement.", "error", err)
return false return false
} }
defer qStr.Close() defer qStr.Close()
@@ -137,11 +173,43 @@ func GetUserByPersonalNr(personalNummer int) (User, error) {
return user, nil return user, nil
} }
func GetUserByPersonalNrMulti(personalNummerMulti []int) ([]User, error) {
var users []User
if len(personalNummerMulti) == 0 {
return users, errors.New("No personalNumbers provided")
}
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten WHERE personal_nummer = ANY($1::int[]);`))
if err != nil {
return users, err
}
rows, err := qStr.Query(pq.Array(personalNummerMulti))
if err == sql.ErrNoRows {
return users, err
}
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var user User
if err := rows.Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche); err != nil {
return users, err
}
users = append(users, user)
}
if err = rows.Err(); err != nil {
return users, err
}
return users, nil
}
func (u *User) Login(password string) bool { func (u *User) Login(password string) bool {
var loginSuccess bool var loginSuccess bool
qStr, err := DB.Prepare((`SELECT (pass_hash = crypt($2, pass_hash)) AS pass_hash FROM user_password WHERE personal_nummer = $1;`)) qStr, err := DB.Prepare((`SELECT (pass_hash = crypt($2, pass_hash)) AS pass_hash FROM user_password WHERE personal_nummer = $1;`))
if err != nil { if err != nil {
log.Println("Error preparing db statement", err) slog.Debug("Error preparing query statement.", "error", err)
return false return false
} }
defer qStr.Close() defer qStr.Close()
@@ -233,7 +301,7 @@ func (u *User) GetLastWorkWeekSubmission() time.Time {
) AS letzte_buchung; ) AS letzte_buchung;
`) `)
if err != nil { if err != nil {
log.Println("Error preparing statement!", err) slog.Debug("Error preparing query statement.", "error", err)
return lastSub return lastSub
} }
err = qStr.QueryRow(u.PersonalNummer, u.CardUID).Scan(&lastSub) err = qStr.QueryRow(u.PersonalNummer, u.CardUID).Scan(&lastSub)
@@ -262,6 +330,22 @@ func (u *User) GetFromCardUID(card_uid string) (User, error) {
return user, nil return user, nil
} }
func (u *User) IsSuperior(e User) bool {
var isSuperior int
qStr, err := DB.Prepare(`SELECT COUNT(1) FROM s_personal_daten WHERE personal_nummer = $1 AND vorgesetzter_pers_nr = $2`)
if err != nil {
slog.Debug("Error preparing query", "error", err)
return false
}
err = qStr.QueryRow(e.PersonalNummer, u.PersonalNummer).Scan(&isSuperior)
if err != nil {
slog.Debug("Error executing query", "error", err)
return false
}
return isSuperior == 1
}
func getMonday(ts time.Time) time.Time { func getMonday(ts time.Time) time.Time {
if ts.Weekday() != time.Monday { if ts.Weekday() != time.Monday {
if ts.Weekday() == time.Sunday { if ts.Weekday() == time.Sunday {

View File

@@ -6,7 +6,7 @@ import (
"testing" "testing"
) )
var testUser models.User = models.User{Vorname: "Kim", Name: "Mustermensch", PersonalNummer: 456, CardUID: "aaaa-aaaa", ArbeitszeitPerTag: 8, ArbeitszeitPerWoche: 40} var testUser models.User = models.User{Vorname: "Kim", Name: "Mustermensch", PersonalNummer: 456, CardUID: "aaaa-aaaa", ArbeitszeitPerTag: 8, ArbeitszeitPerWoche: 35}
func SetupUserFixture(t *testing.T, db models.IDatabase) { func SetupUserFixture(t *testing.T, db models.IDatabase) {
t.Helper() t.Helper()

View File

@@ -10,143 +10,70 @@ import (
"time" "time"
) )
type IWorkDay interface {
Date() time.Time
TimeWorkVirtual(User) time.Duration
TimeWorkReal(User) time.Duration
TimePauseReal(User) (work, pause time.Duration)
TimeOvertimeReal(User) time.Duration
GetAllWorkTimesVirtual(User) (work, pause, overtime time.Duration)
ToString() string
IsWorkDay() bool
IsKurzArbeit() bool
GetDayProgress(User) int8
RequiresAction() bool
GetWorktimeReal(User, WorktimeBase) time.Duration
GetPausetimeReal(User, WorktimeBase) time.Duration
GetOvertimeReal(User, WorktimeBase) time.Duration
GetWorktimeVirtual(User, WorktimeBase) time.Duration
GetPausetimeVirtual(User, WorktimeBase) time.Duration
GetOvertimeVirtual(User, WorktimeBase) time.Duration
GetTimesReal(User, WorktimeBase) (work, pause, overtime time.Duration)
GetTimesVirtual(User, WorktimeBase) (work, pause, overtime time.Duration)
}
type WorkDay struct { type WorkDay struct {
Day time.Time `json:"day"` Day time.Time `json:"day"`
Bookings []Booking `json:"bookings"` Bookings []Booking `json:"bookings"`
workTime time.Duration workTime time.Duration
pauseTime time.Duration pauseTime time.Duration
realWorkTime time.Duration
realPauseTime time.Duration
TimeFrom time.Time TimeFrom time.Time
TimeTo time.Time TimeTo time.Time
kurzArbeit bool kurzArbeit bool
kurzArbeitAbsence Absence kurzArbeitAbsence Absence
// Urlaub untertags
worktimeAbsece Absence
} }
type WorktimeBase string // IsEmpty implements [IWorkDay].
func (d *WorkDay) IsEmpty() bool {
return len(d.Bookings) == 0
}
type WorktimeBase int
const ( const (
WorktimeBaseWeek WorktimeBase = "week" WorktimeBaseWeek WorktimeBase = 5
WorktimeBaseDay WorktimeBase = "day" WorktimeBaseDay WorktimeBase = 1
) )
func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay { func (d *WorkDay) GetWorktimeAbsence() Absence {
var allDays map[string]IWorkDay = make(map[string]IWorkDay) return d.worktimeAbsece
for _, day := range GetWorkDays(user, tsFrom, tsTo) {
allDays[day.Date().Format(time.DateOnly)] = &day
}
absences, err := GetAbsencesByCardUID(user.CardUID, tsFrom, tsTo)
if err != nil {
log.Println("Error gettings absences for all Days!", err)
return nil
}
for _, day := range absences {
if helper.IsWeekend(day.Date()) {
continue
}
if day.AbwesenheitTyp.WorkTime == 1 {
if workDay, ok := allDays[day.Date().Format(time.DateOnly)].(*WorkDay); ok && len(workDay.Bookings) > 0 {
workDay.kurzArbeit = true
workDay.kurzArbeitAbsence = day
}
} else {
allDays[day.Date().Format(time.DateOnly)] = &day
}
}
sortedDays := sortDays(allDays, orderedForward)
return sortedDays
} }
// Gets the time as is in the db (with corrected pause times) // Gets the time as is in the db (with corrected pause times)
func (d *WorkDay) GetWorktimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetWorktime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
if includeKurzarbeit && d.IsKurzArbeit() && len(d.Bookings) > 0 {
return d.kurzArbeitAbsence.GetWorktime(u, base, true)
}
work, pause := calcWorkPause(d.Bookings) work, pause := calcWorkPause(d.Bookings)
work, pause = correctWorkPause(work, pause) work, pause = correctWorkPause(work, pause)
return work if (d.worktimeAbsece != Absence{}) {
work += d.worktimeAbsece.GetWorktime(u, base, false)
}
return work.Round(time.Minute)
} }
// Gets the corrected pause times based on db entries // Gets the corrected pause times based on db entries
func (d *WorkDay) GetPausetimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetPausetime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
work, pause := calcWorkPause(d.Bookings) work, pause := calcWorkPause(d.Bookings)
work, pause = correctWorkPause(work, pause) work, pause = correctWorkPause(work, pause)
return pause return pause.Round(time.Minute)
} }
// Returns the overtime based on the db entries // Returns the overtime based on the db entries
func (d *WorkDay) GetOvertimeReal(u User, base WorktimeBase) time.Duration { func (d *WorkDay) GetOvertime(u User, base WorktimeBase, includeKurzarbeit bool) time.Duration {
work, pause := calcWorkPause(d.Bookings) work := d.GetWorktime(u, base, includeKurzarbeit)
work, pause = correctWorkPause(work, pause)
var targetHours time.Duration var targetHours time.Duration
switch base { switch base {
case WorktimeBaseDay: case WorktimeBaseDay:
targetHours = u.ArbeitszeitProTag() targetHours = u.ArbeitszeitProTag()
case WorktimeBaseWeek: case WorktimeBaseWeek:
targetHours = u.ArbeitszeitProWoche() / 5 targetHours = u.ArbeitszeitProWocheFrac(0.2)
} }
return work - targetHours return (work - targetHours).Round(time.Minute)
} }
// Returns the worktime based on absence or kurzarbeit func (d *WorkDay) GetTimes(u User, base WorktimeBase, includeKurzarbeit bool) (work, pause, overtime time.Duration) {
func (d *WorkDay) GetWorktimeVirtual(u User, base WorktimeBase) time.Duration { return d.GetWorktime(u, base, includeKurzarbeit), d.GetPausetime(u, base, includeKurzarbeit), d.GetOvertime(u, base, includeKurzarbeit)
if !d.IsKurzArbeit() {
return d.GetWorktimeReal(u, base)
}
switch base {
case WorktimeBaseDay:
return u.ArbeitszeitProTag()
case WorktimeBaseWeek:
return u.ArbeitszeitProWoche() / 5
}
return 0
}
func (d *WorkDay) GetPausetimeVirtual(u User, base WorktimeBase) time.Duration {
return d.GetPausetimeReal(u, base)
}
func (d *WorkDay) GetOvertimeVirtual(u User, base WorktimeBase) time.Duration {
work := d.GetWorktimeVirtual(u, base)
var targetHours time.Duration
switch base {
case WorktimeBaseDay:
targetHours = u.ArbeitszeitProTag()
case WorktimeBaseWeek:
targetHours = u.ArbeitszeitProWoche() / 5
}
return work - targetHours
}
func (d *WorkDay) GetTimesReal(u User, base WorktimeBase) (work, pause, overtime time.Duration) {
return d.GetWorktimeReal(u, base), d.GetPausetimeReal(u, base), d.GetOvertimeReal(u, base)
}
func (d *WorkDay) GetTimesVirtual(u User, base WorktimeBase) (work, pause, overtime time.Duration) {
return d.GetWorktimeVirtual(u, base), d.GetPausetimeVirtual(u, base), d.GetOvertimeVirtual(u, base)
} }
func calcWorkPause(bookings []Booking) (work, pause time.Duration) { func calcWorkPause(bookings []Booking) (work, pause time.Duration) {
@@ -204,83 +131,27 @@ func (d *WorkDay) Date() time.Time {
return d.Day return d.Day
} }
func (d *WorkDay) Type() DayType {
return DayTypeWorkday
}
func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) { func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) {
var timeFrom, timeTo time.Time var timeFrom, timeTo time.Time
if d.workTime >= u.ArbeitszeitProTag() { if d.GetWorktime(u, WorktimeBaseDay, false) >= u.ArbeitszeitProTag() {
return timeFrom, timeTo return timeFrom, timeTo
} }
timeFrom = d.Bookings[len(d.Bookings)-1].Timestamp.Add(time.Minute) timeFrom = d.Bookings[len(d.Bookings)-1].Timestamp.Add(time.Minute)
timeTo = timeFrom.Add(u.ArbeitszeitProTag() - d.workTime) timeTo = timeFrom.Add(u.ArbeitszeitProTag() - d.GetWorktime(u, WorktimeBaseDay, false))
slog.Debug("Added duration as Kurzarbeit", "date", d.Date().String(), "duration", timeTo.Sub(timeFrom).String()) slog.Debug("Added duration as Kurzarbeit", "date", d.Date().String(), "duration", timeTo.Sub(timeFrom).String())
return timeFrom, timeTo return timeFrom, timeTo
} }
func (d *WorkDay) TimeWorkVirtual(u User) time.Duration {
if d.IsKurzArbeit() {
return u.ArbeitszeitProTag()
}
return d.workTime
}
func (d *WorkDay) GetKurzArbeit() *Absence { func (d *WorkDay) GetKurzArbeit() *Absence {
return &d.kurzArbeitAbsence return &d.kurzArbeitAbsence
} }
func (d *WorkDay) TimeWorkReal(u User) time.Duration {
d.realWorkTime, d.realPauseTime = 0, 0
var lastBooking Booking
for _, booking := range d.Bookings {
if booking.CheckInOut%2 == 1 {
if !lastBooking.Timestamp.IsZero() {
d.realPauseTime += booking.Timestamp.Sub(lastBooking.Timestamp)
}
} else {
d.realWorkTime += booking.Timestamp.Sub(lastBooking.Timestamp)
}
lastBooking = booking
}
if helper.IsSameDate(d.Date(), time.Now()) && len(d.Bookings)%2 == 1 {
d.realWorkTime += time.Since(lastBooking.Timestamp.Local())
}
// slog.Debug("Calculated RealWorkTime for user", "user", u, slog.String("worktime", d.realWorkTime.String()))
return d.realWorkTime
}
func (d *WorkDay) TimeOvertimeReal(u User) time.Duration {
workTime := d.TimeWorkVirtual(u)
if workTime == 0 {
workTime, _ = d.TimePauseReal(u)
}
if helper.IsWeekend(d.Day) && len(d.Bookings) == 0 {
return 0
}
var overtime time.Duration
overtime = workTime - u.ArbeitszeitProTag()
return overtime
}
func (d *WorkDay) TimePauseReal(u User) (work, pause time.Duration) {
if d.realWorkTime == 0 {
d.TimeWorkReal(u)
}
d.workTime, d.pauseTime = d.realWorkTime, d.realPauseTime
if d.realWorkTime <= 6*time.Hour || d.realPauseTime > 45*time.Minute {
return d.realWorkTime, d.realPauseTime
}
if d.realWorkTime <= (9*time.Hour) && d.realPauseTime < 30*time.Minute {
diff := 30*time.Minute - d.pauseTime
d.workTime -= diff
d.pauseTime += diff
} else if d.realPauseTime < 45*time.Minute {
diff := 45*time.Minute - d.pauseTime
d.workTime -= diff
d.pauseTime += diff
}
return d.workTime, d.pauseTime
}
func (d *WorkDay) ToString() string { func (d *WorkDay) ToString() string {
return fmt.Sprintf("WorkDay: %s with %d bookings and worktime: %s", d.Date().Format(time.DateOnly), len(d.Bookings), helper.FormatDuration(d.workTime)) return fmt.Sprintf("WorkDay: %s with %d bookings and worktime: %s", d.Date().Format(time.DateOnly), len(d.Bookings), helper.FormatDuration(d.workTime))
} }
@@ -304,22 +175,39 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
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,
@@ -357,6 +245,62 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
GROUP BY d.work_date GROUP BY d.work_date
ORDER BY d.work_date ASC;`) ORDER BY d.work_date ASC;`)
// qStr, err := DB.Prepare(`
// WITH all_days AS (
// SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date),
// ordered_bookings AS (
// SELECT
// a.timestamp::DATE AS work_date,
// a.timestamp,
// a.check_in_out,
// a.counter_id,
// a.anwesenheit_typ,
// sat.anwesenheit_name AS anwesenheit_typ_name,
// LAG(a.timestamp) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_timestamp,
// LAG(a.check_in_out) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) 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
// )
// SELECT
// d.work_date,
// COALESCE(MIN(b.timestamp), NOW()) AS time_from,
// COALESCE(MAX(b.timestamp), NOW()) AS time_to,
// COALESCE(
// EXTRACT(EPOCH FROM SUM(
// CASE
// WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 254)
// THEN b.timestamp - b.prev_timestamp
// ELSE INTERVAL '0'
// END
// )), 0
// ) AS total_work_seconds,
// COALESCE(
// EXTRACT(EPOCH FROM SUM(
// CASE
// WHEN b.prev_check IN (2, 4, 254) AND b.check_in_out IN (1, 3)
// THEN b.timestamp - b.prev_timestamp
// ELSE INTERVAL '0'
// END
// )), 0
// ) AS total_pause_seconds,
// COALESCE(jsonb_agg(jsonb_build_object(
// 'check_in_out', b.check_in_out,
// 'timestamp', b.timestamp,
// 'counter_id', b.counter_id,
// 'anwesenheit_typ', b.anwesenheit_typ,
// 'anwesenheit_typ', jsonb_build_object(
// 'anwesenheit_id', b.anwesenheit_typ,
// 'anwesenheit_name', b.anwesenheit_typ_name
// )
// ) ORDER BY b.timestamp), '[]'::jsonb) AS bookings
// FROM all_days d
// LEFT JOIN ordered_bookings b ON d.work_date = b.work_date
// GROUP BY d.work_date
// ORDER BY d.work_date ASC;`)
if err != nil { if err != nil {
log.Println("Error preparing SQL statement", err) log.Println("Error preparing SQL statement", err)
return workDays return workDays
@@ -369,7 +313,6 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
return workDays return workDays
} }
defer rows.Close() defer rows.Close()
// emptyDays, _ := strconv.ParseBool(helper.GetEnv("EMPTY_DAYS", "false"))
for rows.Next() { for rows.Next() {
var workDay WorkDay var workDay WorkDay
var bookings []byte var bookings []byte
@@ -388,8 +331,7 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
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{}
} }
workDay.TimePauseReal(user) 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)
} }
} }
@@ -400,18 +342,6 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
return workDays return workDays
} }
func (d *WorkDay) GetAllWorkTimesReal(user User) (work, pause, overtime time.Duration) {
if d.pauseTime == 0 || d.workTime == 0 {
d.TimePauseReal(user)
}
return d.workTime.Round(time.Minute), d.pauseTime.Round(time.Minute), d.TimeOvertimeReal(user)
}
func (d *WorkDay) GetAllWorkTimesVirtual(user User) (work, pause, overtime time.Duration) {
_, pause, overtime = d.GetAllWorkTimesReal(user)
return d.TimeWorkVirtual(user), pause, overtime
}
// returns bool wheter the workday was ended with an automatic logout // returns bool wheter the workday was ended with an automatic logout
func (d *WorkDay) RequiresAction() bool { func (d *WorkDay) RequiresAction() bool {
if len(d.Bookings) == 0 { if len(d.Bookings) == 0 {
@@ -424,19 +354,7 @@ func (d *WorkDay) GetDayProgress(u User) int8 {
if d.RequiresAction() { if d.RequiresAction() {
return -1 return -1
} }
workTime := d.TimeWorkVirtual(u) workTime := d.GetWorktime(u, WorktimeBaseDay, true)
progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100 progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100
return int8(progress) return int8(progress)
} }
// func (d *WorkDay) CalcOvertime(user User) time.Duration {
// if d.workTime == 0 {
// d.TimePauseReal(user)
// }
// if helper.IsWeekend(d.Day) && len(d.Bookings) == 0 {
// return 0
// }
// var overtime time.Duration
// overtime = d.workTime - user.ArbeitszeitProTag()
// return overtime
// }

View File

@@ -22,59 +22,141 @@ var testWorkDay = models.WorkDay{
TimeTo: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:30")), TimeTo: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:30")),
} }
func TestCalcRealWorkTime(t *testing.T) { func TestWorkdayWorktimeDay(t *testing.T) {
workTime := testWorkDay.TimeWorkReal(testUser) testCases := []struct {
if workTime != time.Hour*8 { testName string
t.Errorf("Calc Worktime Default not working, time should be 8h, but was %s", helper.FormatDuration(workTime)) bookings []models.Booking
} expectedTime time.Duration
} }{
{
func TestCalcWorkPauseDiff(t *testing.T) { testName: "Bookings6hrs",
type testCase struct { bookings: testBookings6hrs,
Name string expectedTime: time.Hour * 6,
bookings []models.Booking
expectedWorkTime time.Duration
expectedPauseTime time.Duration
expectedOvertime time.Duration
}
testCases := []testCase{testCase{
Name: "6hrs no pause",
bookings: testBookings6hrs,
expectedWorkTime: 6 * time.Hour,
expectedPauseTime: 0,
expectedOvertime: -2 * time.Hour,
},
testCase{
Name: "8hrs - 30min pause",
bookings: testBookings8hrs,
expectedWorkTime: 7*time.Hour + 30*time.Minute,
expectedPauseTime: 30 * time.Minute,
expectedOvertime: -30 * time.Minute,
}, },
testCase{ {
Name: "10hrs - 45min pause", testName: "Bookings8hrs",
bookings: testBookings10hrs, bookings: testBookings8hrs,
expectedWorkTime: 9*time.Hour + 15*time.Minute, expectedTime: time.Hour*7 + time.Minute*30,
expectedPauseTime: 45 * time.Minute, },
expectedOvertime: 1*time.Hour + 15*time.Minute, {
}} testName: "Bookings10hrs",
bookings: testBookings10hrs,
expectedTime: time.Hour*9 + time.Minute*15,
},
}
for _, test := range testCases { for _, tc := range testCases {
t.Run(test.Name, func(t *testing.T) { t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
testWorkDay.Bookings = test.bookings var testCase = testWorkDay
testWorkDay.TimeWorkReal(testUser) testCase.Bookings = tc.bookings
testWorkDay.TimePauseReal(testUser) workTime := testCase.GetWorktime(testUser, models.WorktimeBaseDay, false)
testWorkDay.TimeOvertimeReal(testUser) if workTime != tc.expectedTime {
workTime, pauseTime, overTime := testWorkDay.GetAllWorkTimesReal(testUser) t.Errorf("GetWorktimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
if workTime != test.expectedWorkTime { }
t.Errorf("Calculated wrong workTime: should be %s, but was %s", helper.FormatDuration(test.expectedWorkTime), helper.FormatDuration(workTime)) })
} }
if pauseTime != test.expectedPauseTime { }
t.Errorf("Calculated wrong pauseTime: should be %s, but was %s", helper.FormatDuration(test.expectedPauseTime), helper.FormatDuration(pauseTime))
} func TestWorkdayWorktimeWeek(t *testing.T) {
if overTime != test.expectedOvertime { testCases := []struct {
t.Errorf("Calculated wrong overtime: should be %s, but was %s", helper.FormatDuration(test.expectedOvertime), helper.FormatDuration(overTime)) testName string
bookings []models.Booking
expectedTime time.Duration
}{
{
testName: "Bookings6hrs",
bookings: testBookings6hrs,
expectedTime: time.Hour * 6,
},
{
testName: "Bookings8hrs",
bookings: testBookings8hrs,
expectedTime: time.Hour*7 + time.Minute*30,
},
{
testName: "Bookings10hrs",
bookings: testBookings10hrs,
expectedTime: time.Hour*9 + time.Minute*15,
},
}
for _, tc := range testCases {
t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
var testCase = testWorkDay
testCase.Bookings = tc.bookings
workTime := testCase.GetWorktime(testUser, models.WorktimeBaseWeek, false)
if workTime != tc.expectedTime {
t.Errorf("GetWorktimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
}
})
}
}
func TestWorkdayPausetimeDay(t *testing.T) {
testCases := []struct {
testName string
bookings []models.Booking
expectedTime time.Duration
}{
{
testName: "Bookings6hrs",
bookings: testBookings6hrs,
expectedTime: 0,
},
{
testName: "Bookings8hrs",
bookings: testBookings8hrs,
expectedTime: time.Minute * 30,
},
{
testName: "Bookings10hrs",
bookings: testBookings10hrs,
expectedTime: time.Minute * 45,
},
}
for _, tc := range testCases {
t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
var testCase = testWorkDay
testCase.Bookings = tc.bookings
workTime := testCase.GetPausetime(testUser, models.WorktimeBaseDay, false)
if workTime != tc.expectedTime {
t.Errorf("GetPausetimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
}
})
}
}
func TestWorkdayPausetimeWeek(t *testing.T) {
testCases := []struct {
testName string
bookings []models.Booking
expectedTime time.Duration
}{
{
testName: "Bookings6hrs",
bookings: testBookings6hrs,
expectedTime: 0,
},
{
testName: "Bookings8hrs",
bookings: testBookings8hrs,
expectedTime: time.Minute * 30,
},
{
testName: "Bookings10hrs",
bookings: testBookings10hrs,
expectedTime: time.Minute * 45,
},
}
for _, tc := range testCases {
t.Run("Calc Absence Worktime: "+tc.testName, func(t *testing.T) {
var testCase = testWorkDay
testCase.Bookings = tc.bookings
workTime := testCase.GetPausetime(testUser, models.WorktimeBaseWeek, false)
if workTime != tc.expectedTime {
t.Errorf("GetPausetimeReal not working, time should be %s, but was %s", helper.FormatDurationFill(tc.expectedTime, true), helper.FormatDurationFill(workTime, true))
} }
}) })
} }

View File

@@ -20,7 +20,7 @@ type WorkWeek struct {
User User User User
WeekStart time.Time WeekStart time.Time
Worktime time.Duration Worktime time.Duration
WorkTimeVirtual time.Duration WorktimeVirtual time.Duration
Overtime time.Duration Overtime time.Duration
Status WeekStatus Status WeekStatus
} }
@@ -47,17 +47,19 @@ func NewWorkWeek(user User, tsMonday time.Time, populate bool) WorkWeek {
} }
func (w *WorkWeek) PopulateWithDays(worktime time.Duration, overtime time.Duration) { func (w *WorkWeek) PopulateWithDays(worktime time.Duration, overtime time.Duration) {
slog.Debug("Populating Workweek for user", "user", w.User)
slog.Debug("Got Days with overtime and worktime", slog.String("worktime", worktime.String()), slog.String("overtime", overtime.String())) slog.Debug("Got Days with overtime and worktime", slog.String("worktime", worktime.String()), slog.String("overtime", overtime.String()))
w.Days = GetDays(w.User, w.WeekStart, w.WeekStart.Add(6*24*time.Hour), false) w.Days = GetDays(w.User, w.WeekStart, w.WeekStart.Add(6*24*time.Hour), false)
for _, day := range w.Days { for _, day := range w.Days {
work, _ := day.TimePauseReal(w.User) w.Worktime += day.GetWorktime(w.User, WorktimeBaseDay, false)
w.Worktime += work w.WorktimeVirtual += day.GetWorktime(w.User, WorktimeBaseDay, true)
w.WorkTimeVirtual += day.TimeWorkVirtual(w.User)
} }
slog.Debug("Got worktime for user", "user", w.User, "worktime", w.Worktime.String(), "virtualWorkTime", w.WorkTimeVirtual.String()) slog.Debug("Got worktime for user", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorktimeVirtual.String())
w.Overtime = w.WorkTimeVirtual - w.User.ArbeitszeitProWoche() w.Overtime = w.WorktimeVirtual - w.User.ArbeitszeitProWoche()
slog.Debug("Calculated overtime", "worktime", w.Worktime.String(), "virtualWorkTime", w.WorktimeVirtual.String())
w.Worktime = w.Worktime.Round(time.Minute) w.Worktime = w.Worktime.Round(time.Minute)
w.Overtime = w.Overtime.Round(time.Minute) w.Overtime = w.Overtime.Round(time.Minute)

View File

@@ -12,7 +12,6 @@
--color-red-700: oklch(50.5% 0.213 27.518); --color-red-700: oklch(50.5% 0.213 27.518);
--color-orange-500: oklch(70.5% 0.213 47.604); --color-orange-500: oklch(70.5% 0.213 47.604);
--color-purple-600: oklch(55.8% 0.288 302.321); --color-purple-600: oklch(55.8% 0.288 302.321);
--color-slate-300: oklch(86.9% 0.022 252.894);
--color-slate-600: oklch(44.6% 0.043 257.281); --color-slate-600: oklch(44.6% 0.043 257.281);
--color-slate-700: oklch(37.2% 0.044 257.287); --color-slate-700: oklch(37.2% 0.044 257.287);
--color-slate-800: oklch(27.9% 0.041 260.031); --color-slate-800: oklch(27.9% 0.041 260.031);
@@ -200,15 +199,15 @@
.relative { .relative {
position: relative; position: relative;
} }
.top-1 { .sticky {
top: calc(var(--spacing) * 1); position: sticky;
}
.top-0 {
top: calc(var(--spacing) * 0);
} }
.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);
} }
@@ -218,18 +217,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%);
} }
.z-100 {
z-index: 100;
}
.col-span-2 { .col-span-2 {
grid-column: span 2 / span 2; grid-column: span 2 / span 2;
} }
@@ -387,9 +383,6 @@
.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);
} }
@@ -402,6 +395,9 @@
.h-8 { .h-8 {
height: calc(var(--spacing) * 8); height: calc(var(--spacing) * 8);
} }
.h-10 {
height: calc(var(--spacing) * 10);
}
.h-\[100vh\] { .h-\[100vh\] {
height: 100vh; height: 100vh;
} }
@@ -411,9 +407,6 @@
.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);
} }
@@ -423,9 +416,6 @@
.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%);
} }
@@ -438,9 +428,6 @@
.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;
} }
@@ -456,21 +443,10 @@
.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);
@@ -481,9 +457,6 @@
.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);
} }
@@ -617,12 +590,6 @@
.border-neutral-600 { .border-neutral-600 {
border-color: var(--color-neutral-600); border-color: var(--color-neutral-600);
} }
.border-slate-300 {
border-color: var(--color-slate-300);
}
.border-slate-700 {
border-color: var(--color-slate-700);
}
.border-slate-800 { .border-slate-800 {
border-color: var(--color-slate-800); border-color: var(--color-slate-800);
} }
@@ -647,9 +614,6 @@
.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);
} }
@@ -726,16 +690,9 @@
.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,);
} }
@@ -1181,11 +1138,6 @@
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;
@@ -1258,7 +1210,6 @@
--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

@@ -15,7 +15,9 @@ function editWorkday(element, event, id, isWorkDay) {
event.preventDefault(); event.preventDefault();
let form = document.getElementById(id); let form = document.getElementById(id);
if (form == null) { if (form == null) {
form = element.closest(".grid-sub").querySelector(".all-booking-component > form"); form = element
.closest(".grid-sub")
.querySelector(".all-booking-component > form");
} }
clearEditState(); clearEditState();
@@ -37,10 +39,21 @@ function editWorkday(element, event, id, isWorkDay) {
const absenceForm = document.getElementById("absence_form"); const absenceForm = document.getElementById("absence_form");
if (id == 0) { if (id == 0) {
absenceForm.querySelector("[name=date_from]").value = form.id.replace("time-", ""); absenceForm.querySelector("[name=date_from]").value = form.id.replace(
absenceForm.querySelector("[name=date_to]").value = form.id.replace("time-", ""); "time-",
"",
);
absenceForm.querySelector("[name=date_to]").value = form.id.replace(
"time-",
"",
);
} else { } else {
syncFields(form, absenceForm, ["date_from", "date_to", "aw_type", "aw_id"]); syncFields(form, absenceForm, [
"date_from",
"date_to",
"aw_type",
"aw_id",
]);
} }
} }
} }
@@ -49,11 +62,6 @@ function toggleAbsenceEdit(state) {
const form = document.getElementById("absence_form"); const form = document.getElementById("absence_form");
if (state) { if (state) {
form.classList.remove("hidden"); form.classList.remove("hidden");
form.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest",
});
} else { } else {
form.classList.add("hidden"); form.classList.add("hidden");
} }
@@ -85,3 +93,12 @@ function checkAll(pattern, state) {
input.checked = state; input.checked = state;
} }
} }
const bookingForms = document.querySelectorAll("form.bookings");
for (let form of bookingForms) {
let selectKommenInput = form.querySelector("input[name='select_kommen']");
let kommenGehenSelector = form.querySelector("select");
if (selectKommenInput) {
kommenGehenSelector.value = selectKommenInput.value == "true" ? 3 : 4;
}
}

View File

@@ -1,11 +1,12 @@
package templates package templates
templ headerComponent() { templ headerComponent() {
// {{ user := ctx.Value("user").(models.User) }}
<div class="flex flex-row justify-between md:mx-[10%] py-2 items-center"> <div class="flex flex-row justify-between md:mx-[10%] py-2 items-center">
<a href="/time">Zeitverwaltung</a> <a href="/time">Zeitverwaltung</a>
<a href="/team">Abrechnung</a> <a href="/team">Abrechnung</a>
<a href="/pdf">PDF</a>
if true { if true {
<a href="/pdf">Monatsabrechnung</a>
<a href="/presence">Anwesenheit</a> <a href="/presence">Anwesenheit</a>
} }
<a href="/user/settings">Einstellungen</a> <a href="/user/settings">Einstellungen</a>

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924 // templ: version: v0.3.960
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -29,12 +29,12 @@ func headerComponent() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"flex flex-row justify-between md:mx-[10%] py-2 items-center\"><a href=\"/time\">Zeitverwaltung</a> <a href=\"/team\">Abrechnung</a> <a href=\"/pdf\">PDF</a> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"flex flex-row justify-between md:mx-[10%] py-2 items-center\"><a href=\"/time\">Zeitverwaltung</a> <a href=\"/team\">Abrechnung</a> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if true { if true {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a href=\"/presence\">Anwesenheit</a> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a href=\"/pdf\">Monatsabrechnung</a> <a href=\"/presence\">Anwesenheit</a> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@@ -1,6 +1,7 @@
package templates package templates
import "arbeitszeitmessung/models" import "arbeitszeitmessung/models"
import "arbeitszeitmessung/helper"
templ Base() { templ Base() {
<!DOCTYPE html> <!DOCTYPE html>
@@ -29,9 +30,7 @@ templ LoginPage(success bool, errorMsg string) {
} }
templ SettingsPage(status int) { templ SettingsPage(status int) {
{{ {{ user := ctx.Value("user").(models.User) }}
user := ctx.Value("user").(models.User)
}}
@Base() @Base()
@headerComponent() @headerComponent()
<div class="grid-main divide-y-1"> <div class="grid-main divide-y-1">
@@ -59,6 +58,8 @@ templ SettingsPage(status int) {
<div class="grid-cell col-span-3"> <div class="grid-cell col-span-3">
<p>Nutzername: <span class="text-neutral-500">{ user.Vorname } { user.Name }</span></p> <p>Nutzername: <span class="text-neutral-500">{ user.Vorname } { user.Name }</span></p>
<p>Personalnummer: <span class="text-neutral-500">{ user.PersonalNummer }</span></p> <p>Personalnummer: <span class="text-neutral-500">{ user.PersonalNummer }</span></p>
<p>Arbeitszeit pro Tag: <span class="text-neutral-500">{ helper.FormatDuration(user.ArbeitszeitProTag()) }</span></p>
<p>Arbeitszeit pro Woche: <span class="text-neutral-500">{ helper.FormatDuration(user.ArbeitszeitProWoche()) }</span></p>
</div> </div>
<div></div> <div></div>
</div> </div>
@@ -82,27 +83,6 @@ templ statusCheckMark(status models.WeekStatus, target models.WeekStatus) {
} }
} }
templ TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) {
@Base()
@headerComponent()
<div class="grid-main divide-y-1">
<div class="grid-sub lg:divide-x-1 max-md:divide-y-1 responsive @container">
<div class="grid-cell col-span-full bg-neutral-300 lg:border-0">
<h2 class="text-xl uppercase font-bold">Eigene Abrechnung</h2>
</div>
</div>
@workWeekComponent(userWeek, false)
if len(weeks) > 0 {
<div class="grid-cell col-span-full bg-neutral-300">
<h2 class="text-xl uppercase font-bold">Abrechnung Mitarbeiter</h2>
</div>
}
for _, week := range weeks {
@workWeekComponent(week, true)
}
</div>
}
templ LogoutButton() { templ LogoutButton() {
<button onclick="logoutUser()" type="button" class="cursor-pointer">Abmelden</button> <button onclick="logoutUser()" type="button" class="cursor-pointer">Abmelden</button>
} }

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924 // templ: version: v0.3.960
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -9,6 +9,7 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime" import templruntime "github.com/a-h/templ/runtime"
import "arbeitszeitmessung/models" import "arbeitszeitmessung/models"
import "arbeitszeitmessung/helper"
func Base() templ.Component { func Base() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
@@ -76,7 +77,7 @@ func LoginPage(success bool, errorMsg string) templ.Component {
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(errorMsg) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(errorMsg)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 24, Col: 46} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 25, Col: 46}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -116,7 +117,6 @@ func SettingsPage(status int) templ.Component {
templ_7745c5c3_Var4 = templ.NopComponent templ_7745c5c3_Var4 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
user := ctx.Value("user").(models.User) user := ctx.Value("user").(models.User)
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -154,7 +154,7 @@ func SettingsPage(status int) templ.Component {
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname) templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 60, Col: 64} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 59, Col: 64}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -167,7 +167,7 @@ func SettingsPage(status int) templ.Component {
var templ_7745c5c3_Var6 string var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name) templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 60, Col: 78} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 59, Col: 78}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -180,13 +180,39 @@ func SettingsPage(status int) templ.Component {
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(user.PersonalNummer) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(user.PersonalNummer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 61, Col: 75} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 60, Col: 75}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span></p></div><div></div></div><div class=\"grid-sub responsive lg:divide-x-1\"><h1 class=\"grid-cell font-bold uppercase text-xl text-center\">Nutzer abmelden</h1><div class=\"grid-cell col-span-3\"><p>Nutzer von Weboberfläche abmelden.</p></div><div class=\"grid-cell\"><button onclick=\"logoutUser\" type=\"button\" class=\"btn\">Abmelden</button></div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span></p><p>Arbeitszeit pro Tag: <span class=\"text-neutral-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(user.ArbeitszeitProTag()))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 61, Col: 108}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</span></p><p>Arbeitszeit pro Woche: <span class=\"text-neutral-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(user.ArbeitszeitProWoche()))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 62, Col: 112}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span></p></div><div></div></div><div class=\"grid-sub responsive lg:divide-x-1\"><h1 class=\"grid-cell font-bold uppercase text-xl text-center\">Nutzer abmelden</h1><div class=\"grid-cell col-span-3\"><p>Nutzer von Weboberfläche abmelden.</p></div><div class=\"grid-cell\"><button onclick=\"logoutUser\" type=\"button\" class=\"btn\">Abmelden</button></div></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -210,18 +236,18 @@ func statusCheckMark(status models.WeekStatus, target models.WeekStatus) templ.C
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx) templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil { if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var8 = templ.NopComponent templ_7745c5c3_Var10 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
if status >= target { if status >= target {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"icon-[material-symbols-light--check-circle-outline]\"></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div class=\"icon-[material-symbols-light--check-circle-outline]\"></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} else { } else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"icon-[material-symbols-light--circle-outline]\"></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"icon-[material-symbols-light--circle-outline]\"></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -230,63 +256,6 @@ func statusCheckMark(status models.WeekStatus, target models.WeekStatus) templ.C
}) })
} }
func TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = headerComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div class=\"grid-main divide-y-1\"><div class=\"grid-sub lg:divide-x-1 max-md:divide-y-1 responsive @container\"><div class=\"grid-cell col-span-full bg-neutral-300 lg:border-0\"><h2 class=\"text-xl uppercase font-bold\">Eigene Abrechnung</h2></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = workWeekComponent(userWeek, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(weeks) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"grid-cell col-span-full bg-neutral-300\"><h2 class=\"text-xl uppercase font-bold\">Abrechnung Mitarbeiter</h2></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, week := range weeks {
templ_7745c5c3_Err = workWeekComponent(week, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func LogoutButton() templ.Component { func LogoutButton() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -303,12 +272,12 @@ func LogoutButton() templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx) templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil { if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var10 = templ.NopComponent templ_7745c5c3_Var11 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<button onclick=\"logoutUser()\" type=\"button\" class=\"cursor-pointer\">Abmelden</button>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<button onclick=\"logoutUser()\" type=\"button\" class=\"cursor-pointer\">Abmelden</button>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@@ -10,15 +10,15 @@ import (
templ PDFForm(teamMembers []models.User) { templ PDFForm(teamMembers []models.User) {
@Base() @Base()
@headerComponent() @headerComponent()
<div class="grid-main divide-y-1"> <form class="grid-main divide-y-1" action="pdf/generate" method="get">
<div class="grid-cell col-span-full bg-neutral-300"> <div class="grid-cell col-span-full bg-neutral-300">
<h1 class="text-xl uppercase font-bold">PDF Abrechnung erstellen</h1> <h1 class="text-xl uppercase font-bold">Monatsabrechnung erstellen</h1>
</div> </div>
<div class="grid-sub divide-x-1 responsive"> <div class="grid-sub divide-x-1 responsive">
<div class="grid-cell">Zeitraum wählen</div> <div class="grid-cell">Zeitraum wählen</div>
<div class="grid-cell col-span-3"> <div class="grid-cell col-span-3">
<label class="block mb-1 text-sm text-neutral-700">Abrechnungsmonat</label> <label class="block mb-1 text-sm text-neutral-700">Abrechnungsmonat</label>
<input name="start_date" type="date" value="" class="btn bg-neutral-100"/> <input name="start_date" type="date" value={ helper.GetFirstOfMonth(time.Now()).Format(time.DateOnly) } class="btn bg-neutral-100"/>
</div> </div>
<div></div> <div></div>
</div> </div>
@@ -30,7 +30,7 @@ templ PDFForm(teamMembers []models.User) {
<button class="btn" type="button" onclick={ templ.JSFuncCall("checkAll", "pdf-", templ.JSExpression("false")) }>Keine</button> <button class="btn" type="button" onclick={ templ.JSFuncCall("checkAll", "pdf-", templ.JSExpression("false")) }>Keine</button>
</div> </div>
for _, member := range teamMembers { for _, member := range teamMembers {
@CheckboxComponent(fmt.Sprintf("pdf-%d", member.PersonalNummer), fmt.Sprintf("%s %s", member.Vorname, member.Name)) @CheckboxComponent(member.PersonalNummer, fmt.Sprintf("%s %s", member.Vorname, member.Name))
} }
</div> </div>
<div></div> <div></div>
@@ -38,17 +38,18 @@ templ PDFForm(teamMembers []models.User) {
<div class="grid-sub divide-x-1 responsive"> <div class="grid-sub divide-x-1 responsive">
<div class="grid-cell">PDFs Bündeln</div> <div class="grid-cell">PDFs Bündeln</div>
<div class="grid-cell col-span-3 flex gap-2 flex-col md:flex-row"> <div class="grid-cell col-span-3 flex gap-2 flex-col md:flex-row">
<button class="btn" type="button" name="action" value="download">Einzeln</button> <button class="btn" type="submit" name="output" value="download">Einzeln</button>
<button class="btn" type="button" name="action" value="preview" onclick="">Bündel</button> <button class="btn" type="submit" name="output" value="render" onclick="">Bündel</button>
</div> </div>
</div> </div>
</div> </form>
} }
templ CheckboxComponent(id, label string) { templ CheckboxComponent(pNr int, label string) {
{{ id := fmt.Sprintf("pdf-%d", pNr) }}
<div class="inline-flex items-center"> <div class="inline-flex items-center">
<label class="flex items-center cursor-pointer relative" for={ id }> <label class="flex items-center cursor-pointer relative" for={ id }>
<input type="checkbox" class="peer h-5 w-5 cursor-pointer transition-all appearance-none rounded border border-slate-800 checked:bg-slate-800 checked:border-slate-800" id={ id }/> <input type="checkbox" name="employe_list" value={ pNr } id={ id } class="peer h-5 w-5 cursor-pointer transition-all appearance-none rounded border border-slate-800 checked:bg-slate-800 checked:border-slate-800"/>
<span class="absolute text-white opacity-0 peer-checked:opacity-100 top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"> <span class="absolute text-white opacity-0 peer-checked:opacity-100 top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor" stroke="currentColor" stroke-width="1"> <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor" stroke="currentColor" stroke-width="1">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path> <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
@@ -58,77 +59,76 @@ templ CheckboxComponent(id, label string) {
</div> </div>
} }
templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) { // templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) {
{{ // {{
_, kw := tsStart.ISOWeek() // _, kw := tsStart.ISOWeek()
noBorder := "" // noBorder := ""
}} // }}
@Base() // @Base()
<content class="p-8 relative flex flex-col gap-4 break-after-page"> // <content class="p-8 relative flex flex-col gap-4 break-after-page">
<div> // <div>
<h1 class="text-2xl font-bold">{ e.Vorname } { e.Name }</h1> // <h1 class="text-2xl font-bold">{ e.Vorname } { e.Name }</h1>
<p>Zeitraum: <span>{ tsStart.Format("02.01.2006") }</span> - <span>{ tsEnd.Format("02.01.2006") }</span></p> // <p>Zeitraum: <span>{ tsStart.Format("02.01.2006") }</span> - <span>{ tsEnd.Format("02.01.2006") }</span></p>
<p>Arbeitszeit: <span>{ helper.FormatDuration(worktime) }</span></p> // <p>Arbeitszeit: <span>{ helper.FormatDuration(worktime) }</span></p>
<p>Überstunden: <span>{ helper.FormatDuration(overtime) }</span></p> // <p>Überstunden: <span>{ helper.FormatDuration(overtime) }</span></p>
</div> // </div>
<div class="grid grid-rows-6 grid-cols-[3fr_2fr_2fr_2fr_3fr_3fr_3fr] *:not-print:p-2 *:text-center auto-rows-min divide-neutral-300 divide-x-1 divide-y-1"> // <div class="grid grid-rows-6 grid-cols-[3fr_2fr_2fr_2fr_3fr_3fr_3fr] *:not-print:p-2 *:text-center auto-rows-min divide-neutral-300 divide-x-1 divide-y-1">
<p class="bg-neutral-300 border-neutral-600">{ kw }</p> // <p class="bg-neutral-300 border-neutral-600">{ kw }</p>
<p class="bg-neutral-300 border-neutral-600">Kommen</p> // <p class="bg-neutral-300 border-neutral-600">Kommen</p>
<p class="bg-neutral-300 border-neutral-600">Gehen</p> // <p class="bg-neutral-300 border-neutral-600">Gehen</p>
<p class="bg-neutral-300 border-neutral-600">Arbeitsart</p> // <p class="bg-neutral-300 border-neutral-600">Arbeitsart</p>
<p class="bg-neutral-300 border-neutral-600">Stunden</p> // <p class="bg-neutral-300 border-neutral-600">Stunden</p>
<p class="bg-neutral-300 border-neutral-600">Pause</p> // <p class="bg-neutral-300 border-neutral-600">Pause</p>
<p class="bg-neutral-300 border-neutral-600 border-r-0">Überstunden</p> // <p class="bg-neutral-300 border-neutral-600 border-r-0">Überstunden</p>
for index, day := range workDays { // for index, day := range workDays {
{{ // {{
if index == len(workDays)-1 { // if index == len(workDays)-1 {
noBorder = "border-b-0" // noBorder = "border-b-0"
} // }
}} // }}
<p class={ noBorder }>{ day.Date().Format("02.01.2006") }</p> // <p class={ noBorder }>{ day.Date().Format("02.01.2006") }</p>
<div class={ "grid grid-cols-subgrid col-span-3 " + noBorder }> // <div class={ "grid grid-cols-subgrid col-span-3 " + noBorder }>
if day.IsWorkDay() { // if day.IsWorkDay() {
{{ // {{
workDay, _ := day.(*models.WorkDay) // workDay, _ := day.(*models.WorkDay)
}} // }}
for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 { // for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 {
<p>{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }</p> // <p>{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }</p>
<p>{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }</p> // <p>{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }</p>
<p>{ workDay.Bookings[bookingI].BookingType.Name } </p> // <p>{ workDay.Bookings[bookingI].BookingType.Name } </p>
} // }
if workDay.IsKurzArbeit() { // if workDay.IsKurzArbeit() {
{{ // {{
timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e) // timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
}} // }}
<p>{ timeFrom.Format("15:04") }</p> // <p>{ timeFrom.Format("15:04") }</p>
<p>{ timeTo.Format("15:04") }</p> // <p>{ timeTo.Format("15:04") }</p>
<p>Kurzarbeit</p> // <p>Kurzarbeit</p>
} // }
} else { // } else {
{{ // {{
absentDay, _ := day.(*models.Absence) // absentDay, _ := day.(*models.Absence)
}} // }}
<p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p> // <p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p>
} // }
</div> // </div>
{{ work, pause, overtime := day.GetAllWorkTimesVirtual(e) }} // {{ work, pause, overtime := day.GetTimesVirtual(e) }}
@ColorDuration(work, noBorder) // @ColorDuration(work, noBorder)
@ColorDuration(pause, noBorder) // @ColorDuration(pause, noBorder)
@ColorDuration(overtime, noBorder+" border-r-0") // @ColorDuration(overtime, noBorder+" border-r-0")
if day.Date().Weekday() == time.Friday { // if day.Date().Weekday() == time.Friday {
<p class="col-span-full bg-neutral-300">Wochenende</p> // <p class="col-span-full bg-neutral-300">Wochenende</p>
} // }
} // }
</div> // </div>
</content> // </content>
} // }
templ ColorDuration(d time.Duration, classes string) { templ ColorDuration(d time.Duration, classes string) {
{{ {{
color := "" color := ""
if d.Abs() < time.Minute { if d.Abs() < time.Minute {
color = "text-neutral-300" color = "text-neutral-300"
} }
}} }}
<p class={ color + " " + classes }>{ helper.FormatDurationFill(d, true) }</p> <p class={ color + " " + classes }>{ helper.FormatDurationFill(d, true) }</p>
} }

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924 // templ: version: v0.3.960
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -44,7 +44,20 @@ func PDFForm(teamMembers []models.User) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"grid-main divide-y-1\"><div class=\"grid-cell col-span-full bg-neutral-300\"><h1 class=\"text-xl uppercase font-bold\">PDF Abrechnung erstellen</h1></div><div class=\"grid-sub divide-x-1 responsive\"><div class=\"grid-cell\">Zeitraum wählen</div><div class=\"grid-cell col-span-3\"><label class=\"block mb-1 text-sm text-neutral-700\">Abrechnungsmonat</label> <input name=\"start_date\" type=\"date\" value=\"\" class=\"btn bg-neutral-100\"></div><div></div></div><div class=\"grid-sub divide-x-1 responsive\"><div class=\"grid-cell\">Mitarbeiter wählen</div><div class=\"grid-cell col-span-3 flex flex-col gap-2\"><div class=\"flex flex-row gap-2\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<form class=\"grid-main divide-y-1\" action=\"pdf/generate\" method=\"get\"><div class=\"grid-cell col-span-full bg-neutral-300\"><h1 class=\"text-xl uppercase font-bold\">Monatsabrechnung erstellen</h1></div><div class=\"grid-sub divide-x-1 responsive\"><div class=\"grid-cell\">Zeitraum wählen</div><div class=\"grid-cell col-span-3\"><label class=\"block mb-1 text-sm text-neutral-700\">Abrechnungsmonat</label> <input name=\"start_date\" type=\"date\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(helper.GetFirstOfMonth(time.Now()).Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 21, Col: 105}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"btn bg-neutral-100\"></div><div></div></div><div class=\"grid-sub divide-x-1 responsive\"><div class=\"grid-cell\">Mitarbeiter wählen</div><div class=\"grid-cell col-span-3 flex flex-col gap-2\"><div class=\"flex flex-row gap-2\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -52,16 +65,16 @@ func PDFForm(teamMembers []models.User) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<button class=\"btn\" type=\"button\" onclick=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<button class=\"btn\" type=\"button\" onclick=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var2 templ.ComponentScript = templ.JSFuncCall("checkAll", "pdf-", templ.JSExpression("true")) var templ_7745c5c3_Var3 templ.ComponentScript = templ.JSFuncCall("checkAll", "pdf-", templ.JSExpression("true"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2.Call) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3.Call)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">Alle</button> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">Alle</button> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -69,26 +82,26 @@ func PDFForm(teamMembers []models.User) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<button class=\"btn\" type=\"button\" onclick=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<button class=\"btn\" type=\"button\" onclick=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 templ.ComponentScript = templ.JSFuncCall("checkAll", "pdf-", templ.JSExpression("false")) var templ_7745c5c3_Var4 templ.ComponentScript = templ.JSFuncCall("checkAll", "pdf-", templ.JSExpression("false"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3.Call) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4.Call)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">Keine</button></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Keine</button></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, member := range teamMembers { for _, member := range teamMembers {
templ_7745c5c3_Err = CheckboxComponent(fmt.Sprintf("pdf-%d", member.PersonalNummer), fmt.Sprintf("%s %s", member.Vorname, member.Name)).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = CheckboxComponent(member.PersonalNummer, fmt.Sprintf("%s %s", member.Vorname, member.Name)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><div></div></div><div class=\"grid-sub divide-x-1 responsive\"><div class=\"grid-cell\">PDFs Bündeln</div><div class=\"grid-cell col-span-3 flex gap-2 flex-col md:flex-row\"><button class=\"btn\" type=\"button\" name=\"action\" value=\"download\">Einzeln</button> <button class=\"btn\" type=\"button\" name=\"action\" value=\"preview\" onclick=\"\">Bündel</button></div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div><div></div></div><div class=\"grid-sub divide-x-1 responsive\"><div class=\"grid-cell\">PDFs Bündeln</div><div class=\"grid-cell col-span-3 flex gap-2 flex-col md:flex-row\"><button class=\"btn\" type=\"submit\" name=\"output\" value=\"download\">Einzeln</button> <button class=\"btn\" type=\"submit\" name=\"output\" value=\"render\" onclick=\"\">Bündel</button></div></div></form>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -96,7 +109,7 @@ func PDFForm(teamMembers []models.User) templ.Component {
}) })
} }
func CheckboxComponent(id, label string) templ.Component { func CheckboxComponent(pNr int, label string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -112,400 +125,78 @@ func CheckboxComponent(id, label string) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx) templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil { if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var4 = templ.NopComponent templ_7745c5c3_Var5 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"inline-flex items-center\"><label class=\"flex items-center cursor-pointer relative\" for=\"") id := fmt.Sprintf("pdf-%d", pNr)
if templ_7745c5c3_Err != nil { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"inline-flex items-center\"><label class=\"flex items-center cursor-pointer relative\" for=\"")
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 50, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><input type=\"checkbox\" class=\"peer h-5 w-5 cursor-pointer transition-all appearance-none rounded border border-slate-800 checked:bg-slate-800 checked:border-slate-800\" id=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var6 string var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(id) templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 51, Col: 178} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 51, Col: 67}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"> <span class=\"absolute text-white opacity-0 peer-checked:opacity-100 top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3.5 w-3.5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"1\"><path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\"></path></svg></span></label> <label class=\"cursor-pointer ml-2 text-slate-600 select-none\" for=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"><input type=\"checkbox\" name=\"employe_list\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(id) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(pNr)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 57, Col: 81} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 52, Col: 57}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" id=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(label) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 57, Col: 91} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 52, Col: 67}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</label></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" class=\"peer h-5 w-5 cursor-pointer transition-all appearance-none rounded border border-slate-800 checked:bg-slate-800 checked:border-slate-800\"> <span class=\"absolute text-white opacity-0 peer-checked:opacity-100 top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3.5 w-3.5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"1\"><path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\"></path></svg></span></label> <label class=\"cursor-pointer ml-2 text-slate-600 select-none\" for=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
return nil var templ_7745c5c3_Var9 string
}) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(id)
} if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 58, Col: 81}
func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
} }
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, kw := tsStart.ISOWeek()
noBorder := ""
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<content class=\"p-8 relative flex flex-col gap-4 break-after-page\"><div><h1 class=\"text-2xl font-bold\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var10 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(e.Vorname) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 69, Col: 45} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 58, Col: 91}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</label></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 69, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</h1><p>Zeitraum: <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(tsStart.Format("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 70, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span> - <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(tsEnd.Format("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 70, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</span></p><p>Arbeitszeit: <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(worktime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 71, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</span></p><p>Überstunden: <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(overtime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 72, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</span></p></div><div class=\"grid grid-rows-6 grid-cols-[3fr_2fr_2fr_2fr_3fr_3fr_3fr] *:not-print:p-2 *:text-center auto-rows-min divide-neutral-300 divide-x-1 divide-y-1\"><p class=\"bg-neutral-300 border-neutral-600\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(kw)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 75, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</p><p class=\"bg-neutral-300 border-neutral-600\">Kommen</p><p class=\"bg-neutral-300 border-neutral-600\">Gehen</p><p class=\"bg-neutral-300 border-neutral-600\">Arbeitsart</p><p class=\"bg-neutral-300 border-neutral-600\">Stunden</p><p class=\"bg-neutral-300 border-neutral-600\">Pause</p><p class=\"bg-neutral-300 border-neutral-600 border-r-0\">Überstunden</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for index, day := range workDays {
if index == len(workDays)-1 {
noBorder = "border-b-0"
}
var templ_7745c5c3_Var17 = []any{noBorder}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var17).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(day.Date().Format("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 88, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 = []any{"grid grid-cols-subgrid col-span-3 " + noBorder}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var20).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay)
for bookingI := 0; bookingI < len(workDay.Bookings); bookingI += 2 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Bookings[bookingI].Timestamp.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 95, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</p><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Bookings[bookingI+1].Timestamp.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 96, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</p><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Bookings[bookingI].BookingType.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 97, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if workDay.IsKurzArbeit() {
timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(timeFrom.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 103, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(timeTo.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 104, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</p><p>Kurzarbeit</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
} else {
absentDay, _ := day.(*models.Absence)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<p class=\"col-span-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(absentDay.AbwesenheitTyp.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 111, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
work, pause, overtime := day.GetAllWorkTimesVirtual(e)
templ_7745c5c3_Err = ColorDuration(work, noBorder).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = ColorDuration(pause, noBorder).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = ColorDuration(overtime, noBorder+" border-r-0").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if day.Date().Weekday() == time.Friday {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<p class=\"col-span-full bg-neutral-300\">Wochenende</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</div></content>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -513,6 +204,70 @@ func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays
}) })
} }
// templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays []models.IWorkDay, tsStart time.Time, tsEnd time.Time) {
// {{
// _, kw := tsStart.ISOWeek()
// noBorder := ""
// }}
// @Base()
// <content class="p-8 relative flex flex-col gap-4 break-after-page">
// <div>
// <h1 class="text-2xl font-bold">{ e.Vorname } { e.Name }</h1>
// <p>Zeitraum: <span>{ tsStart.Format("02.01.2006") }</span> - <span>{ tsEnd.Format("02.01.2006") }</span></p>
// <p>Arbeitszeit: <span>{ helper.FormatDuration(worktime) }</span></p>
// <p>Überstunden: <span>{ helper.FormatDuration(overtime) }</span></p>
// </div>
// <div class="grid grid-rows-6 grid-cols-[3fr_2fr_2fr_2fr_3fr_3fr_3fr] *:not-print:p-2 *:text-center auto-rows-min divide-neutral-300 divide-x-1 divide-y-1">
// <p class="bg-neutral-300 border-neutral-600">{ kw }</p>
// <p class="bg-neutral-300 border-neutral-600">Kommen</p>
// <p class="bg-neutral-300 border-neutral-600">Gehen</p>
// <p class="bg-neutral-300 border-neutral-600">Arbeitsart</p>
// <p class="bg-neutral-300 border-neutral-600">Stunden</p>
// <p class="bg-neutral-300 border-neutral-600">Pause</p>
// <p class="bg-neutral-300 border-neutral-600 border-r-0">Überstunden</p>
// for index, day := range workDays {
// {{
// if index == len(workDays)-1 {
// noBorder = "border-b-0"
// }
// }}
// <p class={ noBorder }>{ day.Date().Format("02.01.2006") }</p>
// <div class={ "grid grid-cols-subgrid col-span-3 " + noBorder }>
// if day.IsWorkDay() {
// {{
// workDay, _ := day.(*models.WorkDay)
// }}
// for bookingI := 0; bookingI < len(workDay.Bookings); bookingI+= 2 {
// <p>{ workDay.Bookings[bookingI].Timestamp.Format("15:04") }</p>
// <p>{ workDay.Bookings[bookingI+1].Timestamp.Format("15:04") }</p>
// <p>{ workDay.Bookings[bookingI].BookingType.Name } </p>
// }
// if workDay.IsKurzArbeit() {
// {{
// timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
// }}
// <p>{ timeFrom.Format("15:04") }</p>
// <p>{ timeTo.Format("15:04") }</p>
// <p>Kurzarbeit</p>
// }
// } else {
// {{
// absentDay, _ := day.(*models.Absence)
// }}
// <p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p>
// }
// </div>
// {{ work, pause, overtime := day.GetTimesVirtual(e) }}
// @ColorDuration(work, noBorder)
// @ColorDuration(pause, noBorder)
// @ColorDuration(overtime, noBorder+" border-r-0")
// if day.Date().Weekday() == time.Friday {
// <p class="col-span-full bg-neutral-300">Wochenende</p>
// }
// }
// </div>
// </content>
// }
func ColorDuration(d time.Duration, classes string) templ.Component { func ColorDuration(d time.Duration, classes string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -529,48 +284,47 @@ func ColorDuration(d time.Duration, classes string) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var28 := templ.GetChildren(ctx) templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var28 == nil { if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var28 = templ.NopComponent templ_7745c5c3_Var11 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
color := "" color := ""
if d.Abs() < time.Minute { if d.Abs() < time.Minute {
color = "text-neutral-300" color = "text-neutral-300"
} }
var templ_7745c5c3_Var29 = []any{color + " " + classes} var templ_7745c5c3_Var12 = []any{color + " " + classes}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var29...) templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<p class=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<p class=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var30 string var templ_7745c5c3_Var13 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var29).String()) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 1, Col: 0} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 1, Col: 0}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var31 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDurationFill(d, true)) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDurationFill(d, true))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 133, Col: 72} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 133, Col: 72}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924 // templ: version: v0.3.960
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.

View File

@@ -0,0 +1,146 @@
package templates
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"fmt"
"strconv"
"time"
)
templ TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) {
@Base()
@headerComponent()
<div class="grid-main divide-y-1">
<div class="grid-sub lg:divide-x-1 max-md:divide-y-1 responsive @container">
<div class="grid-cell col-span-full bg-neutral-300 lg:border-0">
<h2 class="text-xl uppercase font-bold">Eigene Abrechnung</h2>
</div>
</div>
@workWeekComponent(userWeek, false)
if len(weeks) > 0 {
<div class="grid-cell col-span-full bg-neutral-300">
<h2 class="text-xl uppercase font-bold">Abrechnung Mitarbeiter</h2>
</div>
}
for _, week := range weeks {
@workWeekComponent(week, true)
}
</div>
}
templ workWeekComponent(week models.WorkWeek, onlyAccept bool) {
{{
year, kw := week.WeekStart.ISOWeek()
progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
}}
<div class="employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container">
<div class="grid-cell flex flex-col max-md:bg-neutral-300 gap-2">
if !onlyAccept {
<div class="lg:hidden">
@weekPicker(week.WeekStart)
</div>
}
<p class="font-bold uppercase">{ week.User.Vorname } { week.User.Name }</p>
<div class="grid grid-cols-5 gap-2 lg:grid-cols-1">
if !onlyAccept {
<div class="col-span-2">
<span class="flex flex-row gap-2 items-center">
@statusCheckMark(week.CheckStatus(), models.WeekStatusSent)
Gesendet
</span>
<span class="flex flex-row gap-2 items-center">
@statusCheckMark(week.CheckStatus(), models.WeekStatusAccepted)
Akzeptiert
</span>
</div>
}
<div class="flex flex-row gap-2 col-span-3">
@timeGaugeComponent(int8(progress), false)
<div>
<p>Arbeitszeit: { fmt.Sprintf("%s", helper.FormatDuration(week.Worktime)) }</p>
<p>Überstunden: { fmt.Sprintf("%s", helper.FormatDurationFill(week.Overtime, true)) }</p>
</div>
</div>
</div>
</div>
<div class="grid-cell col-span-3 flex flex-col @7xl:grid @7xl:grid-cols-5 gap-2 py-4 content-baseline">
for _, day := range week.Days {
@defaultWeekDayComponent(week.User, day)
}
</div>
<div class="grid-cell flex flex-col gap-2 justify-between">
if onlyAccept {
<p class="text-sm"><span class="">Woche:</span> { fmt.Sprintf("%02d-%d", kw, year) }</p>
} else {
<div class="max-md:hidden">
@weekPicker(week.WeekStart)
</div>
}
<form method="post" class="flex flex-col gap-2">
{{
week.CheckStatus()
method := "accept"
if !onlyAccept {
method = "send"
}
}}
<input type="hidden" name="method" value={ method }/>
<input type="hidden" name="user" value={ strconv.Itoa(week.User.PersonalNummer) }/>
<input type="hidden" name="week" value={ week.WeekStart.Format(time.DateOnly) }/>
if onlyAccept {
if week.Status == models.WeekStatusDifferences {
<p class="text-red-600 text-sm">Unterschiedliche Arbeitszeit zwischen Abrechnung und individuellen Buchungen</p>
}
<button type="submit" disabled?={ week.Status == models.WeekStatusDifferences } class="btn">Bestätigen</button>
} else {
switch {
case week.RequiresAction():
<p class="text-sm text-red-500">bitte zuerst Buchungen anpassen</p>
case time.Since(week.WeekStart) < 24*7*time.Hour:
<p class="text-sm text-red-500">Die Woche kann erst am nächsten Montag gesendet werden!</p>
case week.Status == models.WeekStatusNone:
<p class="text-sm">an Vorgesetzten senden</p>
case week.Status == models.WeekStatusSent:
<p class="text-sm">an Vorgesetzten gesendet</p>
case week.Status == models.WeekStatusAccepted:
<p class="text-sm">vom Vorgesetzten bestätigt</p>
}
<button disabled?={ week.Status < models.WeekStatusSent } type="submit" class="btn">Korrigieren</button>
<button disabled?={ time.Since(week.WeekStart) < 24*7*time.Hour || week.Status >= models.WeekStatusSent || week.RequiresAction() } type="submit" class="btn">Senden</button>
}
</form>
</div>
</div>
}
templ defaultWeekDayComponent(u models.User, day models.IWorkDay) {
<div class="flex flex-row gap-2">
@timeGaugeComponent(day.GetDayProgress(u), false)
<div class="flex flex-col">
<p class=""><span class="font-bold uppercase hidden md:inline">{ helper.FormatGermanDayOfWeek(day.Date()) }:</span> { day.Date().Format("02.01.2006") }</p>
{{ work, pause, _ := day.GetTimes(u, models.WorktimeBaseDay, false) }}
if day.IsWorkDay() || day.GetDayProgress(u) < 100 {
<div class="flex flex-row gap-2">
<span class="text-accent">{ helper.FormatDuration(work) }</span>
<span class="text-neutral-500">{ helper.FormatDuration(pause) }</span>
</div>
}
@weekDayTypeSwitcher(day)
</div>
</div>
}
templ weekDayTypeSwitcher(day models.IWorkDay) {
switch day.Type() {
case models.DayTypeWorkday:
{{ workDay, _ := day.(*models.WorkDay) }}
@workDayWeekComponent(workDay)
case models.DayTypeCompound:
for _, c := range day.(*models.CompoundDay).DayParts {
@weekDayTypeSwitcher(c)
}
default:
<div>{ day.ToString() }</div>
}
}

View File

@@ -0,0 +1,545 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"fmt"
"strconv"
"time"
)
func TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = headerComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"grid-main divide-y-1\"><div class=\"grid-sub lg:divide-x-1 max-md:divide-y-1 responsive @container\"><div class=\"grid-cell col-span-full bg-neutral-300 lg:border-0\"><h2 class=\"text-xl uppercase font-bold\">Eigene Abrechnung</h2></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = workWeekComponent(userWeek, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(weeks) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"grid-cell col-span-full bg-neutral-300\"><h2 class=\"text-xl uppercase font-bold\">Abrechnung Mitarbeiter</h2></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, week := range weeks {
templ_7745c5c3_Err = workWeekComponent(week, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func workWeekComponent(week models.WorkWeek, onlyAccept bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
year, kw := week.WeekStart.ISOWeek()
progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container\"><div class=\"grid-cell flex flex-col max-md:bg-neutral-300 gap-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !onlyAccept {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"lg:hidden\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = weekPicker(week.WeekStart).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<p class=\"font-bold uppercase\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(week.User.Vorname)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 44, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(week.User.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 44, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</p><div class=\"grid grid-cols-5 gap-2 lg:grid-cols-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !onlyAccept {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"col-span-2\"><span class=\"flex flex-row gap-2 items-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = statusCheckMark(week.CheckStatus(), models.WeekStatusSent).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "Gesendet</span> <span class=\"flex flex-row gap-2 items-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = statusCheckMark(week.CheckStatus(), models.WeekStatusAccepted).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "Akzeptiert</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"flex flex-row gap-2 col-span-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = timeGaugeComponent(int8(progress), false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div><p>Arbeitszeit: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", helper.FormatDuration(week.Worktime)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 61, Col: 79}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</p><p>Überstunden: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", helper.FormatDurationFill(week.Overtime, true)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 62, Col: 90}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</p></div></div></div></div><div class=\"grid-cell col-span-3 flex flex-col @7xl:grid @7xl:grid-cols-5 gap-2 py-4 content-baseline\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, day := range week.Days {
templ_7745c5c3_Err = defaultWeekDayComponent(week.User, day).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div><div class=\"grid-cell flex flex-col gap-2 justify-between\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if onlyAccept {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<p class=\"text-sm\"><span class=\"\">Woche:</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d-%d", kw, year))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 74, Col: 86}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div class=\"max-md:hidden\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = weekPicker(week.WeekStart).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<form method=\"post\" class=\"flex flex-col gap-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
week.CheckStatus()
method := "accept"
if !onlyAccept {
method = "send"
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<input type=\"hidden\" name=\"method\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(method)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 88, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\"> <input type=\"hidden\" name=\"user\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(week.User.PersonalNummer))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 89, Col: 83}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\"> <input type=\"hidden\" name=\"week\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(week.WeekStart.Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 90, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if onlyAccept {
if week.Status == models.WeekStatusDifferences {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<p class=\"text-red-600 text-sm\">Unterschiedliche Arbeitszeit zwischen Abrechnung und individuellen Buchungen</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " <button type=\"submit\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if week.Status == models.WeekStatusDifferences {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, " disabled")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " class=\"btn\">Bestätigen</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
switch {
case week.RequiresAction():
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<p class=\"text-sm text-red-500\">bitte zuerst Buchungen anpassen</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case time.Since(week.WeekStart) < 24*7*time.Hour:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<p class=\"text-sm text-red-500\">Die Woche kann erst am nächsten Montag gesendet werden!</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case week.Status == models.WeekStatusNone:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<p class=\"text-sm\">an Vorgesetzten senden</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case week.Status == models.WeekStatusSent:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<p class=\"text-sm\">an Vorgesetzten gesendet</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case week.Status == models.WeekStatusAccepted:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<p class=\"text-sm\">vom Vorgesetzten bestätigt</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " <button")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if week.Status < models.WeekStatusSent {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, " disabled")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " type=\"submit\" class=\"btn\">Korrigieren</button> <button")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if time.Since(week.WeekStart) < 24*7*time.Hour || week.Status >= models.WeekStatusSent || week.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " disabled")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " type=\"submit\" class=\"btn\">Senden</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</form></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func defaultWeekDayComponent(u models.User, day models.IWorkDay) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div class=\"flex flex-row gap-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = timeGaugeComponent(day.GetDayProgress(u), false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<div class=\"flex flex-col\"><p class=\"\"><span class=\"font-bold uppercase hidden md:inline\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatGermanDayOfWeek(day.Date()))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 121, Col: 108}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, ":</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(day.Date().Format("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 121, Col: 152}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
work, pause, _ := day.GetTimes(u, models.WorktimeBaseDay, false)
if day.IsWorkDay() || day.GetDayProgress(u) < 100 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<div class=\"flex flex-row gap-2\"><span class=\"text-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(work))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 125, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span> <span class=\"text-neutral-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(pause))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 126, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = weekDayTypeSwitcher(day).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func weekDayTypeSwitcher(day models.IWorkDay) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var16 := templ.GetChildren(ctx)
if templ_7745c5c3_Var16 == nil {
templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch day.Type() {
case models.DayTypeWorkday:
workDay, _ := day.(*models.WorkDay)
templ_7745c5c3_Err = workDayWeekComponent(workDay).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case models.DayTypeCompound:
for _, c := range day.(*models.CompoundDay).DayParts {
templ_7745c5c3_Err = weekDayTypeSwitcher(c).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
default:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(day.ToString())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reportPage.templ`, Line: 144, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -1,17 +1,13 @@
package templates package templates
import ( import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"fmt" "fmt"
"strconv"
"time" "time"
) )
templ weekPicker(weekStart time.Time) { templ weekPicker(weekStart time.Time) {
{{ {{ year, kw := weekStart.ISOWeek() }}
year, kw := weekStart.ISOWeek()
}}
<form method="get" class="flex flex-row gap-4 items-center justify-around"> <form method="get" class="flex flex-row gap-4 items-center justify-around">
<input type="date" class="hidden" name="submission_date" value={ weekStart.Format(time.DateOnly) }/> <input type="date" class="hidden" name="submission_date" value={ weekStart.Format(time.DateOnly) }/>
<button onclick={ templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "-1") } class="btn"> <button onclick={ templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "-1") } class="btn">
@@ -28,139 +24,22 @@ templ weekPicker(weekStart time.Time) {
</form> </form>
} }
templ defaultWeekDayComponent(u models.User, day models.IWorkDay) { templ workDayWeekComponent(workDay *models.WorkDay) {
<div class="flex flex-row gap-2"> if !workDay.RequiresAction() {
@timeGaugeComponent(day.GetDayProgress(u), false) <div class="flex flex-row gap-2 items-center">
<div class="flex flex-col"> <span class="icon-[material-symbols-light--schedule-outline] flex-shrink-0"></span>
<p class=""><span class="font-bold uppercase hidden md:inline">{ helper.FormatGermanDayOfWeek(day.Date()) }:</span> { day.Date().Format("02.01.2006") }</p> switch {
if day.IsWorkDay() { case !workDay.TimeFrom.Equal(workDay.TimeTo):
{{ <span>{ workDay.TimeFrom.Format("15:04") }</span>
workDay, _ := day.(*models.WorkDay) <span>-</span>
work, pause, _ := workDay.GetAllWorkTimesReal(u) <span>{ workDay.TimeTo.Format("15:04") }</span>
}} default:
if !workDay.RequiresAction() { <p>Keine Anwesenheit</p>
<div class="flex flex-row gap-2">
<span class="text-accent">{ helper.FormatDuration(work) }</span>
<span class="text-neutral-500">{ helper.FormatDuration(pause) }</span>
</div>
<div class="flex flex-row gap-2 items-center">
<span class="icon-[material-symbols-light--schedule-outline] flex-shrink-0"></span>
switch {
case !workDay.TimeFrom.Equal(workDay.TimeTo):
<span>{ workDay.TimeFrom.Format("15:04") }</span>
<span>-</span>
<span>{ workDay.TimeTo.Format("15:04") }</span>
default:
<p>Keine Anwesenheit</p>
}
</div>
} else {
<p class="text-red-600">Bitte anpassen</p>
}
} else {
{{
absentDay, _ := day.(*models.Absence)
}}
<div>{ absentDay.AbwesenheitTyp.Name } </div>
} }
</div> </div>
</div> } else {
} <p class="text-red-600">Bitte anpassen</p>
}
templ weekDayComponent(user models.User, day models.WorkDay) {
// {{ work, pause, _ := day.GetAllWorkTimesReal(user) }}
<div class="flex flex-row gap-2">
// @timeGaugeComponent(day.GetWorkDayProgress(user), false, day.RequiresAction())
<div class="flex flex-col">
if !day.RequiresAction() {
}
</div>
</div>
}
templ workWeekComponent(week models.WorkWeek, onlyAccept bool) {
{{
year, kw := week.WeekStart.ISOWeek()
progress := (float32(week.WorkTimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
}}
<div class="employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container">
<div class="grid-cell flex flex-col max-md:bg-neutral-300 gap-2">
if !onlyAccept {
<div class="lg:hidden">
@weekPicker(week.WeekStart)
</div>
}
<p class="font-bold uppercase">{ week.User.Vorname } { week.User.Name }</p>
<div class="grid grid-cols-5 gap-2 lg:grid-cols-1">
if !onlyAccept {
<div class="col-span-2">
<span class="flex flex-row gap-2 items-center">
@statusCheckMark(week.CheckStatus(), models.WeekStatusSent)
Gesendet
</span>
<span class="flex flex-row gap-2 items-center">
@statusCheckMark(week.CheckStatus(), models.WeekStatusAccepted)
Akzeptiert
</span>
</div>
}
<div class="flex flex-row gap-2 col-span-3">
@timeGaugeComponent(int8(progress), false)
<div>
<p>Arbeitszeit: { fmt.Sprintf("%s", helper.FormatDuration(week.Worktime)) }</p>
<p>Überstunden: { fmt.Sprintf("%s", helper.FormatDurationFill(week.Overtime, true)) }</p>
</div>
</div>
</div>
</div>
<div class="grid-cell col-span-3 flex flex-col @7xl:grid @7xl:grid-cols-5 gap-2 py-4 content-baseline">
for _, day := range week.Days {
@defaultWeekDayComponent(week.User, day)
}
</div>
<div class="grid-cell flex flex-col gap-2 justify-between">
if onlyAccept {
<p class="text-sm"><span class="">Woche:</span> { fmt.Sprintf("%02d-%d", kw, year) }</p>
} else {
<div class="max-md:hidden">
@weekPicker(week.WeekStart)
</div>
}
<form method="post" class="flex flex-col gap-2">
{{
week.CheckStatus()
method := "accept"
if !onlyAccept {
method = "send"
}
}}
<input type="hidden" name="method" value={ method }/>
<input type="hidden" name="user" value={ strconv.Itoa(week.User.PersonalNummer) }/>
<input type="hidden" name="week" value={ week.WeekStart.Format(time.DateOnly) }/>
if onlyAccept {
if week.Status == models.WeekStatusDifferences {
<p class="text-red-600 text-sm">Unterschiedliche Arbeitszeit zwischen Abrechnung und individuellen Buchungen</p>
}
<button type="submit" disabled?={ week.Status == models.WeekStatusDifferences } class="btn">Bestätigen</button>
} else {
switch {
case week.RequiresAction():
<p class="text-sm text-red-500">bitte zuerst Buchungen anpassen</p>
case time.Since(week.WeekStart) < 24*7*time.Hour:
<p class="text-sm text-red-500">Die Woche kann erst am nächsten Montag gesendet werden!</p>
case week.Status == models.WeekStatusNone:
<p class="text-sm">an Vorgesetzten senden</p>
case week.Status == models.WeekStatusSent:
<p class="text-sm">an Vorgesetzten gesendet</p>
case week.Status == models.WeekStatusAccepted:
<p class="text-sm">vom Vorgesetzten bestätigt</p>
}
<button disabled?={ week.Status < models.WeekStatusSent } type="submit" class="btn">Korrigieren</button>
<button disabled?={ time.Since(week.WeekStart) < 24*7*time.Hour || week.Status >= models.WeekStatusSent || week.RequiresAction() } type="submit" class="btn">Senden</button>
}
</form>
</div>
</div>
} }
templ userPresenceComponent(user models.User, present bool) { templ userPresenceComponent(user models.User, present bool) {

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924 // templ: version: v0.3.960
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -9,10 +9,8 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime" import templruntime "github.com/a-h/templ/runtime"
import ( import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"fmt" "fmt"
"strconv"
"time" "time"
) )
@@ -37,7 +35,6 @@ func weekPicker(weekStart time.Time) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
year, kw := weekStart.ISOWeek() year, kw := weekStart.ISOWeek()
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<form method=\"get\" class=\"flex flex-row gap-4 items-center justify-around\"><input type=\"date\" class=\"hidden\" name=\"submission_date\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<form method=\"get\" class=\"flex flex-row gap-4 items-center justify-around\"><input type=\"date\" class=\"hidden\" name=\"submission_date\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -46,7 +43,7 @@ func weekPicker(weekStart time.Time) templ.Component {
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(weekStart.Format(time.DateOnly)) templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(weekStart.Format(time.DateOnly))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 16, Col: 98} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 12, Col: 98}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -76,7 +73,7 @@ func weekPicker(weekStart time.Time) templ.Component {
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d, %d", kw, year)) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d, %d", kw, year))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 22, Col: 69} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 18, Col: 69}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -117,7 +114,7 @@ func weekPicker(weekStart time.Time) templ.Component {
}) })
} }
func defaultWeekDayComponent(u models.User, day models.IWorkDay) templ.Component { func workDayWeekComponent(workDay *models.WorkDay) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -138,495 +135,58 @@ func defaultWeekDayComponent(u models.User, day models.IWorkDay) templ.Component
templ_7745c5c3_Var6 = templ.NopComponent templ_7745c5c3_Var6 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"flex flex-row gap-2\">") if !workDay.RequiresAction() {
if templ_7745c5c3_Err != nil { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"flex flex-row gap-2 items-center\"><span class=\"icon-[material-symbols-light--schedule-outline] flex-shrink-0\"></span> ")
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = timeGaugeComponent(day.GetDayProgress(u), false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"flex flex-col\"><p class=\"\"><span class=\"font-bold uppercase hidden md:inline\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatGermanDayOfWeek(day.Date()))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 35, Col: 108}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, ":</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(day.Date().Format("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 35, Col: 152}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay)
work, pause, _ := workDay.GetAllWorkTimesReal(u)
if !workDay.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"flex flex-row gap-2\"><span class=\"text-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(work))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 43, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span> <span class=\"text-neutral-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(pause))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 44, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</span></div><div class=\"flex flex-row gap-2 items-center\"><span class=\"icon-[material-symbols-light--schedule-outline] flex-shrink-0\"></span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
switch {
case !workDay.TimeFrom.Equal(workDay.TimeTo):
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.TimeFrom.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 50, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</span> <span>-</span> <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.TimeTo.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 52, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p>Keine Anwesenheit</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<p class=\"text-red-600\">Bitte anpassen</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
} else {
absentDay, _ := day.(*models.Absence)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(absentDay.AbwesenheitTyp.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 64, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func weekDayComponent(user models.User, day models.WorkDay) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var14 := templ.GetChildren(ctx)
if templ_7745c5c3_Var14 == nil {
templ_7745c5c3_Var14 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<div class=\"flex flex-row gap-2\"><div class=\"flex flex-col\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !day.RequiresAction() {
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func workWeekComponent(week models.WorkWeek, onlyAccept bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var15 := templ.GetChildren(ctx)
if templ_7745c5c3_Var15 == nil {
templ_7745c5c3_Var15 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
year, kw := week.WeekStart.ISOWeek()
progress := (float32(week.WorkTimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container\"><div class=\"grid-cell flex flex-col max-md:bg-neutral-300 gap-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !onlyAccept {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"lg:hidden\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = weekPicker(week.WeekStart).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<p class=\"font-bold uppercase\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(week.User.Vorname)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 93, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(week.User.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 93, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</p><div class=\"grid grid-cols-5 gap-2 lg:grid-cols-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !onlyAccept {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div class=\"col-span-2\"><span class=\"flex flex-row gap-2 items-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = statusCheckMark(week.CheckStatus(), models.WeekStatusSent).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "Gesendet</span> <span class=\"flex flex-row gap-2 items-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = statusCheckMark(week.CheckStatus(), models.WeekStatusAccepted).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "Akzeptiert</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<div class=\"flex flex-row gap-2 col-span-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = timeGaugeComponent(int8(progress), false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<div><p>Arbeitszeit: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", helper.FormatDuration(week.Worktime)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 110, Col: 79}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</p><p>Überstunden: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s", helper.FormatDurationFill(week.Overtime, true)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 111, Col: 90}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</p></div></div></div></div><div class=\"grid-cell col-span-3 flex flex-col @7xl:grid @7xl:grid-cols-5 gap-2 py-4 content-baseline\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, day := range week.Days {
templ_7745c5c3_Err = defaultWeekDayComponent(week.User, day).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</div><div class=\"grid-cell flex flex-col gap-2 justify-between\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if onlyAccept {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<p class=\"text-sm\"><span class=\"\">Woche:</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d-%d", kw, year))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 123, Col: 86}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"max-md:hidden\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = weekPicker(week.WeekStart).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<form method=\"post\" class=\"flex flex-col gap-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
week.CheckStatus()
method := "accept"
if !onlyAccept {
method = "send"
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<input type=\"hidden\" name=\"method\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(method)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 137, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "\"> <input type=\"hidden\" name=\"user\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(week.User.PersonalNummer))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 138, Col: 83}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\"> <input type=\"hidden\" name=\"week\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(week.WeekStart.Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 139, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if onlyAccept {
if week.Status == models.WeekStatusDifferences {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<p class=\"text-red-600 text-sm\">Unterschiedliche Arbeitszeit zwischen Abrechnung und individuellen Buchungen</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, " <button type=\"submit\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if week.Status == models.WeekStatusDifferences {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, " disabled")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, " class=\"btn\">Bestätigen</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
switch { switch {
case week.RequiresAction(): case !workDay.TimeFrom.Equal(workDay.TimeTo):
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<p class=\"text-sm text-red-500\">bitte zuerst Buchungen anpassen</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
case time.Since(week.WeekStart) < 24*7*time.Hour: var templ_7745c5c3_Var7 string
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<p class=\"text-sm text-red-500\">Die Woche kann erst am nächsten Montag gesendet werden!</p>") templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.TimeFrom.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 33, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
case week.Status == models.WeekStatusNone: templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</span> <span>-</span> <span>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<p class=\"text-sm\">an Vorgesetzten senden</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
case week.Status == models.WeekStatusSent: var templ_7745c5c3_Var8 string
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<p class=\"text-sm\">an Vorgesetzten gesendet</p>") templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.TimeTo.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 35, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
case week.Status == models.WeekStatusAccepted: templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<p class=\"text-sm\">vom Vorgesetzten bestätigt</p>") if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<p>Keine Anwesenheit</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, " <button") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if week.Status < models.WeekStatusSent { } else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " disabled") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<p class=\"text-red-600\">Bitte anpassen</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, " type=\"submit\" class=\"btn\">Korrigieren</button> <button")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if time.Since(week.WeekStart) < 24*7*time.Hour || week.Status >= models.WeekStatusSent || week.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, " disabled")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, " type=\"submit\" class=\"btn\">Senden</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</form></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
} }
return nil return nil
}) })
@@ -648,53 +208,53 @@ func userPresenceComponent(user models.User, present bool) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var24 := templ.GetChildren(ctx) templ_7745c5c3_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var24 == nil { if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var24 = templ.NopComponent templ_7745c5c3_Var9 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<div class=\"grid-cell group flex flex-row gap-2\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"grid-cell group flex flex-row gap-2\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if present { if present {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "<div class=\"h-8 bg-accent rounded-md group-hover:text-black md:text-transparent text-center p-1\">Anwesend</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div class=\"h-8 bg-accent rounded-md group-hover:text-black md:text-transparent text-center p-1\">Anwesend</div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} else { } else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<div class=\"h-8 bg-red-600 rounded-md group-hover:text-white md:text-transparent text-center p-1\">Abwesend</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"h-8 bg-red-600 rounded-md group-hover:text-white md:text-transparent text-center p-1\">Abwesend</div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var25 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 173, Col: 19} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 52, Col: 19}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, " ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var26 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 173, Col: 33} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 52, Col: 33}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</p></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</p></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@@ -20,15 +20,15 @@ templ lineComponent() {
} }
templ changeButtonComponent(id string, workDay bool) { templ changeButtonComponent(id string, workDay bool) {
<button class="change-button-component btn w-auto group/button" type="button" onclick={ templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), id, workDay) }> <button class="h-10 change-button-component btn w-auto group/button" type="button" onclick={ templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), id, workDay) }>
<p class="hidden md:block group-[.edit]/button:hidden">Ändern</p> <p class="hidden md:block group-[.edit]/button:hidden">Ändern</p>
<p class="hidden group-[.edit]/button:md:block">Absenden</p> <p class="hidden group-[.edit]/button:md:block">Speichern</p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4 md:hidden"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4 md:hidden">
<path class="group-[.edit]/button:hidden md:hidden" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"></path> <path class="group-[.edit]/button:hidden md:hidden" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"></path>
<path class="hidden group-[.edit]/button:block md:hidden" d="M12.736 3.97a.733.733 0 0 1 j1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"></path> <path class="hidden group-[.edit]/button:block md:hidden" d="M12.736 3.97a.733.733 0 0 1 j1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"></path>
</svg> </svg>
</button> </button>
<button class="hidden group-[.edit]:flex btn basis-[content] items-center" onclick={ templ.JSFuncCall("clearEditState") }><span class="size-5 icon-[material-symbols-light--cancel-outline]"></span></button> <button class="h-10 hidden group-[.edit]:flex btn basis-[content] items-center" onclick={ templ.JSFuncCall("clearEditState") }><span class="size-5 icon-[material-symbols-light--cancel-outline]"></span></button>
} }
templ timeGaugeComponent(progress int8, today bool) { templ timeGaugeComponent(progress int8, today bool) {
@@ -77,12 +77,9 @@ templ absenceComponent(a *models.Absence, isKurzarbeit bool) {
} }
}} }}
<div class={ "flex flex-row items-center gap-2", editBox }> <div class={ "flex flex-row items-center gap-2", editBox }>
<input type="hidden" name="date_from" value={ a.DateFrom.Format(time.DateOnly) }/> @absentInput(a)
<input type="hidden" name="date_to" value={ a.DateTo.Format(time.DateOnly) }/>
<input type="hidden" name="aw_type" value={ a.AbwesenheitTyp.Id }/>
<input type="hidden" name="aw_id" value={ a.CounterId }/>
<p class="whitespace-nowrap group-[.edit]:ml-2"> <p class="whitespace-nowrap group-[.edit]:ml-2">
{ a.AbwesenheitTyp.Name } { a.ToString() }
if a.IsMultiDay() { if a.IsMultiDay() {
<span class="text-neutral-500">bis { a.DateTo.Format("02.01.2006") }</span> <span class="text-neutral-500">bis { a.DateTo.Format("02.01.2006") }</span>
} }
@@ -94,15 +91,23 @@ templ absenceComponent(a *models.Absence, isKurzarbeit bool) {
</div> </div>
} }
templ newBookingComponent(d *models.WorkDay) { templ absentInput(a *models.Absence) {
<div class="new-booking-component hidden group-[.edit]:flex flex-row gap-2 items-center edit-box border-dashed"> <input type="hidden" name="date_from" value={ a.DateFrom.Format(time.DateOnly) }/>
<input type="hidden" name="date_to" value={ a.DateTo.Format(time.DateOnly) }/>
<input type="hidden" name="aw_type" value={ a.AbwesenheitTyp.Id }/>
<input type="hidden" name="aw_id" value={ a.CounterId }/>
}
//js function to select the right entry
templ newBookingComponent(d models.IWorkDay) {
<div class="new-booking-component hidden group-[.edit]:flex flex-row gap-2 items-center edit-box border-dashed" id={ "nb" + d.Date().Format(time.DateOnly) }>
<input name="timestamp" type="time" value={ time.Now().Format("15:04") } class="text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer"/> <input name="timestamp" type="time" value={ time.Now().Format("15:04") } class="text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer"/>
<input name="date" type="hidden" value={ d.Day.Format(time.DateOnly) }/> <input name="date" type="hidden" value={ d.Date().Format(time.DateOnly) }/>
<div class="relative"> <div class="relative">
<select class="cursor-pointer appearance-none" name="check_in_out"> <select class="cursor-pointer appearance-none" name="check_in_out">
<option value="0" disabled>Kommen/Gehen</option> <option value="0" disabled>Kommen/Gehen</option>
<option value="3" selected?={ len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 0 }>Kommen</option> <option value="3">Kommen</option>
<option value="4" selected?={ len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 1 }>Gehen</option> <option value="4">Gehen</option>
</select> </select>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.2" stroke="currentColor" class="h-5 w-5 ml-1 absolute right-1 top-[0.125rem] text-slate-700"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.2" stroke="currentColor" class="h-5 w-5 ml-1 absolute right-1 top-[0.125rem] text-slate-700">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"></path> <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"></path>

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924 // templ: version: v0.3.960
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -69,7 +69,7 @@ func changeButtonComponent(id string, workDay bool) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<button class=\"change-button-component btn w-auto group/button\" type=\"button\" onclick=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<button class=\"h-10 change-button-component btn w-auto group/button\" type=\"button\" onclick=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -78,7 +78,7 @@ func changeButtonComponent(id string, workDay bool) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><p class=\"hidden md:block group-[.edit]/button:hidden\">Ändern</p><p class=\"hidden group-[.edit]/button:md:block\">Absenden</p><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"w-4 h-4 md:hidden\"><path class=\"group-[.edit]/button:hidden md:hidden\" d=\"M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325\"></path> <path class=\"hidden group-[.edit]/button:block md:hidden\" d=\"M12.736 3.97a.733.733 0 0 1 j1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z\"></path></svg></button> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><p class=\"hidden md:block group-[.edit]/button:hidden\">Ändern</p><p class=\"hidden group-[.edit]/button:md:block\">Speichern</p><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"w-4 h-4 md:hidden\"><path class=\"group-[.edit]/button:hidden md:hidden\" d=\"M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325\"></path> <path class=\"hidden group-[.edit]/button:block md:hidden\" d=\"M12.736 3.97a.733.733 0 0 1 j1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z\"></path></svg></button> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -86,7 +86,7 @@ func changeButtonComponent(id string, workDay bool) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<button class=\"hidden group-[.edit]:flex btn basis-[content] items-center\" onclick=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<button class=\"h-10 hidden group-[.edit]:flex btn basis-[content] items-center\" onclick=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -124,7 +124,6 @@ func timeGaugeComponent(progress int8, today bool) templ.Component {
templ_7745c5c3_Var5 = templ.NopComponent templ_7745c5c3_Var5 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
var bgColor string var bgColor string
switch { switch {
case (0 > progress): case (0 > progress):
@@ -278,7 +277,6 @@ func absenceComponent(a *models.Absence, isKurzarbeit bool) templ.Component {
templ_7745c5c3_Var13 = templ.NopComponent templ_7745c5c3_Var13 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
editBox := "" editBox := ""
if isKurzarbeit { if isKurzarbeit {
editBox = "edit-box" editBox = "edit-box"
@@ -301,95 +299,51 @@ func absenceComponent(a *models.Absence, isKurzarbeit bool) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"><input type=\"hidden\" name=\"date_from\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = absentInput(a).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<p class=\"whitespace-nowrap group-[.edit]:ml-2\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var16 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateFrom.Format(time.DateOnly)) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(a.ToString())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 80, Col: 80} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 82, Col: 17}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\"> <input type=\"hidden\" name=\"date_to\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateTo.Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 81, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\"> <input type=\"hidden\" name=\"aw_type\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(a.AbwesenheitTyp.Id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 82, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\"> <input type=\"hidden\" name=\"aw_id\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(a.CounterId)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 83, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"><p class=\"whitespace-nowrap group-[.edit]:ml-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(a.AbwesenheitTyp.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 85, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if a.IsMultiDay() { if a.IsMultiDay() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<span class=\"text-neutral-500\">bis ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<span class=\"text-neutral-500\">bis ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var21 string var templ_7745c5c3_Var17 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateTo.Format("02.01.2006")) templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateTo.Format("02.01.2006"))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 87, Col: 70} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 84, Col: 70}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</span>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p><div class=\"w-full\"></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</p><div class=\"w-full\"></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -398,21 +352,21 @@ func absenceComponent(a *models.Absence, isKurzarbeit bool) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<button type=\"button\" onclick=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<button type=\"button\" onclick=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var22 templ.ComponentScript = templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), "time-"+a.Date().Format(time.DateOnly), false) var templ_7745c5c3_Var18 templ.ComponentScript = templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), "time-"+a.Date().Format(time.DateOnly), false)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22.Call) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18.Call)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" class=\"hidden btn border-0 rounded-none grow-0 w-auto group-[.edit]:inline\">Bearbeiten</button>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" class=\"hidden btn border-0 rounded-none grow-0 w-auto group-[.edit]:inline\">Bearbeiten</button>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -420,7 +374,7 @@ func absenceComponent(a *models.Absence, isKurzarbeit bool) templ.Component {
}) })
} }
func newBookingComponent(d *models.WorkDay) templ.Component { func absentInput(a *models.Absence) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -436,58 +390,133 @@ func newBookingComponent(d *models.WorkDay) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var23 := templ.GetChildren(ctx) templ_7745c5c3_Var19 := templ.GetChildren(ctx)
if templ_7745c5c3_Var23 == nil { if templ_7745c5c3_Var19 == nil {
templ_7745c5c3_Var23 = templ.NopComponent templ_7745c5c3_Var19 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"new-booking-component hidden group-[.edit]:flex flex-row gap-2 items-center edit-box border-dashed\"><input name=\"timestamp\" type=\"time\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<input type=\"hidden\" name=\"date_from\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var24 string var templ_7745c5c3_Var20 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(time.Now().Format("15:04")) templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateFrom.Format(time.DateOnly))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 99, Col: 72} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 95, Col: 79}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\" class=\"text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer\"> <input name=\"date\" type=\"hidden\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\"> <input type=\"hidden\" name=\"date_to\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateTo.Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 96, Col: 75}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\"> <input type=\"hidden\" name=\"aw_type\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(a.AbwesenheitTyp.Id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 97, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\"> <input type=\"hidden\" name=\"aw_id\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(a.CounterId)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 98, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// js function to select the right entry
func newBookingComponent(d models.IWorkDay) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var24 := templ.GetChildren(ctx)
if templ_7745c5c3_Var24 == nil {
templ_7745c5c3_Var24 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<div class=\"new-booking-component hidden group-[.edit]:flex flex-row gap-2 items-center edit-box border-dashed\" id=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var25 string var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(d.Day.Format(time.DateOnly)) templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs("nb" + d.Date().Format(time.DateOnly))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 100, Col: 70} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 103, Col: 155}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\"><div class=\"relative\"><select class=\"cursor-pointer appearance-none\" name=\"check_in_out\"><option value=\"0\" disabled>Kommen/Gehen</option> <option value=\"3\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\"><input name=\"timestamp\" type=\"time\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 0 { var templ_7745c5c3_Var26 string
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " selected") templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(time.Now().Format("15:04"))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 104, Col: 72}
}
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, ">Kommen</option> <option value=\"4\"") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 1 { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\" class=\"text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer\"> <input name=\"date\" type=\"hidden\" value=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " selected") if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err
return templ_7745c5c3_Err
}
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, ">Gehen</option></select> <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.2\" stroke=\"currentColor\" class=\"h-5 w-5 ml-1 absolute right-1 top-[0.125rem] text-slate-700\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\"></path></svg></div><div class=\"w-full\"></div><button name=\"action\" value=\"add\" type=\"submit\" class=\"hidden btn border-0 rounded-none grow-0 w-auto group-[.edit]:inline\"><span class=\"hidden md:inline\">Hinzufügen</span><span class=\"md:hidden\">+</span></button></div>") var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(d.Date().Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 105, Col: 73}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"><div class=\"relative\"><select class=\"cursor-pointer appearance-none\" name=\"check_in_out\"><option value=\"0\" disabled>Kommen/Gehen</option> <option value=\"3\">Kommen</option> <option value=\"4\">Gehen</option></select> <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.2\" stroke=\"currentColor\" class=\"h-5 w-5 ml-1 absolute right-1 top-[0.125rem] text-slate-700\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\"></path></svg></div><div class=\"w-full\"></div><button name=\"action\" value=\"add\" type=\"submit\" class=\"hidden btn border-0 rounded-none grow-0 w-auto group-[.edit]:inline\"><span class=\"hidden md:inline\">Hinzufügen</span><span class=\"md:hidden\">+</span></button></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -511,74 +540,74 @@ func bookingComponent(booking models.Booking) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var26 := templ.GetChildren(ctx) templ_7745c5c3_Var28 := templ.GetChildren(ctx)
if templ_7745c5c3_Var26 == nil { if templ_7745c5c3_Var28 == nil {
templ_7745c5c3_Var26 = templ.NopComponent templ_7745c5c3_Var28 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div><p class=\"text-neutral-500 edit-box\"><span class=\"text-black group-[.edit]:hidden inline\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div><p class=\"text-neutral-500 edit-box\"><span class=\"text-black group-[.edit]:hidden inline\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(booking.Timestamp.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 119, Col: 91}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</span> <input disabled name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs("booking_" + strconv.Itoa(booking.CounterId))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 120, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" type=\"time\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var29 string var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(booking.Timestamp.Format("15:04")) templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(booking.Timestamp.Format("15:04"))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 120, Col: 126} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 124, Col: 91}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\" class=\"text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer\"> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</span> <input disabled name=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var30 string var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(booking.GetBookingType()) templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs("booking_" + strconv.Itoa(booking.CounterId))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 121, Col: 29} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 125, Col: 70}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\" type=\"time\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(booking.Timestamp.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 125, Col: 126}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" class=\"text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var32 string
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(booking.GetBookingType())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 126, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if booking.IsSubmittedAndChecked() { if booking.IsSubmittedAndChecked() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<p>submitted</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<p>submitted</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -602,12 +631,12 @@ func LegendComponent() templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var31 := templ.GetChildren(ctx) templ_7745c5c3_Var33 := templ.GetChildren(ctx)
if templ_7745c5c3_Var31 == nil { if templ_7745c5c3_Var33 == nil {
templ_7745c5c3_Var31 = templ.NopComponent templ_7745c5c3_Var33 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div class=\"flex flex-row gap-4 md:mx-[10%] print:hidden\"><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-red-600\"></div><span>Fehler</span></div><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-orange-500\"></div><span>Arbeitszeit unter regulär</span></div><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-accent\"></div><span>Arbeitszeit vollständig</span></div><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-purple-600\"></div><span>Überstunden</span></div><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-neutral-400\"></div><span>Keine Buchungen</span></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<div class=\"flex flex-row gap-4 md:mx-[10%] print:hidden\"><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-red-600\"></div><span>Fehler</span></div><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-orange-500\"></div><span>Arbeitszeit unter regulär</span></div><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-accent\"></div><span>Arbeitszeit vollständig</span></div><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-purple-600\"></div><span>Überstunden</span></div><div class=\"flex flex-row items-center gap-2\"><div class=\"rounded-full size-4 bg-neutral-400\"></div><span>Keine Buchungen</span></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@@ -9,9 +9,7 @@ import (
) )
templ TimePage(workDays []models.WorkDay, lastSub time.Time) { templ TimePage(workDays []models.WorkDay, lastSub time.Time) {
{{ {{ allDays := ctx.Value("days").([]models.IWorkDay) }}
allDays := ctx.Value("days").([]models.IWorkDay)
}}
@Base() @Base()
@headerComponent() @headerComponent()
<div class="grid-main divide-y-1"> <div class="grid-main divide-y-1">
@@ -31,65 +29,67 @@ templ inputForm() {
urlParams := ctx.Value("urlParams").(url.Values) urlParams := ctx.Value("urlParams").(url.Values)
user := ctx.Value("user").(models.User) user := ctx.Value("user").(models.User)
}} }}
<div class="grid-sub divide-x-1 bg-neutral-300 responsive"> <div class="sticky top-0 z-100 grid-sub divide-y-1">
<div class="grid-cell md:col-span-1 max-md:grid grid-cols-2"> <div class="grid-sub divide-x-1 bg-neutral-300 responsive">
<p class="font-bold uppercase">{ user.Vorname + " " + user.Name }</p> <div class="grid-cell md:col-span-1 max-md:grid grid-cols-2">
<div class="justify-self-end"> <p class="font-bold uppercase">{ user.Vorname + " " + user.Name }</p>
<p class="text-sm">Überstunden</p> <div class="justify-self-end">
<p class="text-accent">{ user.Overtime }</p> <p class="text-sm">Überstunden</p>
<p class="text-accent">{ user.Overtime }</p>
</div>
</div>
<form id="timeRangeForm" method="GET" class="grid-cell flex flex-row md:col-span-3 gap-2 ">
@lineComponent()
<div class="flex flex-col gap-2 justify-between grow-1">
<input type="date" value={ urlParams.Get("time_from") } name="time_from" class="btn bg-neutral-100" placeholder="Zeitraum von..."/>
<input type="date" value={ urlParams.Get("time_to") } name="time_to" class="btn bg-neutral-100" placeholder="Zeitraum bis..."/>
</div>
</form>
<div class="grid-cell content-end">
<button type="submit" form="timeRangeForm" class="btn bg-neutral-100 hover:bg-neutral-700 color-neutral-700">
<p class="">Anzeigen</p>
</button>
</div> </div>
</div> </div>
<form id="timeRangeForm" method="GET" class="grid-cell flex flex-row md:col-span-3 gap-2 "> <form id="absence_form" method="POST" action="/absence" class="grid-sub responsive scroll-m-2 bg-neutral-300 hidden">
@lineComponent() <input type="hidden" name="aw_id" value=""/>
<div class="flex flex-col gap-2 justify-between grow-1"> <div class="grid-cell border-r-1"><p class="font-bold uppercase">Abwesenheit</p></div>
<input type="date" value={ urlParams.Get("time_from") } name="time_from" class="btn bg-neutral-100" placeholder="Zeitraum von..."/> <div class="grid-cell">
<input type="date" value={ urlParams.Get("time_to") } name="time_to" class="btn bg-neutral-100" placeholder="Zeitraum bis..."/> <label class="block mb-1 text-sm text-neutral-700">Abwesenheitsart</label>
<div class="relative">
<select name="aw_type" class="btn appearance-none cursor-pointer bg-neutral-100">
for _, absence := range models.GetAbsenceTypesCached() {
<option value={ strconv.Itoa(int(absence.Id)) }>{ absence.Name }</option>
}
</select>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.2" stroke="currentColor" class="h-5 w-5 ml-1 absolute top-2.5 right-2.5 text-slate-700">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"></path>
</svg>
</div>
</div>
<div class="grid-cell">
<label class="block mb-1 text-sm text-neutral-700">Abwesenheit ab</label>
<input name="date_from" type="date" class="btn bg-neutral-100"/>
</div>
<div class="grid-cell border-r-1">
<label class="block mb-1 text-sm text-neutral-700">Abwesenheit bis</label>
<input name="date_to" type="date" class="btn bg-neutral-100"/>
</div>
<div class="grid-cell flex flex-row items-end">
<div class="flex flex-row gap-2 w-full">
<button name="action" value="insert" type="submit" class="bg-neutral-100 btn hover:bg-neutral-700">Speichern</button>
<button name="action" value="delete" type="submit" class="bg-neutral-100 btn hover:bg-red-700 flex basis-[content] items-center"><span class="size-5 icon-[material-symbols-light--delete-outline]"></span></button>
</div>
</div> </div>
</form> </form>
<div class="grid-cell content-end">
<button type="submit" form="timeRangeForm" class="btn bg-neutral-100 hover:bg-neutral-700 color-neutral-700">
<p class="">Anzeigen</p>
</button>
</div>
</div> </div>
<form id="absence_form" method="POST" action="/absence" class="grid-sub responsive scroll-m-2 bg-neutral-300 hidden">
<input type="hidden" name="aw_id" value=""/>
<div class="grid-cell border-r-1"><p class="font-bold uppercase">Abwesenheit</p></div>
<div class="grid-cell">
<label class="block mb-1 text-sm text-neutral-700">Abwesenheitsart</label>
<div class="relative">
<select name="aw_type" class="btn appearance-none cursor-pointer bg-neutral-100">
for _, absence := range models.GetAbsenceTypesCached() {
<option value={ strconv.Itoa(int(absence.Id)) }>{ absence.Name }</option>
}
</select>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.2" stroke="currentColor" class="h-5 w-5 ml-1 absolute top-2.5 right-2.5 text-slate-700">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"></path>
</svg>
</div>
</div>
<div class="grid-cell">
<label class="block mb-1 text-sm text-neutral-700">Abwesenheit ab</label>
<input name="date_from" type="date" class="btn bg-neutral-100"/>
</div>
<div class="grid-cell border-r-1">
<label class="block mb-1 text-sm text-neutral-700">Abwesenheit bis</label>
<input name="date_to" type="date" class="btn bg-neutral-100"/>
</div>
<div class="grid-cell flex flex-row items-end">
<div class="flex flex-row gap-2 w-full">
<button name="action" value="insert" type="submit" class="bg-neutral-100 btn hover:bg-neutral-700">Speichern</button>
<button name="action" value="delete" type="submit" class="bg-neutral-100 btn hover:bg-red-700 flex basis-[content] items-center"><span class="size-5 icon-[material-symbols-light--delete-outline]"></span></button>
</div>
</div>
</form>
} }
templ defaultDayComponent(day models.IWorkDay) { templ defaultDayComponent(day models.IWorkDay) {
{{ {{
user := ctx.Value("user").(models.User) user := ctx.Value("user").(models.User)
justify := "justify-center" justify := "justify-center"
if day.IsWorkDay() && len(day.(*models.WorkDay).Bookings) > 1 { if day.IsWorkDay() && !day.IsEmpty() {
justify = "justify-between" justify = "justify-between"
} }
}} }}
@@ -102,8 +102,8 @@ templ defaultDayComponent(day models.IWorkDay) {
</p> </p>
if day.IsWorkDay() { if day.IsWorkDay() {
{{ {{
workDay, _ := day.(*models.WorkDay) work, pause, overtime := day.GetTimes(user, models.WorktimeBaseDay, true)
work, pause, overtime := workDay.GetAllWorkTimesReal(user) work = day.GetWorktime(user, models.WorktimeBaseDay, false)
}} }}
if day.RequiresAction() { if day.RequiresAction() {
<p class="text-red-600">Bitte anpassen</p> <p class="text-red-600">Bitte anpassen</p>
@@ -115,7 +115,7 @@ templ defaultDayComponent(day models.IWorkDay) {
if pause > 0 { if pause > 0 {
<p class="text-neutral-500 flex flex-row items-center"><span class="icon-[material-symbols-light--motion-photos-paused-outline]"></span>{ helper.FormatDuration(pause) }</p> <p class="text-neutral-500 flex flex-row items-center"><span class="icon-[material-symbols-light--motion-photos-paused-outline]"></span>{ helper.FormatDuration(pause) }</p>
} }
if overtime != 0 && len(workDay.Bookings) > 0 { if !day.IsEmpty() && overtime != 0 {
<p class="text-neutral-500 flex flex-row items-center"> <p class="text-neutral-500 flex flex-row items-center">
<span class="icon-[material-symbols-light--more-time]"></span> <span class="icon-[material-symbols-light--more-time]"></span>
{ helper.FormatDuration(overtime) } { helper.FormatDuration(overtime) }
@@ -127,40 +127,54 @@ templ defaultDayComponent(day models.IWorkDay) {
</div> </div>
<div class="all-booking-component grid-cell flex flex-row md:col-span-3 col-span-2 gap-2 w-full"> <div class="all-booking-component grid-cell flex flex-row md:col-span-3 col-span-2 gap-2 w-full">
@lineComponent() @lineComponent()
<form id={ "time-" + day.Date().Format(time.DateOnly) } class={ "flex flex-col gap-2 w-full", justify } method="post"> <form id={ "time-" + day.Date().Format(time.DateOnly) } class={ "bookings flex flex-col gap-2 w-full", justify } method="post">
if day.IsWorkDay() { if (day.GetDayProgress(user) < 100 || day.IsWorkDay()) {
{{
workDay, _ := day.(*models.WorkDay)
}}
@newAbsenceComponent() @newAbsenceComponent()
if len(workDay.Bookings) < 1 { @timeDayTypeSwitch(day, true)
<p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p> @newBookingComponent(day)
}
if workDay.IsKurzArbeit() {
@absenceComponent(workDay.GetKurzArbeit(), true)
}
for _, booking := range workDay.Bookings {
@bookingComponent(booking)
}
@newBookingComponent(workDay)
} else { } else {
{{ @timeDayTypeSwitch(day, true)
absentDay, _ := day.(*models.Absence)
}}
@absenceComponent(absentDay, false)
} }
<input type="hidden" name="action" value="change"/> <!-- default action value for ändern button --> <input type="hidden" name="action" value="change"/> <!-- default action value for ändern button -->
</form> </form>
</div> </div>
<div class="grid-cell flex flex-row gap-2 items-end"> <div class="grid-cell flex flex-row gap-2 items-end">
@changeButtonComponent("time-"+day.Date().Format(time.DateOnly), day.IsWorkDay()) @changeButtonComponent("time-"+day.Date().Format(time.DateOnly), true)
</div> </div>
</div> </div>
} }
templ absentInput(a models.Absence) { templ timeDayTypeSwitch(day models.IWorkDay, fromCompound bool) {
<input type="hidden" name="date_from" value={ a.DateFrom.Format(time.DateOnly) }/> switch day.Type() {
<input type="hidden" name="date_to" value={ a.DateTo.Format(time.DateOnly) }/> case models.DayTypeWorkday:
<input type="hidden" name="aw_type" value={ a.AbwesenheitTyp.Id }/> {{ workDay, _ := day.(*models.WorkDay) }}
<input type="hidden" name="aw_id" value={ a.CounterId }/> @workdayComponent(workDay)
case models.DayTypeAbsence:
{{ absentDay, _ := day.(*models.Absence) }}
@absenceComponent(absentDay, fromCompound)
case models.DayTypeCompound:
for _, c := range day.(*models.CompoundDay).DayParts {
@timeDayTypeSwitch(c, true)
}
default:
<p>{ day.ToString() }</p>
}
}
templ workdayComponent(workDay *models.WorkDay) {
if len(workDay.Bookings) < 1 {
<p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p>
} else {
if workDay.IsKurzArbeit() && len(workDay.Bookings) > 0 {
@absenceComponent(workDay.GetKurzArbeit(), true)
}
for _, booking := range workDay.Bookings {
@bookingComponent(booking)
}
<input type="hidden" name="select_kommen" value={ len(workDay.Bookings) > 0 && workDay.Bookings[len(workDay.Bookings)-1].CheckInOut%2 == 0 }/>
}
}
templ holidayComponent(d models.IWorkDay) {
<p>{ d.ToString() }</p>
} }

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924 // templ: version: v0.3.960
package templates package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -37,7 +37,6 @@ func TimePage(workDays []models.WorkDay, lastSub time.Time) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
allDays := ctx.Value("days").([]models.IWorkDay) allDays := ctx.Value("days").([]models.IWorkDay)
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -104,17 +103,16 @@ func inputForm() templ.Component {
templ_7745c5c3_Var2 = templ.NopComponent templ_7745c5c3_Var2 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
urlParams := ctx.Value("urlParams").(url.Values) urlParams := ctx.Value("urlParams").(url.Values)
user := ctx.Value("user").(models.User) user := ctx.Value("user").(models.User)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"grid-sub divide-x-1 bg-neutral-300 responsive\"><div class=\"grid-cell md:col-span-1 max-md:grid grid-cols-2\"><p class=\"font-bold uppercase\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"sticky top-0 z-100 grid-sub divide-y-1\"><div class=\"grid-sub divide-x-1 bg-neutral-300 responsive\"><div class=\"grid-cell md:col-span-1 max-md:grid grid-cols-2\"><p class=\"font-bold uppercase\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname + " " + user.Name) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname + " " + user.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 36, Col: 66} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 35, Col: 67}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -127,7 +125,7 @@ func inputForm() templ.Component {
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Overtime) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Overtime)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 39, Col: 42} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 38, Col: 43}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -148,7 +146,7 @@ func inputForm() templ.Component {
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_from")) templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_from"))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 45, Col: 57} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 44, Col: 58}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -161,7 +159,7 @@ func inputForm() templ.Component {
var templ_7745c5c3_Var6 string var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_to")) templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_to"))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 46, Col: 55} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 45, Col: 56}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -179,7 +177,7 @@ func inputForm() templ.Component {
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(absence.Id))) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(absence.Id)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 63, Col: 51} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 62, Col: 52}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -192,7 +190,7 @@ func inputForm() templ.Component {
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(absence.Name) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(absence.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 63, Col: 68} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 62, Col: 69}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -203,7 +201,7 @@ func inputForm() templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</select> <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.2\" stroke=\"currentColor\" class=\"h-5 w-5 ml-1 absolute top-2.5 right-2.5 text-slate-700\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\"></path></svg></div></div><div class=\"grid-cell\"><label class=\"block mb-1 text-sm text-neutral-700\">Abwesenheit ab</label> <input name=\"date_from\" type=\"date\" class=\"btn bg-neutral-100\"></div><div class=\"grid-cell border-r-1\"><label class=\"block mb-1 text-sm text-neutral-700\">Abwesenheit bis</label> <input name=\"date_to\" type=\"date\" class=\"btn bg-neutral-100\"></div><div class=\"grid-cell flex flex-row items-end\"><div class=\"flex flex-row gap-2 w-full\"><button name=\"action\" value=\"insert\" type=\"submit\" class=\"bg-neutral-100 btn hover:bg-neutral-700\">Speichern</button> <button name=\"action\" value=\"delete\" type=\"submit\" class=\"bg-neutral-100 btn hover:bg-red-700 flex basis-[content] items-center\"><span class=\"size-5 icon-[material-symbols-light--delete-outline]\"></span></button></div></div></form>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</select> <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.2\" stroke=\"currentColor\" class=\"h-5 w-5 ml-1 absolute top-2.5 right-2.5 text-slate-700\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9\"></path></svg></div></div><div class=\"grid-cell\"><label class=\"block mb-1 text-sm text-neutral-700\">Abwesenheit ab</label> <input name=\"date_from\" type=\"date\" class=\"btn bg-neutral-100\"></div><div class=\"grid-cell border-r-1\"><label class=\"block mb-1 text-sm text-neutral-700\">Abwesenheit bis</label> <input name=\"date_to\" type=\"date\" class=\"btn bg-neutral-100\"></div><div class=\"grid-cell flex flex-row items-end\"><div class=\"flex flex-row gap-2 w-full\"><button name=\"action\" value=\"insert\" type=\"submit\" class=\"bg-neutral-100 btn hover:bg-neutral-700\">Speichern</button> <button name=\"action\" value=\"delete\" type=\"submit\" class=\"bg-neutral-100 btn hover:bg-red-700 flex basis-[content] items-center\"><span class=\"size-5 icon-[material-symbols-light--delete-outline]\"></span></button></div></div></form></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -232,10 +230,9 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
templ_7745c5c3_Var9 = templ.NopComponent templ_7745c5c3_Var9 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
user := ctx.Value("user").(models.User) user := ctx.Value("user").(models.User)
justify := "justify-center" justify := "justify-center"
if day.IsWorkDay() && len(day.(*models.WorkDay).Bookings) > 1 { if day.IsWorkDay() && !day.IsEmpty() {
justify = "justify-between" justify = "justify-between"
} }
var templ_7745c5c3_Var10 = []any{"grid-sub divide-x-1 hover:bg-neutral-200 transition-colors group"} var templ_7745c5c3_Var10 = []any{"grid-sub divide-x-1 hover:bg-neutral-200 transition-colors group"}
@@ -295,9 +292,8 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if day.IsWorkDay() { if day.IsWorkDay() {
work, pause, overtime := day.GetTimes(user, models.WorktimeBaseDay, true)
workDay, _ := day.(*models.WorkDay) work = day.GetWorktime(user, models.WorktimeBaseDay, false)
work, pause, overtime := workDay.GetAllWorkTimesReal(user)
if day.RequiresAction() { if day.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"text-red-600\">Bitte anpassen</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"text-red-600\">Bitte anpassen</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -350,7 +346,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if overtime != 0 && len(workDay.Bookings) > 0 { if !day.IsEmpty() && overtime != 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<p class=\"text-neutral-500 flex flex-row items-center\"><span class=\"icon-[material-symbols-light--more-time]\"></span> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<p class=\"text-neutral-500 flex flex-row items-center\"><span class=\"icon-[material-symbols-light--more-time]\"></span> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@@ -379,7 +375,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var17 = []any{"flex flex-col gap-2 w-full", justify} var templ_7745c5c3_Var17 = []any{"bookings flex flex-col gap-2 w-full", justify}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...) templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@@ -414,9 +410,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if day.IsWorkDay() { if day.GetDayProgress(user) < 100 || day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay)
templ_7745c5c3_Err = newAbsenceComponent().Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = newAbsenceComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@@ -425,53 +419,33 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if len(workDay.Bookings) < 1 { templ_7745c5c3_Err = timeDayTypeSwitch(day, true).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<p class=\"text group-[.edit]:hidden\">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if workDay.IsKurzArbeit() { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, " ")
templ_7745c5c3_Err = absenceComponent(workDay.GetKurzArbeit(), true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, booking := range workDay.Bookings {
templ_7745c5c3_Err = bookingComponent(booking).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = newBookingComponent(workDay).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = newBookingComponent(day).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} else { } else {
templ_7745c5c3_Err = timeDayTypeSwitch(day, true).Render(ctx, templ_7745c5c3_Buffer)
absentDay, _ := day.(*models.Absence)
templ_7745c5c3_Err = absenceComponent(absentDay, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<input type=\"hidden\" name=\"action\" value=\"change\"><!-- default action value for ändern button --></form></div><div class=\"grid-cell flex flex-row gap-2 items-end\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<input type=\"hidden\" name=\"action\" value=\"change\"><!-- default action value for ändern button --></form></div><div class=\"grid-cell flex flex-row gap-2 items-end\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = changeButtonComponent("time-"+day.Date().Format(time.DateOnly), day.IsWorkDay()).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = changeButtonComponent("time-"+day.Date().Format(time.DateOnly), true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -479,7 +453,7 @@ func defaultDayComponent(day models.IWorkDay) templ.Component {
}) })
} }
func absentInput(a models.Absence) templ.Component { func timeDayTypeSwitch(day models.IWorkDay, fromCompound bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -500,59 +474,145 @@ func absentInput(a models.Absence) templ.Component {
templ_7745c5c3_Var20 = templ.NopComponent templ_7745c5c3_Var20 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<input type=\"hidden\" name=\"date_from\" value=\"") switch day.Type() {
case models.DayTypeWorkday:
workDay, _ := day.(*models.WorkDay)
templ_7745c5c3_Err = workdayComponent(workDay).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case models.DayTypeAbsence:
absentDay, _ := day.(*models.Absence)
templ_7745c5c3_Err = absenceComponent(absentDay, fromCompound).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case models.DayTypeCompound:
for _, c := range day.(*models.CompoundDay).DayParts {
templ_7745c5c3_Err = timeDayTypeSwitch(c, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
default:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(day.ToString())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 160, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func workdayComponent(workDay *models.WorkDay) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var22 := templ.GetChildren(ctx)
if templ_7745c5c3_Var22 == nil {
templ_7745c5c3_Var22 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if len(workDay.Bookings) < 1 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<p class=\"text group-[.edit]:hidden\">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
if workDay.IsKurzArbeit() && len(workDay.Bookings) > 0 {
templ_7745c5c3_Err = absenceComponent(workDay.GetKurzArbeit(), true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, booking := range workDay.Bookings {
templ_7745c5c3_Err = bookingComponent(booking).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " <input type=\"hidden\" name=\"select_kommen\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(len(workDay.Bookings) > 0 && workDay.Bookings[len(workDay.Bookings)-1].CheckInOut%2 == 0)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 174, Col: 140}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func holidayComponent(d models.IWorkDay) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var24 := templ.GetChildren(ctx)
if templ_7745c5c3_Var24 == nil {
templ_7745c5c3_Var24 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var21 string var templ_7745c5c3_Var25 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateFrom.Format(time.DateOnly)) templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(d.ToString())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 162, Col: 79} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 179, Col: 18}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\"> <input type=\"hidden\" name=\"date_to\" value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateTo.Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 163, Col: 75}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\"> <input type=\"hidden\" name=\"aw_type\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(a.AbwesenheitTyp.Id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 164, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\"> <input type=\"hidden\" name=\"aw_id\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(a.CounterId)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 165, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

6
Cron/autoBackup.sh Executable file
View File

@@ -0,0 +1,6 @@
# cron-timing: 05 01 * * 1
container_name="arbeitszeitmessung-main-db-1"
filename=backup-$(date '+%d%m%Y').sql
database_name=__DATABASE__
docker exec $container_name pg_dump $database_name > /home/pi/arbeitszeitmessung-backup/$filename
echo "created backup file: "$filename

3
Cron/autoHolidays.sh Executable file
View File

@@ -0,0 +1,3 @@
# Calls endpoint to write all public Holidays for the current year inside a database.
port=__PORT__
curl localhost:$port/auto/feiertage

4
Cron/autoLogout.sh Executable file
View File

@@ -0,0 +1,4 @@
# cron-timing: 55 23 * * *
# Calls endpoint to log out all users, still logged in for today
port=__PORT__
curl localhost:$port/auto/logout

56
DB/initdb/01_create_user.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/bin/bash
set -e # Exit on error
echo "Creating PostgreSQL user and setting permissions... $POSTGRES_USER for API user $POSTGRES_API_USER"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE ROLE migrate LOGIN ENCRYPTED PASSWORD '$POSTGRES_PASSWORD';
GRANT USAGE, CREATE ON SCHEMA public TO migrate;
GRANT CONNECT ON DATABASE arbeitszeitmessung TO migrate;
EOSQL
# psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
# GRANT SELECT, INSERT, UPDATE ON anwesenheit, abwesenheit, user_password, wochen_report, s_feiertage TO $POSTGRES_API_USER;
# GRANT DELETE ON abwesenheit TO $POSTGRES_API_USER;
# GRANT SELECT ON s_personal_daten, s_abwesenheit_typen, s_anwesenheit_typen, s_feiertage TO $POSTGRES_API_USER;
# GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $POSTGRES_API_USER;
# EOSQL
echo "User creation and permissions setup complete!"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
-- privilege roles
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'app_base') THEN
CREATE ROLE app_base NOLOGIN;
END IF;
END
\$\$;
-- dynamic login role
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '$POSTGRES_API_USER') THEN
CREATE ROLE $POSTGRES_API_USER
LOGIN
ENCRYPTED PASSWORD '$POSTGRES_API_PASS';
END IF;
END
\$\$;
-- grant base privileges
GRANT app_base TO $POSTGRES_API_USER;
GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER;
GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
EOSQL
# psql -v ON_ERROR_STOP=1 --username root --dbname arbeitszeitmessung

View File

@@ -1,8 +0,0 @@
INSERT INTO "s_personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_per_woche", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES
(123, 't', 'Kim', 'Mustermensch', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'aaaa-aaaa', 1, 8, 40, '07:00:00', '20:00:00', 0);
INSERT INTO "user_password" ("personal_nummer", "pass_hash") VALUES
(123, crypt('max_pass', gen_salt('bf')));
INSERT INTO "s_anwesenheit_typen" ("anwesenheit_id", "anwesenheit_name") VALUES (1, 'Büro');
INSERT INTO "s_abwesenheit_typen" ("abwesenheit_id", "abwesenheit_name", "arbeitszeit_equivalent") VALUES (1, 'Urlaub', 10), (2, 'Krank', 10), (3, 'Kurzarbeit', 2);

View File

@@ -1,17 +0,0 @@
#!/bin/bash
set -e # Exit on error
echo "Creating PostgreSQL user and setting permissions... $POSTGRES_USER for API user $POSTGRES_API_USER"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER $POSTGRES_API_USER WITH ENCRYPTED PASSWORD '$POSTGRES_API_PASS';
GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER;
GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER;
GRANT SELECT, INSERT, UPDATE ON anwesenheit, abwesenheit, user_password, wochen_report TO $POSTGRES_API_USER;
GRANT SELECT ON s_personal_daten, s_abwesenheit_typen, s_anwesenheit_typen TO $POSTGRES_API_USER;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $POSTGRES_API_USER;
EOSQL
echo "User creation and permissions setup complete!"
# psql -v ON_ERROR_STOP=1 --username root --dbname arbeitszeitmessung

View File

@@ -3,7 +3,7 @@ services:
db: db:
volumes: volumes:
- ${POSTGRES_PATH}:/var/lib/postgresql/data - ${POSTGRES_PATH}:/var/lib/postgresql/data
# - ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d - ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d
ports: ports:
- 5432:5432 - 5432:5432

View File

@@ -25,12 +25,6 @@ services:
- ${WEB_PORT}:8080 - ${WEB_PORT}:8080
depends_on: depends_on:
- db - db
- document-creator
volumes: volumes:
- ../logs:/app/Backend/logs - ${LOG_PATH}:/app/logs
restart: unless-stopped
document-creator:
image: git.letsstein.de/tom/arbeitszeitmessung-doc-creator
container_name: ${TYPST_CONTAINER}
restart: unless-stopped restart: unless-stopped

View File

@@ -2,11 +2,11 @@ POSTGRES_USER=root # Postgres ADMIN Nutzername
POSTGRES_PASSWORD=very_secure # Postgres ADMIN Passwort POSTGRES_PASSWORD=very_secure # Postgres ADMIN Passwort
POSTGRES_API_USER=api_nutzer # Postgres API Nutzername (für Arbeitszeitmessung) POSTGRES_API_USER=api_nutzer # Postgres API Nutzername (für Arbeitszeitmessung)
POSTGRES_API_PASS=password # Postgres API Passwort (für Arbeitszeitmessung) POSTGRES_API_PASS=password # Postgres API Passwort (für Arbeitszeitmessung)
POSTGRES_PATH=../DB # Datebank Pfad (relativ zu Docker Ordner oder absoluter pfad mit /...) POSTGRES_PATH=__ROOT__/DB # Datebank Pfad (relativ zu Docker Ordner oder absoluter pfad mit /...)
POSTGRES_DB=arbeitszeitmessung # Postgres Datenbank Name POSTGRES_DB=arbeitszeitmessung # Postgres Datenbank Name
POSTGRES_PORT=127.0.0.1:5432 # Postgres Port will not be exposed by default. POSTGRES_PORT=127.0.0.1:5432 # Postgres Port normalerweise nicht freigegeben. regex:^[0-9]{1,5}$
TZ=Europe/Berlin # Zeitzone TZ=Europe/Berlin # Zeitzone
PGTZ=Europe/Berlin # Zeitzone
API_TOKEN=dont_access # API Token für ESP Endpoints API_TOKEN=dont_access # API Token für ESP Endpoints
WEB_PORT=8000 # Port from which Arbeitszeitmessung should be accessable regex:^[0-9]{1,5}$ WEB_PORT=8000 # Port unter welchem Webserver erreichbar ist. regex:^[0-9]{1,5}$
TYPST_CONTAINER=arbeitszeitmessung-doc-creator # Name of the pdf compiler container LOG_PATH=__ROOT__/logs # Pfad für Audit Logs
LOG_LEVEL=warn # Welche Log-Nachrichten werden in der Konsole erscheinen

View File

@@ -1,6 +0,0 @@
FROM ghcr.io/typst/typst:0.14.0
COPY ./templates ./templates
COPY ./static ./static
ENTRYPOINT ["sh", "-c", "while true; do sleep 3600; done"]

View File

@@ -44,7 +44,7 @@ generateFrontend:
backend: generateFrontend login_registry backend: generateFrontend login_registry
docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung:latest Backend --push docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung-webserver:dev Backend --push
# docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung:${GIT_COMMIT} Backend //--push # docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung:${GIT_COMMIT} Backend //--push
test: test:

View File

@@ -1,6 +1,6 @@
# Arbeitszeitmessung # Arbeitszeitmessung
[![Quality Gate Status](https://sonar.letsstein.de/api/project_badges/measure?project=arbeitszeitmessung&metric=alert_status&token=sqb_f8e5ad702b23aa4631a29b99c2f8030caf7d4e05)](https://sonar.letsstein.de/dashboard?id=arbeitszeitmessung) [![Quality Gate Status](https://sonar.letsstein.de/api/project_badges/measure?project=arbeitszeitmessung&metric=alert_status&token=sqb_253028eff30aff24f32b437cd6c484c511b5c33f)](https://sonar.letsstein.de/dashboard?id=arbeitszeitmessung)
bis jetzt ein einfaches Backend mit PostgreSQL Datenbank und GO Webserver um Arbeitszeitbuchungen per HTTP PUT einzufügen bis jetzt ein einfaches Backend mit PostgreSQL Datenbank und GO Webserver um Arbeitszeitbuchungen per HTTP PUT einzufügen

View File

@@ -1,63 +0,0 @@
#set page("a4")
#set text(font: "Lato")
= Stunden
== Kim Mustermensch
Zeitraum: 01.10.2025 - 31.10.2025
Arbeitszeit: 136h 19min
Überstunden: -39h 41min
// #show table.cell: it => {
// if it.y == 0 {
// set text(white)
// strong(it)
// } else if it.body == [] {
// // Replace empty cells with 'N/A'
// pad(..it.inset)[0min]
// } else {
// it
// }
// }
#let subgrid(body) = {
table.cell(colspan: 3, inset: 0em)[
#table(
columns: (1fr, 1fr, 1fr),
gutter: 0em,
stroke: black,
[..#body]
)
]
}
"01.09.2025",
"08:07",
"16:28",
"Büro",
"7h 51min",
"30min",
"-9min",
"02.09.2025",
// return work, pause, overtime
table.cell(colspan: 3, inset: 0em)[#table(
columns: (1fr, 1fr, 1fr),
gutter: 0em,
stroke: black,
[08:12], [16:24], [Büro],
[16:30], [17:24], [Homeoffice]
)],
"6h",
"0min",
"-1h 15min"
)

Binary file not shown.

View File

@@ -4,6 +4,10 @@ set -e
envFile=Docker/.env envFile=Docker/.env
envExample=Docker/env.example envExample=Docker/env.example
autoBackupScript=Cron/autoBackup.sh
autoHolidaysScript=Cron/autoHolidays.sh
autoLogoutScript=Cron/autoLogout.sh
echo "Checking Docker installation..." echo "Checking Docker installation..."
if ! command -v docker >/dev/null 2>&1; then if ! command -v docker >/dev/null 2>&1; then
echo "Docker not found. Install Docker? [y/N]" echo "Docker not found. Install Docker? [y/N]"
@@ -18,12 +22,16 @@ else
echo "Docker is already installed." echo "Docker is already installed."
fi fi
###########################################################################
echo "Checking Docker Compose..." echo "Checking Docker Compose..."
if ! docker compose version >/dev/null 2>&1; then if ! docker compose version >/dev/null 2>&1; then
echo "Docker Compose plugin missing. You may need to update Docker." echo "Docker Compose plugin missing. You may need to update Docker."
exit 1 exit 1
fi fi
###########################################################################
echo "Preparing .env file..." echo "Preparing .env file..."
if [ ! -f $envFile ]; then if [ ! -f $envFile ]; then
if [ -f $envExample ]; then if [ -f $envExample ]; then
@@ -44,6 +52,9 @@ if [ ! -f $envFile ]; then
raw_val=$(printf "%s" "$rest" | sed 's/ *#.*//') raw_val=$(printf "%s" "$rest" | sed 's/ *#.*//')
default_value=$(printf "%s" "$raw_val" | sed 's/"//g') default_value=$(printf "%s" "$raw_val" | sed 's/"//g')
# Replace __ROOT__ with script pwd
default_value="${default_value/__ROOT__/$(pwd)}"
regex="" regex=""
if [[ "$comment" =~ regex:(.*)$ ]]; then if [[ "$comment" =~ regex:(.*)$ ]]; then
regex="${BASH_REMATCH[1]}" regex="${BASH_REMATCH[1]}"
@@ -96,13 +107,80 @@ else
echo "Using existing .env. (found at $envFile)" echo "Using existing .env. (found at $envFile)"
fi fi
###########################################################################
LOG_PATH=$(grep -E '^LOG_PATH=' $envFile | cut -d= -f2)
if [ -z "$LOG_PATH" ]; then
echo "LOG_PATH not found in .env using default $(pwd)/logs"
LOG_PATH=$(pwd)/logs
else
LOG_PATH=Docker/$LOG_PATH
fi
mkdir -p $LOG_PATH
echo "Created logs folder at $LOG_PATH"
###########################################################################
echo -e "\n\n"
echo "Start containers with docker compose up -d? [y/N]" echo "Start containers with docker compose up -d? [y/N]"
read -r start_containers read -r start_containers
if [[ "$start_containers" =~ ^[Yy]$ ]]; then if [[ "$start_containers" =~ ^[Yy]$ ]]; then
cd Docker cd Docker
mkdir ../logs
docker compose up -d docker compose up -d
echo "Containers started." echo "Containers started."
else else
echo "You can start them manually with: docker compose up -d" echo "You can start them manually with: docker compose up -d"
fi fi
###########################################################################
echo -e "\n\n"
echo "Setup Crontab for automatic logout, backup and holiday creation? [y/N]"
read -r setup_cron
if [[ "$setup_cron" =~ ^[Yy]$ ]]; then
WEB_PORT=$(grep -E '^WEB_PORT=' $envFile | cut -d= -f2)
if [ -z "$WEB_PORT" ]; then
echo "WEB_PORT not found in .env using default 8000"
WEB_PORT=8000
fi
POSTGRES_DB=$(grep -E '^POSTGRES_DB=' $envFile | cut -d= -f2)
if [ -z "$POSTGRES_DB" ]; then
echo "arbeitszeitmessung not found in .env using default arbeitszeitmessung"
POSTGRES_DB="arbeitszeitmessung"
fi
sed -i "s/__PORT__/$WEB_PORT/" $autoHolidaysScript
sed -i "s/__PORT__/$WEB_PORT/" $autoLogoutScript
sed -i "s/__DATABASE__/$POSTGRES_DB/" $autoBackupScript
chmod +x $autoBackupScript $autoHolidaysScript $autoLogoutScript
# echo "Scripts build with PORT=$WEB_PORT and DATABSE=$POSTGRES_DB!"
echo "Adding rules to crontab."
cron_commands=$(mktemp /tmp/arbeitszeitmessung-cron.XXX)
for file in Cron/*; do
cron_timing=$(grep -E '^# cron-timing:' "$file" | sed 's/^# cron-timing:[[:space:]]*//')
if [ -z "$cron_timing" ]; then
echo "No cron-timing found in $file, so it's not added to crontab."
continue
fi
( crontab -l ; echo "$cron_timing $(pwd)/$file" )| awk '!x[$0]++' | crontab -
echo "Added entry to crontab: $cron_timing $(pwd)/$file."
sleep 2
done
if systemctl is-active --quiet cron.service ; then
echo "cron.service is running. Everything should be fine now."
else
echo "cron.service is not running. Please start and enable cron.service."
echo "For how to start a service, see: https://wiki.ubuntuusers.de/systemd/systemctl UNITNAME will be cron.service"
fi
else
echo "Please setup cron manually by executing crontab -e and adding all files from inside the Cron directory!"
fi

269
pnpm-lock.yaml generated
View File

@@ -14,9 +14,42 @@ importers:
tailwindcss: tailwindcss:
specifier: ^4.1.12 specifier: ^4.1.12
version: 4.1.12 version: 4.1.12
devDependencies:
'@iconify-json/material-symbols-light':
specifier: ^1.2.33
version: 1.2.50
'@iconify/tailwind4':
specifier: ^1.0.6
version: 1.2.0(tailwindcss@4.1.12)
prettier:
specifier: ^3.6.2
version: 3.7.4
packages: packages:
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
'@cyberalien/svg-utils@1.0.11':
resolution: {integrity: sha512-qEE9mnyI+avfGT3emKuRs3ucYkITeaV0Xi7VlYN41f+uGnZBecQP3jwz/AF437H9J4Q7qPClHKm4NiTYpNE6hA==}
'@iconify-json/material-symbols-light@1.2.50':
resolution: {integrity: sha512-Ehvmar2TPoYxmKgB5szeIMlmvA/mIc7gzUoQ5/AWFG+N6d4T53uCHwxnXFf1nXPWlpf0+cv26AXMJC6W5mkdrQ==}
'@iconify/tailwind4@1.2.0':
resolution: {integrity: sha512-+t7XqfojOB0zzZdd8gV7IQZGq1AaIHTlsxMVzagxYR0hAlJCLUD63o3iSlNKRMH3ZR7gZ8y5c9dJ7J431avRbA==}
peerDependencies:
tailwindcss: '>= 4.0.0'
'@iconify/tools@5.0.1':
resolution: {integrity: sha512-/znhBN9WIpJd9UtKhyEDfRKwNo8rrOy8dShF8bwSZ1i27ukTSHjeS6bmVK4tTYBYriwFhBf70JT6g8GIRwFvbw==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
'@iconify/utils@3.1.0':
resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==}
'@isaacs/fs-minipass@4.0.1': '@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
@@ -208,6 +241,14 @@ packages:
resolution: {integrity: sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==} resolution: {integrity: sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
hasBin: true
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
braces@3.0.3: braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -216,6 +257,32 @@ packages:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'} engines: {node: '>=18'}
commander@11.1.0:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
css-select@5.2.2:
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
css-tree@2.2.1:
resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
css-tree@3.1.0:
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
css-what@6.2.2:
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
engines: {node: '>= 6'}
csso@5.0.5:
resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
detect-libc@1.0.3: detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
@@ -225,10 +292,30 @@ packages:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'} engines: {node: '>=8'}
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
domhandler@5.0.3:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
enhanced-resolve@5.18.3: enhanced-resolve@5.18.3:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
fill-range@7.1.1: fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -319,6 +406,12 @@ packages:
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
mdn-data@2.0.28:
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
mdn-data@2.12.2:
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
micromatch@4.0.8: micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
@@ -336,6 +429,13 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
modern-tar@0.7.3:
resolution: {integrity: sha512-4W79zekKGyYU4JXVmB78DOscMFaJth2gGhgfTl2alWE4rNe3nf4N2pqenQ0rEtIewrnD79M687Ouba3YGTLOvg==}
engines: {node: '>=18.0.0'}
mri@1.2.0: mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -343,6 +443,15 @@ packages:
node-addon-api@7.1.1: node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
package-manager-detector@1.6.0:
resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -350,10 +459,26 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
prettier@3.7.4:
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
engines: {node: '>=14'}
hasBin: true
sax@1.4.3:
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
source-map-js@1.2.1: source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
svgo@4.0.0:
resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==}
engines: {node: '>=16'}
hasBin: true
tailwindcss@4.1.12: tailwindcss@4.1.12:
resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==}
@@ -365,16 +490,61 @@ packages:
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
engines: {node: '>=18'} engines: {node: '>=18'}
tinyexec@1.0.2:
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
engines: {node: '>=18'}
to-regex-range@5.0.1: to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
yallist@5.0.0: yallist@5.0.0:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'} engines: {node: '>=18'}
snapshots: snapshots:
'@antfu/install-pkg@1.1.0':
dependencies:
package-manager-detector: 1.6.0
tinyexec: 1.0.2
'@cyberalien/svg-utils@1.0.11':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/material-symbols-light@1.2.50':
dependencies:
'@iconify/types': 2.0.0
'@iconify/tailwind4@1.2.0(tailwindcss@4.1.12)':
dependencies:
'@iconify/tools': 5.0.1
'@iconify/types': 2.0.0
'@iconify/utils': 3.1.0
tailwindcss: 4.1.12
'@iconify/tools@5.0.1':
dependencies:
'@cyberalien/svg-utils': 1.0.11
'@iconify/types': 2.0.0
'@iconify/utils': 3.1.0
fflate: 0.8.2
modern-tar: 0.7.3
pathe: 2.0.3
svgo: 4.0.0
'@iconify/types@2.0.0': {}
'@iconify/utils@3.1.0':
dependencies:
'@antfu/install-pkg': 1.1.0
'@iconify/types': 2.0.0
mlly: 1.8.0
'@isaacs/fs-minipass@4.0.1': '@isaacs/fs-minipass@4.0.1':
dependencies: dependencies:
minipass: 7.1.2 minipass: 7.1.2
@@ -532,21 +702,75 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.12 '@tailwindcss/oxide-win32-arm64-msvc': 4.1.12
'@tailwindcss/oxide-win32-x64-msvc': 4.1.12 '@tailwindcss/oxide-win32-x64-msvc': 4.1.12
acorn@8.15.0: {}
boolbase@1.0.0: {}
braces@3.0.3: braces@3.0.3:
dependencies: dependencies:
fill-range: 7.1.1 fill-range: 7.1.1
chownr@3.0.0: {} chownr@3.0.0: {}
commander@11.1.0: {}
confbox@0.1.8: {}
css-select@5.2.2:
dependencies:
boolbase: 1.0.0
css-what: 6.2.2
domhandler: 5.0.3
domutils: 3.2.2
nth-check: 2.1.1
css-tree@2.2.1:
dependencies:
mdn-data: 2.0.28
source-map-js: 1.2.1
css-tree@3.1.0:
dependencies:
mdn-data: 2.12.2
source-map-js: 1.2.1
css-what@6.2.2: {}
csso@5.0.5:
dependencies:
css-tree: 2.2.1
detect-libc@1.0.3: {} detect-libc@1.0.3: {}
detect-libc@2.0.4: {} detect-libc@2.0.4: {}
dom-serializer@2.0.0:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
entities: 4.5.0
domelementtype@2.3.0: {}
domhandler@5.0.3:
dependencies:
domelementtype: 2.3.0
domutils@3.2.2:
dependencies:
dom-serializer: 2.0.0
domelementtype: 2.3.0
domhandler: 5.0.3
enhanced-resolve@5.18.3: enhanced-resolve@5.18.3:
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
tapable: 2.2.2 tapable: 2.2.2
entities@4.5.0: {}
fflate@0.8.2: {}
fill-range@7.1.1: fill-range@7.1.1:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
@@ -612,6 +836,10 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
mdn-data@2.0.28: {}
mdn-data@2.12.2: {}
micromatch@4.0.8: micromatch@4.0.8:
dependencies: dependencies:
braces: 3.0.3 braces: 3.0.3
@@ -625,16 +853,53 @@ snapshots:
mkdirp@3.0.1: {} mkdirp@3.0.1: {}
mlly@1.8.0:
dependencies:
acorn: 8.15.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.1
modern-tar@0.7.3: {}
mri@1.2.0: {} mri@1.2.0: {}
node-addon-api@7.1.1: {} node-addon-api@7.1.1: {}
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
package-manager-detector@1.6.0: {}
pathe@2.0.3: {}
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@2.3.1: {} picomatch@2.3.1: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.0
pathe: 2.0.3
prettier@3.7.4: {}
sax@1.4.3: {}
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
svgo@4.0.0:
dependencies:
commander: 11.1.0
css-select: 5.2.2
css-tree: 3.1.0
css-what: 6.2.2
csso: 5.0.5
picocolors: 1.1.1
sax: 1.4.3
tailwindcss@4.1.12: {} tailwindcss@4.1.12: {}
tapable@2.2.2: {} tapable@2.2.2: {}
@@ -648,8 +913,12 @@ snapshots:
mkdirp: 3.0.1 mkdirp: 3.0.1
yallist: 5.0.0 yallist: 5.0.0
tinyexec@1.0.2: {}
to-regex-range@5.0.1: to-regex-range@5.0.1:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
ufo@1.6.1: {}
yallist@5.0.0: {} yallist@5.0.0: {}

View File

@@ -103,8 +103,21 @@ CREATE TABLE "s_abwesenheit_typen" (
"abwesenheit_name" varchar(255) NOT NULL, "abwesenheit_name" varchar(255) NOT NULL,
"arbeitszeit_equivalent" float4 NOT NULL "arbeitszeit_equivalent" float4 NOT NULL
); );
COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; -1=Arbeitszeit auffüllen; <=1 - 100 => Arbeitszeit pro Tag prozentual';
COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; 1=Arbeitszeit auffüllen; 2=Arbeitszeit austauschen'; DROP TABLE IF EXISTS "s_feiertage";
CREATE TABLE "s_feiertage" (
"counter_id" serial PRIMARY KEY NOT NULL,
"datum" date NOT NULL,
"name" varchar(100) NOT NULL,
"wiederholen" smallint NOT NULL DEFAULT 0,
"arbeitszeit_equivalent" smallint NOT NULL DEFAULT 100
);
CREATE UNIQUE index feiertage_unique_pro_jahr on s_feiertage (
extract ( year from datum ),
name
);
-- Adds crypto extension -- Adds crypto extension