66 Commits

Author SHA1 Message Date
ea8e78fd9f fixed #56
Some checks failed
Tests / Run Go Tests (push) Failing after 1m22s
2025-10-09 16:42:49 +02:00
6da58d6753 fixed #54, #55
All checks were successful
Arbeitszeitmessung Deploy / Run Go Tests (push) Successful in 1m28s
Tests / Run Go Tests (push) Successful in 2m19s
Arbeitszeitmessung Deploy / Build Go Image and Upload (push) Successful in 1m39s
2025-10-09 13:12:07 +02:00
tom
89eb5d255d fixed #52
Some checks failed
Tests / Run Go Tests (push) Failing after 1m23s
2025-10-08 12:59:47 +02:00
1b8fb747e8 Update Readme.md
Some checks failed
Tests / Run Go Tests (push) Failing after 22s
2025-10-07 16:24:33 +02:00
74cded42d8 Update test.yaml
All checks were successful
Tests / Run Go Tests (push) Successful in 1m21s
2025-10-07 16:23:01 +02:00
22350142fc Update test.yaml
All checks were successful
Tests / Run Go Tests (push) Successful in 1m21s
2025-10-07 16:20:23 +02:00
659fb80049 updated test.yml
Some checks failed
Tests / Run Go Tests (push) Failing after 1m25s
2025-10-07 16:14:09 +02:00
cbc4028f8d moved sonar.properties
Some checks failed
Tests / Run Go Tests (push) Failing after 1m20s
2025-10-07 16:11:17 +02:00
e4d423385a Update test.yaml
Some checks failed
Tests / Run Go Tests (push) Failing after 1m0s
2025-10-07 16:07:11 +02:00
c9c2d801b0 Update test.yaml
Some checks failed
Tests / Run Go Tests (push) Has been cancelled
2025-10-07 16:06:49 +02:00
94c7c8a36e Update sonar-project.properties
All checks were successful
Tests / Run Go Tests (push) Successful in 1m23s
2025-10-07 15:59:31 +02:00
d69ec600cd Check for coverage output
All checks were successful
Tests / Run Go Tests (push) Successful in 1m23s
2025-10-07 15:56:49 +02:00
95d5c4ab9d Update script.js
All checks were successful
Tests / Run Go Tests (push) Successful in 1m21s
2025-10-07 15:50:29 +02:00
bf841ad5c6 updated sonarqube + fixed first issues
Some checks failed
Tests / Run Go Tests (push) Failing after 1m26s
2025-10-07 15:47:59 +02:00
a1aae9dc56 working on sonarqube
All checks were successful
Tests / Run Go Tests (push) Successful in 1m23s
2025-10-07 15:08:40 +02:00
750fb1ff58 Update test.yaml
All checks were successful
Tests / Run Go Tests (push) Successful in 1m17s
2025-10-07 15:00:39 +02:00
f4e9915e7f Update test.yaml
Some checks failed
Tests / Run Go Tests (push) Failing after 50s
2025-10-07 14:58:53 +02:00
18046bbe18 added sonarqube for static code analysis
All checks were successful
Tests / Run Go Tests (push) Successful in 1m25s
2025-10-07 14:54:31 +02:00
75929e3b7d Merge pull request 'fixed #50, added default action input to defaultDayComponent' (#51) from dev/ui into main
All checks were successful
Tests / Run Go Tests (push) Successful in 49s
Arbeitszeitmessung Deploy / Run Go Tests (push) Successful in 54s
Arbeitszeitmessung Deploy / Build Go Image and Upload (push) Successful in 1m9s
Reviewed-on: #51
2025-10-07 13:01:24 +02:00
627f5b7e5b fixed #50, added default action input to defaultDayComponent
All checks were successful
Tests / Run Go Tests (push) Successful in 22s
2025-10-07 12:55:47 +02:00
9e5dc760d5 Merge pull request 'UX/UI Impovements' (#48) from dev/ui into main
All checks were successful
Arbeitszeitmessung Deploy / Run Go Tests (push) Successful in 52s
Tests / Run Go Tests (push) Successful in 50s
Arbeitszeitmessung Deploy / Build Go Image and Upload (push) Successful in 2m33s
Reviewed-on: #48
2025-10-04 19:39:11 +02:00
tom
0ffb910e37 added user informations cell
All checks were successful
Tests / Run Go Tests (push) Successful in 16s
2025-10-04 19:37:57 +02:00
tom
566776910a ui/ux improvements on time page
All checks were successful
Tests / Run Go Tests (push) Successful in 15s
2025-10-04 19:16:21 +02:00
4d00143a74 Merge pull request 'UI Changes' (#47) from dev/ui into main
All checks were successful
Tests / Run Go Tests (push) Successful in 16s
Reviewed-on: #47
2025-10-01 23:12:56 +02:00
c093127a8c added worktime + overtime to pdf
All checks were successful
Tests / Run Go Tests (push) Successful in 1m48s
2025-10-01 23:02:57 +02:00
3dd4b134c8 closes #44
All checks were successful
Tests / Run Go Tests (push) Successful in 1m43s
2025-10-01 22:53:27 +02:00
7e27c944f3 updated time editing ui
Some checks failed
Tests / Run Go Tests (push) Failing after 34s
2025-10-01 21:56:18 +02:00
5fbe53faf6 Merge pull request 'kurzarbeit + multi day absence' (#46) from dev/kurzarbeit into main
All checks were successful
Tests / Run Go Tests (push) Successful in 1m17s
Arbeitszeitmessung Deploy / Run Go Tests (push) Successful in 1m20s
Arbeitszeitmessung Deploy / Build Go Image and Upload (push) Successful in 1m25s
Reviewed-on: #46
2025-09-30 00:18:51 +02:00
15a2a9c075 overtime only appearing, when there is a booking
All checks were successful
Tests / Run Go Tests (push) Successful in 29s
2025-09-30 00:15:48 +02:00
90193e9346 closes #38, #39, #40
All checks were successful
Tests / Run Go Tests (push) Successful in 37s
2025-09-28 23:29:28 +02:00
tom
e8f1113293 using IWorkDay interface for team
All checks were successful
Tests / Run Go Tests (push) Successful in 42s
2025-09-25 21:52:53 +02:00
tom
db6fc10c28 added interface for workday and absence + multiday absences closes #38, #39 2025-09-23 12:30:02 +02:00
tom
55b0332600 minor fixes 2025-09-16 11:53:41 +02:00
tom
0e1e0b2de0 added volume, to expose logs on host
All checks were successful
Tests / Run Go Tests (push) Successful in 33s
2025-09-15 13:25:33 +02:00
7ceef2c344 Merge pull request 'dev/ui' (#35) from dev/ui into main
All checks were successful
Tests / Run Go Tests (push) Successful in 1m13s
Arbeitszeitmessung Deploy / Run Go Tests (push) Successful in 27s
Arbeitszeitmessung Deploy / Build Go Image and Upload (push) Successful in 1m31s
Reviewed-on: #35
2025-09-15 12:40:26 +02:00
823cb859ea Merge branch 'main' into dev/ui
All checks were successful
Tests / Run Go Tests (push) Successful in 49s
2025-09-15 12:40:00 +02:00
tom
656d4c2340 small fixes in pdf generation + time calculation
All checks were successful
Tests / Run Go Tests (push) Successful in 27s
2025-09-15 12:33:46 +02:00
tom
2d0b117403 added Gleitzeit + Kurzarbeit closes #23
All checks were successful
Tests / Run Go Tests (push) Successful in 33s
2025-09-13 14:12:39 +02:00
tom
ccded6d76b reworked time Calculations 2025-09-13 14:11:26 +02:00
ec69549d13 Merge pull request 'Adding Functions + Finishing CI config' (#34) from dev/ui into main
Some checks failed
Tests / Run Go Tests (push) Failing after 1m15s
Arbeitszeitmessung Deploy / Run Go Tests (push) Successful in 1m17s
Arbeitszeitmessung Deploy / Build Go Image and Upload (push) Successful in 1m44s
Reviewed-on: #34
2025-09-10 09:59:09 +02:00
tom
3d76778d4f Update build.yaml
All checks were successful
Tests / Run Go Tests (push) Successful in 31s
2025-09-10 09:46:06 +02:00
tom
b30686ca06 Update test.yaml
All checks were successful
Tests / Run Go Tests (push) Successful in 34s
2025-09-09 11:15:31 +02:00
tom
2f72eebf22 updated workflows 2025-09-09 11:10:53 +02:00
tom
133e73a55c working on pdf export 2025-09-09 11:07:14 +02:00
2eab598348 working on printable PDF Forms
All checks were successful
Tests / Run Go Tests (push) Successful in 30s
2025-09-08 00:32:29 +02:00
12ed9959cb added helper function, and fixed #28
All checks were successful
Tests / Run Go Tests (push) Successful in 29s
2025-09-05 22:24:42 +02:00
de03c100d4 fixed #28
All checks were successful
Tests / Run Go Tests (push) Successful in 28s
2025-09-05 10:36:26 +02:00
9d70d4db17 updated workflows
All checks were successful
Tests / Run Go Tests (push) Successful in 30s
2025-09-05 00:16:10 +02:00
66db633dc6 Merge pull request 'dev/ui' (#33) from dev/ui into main
All checks were successful
GoLang Tests / Run Go Tests (push) Successful in 27s
Reviewed-on: #33
2025-09-04 22:12:52 +02:00
fe442e8eef closes #14
All checks were successful
GoLang Tests / Run Go Tests (push) Successful in 33s
2025-09-04 22:07:54 +02:00
9ded540314 closed #25, #32
All checks were successful
GoLang Tests / Run Go Tests (push) Successful in 49s
2025-09-04 21:22:26 +02:00
0dd75c2126 Update build-deploy.yaml
All checks were successful
GoLang Tests / Run Go Tests (push) Successful in 59s
2025-09-04 10:23:37 +02:00
327e47840b Fixed tests and git actions (#30)
All checks were successful
GoLang Tests / Run Go Tests (push) Successful in 2m46s
Arbeitszeitmessung Deploy / Build Go Image and Upload (push) Successful in 2m58s
Reviewed-on: #30
Co-authored-by: Tom Tröger <t.troeger.02@gmail.com>
Co-committed-by: Tom Tröger <t.troeger.02@gmail.com>
2025-09-04 10:16:42 +02:00
e9f8ab0a56 Merge pull request 'dev/main -- added License' (#29) from dev/main into main
Some checks failed
GoLang Tests / Run Go Tests (push) Failing after 48s
GoLang Tests / Build Go Image and Upload (push) Has been skipped
Reviewed-on: #29
2025-09-04 09:24:29 +02:00
bcefd7b630 Merge pull request 'dev/actions feature: overtime' (#27) from dev/actions into main
Some checks failed
GoLang Tests / Run Go Tests (push) Failing after 51s
GoLang Tests / Build Go Image and Upload (push) Has been skipped
Reviewed-on: #27
2025-09-04 00:58:25 +02:00
a2cd118644 addin for sonarcube
Some checks failed
GoLang Tests / Run Go Tests (push) Failing after 36s
GoLang Tests / Build Go Image and Upload (push) Has been skipped
2025-09-04 00:56:20 +02:00
d51b0c12c5 update
Some checks failed
GoLang Tests / Build Go Image and Upload (push) Has been cancelled
GoLang Tests / Run Go Tests (push) Has been cancelled
2025-09-04 00:55:35 +02:00
15e28e1b18 added database migrations 2025-09-04 00:48:30 +02:00
1ae30c11cb added overtime to time and team page + ui improvements + mobile support for team page closed #12 2025-09-04 00:11:33 +02:00
45440b6457 added tests
Some checks failed
GoLang Tests / Run Go Tests (push) Failing after 35s
GoLang Tests / Build Go Image and Upload (push) Has been skipped
2025-09-03 14:31:57 +02:00
483c1e29ba ui update /team + overtime updates 2025-09-03 14:31:57 +02:00
492216b160 added overtime to week report closes #18 2025-09-03 14:31:57 +02:00
1397530cb6 added absence hours 2025-09-03 14:27:27 +02:00
de6da2906f added booking types + working on overtime 2025-09-01 22:41:21 +02:00
aa152866d9 added types 2025-09-01 22:11:37 +02:00
28f832694a redoing migratinos 2025-09-01 22:11:23 +02:00
73 changed files with 5977 additions and 1776 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Ensure all text files use LF line endings
* text=auto eol=lf

View File

@@ -1,6 +1,8 @@
name: GoLang Tests name: Arbeitszeitmessung Deploy
run-name: ${{ gitea.actor }} is testing golang Code run-name: ${{ gitea.actor }} is building and deploying arbeitszeitmesssung
on: [push] on:
push:
tags: "*"
jobs: jobs:
testing: testing:
@@ -47,9 +49,9 @@ jobs:
- name: Run Go Tests - name: Run Go Tests
run: cd Backend && go test ./... run: cd Backend && go test ./...
build: build:
needs: testing
name: Build Go Image and Upload name: Build Go Image and Upload
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [testing]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -68,5 +70,7 @@ jobs:
with: with:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: git.letsstein.de/tom/arbeitszeitmessung:latest
context: Backend context: Backend
tags: |
git.letsstein.de/tom/arbeitszeitmessung:latest
git.letsstein.de/tom/arbeitszeitmessung:${{ github.ref_name }}

View File

@@ -0,0 +1,71 @@
name: Tests
run-name: ${{ gitea.actor }} is testing golang Code
on: [push]
jobs:
testing:
name: Run Go Tests
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
with:
# Disabling shallow clone is recommended for improving relevancy of reporting
fetch-depth: 0
- 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 && mkdir .test && go test ./... -coverprofile=.test/coverage.out -json > .test/report.json
- name: Verify coverage report exists
run: |
if [ -f "Backend/.test/coverage.out" ]; then
echo "Coverage report found"
else
echo "Coverage report not found"
fi
- uses: SonarSource/sonarqube-scan-action@v6
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
with:
projectBaseDir: Backend
args: >
-Dsonar.projectVersion=${{ gitea.sha_short }}
- uses: SonarSource/sonarqube-quality-gate-action@v1
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
scanMetadataReportFile: Backend/.scannerwork/report-task.txt

1
.gitignore vendored
View File

@@ -38,3 +38,4 @@ DB/pg_data
node_modules node_modules
atlas.hcl atlas.hcl
.scannerwork .scannerwork
Backend/logs

View File

@@ -13,7 +13,8 @@ RUN go mod download && go mod verify
COPY . . COPY . .
RUN go build -o server . RUN go build -o server .
FROM alpine FROM alpine:3.22
RUN apk add --no-cache tzdata
WORKDIR /app WORKDIR /app
COPY --from=build /app/server /app/server COPY --from=build /app/server /app/server

View File

@@ -3,4 +3,4 @@ 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 sonar-scanner -Dsonar.token=sqa_ca8394c93a728d6cff96703955288d8902c15200

41
Backend/endpoints/pdf.go Normal file
View File

@@ -0,0 +1,41 @@
package endpoints
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"arbeitszeitmessung/templates"
"log"
"net/http"
"time"
)
func PDFHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
startDate, err := parseTimestamp(r, "start", time.Now().Format("2006-01-02"))
if err != nil {
log.Println("Error parsing 'start_date' time", err)
http.Error(w, "Timestamp 'start_date' cannot be parsed!", http.StatusBadRequest)
return
}
if startDate.Day() > 1 {
startDate = startDate.AddDate(0, 0, -(startDate.Day() - 1))
}
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
weeks := models.GetDays(user, startDate, endDate, false)
var aggregatedOvertime, aggregatedWorkTime time.Duration
for _, day := range weeks {
aggregatedOvertime += day.TimeOvertimeReal(user)
aggregatedWorkTime += day.TimeWorkVirtual(user)
}
// log.Printf("Using Dates: %s - %s\n", startDate.String(), endDate.String())
templates.PDFReportEmploye(user, aggregatedOvertime, aggregatedWorkTime, weeks, startDate, endDate).Render(r.Context(), w)
}

View File

@@ -34,7 +34,7 @@ func submitReport(w http.ResponseWriter, r *http.Request) {
_weekTs := r.FormValue("week") _weekTs := r.FormValue("week")
weekTs, err := time.Parse(time.DateOnly, _weekTs) weekTs, err := time.Parse(time.DateOnly, _weekTs)
user, err := models.GetUserByPersonalNr(userPN) user, err := models.GetUserByPersonalNr(userPN)
workWeek := (*models.WorkWeek).GetWeek(nil, user, weekTs, false) workWeek := models.NewWorkWeek(user, weekTs, true)
if err != nil { if err != nil {
log.Println("Could not get user!") log.Println("Could not get user!")
@@ -43,7 +43,7 @@ func submitReport(w http.ResponseWriter, r *http.Request) {
switch r.FormValue("method") { switch r.FormValue("method") {
case "send": case "send":
err = workWeek.Send() err = workWeek.SendWeek()
case "accept": case "accept":
err = workWeek.Accept() err = workWeek.Accept()
default: default:
@@ -56,21 +56,21 @@ func submitReport(w http.ResponseWriter, r *http.Request) {
} }
func showWeeks(w http.ResponseWriter, r *http.Request) { func showWeeks(w http.ResponseWriter, r *http.Request) {
user, err := (*models.User).GetUserFromSession(nil, Session, r.Context()) user, err := models.GetUserFromSession(Session, r.Context())
if err != nil { if err != nil {
log.Println("No user found with the given personal number!") log.Println("No user found with the given personal number!")
http.Redirect(w, r, "/user/login", http.StatusSeeOther) http.Redirect(w, r, "/user/login", http.StatusSeeOther)
return return
} }
submissionDate := r.URL.Query().Get("submission_date") submissionDate := r.URL.Query().Get("submission_date")
lastSub := user.GetLastSubmission() lastSub := user.GetLastWorkWeekSubmission()
if submissionDate != "" { if submissionDate != "" {
submissionDate, err := time.Parse("2006-01-02", submissionDate) submissionDate, err := time.Parse("2006-01-02", submissionDate)
if err == nil { if err == nil {
lastSub = helper.GetMonday(submissionDate) lastSub = helper.GetMonday(submissionDate)
} }
} }
userWeek := (*models.WorkWeek).GetWeek(nil, user, lastSub, true) userWeek := models.NewWorkWeek(user, lastSub, true)
var workWeeks []models.WorkWeek var workWeeks []models.WorkWeek
teamMembers, err := user.GetTeamMembers() teamMembers, err := user.GetTeamMembers()

View File

@@ -23,15 +23,14 @@ func TeamPresenceHandler(w http.ResponseWriter, r *http.Request) {
} }
func teamPresence(w http.ResponseWriter, r *http.Request) { func teamPresence(w http.ResponseWriter, r *http.Request) {
user, err := (*models.User).GetUserFromSession(nil, Session, r.Context()) user, err := models.GetUserFromSession(Session, r.Context())
if err != nil { if err != nil {
log.Println("Error getting user!", err) log.Println("Error getting user!", err)
} }
team, err := user.GetTeamMembers() team, err := user.GetTeamMembers()
teamPresence := make(map[bool][]models.User) teamPresence := make(map[models.User]bool)
for _, user := range team { for _, user := range team {
present := user.CheckAnwesenheit() teamPresence[user] = user.CheckAnwesenheit()
teamPresence[present] = append(teamPresence[present], user)
} }
if err != nil { if err != nil {

View File

@@ -50,8 +50,9 @@ func createBooking(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted) w.WriteHeader(http.StatusAccepted)
json.NewEncoder(w).Encode(booking) json.NewEncoder(w).Encode(booking)
return
} }
w.WriteHeader(http.StatusBadRequest) http.Error(w, "Cannot verify booking, maybe missing a parameter", http.StatusBadRequest)
} }
func verifyToken(r *http.Request) bool { func verifyToken(r *http.Request) bool {

View File

@@ -5,6 +5,7 @@ import (
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"arbeitszeitmessung/templates" "arbeitszeitmessung/templates"
"context" "context"
"database/sql"
"encoding/json" "encoding/json"
"log" "log"
"net/http" "net/http"
@@ -30,6 +31,22 @@ func TimeHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
func AbsencHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
helper.SetCors(w)
switch r.Method {
case http.MethodPost:
err := updateAbsence(r)
if err != nil {
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/time", 301)
default:
http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed)
}
}
func parseTimestamp(r *http.Request, getKey string, fallback string) (time.Time, error) { func parseTimestamp(r *http.Request, getKey string, fallback string) (time.Time, error) {
getTimestamp := r.URL.Query().Get(getKey) getTimestamp := r.URL.Query().Get(getKey)
if getTimestamp == "" { if getTimestamp == "" {
@@ -44,7 +61,7 @@ func parseTimestamp(r *http.Request, getKey string, fallback string) (time.Time,
// Returns bookings from DB with similar card uid -> checks for card uid in http query params // Returns bookings from DB with similar card uid -> checks for card uid in http query params
func getBookings(w http.ResponseWriter, r *http.Request) { func getBookings(w http.ResponseWriter, r *http.Request) {
user, err := (*models.User).GetUserFromSession(nil, Session, r.Context()) user, err := models.GetUserFromSession(Session, r.Context())
if err != nil { if err != nil {
log.Println("No user found with the given personal number!") log.Println("No user found with the given personal number!")
http.Redirect(w, r, "/user/login", http.StatusSeeOther) http.Redirect(w, r, "/user/login", http.StatusSeeOther)
@@ -66,20 +83,35 @@ func getBookings(w http.ResponseWriter, r *http.Request) {
} }
tsTo = tsTo.AddDate(0, 0, 1) // so that today is inside tsTo = tsTo.AddDate(0, 0, 1) // so that today is inside
workDays := (*models.WorkDay).GetWorkDays(nil, user.CardUID, tsFrom, tsTo) days := models.GetDays(user, tsFrom, tsTo, true)
sort.Slice(workDays, func(i, j int) bool { sort.Slice(days, func(i, j int) bool {
return workDays[i].Day.After(workDays[j].Day) return days[i].Date().After(days[j].Date())
}) })
lastSub := user.GetLastWorkWeekSubmission()
var aggregatedOvertime time.Duration
for _, day := range days {
if day.Date().Before(lastSub) {
continue
}
aggregatedOvertime += day.TimeOvertimeReal(user)
}
if reportedOvertime, err := user.GetReportedOvertime(); err == nil {
user.Overtime = (reportedOvertime + aggregatedOvertime).Round(time.Minute)
} else {
log.Println("Cannot calculate overtime: ", err)
}
if r.Header.Get("Accept") == "application/json" { if r.Header.Get("Accept") == "application/json" {
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(workDays) json.NewEncoder(w).Encode(days)
return return
} }
ctx := context.WithValue(r.Context(), "user", user) ctx := context.WithValue(r.Context(), "user", user)
templates.TimePage(workDays).Render(ctx, w) ctx = context.WithValue(ctx, "days", days)
templates.TimePage([]models.WorkDay{}, lastSub).Render(ctx, w)
} }
func updateBooking(w http.ResponseWriter, r *http.Request) { func updateBooking(w http.ResponseWriter, r *http.Request) {
@@ -90,11 +122,12 @@ func updateBooking(w http.ResponseWriter, r *http.Request) {
log.Println("Error loading location", err) log.Println("Error loading location", err)
loc = time.Local loc = time.Local
} }
user, err := (*models.User).GetUserFromSession(nil, Session, r.Context()) user, err := models.GetUserFromSession(Session, r.Context())
if err != nil { if err != nil {
log.Println("No user found!", err) log.Println("No user found!", err)
return return
} }
switch r.FormValue("action") { switch r.FormValue("action") {
case "add": case "add":
timestamp, err := time.ParseInLocation("2006-01-02|15:04", r.FormValue("date")+"|"+r.FormValue("timestamp"), loc) timestamp, err := time.ParseInLocation("2006-01-02|15:04", r.FormValue("date")+"|"+r.FormValue("timestamp"), loc)
@@ -110,21 +143,21 @@ func updateBooking(w http.ResponseWriter, r *http.Request) {
return return
} }
newBooking := (*models.Booking).New(nil, user.CardUID, 0, int16(check_in_out)) newBooking := (*models.Booking).New(nil, user.CardUID, 0, int16(check_in_out), 1)
newBooking.Timestamp = timestamp newBooking.Timestamp = timestamp
err = newBooking.InsertWithTimestamp() err = newBooking.InsertWithTimestamp()
if err != nil { if err != nil {
log.Println("Error inserting booking", err) log.Printf("Error inserting booking %v -> %v\n", newBooking, err)
} }
case "change": case "change":
absenceType, err := strconv.Atoi(r.FormValue("absence")) // absenceType, err := strconv.Atoi(r.FormValue("absence"))
if err != nil { // if err != nil {
log.Println("Error parsing absence type.", err) // log.Println("Error parsing absence type.", err)
absenceType = 0 // absenceType = 0
} // }
if absenceType != 0 { // if absenceType != 0 {
createAbsence(absenceType, user, loc, r) // 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:])
@@ -142,14 +175,93 @@ func updateBooking(w http.ResponseWriter, r *http.Request) {
log.Println("Error parsing time!", err) log.Println("Error parsing time!", err)
continue continue
} }
// log.Println("Parsing time", parsedTime)
booking.UpdateTime(parsedTime) booking.UpdateTime(parsedTime)
} }
} }
default:
log.Println("No action from /time found")
} }
getBookings(w, r) getBookings(w, r)
} }
func updateAbsence(r *http.Request) error {
r.ParseForm()
var loc *time.Location
loc, err := time.LoadLocation(helper.GetEnv("TZ", "Europe/Berlin"))
if err != nil {
log.Println("Error loading location", err)
loc = time.Local
}
dateFrom, err := time.ParseInLocation("2006-01-02", r.FormValue("date_from"), loc)
if err != nil {
log.Println("Error parsing date_from input for absence", err)
return err
}
dateTo, err := time.ParseInLocation("2006-01-02", r.FormValue("date_to"), loc)
if err != nil {
log.Println("Error parsing date_to input for absence", err)
return err
}
absenceTypeId, err := strconv.Atoi(r.FormValue("aw_type"))
if err != nil {
log.Println("Error parsing aw_type", err)
return err
}
absenceId, err := strconv.Atoi(r.FormValue("aw_id"))
if err != nil && r.FormValue("aw_id") == "" {
absenceId = 0
} else if err != nil {
log.Println("Error parsing aw_id", err)
return err
}
absenceType, err := models.GetAbsenceTypeById(int8(absenceTypeId))
if err != nil {
log.Println("No matching absence type found!")
return err
}
newAbsence := models.Absence{DateFrom: dateFrom, DateTo: dateTo, AbwesenheitTyp: absenceType}
absence, err := models.GetAbsenceById(absenceId)
if err == sql.ErrNoRows {
err = nil
log.Println("Absence not found creating new!")
user, err := models.GetUserFromSession(Session, r.Context())
if err != nil {
log.Println("No user found!", err)
return err
}
newAbsence.CardUID = user.CardUID
newAbsence.Insert()
}
if err != nil {
log.Println("Cannot get Absence for id: ", absenceId, err)
return err
}
if r.FormValue("action") == "delete" {
log.Println("Deleting Absence!", "Not implemented")
// TODO
//absence.Delete()
return nil
}
if absence.Update(newAbsence) {
err = absence.Save()
if err != nil {
log.Println("Error saving updated absence!", err)
return err
}
}
return nil
}
func createAbsence(absenceType int, user models.User, loc *time.Location, r *http.Request) { func createAbsence(absenceType int, user models.User, loc *time.Location, r *http.Request) {
absenceDate, err := time.ParseInLocation("2006-01-02", r.FormValue("date"), loc) absenceDate, err := time.ParseInLocation("2006-01-02", r.FormValue("date"), loc)
if err != nil { if err != nil {
@@ -168,82 +280,3 @@ func createAbsence(absenceType int, user models.User, loc *time.Location, r *htt
return return
} }
} }
func getBookingsAPI(w http.ResponseWriter, r *http.Request) {
_user_pn := r.URL.Query().Get("personal_nummer")
user_pn, err := strconv.Atoi(_user_pn)
if err != nil {
log.Println("No personal numver found!")
http.Error(w, "No personal number found", http.StatusBadRequest)
return
}
user, err := models.GetUserByPersonalNr(user_pn)
if err != nil {
log.Println("No user found with the given personal number!")
http.Error(w, "No user found", http.StatusNotFound)
return
}
// TODO add config for timeoffset
tsFrom, err := parseTimestamp(r, "time_from", time.Now().AddDate(0, -1, 0).Format("2006-01-02"))
if err != nil {
log.Println("Error parsing 'from' time", err)
http.Error(w, "Timestamp 'from' cannot be parsed!", http.StatusBadRequest)
return
}
tsTo, err := parseTimestamp(r, "time_to", time.Now().Format("2006-01-02"))
if err != nil {
log.Println("Error parsing 'to' time", err)
http.Error(w, "Timestamp 'to' cannot be parsed!", http.StatusBadRequest)
return
}
tsTo = tsTo.AddDate(0, 0, 1) // so that today is inside
bookings, err := (*models.Booking).GetBookingsGrouped(nil, user.CardUID, tsFrom, tsTo)
if err != nil {
log.Println("Error getting bookings: ", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(bookings)
}
// Updates a booking form the given json body
func updateBookingAPI(w http.ResponseWriter, r *http.Request) {
_booking_id := r.URL.Query().Get("counter_id")
if _booking_id == "" {
http.Error(w, "Missing bookingID query parameter", http.StatusBadRequest)
return
}
booking_id, err := strconv.Atoi(_booking_id)
if err != nil {
http.Error(w, "Invalid bookingID query parameter", http.StatusBadRequest)
return
}
bookingDB, err := (*models.Booking).GetBookingById(nil, booking_id)
if err != nil {
log.Println("Error getting booking: ", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
var booking models.Booking
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
err = dec.Decode(&booking)
if err != nil {
log.Println("Error parsing booking: ", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
if booking.CounterId != 0 && booking.CounterId != bookingDB.CounterId {
log.Println("Booking Ids do not match")
http.Error(w, "Booking Ids do not match", http.StatusBadRequest)
return
}
bookingDB.Update(booking)
bookingDB.Save()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(bookingDB)
}

View File

@@ -1,7 +1,6 @@
package endpoints package endpoints
import ( import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"arbeitszeitmessung/templates" "arbeitszeitmessung/templates"
"context" "context"
@@ -21,54 +20,48 @@ func CreateSessionManager(lifetime time.Duration) *scs.SessionManager {
return Session return Session
} }
func showLoginPage(w http.ResponseWriter, r *http.Request, failed bool) { func showLoginPage(w http.ResponseWriter, r *http.Request, success bool, errorMsg string) {
r = r.WithContext(context.WithValue(r.Context(), "session", Session)) r = r.WithContext(context.WithValue(r.Context(), "session", Session))
if helper.GetEnv("GO_ENV", "production") == "debug" {
// http.Redirect(w, r, "/time", http.StatusSeeOther)
templates.LoginPage(failed).Render(r.Context(), w)
}
if Session.Exists(r.Context(), "user") { if Session.Exists(r.Context(), "user") {
http.Redirect(w, r, "/time", http.StatusSeeOther) http.Redirect(w, r, "/time", http.StatusSeeOther)
} }
templates.LoginPage(failed).Render(r.Context(), w) templates.LoginPage(success, errorMsg).Render(r.Context(), w)
} }
func loginUser(w http.ResponseWriter, r *http.Request) { func loginUser(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
log.Println("Error parsing form!", err) log.Println("Error parsing form!", err)
http.Error(w, "Internal error", http.StatusBadRequest) showLoginPage(w, r, false, "Internal error!")
return return
} }
_personal_nummer := r.FormValue("personal_nummer") _personal_nummer := r.FormValue("personal_nummer")
if _personal_nummer == "" { if _personal_nummer == "" {
log.Println("No personal_nummer provided!") log.Println("No personal_nummer provided!")
http.Error(w, "No personal_nummer provided", http.StatusBadRequest) showLoginPage(w, r, false, "Keine Personalnummer gesetzt.")
return return
} }
personal_nummer, err := strconv.Atoi(_personal_nummer) personal_nummer, err := strconv.Atoi(_personal_nummer)
if err != nil { if err != nil {
log.Println("Cannot parse personal nubmer!") log.Println("Cannot parse personal nubmer!")
http.Error(w, "Cannot parse number", http.StatusBadRequest) showLoginPage(w, r, false, "Personalnummer ist nicht valide gesetzt.")
return return
} }
user, err := models.GetUserByPersonalNr(personal_nummer) user, err := models.GetUserByPersonalNr(personal_nummer)
if err != nil { if err != nil {
log.Println("No user found under this personal number!") log.Println("No user found under this personal number!", err)
http.Error(w, "No user found!", http.StatusNotFound) showLoginPage(w, r, false, "Nutzer unter dieser Personalnummer nicht gefunden.")
return
} }
password := r.FormValue("password") password := r.FormValue("password")
if user.Login(password) { if user.Login(password) {
log.Printf("New succesfull user login from %s %s!\n", user.Vorname, user.Name) log.Printf("New succesfull user login from %s %s (%d)!\n", user.Vorname, user.Name, user.PersonalNummer)
Session.Put(r.Context(), "user", user.PersonalNummer) Session.Put(r.Context(), "user", user.PersonalNummer)
Session.Commit(r.Context())
http.Redirect(w, r, "/time", http.StatusSeeOther) //with this browser always uses GET http.Redirect(w, r, "/time", http.StatusSeeOther) //with this browser always uses GET
} else {
showLoginPage(w, r, true)
return
} }
showLoginPage(w, r, false) showLoginPage(w, r, false, "")
} }
func logoutUser(w http.ResponseWriter, r *http.Request) { func logoutUser(w http.ResponseWriter, r *http.Request) {

View File

@@ -3,6 +3,7 @@ package endpoints
import ( import (
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"arbeitszeitmessung/templates" "arbeitszeitmessung/templates"
"context"
"log" "log"
"net/http" "net/http"
) )
@@ -38,5 +39,9 @@ func changePassword(w http.ResponseWriter, r *http.Request) {
} }
func showUserPage(w http.ResponseWriter, r *http.Request, status int) { func showUserPage(w http.ResponseWriter, r *http.Request, status int) {
templates.UserPage(status).Render(r.Context(), w) var ctx context.Context
if user, err := models.GetUserFromSession(Session, r.Context()); err == nil {
ctx = context.WithValue(r.Context(), "user", user)
}
templates.UserPage(status).Render(ctx, w)
} }

View File

@@ -19,7 +19,7 @@ func UserHandler(w http.ResponseWriter, r *http.Request) {
func LoginHandler(w http.ResponseWriter, r *http.Request) { func LoginHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
showLoginPage(w, r, false) showLoginPage(w, r, true, "")
case http.MethodPost: case http.MethodPost:
loginUser(w, r) loginUser(w, r)
default: default:
@@ -29,6 +29,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
func UserSettingsHandler(w http.ResponseWriter, r *http.Request) { func UserSettingsHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r) helper.RequiresLogin(Session, w, r)
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
showUserPage(w, r, 0) showUserPage(w, r, 0)

View File

@@ -1,6 +1,6 @@
module arbeitszeitmessung module arbeitszeitmessung
go 1.24.5 go 1.24.7
require github.com/lib/pq v1.10.9 require github.com/lib/pq v1.10.9
@@ -17,4 +17,5 @@ require (
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
golang.org/x/sys v0.36.0 // indirect
) )

View File

@@ -68,7 +68,7 @@ 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/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=

View File

@@ -0,0 +1,24 @@
package logs
import (
"log"
"os"
"time"
)
type FileLog struct {
Logger *log.Logger
Close func() error
}
var Logs map[string]FileLog = make(map[string]FileLog)
func NewAudit() (i *log.Logger, close func() error) {
LOG_FILE := "logs/" + time.Now().Format("2006-01-02") + ".log"
logFile, err := os.OpenFile(LOG_FILE, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Panic(err)
}
return log.New(logFile, "", log.LstdFlags), logFile.Close
}

View File

@@ -0,0 +1,9 @@
package helper
func GetFirst[T, U any](val T, _ U) T {
return val
}
func GetSecond[T, U any](_ T, val U) U {
return val
}

View File

@@ -16,8 +16,21 @@ func GetMonday(ts time.Time) time.Time {
return ts return ts
} }
// Converts duration to string func IsWeekend(ts time.Time) bool {
return ts.Weekday() == time.Saturday || ts.Weekday() == time.Sunday
}
func GetKW(t time.Time) int {
_, kw := t.ISOWeek()
return kw
}
func FormatDuration(d time.Duration) string { func FormatDuration(d time.Duration) string {
return FormatDurationFill(d, false)
}
// Converts duration to string
func FormatDurationFill(d time.Duration, fill bool) string {
hours := int(d.Abs().Hours()) hours := int(d.Abs().Hours())
minutes := int(d.Abs().Minutes()) % 60 minutes := int(d.Abs().Minutes()) % 60
sign := "" sign := ""
@@ -32,6 +45,13 @@ func FormatDuration(d time.Duration) string {
case minutes > 0: case minutes > 0:
return fmt.Sprintf("%s%dmin", sign, minutes) return fmt.Sprintf("%s%dmin", sign, minutes)
default: default:
if fill {
return "0min"
}
return "" return ""
} }
} }
func IsSameDate(a, b time.Time) bool {
return a.Truncate(24 * time.Hour).Equal(b.Truncate(24 * time.Hour))
}

View File

@@ -8,7 +8,7 @@ import (
func TestGetMonday(t *testing.T) { func TestGetMonday(t *testing.T) {
isMonday, err := time.Parse("2006-01-02", "2025-07-14") isMonday, err := time.Parse("2006-01-02", "2025-07-14")
notMonday, err := time.Parse("2006-01-02", "2025-07-16") notMonday, err := time.Parse("2006-01-02", "2025-07-16")
if err != nil || isMonday == 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 || GetMonday(notMonday) != isMonday {

View File

@@ -3,7 +3,23 @@ package helper
import "time" import "time"
type TimeFormValue struct { type TimeFormValue struct {
TsFrom time.Time TsFrom time.Time
TsTo time.Time TsTo time.Time
CardUID string CardUID string
} }
func BoolToInt(b bool) int {
var i int = 0
if b {
i = 1
}
return i
}
func BoolToInt8(b bool) int8 {
var i int8 = 0
if b {
i = 1
}
return i
}

View File

@@ -43,6 +43,7 @@ func main() {
// handles the different http routes // handles the different http routes
server.HandleFunc("/time/new", endpoints.TimeCreateHandler) server.HandleFunc("/time/new", endpoints.TimeCreateHandler)
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("/logout", endpoints.LogoutHandler)
server.HandleFunc("/user/{action}", endpoints.UserHandler) server.HandleFunc("/user/{action}", endpoints.UserHandler)
@@ -50,6 +51,7 @@ func main() {
// server.HandleFunc("/user/settings", endpoints.UserSettingsHandler) // server.HandleFunc("/user/settings", endpoints.UserSettingsHandler)
server.HandleFunc("/team", endpoints.TeamHandler) server.HandleFunc("/team", endpoints.TeamHandler)
server.HandleFunc("/team/presence", endpoints.TeamPresenceHandler) server.HandleFunc("/team/presence", endpoints.TeamPresenceHandler)
server.HandleFunc("/pdf", endpoints.PDFHandler)
server.Handle("/", http.RedirectHandler("/time", http.StatusPermanentRedirect)) server.Handle("/", http.RedirectHandler("/time", http.StatusPermanentRedirect))
server.Handle("/static/", http.StripPrefix("/static/", fs)) server.Handle("/static/", http.StripPrefix("/static/", fs))

View File

@@ -1,30 +1,33 @@
package models package models
import ( import (
"encoding/json"
"errors" "errors"
"log" "log"
"time" "time"
) )
type AbsenceType struct { type AbsenceType struct {
Id int8 Id int8 `json:"abwesenheit_id"`
Name string Name string `json:"abwesenheit_name"`
WorkTime int8 `json:"arbeitszeit_equivalent"`
} }
type Absence struct { type Absence struct {
Day time.Time
CounterId int CounterId int
CardUID string CardUID string
AbwesenheitTyp AbsenceType AbwesenheitTyp AbsenceType
Datum time.Time DateFrom time.Time
// Comment string DateTo time.Time
} }
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{
CardUID: card_uid, CardUID: card_uid,
AbwesenheitTyp: AbsenceType{0, "Custom absence"}, AbwesenheitTyp: AbsenceType{0, "Custom absence", 100},
Datum: datum, DateFrom: datum,
}, nil }, nil
} }
_absenceType, ok := GetAbsenceTypesCached()[int8(abwesenheit_typ)] _absenceType, ok := GetAbsenceTypesCached()[int8(abwesenheit_typ)]
@@ -34,17 +37,75 @@ func NewAbsence(card_uid string, abwesenheit_typ int, datum time.Time) (Absence,
return Absence{ return Absence{
CardUID: card_uid, CardUID: card_uid,
AbwesenheitTyp: _absenceType, AbwesenheitTyp: _absenceType,
Datum: datum, DateFrom: datum,
}, nil }, nil
} }
func (a *Absence) Date() time.Time {
return a.Day.Truncate(24 * time.Hour)
}
func (a *Absence) IsMultiDay() bool {
return !a.DateFrom.Equal(a.DateTo)
}
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 {
return "Abwesenheit"
}
func (a *Absence) IsWorkDay() bool {
return false
}
func (a *Absence) IsKurzArbeit() bool {
return false
}
func (a *Absence) GetDayProgress(u User) int8 {
return 100
}
func (a *Absence) RequiresAction() bool {
return false
}
func (a *Absence) GetAllWorkTimesVirtual(u User) (work, pause, overtime time.Duration) {
if a.AbwesenheitTyp.WorkTime > 1 {
return u.ArbeitszeitProTag(), 0, 0
}
return 0, 0, 0
}
func (a *Absence) Insert() error { func (a *Absence) Insert() error {
qStr, err := DB.Prepare(`INSERT INTO abwesenheit (card_uid, abwesenheit_typ, datum) VALUES ($1, $2, $3) RETURNING counter_id;`) qStr, err := DB.Prepare(`INSERT INTO abwesenheit (card_uid, abwesenheit_typ, datum_from, datum_to) VALUES ($1, $2, $3, $4) RETURNING counter_id;`)
if err != nil { if err != nil {
log.Println("Error preparing sql Statement", err) log.Println("Error preparing sql Statement", err)
return err return err
} }
err = qStr.QueryRow(a.CardUID, a.AbwesenheitTyp.Id, a.Datum).Scan(&a.CounterId) defer qStr.Close()
err = qStr.QueryRow(a.CardUID, a.AbwesenheitTyp.Id, a.DateFrom, a.DateTo).Scan(&a.CounterId)
if err != nil { if err != nil {
log.Println("Error executing insert statement", err) log.Println("Error executing insert statement", err)
return err return err
@@ -52,13 +113,117 @@ func (a *Absence) Insert() error {
return nil return nil
} }
// func (a *Absence) GetStringType() string { func (a *Absence) Save() error {
// return AbsenceTypesLabel[a.AbwesenheitTyp] qStr, err := DB.Prepare(`
// } UPDATE abwesenheit SET card_uid = $2, abwesenheit_typ = $3, datum_from = $4, datum_to = $5 WHERE counter_id = $1;
`)
if err != nil {
log.Println("Error preparing sql Statement (Absence Save)", err)
return err
}
defer qStr.Close()
_, err = qStr.Query(a.CounterId, a.CardUID, a.AbwesenheitTyp.Id, a.DateFrom, a.DateTo)
if err != nil {
log.Println("Error executing update statement", err)
return err
}
return nil
}
func GetAbsenceById(counterId int) (Absence, error) {
var absence Absence = Absence{CounterId: counterId}
qStr, err := DB.Prepare("SELECT card_uid, abwesenheit_typ, datum_from, datum_to FROM abwesenheit WHERE counter_id = $1;")
if err != nil {
return absence, err
}
defer qStr.Close()
err = qStr.QueryRow(counterId).Scan(&absence.CardUID, &absence.AbwesenheitTyp.Id, &absence.DateFrom, &absence.DateTo)
if err != nil {
return absence, err
}
return absence, nil
}
func GetAbsencesByCardUID(card_uid string, tsFrom time.Time, tsTo time.Time) ([]Absence, error) {
var absences []Absence
// qStr, err := DB.Prepare(`SELECT counter_id, abwesenheit_typ, datum_from, datum_to FROM abwesenheit WHERE card_uid = $1 AND datum_from <= $2 AND datum_to >= $3 ORDER BY datum_from;`)
qStr, err := DB.Prepare(`
SELECT
ab.counter_id,
gs::DATE AS work_date,
ab.card_uid,
ab.datum_from,
ab.datum_to,
jsonb_build_object(
'abwesenheit_id', sat.abwesenheit_id,
'abwesenheit_name', sat.abwesenheit_name,
'arbeitszeit_equivalent', sat.arbeitszeit_equivalent
) AS abwesenheit_info
FROM generate_series(
$2,
$3,
INTERVAL '1 day'
) gs
JOIN abwesenheit ab
ON ab.card_uid = $1
AND ab.datum_from::DATE <= gs::DATE
AND ab.datum_to::DATE >= gs::DATE
LEFT JOIN s_abwesenheit_typen sat
ON ab.abwesenheit_typ = sat.abwesenheit_id
ORDER BY gs::DATE, ab.counter_id;
`)
if err != nil {
return absences, err
}
defer qStr.Close()
rows, err := qStr.Query(card_uid, tsFrom, tsTo)
if err != nil {
return absences, err
}
defer rows.Close()
for rows.Next() {
var absence Absence
var abwesenheitsTyp []byte
if err := rows.Scan(&absence.CounterId, &absence.Day, &absence.CardUID, &absence.DateFrom, &absence.DateTo, &abwesenheitsTyp); err != nil {
return absences, err
}
err = json.Unmarshal(abwesenheitsTyp, &absence.AbwesenheitTyp)
if err != nil {
log.Println("Error parsing abwesenheitsTyp to JSON!", err)
return absences, nil
}
absences = append(absences, absence)
}
if err = rows.Err(); err != nil {
return absences, err
}
return absences, nil
}
func (a *Absence) Update(na Absence) bool {
change := false
if a.CardUID != na.CardUID && na.CardUID != "" {
a.CardUID = na.CardUID
change = true
}
if a.AbwesenheitTyp != na.AbwesenheitTyp && na.AbwesenheitTyp.Id != 0 {
a.AbwesenheitTyp = na.AbwesenheitTyp
change = true
}
if !a.DateFrom.Equal(na.DateFrom) && !na.DateFrom.IsZero() {
a.DateFrom = na.DateFrom
change = true
}
if !a.DateTo.Equal(na.DateTo) && !na.DateTo.IsZero() {
a.DateTo = na.DateTo
change = true
}
return change
}
func GetAbsenceTypes() (map[int8]AbsenceType, error) { func GetAbsenceTypes() (map[int8]AbsenceType, error) {
var types = make(map[int8]AbsenceType) var types = make(map[int8]AbsenceType)
qStr, err := DB.Prepare("SELECT abwesenheit_id, abwesenheit_name FROM s_abwesenheit_typen;") qStr, err := DB.Prepare("SELECT abwesenheit_id, abwesenheit_name, arbeitszeit_equivalent FROM s_abwesenheit_typen;")
if err != nil { if err != nil {
return types, err return types, err
} }
@@ -71,7 +236,7 @@ func GetAbsenceTypes() (map[int8]AbsenceType, error) {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var absenceType AbsenceType var absenceType AbsenceType
if err := rows.Scan(&absenceType.Id, &absenceType.Name); err != nil { if err := rows.Scan(&absenceType.Id, &absenceType.Name, &absenceType.WorkTime); err != nil {
log.Println("Error scanning absence row!", err) log.Println("Error scanning absence row!", err)
} }
types[absenceType.Id] = absenceType types[absenceType.Id] = absenceType
@@ -86,3 +251,18 @@ func GetAbsenceTypesCached() map[int8]AbsenceType {
} }
return types.(map[int8]AbsenceType) return types.(map[int8]AbsenceType)
} }
func GetAbsenceTypeById(absenceTypeId int8) (AbsenceType, error) {
var absenceType AbsenceType = AbsenceType{Id: absenceTypeId}
qStr, err := DB.Prepare("SELECT abwesenheit_name, arbeitszeit_equivalent FROM s_abwesenheit_typen WHERE abwesenheit_id = $1;")
if err != nil {
return absenceType, err
}
defer qStr.Close()
err = qStr.QueryRow(absenceTypeId).Scan(&absenceType.Name, &absenceType.WorkTime)
if err != nil {
return absenceType, err
}
return absenceType, nil
}

View File

@@ -2,11 +2,11 @@ package models
import ( import (
"arbeitszeitmessung/helper" "arbeitszeitmessung/helper"
"arbeitszeitmessung/helper/logs"
"database/sql" "database/sql"
"fmt" "fmt"
"log" "log"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"time" "time"
) )
@@ -14,8 +14,8 @@ import (
type SameBookingError struct{} type SameBookingError struct{}
type BookingType struct { type BookingType struct {
Id int8 Id int8 `json:"anwesenheit_id"`
Name string Name string `json:"anwesenheit_name"`
} }
func (e SameBookingError) Error() string { func (e SameBookingError) Error() string {
@@ -23,11 +23,12 @@ func (e SameBookingError) Error() string {
} }
type Booking struct { type Booking struct {
CardUID string `json:"card_uid"` CardUID string `json:"card_uid"`
GeraetID int16 `json:"geraet_id"` GeraetID int16 `json:"geraet_id"`
CheckInOut int16 `json:"check_in_out"` CheckInOut int16 `json:"check_in_out"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
CounterId int `json:"counter_id"` CounterId int `json:"counter_id"`
BookingType BookingType `json:"anwesenheit_typ"`
} }
type IDatabase interface { type IDatabase interface {
@@ -37,31 +38,52 @@ type IDatabase interface {
var DB IDatabase var DB IDatabase
func (b *Booking) New(card_uid string, geraet_id int16, check_in_out int16) Booking { func (b *Booking) New(cardUid string, gereatId int16, checkInOut int16, typeId int8) Booking {
bookingType, err := GetBookingTypeById(typeId)
if err != nil {
log.Printf("Cannot get booking type %d, from database!", typeId)
}
return Booking{ return Booking{
CardUID: card_uid, CardUID: cardUid,
GeraetID: geraet_id, GeraetID: gereatId,
CheckInOut: check_in_out, CheckInOut: checkInOut,
BookingType: bookingType,
} }
} }
func (b *Booking) FromUrlParams(params url.Values) Booking { func (b *Booking) FromUrlParams(params url.Values) Booking {
var booking Booking var booking Booking
if _check_in_out, err := strconv.Atoi(params.Get("check_in_out")); err == nil { if _check_in_out, err := strconv.Atoi(params.Get("check_in_out")); err == nil {
booking.CheckInOut = int16(_check_in_out) booking.CheckInOut = int16(_check_in_out)
} }
if _geraet_id, err := strconv.Atoi(params.Get("geraet_id")); err == nil { if _geraet_id, err := strconv.Atoi(params.Get("geraet_id")); err == nil {
booking.GeraetID = int16(_geraet_id) booking.GeraetID = int16(_geraet_id)
} }
if _booking_type, err := strconv.Atoi(params.Get("booking_type")); err == nil {
booking.BookingType.Id = int8(_booking_type)
}
booking.CardUID = params.Get("card_uid") booking.CardUID = params.Get("card_uid")
return booking return booking
} }
func (b *Booking) Verify() bool { func (b *Booking) Verify() bool {
//check for overlapping time + arbeitszeit verstoß //check for overlapping time + arbeitszeit verstoß
if b.CardUID == "" { //|| b.GeraetID == 0 || b.CheckInOut == 0 { if b.CardUID == "" { //|| b.GeraetID == 0 || b.CheckInOut == 0 {
log.Println("Booking verify failed invalid CardUID!")
return false return false
} }
if b.CheckInOut == 0 {
log.Println("Booking verify failed invalid CheckInOut!")
return false
}
if bookingType, err := GetBookingTypeById(b.BookingType.Id); err != nil {
log.Println("Booking verify failed invalid BookingType.Id!")
return false
} else {
b.BookingType.Name = bookingType.Name
}
return true return true
} }
@@ -69,11 +91,11 @@ func (b *Booking) Insert() error {
if !checkLastBooking(*b) { if !checkLastBooking(*b) {
return SameBookingError{} return SameBookingError{}
} }
stmt, err := DB.Prepare((`INSERT INTO anwesenheit (card_uid, geraet_id, check_in_out) VALUES ($1, $2, $3) RETURNING counter_id, timestamp`)) stmt, err := DB.Prepare((`INSERT INTO anwesenheit (card_uid, geraet_id, check_in_out, anwesenheit_typ) VALUES ($1, $2, $3, $4) RETURNING counter_id, timestamp`))
if err != nil { if err != nil {
return err return err
} }
err = stmt.QueryRow(b.CardUID, b.GeraetID, b.CheckInOut).Scan(&b.CounterId, &b.Timestamp) err = stmt.QueryRow(b.CardUID, b.GeraetID, b.CheckInOut, b.BookingType.Id).Scan(&b.CounterId, &b.Timestamp)
if err != nil { if err != nil {
return err return err
} }
@@ -84,11 +106,11 @@ func (b *Booking) InsertWithTimestamp() error {
if b.Timestamp.IsZero() { if b.Timestamp.IsZero() {
return b.Insert() return b.Insert()
} }
stmt, err := DB.Prepare((`INSERT INTO anwesenheit (card_uid, geraet_id, check_in_out, timestamp) VALUES ($1, $2, $3, $4) 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
} }
err = stmt.QueryRow(b.CardUID, b.GeraetID, b.CheckInOut, b.Timestamp).Scan(&b.CounterId) err = stmt.QueryRow(b.CardUID, b.GeraetID, b.CheckInOut, b.BookingType.Id, b.Timestamp).Scan(&b.CounterId)
if err != nil { if err != nil {
return err return err
} }
@@ -97,18 +119,15 @@ func (b *Booking) InsertWithTimestamp() error {
func (b *Booking) GetBookingById(booking_id int) (Booking, error) { func (b *Booking) GetBookingById(booking_id int) (Booking, error) {
var booking Booking var booking Booking
qStr, err := DB.Prepare((`SELECT counter_id, timestamp, card_uid, geraet_id, check_in_out FROM anwesenheit WHERE counter_id = $1`)) qStr, err := DB.Prepare((`SELECT counter_id, timestamp, card_uid, geraet_id, check_in_out, anwesenheit_typ FROM anwesenheit WHERE counter_id = $1`))
if err != nil { if err != nil {
return booking, err return booking, err
} }
err = qStr.QueryRow(booking_id).Scan(&booking.CounterId, &booking.Timestamp, &booking.CardUID, &booking.GeraetID, &booking.CheckInOut) // TODO: also get booking type name
err = qStr.QueryRow(booking_id).Scan(&booking.CounterId, &booking.Timestamp, &booking.CardUID, &booking.GeraetID, &booking.CheckInOut, &booking.BookingType.Id)
if err != nil { if err != nil {
return booking, err return booking, err
} }
// if !booking.Verify() {
// fmt.Printf("Booking verification failed! %d", )
// return booking, nil
// }
return booking, nil return booking, nil
} }
@@ -143,43 +162,13 @@ func (b *Booking) GetBookingsByCardID(card_uid string, tsFrom time.Time, tsTo ti
return bookings, nil return bookings, nil
} }
func (b *Booking) GetBookingsGrouped(card_uid string, tsFrom time.Time, tsTo time.Time) ([]WorkDay, error) {
var grouped = make(map[string][]Booking)
bookings, err := b.GetBookingsByCardID(card_uid, tsFrom, tsTo)
if err != nil {
log.Println("Failed to get bookings", err)
return []WorkDay{}, nil
}
for _, booking := range bookings {
day := booking.Timestamp.Truncate(24 * time.Hour)
key := day.Format("2006-01-02")
grouped[key] = append(grouped[key], booking)
}
var result []WorkDay
for key, bookings := range grouped {
day, _ := time.Parse("2006-01-02", key)
sort.Slice(bookings, func(i, j int) bool {
return bookings[i].Timestamp.Before(bookings[j].Timestamp)
})
workDay := WorkDay{Day: day, Bookings: bookings}
workDay.getWorkTime()
result = append(result, workDay)
}
sort.Slice(result, func(i, j int) bool {
return result[i].Day.After(result[j].Day)
})
return result, nil
}
func (b Booking) Save() { func (b Booking) Save() {
qStr, err := DB.Prepare((`UPDATE "anwesenheit" SET "card_uid" = $2, "geraet_id" = $3, "check_in_out" = $4, "timestamp" = $5 WHERE "counter_id" = $1;`)) qStr, err := DB.Prepare((`UPDATE "anwesenheit" SET "card_uid" = $2, "geraet_id" = $3, "check_in_out" = $4, "timestamp" = $5 WHERE "counter_id" = $1;`))
if err != nil { if err != nil {
log.Fatalf("Error preparing query: %v", err) log.Fatalf("Error preparing query: %v", err)
return return
} }
_, err = qStr.Query(b.CounterId, b.CardUID, b.GeraetID, b.CheckInOut, b.Timestamp) _, err = qStr.Query(b.CounterId, b.CardUID, b.GeraetID, b.CheckInOut, b.Timestamp)
if err != nil { if err != nil {
log.Fatalf("Error executing query: %v", err) log.Fatalf("Error executing query: %v", err)
@@ -212,6 +201,8 @@ func (b *Booking) GetBookingType() string {
} }
func (b *Booking) Update(nb Booking) { func (b *Booking) Update(nb Booking) {
auditLog, closeLog := logs.NewAudit()
defer closeLog()
if b.CheckInOut != nb.CheckInOut && nb.CheckInOut != 0 { if b.CheckInOut != nb.CheckInOut && nb.CheckInOut != 0 {
b.CheckInOut = nb.CheckInOut b.CheckInOut = nb.CheckInOut
} }
@@ -222,6 +213,7 @@ func (b *Booking) Update(nb Booking) {
b.GeraetID = nb.GeraetID b.GeraetID = nb.GeraetID
} }
if b.Timestamp != nb.Timestamp { if b.Timestamp != nb.Timestamp {
auditLog.Printf("Änderung in Buchung %d von '%s': Buchungszeit (%s -> %s).", b.CounterId, b.CardUID, b.Timestamp.Format("15:04"), nb.Timestamp.Format("15:04)"))
b.Timestamp = nb.Timestamp b.Timestamp = nb.Timestamp
} }
} }
@@ -262,17 +254,22 @@ func (b *Booking) UpdateTime(newTime time.Time) {
if b.CheckInOut == 254 { if b.CheckInOut == 254 {
newBooking.CheckInOut = 4 newBooking.CheckInOut = 4
} }
log.Println("Updating")
b.Update(newBooking) b.Update(newBooking)
b.Verify() // TODO Check verify
b.Save() if b.Verify() {
b.Save()
} else {
log.Println("Cannot save updated booking!", b.ToString())
}
// b.Verify()
// b.Save()
} }
func (b *Booking) ToString() string { func (b *Booking) ToString() string {
return fmt.Sprintf("Booking %d: at: %s, as type: %d", b.CounterId, b.Timestamp.Format("15:04"), b.CheckInOut) return fmt.Sprintf("Booking %d: at: %s, CheckInOut: %d, TypeId: %d", b.CounterId, b.Timestamp.Format("15:04"), b.CheckInOut, b.BookingType.Id)
} }
func GetBokkingTypes() ([]BookingType, error) { func GetBookingTypes() ([]BookingType, error) {
var types []BookingType var types []BookingType
qStr, err := DB.Prepare("SELECT anwesenheit_id, anwesenheit_name FROM s_anwesenheit_typen;") qStr, err := DB.Prepare("SELECT anwesenheit_id, anwesenheit_name FROM s_anwesenheit_typen;")
if err != nil { if err != nil {
@@ -295,6 +292,21 @@ func GetBokkingTypes() ([]BookingType, error) {
return types, nil return types, nil
} }
func GetBookingTypeById(bookingTypeId int8) (BookingType, error) {
var bookingType BookingType = BookingType{Id: bookingTypeId}
qStr, err := DB.Prepare("SELECT anwesenheit_name FROM s_anwesenheit_typen WHERE anwesenheit_id = $1;")
if err != nil {
return bookingType, err
}
defer qStr.Close()
err = qStr.QueryRow(bookingTypeId).Scan(&bookingType.Name)
if err != nil {
return bookingType, err
}
return bookingType, nil
}
func GetBookingTypesCached() []BookingType { func GetBookingTypesCached() []BookingType {
types, err := definedTypes.Get("s_anwesenheit_typen") types, err := definedTypes.Get("s_anwesenheit_typen")
if err != nil { if err != nil {

View File

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

View File

@@ -9,7 +9,7 @@ var definedTypes = helper.NewCache(3600, func(key string) (any, error) {
case "s_abwesenheit_typen": case "s_abwesenheit_typen":
return GetAbsenceTypes() return GetAbsenceTypes()
case "s_anwesenheit_typen": case "s_anwesenheit_typen":
return GetBokkingTypes() return GetBookingTypes()
} }
return nil, nil return nil, nil
}) })

View File

@@ -22,6 +22,9 @@ type DBFixture struct {
func SetupDBFixture(t *testing.T) *DBFixture { func SetupDBFixture(t *testing.T) *DBFixture {
t.Helper() t.Helper()
if helper.GetEnv("TEST_SQL", "false") != "true" {
t.Skip("Skipping Test because TEST_SQL is not 'true'!")
}
dbHost := helper.GetEnv("POSTGRES_HOST", "localhost") dbHost := helper.GetEnv("POSTGRES_HOST", "localhost")
dbPort := helper.GetEnv("POSTGRES_PORT", "5433") dbPort := helper.GetEnv("POSTGRES_PORT", "5433")
@@ -36,10 +39,12 @@ func SetupDBFixture(t *testing.T) *DBFixture {
t.Fatalf("failed to connect to database: %v", err) t.Fatalf("failed to connect to database: %v", err)
} }
// err = MigrateDB(db, "file://../../migrations") defer db.Close()
// if err != nil && err != migrate.ErrNoChange {
// t.Fatalf("Failed to migrate database: %v", err) err = MigrateDB(db, "file://../../migrations")
// } if err != nil && err != migrate.ErrNoChange {
t.Fatalf("Failed to migrate database: %v", err)
}
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {

View File

@@ -13,14 +13,16 @@ import (
) )
type User struct { type User struct {
CardUID string `json:"card_uid"` CardUID string //`json:"card_uid"`
Name string `json:"name"` Name string `json:"name"`
Vorname string `json:"vorname"` Vorname string `json:"vorname"`
PersonalNummer int `json:"personal_nummer"` PersonalNummer int //`json:"personal_nummer"`
ArbeitszeitPerTag float32 `json:"arbeitszeit"` ArbeitszeitPerTag float32 //`json:"arbeitszeit_per_tag"`
ArbeitszeitPerWoche float32 //`json:"arbeitszeit_per_woche"`
Overtime time.Duration
} }
func (u *User) GetUserFromSession(Session *scs.SessionManager, ctx context.Context) (User, error) { func GetUserFromSession(Session *scs.SessionManager, ctx context.Context) (User, error) {
var user User var user User
var err error var err error
if helper.GetEnv("GO_ENV", "production") == "debug" { if helper.GetEnv("GO_ENV", "production") == "debug" {
@@ -39,6 +41,22 @@ func (u *User) GetUserFromSession(Session *scs.SessionManager, ctx context.Conte
return user, nil return user, nil
} }
// Returns the actual overtime for this moment
func (u *User) GetReportedOvertime() (time.Duration, error) {
var overtime time.Duration
qStr, err := DB.Prepare("SELECT COALESCE(SUM(EXTRACT(EPOCH FROM ueberstunden) * 1000000000)::BIGINT, 0) AS total_ueberstunden_ns FROM wochen_report WHERE personal_nummer = $1;")
if err != nil {
return 0, err
}
defer qStr.Close()
err = qStr.QueryRow(u.PersonalNummer).Scan(&overtime)
if err != nil {
return 0, err
}
return overtime, 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
@@ -67,6 +85,15 @@ 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 {
return time.Duration(u.ArbeitszeitPerTag * float32(time.Hour)).Round(time.Minute)
}
func (u *User) ArbeitszeitProWoche() time.Duration {
return time.Duration(u.ArbeitszeitPerWoche * float32(time.Hour)).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
// Returns false if there is no booking today or the user is already booked out of the system // Returns false if there is no booking today or the user is already booked out of the system
func (u *User) CheckAnwesenheit() bool { func (u *User) CheckAnwesenheit() bool {
@@ -86,10 +113,10 @@ func (u *User) CheckAnwesenheit() bool {
// Creates a new booking for the user -> check_in_out will be 254 for automatic check out // Creates a new booking for the user -> check_in_out will be 254 for automatic check out
func (u *User) CheckOut() error { func (u *User) CheckOut() error {
booking := (*Booking).New(nil, u.CardUID, 0, 254) booking := (*Booking).New(nil, u.CardUID, 0, 254, 1)
err := booking.Insert() err := booking.Insert()
if err != nil { if err != nil {
fmt.Printf("Error inserting booking %v\n", err) fmt.Printf("Error inserting booking %v -> %v\n", booking, err)
return err return err
} }
return nil return nil
@@ -98,11 +125,11 @@ func (u *User) CheckOut() error {
func GetUserByPersonalNr(personalNummer int) (User, error) { func GetUserByPersonalNr(personalNummer int) (User, error) {
var user User var user User
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM s_personal_daten WHERE personal_nummer = $1;`)) qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten WHERE personal_nummer = $1;`))
if err != nil { if err != nil {
return user, err return user, err
} }
err = qStr.QueryRow(personalNummer).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag) err = qStr.QueryRow(personalNummer).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche)
if err != nil { if err != nil {
return user, err return user, err
@@ -146,7 +173,7 @@ func (u *User) ChangePass(password, newPassword string) (bool, error) {
func (u *User) GetTeamMembers() ([]User, error) { func (u *User) GetTeamMembers() ([]User, error) {
var teamMembers []User var teamMembers []User
qStr, err := DB.Prepare(`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM s_personal_daten WHERE vorgesetzter_pers_nr = $1`) qStr, err := DB.Prepare(`SELECT personal_nummer FROM s_personal_daten WHERE vorgesetzter_pers_nr = $1 ORDER BY "nachname";`)
if err != nil { if err != nil {
return teamMembers, err return teamMembers, err
} }
@@ -158,9 +185,11 @@ func (u *User) GetTeamMembers() ([]User, error) {
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
user, err := parseUser(rows) var personalNr int
err := rows.Scan(&personalNr)
user, err := GetUserByPersonalNr(personalNr)
if err != nil { if err != nil {
log.Println("Error parsing user!") log.Println("Error getting user!")
return teamMembers, err return teamMembers, err
} }
teamMembers = append(teamMembers, user) teamMembers = append(teamMembers, user)
@@ -178,17 +207,6 @@ func (u *User) IsTeamLeader() bool {
return len(team) > 0 return len(team) > 0
} }
func (u *User) GetWeek(tsFrom time.Time) WorkWeek {
var bookings []WorkDay
weekStart := tsFrom.AddDate(0, 0, -1*int(tsFrom.Local().Weekday())-1)
bookings, err := (*Booking).GetBookingsGrouped(nil, u.CardUID, weekStart, time.Now())
if err != nil {
log.Println("Error fetching bookings!")
return WorkWeek{WorkDays: bookings}
}
return WorkWeek{WorkDays: bookings}
}
// gets the first week, that needs to be submitted // gets the first week, that needs to be submitted
func (u *User) GetNextWeek() WorkWeek { func (u *User) GetNextWeek() WorkWeek {
var week WorkWeek var week WorkWeek
@@ -205,8 +223,8 @@ func parseUser(rows *sql.Rows) (User, error) {
return user, nil return user, nil
} }
// returns the start of the week, the last submission was made, submission == first booking or last send booking_report to team leader // returns the start of the week, the last submission was made, submission == first booking or last send wochen_report to team leader
func (u *User) GetLastSubmission() time.Time { func (u *User) GetLastWorkWeekSubmission() time.Time {
var lastSub time.Time var lastSub time.Time
qStr, err := DB.Prepare(` qStr, err := DB.Prepare(`
SELECT COALESCE( SELECT COALESCE(
@@ -223,10 +241,8 @@ func (u *User) GetLastSubmission() time.Time {
log.Println("Error executing query!", err) log.Println("Error executing query!", err)
return lastSub return lastSub
} }
log.Println("From DB: ", lastSub)
lastSub = getMonday(lastSub) lastSub = getMonday(lastSub)
lastSub = lastSub.Round(24 * time.Hour) lastSub = lastSub.Round(24 * time.Hour)
log.Println("After truncate: ", lastSub)
return lastSub return lastSub
} }

View File

@@ -6,16 +6,20 @@ import (
"testing" "testing"
) )
var testUser models.User = models.User{Vorname: "Kim", Name: "Mustermensch", PersonalNummer: 456, CardUID: "aaaa-aaaa", ArbeitszeitPerTag: 8} var testUser models.User = models.User{Vorname: "Kim", Name: "Mustermensch", PersonalNummer: 456, CardUID: "aaaa-aaaa", ArbeitszeitPerTag: 8, ArbeitszeitPerWoche: 40}
func SetupUserFixture(t *testing.T, db models.IDatabase) { func SetupUserFixture(t *testing.T, db models.IDatabase) {
t.Helper() t.Helper()
db.Exec(`INSERT INTO "s_personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES _, err := db.Exec(`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
(456, 't', 'Kim', 'Mustermensch', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'aaaa-aaaa', 1, 8, '07:00:00', '20:00:00', 0);`) (456, 't', 'Kim', 'Mustermensch', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'aaaa-aaaa', 1, 8, 40, '07:00:00', '20:00:00', 0);`)
if err != nil {
t.Fatal("SetupUserFixture:", err)
}
} }
func TestGetUserByPersonalNr(t *testing.T) { func TestGetUserByPersonalNr(t *testing.T) {
tc := SetupDBFixture(t) tc := SetupDBFixture(t)
SetupUserFixture(t, tc.Database) SetupUserFixture(t, tc.Database)
models.DB = tc.Database models.DB = tc.Database
@@ -44,12 +48,12 @@ func TestCheckAnwesenheit(t *testing.T) {
if actual = testUser.CheckAnwesenheit(); actual != false { if actual = testUser.CheckAnwesenheit(); actual != false {
t.Errorf("Checkabwesenheit with no booking should be false but is %t", actual) t.Errorf("Checkabwesenheit with no booking should be false but is %t", actual)
} }
tc.Database.Exec("INSERT INTO anwesenheit (timestamp, card_uid, check_in_out, geraet_id) VALUES (NOW() - INTERVAL '2 minute', 'aaaa-aaaa', 1, 1);") tc.Database.Exec("INSERT INTO anwesenheit (timestamp, card_uid, check_in_out, geraet_id, anwesenheit_typ) VALUES (NOW() - INTERVAL '2 minute', 'aaaa-aaaa', 1, 1, 1);")
if actual = testUser.CheckAnwesenheit(); actual != true { if actual = testUser.CheckAnwesenheit(); actual != true {
t.Errorf("Checkabwesenheit with 'kommen' booking should be true but is %t", actual) t.Errorf("Checkabwesenheit with 'kommen' booking should be true but is %t", actual)
} }
tc.Database.Exec("INSERT INTO anwesenheit (timestamp, card_uid, check_in_out, geraet_id) VALUES (NOW() - INTERVAL '1 minute', 'aaaa-aaaa', 2, 1);") tc.Database.Exec("INSERT INTO anwesenheit (timestamp, card_uid, check_in_out, geraet_id, anwesenheit_typ) VALUES (NOW() - INTERVAL '1 minute', 'aaaa-aaaa', 2, 1, 1);")
if actual = testUser.CheckAnwesenheit(); actual != false { if actual = testUser.CheckAnwesenheit(); actual != false {
t.Errorf("Checkabwesenheit with 'gehen' booking should be false but is %t", actual) t.Errorf("Checkabwesenheit with 'gehen' booking should be false but is %t", actual)
} }

View File

@@ -2,86 +2,225 @@ package models
import ( import (
"arbeitszeitmessung/helper" "arbeitszeitmessung/helper"
"database/sql"
"encoding/json" "encoding/json"
"fmt"
"log" "log"
"strconv" "sort"
"time" "time"
) )
type WorkDay struct { type IWorkDay interface {
Day time.Time `json:"day"` Date() time.Time
Bookings []Booking `json:"bookings"` TimeWorkVirtual(User) time.Duration
workTime time.Duration TimeWorkReal(User) time.Duration
pauseTime time.Duration TimePauseReal(User) (work, pause time.Duration)
TimeFrom time.Time TimeOvertimeReal(User) time.Duration
TimeTo time.Time GetAllWorkTimesVirtual(User) (work, pause, overtime time.Duration)
Absence Absence ToString() string
IsWorkDay() bool
IsKurzArbeit() bool
GetDayProgress(User) int8
RequiresAction() bool
} }
func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay { type WorkDay struct {
Day time.Time `json:"day"`
Bookings []Booking `json:"bookings"`
workTime time.Duration
pauseTime time.Duration
realWorkTime time.Duration
realPauseTime time.Duration
TimeFrom time.Time
TimeTo time.Time
kurzArbeit bool
kurzArbeitAbsence Absence
}
func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay {
var allDays map[string]IWorkDay = make(map[string]IWorkDay)
var sortedDays []IWorkDay
for _, day := range GetWorkDays(user, tsFrom, tsTo) {
allDays[day.Date().Format("2006-01-02")] = &day
}
absences, err := GetAbsencesByCardUID(user.CardUID, tsFrom, tsTo)
if err != nil {
log.Println("Error gettings absences for all Days!", err)
return sortedDays
}
for _, day := range absences {
if helper.IsWeekend(day.Date()) {
continue
}
if day.AbwesenheitTyp.WorkTime == 1 {
if workDay, ok := allDays[day.Date().Format("2006-01-02")].(*WorkDay); ok {
if len(workDay.Bookings) > 0 {
workDay.kurzArbeit = true
workDay.kurzArbeitAbsence = day
}
}
} else {
allDays[day.Date().Format("2006-01-02")] = &day
}
}
for _, day := range allDays {
sortedDays = append(sortedDays, day)
}
if orderedForward {
sort.Slice(sortedDays, func(i, j int) bool {
return sortedDays[i].Date().After(sortedDays[j].Date())
})
} else {
sort.Slice(sortedDays, func(i, j int) bool {
return sortedDays[i].Date().Before(sortedDays[j].Date())
})
}
return sortedDays
}
func (d *WorkDay) Date() time.Time {
return d.Day
}
func (d *WorkDay) TimeWorkVirtual(u User) time.Duration {
if d.IsKurzArbeit() {
return u.ArbeitszeitProTag()
}
return d.workTime
}
func (d *WorkDay) GetKurzArbeit() *Absence {
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())
}
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 {
return fmt.Sprintf("WorkDay: %s with %d bookings and worktime: %s", d.Date().Format("2006-01-02"), len(d.Bookings), helper.FormatDuration(d.workTime))
}
func (d *WorkDay) IsWorkDay() bool {
return true
}
func (d *WorkDay) SetKurzArbeit(kurzArbeit bool) {
d.kurzArbeit = kurzArbeit
}
func (d *WorkDay) IsKurzArbeit() bool {
return d.kurzArbeit
}
func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay {
var workDays []WorkDay var workDays []WorkDay
var workSec, pauseSec float64 var workSec, pauseSec float64
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 ( ordered_bookings AS (
SELECT SELECT
timestamp::DATE AS work_date, a.timestamp::DATE AS work_date,
timestamp, a.timestamp,
check_in_out, a.check_in_out,
counter_id, a.counter_id,
LAG(timestamp) OVER (PARTITION BY card_uid, timestamp::DATE ORDER BY timestamp) AS prev_timestamp, a.anwesenheit_typ,
LAG(check_in_out) OVER (PARTITION BY card_uid, timestamp::DATE ORDER BY timestamp) AS prev_check sat.anwesenheit_name AS anwesenheit_typ_name,
FROM anwesenheit LAG(a.timestamp) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_timestamp,
WHERE card_uid = $1 LAG(a.check_in_out) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_check
AND timestamp::DATE >= $2 FROM anwesenheit a
AND timestamp::DATE <= $3 LEFT JOIN s_anwesenheit_typen sat ON a.anwesenheit_typ = sat.anwesenheit_id
), WHERE a.card_uid = $1
abwesenheiten AS ( AND a.timestamp::DATE >= $2
SELECT AND a.timestamp::DATE <= $3
datum::DATE AS work_date, )
abwesenheit_typ SELECT
FROM abwesenheit d.work_date,
WHERE card_uid = $1 COALESCE(MIN(b.timestamp), NOW()) AS time_from,
AND datum::DATE >= $2 COALESCE(MAX(b.timestamp), NOW()) AS time_to,
AND datum::DATE <= $3 COALESCE(
) EXTRACT(EPOCH FROM SUM(
SELECT CASE
d.work_date, WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 254)
COALESCE(MIN(b.timestamp), NOW()) AS time_from, THEN b.timestamp - b.prev_timestamp
COALESCE(MAX(b.timestamp), NOW()) AS time_to, ELSE INTERVAL '0'
COALESCE( END
EXTRACT(EPOCH FROM SUM( )), 0
CASE ) AS total_work_seconds,
WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 255) COALESCE(
THEN b.timestamp - b.prev_timestamp EXTRACT(EPOCH FROM SUM(
ELSE INTERVAL '0' CASE
END WHEN b.prev_check IN (2, 4, 254) AND b.check_in_out IN (1, 3)
)), 0 THEN b.timestamp - b.prev_timestamp
) AS total_work_seconds, ELSE INTERVAL '0'
COALESCE( END
EXTRACT(EPOCH FROM SUM( )), 0
CASE ) AS total_pause_seconds,
WHEN b.prev_check IN (2, 4, 255) AND b.check_in_out IN (1, 3) COALESCE(jsonb_agg(jsonb_build_object(
THEN b.timestamp - b.prev_timestamp 'check_in_out', b.check_in_out,
ELSE INTERVAL '0' 'timestamp', b.timestamp,
END 'counter_id', b.counter_id,
)), 0 'anwesenheit_typ', b.anwesenheit_typ,
) AS total_pause_seconds, 'anwesenheit_typ', jsonb_build_object(
COALESCE(jsonb_agg(jsonb_build_object( 'anwesenheit_id', b.anwesenheit_typ,
'check_in_out', b.check_in_out, 'anwesenheit_name', b.anwesenheit_typ_name
'timestamp', b.timestamp, )
'counter_id', b.counter_id ) ORDER BY b.timestamp), '[]'::jsonb) AS bookings
) ORDER BY b.timestamp), '[]'::jsonb) AS bookings, FROM all_days d
a.abwesenheit_typ LEFT JOIN ordered_bookings b ON d.work_date = b.work_date
FROM all_days d GROUP BY d.work_date
LEFT JOIN ordered_bookings b ON d.work_date = b.work_date ORDER BY d.work_date ASC;`)
LEFT JOIN abwesenheiten a ON d.work_date = a.work_date
GROUP BY d.work_date, a.abwesenheit_typ
ORDER BY d.work_date;`)
if err != nil { if err != nil {
log.Println("Error preparing SQL statement", err) log.Println("Error preparing SQL statement", err)
@@ -89,18 +228,17 @@ func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay
} }
defer qStr.Close() defer qStr.Close()
rows, err := qStr.Query(card_uid, tsFrom, tsTo) rows, err := qStr.Query(user.CardUID, tsFrom, tsTo)
if err != nil { if err != nil {
log.Println("Error getting rows!") log.Println("Error getting rows!")
return workDays return workDays
} }
defer rows.Close() defer rows.Close()
emptyDays, _ := strconv.ParseBool(helper.GetEnv("EMPTY_DAYS", "false")) // 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
var absenceType sql.NullInt16 if err := rows.Scan(&workDay.Day, &workDay.TimeFrom, &workDay.TimeTo, &workSec, &pauseSec, &bookings); err != nil {
if err := rows.Scan(&workDay.Day, &workDay.TimeFrom, &workDay.TimeTo, &workSec, &pauseSec, &bookings, &absenceType); err != nil {
log.Println("Error scanning row!", err) log.Println("Error scanning row!", err)
return workDays return workDays
} }
@@ -115,92 +253,55 @@ func (d *WorkDay) GetWorkDays(card_uid string, 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 absenceType.Valid { if len(workDay.Bookings) > 1 || !helper.IsWeekend(workDay.Date()) {
workDay.Absence, err = NewAbsence(card_uid, int(absenceType.Int16), workDay.Day)
// log.Println("Found absence", workDay.Absence)
}
if workDay.Day.Equal(time.Now().Truncate(24 * time.Hour)) {
workDay.getWorkTime()
} else {
workDay.calcPauseTime()
}
if emptyDays || len(workDay.Bookings) > 0 || (workDay.Absence != Absence{}) {
workDays = append(workDays, workDay) workDays = append(workDays, workDay)
} else {
log.Println("no booking on day", workDay.Day.Format("02.01.2006"))
} }
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
log.Println("Error in workday rows!", err)
return workDays return workDays
} }
return workDays return workDays
} }
func (d *WorkDay) calcPauseTime() { func (d *WorkDay) GetAllWorkTimesReal(user User) (work, pause, overtime time.Duration) {
if d.workTime > 6*time.Hour && d.pauseTime < 45*time.Minute { if d.pauseTime == 0 || d.workTime == 0 {
if d.workTime <= (9*time.Hour) && d.pauseTime < 30*time.Minute { d.TimePauseReal(user)
diff := 30*time.Minute - d.pauseTime
d.workTime -= diff
d.pauseTime += diff
} else if d.pauseTime < 45*time.Minute {
diff := 45*time.Minute - d.pauseTime
d.workTime -= diff
d.pauseTime += diff
}
} }
return d.workTime.Round(time.Minute), d.pauseTime.Round(time.Minute), d.TimeOvertimeReal(user)
} }
// Gets the duration someone worked that day func (d *WorkDay) GetAllWorkTimesVirtual(user User) (work, pause, overtime time.Duration) {
func (d *WorkDay) getWorkTime() { _, pause, overtime = d.GetAllWorkTimesReal(user)
if len(d.Bookings) < 1 { return d.TimeWorkVirtual(user), pause, overtime
return
}
var workTime, pauseTime time.Duration
var lastBooking Booking
for _, booking := range d.Bookings {
if booking.CheckInOut%2 == 1 {
if !lastBooking.Timestamp.IsZero() {
pauseTime += booking.Timestamp.Sub(lastBooking.Timestamp)
}
} else {
workTime += booking.Timestamp.Sub(lastBooking.Timestamp)
}
lastBooking = booking
}
// checks if booking is today and has no gehen yet, so the time since last kommen booking is added to workTime
if d.Day.Day() == time.Now().Day() && len(d.Bookings)%2 == 1 {
workTime += time.Since(lastBooking.Timestamp.Local())
}
d.workTime = workTime
d.pauseTime = pauseTime
d.calcPauseTime()
}
func (d *WorkDay) GetWorkTimeString() (string, string) {
workString := helper.FormatDuration(d.workTime)
pauseString := helper.FormatDuration(d.pauseTime)
return workString, pauseString
} }
// 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 {
return d.Bookings[len(d.Bookings)-1].CheckInOut == 254 return false
} }
return false return d.Bookings[len(d.Bookings)-1].CheckInOut == 254
} }
// returns a integer percentage of how much day has been worked of func (d *WorkDay) GetDayProgress(u User) int8 {
func (d *WorkDay) GetWorkDayProgress(user User) uint8 { if d.RequiresAction() {
defaultWorkTime := time.Duration(user.ArbeitszeitPerTag * float32(time.Hour)).Round(time.Minute) return -1
progress := (d.workTime.Seconds() / defaultWorkTime.Seconds()) * 100 }
return uint8(progress) workTime := d.TimeWorkVirtual(u)
progress := (workTime.Seconds() / u.ArbeitszeitProTag().Seconds()) * 100
return int8(progress)
} }
func (d *WorkDay) CalcOvertime(user User) time.Duration { // func (d *WorkDay) CalcOvertime(user User) time.Duration {
overtime := d.workTime - time.Duration(user.ArbeitszeitPerTag*float32(time.Hour)).Round(time.Minute) // if d.workTime == 0 {
return overtime // 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

@@ -0,0 +1,81 @@
package models_test
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"log"
"testing"
"time"
)
func CatchError[T any](val T, err error) T {
if err != nil {
log.Fatalln(err)
}
return val
}
var testWorkDay = models.WorkDay{
Day: CatchError(time.Parse("2006-01-02", "2025-01-01")),
Bookings: testBookings8hrs,
TimeFrom: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 08:00")),
TimeTo: CatchError(time.Parse("2006-01-02 15:04", "2025-01-01 16:30")),
}
func TestCalcRealWorkTime(t *testing.T) {
workTime := testWorkDay.TimeWorkReal(testUser)
if workTime != time.Hour*8 {
t.Errorf("Calc Worktime Default not working, time should be 8h, but was %s", helper.FormatDuration(workTime))
}
}
func TestCalcWorkPauseDiff(t *testing.T) {
type testCase struct {
Name string
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",
bookings: testBookings10hrs,
expectedWorkTime: 9*time.Hour + 15*time.Minute,
expectedPauseTime: 45 * time.Minute,
expectedOvertime: 1*time.Hour + 15*time.Minute,
}}
for _, test := range testCases {
t.Run(test.Name, func(t *testing.T) {
testWorkDay.Bookings = test.bookings
testWorkDay.TimeWorkReal(testUser)
testWorkDay.TimePauseReal(testUser)
testWorkDay.TimeOvertimeReal(testUser)
workTime, pauseTime, overTime := testWorkDay.GetAllWorkTimesReal(testUser)
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))
}
if overTime != test.expectedOvertime {
t.Errorf("Calculated wrong overtime: should be %s, but was %s", helper.FormatDuration(test.expectedOvertime), helper.FormatDuration(overTime))
}
})
}
}

View File

@@ -1,19 +1,26 @@
package models package models
import ( import (
"arbeitszeitmessung/helper"
"database/sql" "database/sql"
"errors" "errors"
"log" "log"
"time" "time"
) )
// Workweeks are
type WorkWeek struct { type WorkWeek struct {
Id int Id int
WorkDays []WorkDay WorkDays []WorkDay
User User Absences []Absence
WeekStart time.Time Days []IWorkDay
WorkHours time.Duration User User
WeekStart time.Time
Worktime time.Duration
Overtime time.Duration
Status WeekStatus
overtimeDiff time.Duration
worktimeDiff time.Duration
} }
type WeekStatus int8 type WeekStatus int8
@@ -22,59 +29,94 @@ const (
WeekStatusNone WeekStatus = iota WeekStatusNone WeekStatus = iota
WeekStatusSent WeekStatusSent
WeekStatusAccepted WeekStatusAccepted
WeekStatusDifferences
) )
func (w *WorkWeek) GetWeek(user User, tsMonday time.Time, populateDays bool) WorkWeek { func NewWorkWeek(user User, tsMonday time.Time, populate bool) WorkWeek {
var week WorkWeek var week WorkWeek = WorkWeek{
if populateDays { User: user,
week.WorkDays = (*WorkDay).GetWorkDays(nil, user.CardUID, tsMonday, tsMonday.Add(7*24*time.Hour)) WeekStart: tsMonday,
week.WorkHours = aggregateWorkTime(week.WorkDays) Status: WeekStatusNone,
}
if populate {
week.PopulateWithDays(0, 0)
} }
week.User = user
week.WeekStart = tsMonday
return week return week
} }
func (w *WorkWeek) PopulateWithDays(worktime time.Duration, overtime time.Duration) {
log.Println("Got Days with overtime and worktime", worktime, overtime)
w.Days = GetDays(w.User, w.WeekStart, w.WeekStart.Add(6*24*time.Hour), false)
log.Println(w.Worktime)
for _, day := range w.Days {
log.Println(day.TimeWorkVirtual(w.User))
w.Worktime += day.TimeWorkVirtual(w.User)
}
log.Println("Calculated new worktime", w.Worktime)
w.Overtime = w.Worktime - w.User.ArbeitszeitProWoche()
w.Worktime = w.Worktime.Round(time.Minute)
w.Overtime = w.Overtime.Round(time.Minute)
if overtime == 0 && worktime == 0 {
return
}
if overtime != w.Overtime || worktime != w.Worktime {
w.Status = WeekStatusDifferences
w.overtimeDiff = overtime
w.worktimeDiff = worktime
}
}
func (w *WorkWeek) CheckStatus() WeekStatus { func (w *WorkWeek) CheckStatus() WeekStatus {
weekStatus := WeekStatusNone if w.Status != WeekStatusNone {
return w.Status
}
if DB == nil {
log.Println("Cannot access Database!")
return w.Status
}
qStr, err := DB.Prepare(`SELECT bestaetigt FROM wochen_report WHERE woche_start = $1::DATE AND personal_nummer = $2;`) qStr, err := DB.Prepare(`SELECT bestaetigt FROM wochen_report WHERE woche_start = $1::DATE AND personal_nummer = $2;`)
if err != nil { if err != nil {
log.Println("Error preparing SQL statement", err) log.Println("Error preparing SQL statement", err)
return weekStatus return w.Status
} }
defer qStr.Close() defer qStr.Close()
var beastatigt bool var beastatigt bool
err = qStr.QueryRow(w.WeekStart, w.User.PersonalNummer).Scan(&beastatigt) err = qStr.QueryRow(w.WeekStart, w.User.PersonalNummer).Scan(&beastatigt)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return weekStatus return w.Status
} }
if err != nil { if err != nil {
log.Println("Error querying database", err) log.Println("Error querying database", err)
return weekStatus return w.Status
} }
if beastatigt { if beastatigt {
weekStatus = WeekStatusAccepted w.Status = WeekStatusAccepted
} else { } else {
weekStatus = WeekStatusSent w.Status = WeekStatusSent
} }
return weekStatus return w.Status
} }
func (w *WorkWeek) GetWorkHourString() string { func (w *WorkWeek) aggregateWorkTime() time.Duration {
return helper.FormatDuration(w.WorkHours)
}
func aggregateWorkTime(days []WorkDay) time.Duration {
var workTime time.Duration var workTime time.Duration
for _, day := range days { for _, day := range w.WorkDays {
workTime += day.workTime workTime += day.workTime
} }
// for _, absence := range w.Absences {
// log.Println(absence)
// absenceWorkTime := float32(8) // := absences.AbwesenheitTyp.WorkTime - (absences.AbwesenheitTyp.WorkTime - w.User.ArbeitszeitPerTag) // workTime Equivalent of Absence is capped at user Worktime per Day
// workTime += time.Duration(absenceWorkTime * float32(time.Hour)).Round(time.Minute)
// }
return workTime return workTime
} }
func (w *WorkWeek) GetSendWeeks(user User) []WorkWeek { func (w *WorkWeek) GetSendWeeks(user User) []WorkWeek {
var weeks []WorkWeek var weeks []WorkWeek
qStr, err := DB.Prepare(`SELECT id, woche_start::DATE FROM wochen_report WHERE bestaetigt = FALSE AND personal_nummer = $1;`) qStr, err := DB.Prepare(`SELECT id, woche_start::DATE, (EXTRACT(epoch FROM arbeitszeit)*1000000000)::BIGINT, (EXTRACT(epoch FROM ueberstunden)*1000000000)::BIGINT FROM wochen_report WHERE bestaetigt = FALSE AND personal_nummer = $1;`)
if err != nil { if err != nil {
log.Println("Error preparing SQL statement", err) log.Println("Error preparing SQL statement", err)
return weeks return weeks
@@ -88,14 +130,14 @@ func (w *WorkWeek) GetSendWeeks(user User) []WorkWeek {
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var week WorkWeek var week WorkWeek = WorkWeek{User: user}
week.User = user var workTime, overTime time.Duration
if err := rows.Scan(&week.Id, &week.WeekStart); err != nil { if err := rows.Scan(&week.Id, &week.WeekStart, &workTime, &overTime); err != nil {
log.Println("Error scanning row!", err) log.Println("Error scanning row!", err)
return weeks return weeks
} }
week.WorkDays = (*WorkDay).GetWorkDays(nil, user.CardUID, week.WeekStart, week.WeekStart.Add(7*24*time.Hour))
week.WorkHours = aggregateWorkTime(week.WorkDays) week.PopulateWithDays(workTime, overTime)
weeks = append(weeks, week) weeks = append(weeks, week)
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
@@ -108,33 +150,34 @@ func (w *WorkWeek) GetSendWeeks(user User) []WorkWeek {
var ErrRunningWeek = errors.New("Week is in running week") var ErrRunningWeek = errors.New("Week is in running week")
// creates a new entry in the woche_report table with the given workweek // creates a new entry in the woche_report table with the given workweek
func (w *WorkWeek) Send() error { func (w *WorkWeek) SendWeek() error {
var qStr *sql.Stmt var qStr *sql.Stmt
var err error var err error
if time.Since(w.WeekStart) < 5*24*time.Hour { if time.Since(w.WeekStart) < 5*24*time.Hour {
log.Println("Cannot send week, because it's the running week!") log.Println("Cannot send week, because it's the running week!")
return ErrRunningWeek return ErrRunningWeek
} }
if w.CheckStatus() != WeekStatusNone { if w.CheckStatus() != WeekStatusNone {
qStr, err = DB.Prepare(`UPDATE "wochen_report" SET bestaetigt = FALSE WHERE personal_nummer = $1 AND woche_start = $2;`) qStr, err = DB.Prepare(`UPDATE "wochen_report" SET bestaetigt = FALSE, arbeitszeit = make_interval(secs => $3::numeric / 1000000000), ueberstunden = make_interval(secs => $4::numeric / 1000000000) WHERE personal_nummer = $1 AND woche_start = $2;`)
if err != nil { if err != nil {
log.Println("Error preparing SQL statement", err) log.Println("Error preparing SQL statement", err)
return err return err
} }
} else { } else {
qStr, err = DB.Prepare(`INSERT INTO wochen_report (personal_nummer, woche_start) VALUES ($1, $2);`) qStr, err = DB.Prepare(`INSERT INTO wochen_report (personal_nummer, woche_start, arbeitszeit, ueberstunden) VALUES ($1, $2, make_interval(secs => $3::numeric / 1000000000), make_interval(secs => $4::numeric / 1000000000));`)
if err != nil { if err != nil {
log.Println("Error preparing SQL statement", err) log.Println("Error preparing SQL statement", err)
return err return err
} }
} }
_, err = qStr.Exec(w.User.PersonalNummer, w.WeekStart) _, err = qStr.Exec(w.User.PersonalNummer, w.WeekStart, int64(w.Worktime), int64(w.Overtime))
if err != nil { if err != nil {
log.Println("Error executing query!", err) log.Println("Error executing query!", err)
return err return err
} }
return nil return nil
} }
func (w *WorkWeek) Accept() error { func (w *WorkWeek) Accept() error {
@@ -150,3 +193,11 @@ func (w *WorkWeek) Accept() error {
} }
return nil return nil
} }
func (w *WorkWeek) RequiresAction() bool {
var requiresAction bool = false
for _, day := range w.Days {
requiresAction = requiresAction || day.RequiresAction()
}
return requiresAction
}

View File

@@ -0,0 +1,51 @@
package models_test
import (
"arbeitszeitmessung/models"
"testing"
"time"
)
func SetupWorkWeekFixture(t *testing.T) models.WorkWeek {
t.Helper()
monday, err := time.Parse("2006-01-02", "2025-01-10")
if err != nil {
t.Fatal(err)
}
return models.WorkWeek{User: testUser, WeekStart: monday, Status: models.WeekStatusSent}
}
func TestNewWorkWeekNoPopulate(t *testing.T) {
monday, err := time.Parse("2006-01-02", "2025-01-10")
if err != nil {
t.Fatal(err)
}
workWeek := models.NewWorkWeek(testUser, monday, false)
if workWeek.User != testUser || workWeek.WeekStart != monday {
t.Error("No populate workweek does not have right values!")
}
}
func TestCheckStatus(t *testing.T) {
SetupDBFixture(t)
testWeek := SetupWorkWeekFixture(t)
testCases := []struct {
name string
weekStatus models.WeekStatus
}{
{"State=None", models.WeekStatusNone},
{"State=Sent", models.WeekStatusSent},
{"State=Accepted", models.WeekStatusAccepted},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testWeek.Status = tc.weekStatus
if testWeek.CheckStatus() != tc.weekStatus {
t.Error("WorkWeek Status missmatch!")
}
})
}
}

View File

@@ -1,6 +1,6 @@
sonar.projectKey=Arbeitszeitmessung sonar.projectKey=arbeitszeitmessung
sonar.sources=. sonar.sources=.
sonar.exclusions=**/*_test.go sonar.exclusions=**/*_test.go, **/*_templ.go
sonar.tests=. sonar.tests=.
sonar.test.inclusions=**/*_test.go sonar.test.inclusions=**/*_test.go

View File

@@ -1,5 +1,8 @@
@import "tailwindcss"; @import "tailwindcss";
@source "../templates/*.templ"; @source "../templates/*.templ";
@plugin "@iconify/tailwind4" {
scale: 1.25;
}
@theme { @theme {
--color-accent-50: #e7fdea; --color-accent-50: #e7fdea;
@@ -27,10 +30,18 @@
--color-text-950: #000000; --color-text-950: #000000;
} }
@layer base {
body {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
background-color: white;
}
}
@layer components { @layer components {
.grid-main { .grid-main {
display: grid; display: grid;
grid-template-columns: 2fr auto 1fr; grid-template-columns: 4fr 3fr 3fr 1fr;
align-items: stretch; align-items: stretch;
} }
@@ -42,6 +53,11 @@
transition: background-color 0.2s ease-in-out; transition: background-color 0.2s ease-in-out;
} }
.grid-sub.responsive {
display: flex;
flex-direction: column;
}
.grid-sub:hover { .grid-sub:hover {
background-color: var(--color-neutral-200); background-color: var(--color-neutral-200);
} }
@@ -51,13 +67,97 @@
border-color: var(--color-neutral-400); border-color: var(--color-neutral-400);
} }
.btn {
width: 100%;
cursor: pointer;
border-radius: var(--radius-md);
color: var(--color-neutral-800);
font-size: var(--text-sm);
text-align: center;
padding: calc(var(--spacing) * 2);
border-style: var(--tw-border-style);
border-width: 1px;
border-color: var(--color-neutral-800);
transition-property:
color, background-color, border-color, outline-color,
text-decoration-color, fill, stroke, --tw-gradient-from,
--tw-gradient-via, --tw-gradient-to;
transition-timing-function: var(
--tw-ease,
var(--default-transition-timing-function)
);
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
input.btn,
select.btn {
transition-duration: 300ms;
}
.btn:hover {
color: var(--color-white);
background-color: var(--color-neutral-700);
}
.btn:disabled {
opacity: 50%;
pointer-events: none;
}
input.btn,
select.btn {
text-align: left;
}
input.btn:hover,
select.btn:hover {
border-color: var(--color-neutral-300);
background-color: var(--color-neutral-100);
color: var(--color-neutral-800);
}
.edit-box {
border-radius: var(--radius-md);
overflow: hidden;
border-color: var(--color-neutral-500);
transition-property: background-color, border-color;
transition-timing-function: var(--default-transition-timing-function) * 2;
transition-duration: var(--default-transition-duration);
outline: none;
&:is(:where(.group):is(.edit) *) {
border-width: 1px;
}
}
.edit-box:hover {
&:is(:where(.group):is(.edit) *) {
background-color: var(--color-white);
border-color: var(--color-neutral-300);
}
}
.edit-box input:focus {
outline: none;
}
div.edit {
border-width: 1px;
background-color: var(--color-neutral-300);
}
@media (width >=48rem) { @media (width >=48rem) {
.grid-main { .grid-main {
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
margin: 0 10%; margin: 0 10%;
} }
.grid-sub { .grid-sub.responsive {
display: grid;
}
.btn {
padding-inline: calc(var(--spacing) * 4);
} }
} }
} }

View File

@@ -9,16 +9,18 @@
"Courier New", monospace; "Courier New", monospace;
--color-red-500: oklch(63.7% 0.237 25.331); --color-red-500: oklch(63.7% 0.237 25.331);
--color-red-600: oklch(57.7% 0.245 27.325); --color-red-600: oklch(57.7% 0.245 27.325);
--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-700: oklch(37.2% 0.044 257.287);
--color-neutral-100: oklch(97% 0 0); --color-neutral-100: oklch(97% 0 0);
--color-neutral-200: oklch(92.2% 0 0); --color-neutral-200: oklch(92.2% 0 0);
--color-neutral-300: oklch(87% 0 0); --color-neutral-300: oklch(87% 0 0);
--color-neutral-400: oklch(70.8% 0 0); --color-neutral-400: oklch(70.8% 0 0);
--color-neutral-500: oklch(55.6% 0 0); --color-neutral-500: oklch(55.6% 0 0);
--color-neutral-600: oklch(43.9% 0 0);
--color-neutral-700: oklch(37.1% 0 0); --color-neutral-700: oklch(37.1% 0 0);
--color-neutral-800: oklch(26.9% 0 0); --color-neutral-800: oklch(26.9% 0 0);
--color-neutral-900: oklch(20.5% 0 0);
--color-black: #000; --color-black: #000;
--color-white: #fff; --color-white: #fff;
--spacing: 0.25rem; --spacing: 0.25rem;
@@ -26,6 +28,8 @@
--text-sm--line-height: calc(1.25 / 0.875); --text-sm--line-height: calc(1.25 / 0.875);
--text-xl: 1.25rem; --text-xl: 1.25rem;
--text-xl--line-height: calc(1.75 / 1.25); --text-xl--line-height: calc(1.75 / 1.25);
--text-2xl: 1.5rem;
--text-2xl--line-height: calc(2 / 1.5);
--font-weight-bold: 700; --font-weight-bold: 700;
--radius-md: 0.375rem; --radius-md: 0.375rem;
--default-transition-duration: 150ms; --default-transition-duration: 150ms;
@@ -184,12 +188,36 @@
} }
} }
@layer utilities { @layer utilities {
.\@container {
container-type: inline-size;
}
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.top-2\.5 {
top: calc(var(--spacing) * 2.5);
}
.top-\[0\.125rem\] {
top: 0.125rem;
}
.right-1 {
right: calc(var(--spacing) * 1);
}
.right-2\.5 {
right: calc(var(--spacing) * 2.5);
}
.col-span-2 { .col-span-2 {
grid-column: span 2 / span 2; grid-column: span 2 / span 2;
} }
.col-span-3 { .col-span-3 {
grid-column: span 3 / span 3; grid-column: span 3 / span 3;
} }
.col-span-full {
grid-column: 1 / -1;
}
.mx-auto { .mx-auto {
margin-inline: auto; margin-inline: auto;
} }
@@ -199,12 +227,115 @@
.mt-1 { .mt-1 {
margin-top: calc(var(--spacing) * 1); margin-top: calc(var(--spacing) * 1);
} }
.mb-1 {
margin-bottom: calc(var(--spacing) * 1);
}
.mb-2 { .mb-2 {
margin-bottom: calc(var(--spacing) * 2); margin-bottom: calc(var(--spacing) * 2);
} }
.ml-1 {
margin-left: calc(var(--spacing) * 1);
}
.icon-\[material-symbols-light--cancel-outline\] {
display: inline-block;
width: 1.25em;
height: 1.25em;
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='m8.4 16.308l3.6-3.6l3.6 3.6l.708-.708l-3.6-3.6l3.6-3.6l-.708-.708l-3.6 3.6l-3.6-3.6l-.708.708l3.6 3.6l-3.6 3.6zM12.003 21q-1.866 0-3.51-.708q-1.643-.709-2.859-1.924t-1.925-2.856T3 12.003t.709-3.51Q4.417 6.85 5.63 5.634t2.857-1.925T11.997 3t3.51.709q1.643.708 2.859 1.922t1.925 2.857t.709 3.509t-.708 3.51t-1.924 2.859t-2.856 1.925t-3.509.709M12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8'/%3E%3C/svg%3E");
}
.icon-\[material-symbols-light--check-circle-outline\] {
display: inline-block;
width: 1.25em;
height: 1.25em;
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='m10.562 15.908l6.396-6.396l-.708-.708l-5.688 5.688l-2.85-2.85l-.708.708zM12.003 21q-1.866 0-3.51-.708q-1.643-.709-2.859-1.924t-1.925-2.856T3 12.003t.709-3.51Q4.417 6.85 5.63 5.634t2.857-1.925T11.997 3t3.51.709q1.643.708 2.859 1.922t1.925 2.857t.709 3.509t-.708 3.51t-1.924 2.859t-2.856 1.925t-3.509.709M12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8'/%3E%3C/svg%3E");
}
.icon-\[material-symbols-light--circle-outline\] {
display: inline-block;
width: 1.25em;
height: 1.25em;
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M12.003 21q-1.866 0-3.51-.708q-1.643-.709-2.859-1.924t-1.925-2.856T3 12.003t.709-3.51Q4.417 6.85 5.63 5.634t2.857-1.925T11.997 3t3.51.709q1.643.708 2.859 1.922t1.925 2.857t.709 3.509t-.708 3.51t-1.924 2.859t-2.856 1.925t-3.509.709M12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8'/%3E%3C/svg%3E");
}
.icon-\[material-symbols-light--delete-outline\] {
display: inline-block;
width: 1.25em;
height: 1.25em;
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M7.616 20q-.672 0-1.144-.472T6 18.385V6H5V5h4v-.77h6V5h4v1h-1v12.385q0 .69-.462 1.153T16.384 20zM17 6H7v12.385q0 .269.173.442t.443.173h8.769q.23 0 .423-.192t.192-.424zM9.808 17h1V8h-1zm3.384 0h1V8h-1zM7 6v13z'/%3E%3C/svg%3E");
}
.icon-\[material-symbols-light--more-time\] {
display: inline-block;
width: 1.25em;
height: 1.25em;
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M11.003 20q-1.666 0-3.123-.622t-2.545-1.71t-1.712-2.544T3 12.003t.622-3.123t1.711-2.546q1.09-1.089 2.545-1.711T11 4q.525 0 1.013.063T13 4.25V5.3q-.5-.15-.987-.225T11 5Q8.089 5 6.044 7.044T4 12t2.044 4.956T11 19t4.956-2.044T18 11.996q0-.271-.025-.554t-.094-.557h1.011q.05.236.08.538q.028.302.028.577q0 1.667-.622 3.122t-1.71 2.545q-1.089 1.088-2.544 1.71q-1.455.623-3.121.623m3.143-4.146L10.5 12.208V7h1v4.792l3.354 3.354zM18 8.884v-3h-3v-1h3v-3h1v3h3v1h-3v3z'/%3E%3C/svg%3E");
}
.icon-\[material-symbols-light--motion-photos-paused-outline\] {
display: inline-block;
width: 1.25em;
height: 1.25em;
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M9.808 14.616h1V9.385h-1zm3.384 0h1V9.385h-1zM12.003 21q-1.866 0-3.51-.705q-1.643-.706-2.859-1.915t-1.925-2.843T3 12.039q0-.905.167-1.778t.497-1.713l.78.78q-.219.65-.331 1.32T4 12q0 3.35 2.325 5.675T12 20t5.675-2.325T20 12t-2.325-5.675T12 4q-.675 0-1.332.112t-1.3.332l-.776-.775q.789-.315 1.606-.492T11.885 3q1.887 0 3.546.701t2.894 1.926t1.955 2.866t.72 3.505t-.708 3.509t-1.924 2.859t-2.856 1.925t-3.509.709M5.923 6.808q-.356 0-.62-.265q-.264-.264-.264-.62t.264-.62t.62-.264t.62.264t.265.62t-.265.62t-.62.265M12 12'/%3E%3C/svg%3E");
}
.icon-\[material-symbols-light--schedule-outline\] {
display: inline-block;
width: 1.25em;
height: 1.25em;
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='m15.646 16.354l.708-.708l-3.854-3.854V7h-1v5.208zM12.003 21q-1.866 0-3.51-.708q-1.643-.709-2.859-1.924t-1.925-2.856T3 12.003t.709-3.51Q4.417 6.85 5.63 5.634t2.857-1.925T11.997 3t3.51.709q1.643.708 2.859 1.922t1.925 2.857t.709 3.509t-.708 3.51t-1.924 2.859t-2.856 1.925t-3.509.709M12 20q3.325 0 5.663-2.337T20 12t-2.337-5.663T12 4T6.337 6.338T4 12t2.338 5.663T12 20'/%3E%3C/svg%3E");
}
.block {
display: block;
}
.flex { .flex {
display: flex; display: flex;
} }
.grid {
display: grid;
}
.hidden { .hidden {
display: none; display: none;
} }
@@ -222,9 +353,19 @@
width: calc(var(--spacing) * 4); width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4); height: calc(var(--spacing) * 4);
} }
.size-5 {
width: calc(var(--spacing) * 5);
height: calc(var(--spacing) * 5);
}
.h-2 {
height: calc(var(--spacing) * 2);
}
.h-4 { .h-4 {
height: calc(var(--spacing) * 4); height: calc(var(--spacing) * 4);
} }
.h-5 {
height: calc(var(--spacing) * 5);
}
.h-8 { .h-8 {
height: calc(var(--spacing) * 8); height: calc(var(--spacing) * 8);
} }
@@ -234,51 +375,90 @@
.h-full { .h-full {
height: 100%; height: 100%;
} }
.w-1\/3 {
width: calc(1/3 * 100%);
}
.w-2 { .w-2 {
width: calc(var(--spacing) * 2); width: calc(var(--spacing) * 2);
} }
.w-4 { .w-4 {
width: calc(var(--spacing) * 4); width: calc(var(--spacing) * 4);
} }
.w-5 {
width: calc(var(--spacing) * 5);
}
.w-9\/10 { .w-9\/10 {
width: calc(9/10 * 100%); width: calc(9/10 * 100%);
} }
.w-\[2px\] { .w-\[2px\] {
width: 2px; width: 2px;
} }
.w-auto {
width: auto;
}
.w-full { .w-full {
width: 100%; width: 100%;
} }
.flex-shrink-0 {
flex-shrink: 0;
}
.flex-grow { .flex-grow {
flex-grow: 1; flex-grow: 1;
} }
.grow { .grow-0 {
flex-grow: 1; flex-grow: 0;
} }
.grow-1 { .grow-1 {
flex-grow: 1; flex-grow: 1;
} }
.basis-\[content\] {
flex-basis: content;
}
.cursor-pointer { .cursor-pointer {
cursor: pointer; cursor: pointer;
} }
.scroll-m-2 {
scroll-margin: calc(var(--spacing) * 2);
}
.appearance-none {
appearance: none;
}
.break-after-page {
break-after: page;
}
.auto-rows-min {
grid-auto-rows: min-content;
}
.grid-cols-2 { .grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-cols-\[3fr_2fr_2fr_2fr_3fr_3fr_3fr\] {
grid-template-columns: 3fr 2fr 2fr 2fr 3fr 3fr 3fr;
}
.grid-cols-subgrid {
grid-template-columns: subgrid;
}
.grid-rows-6 {
grid-template-rows: repeat(6, minmax(0, 1fr));
}
.flex-col { .flex-col {
flex-direction: column; flex-direction: column;
} }
.flex-row { .flex-row {
flex-direction: row; flex-direction: row;
} }
.content-baseline {
align-content: baseline;
}
.content-end { .content-end {
align-content: flex-end; align-content: flex-end;
} }
.items-center { .items-center {
align-items: center; align-items: center;
} }
.items-end {
align-items: flex-end;
}
.justify-around { .justify-around {
justify-content: space-around; justify-content: space-around;
} }
@@ -311,38 +491,61 @@
border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
} }
} }
.divide-neutral-300 {
:where(& > :not(:last-child)) {
border-color: var(--color-neutral-300);
}
}
.justify-self-end { .justify-self-end {
justify-self: flex-end; justify-self: flex-end;
} }
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }
.rounded {
border-radius: 0.25rem;
}
.rounded-full { .rounded-full {
border-radius: calc(infinity * 1px); border-radius: calc(infinity * 1px);
} }
.rounded-md { .rounded-md {
border-radius: var(--radius-md); border-radius: var(--radius-md);
} }
.rounded-none {
border-radius: 0;
}
.border { .border {
border-style: var(--tw-border-style); border-style: var(--tw-border-style);
border-width: 1px; border-width: 1px;
} }
.border-neutral-200 { .border-0 {
border-color: var(--color-neutral-200); border-style: var(--tw-border-style);
border-width: 0px;
}
.border-r-0 {
border-right-style: var(--tw-border-style);
border-right-width: 0px;
}
.border-r-1 {
border-right-style: var(--tw-border-style);
border-right-width: 1px;
}
.border-b-0 {
border-bottom-style: var(--tw-border-style);
border-bottom-width: 0px;
}
.border-dashed {
--tw-border-style: dashed;
border-style: dashed;
} }
.border-neutral-300 { .border-neutral-300 {
border-color: var(--color-neutral-300); border-color: var(--color-neutral-300);
} }
.border-neutral-800 { .border-neutral-500 {
border-color: var(--color-neutral-800); border-color: var(--color-neutral-500);
} }
.border-neutral-900 { .border-neutral-600 {
border-color: var(--color-neutral-900); border-color: var(--color-neutral-600);
} }
.bg-accent { .bg-accent {
background-color: var(--color-accent); background-color: var(--color-accent);
@@ -371,15 +574,25 @@
.p-2 { .p-2 {
padding: calc(var(--spacing) * 2); padding: calc(var(--spacing) * 2);
} }
.p-8 {
padding: calc(var(--spacing) * 8);
}
.px-3 { .px-3 {
padding-inline: calc(var(--spacing) * 3); padding-inline: calc(var(--spacing) * 3);
} }
.py-2 { .py-2 {
padding-block: calc(var(--spacing) * 2); padding-block: calc(var(--spacing) * 2);
} }
.py-4 {
padding-block: calc(var(--spacing) * 4);
}
.text-center { .text-center {
text-align: center; text-align: center;
} }
.text-2xl {
font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
}
.text-sm { .text-sm {
font-size: var(--text-sm); font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height)); line-height: var(--tw-leading, var(--text-sm--line-height));
@@ -398,6 +611,12 @@
.text-accent { .text-accent {
color: var(--color-accent); color: var(--color-accent);
} }
.text-black {
color: var(--color-black);
}
.text-neutral-300 {
color: var(--color-neutral-300);
}
.text-neutral-500 { .text-neutral-500 {
color: var(--color-neutral-500); color: var(--color-neutral-500);
} }
@@ -413,6 +632,9 @@
.text-red-600 { .text-red-600 {
color: var(--color-red-600); color: var(--color-red-600);
} }
.text-slate-700 {
color: var(--color-slate-700);
}
.uppercase { .uppercase {
text-transform: uppercase; text-transform: uppercase;
} }
@@ -433,6 +655,18 @@
--tw-duration: 300ms; --tw-duration: 300ms;
transition-duration: 300ms; transition-duration: 300ms;
} }
.\*\:text-center {
:is(& > *) {
text-align: center;
}
}
.\*\:not-print\:p-2 {
:is(& > *) {
@media not print {
padding: calc(var(--spacing) * 2);
}
}
}
.group-hover\:text-black { .group-hover\:text-black {
&:is(:where(.group):hover *) { &:is(:where(.group):hover *) {
@media (hover: hover) { @media (hover: hover) {
@@ -447,9 +681,9 @@
} }
} }
} }
.group-\[\.edit\]\:block { .group-\[\.edit\]\:ml-2 {
&:is(:where(.group):is(.edit) *) { &:is(:where(.group):is(.edit) *) {
display: block; margin-left: calc(var(--spacing) * 2);
} }
} }
.group-\[\.edit\]\:flex { .group-\[\.edit\]\:flex {
@@ -467,18 +701,21 @@
display: inline; display: inline;
} }
} }
.group-\[\.edit\]\/button\:block {
&:is(:where(.group\/button):is(.edit) *) {
display: block;
}
}
.group-\[\.edit\]\/button\:hidden {
&:is(:where(.group\/button):is(.edit) *) {
display: none;
}
}
.placeholder\:text-neutral-400 { .placeholder\:text-neutral-400 {
&::placeholder { &::placeholder {
color: var(--color-neutral-400); color: var(--color-neutral-400);
} }
} }
.hover\:border-neutral-300 {
&:hover {
@media (hover: hover) {
border-color: var(--color-neutral-300);
}
}
}
.hover\:border-neutral-500 { .hover\:border-neutral-500 {
&:hover { &:hover {
@media (hover: hover) { @media (hover: hover) {
@@ -500,10 +737,10 @@
} }
} }
} }
.hover\:text-accent { .hover\:bg-red-700 {
&:hover { &:hover {
@media (hover: hover) { @media (hover: hover) {
color: var(--color-accent); background-color: var(--color-red-700);
} }
} }
} }
@@ -514,11 +751,6 @@
} }
} }
} }
.focus\:border-neutral-400 {
&:focus {
border-color: var(--color-neutral-400);
}
}
.focus\:bg-neutral-700 { .focus\:bg-neutral-700 {
&:focus { &:focus {
background-color: var(--color-neutral-700); background-color: var(--color-neutral-700);
@@ -545,19 +777,36 @@
opacity: 50%; opacity: 50%;
} }
} }
.max-md\:flex {
@media (width < 48rem) {
display: flex;
}
}
.max-md\:grid { .max-md\:grid {
@media (width < 48rem) { @media (width < 48rem) {
display: grid; display: grid;
} }
} }
.max-md\:flex-col { .max-md\:hidden {
@media (width < 48rem) { @media (width < 48rem) {
flex-direction: column; display: none;
}
}
.max-md\:divide-y-1 {
@media (width < 48rem) {
:where(& > :not(:last-child)) {
--tw-divide-y-reverse: 0;
border-bottom-style: var(--tw-border-style);
border-top-style: var(--tw-border-style);
border-top-width: calc(1px * var(--tw-divide-y-reverse));
border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
}
}
}
.max-md\:border-b-1 {
@media (width < 48rem) {
border-bottom-style: var(--tw-border-style);
border-bottom-width: 1px;
}
}
.max-md\:bg-neutral-300 {
@media (width < 48rem) {
background-color: var(--color-neutral-300);
} }
} }
.md\:col-span-1 { .md\:col-span-1 {
@@ -610,23 +859,66 @@
color: transparent; color: transparent;
} }
} }
.group-\[\.edit\]\:md\:block { .group-\[\.edit\]\/button\:md\:block {
&:is(:where(.group):is(.edit) *) { &:is(:where(.group\/button):is(.edit) *) {
@media (width >= 48rem) { @media (width >= 48rem) {
display: block; display: block;
} }
} }
} }
.lg\:hidden {
@media (width >= 64rem) {
display: none;
}
}
.lg\:grid-cols-1 {
@media (width >= 64rem) {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
}
.lg\:divide-x-1 {
@media (width >= 64rem) {
:where(& > :not(:last-child)) {
--tw-divide-x-reverse: 0;
border-inline-style: var(--tw-border-style);
border-inline-start-width: calc(1px * var(--tw-divide-x-reverse));
border-inline-end-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
}
}
}
.lg\:border-0 {
@media (width >= 64rem) {
border-style: var(--tw-border-style);
border-width: 0px;
}
}
.\@7xl\:grid {
@container (width >= 80rem) {
display: grid;
}
}
.\@7xl\:grid-cols-5 {
@container (width >= 80rem) {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
.print\:hidden { .print\:hidden {
@media print { @media print {
display: none; display: none;
} }
} }
} }
@layer base {
body {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
background-color: white;
}
}
@layer components { @layer components {
.grid-main { .grid-main {
display: grid; display: grid;
grid-template-columns: 2fr auto 1fr; grid-template-columns: 4fr 3fr 3fr 1fr;
align-items: stretch; align-items: stretch;
} }
.grid-sub { .grid-sub {
@@ -636,6 +928,10 @@
border-color: var(--color-neutral-400); border-color: var(--color-neutral-400);
transition: background-color 0.2s ease-in-out; transition: background-color 0.2s ease-in-out;
} }
.grid-sub.responsive {
display: flex;
flex-direction: column;
}
.grid-sub:hover { .grid-sub:hover {
background-color: var(--color-neutral-200); background-color: var(--color-neutral-200);
} }
@@ -643,11 +939,76 @@
padding: calc(var(--spacing) * 2); padding: calc(var(--spacing) * 2);
border-color: var(--color-neutral-400); border-color: var(--color-neutral-400);
} }
.btn {
width: 100%;
cursor: pointer;
border-radius: var(--radius-md);
color: var(--color-neutral-800);
font-size: var(--text-sm);
text-align: center;
padding: calc(var(--spacing) * 2);
border-style: var(--tw-border-style);
border-width: 1px;
border-color: var(--color-neutral-800);
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
transition-timing-function: var( --tw-ease, var(--default-transition-timing-function) );
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
input.btn, select.btn {
transition-duration: 300ms;
}
.btn:hover {
color: var(--color-white);
background-color: var(--color-neutral-700);
}
.btn:disabled {
opacity: 50%;
pointer-events: none;
}
input.btn, select.btn {
text-align: left;
}
input.btn:hover, select.btn:hover {
border-color: var(--color-neutral-300);
background-color: var(--color-neutral-100);
color: var(--color-neutral-800);
}
.edit-box {
border-radius: var(--radius-md);
overflow: hidden;
border-color: var(--color-neutral-500);
transition-property: background-color, border-color;
transition-timing-function: var(--default-transition-timing-function) * 2;
transition-duration: var(--default-transition-duration);
outline: none;
&:is(:where(.group):is(.edit) *) {
border-width: 1px;
}
}
.edit-box:hover {
&:is(:where(.group):is(.edit) *) {
background-color: var(--color-white);
border-color: var(--color-neutral-300);
}
}
.edit-box input:focus {
outline: none;
}
div.edit {
border-width: 1px;
background-color: var(--color-neutral-300);
}
@media (width >=48rem) { @media (width >=48rem) {
.grid-main { .grid-main {
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
margin: 0 10%; margin: 0 10%;
} }
.grid-sub.responsive {
display: grid;
}
.btn {
padding-inline: calc(var(--spacing) * 4);
}
} }
} }
@property --tw-divide-x-reverse { @property --tw-divide-x-reverse {

View File

@@ -1,34 +1,81 @@
function editDay(element, event, formId) { function clearEditState() {
var form = element.closest(".grid-sub").querySelector(".all-booking-component > form"); for (let e of document.querySelectorAll(".edit")) {
form.classList.toggle("edit"); e.classList.remove("edit");
element.classList.toggle("edit"); }
if (element.classList.contains("edit")) { toggleAbsenceEdit(false);
event.preventDefault(); }
form.querySelectorAll("input, select").forEach((input) => {
input.disabled = false; function clearButtonState() {
}); for (let b of document.querySelectorAll(".change-button-component")) {
} else { b.type = "button";
form.submit();
} }
} }
function editAbwesenheit(element, event) { function editWorkday(element, event, id, isWorkDay) {
var newBookingComponent = element.closest(".grid-sub").querySelector(".new-booking-component"); event.preventDefault();
if (element.value == 0) { let form = document.getElementById(id);
newBookingComponent.style.display = ""; if (form == null) {
form = element.closest(".grid-sub").querySelector(".all-booking-component > form");
}
clearEditState();
element.closest(".grid-sub").classList.add("edit");
toggleAbsenceEdit(!isWorkDay);
if (isWorkDay) {
element.classList.add("edit");
if (element.type == "button") {
for (let input of form.querySelectorAll("input, select")) {
input.disabled = false;
}
clearButtonState();
element.type = "submit";
} else {
form.submit();
}
} else { } else {
newBookingComponent.style.display = "none"; const absenceForm = document.getElementById("absence_form");
if (id == 0) {
absenceForm.querySelector("[name=date_from]").value = form.id.replace("time-", "");
absenceForm.querySelector("[name=date_to]").value = form.id.replace("time-", "");
} else {
syncFields(form, absenceForm, ["date_from", "date_to", "aw_type", "aw_id"]);
}
}
}
function toggleAbsenceEdit(state) {
const form = document.getElementById("absence_form");
if (state) {
form.classList.remove("hidden");
form.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest",
});
} else {
form.classList.add("hidden");
}
}
function syncFields(from, to, fieldsToSync) {
for (let field of fieldsToSync) {
const src = from.querySelector(`[name=${field}]`);
const target = to.querySelector(`[name=${field}]`);
if (!src || !target) return;
target.value = src.value;
} }
} }
function navigateWeek(element, event, direction) { function navigateWeek(element, event, direction) {
var dateInput = element.closest("form").querySelector("input[type=date]"); const dateInput = element.closest("form").querySelector("input[type=date]");
var date = dateInput.valueAsDate; const date = dateInput.valueAsDate;
date.setDate(date.getDate() + 7 * direction); date.setDate(date.getDate() + 7 * direction);
date.setHours(10); date.setHours(10);
dateInput.valueAsDate = date; dateInput.valueAsDate = date;
} }
function logoutUser() { function logoutUser() {
fetch("/user/logout", {}).then(() => window.location.reload()); fetch("/user/logout", {}).then(() => globalThis.location.reload());
} }

View File

@@ -1,11 +1,7 @@
package templates package templates
import ( import "arbeitszeitmessung/models"
"arbeitszeitmessung/models" import "arbeitszeitmessung/helper"
"fmt"
"strconv"
"time"
)
templ Base() { templ Base() {
<!DOCTYPE html> <!DOCTYPE html>
@@ -17,27 +13,16 @@ templ Base() {
</head> </head>
} }
templ TimePage(workDays []models.WorkDay) { templ LoginPage(success bool, errorMsg string) {
@Base()
@headerComponent()
<div class="grid-main divide-y-1">
@inputForm()
for _, day := range workDays {
@dayComponent(day)
}
</div>
@LegendComponent()
}
templ LoginPage(failed bool) {
@Base() @Base()
<div class="w-full h-[100vh] flex flex-col justify-center items-center"> <div class="w-full h-[100vh] flex flex-col justify-center items-center">
<form method="POST" class="w-9/10 md:w-1/2 flex flex-col gap-4 p-2 mb-2"> <form method="POST" class="w-9/10 md:w-1/2 flex flex-col gap-4 p-2 mb-2">
<h1 class="font-bold uppercase text-xl text-center mb-2">Benutzer Anmelden</h1> <h1 class="font-bold uppercase text-xl text-center mb-2">Benutzer Anmelden</h1>
<input name="personal_nummer" placeholder="Personalnummer" type="text" class="w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500"/> <input name="personal_nummer" placeholder="Personalnummer" type="text" class="w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500"/>
<input name="password" placeholder="Passwort" type="password" class="w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500"/> <input name="password" placeholder="Passwort" type="password" class="w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500"/>
if failed { if !success {
<p class="text-red-600 text-sm">Login fehlgeschlagen, bitte erneut versuchen!</p> <p class="text-red-600 text-sm">Login fehlgeschlagen, bitte erneut versuchen!</p>
<p class="text-red-600 text-sm">{ errorMsg }</p>
} }
<button type="submit" class="cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Login</button> <button type="submit" class="cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Login</button>
</form> </form>
@@ -45,11 +30,13 @@ templ LoginPage(failed bool) {
} }
templ UserPage(status int) { templ UserPage(status int) {
{{
user := ctx.Value("user").(models.User)
}}
@Base() @Base()
@headerComponent() @headerComponent()
<div class="grid-main divide-y-1"> <div class="grid-main divide-y-1">
<div class="grid-sub"></div> <form method="POST" class="grid-sub responsive lg:divide-x-1">
<form method="POST" class="grid-sub divide-x-1">
<h1 class="grid-cell font-bold uppercase text-xl text-center">Passwort ändern</h1> <h1 class="grid-cell font-bold uppercase text-xl text-center">Passwort ändern</h1>
<div class="grid-cell col-span-3 flex flex-col gap-2"> <div class="grid-cell col-span-3 flex flex-col gap-2">
<input name="password" placeholder="Aktuelles Passwort" type="password" class="w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500"/> <input name="password" placeholder="Aktuelles Passwort" type="password" class="w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500"/>
@@ -65,126 +52,91 @@ templ UserPage(status int) {
} }
</div> </div>
<div class="grid-cell"> <div class="grid-cell">
<button name="action" value="change-pass" type="submit" class="w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Ändern</button> <button name="action" value="change-pass" type="submit" class="btn">Ändern</button>
</div> </div>
</form> </form>
<div class="grid-sub divide-x-1"> <div class="grid-sub responsive lg:divide-x-1">
<h1 class="grid-cell font-bold uppercase text-xl text-center">Nutzerdaten</h1>
<div class="grid-cell col-span-3">
<p>Nutzername: <span class="text-neutral-500">{ user.Vorname } { user.Name }</span></p>
<p>Personalnummer: <span class="text-neutral-500">{ user.PersonalNummer }</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> <h1 class="grid-cell font-bold uppercase text-xl text-center">Nutzer abmelden</h1>
<div class="grid-cell col-span-3"> <div class="grid-cell col-span-3">
<p>Nutzer von Weboberfläche abmelden.</p> <p>Nutzer von Weboberfläche abmelden.</p>
</div> </div>
<div class="grid-cell"> <div class="grid-cell">
<button onclick="logoutUser" type="button" class="w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Abmelden</button> <button onclick="logoutUser" type="button" class="btn">Abmelden</button>
</div> </div>
</div> </div>
</div> </div>
} }
templ statusCheckMark(status models.WeekStatus, target models.WeekStatus) {
if status >= target {
<div class="icon-[material-symbols-light--check-circle-outline]"></div>
} else {
<div class="icon-[material-symbols-light--circle-outline]"></div>
}
}
templ TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) { templ TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) {
{{
year, kw := userWeek.WeekStart.ISOWeek()
}}
@Base() @Base()
@headerComponent() @headerComponent()
<div class="grid-main divide-y-1"> <div class="grid-main divide-y-1">
<div class="grid-sub divide-x-1 bg-neutral-300"> <div class="grid-sub lg:divide-x-1 max-md:divide-y-1 responsive @container">
<div class="grid-cell font-bold uppercase"> <div class="grid-cell col-span-full bg-neutral-300 lg:border-0">
{ fmt.Sprintf("%s %s", userWeek.User.Vorname, userWeek.User.Name) } <h2 class="text-2xl uppercase font-bold">Eigene Abrechnung</h2>
</div>
<div class="grid-cell col-span-3 flex flex-col gap-2">
for _, day := range userWeek.WorkDays {
@weekDayComponent(userWeek.User, day)
}
</div>
<div class="grid-cell flex flex-col gap-2">
<form method="get" class="flex flex-row gap-4 items-center justify-around">
<input type="date" class="hidden" name="submission_date" value={ userWeek.WeekStart.Format(time.DateOnly) }/>
<button onclick={ templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "-1") } class="p-2 w-1/3 cursor-pointer rounded-md text-neutral-800 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="chevron-left size-4 mx-auto" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0"></path>
</svg>
</button>
<p class="whitespace-nowrap">KW { fmt.Sprintf("%02d, %d", kw, year) }</p>
<button disabled?={ time.Since(userWeek.WeekStart) < 24*7*time.Hour } onclick={ templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "1") } class="p-2 w-1/3 cursor-pointer rounded-md text-neutral-800 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="chevron-right size-4 mx-auto" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708"></path>
</svg>
</button>
</form>
<form method="post">
<input type="hidden" name="method" value="send"/>
<input type="hidden" name="user" value={ strconv.Itoa(userWeek.User.PersonalNummer) }/>
<input type="hidden" name="week" value={ userWeek.WeekStart.Format(time.DateOnly) }/>
switch userWeek.CheckStatus() {
case models.WeekStatusNone:
<p class="text-sm">an Vorgesetzten senden</p>
<button disabled?={ time.Since(userWeek.WeekStart) < 24*7*time.Hour } type="submit" class="w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Senden</button>
if time.Since(userWeek.WeekStart) < 24*7*time.Hour {
<p class="text-sm text-red-500">Die Woche kann erst am nächsten Montag abgesendet werden!</p>
}
case models.WeekStatusSent:
<p class="text-sm">an Vorgesetzten gesendet</p>
<button type="submit" class="w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Korrigieren</button>
<p class="flex flex-row gap-2 items-center">
akzeptiert:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"></path>
</svg>
</p>
case models.WeekStatusAccepted:
<p class="text-sm">vom Vorgesetzten bestätigt</p>
<button type="submit" class="w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Korrigieren</button>
<p class="flex flex-row gap-2 text-accent items-center">
akzeptiert:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"></path>
<path d="m10.97 4.97-.02.022-3.473 4.425-2.093-2.094a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05"></path>
</svg>
</p>
}
</form>
</div> </div>
</div> </div>
@workWeekComponent(userWeek, false)
if len(weeks) > 0 {
<div class="grid-cell col-span-full bg-neutral-300">
<h2 class="text-2xl uppercase font-bold">Abrechnung Mitarbeiter</h2>
</div>
}
for _, week := range weeks { for _, week := range weeks {
@employeComponent(week) @workWeekComponent(week, true)
} }
</div> </div>
} }
templ NavPage() { templ TeamPresencePage(teamPresence map[models.User]bool) {
@Base()
<div class="w-full h-[100vh] flex flex-col justify-center items-center">
<div class="flex flex-col justify-between w-full md:w-1/2 py-2">
<a class="text-xl hover:text-accent transition-colors1" href="/time">Zeitverwaltung</a>
<a class="text-xl hover:text-accent transition-colors1" href="/team">Mitarbeiter</a>
<a class="text-xl hover:text-accent transition-colors1" href="/user">Nutzer</a>
</div>
</div>
}
templ TeamPresencePage(teamPresence map[bool][]models.User) {
@Base() @Base()
@headerComponent() @headerComponent()
<div class="grid-main divide-y-1"> <div class="grid-main divide-y-1">
<div class="grid-sub divide-x-1"> <div class="grid-sub divide-x-1">
<h2 class="grid-cell font-bold uppercase">Anwesend</h2> <h2 class="grid-cell font-bold uppercase">Mitarbeiter</h2>
<div class="flex flex-col col-span-2 md:col-span-4">
for _, user := range teamPresence[true] {
@userPresenceComponent(user, true)
}
</div>
</div> </div>
<div class="grid-sub divide-x-1"> for user, present := range teamPresence {
<h2 class="grid-cell font-bold uppercase">Nicht Anwesend</h2> <div class="grid-sub">
<div class="flex flex-col col-span-2 md:col-span-4"> <div class="grid-cell flex flex-row gap-2 col-span-2 md:col-span-1">
for _, user := range teamPresence[false] { @timeGaugeComponent(helper.BoolToInt8(present)*100-1, false)
@userPresenceComponent(user, false) <p>{ user.Vorname } { user.Name }</p>
} </div>
<div class="grid-cell col-span-2">
if present {
<span class="text-neutral-500">Anwesend</span>
} else {
<span class="text-neutral-500">Abwesend</span>
}
</div>
</div> </div>
</div> }
// <div class="grid-sub divide-x-1">
// <h2 class="grid-cell font-bold uppercase">Nicht Anwesend</h2>
// <div class="flex flex-col col-span-2 md:col-span-4">
// for _, user := range teamPresence[false] {
// @userPresenceComponent(user, false)
// }
// </div>
// </div>
</div> </div>
} }
templ LogoutButton() { templ LogoutButton() {
<button onclick="logoutUser()" type="button" class="cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Abmelden</button> <button onclick="logoutUser()" type="button" class="cursor-pointer">Abmelden</button>
} }

View File

@@ -8,12 +8,8 @@ package templates
import "github.com/a-h/templ" 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/models"
"arbeitszeitmessung/models" import "arbeitszeitmessung/helper"
"fmt"
"strconv"
"time"
)
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) {
@@ -44,7 +40,7 @@ func Base() templ.Component {
}) })
} }
func TimePage(workDays []models.WorkDay) templ.Component { func LoginPage(success bool, errorMsg 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 {
@@ -69,72 +65,30 @@ func TimePage(workDays []models.WorkDay) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = headerComponent().Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"w-full h-[100vh] flex flex-col justify-center items-center\"><form method=\"POST\" class=\"w-9/10 md:w-1/2 flex flex-col gap-4 p-2 mb-2\"><h1 class=\"font-bold uppercase text-xl text-center mb-2\">Benutzer Anmelden</h1><input name=\"personal_nummer\" placeholder=\"Personalnummer\" type=\"text\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> <input name=\"password\" placeholder=\"Passwort\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> ")
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, "<div class=\"grid-main divide-y-1\">") if !success {
if templ_7745c5c3_Err != nil { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<p class=\"text-red-600 text-sm\">Login fehlgeschlagen, bitte erneut versuchen!</p><p class=\"text-red-600 text-sm\">")
return templ_7745c5c3_Err if templ_7745c5c3_Err != nil {
} return templ_7745c5c3_Err
templ_7745c5c3_Err = inputForm().Render(ctx, templ_7745c5c3_Buffer) }
if templ_7745c5c3_Err != nil { var templ_7745c5c3_Var3 string
return templ_7745c5c3_Err templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(errorMsg)
} if templ_7745c5c3_Err != nil {
for _, day := range workDays { return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 25, Col: 46}
templ_7745c5c3_Err = dayComponent(day).Render(ctx, templ_7745c5c3_Buffer) }
_, 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, 4, "</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, 3, "</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<button type=\"submit\" class=\"cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Login</button></form></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = LegendComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func LoginPage(failed 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_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"w-full h-[100vh] flex flex-col justify-center items-center\"><form method=\"POST\" class=\"w-9/10 md:w-1/2 flex flex-col gap-4 p-2 mb-2\"><h1 class=\"font-bold uppercase text-xl text-center mb-2\">Benutzer Anmelden</h1><input name=\"personal_nummer\" placeholder=\"Personalnummer\" type=\"text\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> <input name=\"password\" placeholder=\"Passwort\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if failed {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<p class=\"text-red-600 text-sm\">Login fehlgeschlagen, bitte erneut versuchen!</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<button type=\"submit\" class=\"cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Login</button></form></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -163,6 +117,8 @@ func UserPage(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)
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 {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@@ -171,31 +127,106 @@ func UserPage(status int) 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, 7, "<div class=\"grid-main divide-y-1\"><div class=\"grid-sub\"></div><form method=\"POST\" class=\"grid-sub divide-x-1\"><h1 class=\"grid-cell font-bold uppercase text-xl text-center\">Passwort ändern</h1><div class=\"grid-cell col-span-3 flex flex-col gap-2\"><input name=\"password\" placeholder=\"Aktuelles Passwort\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> <input name=\"new_password\" placeholder=\"Neues Passwort\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> <input name=\"new_password_repeat\" placeholder=\"Neues Passwort wiederholen\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"grid-main divide-y-1\"><form method=\"POST\" class=\"grid-sub responsive lg:divide-x-1\"><h1 class=\"grid-cell font-bold uppercase text-xl text-center\">Passwort ändern</h1><div class=\"grid-cell col-span-3 flex flex-col gap-2\"><input name=\"password\" placeholder=\"Aktuelles Passwort\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> <input name=\"new_password\" placeholder=\"Neues Passwort\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> <input name=\"new_password_repeat\" placeholder=\"Neues Passwort wiederholen\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
switch { switch {
case status == 401: case status == 401:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<p class=\"text-red-600 text-sm\">Aktuelles Passwort nicht korrekt!</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<p class=\"text-red-600 text-sm\">Aktuelles Passwort nicht korrekt!</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
case status >= 400: case status >= 400:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<p class=\"text-red-600 text-sm\">Passwortwechsel fehlgeschlagen, bitte erneut versuchen!</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<p class=\"text-red-600 text-sm\">Passwortwechsel fehlgeschlagen, bitte erneut versuchen!</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
case status == 202: case status == 202:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<p class=\"text-accent text-sm\">Passwortänderung erfolgreich</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<p class=\"text-accent text-sm\">Passwortänderung erfolgreich</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, 11, "</div><div class=\"grid-cell\"><button name=\"action\" value=\"change-pass\" type=\"submit\" class=\"w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Ändern</button></div></form><div class=\"grid-sub 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=\"w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Abmelden</button></div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div><div class=\"grid-cell\"><button name=\"action\" value=\"change-pass\" type=\"submit\" class=\"btn\">Ändern</button></div></form><div class=\"grid-sub responsive lg:divide-x-1\"><h1 class=\"grid-cell font-bold uppercase text-xl text-center\">Nutzerdaten</h1><div class=\"grid-cell col-span-3\"><p>Nutzername: <span class=\"text-neutral-500\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 61, Col: 64}
}
_, 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, 11, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 61, Col: 78}
}
_, 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, 12, "</span></p><p>Personalnummer: <span class=\"text-neutral-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(user.PersonalNummer)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 62, Col: 75}
}
_, 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, 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>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func statusCheckMark(status models.WeekStatus, target models.WeekStatus) 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_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if status >= target {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"icon-[material-symbols-light--check-circle-outline]\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"icon-[material-symbols-light--circle-outline]\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil return nil
}) })
} }
@@ -216,13 +247,11 @@ func TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) templ.Component
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx) templ_7745c5c3_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil { if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var5 = templ.NopComponent templ_7745c5c3_Var9 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
year, kw := userWeek.WeekStart.ISOWeek()
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 {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@@ -231,173 +260,27 @@ func TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) 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, 12, "<div class=\"grid-main divide-y-1\"><div class=\"grid-sub divide-x-1 bg-neutral-300\"><div class=\"grid-cell font-bold uppercase\">") 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-2xl uppercase font-bold\">Eigene Abrechnung</h2></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var6 string templ_7745c5c3_Err = workWeekComponent(userWeek, false).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s %s", userWeek.User.Vorname, userWeek.User.Name))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 92, Col: 69}
}
_, 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, 13, "</div><div class=\"grid-cell col-span-3 flex flex-col gap-2\">") if len(weeks) > 0 {
if templ_7745c5c3_Err != nil { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"grid-cell col-span-full bg-neutral-300\"><h2 class=\"text-2xl uppercase font-bold\">Abrechnung Mitarbeiter</h2></div>")
return templ_7745c5c3_Err
}
for _, day := range userWeek.WorkDays {
templ_7745c5c3_Err = weekDayComponent(userWeek.User, day).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, 14, "</div><div class=\"grid-cell flex flex-col gap-2\"><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 {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(userWeek.WeekStart.Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 101, Col: 110}
}
_, 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, 15, "\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "-1"))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<button onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 templ.ComponentScript = templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "-1")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" class=\"p-2 w-1/3 cursor-pointer rounded-md text-neutral-800 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" class=\"chevron-left size-4 mx-auto\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0\"></path></svg></button><p class=\"whitespace-nowrap\">KW ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, 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/pages.templ`, Line: 107, Col: 72}
}
_, 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, 18, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "1"))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<button")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if time.Since(userWeek.WeekStart) < 24*7*time.Hour {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, " disabled")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.ComponentScript = templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "1")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" class=\"p-2 w-1/3 cursor-pointer rounded-md text-neutral-800 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" class=\"chevron-right size-4 mx-auto\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708\"></path></svg></button></form><form method=\"post\"><input type=\"hidden\" name=\"method\" value=\"send\"> <input type=\"hidden\" name=\"user\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(userWeek.User.PersonalNummer))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 116, Col: 88}
}
_, 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, 23, "\"> <input type=\"hidden\" name=\"week\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(userWeek.WeekStart.Format(time.DateOnly))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 117, Col: 86}
}
_, 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, 24, "\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
switch userWeek.CheckStatus() {
case models.WeekStatusNone:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<p class=\"text-sm\">an Vorgesetzten senden</p><button")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if time.Since(userWeek.WeekStart) < 24*7*time.Hour {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " disabled")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " type=\"submit\" class=\"w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Senden</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if time.Since(userWeek.WeekStart) < 24*7*time.Hour {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<p class=\"text-sm text-red-500\">Die Woche kann erst am nächsten Montag abgesendet werden!</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
case models.WeekStatusSent:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<p class=\"text-sm\">an Vorgesetzten gesendet</p><button type=\"submit\" class=\"w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Korrigieren</button><p class=\"flex flex-row gap-2 items-center\">akzeptiert: <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-circle\" viewBox=\"0 0 16 16\"><path d=\"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16\"></path></svg></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case models.WeekStatusAccepted:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<p class=\"text-sm\">vom Vorgesetzten bestätigt</p><button type=\"submit\" class=\"w-full cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-800 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Korrigieren</button><p class=\"flex flex-row gap-2 text-accent items-center\">akzeptiert: <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-check-circle\" viewBox=\"0 0 16 16\"><path d=\"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16\"></path> <path d=\"m10.97 4.97-.02.022-3.473 4.425-2.093-2.094a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05\"></path></svg></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</form></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, week := range weeks { for _, week := range weeks {
templ_7745c5c3_Err = employeComponent(week).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = workWeekComponent(week, 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, 32, "</div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -405,7 +288,7 @@ func TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) templ.Component
}) })
} }
func NavPage() templ.Component { func TeamPresencePage(teamPresence map[models.User]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 {
@@ -421,42 +304,9 @@ func NavPage() templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var13 := templ.GetChildren(ctx) templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var13 == nil { if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var13 = templ.NopComponent templ_7745c5c3_Var10 = 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"w-full h-[100vh] flex flex-col justify-center items-center\"><div class=\"flex flex-col justify-between w-full md:w-1/2 py-2\"><a class=\"text-xl hover:text-accent transition-colors1\" href=\"/time\">Zeitverwaltung</a> <a class=\"text-xl hover:text-accent transition-colors1\" href=\"/team\">Mitarbeiter</a> <a class=\"text-xl hover:text-accent transition-colors1\" href=\"/user\">Nutzer</a></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func TeamPresencePage(teamPresence map[bool][]models.User) 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) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer)
@@ -467,27 +317,66 @@ func TeamPresencePage(teamPresence map[bool][]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, 34, "<div class=\"grid-main divide-y-1\"><div class=\"grid-sub divide-x-1\"><h2 class=\"grid-cell font-bold uppercase\">Anwesend</h2><div class=\"flex flex-col col-span-2 md:col-span-4\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"grid-main divide-y-1\"><div class=\"grid-sub divide-x-1\"><h2 class=\"grid-cell font-bold uppercase\">Mitarbeiter</h2></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, user := range teamPresence[true] { for user, present := range teamPresence {
templ_7745c5c3_Err = userPresenceComponent(user, true).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div class=\"grid-sub\"><div class=\"grid-cell flex flex-row gap-2 col-span-2 md:col-span-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = timeGaugeComponent(helper.BoolToInt8(present)*100-1, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 118, Col: 22}
}
_, 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, 22, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pages.templ`, Line: 118, Col: 36}
}
_, 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, 23, "</p></div><div class=\"grid-cell col-span-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if present {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<span class=\"text-neutral-500\">Anwesend</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<span class=\"text-neutral-500\">Abwesend</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div></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, 35, "</div></div><div class=\"grid-sub divide-x-1\"><h2 class=\"grid-cell font-bold uppercase\">Nicht Anwesend</h2><div class=\"flex flex-col col-span-2 md:col-span-4\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, user := range teamPresence[false] {
templ_7745c5c3_Err = userPresenceComponent(user, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</div></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -511,12 +400,12 @@ func LogoutButton() templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var15 := templ.GetChildren(ctx) templ_7745c5c3_Var13 := templ.GetChildren(ctx)
if templ_7745c5c3_Var15 == nil { if templ_7745c5c3_Var13 == nil {
templ_7745c5c3_Var15 = templ.NopComponent templ_7745c5c3_Var13 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<button onclick=\"logoutUser()\" type=\"button\" class=\"cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Abmelden</button>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<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

@@ -0,0 +1,77 @@
package templates
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"time"
)
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() {
<p class="col-span-full">Kurzarbeit</p>
}
} else {
{{
absentDay, _ := day.(*models.Absence)
}}
<p class="col-span-full">{ absentDay.AbwesenheitTyp.Name }</p>
}
</div>
{{ work, pause, overtime := day.GetAllWorkTimesVirtual(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>
}
templ ColorDuration(d time.Duration, classes string) {
{{
color := ""
if d.Abs() < time.Minute {
color = "text-neutral-300"
}
}}
<p class={ color + " " + classes }>{ helper.FormatDurationFill(d, true) }</p>
}

View File

@@ -0,0 +1,390 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924
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"
"time"
)
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)
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)
_, kw := tsStart.ISOWeek()
noBorder := ""
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<content class=\"p-8 relative flex flex-col gap-4 break-after-page\"><div><h1 class=\"text-2xl font-bold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(e.Vorname)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 17, Col: 45}
}
_, 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, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 17, Col: 56}
}
_, 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, 3, "</h1><p>Zeitraum: <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, 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: 18, Col: 52}
}
_, 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, 4, "</span> - <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, 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: 18, Col: 98}
}
_, 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, 5, "</span></p><p>Arbeitszeit: <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, 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: 19, Col: 58}
}
_, 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, 6, "</span></p><p>Überstunden: <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, 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: 20, Col: 59}
}
_, 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, 7, "</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_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(kw)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 23, Col: 52}
}
_, 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, 8, "</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_Var9 = []any{noBorder}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<p class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).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_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, 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: 36, Col: 59}
}
_, 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, 11, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 = []any{"grid grid-cols-subgrid col-span-3 " + noBorder}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).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_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">")
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, 14, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, 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: 43, Col: 64}
}
_, 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, 15, "</p><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, 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: 44, 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, 16, "</p><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, 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: 45, Col: 55}
}
_, 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, 17, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if workDay.IsKurzArbeit() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<p class=\"col-span-full\">Kurzarbeit</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
} else {
absentDay, _ := day.(*models.Absence)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"col-span-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, 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: 54, Col: 62}
}
_, 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, 21, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</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, 23, " ")
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, 24, " ")
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, 25, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if day.Date().Weekday() == time.Friday {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<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, 27, "</div></content>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func ColorDuration(d time.Duration, classes string) 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_Var18 := templ.GetChildren(ctx)
if templ_7745c5c3_Var18 == nil {
templ_7745c5c3_Var18 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
color := ""
if d.Abs() < time.Minute {
color = "text-neutral-300"
}
var templ_7745c5c3_Var19 = []any{color + " " + classes}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<p class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var19).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_Var20))
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
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDurationFill(d, true))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 76, Col: 72}
}
_, 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, 30, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -1,65 +1,165 @@
package templates package templates
import ( import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
) )
templ weekDayComponent(user models.User, day models.WorkDay) { templ weekPicker(weekStart time.Time) {
{{ work, pause := day.GetWorkTimeString() }} {{
year, kw := weekStart.ISOWeek()
}}
<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) }/>
<button onclick={ templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "-1") } class="btn">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="chevron-left size-4 mx-auto" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0"></path>
</svg>
</button>
<p class="whitespace-nowrap">KW { fmt.Sprintf("%02d, %d", kw, year) }</p>
<button disabled?={ time.Since(weekStart) < 24*7*time.Hour } onclick={ templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "1") } class="btn disabled:pointer-events-none disabled:opacity-50">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="chevron-right size-4 mx-auto" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708"></path>
</svg>
</button>
</form>
}
templ defaultWeekDayComponent(u models.User, day models.IWorkDay) {
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
@timeGaugeComponent(day.GetWorkDayProgress(user), false, false) @timeGaugeComponent(day.GetDayProgress(u), false)
<div class="flex flex-col"> <div class="flex flex-col">
<p class=""><span class="font-bold uppercase hidden md:inline">{ day.Day.Format("Mon") }:</span> { day.Day.Format("02.01.2006") }</p> <p class=""><span class="font-bold uppercase hidden md:inline">{ day.Date().Format("Mon") }:</span> { day.Date().Format("02.01.2006") }</p>
<div class="flex flex-row gap-2"> if day.IsWorkDay() {
<span class="text-accent">{ work }</span> {{
<span class="text-neutral-500">{ pause }</span> workDay, _ := day.(*models.WorkDay)
</div> work, pause, _ := workDay.GetAllWorkTimesReal(u)
<div class="flex flex-row gap-2 items-center"> }}
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="size-4" viewBox="0 0 16 16"> if !workDay.RequiresAction() {
<path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71z"></path> <div class="flex flex-row gap-2">
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0"></path> <span class="text-accent">{ helper.FormatDuration(work) }</span>
</svg> <span class="text-neutral-500">{ helper.FormatDuration(pause) }</span>
if day.Absence.Datum.Equal(day.Day) { </div>
<p>{ day.Absence.AbwesenheitTyp.Name }</p> <div class="flex flex-row gap-2 items-center">
} else if !day.TimeFrom.Equal(day.TimeTo) { <span class="icon-[material-symbols-light--schedule-outline] flex-shrink-0"></span>
<span>{ day.TimeFrom.Format("15:04") }</span> switch {
<span>-</span> case !workDay.TimeFrom.Equal(workDay.TimeTo):
<span>{ day.TimeTo.Format("15:04") }</span> <span>{ workDay.TimeFrom.Format("15:04") }</span>
<span>-</span>
<span>{ workDay.TimeTo.Format("15:04") }</span>
default:
<p>Keine Anwesenheit</p>
}
</div>
} else { } else {
<p>Keine Anwesenheit</p> <p class="text-red-600">Bitte anpassen</p>
} }
</div> } else {
{{
absentDay, _ := day.(*models.Absence)
}}
<div>{ absentDay.AbwesenheitTyp.Name } </div>
}
</div> </div>
</div> </div>
} }
templ employeComponent(week models.WorkWeek) { templ weekDayComponent(user models.User, day models.WorkDay) {
{{ // {{ work, pause, _ := day.GetAllWorkTimesReal(user) }}
year, kw := week.WeekStart.ISOWeek() <div class="flex flex-row gap-2">
}} // @timeGaugeComponent(day.GetWorkDayProgress(user), false, day.RequiresAction())
<div class="employeComponent grid-sub divide-x-1"> <div class="flex flex-col">
<div class="grid-cell"> if !day.RequiresAction() {
<p class="font-bold uppercase">{ week.User.Vorname } { week.User.Name }</p>
<p class="text-sm">Arbeitszeit</p>
<p class="text-accent">{ week.GetWorkHourString() }</p>
</div>
<div class="grid-cell col-span-3 flex flex-col gap-2">
for _, day := range week.WorkDays {
@weekDayComponent(week.User, day)
} }
</div> </div>
<form class="grid-cell flex flex-col justify-between gap-2" method="post"> </div>
<p class="text-sm"><span class="">Woche:</span> { fmt.Sprintf("%02d-%d", kw, year) }</p> }
<input type="hidden" name="method" value="accept"/>
<input type="hidden" name="user" value={ strconv.Itoa(week.User.PersonalNummer) }/> templ workWeekComponent(week models.WorkWeek, onlyAccept bool) {
<input type="hidden" name="week" value={ week.WeekStart.Format(time.DateOnly) }/> {{
<button type="submit" class="w-full bg-neutral-100 cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-900 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50"> year, kw := week.WeekStart.ISOWeek()
<p class="">Bestätigen</p> progress := (float32(week.Worktime.Hours()) / week.User.ArbeitszeitPerWoche) * 100
</button> }}
</form> <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> </div>
} }

View File

@@ -9,13 +9,14 @@ 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" "strconv"
"time" "time"
) )
func weekDayComponent(user models.User, day models.WorkDay) templ.Component { func weekPicker(weekStart time.Time) 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 {
@@ -36,127 +37,79 @@ func weekDayComponent(user models.User, day models.WorkDay) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
work, pause := day.GetWorkTimeString()
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"flex flex-row gap-2\">") year, kw := weekStart.ISOWeek()
if templ_7745c5c3_Err != nil { 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=\"")
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = timeGaugeComponent(day.GetWorkDayProgress(user), false, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"flex flex-col\"><p class=\"\"><span class=\"font-bold uppercase hidden md:inline\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(day.Day.Format("Mon")) 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: 15, Col: 89} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 16, 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 {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, ":</span> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 string templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "-1"))
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(day.Day.Format("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 15, Col: 130}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
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, "</p><div class=\"flex flex-row gap-2\"><span class=\"text-accent\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<button onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.ComponentScript = templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "-1")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" class=\"btn\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" class=\"chevron-left size-4 mx-auto\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0\"></path></svg></button><p class=\"whitespace-nowrap\">KW ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(work) 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: 17, Col: 36} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 22, 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 {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span> <span class=\"text-neutral-500\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var5 string templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "1"))
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(pause)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 18, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
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, "</span></div><div class=\"flex flex-row gap-2 items-center\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" class=\"size-4\" viewBox=\"0 0 16 16\"><path d=\"M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71z\"></path> <path d=\"M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0\"></path></svg> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<button")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if day.Absence.Datum.Equal(day.Day) { if time.Since(weekStart) < 24*7*time.Hour {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " disabled")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(day.Absence.AbwesenheitTyp.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 26, Col: 41}
}
_, 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, 8, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if !day.TimeFrom.Equal(day.TimeTo) {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(day.TimeFrom.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 28, Col: 41}
}
_, 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, 10, "</span> <span>-</span> <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(day.TimeTo.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 30, Col: 39}
}
_, 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, 11, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<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, 13, "</div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.ComponentScript = templ.JSFuncCall("navigateWeek", templ.JSExpression("this"), templ.JSExpression("event"), "1")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"btn disabled:pointer-events-none disabled:opacity-50\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" class=\"chevron-right size-4 mx-auto\" viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708\"></path></svg></button></form>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -164,7 +117,7 @@ func weekDayComponent(user models.User, day models.WorkDay) templ.Component {
}) })
} }
func employeComponent(week models.WorkWeek) templ.Component { func defaultWeekDayComponent(u models.User, day models.IWorkDay) 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 {
@@ -180,102 +133,498 @@ func employeComponent(week models.WorkWeek) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var9 := templ.GetChildren(ctx) templ_7745c5c3_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var9 == nil { if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var9 = 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 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, 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(day.Date().Format("Mon"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 35, Col: 92}
}
_, 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: 136}
}
_, 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() {
year, kw := week.WeekStart.ISOWeek() workDay, _ := day.(*models.WorkDay)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"employeComponent grid-sub divide-x-1\"><div class=\"grid-cell\"><p class=\"font-bold uppercase\">") work, pause, _ := workDay.GetAllWorkTimesReal(u)
if templ_7745c5c3_Err != nil { if !workDay.RequiresAction() {
return templ_7745c5c3_Err 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 {
var templ_7745c5c3_Var10 string return templ_7745c5c3_Err
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(week.User.Vorname) }
if templ_7745c5c3_Err != nil { var templ_7745c5c3_Var9 string
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 45, Col: 53} templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(work))
} if templ_7745c5c3_Err != nil {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 43, Col: 61}
if templ_7745c5c3_Err != nil { }
return templ_7745c5c3_Err _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
} if templ_7745c5c3_Err != nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " ") return templ_7745c5c3_Err
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 {
var templ_7745c5c3_Var11 string return templ_7745c5c3_Err
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(week.User.Name) }
if templ_7745c5c3_Err != nil { var templ_7745c5c3_Var10 string
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 45, Col: 72} templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(pause))
} if templ_7745c5c3_Err != nil {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 44, Col: 67}
if templ_7745c5c3_Err != nil { }
return templ_7745c5c3_Err _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
} if templ_7745c5c3_Err != nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</p><p class=\"text-sm\">Arbeitszeit</p><p class=\"text-accent\">") return templ_7745c5c3_Err
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 {
var templ_7745c5c3_Var12 string return templ_7745c5c3_Err
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(week.GetWorkHourString()) }
if templ_7745c5c3_Err != nil { switch {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 47, Col: 52} case !workDay.TimeFrom.Equal(workDay.TimeTo):
} templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err
return templ_7745c5c3_Err }
} var templ_7745c5c3_Var11 string
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</p></div><div class=\"grid-cell col-span-3 flex flex-col gap-2\">") templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.TimeFrom.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/teamComponents.templ`, Line: 50, Col: 48}
} }
for _, day := range week.WorkDays { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
templ_7745c5c3_Err = weekDayComponent(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, 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 {
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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div><form class=\"grid-cell flex flex-col justify-between gap-2\" method=\"post\"><p class=\"text-sm\"><span class=\"\">Woche:</span> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var13 string return nil
templ_7745c5c3_Var13, 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: 55, Col: 85}
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_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</p><input type=\"hidden\" name=\"method\" value=\"accept\"> <input type=\"hidden\" name=\"user\" value=\"") if !day.RequiresAction() {
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var14 string return nil
templ_7745c5c3_Var14, 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: 57, Col: 82}
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_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) 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.Worktime.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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"> <input type=\"hidden\" name=\"week\" value=\"") 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(week.WeekStart.Format(time.DateOnly)) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(week.User.Vorname)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 58, Col: 80} 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_Var15)) _, 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, 21, "\"> <button type=\"submit\" class=\"w-full bg-neutral-100 cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-900 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\"><p class=\"\">Bestätigen</p></button></form></div>") 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 {
case week.RequiresAction():
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<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, 56, "<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, 57, "<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, 58, "<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, 59, "<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, 60, " <button")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if week.Status < models.WeekStatusSent {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " disabled")
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 {
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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -299,53 +648,53 @@ func userPresenceComponent(user models.User, present bool) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var16 := templ.GetChildren(ctx) templ_7745c5c3_Var24 := templ.GetChildren(ctx)
if templ_7745c5c3_Var16 == nil { if templ_7745c5c3_Var24 == nil {
templ_7745c5c3_Var16 = templ.NopComponent templ_7745c5c3_Var24 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"grid-cell group flex flex-row gap-2\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<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, 23, "<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, 67, "<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, 24, "<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, 68, "<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, 25, "<p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var17 string var templ_7745c5c3_Var25 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname) templ_7745c5c3_Var25, 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: 73, Col: 19} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 173, Col: 19}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) _, 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, 26, " ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, " ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var18 string var templ_7745c5c3_Var26 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name) templ_7745c5c3_Var26, 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: 73, Col: 33} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 173, Col: 33}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) _, 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
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</p></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</p></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@@ -1,136 +1,12 @@
package templates package templates
import ( import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models" "arbeitszeitmessung/models"
"fmt" "fmt"
"net/url"
"strconv" "strconv"
"time" "time"
) )
templ inputForm() {
{{
urlParams := ctx.Value("urlParams").(url.Values)
user := ctx.Value("user").(models.User)
}}
<div class="grid-sub divide-x-1 bg-neutral-300 max-md:flex max-md:flex-col">
<div class="grid-cell md:col-span-1 max-md:grid grid-cols-2">
<p class="font-bold uppercase">{ user.Vorname + " " + user.Name }</p>
<div class="justify-self-end">
<p class="text-sm">Überstunden</p>
<p class="text-accent">0h 0min (statisch)</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="w-full bg-neutral-100 placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-0 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-neutral-400 hover:border-neutral-300" placeholder="Zeitraum von..."/>
<input type="date" value={ urlParams.Get("time_to") } name="time_to" class="w-full bg-neutral-100 placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-0 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-neutral-400 hover:border-neutral-300" placeholder="Zeitraum bis..."/>
</div>
</form>
<div class="grid-cell content-end">
<button type="submit" form="timeRangeForm" class="w-full bg-neutral-100 cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-900 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">
<p class="">Anzeigen</p>
</button>
</div>
</div>
}
templ dayComponent(workDay models.WorkDay) {
{{
work, pause := workDay.GetWorkTimeString()
user := ctx.Value("user").(models.User)
overtime := helper.FormatDuration(workDay.CalcOvertime(user))
justify := ""
if len(workDay.Bookings) <= 1 {
justify = "justify-content: center"
}
}}
<div class="grid-sub divide-x-1 hover:bg-neutral-200 transition-colors">
<div class="grid-cell md:col-span-1 flex flex-row gap-2">
@timeGaugeComponent(workDay.GetWorkDayProgress(ctx.Value("user").(models.User)), workDay.Day.Equal(time.Now().Truncate(24*time.Hour)), workDay.RequiresAction())
<div>
<p class=""><span class="font-bold uppercase hidden md:inline">{ workDay.Day.Format("Mon") }:</span> { workDay.Day.Format("02.01.2006") }</p>
if work!="" {
<p class=" text-sm mt-1">Arbeitszeit:</p>
if (workDay.RequiresAction()) {
<p class="text-red-600">Bitte anpassen</p>
} else {
<p class=" text-accent">{ work }</p>
}
<p class="text-neutral-500">{ pause }</p>
<p class="text-neutral-500">{ overtime }</p>
}
</div>
</div>
<div class="all-booking-component flex flex-row md:col-span-3 gap-2 w-full grid-cell">
@lineComponent()
<form id={ "time-" + workDay.Day.Format("2006-01-02") } class="flex flex-col gap-2 group w-full justify-between" style={ justify } method="post">
if (workDay.Absence != models.Absence{}) {
<p>{ workDay.Absence.AbwesenheitTyp.Name }</p>
}
if len(workDay.Bookings) < 1 && (workDay.Absence == models.Absence{}) {
<p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p>
@absenceComponent(workDay)
@newBookingComponent(workDay)
} else {
@absenceComponent(workDay)
for _, booking := range workDay.Bookings {
@bookingComponent(booking)
}
@newBookingComponent(workDay)
}
<input type="hidden" name="action" value="change"/> <!-- default action value for ändern button -->
</form>
</div>
<div class="grid-cell">
@changeButtonComponent("time-" + workDay.Day.Format("2006-01-02"))
</div>
</div>
}
templ changeButtonComponent(id string) {
<button class="cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-900 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50 group" type="submit" onclick={ templ.JSFuncCall("editDay", templ.JSExpression("this"), templ.JSExpression("event"), id) }>
<p class="hidden md:block group-[.edit]:hidden">Ändern</p>
<p class="hidden group-[.edit]: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]: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]: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 timeGaugeComponent(progress uint8, today bool, warning bool) {
{{
var bgColor string
switch {
case (warning):
bgColor = "bg-red-600"
break
case (progress > 0 && progress < 90):
bgColor = "bg-orange-500"
break
case (90 <= progress && progress <= 110):
bgColor = "bg-accent"
break
case (progress > 110):
bgColor = "bg-purple-600"
break
default:
bgColor = "bg-neutral-400"
break
}
}}
if today {
<div class="flex-start flex w-2 h-full overflow-hidden rounded-full bg-neutral-300 print:hidden">
<div class={ "flex w-full items-center justify-center overflow-hidden rounded-full", bgColor } style={ fmt.Sprintf("height: %d%%", int(progress)) }></div>
</div>
} else {
<div class={ "w-2 h-full bg-accent rounded-md", bgColor }></div>
}
}
templ lineComponent() { templ lineComponent() {
<div class="flex flex-col w-2 py-2 items-center text-accent print:hidden"> <div class="flex flex-col w-2 py-2 items-center text-accent print:hidden">
<svg class="size-2" viewBox="0 0 24 24" fill="currentColor"> <svg class="size-2" viewBox="0 0 24 24" fill="currentColor">
@@ -143,40 +19,105 @@ templ lineComponent() {
</div> </div>
} }
templ absenceComponent(d models.WorkDay) { templ changeButtonComponent(id string, workDay bool) {
<div class="no-booking-component hidden group-[.edit]:flex flex-col gap-2 align-center"> <button class="change-button-component btn w-auto group/button" type="button" onclick={ templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), id, workDay) }>
<select name="absence" onchange={ templ.JSFuncCall("editAbwesenheit", templ.JSExpression("this"), templ.JSExpression("event")) } class="grow cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm transition-colors border-neutral-900" disabled> <p class="hidden md:block group-[.edit]/button:hidden">Ändern</p>
<option value="0">Abwesenheit?</option> <p class="hidden group-[.edit]/button:md:block">Absenden</p>
for _, absence := range models.GetAbsenceTypesCached() { <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4 md:hidden">
<option value={ strconv.Itoa(int(absence.Id)) }>{ absence.Name }</option> <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>
</select> </svg>
</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>
}
templ timeGaugeComponent(progress int8, today bool) {
{{
var bgColor string
switch {
case (0 > progress):
bgColor = "bg-red-600"
break
case (progress > 0 && progress < 95):
bgColor = "bg-orange-500"
break
case (95 <= progress && progress <= 105):
bgColor = "bg-accent"
break
case (progress > 105):
bgColor = "bg-purple-600"
break
default:
bgColor = "bg-neutral-400"
break
}
}}
if today {
<div class="flex-start flex w-2 h-full overflow-hidden rounded-full bg-neutral-300 print:hidden">
<div class={ "flex w-full items-center justify-center overflow-hidden rounded-full", bgColor } style={ fmt.Sprintf("height: %d%%", int(progress)) }></div>
</div>
} else {
<div class={ "w-2 h-full bg-accent rounded-md flex-shrink-0", bgColor }></div>
}
}
templ newAbsenceComponent() {
<div class="no-booking-component hidden group-[.edit]:flex flex-col gap-2 align-center ">
<button type="button" name="absence" onclick={ templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), 0, false) } class="btn border-neutral-500">
Neue Abwesenheit
</button>
</div> </div>
} }
templ newBookingComponent(d models.WorkDay) { templ absenceComponent(a *models.Absence, isKurzarbeit bool) {
<div class="new-booking-component hidden group-[.edit]:flex flex-row gap-2 items-center"> {{
<button name="action" value="add" type="submit" class="hover:text-accent cursor-pointer"> editBox := ""
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="size-4 transition-colors" viewBox="0 0 16 16"> if isKurzarbeit {
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"></path> editBox = "edit-box"
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"></path> }
</svg> }}
</button> <div class={ "flex flex-row items-center gap-2", editBox }>
<input name="timestamp" type="time" value={ time.Now().Format("15:04") } class="text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm border border-neutral-200 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-neutral-400 hover:border-neutral-300"/> <input type="hidden" name="date_from" value={ a.DateFrom.Format("2006-01-02") }/>
<input type="hidden" name="date_to" value={ a.DateTo.Format("2006-01-02") }/>
<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">
{ a.AbwesenheitTyp.Name }
if a.IsMultiDay() {
<span class="text-neutral-500">bis { a.DateTo.Format("02.01.2006") }</span>
}
</p>
<div class="w-full"></div>
if isKurzarbeit {
<button type="button" onclick={ templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), "time-"+a.Date().Format("2006-01-02"), false) } class="hidden btn border-0 rounded-none grow-0 w-auto group-[.edit]:inline">Bearbeiten</button>
}
</div>
}
templ newBookingComponent(d *models.WorkDay) {
<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={ 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("2006-01-02") }/> <input name="date" type="hidden" value={ d.Day.Format("2006-01-02") }/>
<select name="check_in_out"> <div class="relative">
<option value="0" disabled>Kommen/Gehen</option> <select class="cursor-pointer appearance-none" name="check_in_out">
<option value="3" selected?={ len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 0 }>Kommen</option> <option value="0" disabled>Kommen/Gehen</option>
<option value="4" selected?={ len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 1 }>Gehen</option> <option value="3" selected?={ len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 0 }>Kommen</option>
</select> <option value="4" selected?={ len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 1 }>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> </div>
} }
templ bookingComponent(booking models.Booking) { templ bookingComponent(booking models.Booking) {
<div> <div>
<p class="text-neutral-500"> <p class="text-neutral-500 edit-box">
<span class="text-neutral-700 group-[.edit]:hidden inline">{ booking.Timestamp.Format("15:04") }</span> <span class="text-black group-[.edit]:hidden inline">{ booking.Timestamp.Format("15:04") }</span>
<input disabled name={ "booking_" + strconv.Itoa(booking.CounterId) } type="time" value={ booking.Timestamp.Format("15:04") } class="text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm border border-neutral-200 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-neutral-400 hover:border-neutral-300"/> <input disabled name={ "booking_" + strconv.Itoa(booking.CounterId) } type="time" value={ booking.Timestamp.Format("15:04") } class="text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm px-3 py-2 cursor-pointer"/>
{ booking.GetBookingType() } { booking.GetBookingType() }
</p> </p>
</div> </div>

View File

@@ -9,15 +9,13 @@ 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"
"net/url"
"strconv" "strconv"
"time" "time"
) )
func inputForm() templ.Component { func lineComponent() 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 {
@@ -38,57 +36,7 @@ func inputForm() 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-col w-2 py-2 items-center text-accent print:hidden\"><svg class=\"size-2\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><polygon points=\"12,2 22,12 12,22 2,12\"></polygon></svg><div class=\"w-[2px] bg-accent flex-grow -my-1\"></div><svg class=\"size-2\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><polygon points=\"12,2 22,12 12,22 2,12\"></polygon></svg></div>")
urlParams := ctx.Value("urlParams").(url.Values)
user := ctx.Value("user").(models.User)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"grid-sub divide-x-1 bg-neutral-300 max-md:flex max-md:flex-col\"><div class=\"grid-cell md:col-span-1 max-md:grid grid-cols-2\"><p class=\"font-bold uppercase\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname + " " + user.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 19, Col: 66}
}
_, 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, "</p><div class=\"justify-self-end\"><p class=\"text-sm\">Überstunden</p><p class=\"text-accent\">0h 0min (statisch)</p></div></div><form id=\"timeRangeForm\" method=\"GET\" class=\"grid-cell flex flex-row md:col-span-3 gap-2 \">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = lineComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"flex flex-col gap-2 justify-between grow-1\"><input type=\"date\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_from"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 28, Col: 57}
}
_, 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, 4, "\" name=\"time_from\" class=\"w-full bg-neutral-100 placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-0 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-neutral-400 hover:border-neutral-300\" placeholder=\"Zeitraum von...\"> <input type=\"date\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_to"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 29, Col: 55}
}
_, 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, 5, "\" name=\"time_to\" class=\"w-full bg-neutral-100 placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-0 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-neutral-400 hover:border-neutral-300\" placeholder=\"Zeitraum bis...\"></div></form><div class=\"grid-cell content-end\"><button type=\"submit\" form=\"timeRangeForm\" class=\"w-full bg-neutral-100 cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-900 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\"><p class=\"\">Anzeigen</p></button></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -96,7 +44,66 @@ func inputForm() templ.Component {
}) })
} }
func dayComponent(workDay models.WorkDay) templ.Component { func changeButtonComponent(id string, workDay 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)
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), id, workDay))
if templ_7745c5c3_Err != nil {
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=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.ComponentScript = templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), id, workDay)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3.Call)
if templ_7745c5c3_Err != nil {
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> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("clearEditState"))
if templ_7745c5c3_Err != nil {
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=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.ComponentScript = templ.JSFuncCall("clearEditState")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><span class=\"size-5 icon-[material-symbols-light--cancel-outline]\"></span></button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func timeGaugeComponent(progress int8, today 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 {
@@ -118,297 +125,18 @@ func dayComponent(workDay models.WorkDay) templ.Component {
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
work, pause := workDay.GetWorkTimeString()
user := ctx.Value("user").(models.User)
overtime := helper.FormatDuration(workDay.CalcOvertime(user))
justify := ""
if len(workDay.Bookings) <= 1 {
justify = "justify-content: center"
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"grid-sub divide-x-1 hover:bg-neutral-200 transition-colors\"><div class=\"grid-cell md:col-span-1 flex flex-row gap-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = timeGaugeComponent(workDay.GetWorkDayProgress(ctx.Value("user").(models.User)), workDay.Day.Equal(time.Now().Truncate(24*time.Hour)), workDay.RequiresAction()).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div><p class=\"\"><span class=\"font-bold uppercase hidden md:inline\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Day.Format("Mon"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 54, Col: 94}
}
_, 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, 8, ":</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Day.Format("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 54, Col: 139}
}
_, 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, 9, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if work != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<p class=\" text-sm mt-1\">Arbeitszeit:</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if workDay.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p class=\"text-red-600\">Bitte anpassen</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<p class=\" text-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(work)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 60, Col: 36}
}
_, 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
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " <p 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(pause)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 62, Col: 40}
}
_, 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, "</p><p 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(overtime)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 63, Col: 43}
}
_, 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, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div></div><div class=\"all-booking-component flex flex-row md:col-span-3 gap-2 w-full grid-cell\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = lineComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<form id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs("time-" + workDay.Day.Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.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, 19, "\" class=\"flex flex-col gap-2 group w-full justify-between\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(justify)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 69, Col: 131}
}
_, 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, 20, "\" method=\"post\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if (workDay.Absence != models.Absence{}) {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Absence.AbwesenheitTyp.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 71, Col: 45}
}
_, 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, 22, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(workDay.Bookings) < 1 && (workDay.Absence == models.Absence{}) {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<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 = absenceComponent(workDay).Render(ctx, templ_7745c5c3_Buffer)
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
}
templ_7745c5c3_Err = newBookingComponent(workDay).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = absenceComponent(workDay).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, 25, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = newBookingComponent(workDay).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<input type=\"hidden\" name=\"action\" value=\"change\"><!-- default action value for ändern button --></form></div><div class=\"grid-cell\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = changeButtonComponent("time-"+workDay.Day.Format("2006-01-02")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func changeButtonComponent(id string) 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 = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("editDay", templ.JSExpression("this"), templ.JSExpression("event"), id))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<button class=\"cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-900 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50 group\" type=\"submit\" onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 templ.ComponentScript = templ.JSFuncCall("editDay", templ.JSExpression("this"), templ.JSExpression("event"), id)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\"><p class=\"hidden md:block group-[.edit]:hidden\">Ändern</p><p class=\"hidden group-[.edit]: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]: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]: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 {
return templ_7745c5c3_Err
}
return nil
})
}
func timeGaugeComponent(progress uint8, today bool, warning 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_Var16 := templ.GetChildren(ctx)
if templ_7745c5c3_Var16 == nil {
templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var bgColor string var bgColor string
switch { switch {
case (warning): case (0 > progress):
bgColor = "bg-red-600" bgColor = "bg-red-600"
break break
case (progress > 0 && progress < 90): case (progress > 0 && progress < 95):
bgColor = "bg-orange-500" bgColor = "bg-orange-500"
break break
case (90 <= progress && progress <= 110): case (95 <= progress && progress <= 105):
bgColor = "bg-accent" bgColor = "bg-accent"
break break
case (progress > 110): case (progress > 105):
bgColor = "bg-purple-600" bgColor = "bg-purple-600"
break break
default: default:
@@ -416,65 +144,65 @@ func timeGaugeComponent(progress uint8, today bool, warning bool) templ.Componen
break break
} }
if today { if today {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<div class=\"flex-start flex w-2 h-full overflow-hidden rounded-full bg-neutral-300 print:hidden\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"flex-start flex w-2 h-full overflow-hidden rounded-full bg-neutral-300 print:hidden\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var17 = []any{"flex w-full items-center justify-center overflow-hidden rounded-full", bgColor} var templ_7745c5c3_Var6 = []any{"flex w-full items-center justify-center overflow-hidden rounded-full", bgColor}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...) templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, 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, 31, "<div class=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var18 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var17).String()) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 1, Col: 0} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 1, Col: 0}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) _, 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, 32, "\" style=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" style=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var19 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("height: %d%%", int(progress))) templ_7745c5c3_Var8, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("height: %d%%", int(progress)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 127, Col: 149} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 57, Col: 149}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) _, 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, 33, "\"></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} else { } else {
var templ_7745c5c3_Var20 = []any{"w-2 h-full bg-accent rounded-md", bgColor} var templ_7745c5c3_Var9 = []any{"w-2 h-full bg-accent rounded-md flex-shrink-0", bgColor}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...) templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
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, "<div class=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"")
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_Var10 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var20).String()) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).String())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 1, Col: 0} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 1, Col: 0}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) _, 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, 35, "\"></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\"></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -483,7 +211,7 @@ func timeGaugeComponent(progress uint8, today bool, warning bool) templ.Componen
}) })
} }
func lineComponent() templ.Component { func newAbsenceComponent() 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 {
@@ -499,12 +227,29 @@ func lineComponent() templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var22 := templ.GetChildren(ctx) templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var22 == nil { if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var22 = templ.NopComponent templ_7745c5c3_Var11 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<div class=\"flex flex-col w-2 py-2 items-center text-accent print:hidden\"><svg class=\"size-2\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><polygon points=\"12,2 22,12 12,22 2,12\"></polygon></svg><div class=\"w-[2px] bg-accent flex-grow -my-1\"></div><svg class=\"size-2\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><polygon points=\"12,2 22,12 12,22 2,12\"></polygon></svg></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"no-booking-component hidden group-[.edit]:flex flex-col gap-2 align-center \">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), 0, false))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<button type=\"button\" name=\"absence\" onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 templ.ComponentScript = templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), 0, false)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12.Call)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" class=\"btn border-neutral-500\">Neue Abwesenheit</button></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -512,7 +257,170 @@ func lineComponent() templ.Component {
}) })
} }
func absenceComponent(d models.WorkDay) templ.Component { func absenceComponent(a *models.Absence, isKurzarbeit 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_Var13 := templ.GetChildren(ctx)
if templ_7745c5c3_Var13 == nil {
templ_7745c5c3_Var13 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
editBox := ""
if isKurzarbeit {
editBox = "edit-box"
}
var templ_7745c5c3_Var14 = []any{"flex flex-row items-center gap-2", editBox}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var14).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 1, Col: 0}
}
_, 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, 16, "\"><input type=\"hidden\" name=\"date_from\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateFrom.Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 80, Col: 79}
}
_, 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, 17, "\"> <input type=\"hidden\" name=\"date_to\" value=\"")
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("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 81, Col: 75}
}
_, 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 {
return templ_7745c5c3_Err
}
if a.IsMultiDay() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<span class=\"text-neutral-500\">bis ")
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("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 87, Col: 70}
}
_, 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, 23, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p><div class=\"w-full\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if isKurzarbeit {
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), "time-"+a.Date().Format("2006-01-02"), false))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<button type=\"button\" onclick=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 templ.ComponentScript = templ.JSFuncCall("editWorkday", templ.JSExpression("this"), templ.JSExpression("event"), "time-"+a.Date().Format("2006-01-02"), false)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22.Call)
if templ_7745c5c3_Err != nil {
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>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func newBookingComponent(d *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 {
@@ -533,135 +441,53 @@ func absenceComponent(d models.WorkDay) templ.Component {
templ_7745c5c3_Var23 = templ.NopComponent templ_7745c5c3_Var23 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<div class=\"no-booking-component hidden group-[.edit]:flex flex-col gap-2 align-center\">") 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=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.JSFuncCall("editAbwesenheit", templ.JSExpression("this"), templ.JSExpression("event"))) var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(time.Now().Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 99, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
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, "<select name=\"absence\" onchange=\"") 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=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var24 templ.ComponentScript = templ.JSFuncCall("editAbwesenheit", templ.JSExpression("this"), templ.JSExpression("event")) var templ_7745c5c3_Var25 string
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24.Call) templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(d.Day.Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 100, Col: 69}
}
_, 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, 39, "\" class=\"grow cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm transition-colors border-neutral-900\" disabled><option value=\"0\">Abwesenheit?</option> ") 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\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, absence := range models.GetAbsenceTypesCached() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<option value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(absence.Id)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 151, Col: 49}
}
_, 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, 41, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(absence.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 151, Col: 66}
}
_, 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, 42, "</option>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</select></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func newBookingComponent(d 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_Var27 := templ.GetChildren(ctx)
if templ_7745c5c3_Var27 == nil {
templ_7745c5c3_Var27 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"new-booking-component hidden group-[.edit]:flex flex-row gap-2 items-center\"><button name=\"action\" value=\"add\" type=\"submit\" class=\"hover:text-accent cursor-pointer\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" class=\"size-4 transition-colors\" viewBox=\"0 0 16 16\"><path d=\"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16\"></path> <path d=\"M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4\"></path></svg></button> <input name=\"timestamp\" type=\"time\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(time.Now().Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 165, Col: 72}
}
_, 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, 45, "\" class=\"text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm border border-neutral-200 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-neutral-400 hover:border-neutral-300\"> <input name=\"date\" type=\"hidden\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(d.Day.Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 166, Col: 69}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\"> <select name=\"check_in_out\"><option value=\"0\" disabled>Kommen/Gehen</option> <option value=\"3\"")
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 { if len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " selected") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " 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, 48, ">Kommen</option> <option value=\"4\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, ">Kommen</option> <option value=\"4\"")
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 { if len(d.Bookings) > 0 && d.Bookings[len(d.Bookings)-1].CheckInOut%2 == 1 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, " selected") 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, 50, ">Gehen</option></select></div>") 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>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -685,64 +511,64 @@ func bookingComponent(booking models.Booking) templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var30 := templ.GetChildren(ctx) templ_7745c5c3_Var26 := templ.GetChildren(ctx)
if templ_7745c5c3_Var30 == nil { if templ_7745c5c3_Var26 == nil {
templ_7745c5c3_Var30 = templ.NopComponent templ_7745c5c3_Var26 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<div><p class=\"text-neutral-500\"><span class=\"text-neutral-700 group-[.edit]:hidden inline\">") 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\">")
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_Var27 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(booking.Timestamp.Format("15:04")) templ_7745c5c3_Var27, 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: 178, Col: 97} 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_Var31)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
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, 52, "</span> <input disabled name=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</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_Var32 string var templ_7745c5c3_Var28 string
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs("booking_" + strconv.Itoa(booking.CounterId)) templ_7745c5c3_Var28, 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: 179, Col: 70} 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_Var32)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
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, 53, "\" type=\"time\" value=\"") 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_Var33 string var templ_7745c5c3_Var29 string
templ_7745c5c3_Var33, 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: 179, Col: 126} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 120, Col: 126}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) _, 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, 54, "\" class=\"text-neutral-700 group-[.edit]:inline hidden bg-neutral-100 text-sm border border-neutral-200 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none focus:border-neutral-400 hover:border-neutral-300\"> ") 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\"> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var34 string var templ_7745c5c3_Var30 string
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(booking.GetBookingType()) templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(booking.GetBookingType())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 180, Col: 29} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 121, Col: 29}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) _, 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, 55, "</p></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</p></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -766,12 +592,12 @@ func LegendComponent() templ.Component {
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var35 := templ.GetChildren(ctx) templ_7745c5c3_Var31 := templ.GetChildren(ctx)
if templ_7745c5c3_Var35 == nil { if templ_7745c5c3_Var31 == nil {
templ_7745c5c3_Var35 = templ.NopComponent templ_7745c5c3_Var31 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<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, 40, "<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

@@ -0,0 +1,166 @@
package templates
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"net/url"
"strconv"
"time"
)
templ TimePage(workDays []models.WorkDay, lastSub time.Time) {
{{
allDays := ctx.Value("days").([]models.IWorkDay)
}}
@Base()
@headerComponent()
<div class="grid-main divide-y-1">
@inputForm()
for _, day := range allDays {
@defaultDayComponent(day)
if (day.Date().Weekday() == time.Monday) {
<div class="grid-sub responsive bg-neutral-300 h-2"></div>
}
}
</div>
@LegendComponent()
}
templ inputForm() {
{{
urlParams := ctx.Value("urlParams").(url.Values)
user := ctx.Value("user").(models.User)
}}
<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">{ user.Vorname + " " + user.Name }</p>
<div class="justify-self-end">
<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>
<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) {
{{
user := ctx.Value("user").(models.User)
justify := "justify-center"
if day.IsWorkDay() && len(day.(*models.WorkDay).Bookings) > 1 {
justify = "justify-between"
}
}}
<div class={ "grid-sub divide-x-1 hover:bg-neutral-200 transition-colors group" }>
<div class="grid-cell md:col-span-1 flex flex-row gap-2">
@timeGaugeComponent(day.GetDayProgress(user), day.Date().Equal(time.Now().Truncate(24*time.Hour)))
<div>
<p>
<span class="font-bold uppercase hidden md:inline">{ day.Date().Format("Mon") }:</span> { day.Date().Format("02.01.2006") }
</p>
if day.IsWorkDay() {
{{
workDay, _ := day.(*models.WorkDay)
work, pause, overtime := workDay.GetAllWorkTimesReal(user)
}}
if day.RequiresAction() {
<p class="text-red-600">Bitte anpassen</p>
} else {
if work > 0 {
<p class=" text-sm mt-1">Arbeitszeit:</p>
<p class="text-accent flex flex-row items-center"><span class="icon-[material-symbols-light--schedule-outline]"></span>{ helper.FormatDuration(work) }</p>
}
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>
}
if overtime != 0 && len(workDay.Bookings) > 0 {
<p class="text-neutral-500 flex flex-row items-center">
<span class="icon-[material-symbols-light--more-time]"></span>
{ helper.FormatDuration(overtime) }
</p>
}
}
}
</div>
</div>
<div class="all-booking-component grid-cell flex flex-row md:col-span-3 col-span-2 gap-2 w-full">
@lineComponent()
<form id={ "time-" + day.Date().Format("2006-01-02") } class={ "flex flex-col gap-2 w-full", justify } method="post">
if day.IsWorkDay() {
{{
workDay, _ := day.(*models.WorkDay)
}}
@newAbsenceComponent()
if len(workDay.Bookings) < 1 {
<p class="text group-[.edit]:hidden">Keine Buchung gefunden. Bitte Arbeitsstunden oder Grund der Abwesenheit eingeben!</p>
}
if workDay.IsKurzArbeit() {
@absenceComponent(workDay.GetKurzArbeit(), true)
}
for _, booking := range workDay.Bookings {
@bookingComponent(booking)
}
@newBookingComponent(workDay)
} else {
{{
absentDay, _ := day.(*models.Absence)
}}
@absenceComponent(absentDay, false)
}
<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">
@changeButtonComponent("time-"+day.Date().Format("2006-01-02"), day.IsWorkDay())
</div>
</div>
}
templ absentInput(a models.Absence) {
<input type="hidden" name="date_from" value={ a.DateFrom.Format("2006-01-02") }/>
<input type="hidden" name="date_to" value={ a.DateTo.Format("2006-01-02") }/>
<input type="hidden" name="aw_type" value={ a.AbwesenheitTyp.Id }/>
<input type="hidden" name="aw_id" value={ a.CounterId }/>
}

View File

@@ -0,0 +1,563 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924
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"
"net/url"
"strconv"
"time"
)
func TimePage(workDays []models.WorkDay, lastSub 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)
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)
allDays := ctx.Value("days").([]models.IWorkDay)
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\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = inputForm().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, day := range allDays {
templ_7745c5c3_Err = defaultDayComponent(day).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if day.Date().Weekday() == time.Monday {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"grid-sub responsive bg-neutral-300 h-2\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = LegendComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func inputForm() 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)
urlParams := ctx.Value("urlParams").(url.Values)
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\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname + " " + user.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 36, Col: 66}
}
_, 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, 6, "</p><div class=\"justify-self-end\"><p class=\"text-sm\">Überstunden</p><p class=\"text-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Overtime)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 39, Col: 42}
}
_, 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, 7, "</p></div></div><form id=\"timeRangeForm\" method=\"GET\" class=\"grid-cell flex flex-row md:col-span-3 gap-2 \">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = lineComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"flex flex-col gap-2 justify-between grow-1\"><input type=\"date\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_from"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 45, Col: 57}
}
_, 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, 9, "\" name=\"time_from\" class=\"btn bg-neutral-100\" placeholder=\"Zeitraum von...\"> <input type=\"date\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_to"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 46, Col: 55}
}
_, 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, 10, "\" 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><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\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, absence := range models.GetAbsenceTypesCached() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<option value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(absence.Id)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 63, Col: 51}
}
_, 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, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(absence.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 63, Col: 68}
}
_, 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, "</option>")
if templ_7745c5c3_Err != nil {
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>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func defaultDayComponent(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_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
user := ctx.Value("user").(models.User)
justify := "justify-center"
if day.IsWorkDay() && len(day.(*models.WorkDay).Bookings) > 1 {
justify = "justify-between"
}
var templ_7745c5c3_Var10 = []any{"grid-sub divide-x-1 hover:bg-neutral-200 transition-colors group"}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var10).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 1, Col: 0}
}
_, 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, 16, "\"><div class=\"grid-cell md:col-span-1 flex flex-row gap-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = timeGaugeComponent(day.GetDayProgress(user), day.Date().Equal(time.Now().Truncate(24*time.Hour))).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div><p><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(day.Date().Format("Mon"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 101, Col: 82}
}
_, 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, 18, ":</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/timePage.templ`, Line: 101, Col: 126}
}
_, 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, 19, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay)
work, pause, overtime := workDay.GetAllWorkTimesReal(user)
if day.RequiresAction() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"text-red-600\">Bitte anpassen</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
if work > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<p class=\" text-sm mt-1\">Arbeitszeit:</p><p class=\"text-accent flex flex-row items-center\"><span class=\"icon-[material-symbols-light--schedule-outline]\"></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(work))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 113, Col: 155}
}
_, 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, 22, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if pause > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<p class=\"text-neutral-500 flex flex-row items-center\"><span class=\"icon-[material-symbols-light--motion-photos-paused-outline]\"></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(pause))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 116, Col: 173}
}
_, 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, 25, "</p>")
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 overtime != 0 && len(workDay.Bookings) > 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> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(helper.FormatDuration(overtime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 121, Col: 41}
}
_, 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, 28, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</div></div><div class=\"all-booking-component grid-cell flex flex-row md:col-span-3 col-span-2 gap-2 w-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = lineComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 = []any{"flex flex-col gap-2 w-full", justify}
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, 30, "<form id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("time-" + day.Date().Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 130, Col: 55}
}
_, 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, 31, "\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, 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/timePage.templ`, Line: 1, Col: 0}
}
_, 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, 32, "\" method=\"post\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if day.IsWorkDay() {
workDay, _ := day.(*models.WorkDay)
templ_7745c5c3_Err = newAbsenceComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(workDay.Bookings) < 1 {
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 {
return templ_7745c5c3_Err
}
if workDay.IsKurzArbeit() {
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 {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = newBookingComponent(workDay).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
absentDay, _ := day.(*models.Absence)
templ_7745c5c3_Err = absenceComponent(absentDay, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
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\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = changeButtonComponent("time-"+day.Date().Format("2006-01-02"), day.IsWorkDay()).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func absentInput(a models.Absence) 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_Var20 := templ.GetChildren(ctx)
if templ_7745c5c3_Var20 == nil {
templ_7745c5c3_Var20 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<input type=\"hidden\" name=\"date_from\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(a.DateFrom.Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 162, Col: 78}
}
_, 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, 40, "\"> <input type=\"hidden\" name=\"date_to\" value=\"")
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("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timePage.templ`, Line: 163, Col: 74}
}
_, 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 {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

27
DB/initdb/01_schema.sql Normal file → Executable file
View File

@@ -3,13 +3,15 @@
-- ---------------------------- -- ----------------------------
DROP TABLE IF EXISTS "anwesenheit"; DROP TABLE IF EXISTS "anwesenheit";
CREATE TABLE "anwesenheit" ( CREATE TABLE "anwesenheit" (
"counter_id" bigserial PRIMARY KEY, "counter_id" bigserial NOT NULL,
"timestamp" timestamptz(6) DEFAULT CURRENT_TIMESTAMP, "timestamp" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
"card_uid" varchar(255), "card_uid" character varying(255) NOT NULL,
"check_in_out" int2, "check_in_out" smallint NOT NULL,
"geraet_id" int2, "geraet_id" smallint NOT NULL,
"manuelle_buchung" bool "anwesenheit_typ" int2 NOT NULL,
PRIMARY KEY ("counter_id")
); );
COMMENT ON COLUMN "anwesenheit"."check_in_out" IS '1=Check In 2=Check Out , 3=Check in Manuell, 4=Check out manuell255=Automatic Check Out'; COMMENT ON COLUMN "anwesenheit"."check_in_out" IS '1=Check In 2=Check Out , 3=Check in Manuell, 4=Check out manuell255=Automatic Check Out';
COMMENT ON COLUMN "anwesenheit"."geraet_id" IS 'ID des Lesegerätes'; COMMENT ON COLUMN "anwesenheit"."geraet_id" IS 'ID des Lesegerätes';
@@ -30,8 +32,8 @@ DROP TABLE IF EXISTS "s_personal_daten";
CREATE TABLE "s_personal_daten" ( CREATE TABLE "s_personal_daten" (
"personal_nummer" int4 NOT NULL PRIMARY KEY, "personal_nummer" int4 NOT NULL PRIMARY KEY,
"aktiv_beschaeftigt" bool, "aktiv_beschaeftigt" bool,
"vorname" varchar(255), "vorname" varchar(255) NOT NULL,
"nachname" varchar(255), "nachname" varchar(255) NOT NULL,
"geburtsdatum" date, "geburtsdatum" date,
"plz" varchar(255), "plz" varchar(255),
"adresse" varchar(255), "adresse" varchar(255),
@@ -39,6 +41,7 @@ CREATE TABLE "s_personal_daten" (
"card_uid" varchar(255), "card_uid" varchar(255),
"hauptbeschaeftigungs_ort" int2, "hauptbeschaeftigungs_ort" int2,
"arbeitszeit_per_tag" float4, "arbeitszeit_per_tag" float4,
"arbeitszeit_per_woche" float4,
"arbeitszeit_min_start" time(6), "arbeitszeit_min_start" time(6),
"arbeitszeit_max_ende" time(6), "arbeitszeit_max_ende" time(6),
"vorgesetzter_pers_nr" int4 "vorgesetzter_pers_nr" int4
@@ -78,6 +81,8 @@ CREATE TABLE "wochen_report" (
"personal_nummer" int4, "personal_nummer" int4,
"woche_start" date, "woche_start" date,
"bestaetigt" bool DEFAULT FALSE, "bestaetigt" bool DEFAULT FALSE,
"arbeitszeit" interval,
"ueberstunden" interval,
UNIQUE ("personal_nummer", "woche_start") UNIQUE ("personal_nummer", "woche_start")
); );
@@ -86,13 +91,15 @@ CREATE TABLE "abwesenheit" (
"counter_id" bigserial PRIMARY KEY, "counter_id" bigserial PRIMARY KEY,
"card_uid" varchar(255), "card_uid" varchar(255),
"abwesenheit_typ" int2, "abwesenheit_typ" int2,
"datum" timestamptz(6) DEFAULT NOW()::DATE "datum_from" timestamptz DEFAULT NOW()::DATE,
"datum_to" timestamptz
); );
DROP TABLE IF EXISTS "s_abwesenheit_typen"; DROP TABLE IF EXISTS "s_abwesenheit_typen";
CREATE TABLE "s_abwesenheit_typen" ( CREATE TABLE "s_abwesenheit_typen" (
"abwesenheit_id" int2 PRIMARY KEY, "abwesenheit_id" int2 PRIMARY KEY,
"abwesenheit_name" varchar(255) "abwesenheit_name" varchar(255),
"arbeitszeit_equivalent" float4
); );
-- Adds crypto extension -- Adds crypto extension

6
DB/initdb/02_sample_data.sql Normal file → Executable file
View File

@@ -1,8 +1,8 @@
INSERT INTO "s_personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES 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, '07:00:00', '20:00:00', 0); (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 INSERT INTO "user_password" ("personal_nummer", "pass_hash") VALUES
(123, crypt('max_pass', gen_salt('bf'))); (123, crypt('max_pass', gen_salt('bf')));
INSERT INTO "s_anwesenheit_typen" ("anwesenheit_id", "anwesenheit_name") VALUES (1, 'Büro'); INSERT INTO "s_anwesenheit_typen" ("anwesenheit_id", "anwesenheit_name") VALUES (1, 'Büro');
INSERT INTO "s_abwesenheit_typen" ("abwesenheit_id", "abwesenheit_name") VALUES (1, 'Urlaub'), (2, 'Krank'), (3, 'Kurzarbeit'); INSERT INTO "s_abwesenheit_typen" ("abwesenheit_id", "abwesenheit_name", "arbeitszeit_equivalent") VALUES (1, 'Urlaub', 10), (2, 'Krank', 10), (3, 'Kurzarbeit', 2);

0
DB/initdb/03_create_user.sh Normal file → Executable file
View File

View File

@@ -9,7 +9,7 @@ services:
PGDATA: /var/lib/postgresql/data/pg_data PGDATA: /var/lib/postgresql/data/pg_data
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
@@ -19,7 +19,6 @@ services:
ports: ports:
- 8001:8080 - 8001:8080
backend: backend:
build: ../Backend
image: git.letsstein.de/tom/arbeitszeitmessung image: git.letsstein.de/tom/arbeitszeitmessung
restart: unless-stopped restart: unless-stopped
env_file: env_file:
@@ -31,8 +30,11 @@ services:
NO_CORS: true NO_CORS: true
ports: ports:
- ${EXPOSED_PORT}:8080 - ${EXPOSED_PORT}:8080
volumes:
- ../logs:/app/Backend/logs
depends_on: depends_on:
- db - db
swagger: swagger:
image: swaggerapi/swagger-ui image: swaggerapi/swagger-ui
restart: unless-stopped restart: unless-stopped

View File

@@ -28,4 +28,6 @@ services:
- ${EXPOSED_PORT}:8080 - ${EXPOSED_PORT}:8080
depends_on: depends_on:
- db - db
volumes:
- ../logs:/app/Backend/logs
restart: unless-stopped restart: unless-stopped

View File

@@ -44,8 +44,8 @@ generateFrontend:
backend: generateFrontend login_registry backend: generateFrontend login_registry
docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeit-backend:latest Backend --push docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung:latest Backend --load #--push
docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeit-backend:${GIT_COMMIT} Backend --push # docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung:${GIT_COMMIT} Backend //--push
test: test:
$(MAKE) -C Backend test $(MAKE) -C Backend test

View File

@@ -1,5 +1,7 @@
# 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)
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
## Installation ## Installation

118
db.sql
View File

@@ -146,3 +146,121 @@ SELECT
FROM ordered_bookings FROM ordered_bookings
GROUP BY work_date GROUP BY work_date
ORDER BY work_date; ORDER BY work_date;
-- Generate weekdays for 2 weeks (MonFri), starting 2 weeks ago
WITH days AS (
SELECT gs::date AS work_date
FROM generate_series(
date_trunc('week', CURRENT_DATE) - interval '14 days', -- start 2 weeks ago Monday
CURRENT_DATE, -- end TODAY (no future days)
interval '1 day'
) gs
WHERE EXTRACT(ISODOW FROM gs) <= 5 -- only MonFri
),
sample_bookings AS (
SELECT
d.work_date,
'aaaa-aaaa'::varchar AS card_uid,
1 AS check_in_out, -- come
101 AS geraet_id,
(d.work_date + make_time(8, floor(random()*50)::int, 0))::timestamptz AS ts,
1 AS anwesenheit_typ
FROM days d
UNION ALL
SELECT
d.work_date,
'aaaa-aaaa'::varchar AS card_uid,
2 AS check_in_out, -- go
101 AS geraet_id,
(d.work_date + make_time(16, floor(random()*50)::int, 0))::timestamptz AS ts,
1 AS anwesenheit_typ
FROM days d
),
ins_anw AS (
-- insert only bookings up to now (prevents future times on today)
INSERT INTO anwesenheit ("timestamp", card_uid, check_in_out, geraet_id)
SELECT ts, card_uid, check_in_out, geraet_id
FROM sample_bookings
WHERE ts <= NOW()
RETURNING 1
)
-- now insert absences (uses the same days CTE)
INSERT INTO abwesenheit (card_uid, abwesenheit_typ, datum)
SELECT
'aaaa-aaaa',
(ARRAY[1, 2])[floor(random()*2 + 1)], -- example types
d.work_date::timestamptz
FROM days d
WHERE random() < 0.2 -- ~20% random absences
ORDER BY d.work_date;
WITH params AS (
SELECT
'aaaa-aaaa'::varchar AS card_uid,
101::int AS geraet_id,
14::int AS start_days_ago, -- how many days back to start
0::int AS end_days_ahead, -- how many days forward (0 = today)
0::float AS pause_probability,
0.0::float AS absence_probability
),
days AS (
SELECT gs::date AS work_date, p.card_uid, p.geraet_id, p.pause_probability, p.absence_probability
FROM params p,
generate_series(
date_trunc('week', CURRENT_DATE) - (p.start_days_ago || ' days')::interval,
CURRENT_DATE + (p.end_days_ahead || ' days')::interval,
interval '1 day'
) gs
WHERE EXTRACT(ISODOW FROM gs) <= 5 -- only MonFri
),
base_bookings AS (
-- come
SELECT
d.work_date, d.card_uid, 1 AS check_in_out, d.geraet_id,
(d.work_date + make_time(8, floor(random()*40)::int, 0))::timestamptz AS ts
FROM days d
UNION ALL
-- go
SELECT
d.work_date, d.card_uid, 2 AS check_in_out, d.geraet_id,
(d.work_date + make_time(16, floor(random()*40)::int, 0))::timestamptz AS ts
FROM days d
),
pause_bookings AS (
-- pause come
SELECT
d.work_date, d.card_uid, 3 AS check_in_out, d.geraet_id,
(d.work_date + make_time(11, floor(random()*30)::int, 0))::timestamptz AS ts
FROM days d
WHERE random() < d.pause_probability
UNION ALL
-- pause go
SELECT
d.work_date, d.card_uid, 4 AS check_in_out, d.geraet_id,
(d.work_date + make_time(12, floor(random()*30)::int, 0))::timestamptz AS ts
FROM days d
WHERE random() < d.pause_probability
),
all_bookings AS (
SELECT * FROM base_bookings
UNION ALL
SELECT * FROM pause_bookings
),
ins_anw AS (
INSERT INTO anwesenheit ("timestamp", "card_uid", "check_in_out", "geraet_id", "anwesenheit_typ")
SELECT ts, card_uid, check_in_out, geraet_id, 1 as anwesenheit_typ
FROM all_bookings
WHERE ts <= NOW()
ORDER BY work_date, ts
RETURNING 1
)
INSERT INTO abwesenheit (card_uid, abwesenheit_typ, datum)
SELECT
d.card_uid,
(ARRAY[1, 2])[floor(random()*2 + 1)], -- example types
d.work_date::timestamptz
FROM days d
WHERE random() < d.absence_probability
ORDER BY d.work_date;

View File

@@ -1,10 +0,0 @@
-- reverse rename "s_personal_daten" table
ALTER TABLE "s_personal_daten" RENAME TO "personal_daten";
DROP TABLE "s_personal_daten";
-- reverse: create "s_anwesenheit_typen" table
DROP TABLE "s_anwesenheit_typen";
-- reverse: create "s_abwesenheit_typen" table
DROP TABLE "s_abwesenheit_typen";
-- reverse: modify "anwesenheit" table
ALTER TABLE "anwesenheit" DROP COLUMN "manuelle_buchung";

View File

@@ -1,7 +1,7 @@
-- reverse: create "wochen_report" table
DROP TABLE "wochen_report";
-- reverse: create "user_password" table -- reverse: create "user_password" table
DROP TABLE "user_password"; DROP TABLE "user_password";
-- reverse: create "wochen_report" table
DROP TABLE "wochen_report";
-- reverse: set comment to column: "geschlecht" on table: "personal_daten" -- reverse: set comment to column: "geschlecht" on table: "personal_daten"
COMMENT ON COLUMN "personal_daten"."geschlecht" IS NULL; COMMENT ON COLUMN "personal_daten"."geschlecht" IS NULL;
-- reverse: create "personal_daten" table -- reverse: create "personal_daten" table

View File

@@ -0,0 +1,14 @@
-- reverse: remame "personal_daten" table
ALTER TABLE "s_personal_daten" RENAME TO "personal_daten";
-- reverse: create "s_anwesenheit_typen" table
DROP TABLE "s_anwesenheit_typen";
-- reverse: create "s_abwesenheit_typen" table
DROP TABLE "s_abwesenheit_typen";
-- reverse: modify "wochen_report" table
ALTER TABLE "wochen_report" DROP COLUMN "ueberstunden";
-- reverse: modify "anwesenheit" table
ALTER TABLE "anwesenheit" DROP COLUMN "anwesenheit_typ", ALTER COLUMN "check_in_out" DROP NOT NULL, ALTER COLUMN "card_uid" DROP NOT NULL;
-- reverse: rename a constraint from "personal_daten_pkey" to "s_personal_daten_pkey"
ALTER TABLE "s_personal_daten" RENAME CONSTRAINT "s_personal_daten_pkey" TO "personal_daten_pkey";

View File

@@ -1,5 +1,7 @@
-- modify "anwesenheit" table -- modify "anwesenheit" table
ALTER TABLE "anwesenheit" ADD COLUMN "manuelle_buchung" boolean NULL; ALTER TABLE "anwesenheit" ALTER COLUMN "card_uid" SET NOT NULL, ALTER COLUMN "check_in_out" SET NOT NULL, ADD COLUMN "anwesenheit_typ" smallint NULL;
-- modify "wochen_report" table
ALTER TABLE "wochen_report" ADD COLUMN "ueberstunden" smallint NULL;
-- create "s_abwesenheit_typen" table -- create "s_abwesenheit_typen" table
CREATE TABLE "s_abwesenheit_typen" ( CREATE TABLE "s_abwesenheit_typen" (
"abwesenheit_id" smallint NOT NULL, "abwesenheit_id" smallint NOT NULL,
@@ -12,5 +14,8 @@ CREATE TABLE "s_anwesenheit_typen" (
"anwesenheit_name" character varying(255) NULL, "anwesenheit_name" character varying(255) NULL,
PRIMARY KEY ("anwesenheit_id") PRIMARY KEY ("anwesenheit_id")
); );
-- create "s_personal_daten" table -- rename "s_personal_daten" table
ALTER TABLE "personal_daten" RENAME TO "s_personal_daten"; ALTER TABLE "personal_daten" RENAME TO "s_personal_daten";
-- rename a constraint from "personal_daten_pkey" to "s_personal_daten_pkey"
ALTER TABLE "s_personal_daten" RENAME CONSTRAINT "personal_daten_pkey" TO "s_personal_daten_pkey";

View File

@@ -0,0 +1,10 @@
-- update Funktion für pass_hash
DROP FUNCTION update_zuletzt_geandert;
DROP TRIGGER IF EXISTS pass_hash_update ON user_password;
-- revert: Adds crypto extension
DROP EXTENSION IF EXISTS pgcrypto;

View File

@@ -0,0 +1,21 @@
-- update Funktion für pass_hash
CREATE OR REPLACE FUNCTION update_zuletzt_geandert()
RETURNS TRIGGER AS $$
BEGIN
-- Nur wenn hash geändert wurde
IF NEW.pass_hash IS DISTINCT FROM OLD.pass_hash THEN
NEW.zuletzt_geandert = now();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER pass_hash_update
BEFORE UPDATE ON user_password
FOR EACH ROW
EXECUTE FUNCTION update_zuletzt_geandert();
-- Adds crypto extension
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -0,0 +1,6 @@
-- reverse: modify "wochen_report" table
ALTER TABLE "wochen_report" DROP COLUMN "arbeitszeit", ALTER COLUMN "ueberstunden" TYPE smallint;
-- reverse: modify "s_personal_daten" table
ALTER TABLE "s_personal_daten" DROP COLUMN "arbeitszeit_per_woche";
-- reverse: modify "s_abwesenheit_typen" table
ALTER TABLE "s_abwesenheit_typen" DROP COLUMN "arbeitszeit_equivalent";

View File

@@ -0,0 +1,6 @@
-- modify "s_abwesenheit_typen" table
ALTER TABLE "s_abwesenheit_typen" ADD COLUMN "arbeitszeit_equivalent" real NULL;
-- modify "s_personal_daten" table
ALTER TABLE "s_personal_daten" ADD COLUMN "arbeitszeit_per_woche" real NULL;
-- modify "wochen_report" table
ALTER TABLE "wochen_report" ALTER COLUMN "ueberstunden" TYPE real, ADD COLUMN "arbeitszeit" real NULL;

View File

@@ -0,0 +1,4 @@
-- reverse: modify "s_personal_daten" table
ALTER TABLE "s_personal_daten" ALTER COLUMN "nachname" DROP NOT NULL, ALTER COLUMN "vorname" DROP NOT NULL;
-- reverse: modify "anwesenheit" table
ALTER TABLE "anwesenheit" ALTER COLUMN "anwesenheit_typ" DROP NOT NULL, ALTER COLUMN "geraet_id" DROP NOT NULL, ALTER COLUMN "timestamp" DROP NOT NULL;

View File

@@ -0,0 +1,4 @@
-- modify "anwesenheit" table
ALTER TABLE "anwesenheit" ALTER COLUMN "timestamp" SET NOT NULL, ALTER COLUMN "geraet_id" SET NOT NULL, ALTER COLUMN "anwesenheit_typ" SET NOT NULL;
-- modify "s_personal_daten" table
ALTER TABLE "s_personal_daten" ALTER COLUMN "vorname" SET NOT NULL, ALTER COLUMN "nachname" SET NOT NULL;

View File

@@ -0,0 +1,7 @@
ALTER TABLE wochen_report
ALTER COLUMN ueberstunden TYPE float4
USING
extract(epoch from ueberstunden) / 3600.0,
ALTER COLUMN arbeitszeit TYPE float4
USING
extract(epoch from arbeitszeit) / 3600.0;

View File

@@ -0,0 +1,19 @@
ALTER TABLE wochen_report
ADD COLUMN ueberstunden_interval interval,
ADD COLUMN arbeitszeit_interval interval;
UPDATE wochen_report
SET
ueberstunden_interval = CASE WHEN ueberstunden IS NULL THEN NULL ELSE (ueberstunden::double precision * INTERVAL '1 hour') END,
arbeitszeit_interval = CASE WHEN arbeitszeit IS NULL THEN NULL ELSE (arbeitszeit::double precision * INTERVAL '1 hour') END;
-- when happy, drop old columns and rename new ones
ALTER TABLE wochen_report
DROP COLUMN ueberstunden,
DROP COLUMN arbeitszeit;
ALTER TABLE wochen_report
RENAME COLUMN ueberstunden_interval TO ueberstunden;
ALTER TABLE wochen_report
RENAME COLUMN arbeitszeit_interval TO arbeitszeit;

View File

@@ -0,0 +1,11 @@
-- reverse: modify "wochen_report" table
ALTER TABLE "wochen_report" ALTER COLUMN "personal_nummer" DROP NOT NULL;
-- reverse: modify "s_personal_daten" table
ALTER TABLE "s_anwesenheit_typen" ALTER COLUMN "anwesenheit_name" DROP NOT NULL;
-- reverse: set comment to column: "arbeitszeit_equivalent" on table: "s_abwesenheit_typen"
COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS NULL;
-- reverse: modify "s_abwesenheit_typen" table
ALTER TABLE "s_abwesenheit_typen" ALTER COLUMN "arbeitszeit_equivalent" DROP NOT NULL, ALTER COLUMN "abwesenheit_name" DROP NOT NULL;
-- reverse: modify "abwesenheit" table
ALTER TABLE "abwesenheit" DROP COLUMN "datum_to", ALTER COLUMN "datum_from" DROP NOT NULL, ALTER COLUMN "abwesenheit_typ" DROP NOT NULL, ALTER COLUMN "card_uid" DROP NOT NULL;
ALTER TABLE "abwesenheit" RENAME COLUMN "datum_from" TO "datum";

View File

@@ -0,0 +1,11 @@
-- modify "abwesenheit" table
ALTER TABLE "abwesenheit" RENAME COLUMN "datum" TO "datum_from";
ALTER TABLE "abwesenheit" ALTER COLUMN "card_uid" SET NOT NULL, ALTER COLUMN "abwesenheit_typ" SET NOT NULL, ALTER COLUMN "datum_from" SET NOT NULL, ADD COLUMN "datum_to" timestamptz;
-- modify "s_abwesenheit_typen" table
ALTER TABLE "s_abwesenheit_typen" ALTER COLUMN "abwesenheit_name" SET NOT NULL, ALTER COLUMN "arbeitszeit_equivalent" SET NOT NULL;
-- 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';
-- modify "s_anwesenheit_typen" table
ALTER TABLE "s_anwesenheit_typen" ALTER COLUMN "anwesenheit_name" SET NOT NULL;
-- modify "s_personal_daten" table
ALTER TABLE "wochen_report" ALTER COLUMN "personal_nummer" SET NOT NULL;

View File

@@ -1,3 +1,15 @@
h1:5gPDmrcQS12KjKLuwN1ycTBHtbHbkzd7rUIj01uJrhA= h1:3AxgD8mnu/F+JGtJu9FZvA9Ro0UUtGPgyjskKtfTYUQ=
20250802065143_initial.up.sql h1:9cUWduWgONRfI5LV+b3nFvei6DKDqPxcNryKVg1xo80= 20250901201159_initial.down.sql h1:cmF5CvNGqEfcmbRgiqaqDWERdNNRaMzarbNLJ/Y35o4=
20250802075213_control_tables.up.sql h1:5vQLBHMM2Sa1FErP5gQUUHAoSiV2RQ0cOlMEEDFcoKA= 20250901201159_initial.up.sql h1:Yrak/+wfQ4Tu/dVR/cUZ/75DlAcv4G/OJXDqpgSw47U=
20250901201250_control_tables.down.sql h1:f/KmhO9pOI45J8ZRjFonvD3CypB+rOoGOPN2WMFHvOw=
20250901201250_control_tables.up.sql h1:of5E07p0N1aen9CdQNEOrO7ffbKZC6kp4oK5KPzU9+g=
20250901201710_triggers_extension.down.sql h1:a9va3FSfHBWzODJSJO+ywNa2hiZwjG/vmvYGb3L1lnM=
20250901201710_triggers_extension.up.sql h1:nUBPd2eDssi/TwMVF/nOJkIM5rUM0iINdg1K9pZRZN0=
20250903221313_overtime.down.sql h1:X+jJESqcZ6ZTd2H563z6kRaXb4dn4sA02D3ck2795v8=
20250903221313_overtime.up.sql h1:C3DSiNVpe9v0Un1DEQ0lsy5yToR8iqcggv91GSr6tRE=
20250903233030_non_null_contraints.down.sql h1:42TZzPsji2Ze50k6sLwgIuNo4Trk3m3ni/aIfQJ97dE=
20250903233030_non_null_contraints.up.sql h1:k6zR5YNSAP4fo5QEc58KZ0LxvEz1nl0X/AAcZ+TG3I4=
20250904114004_intervals.down.sql h1:SquJAPinzFIRN6fJjLLIRsz59Tyr4RwGiGuOFI/N1SQ=
20250904114004_intervals.up.sql h1:AFqncTGOiEZVBbhWFqN2zlQ7DyhybB5wJr6a36Atk1E=
20250916093608_kurzarbeit.down.sql h1:ljM1a1pQCxOQiXRaXU04GC4V9yy2y20x5eUNQ/zyx+o=
20250916093608_kurzarbeit.up.sql h1:pTiw0VfGaf26mhJg4wf98Fqwn1kShJ+PiN2PiM4q1kk=

1412
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,11 @@
{ {
"dependencies": { "dependencies": {
"tailwindcss": "^4.1.12", "@tailwindcss/cli": "^4.1.12",
"@tailwindcss/cli": "^4.1.12" "tailwindcss": "^4.1.12"
},
"devDependencies": {
"@iconify-json/material-symbols-light": "^1.2.33",
"@iconify/tailwind4": "^1.0.6",
"prettier": "^3.6.2"
} }
} }