diff --git a/Backend/endpoints/time.go b/Backend/endpoints/time.go index 255ccd9..803dc92 100644 --- a/Backend/endpoints/time.go +++ b/Backend/endpoints/time.go @@ -3,10 +3,12 @@ package endpoints import ( "arbeitszeitmessung/helper" "arbeitszeitmessung/models" + "arbeitszeitmessung/templates" "encoding/json" "log" "net/http" "strconv" + "time" ) // Frontend relevant backend functionality -> not used by the arduino devices @@ -15,7 +17,7 @@ func TimeHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": getBookings(w, r) - case "UPDATE": + case "POST": updateBooking(w, r) case "OPTIONS": // just support options header for non GET Requests from SWAGGER @@ -25,22 +27,53 @@ func TimeHandler(w http.ResponseWriter, r *http.Request) { } } +func parseTimestamp(r *http.Request , get_key string, fallback string) (time.Time, error) { + _timestamp_get := r.URL.Query().Get(get_key) + if(_timestamp_get == "") { + _timestamp_get = fallback + } + timestamp_get, err := time.Parse("2006-01-02", _timestamp_get) + if(err != nil){ + return time.Now(), err + } + return timestamp_get, nil +} + // Returns bookings from DB with similar card uid -> checks for card uid in http query params func getBookings(w http.ResponseWriter, r *http.Request) { card_uid := r.URL.Query().Get("card_uid") + if card_uid == "" && Session.Exists(r.Context(), "card_uid"){ + card_uid = Session.GetString(r.Context(), "user") + } if card_uid == "" { - http.Error(w, "Missing cardID query parameter", http.StatusBadRequest) + http.Error(w, "Missing card_uid query parameter", http.StatusBadRequest) return } - bookings, err := (*models.Booking).GetBookingsByCardID(nil, card_uid) + + tsFrom, err := parseTimestamp(r, "time_from", "2000-01-01") + 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, card_uid, 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) + templates.OverviewPage(bookings).Render(r.Context(), w) + // w.Header().Set("Content-Type", "application/json") + // json.NewEncoder(w).Encode(bookings) } // Updates a booking form the given json body @@ -55,7 +88,7 @@ func updateBooking(w http.ResponseWriter, r *http.Request) { http.Error(w, "Invalid bookingID query parameter", http.StatusBadRequest) return } - _booking, err := (*models.Booking).GetBookingById(nil, booking_id) + 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) @@ -70,13 +103,13 @@ func updateBooking(w http.ResponseWriter, r *http.Request) { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } - if booking.CounterId != 0 && booking.CounterId != _booking.CounterId { + 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 } - _booking.Update(booking) - _booking.Save() + bookingDB.Update(booking) + bookingDB.Save() w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(_booking) + json.NewEncoder(w).Encode(bookingDB) } diff --git a/Backend/endpoints/user.go b/Backend/endpoints/user.go new file mode 100644 index 0000000..585d3a6 --- /dev/null +++ b/Backend/endpoints/user.go @@ -0,0 +1,62 @@ +package endpoints + +import ( + "arbeitszeitmessung/models" + "arbeitszeitmessung/templates" + "log" + "net/http" + "time" + + "github.com/alexedwards/scs/v2" +) + +var Session *scs.SessionManager + +func CreateSessionManager(lifetime time.Duration) *scs.SessionManager { + Session = scs.New() + Session.Lifetime = lifetime + return Session +} + +func LoginHandler(w http.ResponseWriter, r *http.Request){ + switch r.Method{ + case http.MethodGet: showForm(w, r) + break + case http.MethodPost: loginUser(w, r) + break + default: + showForm(w, r) + break + } +} + +func showForm(w http.ResponseWriter, r *http.Request){ + templates.LoginForm().Render(r.Context(), w) +} + +func loginUser(w http.ResponseWriter, r *http.Request){ + err := r.ParseForm() + if err != nil { + log.Println("Error parsing form!", err) + http.Error(w, "Internal error", http.StatusBadRequest) + } + card_uid := r.FormValue("card_uid") + if(card_uid == ""){ + log.Println("No card_uid provided!") + http.Error(w, "No card_uid provided", http.StatusBadRequest) + } + user, err := (*models.User).GetById(nil, card_uid) + if(err != nil){ + log.Println("No user found under this card_uid!") + http.Error(w, "No user found!", http.StatusNotFound) + } + + password := r.FormValue("password") + if(user.Login(password)){ + log.Printf("New succesfull user login from %s %s!\n", user.Vorname, user.Name) + Session.Put(r.Context(), "user", user.CardUID) + http.Redirect(w, r, "/time", http.StatusSeeOther) //with this browser always uses GET + } + +showForm(w, r) +} diff --git a/Backend/go.mod b/Backend/go.mod index 5308e06..a1f0b45 100644 --- a/Backend/go.mod +++ b/Backend/go.mod @@ -1,5 +1,11 @@ module arbeitszeitmessung -go 1.21.7 +go 1.23 + +toolchain go1.23.6 require github.com/lib/pq v1.10.9 + +require github.com/a-h/templ v0.3.833 + +require github.com/alexedwards/scs/v2 v2.8.0 // indirect diff --git a/Backend/go.sum b/Backend/go.sum index aeddeae..7efc01b 100644 --- a/Backend/go.sum +++ b/Backend/go.sum @@ -1,2 +1,8 @@ +github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU= +github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk= +github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw= +github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/Backend/helper/system.go b/Backend/helper/system.go index 68446d1..4ebffb2 100644 --- a/Backend/helper/system.go +++ b/Backend/helper/system.go @@ -7,7 +7,6 @@ import ( // Returns env with default fallback value. // // Params: -// // key - enviroment var name // fallback - default value func GetEnv(key, fallback string) string { diff --git a/Backend/helper/types.go b/Backend/helper/types.go new file mode 100644 index 0000000..84fa2c5 --- /dev/null +++ b/Backend/helper/types.go @@ -0,0 +1,9 @@ +package helper + +import "time" + +type TimeFormValue struct { + TsFrom time.Time + TsTo time.Time + CardUID string +} diff --git a/Backend/main.go b/Backend/main.go index 3f3d718..458521f 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -4,13 +4,17 @@ import ( "arbeitszeitmessung/endpoints" "arbeitszeitmessung/helper" "arbeitszeitmessung/models" + "context" "fmt" "log" "net/http" + "time" _ "github.com/lib/pq" ) + + func main() { var err error models.DB, err = OpenDatabase() @@ -19,12 +23,30 @@ func main() { } defer models.DB.Close() + fs := http.FileServer(http.Dir("./static")) + endpoints.CreateSessionManager(24 * time.Hour) + + server := http.NewServeMux() + // handles the different http routes - http.HandleFunc("/time/new", endpoints.TimeCreateHandler) - http.HandleFunc("/time", endpoints.TimeHandler) - http.HandleFunc("/logout", endpoints.LogoutHandler) + withMiddleware := ParamsMiddleware(endpoints.TimeHandler) + server.HandleFunc("/time/new", endpoints.TimeCreateHandler) + server.Handle("/time", withMiddleware) + server.HandleFunc("/logout", endpoints.LogoutHandler) + server.HandleFunc("/", endpoints.LoginHandler) + server.Handle("/static/", http.StripPrefix("/static/", fs)) + + serverSessionMiddleware := endpoints.Session.LoadAndSave(server) // starting the http server fmt.Printf("Server is running at http://localhost:8000 exposed to port %s\n", helper.GetEnv("EXPOSED_PORT", "8000")) - log.Fatal(http.ListenAndServe(":8080", nil)) + log.Fatal(http.ListenAndServe(":8080", serverSessionMiddleware)) +} + +func ParamsMiddleware(next http.HandlerFunc) http.Handler{ + return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { + queryParams := r.URL.Query() + ctx := context.WithValue(r.Context(), "urlParams", queryParams) + next.ServeHTTP(w, r.WithContext(ctx)) + }) } diff --git a/Backend/models/booking.go b/Backend/models/booking.go index 644f30c..5a83347 100644 --- a/Backend/models/booking.go +++ b/Backend/models/booking.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/url" + "sort" "strconv" "time" ) @@ -84,14 +85,17 @@ func (b *Booking) GetBookingById(booking_id int) (Booking, error) { return booking, nil } -func (b *Booking) GetBookingsByCardID(card_id string) ([]Booking, error) { - qStr, err := DB.Prepare((`SELECT counter_id, timestamp, card_uid, geraet_id, check_in_out FROM anwesenheit WHERE card_uid = $1`)) +// Gets all booking based on a card uid +// +// optional filter parameter +func (b *Booking) GetBookingsByCardID(card_uid string, tsFrom time.Time, tsTo time.Time) ([]Booking, error) { + qStr, err := DB.Prepare((`SELECT counter_id, timestamp, card_uid, geraet_id, check_in_out FROM anwesenheit WHERE card_uid = $1 AND timestamp BETWEEN $2 AND $3 ORDER BY timestamp`)) if err != nil { return nil, err } defer qStr.Close() var bookings []Booking - rows, err := qStr.Query(card_id) + rows, err := qStr.Query(card_uid, tsFrom, tsTo) if err == sql.ErrNoRows { return bookings, err } @@ -112,19 +116,58 @@ func (b *Booking) GetBookingsByCardID(card_id string) ([]Booking, error) { 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) + result = append(result, WorkDay{Day: day, Bookings: bookings}) + } + + sort.Slice(result, func(i, j int) bool { + return result[i].Day.After(result[j].Day) + }) + + return result, nil +} + func (b Booking) Save() { - qStr, err := DB.Prepare((`UPDATE "anwesenheit" SET "id" = $1, "card_uid" = $2, "reader_id" = $3, "booking_type" = $4 WHERE "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 { log.Fatalf("Error preparing query: %v", err) return } - _, err = qStr.Query(b.CounterId, b.CardUID, b.GeraetID, b.CheckInOut) + _, err = qStr.Query(b.CounterId, b.CardUID, b.GeraetID, b.CheckInOut, b.Timestamp) if err != nil { log.Fatalf("Error executing query: %v", err) return } } +func (b *Booking) GetBookingType() string { + switch b.CheckInOut{ + case (1): + return "kommen" + case (2): + return "gehen" + case(255): + return "abgemeldet" + default: + return "Buchungs Typ unbekannt" + } +} + func (b *Booking) Update(nb Booking) { if b.CheckInOut != nb.CheckInOut && nb.CheckInOut != 0 { b.CheckInOut = nb.CheckInOut @@ -135,6 +178,9 @@ func (b *Booking) Update(nb Booking) { if b.GeraetID != nb.GeraetID && nb.GeraetID != 0 { b.GeraetID = nb.GeraetID } + if(b.Timestamp != nb.Timestamp){ + b.Timestamp = nb.Timestamp + } } func checkLastBooking(b Booking) bool { diff --git a/Backend/models/user.go b/Backend/models/user.go index 9008fc4..3846524 100644 --- a/Backend/models/user.go +++ b/Backend/models/user.go @@ -2,6 +2,7 @@ package models import ( "fmt" + "strings" ) type User struct { @@ -64,3 +65,21 @@ func (u *User) Logout() error { } return nil } + +func (u *User) GetById(card_uid string) (User, error) { + var user User + qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname, hauptbeschaeftigung_ort FROM personal_daten WHERE card_uid = $1;`)) + if err != nil { + return user, err + } + err = qStr.QueryRow(card_uid).Scan(&user.CardUID, &user.Vorname, &user.Name, &user.HauptbeschaeftigungsOrt) + if err != nil{ + return user, err + } + return user, nil +} + +func (u *User) Login(password string) bool { + userPassword := strings.ToLower(fmt.Sprintf("%s_%s", u.Vorname, u.Name)) //temp password: "max_mustermann" + return userPassword == password +} diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go new file mode 100644 index 0000000..da0a42e --- /dev/null +++ b/Backend/models/workDay.go @@ -0,0 +1,46 @@ +package models + +import "time" + +type WorkDay struct { + Day time.Time + workTime time.Duration + Bookings []Booking +} + +func (d WorkDay) GetWorkTime() time.Duration{ + if(d.workTime > 0) { + return d.workTime + } + var lastCame Booking + for _, booking := range d.Bookings{ + if(booking.CheckInOut == 1){//kommen + lastCame = booking + }else{ + d.workTime = d.workTime + booking.Timestamp.Sub(lastCame.Timestamp) + } + } + if(time.Since(d.Day) < 24 * time.Hour && len(d.Bookings) % 2 == 1){ + d.workTime += time.Since(lastCame.Timestamp) + } + return d.workTime +} + +func (d WorkDay) GetWorkTimeString() string { + str := d.GetWorkTime().Abs().Round(time.Minute).String() + str = str[:len(str)-2] + "in" + if(len(str) > 2){ + return str + } + return "" +} + +func (d WorkDay) RequiresAction() bool { + return d.Bookings[len(d.Bookings)-1].CheckInOut == 255 +} + +func (d WorkDay) GetWorkDayProgress() uint8 { + defaultWorkTime, _ := time.ParseDuration("7h42m") + progress := (d.GetWorkTime().Seconds()/defaultWorkTime.Seconds())*100 + return uint8(progress) +} diff --git a/Backend/src/main.css b/Backend/src/main.css new file mode 100644 index 0000000..1309cf2 --- /dev/null +++ b/Backend/src/main.css @@ -0,0 +1,28 @@ +@import "tailwindcss"; +@source "../templates/*.templ"; + +@theme { + --color-accent-50: #e7fdea; + --color-accent-100: #cbfbd1; + --color-accent-200: #9cf7a8; + --color-accent-300: #68f37a; + --color-accent-400: #33ef4d; + --color-accent-500: #11db2d; + --color-accent-600: #0eaf23; + --color-accent-700: #0a851b; + --color-accent-800: #075a12; + --color-accent-900: #032b09; + --color-accent-950: #021805; + --color-accent: #0eaf23; + --color-text-50: #f7f8f7; + --color-text-100: #f2f3f2; + --color-text-200: #e2e4e2; + --color-text-300: #d2d6d2; + --color-text-400: #c2c7c2; + --color-text-500: #afb6af; + --color-text-600: #97a097; + --color-text-700: #7d877d; + --color-text-800: #5a625a; + --color-text-900: #161816; + --color-text-950: #000000; +} diff --git a/Backend/static/css/styles.css b/Backend/static/css/styles.css new file mode 100644 index 0000000..1a07df8 --- /dev/null +++ b/Backend/static/css/styles.css @@ -0,0 +1,944 @@ +/*! tailwindcss v4.0.6 | MIT License | https://tailwindcss.com */ +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + --color-red-50: oklch(0.971 0.013 17.38); + --color-red-100: oklch(0.936 0.032 17.717); + --color-red-200: oklch(0.885 0.062 18.334); + --color-red-300: oklch(0.808 0.114 19.571); + --color-red-400: oklch(0.704 0.191 22.216); + --color-red-500: oklch(0.637 0.237 25.331); + --color-red-600: oklch(0.577 0.245 27.325); + --color-red-700: oklch(0.505 0.213 27.518); + --color-red-800: oklch(0.444 0.177 26.899); + --color-red-900: oklch(0.396 0.141 25.723); + --color-red-950: oklch(0.258 0.092 26.042); + --color-orange-50: oklch(0.98 0.016 73.684); + --color-orange-100: oklch(0.954 0.038 75.164); + --color-orange-200: oklch(0.901 0.076 70.697); + --color-orange-300: oklch(0.837 0.128 66.29); + --color-orange-400: oklch(0.75 0.183 55.934); + --color-orange-500: oklch(0.705 0.213 47.604); + --color-orange-600: oklch(0.646 0.222 41.116); + --color-orange-700: oklch(0.553 0.195 38.402); + --color-orange-800: oklch(0.47 0.157 37.304); + --color-orange-900: oklch(0.408 0.123 38.172); + --color-orange-950: oklch(0.266 0.079 36.259); + --color-amber-50: oklch(0.987 0.022 95.277); + --color-amber-100: oklch(0.962 0.059 95.617); + --color-amber-200: oklch(0.924 0.12 95.746); + --color-amber-300: oklch(0.879 0.169 91.605); + --color-amber-400: oklch(0.828 0.189 84.429); + --color-amber-500: oklch(0.769 0.188 70.08); + --color-amber-600: oklch(0.666 0.179 58.318); + --color-amber-700: oklch(0.555 0.163 48.998); + --color-amber-800: oklch(0.473 0.137 46.201); + --color-amber-900: oklch(0.414 0.112 45.904); + --color-amber-950: oklch(0.279 0.077 45.635); + --color-yellow-50: oklch(0.987 0.026 102.212); + --color-yellow-100: oklch(0.973 0.071 103.193); + --color-yellow-200: oklch(0.945 0.129 101.54); + --color-yellow-300: oklch(0.905 0.182 98.111); + --color-yellow-400: oklch(0.852 0.199 91.936); + --color-yellow-500: oklch(0.795 0.184 86.047); + --color-yellow-600: oklch(0.681 0.162 75.834); + --color-yellow-700: oklch(0.554 0.135 66.442); + --color-yellow-800: oklch(0.476 0.114 61.907); + --color-yellow-900: oklch(0.421 0.095 57.708); + --color-yellow-950: oklch(0.286 0.066 53.813); + --color-lime-50: oklch(0.986 0.031 120.757); + --color-lime-100: oklch(0.967 0.067 122.328); + --color-lime-200: oklch(0.938 0.127 124.321); + --color-lime-300: oklch(0.897 0.196 126.665); + --color-lime-400: oklch(0.841 0.238 128.85); + --color-lime-500: oklch(0.768 0.233 130.85); + --color-lime-600: oklch(0.648 0.2 131.684); + --color-lime-700: oklch(0.532 0.157 131.589); + --color-lime-800: oklch(0.453 0.124 130.933); + --color-lime-900: oklch(0.405 0.101 131.063); + --color-lime-950: oklch(0.274 0.072 132.109); + --color-green-50: oklch(0.982 0.018 155.826); + --color-green-100: oklch(0.962 0.044 156.743); + --color-green-200: oklch(0.925 0.084 155.995); + --color-green-300: oklch(0.871 0.15 154.449); + --color-green-400: oklch(0.792 0.209 151.711); + --color-green-500: oklch(0.723 0.219 149.579); + --color-green-600: oklch(0.627 0.194 149.214); + --color-green-700: oklch(0.527 0.154 150.069); + --color-green-800: oklch(0.448 0.119 151.328); + --color-green-900: oklch(0.393 0.095 152.535); + --color-green-950: oklch(0.266 0.065 152.934); + --color-emerald-50: oklch(0.979 0.021 166.113); + --color-emerald-100: oklch(0.95 0.052 163.051); + --color-emerald-200: oklch(0.905 0.093 164.15); + --color-emerald-300: oklch(0.845 0.143 164.978); + --color-emerald-400: oklch(0.765 0.177 163.223); + --color-emerald-500: oklch(0.696 0.17 162.48); + --color-emerald-600: oklch(0.596 0.145 163.225); + --color-emerald-700: oklch(0.508 0.118 165.612); + --color-emerald-800: oklch(0.432 0.095 166.913); + --color-emerald-900: oklch(0.378 0.077 168.94); + --color-emerald-950: oklch(0.262 0.051 172.552); + --color-teal-50: oklch(0.984 0.014 180.72); + --color-teal-100: oklch(0.953 0.051 180.801); + --color-teal-200: oklch(0.91 0.096 180.426); + --color-teal-300: oklch(0.855 0.138 181.071); + --color-teal-400: oklch(0.777 0.152 181.912); + --color-teal-500: oklch(0.704 0.14 182.503); + --color-teal-600: oklch(0.6 0.118 184.704); + --color-teal-700: oklch(0.511 0.096 186.391); + --color-teal-800: oklch(0.437 0.078 188.216); + --color-teal-900: oklch(0.386 0.063 188.416); + --color-teal-950: oklch(0.277 0.046 192.524); + --color-cyan-50: oklch(0.984 0.019 200.873); + --color-cyan-100: oklch(0.956 0.045 203.388); + --color-cyan-200: oklch(0.917 0.08 205.041); + --color-cyan-300: oklch(0.865 0.127 207.078); + --color-cyan-400: oklch(0.789 0.154 211.53); + --color-cyan-500: oklch(0.715 0.143 215.221); + --color-cyan-600: oklch(0.609 0.126 221.723); + --color-cyan-700: oklch(0.52 0.105 223.128); + --color-cyan-800: oklch(0.45 0.085 224.283); + --color-cyan-900: oklch(0.398 0.07 227.392); + --color-cyan-950: oklch(0.302 0.056 229.695); + --color-sky-50: oklch(0.977 0.013 236.62); + --color-sky-100: oklch(0.951 0.026 236.824); + --color-sky-200: oklch(0.901 0.058 230.902); + --color-sky-300: oklch(0.828 0.111 230.318); + --color-sky-400: oklch(0.746 0.16 232.661); + --color-sky-500: oklch(0.685 0.169 237.323); + --color-sky-600: oklch(0.588 0.158 241.966); + --color-sky-700: oklch(0.5 0.134 242.749); + --color-sky-800: oklch(0.443 0.11 240.79); + --color-sky-900: oklch(0.391 0.09 240.876); + --color-sky-950: oklch(0.293 0.066 243.157); + --color-blue-50: oklch(0.97 0.014 254.604); + --color-blue-100: oklch(0.932 0.032 255.585); + --color-blue-200: oklch(0.882 0.059 254.128); + --color-blue-300: oklch(0.809 0.105 251.813); + --color-blue-400: oklch(0.707 0.165 254.624); + --color-blue-500: oklch(0.623 0.214 259.815); + --color-blue-600: oklch(0.546 0.245 262.881); + --color-blue-700: oklch(0.488 0.243 264.376); + --color-blue-800: oklch(0.424 0.199 265.638); + --color-blue-900: oklch(0.379 0.146 265.522); + --color-blue-950: oklch(0.282 0.091 267.935); + --color-indigo-50: oklch(0.962 0.018 272.314); + --color-indigo-100: oklch(0.93 0.034 272.788); + --color-indigo-200: oklch(0.87 0.065 274.039); + --color-indigo-300: oklch(0.785 0.115 274.713); + --color-indigo-400: oklch(0.673 0.182 276.935); + --color-indigo-500: oklch(0.585 0.233 277.117); + --color-indigo-600: oklch(0.511 0.262 276.966); + --color-indigo-700: oklch(0.457 0.24 277.023); + --color-indigo-800: oklch(0.398 0.195 277.366); + --color-indigo-900: oklch(0.359 0.144 278.697); + --color-indigo-950: oklch(0.257 0.09 281.288); + --color-violet-50: oklch(0.969 0.016 293.756); + --color-violet-100: oklch(0.943 0.029 294.588); + --color-violet-200: oklch(0.894 0.057 293.283); + --color-violet-300: oklch(0.811 0.111 293.571); + --color-violet-400: oklch(0.702 0.183 293.541); + --color-violet-500: oklch(0.606 0.25 292.717); + --color-violet-600: oklch(0.541 0.281 293.009); + --color-violet-700: oklch(0.491 0.27 292.581); + --color-violet-800: oklch(0.432 0.232 292.759); + --color-violet-900: oklch(0.38 0.189 293.745); + --color-violet-950: oklch(0.283 0.141 291.089); + --color-purple-50: oklch(0.977 0.014 308.299); + --color-purple-100: oklch(0.946 0.033 307.174); + --color-purple-200: oklch(0.902 0.063 306.703); + --color-purple-300: oklch(0.827 0.119 306.383); + --color-purple-400: oklch(0.714 0.203 305.504); + --color-purple-500: oklch(0.627 0.265 303.9); + --color-purple-600: oklch(0.558 0.288 302.321); + --color-purple-700: oklch(0.496 0.265 301.924); + --color-purple-800: oklch(0.438 0.218 303.724); + --color-purple-900: oklch(0.381 0.176 304.987); + --color-purple-950: oklch(0.291 0.149 302.717); + --color-fuchsia-50: oklch(0.977 0.017 320.058); + --color-fuchsia-100: oklch(0.952 0.037 318.852); + --color-fuchsia-200: oklch(0.903 0.076 319.62); + --color-fuchsia-300: oklch(0.833 0.145 321.434); + --color-fuchsia-400: oklch(0.74 0.238 322.16); + --color-fuchsia-500: oklch(0.667 0.295 322.15); + --color-fuchsia-600: oklch(0.591 0.293 322.896); + --color-fuchsia-700: oklch(0.518 0.253 323.949); + --color-fuchsia-800: oklch(0.452 0.211 324.591); + --color-fuchsia-900: oklch(0.401 0.17 325.612); + --color-fuchsia-950: oklch(0.293 0.136 325.661); + --color-pink-50: oklch(0.971 0.014 343.198); + --color-pink-100: oklch(0.948 0.028 342.258); + --color-pink-200: oklch(0.899 0.061 343.231); + --color-pink-300: oklch(0.823 0.12 346.018); + --color-pink-400: oklch(0.718 0.202 349.761); + --color-pink-500: oklch(0.656 0.241 354.308); + --color-pink-600: oklch(0.592 0.249 0.584); + --color-pink-700: oklch(0.525 0.223 3.958); + --color-pink-800: oklch(0.459 0.187 3.815); + --color-pink-900: oklch(0.408 0.153 2.432); + --color-pink-950: oklch(0.284 0.109 3.907); + --color-rose-50: oklch(0.969 0.015 12.422); + --color-rose-100: oklch(0.941 0.03 12.58); + --color-rose-200: oklch(0.892 0.058 10.001); + --color-rose-300: oklch(0.81 0.117 11.638); + --color-rose-400: oklch(0.712 0.194 13.428); + --color-rose-500: oklch(0.645 0.246 16.439); + --color-rose-600: oklch(0.586 0.253 17.585); + --color-rose-700: oklch(0.514 0.222 16.935); + --color-rose-800: oklch(0.455 0.188 13.697); + --color-rose-900: oklch(0.41 0.159 10.272); + --color-rose-950: oklch(0.271 0.105 12.094); + --color-slate-50: oklch(0.984 0.003 247.858); + --color-slate-100: oklch(0.968 0.007 247.896); + --color-slate-200: oklch(0.929 0.013 255.508); + --color-slate-300: oklch(0.869 0.022 252.894); + --color-slate-400: oklch(0.704 0.04 256.788); + --color-slate-500: oklch(0.554 0.046 257.417); + --color-slate-600: oklch(0.446 0.043 257.281); + --color-slate-700: oklch(0.372 0.044 257.287); + --color-slate-800: oklch(0.279 0.041 260.031); + --color-slate-900: oklch(0.208 0.042 265.755); + --color-slate-950: oklch(0.129 0.042 264.695); + --color-gray-50: oklch(0.985 0.002 247.839); + --color-gray-100: oklch(0.967 0.003 264.542); + --color-gray-200: oklch(0.928 0.006 264.531); + --color-gray-300: oklch(0.872 0.01 258.338); + --color-gray-400: oklch(0.707 0.022 261.325); + --color-gray-500: oklch(0.551 0.027 264.364); + --color-gray-600: oklch(0.446 0.03 256.802); + --color-gray-700: oklch(0.373 0.034 259.733); + --color-gray-800: oklch(0.278 0.033 256.848); + --color-gray-900: oklch(0.21 0.034 264.665); + --color-gray-950: oklch(0.13 0.028 261.692); + --color-zinc-50: oklch(0.985 0 0); + --color-zinc-100: oklch(0.967 0.001 286.375); + --color-zinc-200: oklch(0.92 0.004 286.32); + --color-zinc-300: oklch(0.871 0.006 286.286); + --color-zinc-400: oklch(0.705 0.015 286.067); + --color-zinc-500: oklch(0.552 0.016 285.938); + --color-zinc-600: oklch(0.442 0.017 285.786); + --color-zinc-700: oklch(0.37 0.013 285.805); + --color-zinc-800: oklch(0.274 0.006 286.033); + --color-zinc-900: oklch(0.21 0.006 285.885); + --color-zinc-950: oklch(0.141 0.005 285.823); + --color-neutral-50: oklch(0.985 0 0); + --color-neutral-100: oklch(0.97 0 0); + --color-neutral-200: oklch(0.922 0 0); + --color-neutral-300: oklch(0.87 0 0); + --color-neutral-400: oklch(0.708 0 0); + --color-neutral-500: oklch(0.556 0 0); + --color-neutral-600: oklch(0.439 0 0); + --color-neutral-700: oklch(0.371 0 0); + --color-neutral-800: oklch(0.269 0 0); + --color-neutral-900: oklch(0.205 0 0); + --color-neutral-950: oklch(0.145 0 0); + --color-stone-50: oklch(0.985 0.001 106.423); + --color-stone-100: oklch(0.97 0.001 106.424); + --color-stone-200: oklch(0.923 0.003 48.717); + --color-stone-300: oklch(0.869 0.005 56.366); + --color-stone-400: oklch(0.709 0.01 56.259); + --color-stone-500: oklch(0.553 0.013 58.071); + --color-stone-600: oklch(0.444 0.011 73.639); + --color-stone-700: oklch(0.374 0.01 67.558); + --color-stone-800: oklch(0.268 0.007 34.298); + --color-stone-900: oklch(0.216 0.006 56.043); + --color-stone-950: oklch(0.147 0.004 49.25); + --color-black: #000; + --color-white: #fff; + --spacing: 0.25rem; + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + --container-3xs: 16rem; + --container-2xs: 18rem; + --container-xs: 20rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-2xl: 42rem; + --container-3xl: 48rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-6xl: 72rem; + --container-7xl: 80rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-base: 1rem; + --text-base--line-height: calc(1.5 / 1); + --text-lg: 1.125rem; + --text-lg--line-height: calc(1.75 / 1.125); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --text-3xl: 1.875rem; + --text-3xl--line-height: calc(2.25 / 1.875); + --text-4xl: 2.25rem; + --text-4xl--line-height: calc(2.5 / 2.25); + --text-5xl: 3rem; + --text-5xl--line-height: 1; + --text-6xl: 3.75rem; + --text-6xl--line-height: 1; + --text-7xl: 4.5rem; + --text-7xl--line-height: 1; + --text-8xl: 6rem; + --text-8xl--line-height: 1; + --text-9xl: 8rem; + --text-9xl--line-height: 1; + --font-weight-thin: 100; + --font-weight-extralight: 200; + --font-weight-light: 300; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --font-weight-extrabold: 800; + --font-weight-black: 900; + --tracking-tighter: -0.05em; + --tracking-tight: -0.025em; + --tracking-normal: 0em; + --tracking-wide: 0.025em; + --tracking-wider: 0.05em; + --tracking-widest: 0.1em; + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; + --radius-xs: 0.125rem; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --radius-xl: 0.75rem; + --radius-2xl: 1rem; + --radius-3xl: 1.5rem; + --radius-4xl: 2rem; + --shadow-2xs: 0 1px rgb(0 0 0 / 0.05); + --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); + --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05); + --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05); + --inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05); + --drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05); + --drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15); + --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12); + --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15); + --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1); + --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15); + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --animate-spin: spin 1s linear infinite; + --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + --animate-bounce: bounce 1s infinite; + --blur-xs: 4px; + --blur-sm: 8px; + --blur-md: 12px; + --blur-lg: 16px; + --blur-xl: 24px; + --blur-2xl: 40px; + --blur-3xl: 64px; + --perspective-dramatic: 100px; + --perspective-near: 300px; + --perspective-normal: 500px; + --perspective-midrange: 800px; + --perspective-distant: 1200px; + --aspect-video: 16 / 9; + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + --default-font-family: var(--font-sans); + --default-font-feature-settings: var(--font-sans--font-feature-settings); + --default-font-variation-settings: var( + --font-sans--font-variation-settings + ); + --default-mono-font-family: var(--font-mono); + --default-mono-font-feature-settings: var( + --font-mono--font-feature-settings + ); + --default-mono-font-variation-settings: var( + --font-mono--font-variation-settings + ); + --color-accent-50: #e7fdea; + --color-accent-100: #cbfbd1; + --color-accent-200: #9cf7a8; + --color-accent-300: #68f37a; + --color-accent-400: #33ef4d; + --color-accent-500: #11db2d; + --color-accent-600: #0eaf23; + --color-accent-700: #0a851b; + --color-accent-800: #075a12; + --color-accent-900: #032b09; + --color-accent-950: #021805; + --color-accent: #0eaf23; + --color-text-50: #f7f8f7; + --color-text-100: #f2f3f2; + --color-text-200: #e2e4e2; + --color-text-300: #d2d6d2; + --color-text-400: #c2c7c2; + --color-text-500: #afb6af; + --color-text-600: #97a097; + --color-text-700: #7d877d; + --color-text-800: #5a625a; + --color-text-900: #161816; + --color-text-950: #000000; + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" ); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var( --default-font-variation-settings, normal ); + -webkit-tap-highlight-color: transparent; + } + body { + line-height: inherit; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace ); + font-feature-settings: var( --default-mono-font-feature-settings, normal ); + font-variation-settings: var( --default-mono-font-variation-settings, normal ); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + color: color-mix(in oklab, currentColor 50%, transparent); + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden="until-found"])) { + display: none !important; + } +} +@layer utilities { + .static { + position: static; + } + .col-span-2 { + grid-column: span 2 / span 2; + } + .col-span-3 { + grid-column: span 3 / span 3; + } + .-my-1 { + margin-block: calc(var(--spacing) * -1); + } + .mt-1 { + margin-top: calc(var(--spacing) * 1); + } + .flex { + display: flex; + } + .grid { + display: grid; + } + .hidden { + display: none; + } + .size-2 { + width: calc(var(--spacing) * 2); + height: calc(var(--spacing) * 2); + } + .size-4 { + width: calc(var(--spacing) * 4); + height: calc(var(--spacing) * 4); + } + .h-4 { + height: calc(var(--spacing) * 4); + } + .h-full { + height: 100%; + } + .w-2 { + width: calc(var(--spacing) * 2); + } + .w-4 { + width: calc(var(--spacing) * 4); + } + .w-\[2px\] { + width: 2px; + } + .w-full { + width: 100%; + } + .flex-grow { + flex-grow: 1; + } + .grow-1 { + flex-grow: 1; + } + .cursor-pointer { + cursor: pointer; + } + .grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + .flex-col { + flex-direction: column; + } + .flex-row { + flex-direction: row; + } + .items-center { + align-items: center; + } + .justify-between { + justify-content: space-between; + } + .justify-center { + justify-content: center; + } + .gap-2 { + gap: calc(var(--spacing) * 2); + } + .gap-4 { + gap: calc(var(--spacing) * 4); + } + .divide-x-1 { + :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))); + } + } + .divide-y-1 { + :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))); + } + } + .divide-neutral-400 { + :where(& > :not(:last-child)) { + border-color: var(--color-neutral-400); + } + } + .overflow-hidden { + overflow: hidden; + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .rounded-md { + border-radius: var(--radius-md); + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-0 { + border-style: var(--tw-border-style); + border-width: 0px; + } + .border-r-0 { + border-right-style: var(--tw-border-style); + border-right-width: 0px; + } + .border-b-0 { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 0px; + } + .border-neutral-200 { + border-color: var(--color-neutral-200); + } + .border-neutral-400 { + border-color: var(--color-neutral-400); + } + .border-neutral-900 { + border-color: var(--color-neutral-900); + } + .bg-accent { + background-color: var(--color-accent); + } + .bg-neutral-100 { + background-color: var(--color-neutral-100); + } + .bg-neutral-200 { + background-color: var(--color-neutral-200); + } + .bg-neutral-300 { + background-color: var(--color-neutral-300); + } + .bg-neutral-400 { + background-color: var(--color-neutral-400); + } + .bg-orange-500 { + background-color: var(--color-orange-500); + } + .bg-purple-600 { + background-color: var(--color-purple-600); + } + .bg-red-600 { + background-color: var(--color-red-600); + } + .p-2 { + padding: calc(var(--spacing) * 2); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } + .py-2 { + padding-block: calc(var(--spacing) * 2); + } + .text-center { + text-align: center; + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + } + .text-accent { + color: var(--color-accent); + } + .text-neutral-500 { + color: var(--color-neutral-500); + } + .text-neutral-700 { + color: var(--color-neutral-700); + } + .text-neutral-800 { + color: var(--color-neutral-800); + } + .text-red-600 { + color: var(--color-red-600); + } + .uppercase { + text-transform: uppercase; + } + .filter { + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + .transition { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-colors { + 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)); + } + .duration-300 { + --tw-duration: 300ms; + transition-duration: 300ms; + } + .placeholder\:text-neutral-400 { + &::placeholder { + color: var(--color-neutral-400); + } + } + .hover\:border-neutral-300 { + &:hover { + @media (hover: hover) { + border-color: var(--color-neutral-300); + } + } + } + .hover\:bg-neutral-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-neutral-700); + } + } + } + .hover\:text-white { + &:hover { + @media (hover: hover) { + color: var(--color-white); + } + } + } + .focus\:border-neutral-400 { + &:focus { + border-color: var(--color-neutral-400); + } + } + .focus\:bg-neutral-700 { + &:focus { + background-color: var(--color-neutral-700); + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } + .active\:bg-neutral-700 { + &:active { + background-color: var(--color-neutral-700); + } + } + .disabled\:pointer-events-none { + &:disabled { + pointer-events: none; + } + } + .disabled\:opacity-50 { + &:disabled { + opacity: 50%; + } + } + .md\:col-span-1 { + @media (width >= 48rem) { + grid-column: span 1 / span 1; + } + } + .md\:col-span-3 { + @media (width >= 48rem) { + grid-column: span 3 / span 3; + } + } + .md\:mx-\[10\%\] { + @media (width >= 48rem) { + margin-inline: 10%; + } + } + .md\:block { + @media (width >= 48rem) { + display: block; + } + } + .md\:hidden { + @media (width >= 48rem) { + display: none; + } + } + .md\:grid-cols-5 { + @media (width >= 48rem) { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + } + .md\:px-4 { + @media (width >= 48rem) { + padding-inline: calc(var(--spacing) * 4); + } + } + .print\:hidden { + @media print { + display: none; + } + } +} +@keyframes spin { + to { + transform: rotate(360deg); + } +} +@keyframes ping { + 75%, 100% { + transform: scale(2); + opacity: 0; + } +} +@keyframes pulse { + 50% { + opacity: 0.5; + } +} +@keyframes bounce { + 0%, 100% { + transform: translateY(-25%); + animation-timing-function: cubic-bezier(0.8, 0, 1, 1); + } + 50% { + transform: none; + animation-timing-function: cubic-bezier(0, 0, 0.2, 1); + } +} +@property --tw-divide-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-divide-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-blur { + syntax: "*"; + inherits: false; +} +@property --tw-brightness { + syntax: "*"; + inherits: false; +} +@property --tw-contrast { + syntax: "*"; + inherits: false; +} +@property --tw-grayscale { + syntax: "*"; + inherits: false; +} +@property --tw-hue-rotate { + syntax: "*"; + inherits: false; +} +@property --tw-invert { + syntax: "*"; + inherits: false; +} +@property --tw-opacity { + syntax: "*"; + inherits: false; +} +@property --tw-saturate { + syntax: "*"; + inherits: false; +} +@property --tw-sepia { + syntax: "*"; + inherits: false; +} +@property --tw-duration { + syntax: "*"; + inherits: false; +} diff --git a/Backend/templates/base.templ b/Backend/templates/base.templ new file mode 100644 index 0000000..868fddd --- /dev/null +++ b/Backend/templates/base.templ @@ -0,0 +1,10 @@ +package templates + +templ Style(){ + + + Arbeitszeit + + + +} diff --git a/Backend/templates/base_templ.go b/Backend/templates/base_templ.go new file mode 100644 index 0000000..c2b07f3 --- /dev/null +++ b/Backend/templates/base_templ.go @@ -0,0 +1,40 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +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" + +func Style() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "Arbeitszeit") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/Backend/templates/base_templ.txt b/Backend/templates/base_templ.txt new file mode 100644 index 0000000..f6ec6b3 --- /dev/null +++ b/Backend/templates/base_templ.txt @@ -0,0 +1 @@ +Arbeitszeit \ No newline at end of file diff --git a/Backend/templates/hello.templ b/Backend/templates/hello.templ new file mode 100644 index 0000000..66aeae0 --- /dev/null +++ b/Backend/templates/hello.templ @@ -0,0 +1,5 @@ +package templates + +templ Hello(name string) { +
Hello, { name }
+} diff --git a/Backend/templates/hello_templ.go b/Backend/templates/hello_templ.go new file mode 100644 index 0000000..9a783dd --- /dev/null +++ b/Backend/templates/hello_templ.go @@ -0,0 +1,53 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +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" + +func Hello(name 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_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Hello, ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/hello.templ`, Line: 4, Col: 19} + } + _, 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 + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/Backend/templates/hello_templ.txt b/Backend/templates/hello_templ.txt new file mode 100644 index 0000000..b3e5211 --- /dev/null +++ b/Backend/templates/hello_templ.txt @@ -0,0 +1,2 @@ +
Hello, +
\ No newline at end of file diff --git a/Backend/templates/overview.templ b/Backend/templates/overview.templ new file mode 100644 index 0000000..456df40 --- /dev/null +++ b/Backend/templates/overview.templ @@ -0,0 +1,147 @@ +package templates + +import ( + "arbeitszeitmessung/models" + "time" + "fmt" + "net/url" +) + +templ inputForm(){ + {{ + urlParams := ctx.Value("urlParams").(url.Values) + }} +
+

Tom Tröger

+

Überstunden

+

4h 32min

+
+
+ // + @lineComponent() +
+ + +
+
+
+ +
+} + +templ bookingComponent(booking models.Booking) { +
+

{booking.Timestamp.Local().Format("15:04")} {booking.GetBookingType()}

+
+} + +templ lineComponent(){ +
+ + + +
+ + + +
+} + +templ dayComponent(workDay models.WorkDay, last bool){ + {{ last_border := "" + if(last) { + last_border = "border-b-0" + } + }} +
+ @timeGaugeComponent(workDay.GetWorkDayProgress(), workDay.Day.Equal(time.Now().Truncate(24 * time.Hour)), workDay.RequiresAction()) +
+

{workDay.Day.Format("Mon")}: {workDay.Day.Format("02.01.2006")}

+

Arbeitszeit

+ if (workDay.RequiresAction()) { +

Bitte anpassen

+ }else{ +

{workDay.GetWorkTimeString()}

+ } +
+
+
+ @lineComponent() +
+ for _, booking := range workDay.Bookings { + @bookingComponent(booking) + } +
+
+
+ +
+} + +templ allBookings(bookings []models.WorkDay) { + for index, bookingGroup := range bookings { + @dayComponent(bookingGroup, index == len(bookings) -1) + } +} + +templ timeGaugeComponent(progress uint8, today bool, warning bool){ + {{ + var bgColor string + switch { + case (warning): + bgColor = "bg-red-600" + break + case (progress > 0 && progress < 80): + bgColor = "bg-orange-500" + break + case (80 < progress && progress <=110): + bgColor = "bg-accent" + break + case(progress > 110): + bgColor = "bg-purple-600" + break + default: + bgColor = "bg-neutral-400" + break + } + }} + if today { +
+
+
+ }else { +
+ + } +} + +templ LegendComponent(){ +
+
Fehler
+
Arbeitszeit unter regulär
+
Arbeitszeit vollständig
+
Überstunden
+
Keine Buchungen
+
+} + +templ OverviewPage(bookings []models.WorkDay){ + @Style() +
+ @inputForm() + @allBookings(bookings) +
+ @LegendComponent() + + +} diff --git a/Backend/templates/overview_templ.go b/Backend/templates/overview_templ.go new file mode 100644 index 0000000..1ffed69 --- /dev/null +++ b/Backend/templates/overview_templ.go @@ -0,0 +1,572 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +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/models" + "fmt" + "net/url" + "time" +) + +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_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + urlParams := ctx.Value("urlParams").(url.Values) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Tom Tröger

Überstunden

4h 32min

") + 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, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func bookingComponent(booking models.Booking) 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_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(booking.Timestamp.Local().Format("15:04")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/overview.templ`, Line: 39, Col: 47} + } + _, 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, 6, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(booking.GetBookingType()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/overview.templ`, Line: 39, Col: 105} + } + _, 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, 7, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func lineComponent() 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_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func dayComponent(workDay models.WorkDay, last 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_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + last_border := "" + if last { + last_border = "border-b-0" + } + var templ_7745c5c3_Var9 = []any{"p-2 col-span-2 md:col-span-1 flex flex-row gap-2", last_border} + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = timeGaugeComponent(workDay.GetWorkDayProgress(), 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, 11, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Day.Format("Mon")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/overview.templ`, Line: 64, Col: 77} + } + _, 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, 12, ": ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, 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/overview.templ`, Line: 64, Col: 120} + } + _, 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, 13, "

Arbeitszeit

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if workDay.RequiresAction() { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

Bitte anpassen

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.GetWorkTimeString()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/overview.templ`, Line: 69, Col: 55} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 = []any{"flex flex-row col-span-3 md:col-span-3 gap-2 w-full p-2", last_border} + 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, 18, "
") + 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, 20, "
") + 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, 21, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 = []any{"border-r-0 p-2", last_border} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...) + 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 + } + return nil + }) +} + +func allBookings(bookings []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_Var19 := templ.GetChildren(ctx) + if templ_7745c5c3_Var19 == nil { + templ_7745c5c3_Var19 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + for index, bookingGroup := range bookings { + templ_7745c5c3_Err = dayComponent(bookingGroup, index == len(bookings)-1).Render(ctx, templ_7745c5c3_Buffer) + 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_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + templ_7745c5c3_Var20 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + var bgColor string + switch { + case (warning): + bgColor = "bg-red-600" + break + case (progress > 0 && progress < 80): + bgColor = "bg-orange-500" + break + case (80 < progress && progress <= 110): + bgColor = "bg-accent" + break + case (progress > 110): + bgColor = "bg-purple-600" + break + default: + bgColor = "bg-neutral-400" + break + } + if today { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 = []any{"flex w-full items-center justify-center overflow-hidden rounded-full", bgColor} + 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, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var24 = []any{"w-2 h-full bg-accent rounded-md", bgColor} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var24...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func LegendComponent() 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_Var26 := templ.GetChildren(ctx) + if templ_7745c5c3_Var26 == nil { + templ_7745c5c3_Var26 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
Fehler
Arbeitszeit unter regulär
Arbeitszeit vollständig
Überstunden
Keine Buchungen
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func OverviewPage(bookings []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 = Style().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = inputForm().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = allBookings(bookings).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 + } + templ_7745c5c3_Err = LegendComponent().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/Backend/templates/overview_templ.txt b/Backend/templates/overview_templ.txt new file mode 100644 index 0000000..b30e965 --- /dev/null +++ b/Backend/templates/overview_templ.txt @@ -0,0 +1,33 @@ +

Tom Tröger

Überstunden

4h 32min

+
+

+ +

+
+
+

+: +

Arbeitszeit

+

Bitte anpassen

+

+

+
+
+
+
+
+
+
+
+
Fehler
Arbeitszeit unter regulär
Arbeitszeit vollständig
Überstunden
Keine Buchungen
+
+
\ No newline at end of file diff --git a/Backend/templates/user.templ b/Backend/templates/user.templ new file mode 100644 index 0000000..ee7d1f6 --- /dev/null +++ b/Backend/templates/user.templ @@ -0,0 +1,10 @@ +package templates + +templ LoginForm(){ + @Style() +
+ + + +
+} diff --git a/Backend/templates/user_templ.go b/Backend/templates/user_templ.go new file mode 100644 index 0000000..18ef07c --- /dev/null +++ b/Backend/templates/user_templ.go @@ -0,0 +1,44 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +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" + +func LoginForm() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = Style().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/Backend/templates/user_templ.txt b/Backend/templates/user_templ.txt new file mode 100644 index 0000000..24f2c45 --- /dev/null +++ b/Backend/templates/user_templ.txt @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/Docker/.env b/Docker/.env index 11718f7..fdf9775 100644 --- a/Docker/.env +++ b/Docker/.env @@ -1,5 +1,5 @@ POSTGRES_USER=arbeit_zeit POSTGRES_PASSWORD=password -POSTGRES_PATH=../Backend/db +POSTGRES_PATH=../DB POSTGRES_DB=arbeitszeitmessung EXPOSED_PORT=8000