From 7eda8eb538911410e41d2d1621da32c3629e924b Mon Sep 17 00:00:00 2001
From: tom
Date: Thu, 23 Oct 2025 16:17:49 +0200
Subject: [PATCH] reworked pdf exporter to use typst
---
Backend/endpoints/pdf.go | 53 +++++++++++++++++++++
Backend/go.mod | 3 ++
Backend/go.sum | 8 ++++
Backend/models/workDay.go | 14 +++++-
Backend/templates/pdf.templ | 7 ++-
Backend/templates/pdf_templ.go | 84 ++++++++++++++++++++++-----------
WIR-typst/main.typ | 63 +++++++++++++++++++++++++
WIR-typst/template.pdf | Bin 0 -> 2193 bytes
WIR-typst/template.typ | 58 +++++++++++++++++++++++
WIR-typst/test.typ | 36 ++++++++++++++
10 files changed, 296 insertions(+), 30 deletions(-)
create mode 100644 WIR-typst/main.typ
create mode 100644 WIR-typst/template.pdf
create mode 100644 WIR-typst/template.typ
create mode 100644 WIR-typst/test.typ
diff --git a/Backend/endpoints/pdf.go b/Backend/endpoints/pdf.go
index f3ebc12..4bb8d53 100644
--- a/Backend/endpoints/pdf.go
+++ b/Backend/endpoints/pdf.go
@@ -9,6 +9,59 @@ import (
"time"
)
+type typstMetadata struct {
+ ISOWeek string `json:"iso-week"`
+ EmployeeName string `json:"employee-name"`
+ WorkTime string `json:"worktime"`
+ Overtime string `json:"overtime"`
+ OvertimeTotal string `json:"overtime-total"`
+}
+
+type typstDayPart struct {
+ BookingFrom string `json:"booking-from"`
+ BookingTo string `json:"booking-to"`
+ WorkType string `json:"worktype"`
+ IsWorkDay bool `json:"is-workday"`
+}
+
+type typstDay struct {
+ Date string `json:"date"`
+ DayParts []typstDayPart `json:"day-parts"`
+ Worktime string `json:"worktime"`
+ Pausetime string `json:"pausetime"`
+ Overtime string `json:"overtime"`
+}
+
+func ConvertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, error) {
+ var typstDays []typstDay
+ for _, day := range days {
+ var typstDay typstDay
+ var typstDayParts []typstDayPart
+ work, pause, overtime := day.GetAllWorkTimesVirtual(u)
+ typstDay.Date = day.Date().Format("01.02.2006")
+ typstDay.Worktime = helper.FormatDuration(work)
+ typstDay.Pausetime = helper.FormatDuration(pause)
+ typstDay.Overtime = helper.FormatDuration(overtime)
+ if day.IsWorkDay() {
+ workDay, _ := day.(*models.WorkDay)
+ for i := 0; i < len(workDay.Bookings); i += 2 {
+ var typstDayPart typstDayPart
+ typstDayPart.BookingFrom = workDay.Bookings[i].Timestamp.Format("15:04")
+ typstDayPart.BookingTo = workDay.Bookings[i+1].Timestamp.Format("15:04")
+ typstDayPart.WorkType = workDay.Bookings[i].BookingType.Name
+ typstDayPart.IsWorkDay = true
+ typstDayParts = append(typstDayParts, typstDayPart)
+ }
+ } else {
+ absentDay, _ := day.(*models.Absence)
+ typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name})
+ }
+ typstDay.DayParts = typstDayParts
+ typstDays = append(typstDays, typstDay)
+ }
+ return typstDays, nil
+}
+
func PDFHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
startDate, err := parseTimestamp(r, "start_date", time.Now().Format("2006-01-02"))
diff --git a/Backend/go.mod b/Backend/go.mod
index b993a01..030b122 100644
--- a/Backend/go.mod
+++ b/Backend/go.mod
@@ -14,8 +14,11 @@ require (
)
require (
+ github.com/Dadido3/go-typst v0.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
+ github.com/smasher164/xid v0.1.2 // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/sys v0.36.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
)
diff --git a/Backend/go.sum b/Backend/go.sum
index 6c7522d..a4fe99c 100644
--- a/Backend/go.sum
+++ b/Backend/go.sum
@@ -1,5 +1,7 @@
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/Dadido3/go-typst v0.3.0 h1:Itix2FtQgBiOuHUNqgGUAK11Oo2WMlZGGGpCiQNK1IA=
+github.com/Dadido3/go-typst v0.3.0/go.mod h1:QYis9sT70u65kn1SkFfyPRmHsPxgoxWbAixwfPReOZA=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/a-h/templ v0.3.943 h1:o+mT/4yqhZ33F3ootBiHwaY4HM5EVaOJfIshvd5UNTY=
@@ -54,6 +56,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/smasher164/xid v0.1.2 h1:erplXSdBRIIw+MrwjJ/m8sLN2XY16UGzpTA0E2Ru6HA=
+github.com/smasher164/xid v0.1.2/go.mod h1:tgivm8CQl19fH1c5y+8F4mA+qY6n2i6qDRBlY/6nm+I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -70,5 +74,9 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go
index 5ae30a0..0fc67aa 100644
--- a/Backend/models/workDay.go
+++ b/Backend/models/workDay.go
@@ -83,11 +83,23 @@ func (d *WorkDay) Date() time.Time {
return d.Day
}
+func (d *WorkDay) GenerateKurzArbeitBookings(u User) (time.Time, time.Time) {
+ var timeFrom, timeTo time.Time
+ if d.workTime >= u.ArbeitszeitProTag() {
+ return timeFrom, timeTo
+ }
+
+ timeFrom = d.Bookings[len(d.Bookings)-1].Timestamp.Add(time.Minute)
+ timeTo = timeFrom.Add(u.ArbeitszeitProTag() - d.workTime)
+ slog.Debug("Added duration as Kurzarbeit", "date", d.Date().String(), "duration", timeTo.Sub(timeFrom).String())
+
+ return timeFrom, timeTo
+}
+
func (d *WorkDay) TimeWorkVirtual(u User) time.Duration {
if d.IsKurzArbeit() {
return u.ArbeitszeitProTag()
}
-
return d.workTime
}
diff --git a/Backend/templates/pdf.templ b/Backend/templates/pdf.templ
index 204e9dd..e740c5e 100644
--- a/Backend/templates/pdf.templ
+++ b/Backend/templates/pdf.templ
@@ -45,7 +45,12 @@ templ PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays
{ workDay.Bookings[bookingI].BookingType.Name }
}
if workDay.IsKurzArbeit() {
- Kurzarbeit
+ {{
+ timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
+ }}
+ { timeFrom.Format("15:04") }
+ { timeTo.Format("15:04") }
+ Kurzarbeit
}
} else {
{{
diff --git a/Backend/templates/pdf_templ.go b/Backend/templates/pdf_templ.go
index 3c73ded..a7ee638 100644
--- a/Backend/templates/pdf_templ.go
+++ b/Backend/templates/pdf_templ.go
@@ -252,7 +252,35 @@ func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays
return templ_7745c5c3_Err
}
if workDay.IsKurzArbeit() {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "Kurzarbeit
")
+
+ timeFrom, timeTo := workDay.GenerateKurzArbeitBookings(e)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var17 string
+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(timeFrom.Format("15:04"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 51, Col: 36}
+ }
+ _, 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, 20, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var18 string
+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(timeTo.Format("15:04"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 52, Col: 34}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
Kurzarbeit
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -260,25 +288,25 @@ func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays
} else {
absentDay, _ := day.(*models.Absence)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
")
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)
+ var templ_7745c5c3_Var19 string
+ templ_7745c5c3_Var19, 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}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 59, Col: 62}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
+ _, 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, 21, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -287,7 +315,7 @@ func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -295,7 +323,7 @@ func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -303,18 +331,18 @@ func PDFReportEmploye(e models.User, overtime, worktime time.Duration, workDays
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if day.Date().Weekday() == time.Friday {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "Wochenende
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "Wochenende
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -338,9 +366,9 @@ func ColorDuration(d time.Duration, classes string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var18 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var18 == nil {
- templ_7745c5c3_Var18 = templ.NopComponent
+ templ_7745c5c3_Var20 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var20 == nil {
+ templ_7745c5c3_Var20 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
@@ -348,38 +376,38 @@ func ColorDuration(d time.Duration, classes string) templ.Component {
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...)
+ var templ_7745c5c3_Var21 = []any{color + " " + classes}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var21...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\">")
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))
+ var templ_7745c5c3_Var23 string
+ templ_7745c5c3_Var23, 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}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/pdf.templ`, Line: 81, Col: 72}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
+ _, 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, 30, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/WIR-typst/main.typ b/WIR-typst/main.typ
new file mode 100644
index 0000000..2f9effa
--- /dev/null
+++ b/WIR-typst/main.typ
@@ -0,0 +1,63 @@
+#set page("a4")
+#set text(font: "Lato")
+
+= Stunden
+
+== Kim Mustermensch
+
+Zeitraum: 01.10.2025 - 31.10.2025
+
+Arbeitszeit: 136h 19min
+
+Überstunden: -39h 41min
+
+
+
+
+
+// #show table.cell: it => {
+// if it.y == 0 {
+// set text(white)
+// strong(it)
+// } else if it.body == [] {
+// // Replace empty cells with 'N/A'
+// pad(..it.inset)[0min]
+
+// } else {
+// it
+// }
+// }
+
+#let subgrid(body) = {
+ table.cell(colspan: 3, inset: 0em)[
+ #table(
+ columns: (1fr, 1fr, 1fr),
+ gutter: 0em,
+ stroke: black,
+ [..#body]
+ )
+ ]
+}
+
+
+ "01.09.2025",
+ "08:07",
+ "16:28",
+ "Büro",
+ "7h 51min",
+ "30min",
+ "-9min",
+ "02.09.2025",
+ // return work, pause, overtime
+table.cell(colspan: 3, inset: 0em)[#table(
+ columns: (1fr, 1fr, 1fr),
+ gutter: 0em,
+ stroke: black,
+ [08:12], [16:24], [Büro],
+ [16:30], [17:24], [Homeoffice]
+ )],
+ "6h",
+ "0min",
+ "-1h 15min"
+
+)
\ No newline at end of file
diff --git a/WIR-typst/template.pdf b/WIR-typst/template.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..db53c7ae6ae2d912d5f666f3260dc937145546e3
GIT binary patch
literal 2193
zcma)8&2Hm15Z*&iTJ#+V6#)_ywnWKtY(dsyW39VJ<6Xlxwu`1Y&=Rf4kwk%_VI`+N
zLLXo+z4aY>=nEC-D|ATz$xgDZE|xxiGn{Y8neo{;7&LJ6Qak$<9!Z%hw;1
z2qKi}nH+`b?Coo@x1R$qp95tM4%>hJIA79GAn8=4cuWK+&5?AdfOnPnVJbj2lI|4L
zXDQ>;f`k-xcGv*Yjyw-SszoJNH+$8gfdT8+GAb!iJ6%McnsvU^dn~fdkNek4Fo45)8Ix@YU1rgeY9YSg+U3wcTVq
zHgLDwHB8H}tOhU|kc%EHf%n0Mk5Tjz(YSX4w&aHjcl}7e)~uEQ(q-jzWP2
z2*m~_WIik0{^okk7)$;74fBlCRY@O>+<=8EseKsuv*9?JT&|Z(YZ&&UFka3HGvD@l
z%J!JLH6G@rAbCjthjESSMWMT3JR`yY-wyt1@ strong(text(fill: white, h)))
+ )
+}
+
+#set table(
+ stroke: black,
+ inset: .5em,
+ align: center,
+)
+
+#let abrechnung(meta, days) = {
+ set page(paper: "a4")
+
+ [= Abrechnung Arbeitszeit -- #meta.employee-name]
+
+ [Zeitraum: #meta.Zeitraum
+
+ Arbeitszeit: #user.Arbeitszeit
+
+ Überstunden: #user.Überstunden
+]
+
+ table(
+ columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1.25fr),
+ fill: (x, y) =>
+ if y == 0 { gray },
+ align: center,
+ table-header(
+ [Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Pause], [Überstunden]
+ ),
+ .. for day in days {
+ (
+ [#day.Date],
+ table.cell(colspan: 3, inset: 0em)[
+ #table(
+ columns: (1fr, 1fr, 1fr),
+ gutter: 0em,
+ stroke: black,
+ .. for Zeit in day.Zeiten {
+ (
+ [#Zeit.Kommen],
+ [#Zeit.Gehen],
+ [#Zeit.Art],
+ )
+ },
+ )
+ ],
+ [#day.Arbeitszeit],
+ [#day.Pause],
+ [#day.Überstunden],
+
+ )
+
+ }
+ )
+}
diff --git a/WIR-typst/test.typ b/WIR-typst/test.typ
new file mode 100644
index 0000000..555ce61
--- /dev/null
+++ b/WIR-typst/test.typ
@@ -0,0 +1,36 @@
+#let user = (
+ Name: "Mustermensch",
+ Vorname: "Kim",
+ Arbeitszeit: "139h 12min",
+ Überstunden: "-14h 12min"
+)
+
+#let meta = (
+ Zeitraum: "01.09.2025 - 30.09.2025",
+ KW: "26"
+)
+
+#let days = (
+ (
+ Date: "01.09.2025",
+ Zeiten: (
+ (Kommen: "07:17", Gehen: "14:13", Art: "Büro"),
+ (Kommen: "14:24", Gehen: "16:13", Art: "Homeoffice")
+ ),
+ Arbeitszeit: "7h 32min",
+ Pause: "34min",
+ Überstunden: "12min"
+ ),(
+ Date: "02.09.2025",
+ Zeiten: (
+ (Kommen: "07:23", Gehen: "14:21", Art: "Büro"),
+ (Kommen: "14:38", Gehen: "17:13", Art: "Homeoffice")
+ ),
+ Arbeitszeit: "6h 22min",
+ Pause: "45min",
+ Überstunden: "-23min"
+ )
+)
+
+#import "template.typ": abrechnung
+#show: doc => abrechnung(meta, user, days)
\ No newline at end of file