package endpoints import ( "arbeitszeitmessung/helper" "arbeitszeitmessung/models" "arbeitszeitmessung/templates" "bytes" "fmt" "log" "log/slog" "net/http" "time" "github.com/Dadido3/go-typst" ) type typstMetadata struct { TimeRange string `json:"time-range"` EmployeeName string `json:"employee-name"` WorkTime string `json:"worktime"` Overtime string `json:"overtime"` OvertimeTotal string `json:"overtime-total"` CurrentTimestamp string `json:"current-timestamp"` } 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"` IsFriday bool `json:"is-weekend"` } func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, error) { var typstDays []typstDay for _, day := range days { var thisTypstDay typstDay work, pause, overtime := day.GetAllWorkTimesVirtual(u) thisTypstDay.Date = day.Date().Format("02.01.2006") thisTypstDay.Worktime = helper.FormatDurationFill(work, true) thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true) thisTypstDay.Overtime = helper.FormatDurationFill(overtime, true) thisTypstDay.IsFriday = day.Date().Weekday() == time.Friday thisTypstDay.DayParts = convertDayToTypstDayParts(day, u) typstDays = append(typstDays, thisTypstDay) } return typstDays, nil } func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDayPart { var typstDayParts []typstDayPart 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) } if day.IsKurzArbeit() { tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user) typstDayParts = append(typstDayParts, typstDayPart{ BookingFrom: tsFrom.Format("15:04"), BookingTo: tsTo.Format("15:04"), WorkType: "Kurzarbeit", IsWorkDay: true, }) } } else { absentDay, _ := day.(*models.Absence) typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name}) } return typstDayParts } func renderPDF(days []typstDay, metadata typstMetadata) (bytes.Buffer, error) { var markup bytes.Buffer var output bytes.Buffer if err := typst.InjectValues(&markup, map[string]any{"meta": metadata, "days": days}); err != nil { return output, err } // Import the template and invoke the template function with the custom data. // Show is used to replace the current document with whatever content the template function in `template.typ` returns. markup.WriteString(` #import "template.typ": abrechnung #show: doc => abrechnung(meta, days) `) // Compile the prepared markup with Typst and write the result it into `output.pdf`. // f, err := os.Create("output.pdf") // if err != nil { // log.Panicf("Failed to create output file: %v.", err) // } // defer f.Close() // typstCLI := typst.CLI{} if err := typstCLI.Compile(&markup, &output, nil); err != nil { return output, err } return output, nil } func PDFFormHandler(w http.ResponseWriter, r *http.Request) { helper.RequiresLogin(Session, w, r) user, err := models.GetUserFromSession(Session, r.Context()) if err != nil { slog.Warn("Error getting user!", slog.Any("Error", err)) // TODO add error handling } teamMembers, err := user.GetTeamMembers() if err != nil { slog.Warn("Error getting team members!", slog.Any("Error", err)) } templates.PDFForm(teamMembers).Render(r.Context(), w) } 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")) 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) } typstDays, err := convertDaysToTypst(weeks, user) if err != nil { log.Panicf("Failed to convert days!") } metadata := typstMetadata{ EmployeeName: fmt.Sprintf("%s %s", user.Vorname, user.Name), TimeRange: fmt.Sprintf("%s - %s", startDate.Format("02.01.2006"), endDate.Format("02.01.2006")), Overtime: helper.FormatDurationFill(aggregatedOvertime, true), WorkTime: helper.FormatDurationFill(aggregatedWorkTime, true), OvertimeTotal: "", CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"), } output, err := renderPDF(typstDays, metadata) if err != nil { slog.Warn("Could not create pdf report", slog.Any("Error", err)) } w.Header().Set("Content-type", "application/pdf") output.WriteTo(w) w.WriteHeader(200) }