fix: fix wrong date calculation
This commit is contained in:
parent
4514ce44a2
commit
c8b7666971
4 changed files with 815 additions and 62 deletions
|
|
@ -244,3 +244,43 @@ func DeleteUser(db *sql.DB, id int) error {
|
||||||
_, err := db.Exec("DELETE FROM users WHERE id = ?", id)
|
_, err := db.Exec("DELETE FROM users WHERE id = ?", id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteTimeEntriesByUserAndWeek(db *sql.DB, userID int, year int, week int) error {
|
||||||
|
query := `
|
||||||
|
DELETE FROM time_entries
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND CAST(strftime('%W', date) AS INTEGER) = ?
|
||||||
|
AND CAST(strftime('%Y', date) AS INTEGER) = ?
|
||||||
|
`
|
||||||
|
_, err := db.Exec(query, userID, week, year)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckUserHasEntriesForWeek(db *sql.DB, userID int, year int, week int) (bool, error) {
|
||||||
|
// Berechne die Daten der Woche
|
||||||
|
dates := calculateWeekDates(year, week)
|
||||||
|
|
||||||
|
// Hole alle Daten als Liste
|
||||||
|
var dateList []string
|
||||||
|
for _, date := range dates.Dates {
|
||||||
|
dateList = append(dateList, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe ob Einträge existieren
|
||||||
|
query := `
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM time_entries
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND date IN (?, ?, ?, ?, ?)
|
||||||
|
`
|
||||||
|
|
||||||
|
var count int
|
||||||
|
err := db.QueryRow(query, userID,
|
||||||
|
dateList[0], dateList[1], dateList[2], dateList[3], dateList[4]).Scan(&count)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
@ -147,6 +148,44 @@ func (app *App) GetMyTimeEntriesHandler(c echo.Context) error {
|
||||||
return c.JSON(http.StatusOK, entries)
|
return c.JSON(http.StatusOK, entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWeekDates - Gibt die Daten einer Woche zurück (Montag-Freitag)
|
||||||
|
func (app *App) GetWeekDates(c echo.Context) error {
|
||||||
|
year, err := strconv.Atoi(c.QueryParam("year"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid year")
|
||||||
|
}
|
||||||
|
|
||||||
|
week, err := strconv.Atoi(c.QueryParam("week"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid week")
|
||||||
|
}
|
||||||
|
|
||||||
|
dates := calculateWeekDates(year, week)
|
||||||
|
return c.JSON(http.StatusOK, dates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckWeekHasEntries - Prüft ob User Einträge für eine Woche hat
|
||||||
|
func (app *App) CheckWeekHasEntries(c echo.Context) error {
|
||||||
|
userID := c.Get("user_id").(int)
|
||||||
|
|
||||||
|
year, err := strconv.Atoi(c.QueryParam("year"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid year")
|
||||||
|
}
|
||||||
|
|
||||||
|
week, err := strconv.Atoi(c.QueryParam("week"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid week")
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEntries, err := CheckUserHasEntriesForWeek(app.DB, userID, year, week)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, map[string]bool{"has_entries": hasEntries})
|
||||||
|
}
|
||||||
|
|
||||||
func (app *App) GetAllTimeEntriesHandler(c echo.Context) error {
|
func (app *App) GetAllTimeEntriesHandler(c echo.Context) error {
|
||||||
entries, err := GetAllTimeEntries(app.DB)
|
entries, err := GetAllTimeEntries(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -162,3 +201,72 @@ func (app *App) GetWeeklyHoursHandler(c echo.Context) error {
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, hours)
|
return c.JSON(http.StatusOK, hours)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *App) DeleteWeekEntries(c echo.Context) error {
|
||||||
|
userID := c.Get("user_id").(int)
|
||||||
|
|
||||||
|
year, err := strconv.Atoi(c.QueryParam("year"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid year")
|
||||||
|
}
|
||||||
|
|
||||||
|
week, err := strconv.Atoi(c.QueryParam("week"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid week")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := DeleteTimeEntriesByUserAndWeek(app.DB, userID, year, week); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.NoContent(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeekDates struct {
|
||||||
|
Year int `json:"year"`
|
||||||
|
Week int `json:"week"`
|
||||||
|
Dates map[string]string `json:"dates"` // dayOfWeek -> date
|
||||||
|
Range string `json:"range"` // "2025-11-03 bis 2025-11-07"
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateWeekDates(year, week int) WeekDates {
|
||||||
|
// ISO 8601: Woche 1 ist die Woche mit dem ersten Donnerstag
|
||||||
|
// Finde den ersten Donnerstag des Jahres
|
||||||
|
jan4 := time.Date(year, time.January, 4, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
// Finde Montag der Woche 1
|
||||||
|
weekday := int(jan4.Weekday())
|
||||||
|
if weekday == 0 {
|
||||||
|
weekday = 7 // Sonntag -> 7
|
||||||
|
}
|
||||||
|
daysToMonday := weekday - 1
|
||||||
|
mondayWeek1 := jan4.AddDate(0, 0, -daysToMonday)
|
||||||
|
|
||||||
|
// Berechne Montag der gewünschten Woche
|
||||||
|
targetMonday := mondayWeek1.AddDate(0, 0, (week-1)*7)
|
||||||
|
|
||||||
|
dates := make(map[string]string)
|
||||||
|
weekDays := []string{"0", "1", "2", "3", "4"} // Montag bis Freitag
|
||||||
|
|
||||||
|
var firstDate, lastDate time.Time
|
||||||
|
for i, day := range weekDays {
|
||||||
|
date := targetMonday.AddDate(0, 0, i)
|
||||||
|
dates[day] = date.Format("2006-01-02")
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
firstDate = date
|
||||||
|
}
|
||||||
|
if i == 4 {
|
||||||
|
lastDate = date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rangeStr := firstDate.Format("2006-01-02") + " bis " + lastDate.Format("2006-01-02")
|
||||||
|
|
||||||
|
return WeekDates{
|
||||||
|
Year: year,
|
||||||
|
Week: week,
|
||||||
|
Dates: dates,
|
||||||
|
Range: rangeStr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ func main() {
|
||||||
protected.GET("/schedules", app.GetSchedulesHandler)
|
protected.GET("/schedules", app.GetSchedulesHandler)
|
||||||
protected.POST("/time-entries", app.CreateTimeEntryHandler)
|
protected.POST("/time-entries", app.CreateTimeEntryHandler)
|
||||||
protected.GET("/my-time-entries", app.GetMyTimeEntriesHandler)
|
protected.GET("/my-time-entries", app.GetMyTimeEntriesHandler)
|
||||||
|
protected.DELETE("/my-time-entries/week", app.DeleteWeekEntries)
|
||||||
|
protected.GET("/week-dates", app.GetWeekDates) // NEU
|
||||||
|
protected.GET("/week-has-entries", app.CheckWeekHasEntries) // NEU
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin routes group
|
// Admin routes group
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import Json.Decode as Decode exposing (Decoder, field, int, string, bool, list,
|
||||||
import Json.Encode as Encode
|
import Json.Encode as Encode
|
||||||
import Task
|
import Task
|
||||||
import Time
|
import Time
|
||||||
|
import Dict exposing (Dict)
|
||||||
|
|
||||||
|
|
||||||
-- PORTS
|
-- PORTS
|
||||||
|
|
@ -45,12 +46,37 @@ type alias Model =
|
||||||
, selectedEntries : List SelectedEntry
|
, selectedEntries : List SelectedEntry
|
||||||
, currentWeek : Int
|
, currentWeek : Int
|
||||||
, currentYear : Int
|
, currentYear : Int
|
||||||
|
, weekDates : Maybe WeekDates -- NEU: Backend liefert Daten
|
||||||
, currentTime : Time.Posix
|
, currentTime : Time.Posix
|
||||||
, zone : Time.Zone
|
, zone : Time.Zone
|
||||||
, newSchedule : NewSchedule
|
, newSchedule : NewSchedule
|
||||||
, newUser : NewUser
|
, newUser : NewUser
|
||||||
, error : Maybe String
|
, error : Maybe String
|
||||||
|
, weekEditMode : Bool
|
||||||
|
, hasEntriesForCurrentWeek : Bool
|
||||||
}
|
}
|
||||||
|
-- type alias Model =
|
||||||
|
-- { page : Page
|
||||||
|
-- , activeTab : AdminTab
|
||||||
|
-- , username : String
|
||||||
|
-- , password : String
|
||||||
|
-- , token : Maybe String
|
||||||
|
-- , isAdmin : Bool
|
||||||
|
-- , schedules : List Schedule
|
||||||
|
-- , users : List User
|
||||||
|
-- , timeEntries : List TimeEntry
|
||||||
|
-- , weeklyHours : List WeeklyHours
|
||||||
|
-- , selectedEntries : List SelectedEntry
|
||||||
|
-- , currentWeek : Int
|
||||||
|
-- , currentYear : Int
|
||||||
|
-- , currentTime : Time.Posix
|
||||||
|
-- , zone : Time.Zone
|
||||||
|
-- , newSchedule : NewSchedule
|
||||||
|
-- , newUser : NewUser
|
||||||
|
-- , error : Maybe String
|
||||||
|
-- , weekEditMode : Bool -- NEU: Edit-Modus für die Woche
|
||||||
|
-- , hasEntriesForCurrentWeek : Bool -- NEU: Hat die aktuelle Woche bereits Einträge?
|
||||||
|
-- }
|
||||||
|
|
||||||
type Page
|
type Page
|
||||||
= LoginPage
|
= LoginPage
|
||||||
|
|
@ -115,6 +141,13 @@ type alias NewUser =
|
||||||
, isAdmin : Bool
|
, isAdmin : Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type alias WeekDates =
|
||||||
|
{ year : Int
|
||||||
|
, week : Int
|
||||||
|
, dates : List (String, String) -- [(dayOfWeek, date)]
|
||||||
|
, range : String
|
||||||
|
}
|
||||||
|
|
||||||
init : Maybe String -> (Model, Cmd Msg)
|
init : Maybe String -> (Model, Cmd Msg)
|
||||||
init storedToken =
|
init storedToken =
|
||||||
let
|
let
|
||||||
|
|
@ -137,19 +170,34 @@ init storedToken =
|
||||||
, newSchedule = NewSchedule "" "" "" "lesson" ""
|
, newSchedule = NewSchedule "" "" "" "lesson" ""
|
||||||
, newUser = NewUser "" "" False
|
, newUser = NewUser "" "" False
|
||||||
, error = Nothing
|
, error = Nothing
|
||||||
|
, weekEditMode = False
|
||||||
|
, hasEntriesForCurrentWeek = False
|
||||||
|
, weekDates = Nothing -- NEU
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd =
|
cmd =
|
||||||
case storedToken of
|
case storedToken of
|
||||||
Just token ->
|
Just token ->
|
||||||
Cmd.batch
|
Cmd.batch
|
||||||
[ Task.perform SetTime Time.now
|
[ Task.perform SetTime Time.now -- Dies lädt dann automatisch Daten
|
||||||
, fetchSchedules (Just token)
|
, fetchSchedules (Just token)
|
||||||
]
|
]
|
||||||
Nothing ->
|
Nothing ->
|
||||||
Task.perform SetTime Time.now
|
Task.perform SetTime Time.now
|
||||||
in
|
in
|
||||||
(model, cmd)
|
(model, cmd)
|
||||||
|
-- cmd =
|
||||||
|
-- case storedToken of
|
||||||
|
-- Just token ->
|
||||||
|
-- Cmd.batch
|
||||||
|
-- [ Task.perform SetTime Time.now
|
||||||
|
-- , fetchSchedules (Just token)
|
||||||
|
-- , fetchMyTimeEntries token
|
||||||
|
-- ]
|
||||||
|
-- Nothing ->
|
||||||
|
-- Task.perform SetTime Time.now
|
||||||
|
-- in
|
||||||
|
-- (model, cmd)
|
||||||
|
|
||||||
|
|
||||||
-- UPDATE
|
-- UPDATE
|
||||||
|
|
@ -168,6 +216,10 @@ type Msg
|
||||||
| TimeEntriesSaved (Result Http.Error ())
|
| TimeEntriesSaved (Result Http.Error ())
|
||||||
| PreviousWeek
|
| PreviousWeek
|
||||||
| NextWeek
|
| NextWeek
|
||||||
|
| EnableEditMode -- NEU
|
||||||
|
| DisableEditMode -- NEU
|
||||||
|
| DeleteWeekEntries -- NEU
|
||||||
|
| WeekEntriesDeleted (Result Http.Error ()) -- NEU
|
||||||
| SwitchTab AdminTab
|
| SwitchTab AdminTab
|
||||||
| UpdateNewScheduleDay String
|
| UpdateNewScheduleDay String
|
||||||
| UpdateNewScheduleStart String
|
| UpdateNewScheduleStart String
|
||||||
|
|
@ -187,10 +239,16 @@ type Msg
|
||||||
| UserDeleted (Result Http.Error ())
|
| UserDeleted (Result Http.Error ())
|
||||||
| FetchUsers
|
| FetchUsers
|
||||||
| UsersReceived (Result Http.Error (List User))
|
| UsersReceived (Result Http.Error (List User))
|
||||||
|
| FetchMyTimeEntries -- NEU
|
||||||
|
| MyTimeEntriesReceived (Result Http.Error (List TimeEntry)) -- NEU
|
||||||
| FetchAllTimeEntries
|
| FetchAllTimeEntries
|
||||||
| AllTimeEntriesReceived (Result Http.Error (List TimeEntry))
|
| AllTimeEntriesReceived (Result Http.Error (List TimeEntry))
|
||||||
| FetchWeeklyHours
|
| FetchWeeklyHours
|
||||||
| WeeklyHoursReceived (Result Http.Error (List WeeklyHours))
|
| WeeklyHoursReceived (Result Http.Error (List WeeklyHours))
|
||||||
|
| FetchWeekDates
|
||||||
|
| WeekDatesReceived (Result Http.Error WeekDates)
|
||||||
|
| CheckWeekHasEntries
|
||||||
|
| WeekHasEntriesReceived (Result Http.Error Bool)
|
||||||
|
|
||||||
update : Msg -> Model -> (Model, Cmd Msg)
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
update msg model =
|
update msg model =
|
||||||
|
|
@ -210,12 +268,14 @@ update msg model =
|
||||||
in
|
in
|
||||||
({ model
|
({ model
|
||||||
| token = Just result.token
|
| token = Just result.token
|
||||||
|
, username = result.username
|
||||||
, isAdmin = result.isAdmin
|
, isAdmin = result.isAdmin
|
||||||
, page = newPage
|
, page = newPage
|
||||||
, error = Nothing
|
, error = Nothing
|
||||||
}, Cmd.batch
|
}, Cmd.batch
|
||||||
[ saveToken result.token
|
[ saveToken result.token
|
||||||
, fetchSchedules (Just result.token)
|
, fetchSchedules (Just result.token)
|
||||||
|
, if not result.isAdmin then fetchMyTimeEntries result.token else Cmd.none
|
||||||
])
|
])
|
||||||
|
|
||||||
LoginResponse (Err _) ->
|
LoginResponse (Err _) ->
|
||||||
|
|
@ -230,15 +290,15 @@ update msg model =
|
||||||
, password = ""
|
, password = ""
|
||||||
}, removeToken ())
|
}, removeToken ())
|
||||||
|
|
||||||
SetTime time ->
|
-- SetTime time ->
|
||||||
let
|
-- let
|
||||||
(year, week) = getISOWeekFromPosix time
|
-- (year, week) = getISOWeekFromPosix time
|
||||||
in
|
-- in
|
||||||
({ model
|
-- ({ model
|
||||||
| currentTime = time
|
-- | currentTime = time
|
||||||
, currentWeek = week
|
-- , currentWeek = week
|
||||||
, currentYear = year
|
-- , currentYear = year
|
||||||
}, Cmd.none)
|
-- }, Cmd.none)
|
||||||
|
|
||||||
FetchSchedules ->
|
FetchSchedules ->
|
||||||
(model, fetchSchedules model.token)
|
(model, fetchSchedules model.token)
|
||||||
|
|
@ -250,6 +310,7 @@ update msg model =
|
||||||
({ model | error = Just "Fehler beim Laden des Stundenplans" }, Cmd.none)
|
({ model | error = Just "Fehler beim Laden des Stundenplans" }, Cmd.none)
|
||||||
|
|
||||||
ToggleScheduleSelection scheduleId dayOfWeek ->
|
ToggleScheduleSelection scheduleId dayOfWeek ->
|
||||||
|
if model.weekEditMode then
|
||||||
let
|
let
|
||||||
entry = { scheduleId = scheduleId, dayOfWeek = dayOfWeek }
|
entry = { scheduleId = scheduleId, dayOfWeek = dayOfWeek }
|
||||||
newSelected =
|
newSelected =
|
||||||
|
|
@ -259,6 +320,8 @@ update msg model =
|
||||||
entry :: model.selectedEntries
|
entry :: model.selectedEntries
|
||||||
in
|
in
|
||||||
({ model | selectedEntries = newSelected }, Cmd.none)
|
({ model | selectedEntries = newSelected }, Cmd.none)
|
||||||
|
else
|
||||||
|
(model, Cmd.none)
|
||||||
|
|
||||||
SaveTimeEntries ->
|
SaveTimeEntries ->
|
||||||
case model.token of
|
case model.token of
|
||||||
|
|
@ -268,7 +331,16 @@ update msg model =
|
||||||
(model, Cmd.none)
|
(model, Cmd.none)
|
||||||
|
|
||||||
TimeEntriesSaved (Ok _) ->
|
TimeEntriesSaved (Ok _) ->
|
||||||
({ model | selectedEntries = [], error = Nothing }, Cmd.none)
|
case model.token of
|
||||||
|
Just token ->
|
||||||
|
({ model
|
||||||
|
| selectedEntries = []
|
||||||
|
, error = Nothing
|
||||||
|
, weekEditMode = False
|
||||||
|
, hasEntriesForCurrentWeek = True
|
||||||
|
}, fetchMyTimeEntries token)
|
||||||
|
Nothing ->
|
||||||
|
(model, Cmd.none)
|
||||||
|
|
||||||
TimeEntriesSaved (Err _) ->
|
TimeEntriesSaved (Err _) ->
|
||||||
({ model | error = Just "Fehler beim Speichern" }, Cmd.none)
|
({ model | error = Just "Fehler beim Speichern" }, Cmd.none)
|
||||||
|
|
@ -277,13 +349,198 @@ update msg model =
|
||||||
let
|
let
|
||||||
(newYear, newWeek) = previousWeek model.currentYear model.currentWeek
|
(newYear, newWeek) = previousWeek model.currentYear model.currentWeek
|
||||||
in
|
in
|
||||||
({ model | currentWeek = newWeek, currentYear = newYear, selectedEntries = [] }, Cmd.none)
|
({ model
|
||||||
|
| currentWeek = newWeek
|
||||||
|
, currentYear = newYear
|
||||||
|
, selectedEntries = []
|
||||||
|
, weekEditMode = False
|
||||||
|
}, case model.token of
|
||||||
|
Just token ->
|
||||||
|
Cmd.batch
|
||||||
|
[ fetchWeekDates token newYear newWeek
|
||||||
|
, checkWeekHasEntries token newYear newWeek
|
||||||
|
]
|
||||||
|
Nothing ->
|
||||||
|
Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
NextWeek ->
|
NextWeek ->
|
||||||
let
|
let
|
||||||
(newYear, newWeek) = nextWeek model.currentYear model.currentWeek
|
(newYear, newWeek) = nextWeek model.currentYear model.currentWeek
|
||||||
in
|
in
|
||||||
({ model | currentWeek = newWeek, currentYear = newYear, selectedEntries = [] }, Cmd.none)
|
({ model
|
||||||
|
| currentWeek = newWeek
|
||||||
|
, currentYear = newYear
|
||||||
|
, selectedEntries = []
|
||||||
|
, weekEditMode = False
|
||||||
|
}, case model.token of
|
||||||
|
Just token ->
|
||||||
|
Cmd.batch
|
||||||
|
[ fetchWeekDates token newYear newWeek
|
||||||
|
, checkWeekHasEntries token newYear newWeek
|
||||||
|
]
|
||||||
|
Nothing ->
|
||||||
|
Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchWeekDates ->
|
||||||
|
case model.token of
|
||||||
|
Just token ->
|
||||||
|
(model, fetchWeekDates token model.currentYear model.currentWeek)
|
||||||
|
Nothing ->
|
||||||
|
(model, Cmd.none)
|
||||||
|
|
||||||
|
WeekDatesReceived (Ok weekDates) ->
|
||||||
|
({ model | weekDates = Just weekDates }, Cmd.none)
|
||||||
|
|
||||||
|
WeekDatesReceived (Err _) ->
|
||||||
|
({ model | error = Just "Fehler beim Laden der Wochendaten" }, Cmd.none)
|
||||||
|
|
||||||
|
CheckWeekHasEntries ->
|
||||||
|
case model.token of
|
||||||
|
Just token ->
|
||||||
|
(model, checkWeekHasEntries token model.currentYear model.currentWeek)
|
||||||
|
Nothing ->
|
||||||
|
(model, Cmd.none)
|
||||||
|
|
||||||
|
WeekHasEntriesReceived (Ok hasEntries) ->
|
||||||
|
({ model | hasEntriesForCurrentWeek = hasEntries }, Cmd.none)
|
||||||
|
|
||||||
|
WeekHasEntriesReceived (Err _) ->
|
||||||
|
(model, Cmd.none)
|
||||||
|
|
||||||
|
SetTime time ->
|
||||||
|
let
|
||||||
|
(year, week) = getISOWeekFromPosix time
|
||||||
|
in
|
||||||
|
({ model
|
||||||
|
| currentTime = time
|
||||||
|
, currentWeek = week
|
||||||
|
, currentYear = year
|
||||||
|
}, case model.token of
|
||||||
|
Just token ->
|
||||||
|
Cmd.batch
|
||||||
|
[ fetchWeekDates token year week
|
||||||
|
, checkWeekHasEntries token year week
|
||||||
|
]
|
||||||
|
Nothing ->
|
||||||
|
Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
-- PreviousWeek ->
|
||||||
|
-- let
|
||||||
|
-- (newYear, newWeek) = previousWeek model.currentYear model.currentWeek
|
||||||
|
-- in
|
||||||
|
-- ({ model
|
||||||
|
-- | currentWeek = newWeek
|
||||||
|
-- , currentYear = newYear
|
||||||
|
-- , selectedEntries = []
|
||||||
|
-- , weekEditMode = False
|
||||||
|
-- , hasEntriesForCurrentWeek = False -- WICHTIG: Zurücksetzen!
|
||||||
|
-- }, case model.token of
|
||||||
|
-- Just token -> fetchMyTimeEntries token
|
||||||
|
-- Nothing -> Cmd.none
|
||||||
|
-- )
|
||||||
|
|
||||||
|
-- NextWeek ->
|
||||||
|
-- let
|
||||||
|
-- (newYear, newWeek) = nextWeek model.currentYear model.currentWeek
|
||||||
|
-- in
|
||||||
|
-- ({ model
|
||||||
|
-- | currentWeek = newWeek
|
||||||
|
-- , currentYear = newYear
|
||||||
|
-- , selectedEntries = []
|
||||||
|
-- , weekEditMode = False
|
||||||
|
-- , hasEntriesForCurrentWeek = False -- WICHTIG: Zurücksetzen!
|
||||||
|
-- }, case model.token of
|
||||||
|
-- Just token -> fetchMyTimeEntries token
|
||||||
|
-- Nothing -> Cmd.none
|
||||||
|
-- )
|
||||||
|
-- PreviousWeek ->
|
||||||
|
-- let
|
||||||
|
-- (newYear, newWeek) = previousWeek model.currentYear model.currentWeek
|
||||||
|
-- in
|
||||||
|
-- ({ model
|
||||||
|
-- | currentWeek = newWeek
|
||||||
|
-- , currentYear = newYear
|
||||||
|
-- , selectedEntries = []
|
||||||
|
-- , weekEditMode = False
|
||||||
|
-- }, case model.token of
|
||||||
|
-- Just token -> fetchMyTimeEntries token
|
||||||
|
-- Nothing -> Cmd.none
|
||||||
|
-- )
|
||||||
|
|
||||||
|
-- NextWeek ->
|
||||||
|
-- let
|
||||||
|
-- (newYear, newWeek) = nextWeek model.currentYear model.currentWeek
|
||||||
|
-- in
|
||||||
|
-- ({ model
|
||||||
|
-- | currentWeek = newWeek
|
||||||
|
-- , currentYear = newYear
|
||||||
|
-- , selectedEntries = []
|
||||||
|
-- , weekEditMode = False
|
||||||
|
-- }, case model.token of
|
||||||
|
-- Just token -> fetchMyTimeEntries token
|
||||||
|
-- Nothing -> Cmd.none
|
||||||
|
-- )
|
||||||
|
|
||||||
|
EnableEditMode ->
|
||||||
|
let
|
||||||
|
-- Lade bestehende Einträge in selectedEntries
|
||||||
|
currentWeekEntries = List.filter
|
||||||
|
(\e ->
|
||||||
|
let
|
||||||
|
(entryYear, entryWeek) = getYearWeekFromDate e.date
|
||||||
|
in
|
||||||
|
entryWeek == model.currentWeek && entryYear == model.currentYear
|
||||||
|
)
|
||||||
|
model.timeEntries
|
||||||
|
|
||||||
|
preSelectedEntries = List.map
|
||||||
|
(\entry ->
|
||||||
|
-- Finde den dayOfWeek aus dem Datum
|
||||||
|
let
|
||||||
|
parts = String.split "-" entry.date
|
||||||
|
year = parts |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 2025
|
||||||
|
month = parts |> List.drop 1 |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 1
|
||||||
|
day = parts |> List.drop 2 |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 1
|
||||||
|
dayOfWeek = (getDayOfWeek year month day)
|
||||||
|
in
|
||||||
|
{ scheduleId = entry.scheduleId, dayOfWeek = dayOfWeek }
|
||||||
|
)
|
||||||
|
currentWeekEntries
|
||||||
|
in
|
||||||
|
({ model
|
||||||
|
| weekEditMode = True
|
||||||
|
, selectedEntries = preSelectedEntries
|
||||||
|
}, Cmd.none)
|
||||||
|
|
||||||
|
DisableEditMode ->
|
||||||
|
({ model
|
||||||
|
| weekEditMode = False
|
||||||
|
, selectedEntries = []
|
||||||
|
}, Cmd.none)
|
||||||
|
|
||||||
|
DeleteWeekEntries ->
|
||||||
|
case model.token of
|
||||||
|
Just token ->
|
||||||
|
(model, deleteWeekEntries token model.currentYear model.currentWeek)
|
||||||
|
Nothing ->
|
||||||
|
(model, Cmd.none)
|
||||||
|
|
||||||
|
WeekEntriesDeleted (Ok _) ->
|
||||||
|
case model.token of
|
||||||
|
Just token ->
|
||||||
|
({ model
|
||||||
|
| weekEditMode = True
|
||||||
|
, selectedEntries = []
|
||||||
|
, hasEntriesForCurrentWeek = False
|
||||||
|
}, fetchMyTimeEntries token)
|
||||||
|
Nothing ->
|
||||||
|
(model, Cmd.none)
|
||||||
|
|
||||||
|
WeekEntriesDeleted (Err _) ->
|
||||||
|
({ model | error = Just "Fehler beim Löschen" }, Cmd.none)
|
||||||
|
|
||||||
SwitchTab tab ->
|
SwitchTab tab ->
|
||||||
let
|
let
|
||||||
|
|
@ -294,7 +551,6 @@ update msg model =
|
||||||
fetchUsers token
|
fetchUsers token
|
||||||
Nothing ->
|
Nothing ->
|
||||||
Cmd.none
|
Cmd.none
|
||||||
-- fetchUsers model.token
|
|
||||||
TimeEntriesTab ->
|
TimeEntriesTab ->
|
||||||
case model.token of
|
case model.token of
|
||||||
Just token ->
|
Just token ->
|
||||||
|
|
@ -410,7 +666,6 @@ update msg model =
|
||||||
({ model | newUser = emptyUser }, fetchUsers token)
|
({ model | newUser = emptyUser }, fetchUsers token)
|
||||||
Nothing ->
|
Nothing ->
|
||||||
(model, Cmd.none)
|
(model, Cmd.none)
|
||||||
-- ({ model | newUser = emptyUser }, fetchUsers model.token)
|
|
||||||
|
|
||||||
UserCreated (Err _) ->
|
UserCreated (Err _) ->
|
||||||
({ model | error = Just "Fehler beim Erstellen des Benutzers" }, Cmd.none)
|
({ model | error = Just "Fehler beim Erstellen des Benutzers" }, Cmd.none)
|
||||||
|
|
@ -428,7 +683,6 @@ update msg model =
|
||||||
(model, fetchUsers token)
|
(model, fetchUsers token)
|
||||||
Nothing ->
|
Nothing ->
|
||||||
(model, Cmd.none)
|
(model, Cmd.none)
|
||||||
-- (model, fetchUsers model.token)
|
|
||||||
|
|
||||||
UserDeleted (Err _) ->
|
UserDeleted (Err _) ->
|
||||||
({ model | error = Just "Fehler beim Löschen des Benutzers" }, Cmd.none)
|
({ model | error = Just "Fehler beim Löschen des Benutzers" }, Cmd.none)
|
||||||
|
|
@ -446,6 +700,33 @@ update msg model =
|
||||||
UsersReceived (Err _) ->
|
UsersReceived (Err _) ->
|
||||||
({ model | error = Just "Fehler beim Laden der Benutzer" }, Cmd.none)
|
({ model | error = Just "Fehler beim Laden der Benutzer" }, Cmd.none)
|
||||||
|
|
||||||
|
FetchMyTimeEntries ->
|
||||||
|
case model.token of
|
||||||
|
Just token ->
|
||||||
|
(model, fetchMyTimeEntries token)
|
||||||
|
Nothing ->
|
||||||
|
(model, Cmd.none)
|
||||||
|
|
||||||
|
MyTimeEntriesReceived (Ok entries) ->
|
||||||
|
let
|
||||||
|
hasEntries = List.any
|
||||||
|
(\e ->
|
||||||
|
let
|
||||||
|
(entryYear, entryWeek) = getYearWeekFromDate e.date
|
||||||
|
in
|
||||||
|
entryWeek == model.currentWeek && entryYear == model.currentYear
|
||||||
|
)
|
||||||
|
entries
|
||||||
|
in
|
||||||
|
({ model
|
||||||
|
| timeEntries = entries
|
||||||
|
, hasEntriesForCurrentWeek = hasEntries
|
||||||
|
, weekEditMode = False
|
||||||
|
}, Cmd.none)
|
||||||
|
|
||||||
|
MyTimeEntriesReceived (Err _) ->
|
||||||
|
({ model | error = Just "Fehler beim Laden der Einträge" }, Cmd.none)
|
||||||
|
|
||||||
FetchAllTimeEntries ->
|
FetchAllTimeEntries ->
|
||||||
case model.token of
|
case model.token of
|
||||||
Just token ->
|
Just token ->
|
||||||
|
|
@ -511,11 +792,56 @@ getISOWeek : Int -> Int -> Int -> Int
|
||||||
getISOWeek year month day =
|
getISOWeek year month day =
|
||||||
let
|
let
|
||||||
dayOfYear = getDayOfYear year month day
|
dayOfYear = getDayOfYear year month day
|
||||||
jan1DayOfWeek = getDayOfWeek year 1 1
|
|
||||||
weekDay = modBy 7 (jan1DayOfWeek + dayOfYear - 1)
|
-- Wochentag des 4. Januar (definiert ISO Woche 1)
|
||||||
weekNumber = ((dayOfYear + jan1DayOfWeek - 1) // 7) + 1
|
jan4DayOfWeek = getDayOfWeek year 1 4
|
||||||
|
|
||||||
|
-- Tag des Jahres für den Montag von Woche 1
|
||||||
|
-- Der 4. Januar ist immer in Woche 1
|
||||||
|
mondayOfWeek1DayOfYear = 4 - jan4DayOfWeek
|
||||||
|
|
||||||
|
-- Berechne die Wochennummer
|
||||||
|
weekNum = ((dayOfYear - mondayOfWeek1DayOfYear) // 7) + 1
|
||||||
in
|
in
|
||||||
if weekNumber > 52 then 52 else if weekNumber < 1 then 1 else weekNumber
|
if weekNum < 1 then
|
||||||
|
-- Gehört zur letzten Woche des Vorjahres
|
||||||
|
52 -- Vereinfachung: könnte auch 53 sein
|
||||||
|
else if weekNum > 52 then
|
||||||
|
let
|
||||||
|
-- Prüfe ob Jahr 53 Wochen hat
|
||||||
|
dec31DayOfWeek = getDayOfWeek year 12 31
|
||||||
|
jan1DayOfWeek = getDayOfWeek year 1 1
|
||||||
|
in
|
||||||
|
-- Jahr hat 53 Wochen wenn 1. Januar ein Donnerstag ist
|
||||||
|
-- oder 31. Dezember ein Donnerstag ist (bei Schaltjahren)
|
||||||
|
if jan1DayOfWeek == 3 || (isLeapYear year && jan1DayOfWeek == 2) then
|
||||||
|
weekNum
|
||||||
|
else
|
||||||
|
1
|
||||||
|
else
|
||||||
|
weekNum
|
||||||
|
-- -- Korrigierte ISO-8601 Wochenberechnung
|
||||||
|
-- getISOWeek : Int -> Int -> Int -> Int
|
||||||
|
-- getISOWeek year month day =
|
||||||
|
-- let
|
||||||
|
-- dayOfYear = getDayOfYear year month day
|
||||||
|
-- jan1DayOfWeek = getDayOfWeek year 1 1
|
||||||
|
|
||||||
|
-- -- ISO 8601: Woche beginnt Montag (0), Jahr beginnt mit der Woche die den 4. Januar enthält
|
||||||
|
-- correction = (jan1DayOfWeek + 6) |> modBy 7 -- Montag = 0
|
||||||
|
-- weekNumber = (dayOfYear + correction - 1) // 7
|
||||||
|
-- in
|
||||||
|
-- if weekNumber == 0 then
|
||||||
|
-- -- Gehört zur letzten Woche des Vorjahres
|
||||||
|
-- getISOWeek (year - 1) 12 31
|
||||||
|
-- else if weekNumber > 52 then
|
||||||
|
-- let
|
||||||
|
-- dec31DayOfWeek = getDayOfWeek year 12 31
|
||||||
|
-- in
|
||||||
|
-- -- Prüfe ob es Woche 53 ist oder schon Woche 1 des nächsten Jahres
|
||||||
|
-- if dec31DayOfWeek < 3 then 1 else weekNumber
|
||||||
|
-- else
|
||||||
|
-- weekNumber
|
||||||
|
|
||||||
getDayOfYear : Int -> Int -> Int -> Int
|
getDayOfYear : Int -> Int -> Int -> Int
|
||||||
getDayOfYear year month day =
|
getDayOfYear year month day =
|
||||||
|
|
@ -529,6 +855,7 @@ isLeapYear : Int -> Bool
|
||||||
isLeapYear year =
|
isLeapYear year =
|
||||||
(modBy 4 year == 0) && ((modBy 100 year /= 0) || (modBy 400 year == 0))
|
(modBy 4 year == 0) && ((modBy 100 year /= 0) || (modBy 400 year == 0))
|
||||||
|
|
||||||
|
-- Korrigierter getDayOfWeek: Montag = 0, Sonntag = 6 (ISO 8601)
|
||||||
getDayOfWeek : Int -> Int -> Int -> Int
|
getDayOfWeek : Int -> Int -> Int -> Int
|
||||||
getDayOfWeek year month day =
|
getDayOfWeek year month day =
|
||||||
let
|
let
|
||||||
|
|
@ -540,25 +867,56 @@ getDayOfWeek year month day =
|
||||||
j = adjustedYear // 100
|
j = adjustedYear // 100
|
||||||
h = (q + ((13 * (m + 1)) // 5) + k + (k // 4) + (j // 4) - (2 * j)) |> modBy 7
|
h = (q + ((13 * (m + 1)) // 5) + k + (k // 4) + (j // 4) - (2 * j)) |> modBy 7
|
||||||
in
|
in
|
||||||
|
-- Konvertiere: Zeller gibt Samstag=0, Sonntag=1, ... Freitag=6
|
||||||
|
-- ISO 8601 will: Montag=0, ..., Sonntag=6
|
||||||
(h + 5) |> modBy 7
|
(h + 5) |> modBy 7
|
||||||
|
|
||||||
|
-- Korrigiertes getDateForWeekDay
|
||||||
getDateForWeekDay : Int -> Int -> Int -> String
|
getDateForWeekDay : Int -> Int -> Int -> String
|
||||||
getDateForWeekDay year week dayOfWeek =
|
getDateForWeekDay year week dayOfWeek =
|
||||||
let
|
let
|
||||||
|
-- Finde den 4. Januar (immer in Woche 1 nach ISO 8601)
|
||||||
jan4DayOfWeek = getDayOfWeek year 1 4
|
jan4DayOfWeek = getDayOfWeek year 1 4
|
||||||
daysToMonday = jan4DayOfWeek
|
|
||||||
firstMondayOfYear = 4 - daysToMonday
|
|
||||||
daysFromFirstMonday = (week - 1) * 7 + dayOfWeek
|
|
||||||
totalDays = firstMondayOfYear + daysFromFirstMonday
|
|
||||||
|
|
||||||
(finalYear, finalMonth, finalDay) = addDaysToDate year 1 1 totalDays
|
-- Montag von Woche 1
|
||||||
|
-- Wenn der 4. Januar z.B. ein Mittwoch ist (dayOfWeek=2),
|
||||||
|
-- dann ist Montag 2 Tage früher, also der 2. Januar
|
||||||
|
mondayOfWeek1Date = 4 - jan4DayOfWeek
|
||||||
|
|
||||||
|
-- Berechne den Tag: Montag Woche 1 + (Woche - 1) * 7 Tage + Wochentag
|
||||||
|
targetDayOfYear = mondayOfWeek1Date + ((week - 1) * 7) + dayOfWeek
|
||||||
|
|
||||||
|
(finalYear, finalMonth, finalDay) =
|
||||||
|
if targetDayOfYear < 1 then
|
||||||
|
-- Datum liegt im Vorjahr
|
||||||
|
addDaysToDate (year - 1) 12 31 (targetDayOfYear)
|
||||||
|
else
|
||||||
|
addDaysToDate year 1 targetDayOfYear 0
|
||||||
in
|
in
|
||||||
String.fromInt finalYear ++ "-" ++
|
String.fromInt finalYear ++ "-" ++
|
||||||
String.padLeft 2 '0' (String.fromInt finalMonth) ++ "-" ++
|
String.padLeft 2 '0' (String.fromInt finalMonth) ++ "-" ++
|
||||||
String.padLeft 2 '0' (String.fromInt finalDay)
|
String.padLeft 2 '0' (String.fromInt finalDay)
|
||||||
|
-- getDateForWeekDay : Int -> Int -> Int -> String
|
||||||
|
-- getDateForWeekDay year week dayOfWeek =
|
||||||
|
-- let
|
||||||
|
-- -- Finde den ersten Montag der ersten ISO-Woche
|
||||||
|
-- jan4 = { year = year, month = 1, day = 4 }
|
||||||
|
-- jan4DayOfWeek = getDayOfWeek year 1 4
|
||||||
|
|
||||||
|
-- -- Montag der Woche 1 (die Woche mit dem 4. Januar)
|
||||||
|
-- mondayOfWeek1 = 4 - jan4DayOfWeek
|
||||||
|
|
||||||
|
-- -- Berechne Tage vom Jahresbeginn
|
||||||
|
-- daysFromJan1 = mondayOfWeek1 + (week - 1) * 7 + dayOfWeek
|
||||||
|
|
||||||
|
-- (finalYear, finalMonth, finalDay) = addDaysToDate year 1 1 daysFromJan1
|
||||||
|
-- in
|
||||||
|
-- String.fromInt finalYear ++ "-" ++
|
||||||
|
-- String.padLeft 2 '0' (String.fromInt finalMonth) ++ "-" ++
|
||||||
|
-- String.padLeft 2 '0' (String.fromInt finalDay)
|
||||||
|
|
||||||
addDaysToDate : Int -> Int -> Int -> Int -> (Int, Int, Int)
|
addDaysToDate : Int -> Int -> Int -> Int -> (Int, Int, Int)
|
||||||
addDaysToDate year month day daysToAdd =
|
addDaysToDate startYear startMonth startDay daysToAdd =
|
||||||
let
|
let
|
||||||
daysInMonth m y =
|
daysInMonth m y =
|
||||||
case m of
|
case m of
|
||||||
|
|
@ -577,21 +935,71 @@ addDaysToDate year month day daysToAdd =
|
||||||
_ -> 0
|
_ -> 0
|
||||||
|
|
||||||
helper y m d remaining =
|
helper y m d remaining =
|
||||||
if remaining <= 0 then
|
if remaining == 0 then
|
||||||
(y, m, d)
|
(y, m, d)
|
||||||
else
|
else if remaining > 0 then
|
||||||
|
-- Vorwärts zählen
|
||||||
let
|
let
|
||||||
daysInCurrentMonth = daysInMonth m y
|
daysInCurrentMonth = daysInMonth m y
|
||||||
daysLeftInMonth = daysInCurrentMonth - d + 1
|
daysLeftInMonth = daysInCurrentMonth - d
|
||||||
in
|
in
|
||||||
if remaining < daysLeftInMonth then
|
if remaining <= daysLeftInMonth then
|
||||||
(y, m, d + remaining)
|
(y, m, d + remaining)
|
||||||
else if m == 12 then
|
else if m == 12 then
|
||||||
helper (y + 1) 1 1 (remaining - daysLeftInMonth)
|
helper (y + 1) 1 1 (remaining - daysLeftInMonth - 1)
|
||||||
else
|
else
|
||||||
helper y (m + 1) 1 (remaining - daysLeftInMonth)
|
helper y (m + 1) 1 (remaining - daysLeftInMonth - 1)
|
||||||
|
else
|
||||||
|
-- Rückwärts zählen
|
||||||
|
if d + remaining >= 1 then
|
||||||
|
(y, m, d + remaining)
|
||||||
|
else if m == 1 then
|
||||||
|
let
|
||||||
|
prevMonthDays = daysInMonth 12 (y - 1)
|
||||||
in
|
in
|
||||||
helper year month day daysToAdd
|
helper (y - 1) 12 prevMonthDays (remaining + d)
|
||||||
|
else
|
||||||
|
let
|
||||||
|
prevMonthDays = daysInMonth (m - 1) y
|
||||||
|
in
|
||||||
|
helper y (m - 1) prevMonthDays (remaining + d)
|
||||||
|
in
|
||||||
|
helper startYear startMonth startDay daysToAdd
|
||||||
|
-- addDaysToDate : Int -> Int -> Int -> Int -> (Int, Int, Int)
|
||||||
|
-- addDaysToDate year month day daysToAdd =
|
||||||
|
-- let
|
||||||
|
-- daysInMonth m y =
|
||||||
|
-- case m of
|
||||||
|
-- 1 -> 31
|
||||||
|
-- 2 -> if isLeapYear y then 29 else 28
|
||||||
|
-- 3 -> 31
|
||||||
|
-- 4 -> 30
|
||||||
|
-- 5 -> 31
|
||||||
|
-- 6 -> 30
|
||||||
|
-- 7 -> 31
|
||||||
|
-- 8 -> 31
|
||||||
|
-- 9 -> 30
|
||||||
|
-- 10 -> 31
|
||||||
|
-- 11 -> 30
|
||||||
|
-- 12 -> 31
|
||||||
|
-- _ -> 0
|
||||||
|
|
||||||
|
-- helper y m d remaining =
|
||||||
|
-- if remaining <= 0 then
|
||||||
|
-- (y, m, d)
|
||||||
|
-- else
|
||||||
|
-- let
|
||||||
|
-- daysInCurrentMonth = daysInMonth m y
|
||||||
|
-- daysLeftInMonth = daysInCurrentMonth - d + 1
|
||||||
|
-- in
|
||||||
|
-- if remaining < daysLeftInMonth then
|
||||||
|
-- (y, m, d + remaining)
|
||||||
|
-- else if m == 12 then
|
||||||
|
-- helper (y + 1) 1 1 (remaining - daysLeftInMonth)
|
||||||
|
-- else
|
||||||
|
-- helper y (m + 1) 1 (remaining - daysLeftInMonth)
|
||||||
|
-- in
|
||||||
|
-- helper year month day daysToAdd
|
||||||
|
|
||||||
previousWeek : Int -> Int -> (Int, Int)
|
previousWeek : Int -> Int -> (Int, Int)
|
||||||
previousWeek year week =
|
previousWeek year week =
|
||||||
|
|
@ -602,7 +1010,7 @@ previousWeek year week =
|
||||||
|
|
||||||
nextWeek : Int -> Int -> (Int, Int)
|
nextWeek : Int -> Int -> (Int, Int)
|
||||||
nextWeek year week =
|
nextWeek year week =
|
||||||
if week == 52 then
|
if week >= 52 then
|
||||||
(year + 1, 1)
|
(year + 1, 1)
|
||||||
else
|
else
|
||||||
(year, week + 1)
|
(year, week + 1)
|
||||||
|
|
@ -615,6 +1023,16 @@ getWeekDateRange year week =
|
||||||
in
|
in
|
||||||
mondayDate ++ " bis " ++ fridayDate
|
mondayDate ++ " bis " ++ fridayDate
|
||||||
|
|
||||||
|
getYearWeekFromDate : String -> (Int, Int)
|
||||||
|
getYearWeekFromDate dateStr =
|
||||||
|
let
|
||||||
|
parts = String.split "-" dateStr
|
||||||
|
year = parts |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 2025
|
||||||
|
month = parts |> List.drop 1 |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 1
|
||||||
|
day = parts |> List.drop 2 |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 1
|
||||||
|
in
|
||||||
|
(year, getISOWeek year month day)
|
||||||
|
|
||||||
calculateHours : String -> String -> Float
|
calculateHours : String -> String -> Float
|
||||||
calculateHours startTime endTime =
|
calculateHours startTime endTime =
|
||||||
let
|
let
|
||||||
|
|
@ -724,16 +1142,71 @@ viewUserDashboard model =
|
||||||
[ div [ class "container" ]
|
[ div [ class "container" ]
|
||||||
[ viewWeekNavigation model
|
[ viewWeekNavigation model
|
||||||
, h2 [ class "title" ] [ text "Stundenplan" ]
|
, h2 [ class "title" ] [ text "Stundenplan" ]
|
||||||
|
|
||||||
|
-- Status-Anzeige und Bearbeiten-Button
|
||||||
|
, if model.hasEntriesForCurrentWeek && not model.weekEditMode then
|
||||||
|
div [ class "notification is-success" ]
|
||||||
|
[ div [ class "level" ]
|
||||||
|
[ div [ class "level-left" ]
|
||||||
|
[ div [ class "level-item" ]
|
||||||
|
[ span [ class "icon" ]
|
||||||
|
[ i [ class "fas fa-check-circle" ] [] ]
|
||||||
|
, span [] [ text "Diese Woche wurde bereits erfasst" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "level-right" ]
|
||||||
|
[ div [ class "level-item" ]
|
||||||
|
[ button
|
||||||
|
[ class "button is-warning"
|
||||||
|
, onClick EnableEditMode
|
||||||
|
] [ text "Bearbeiten" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
else if model.weekEditMode then
|
||||||
|
div [ class "notification is-warning" ]
|
||||||
|
[ div [ class "level" ]
|
||||||
|
[ div [ class "level-left" ]
|
||||||
|
[ div [ class "level-item" ]
|
||||||
|
[ span [ class "icon" ]
|
||||||
|
[ i [ class "fas fa-edit" ] [] ]
|
||||||
|
, span [] [ text "Bearbeitungsmodus aktiv" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "level-right" ]
|
||||||
|
[ div [ class "level-item" ]
|
||||||
|
[ button
|
||||||
|
[ class "button is-danger is-small mr-2"
|
||||||
|
, onClick DeleteWeekEntries
|
||||||
|
] [ text "Einträge löschen" ]
|
||||||
|
, button
|
||||||
|
[ class "button is-light is-small"
|
||||||
|
, onClick DisableEditMode
|
||||||
|
] [ text "Abbrechen" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
else
|
||||||
|
div [ class "notification is-info is-light" ]
|
||||||
|
[ text "Wählen Sie die Zeiten aus, die Sie in dieser Woche gearbeitet haben." ]
|
||||||
|
|
||||||
, viewScheduleGridWithWeek model
|
, viewScheduleGridWithWeek model
|
||||||
, div [ class "field mt-4" ]
|
|
||||||
|
, if model.weekEditMode || not model.hasEntriesForCurrentWeek then
|
||||||
|
div [ class "field mt-4" ]
|
||||||
[ div [ class "control" ]
|
[ div [ class "control" ]
|
||||||
[ button
|
[ button
|
||||||
[ class "button is-primary is-large is-fullwidth"
|
[ class "button is-primary is-large is-fullwidth"
|
||||||
, onClick SaveTimeEntries
|
, onClick SaveTimeEntries
|
||||||
, disabled (List.isEmpty model.selectedEntries)
|
, disabled (List.isEmpty model.selectedEntries)
|
||||||
] [ text "Speichern" ]
|
] [ text (if model.weekEditMode then "Änderungen speichern" else "Speichern") ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
else
|
||||||
|
text ""
|
||||||
|
|
||||||
, case model.error of
|
, case model.error of
|
||||||
Just err ->
|
Just err ->
|
||||||
div [ class "notification is-danger mt-4" ] [ text err ]
|
div [ class "notification is-danger mt-4" ] [ text err ]
|
||||||
|
|
@ -812,6 +1285,12 @@ viewTimeEntriesTab model =
|
||||||
|
|
||||||
viewWeekNavigation : Model -> Html Msg
|
viewWeekNavigation : Model -> Html Msg
|
||||||
viewWeekNavigation model =
|
viewWeekNavigation model =
|
||||||
|
let
|
||||||
|
dateRange =
|
||||||
|
case model.weekDates of
|
||||||
|
Just wd -> wd.range
|
||||||
|
Nothing -> "Laden..."
|
||||||
|
in
|
||||||
div [ class "box" ]
|
div [ class "box" ]
|
||||||
[ nav [ class "level" ]
|
[ nav [ class "level" ]
|
||||||
[ div [ class "level-left" ]
|
[ div [ class "level-left" ]
|
||||||
|
|
@ -829,7 +1308,7 @@ viewWeekNavigation model =
|
||||||
, p [ class "title" ]
|
, p [ class "title" ]
|
||||||
[ text ("KW " ++ String.fromInt model.currentWeek ++ " / " ++ String.fromInt model.currentYear) ]
|
[ text ("KW " ++ String.fromInt model.currentWeek ++ " / " ++ String.fromInt model.currentYear) ]
|
||||||
, p [ class "subtitle is-6" ]
|
, p [ class "subtitle is-6" ]
|
||||||
[ text (getWeekDateRange model.currentYear model.currentWeek) ]
|
[ text dateRange ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
, div [ class "level-right" ]
|
, div [ class "level-right" ]
|
||||||
|
|
@ -843,6 +1322,39 @@ viewWeekNavigation model =
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
-- viewWeekNavigation : Model -> Html Msg
|
||||||
|
-- viewWeekNavigation model =
|
||||||
|
-- div [ class "box" ]
|
||||||
|
-- [ nav [ class "level" ]
|
||||||
|
-- [ div [ class "level-left" ]
|
||||||
|
-- [ div [ class "level-item" ]
|
||||||
|
-- [ button
|
||||||
|
-- [ class "button is-primary"
|
||||||
|
-- , onClick PreviousWeek
|
||||||
|
-- ]
|
||||||
|
-- [ text "← Vorherige Woche" ]
|
||||||
|
-- ]
|
||||||
|
-- ]
|
||||||
|
-- , div [ class "level-item has-text-centered" ]
|
||||||
|
-- [ div []
|
||||||
|
-- [ p [ class "heading" ] [ text "Kalenderwoche" ]
|
||||||
|
-- , p [ class "title" ]
|
||||||
|
-- [ text ("KW " ++ String.fromInt model.currentWeek ++ " / " ++ String.fromInt model.currentYear) ]
|
||||||
|
-- , p [ class "subtitle is-6" ]
|
||||||
|
-- [ text (getWeekDateRange model.currentYear model.currentWeek) ]
|
||||||
|
-- ]
|
||||||
|
-- ]
|
||||||
|
-- , div [ class "level-right" ]
|
||||||
|
-- [ div [ class "level-item" ]
|
||||||
|
-- [ button
|
||||||
|
-- [ class "button is-primary"
|
||||||
|
-- , onClick NextWeek
|
||||||
|
-- ]
|
||||||
|
-- [ text "Nächste Woche →" ]
|
||||||
|
-- ]
|
||||||
|
-- ]
|
||||||
|
-- ]
|
||||||
|
-- ]
|
||||||
|
|
||||||
viewScheduleGridWithWeek : Model -> Html Msg
|
viewScheduleGridWithWeek : Model -> Html Msg
|
||||||
viewScheduleGridWithWeek model =
|
viewScheduleGridWithWeek model =
|
||||||
|
|
@ -869,27 +1381,61 @@ viewScheduleGridWithWeek model =
|
||||||
viewDayColumnWithWeek : Model -> (Int, List Schedule) -> Html Msg
|
viewDayColumnWithWeek : Model -> (Int, List Schedule) -> Html Msg
|
||||||
viewDayColumnWithWeek model (dayOfWeek, schedules) =
|
viewDayColumnWithWeek model (dayOfWeek, schedules) =
|
||||||
let
|
let
|
||||||
dateForDay = getDateForWeekDay model.currentYear model.currentWeek dayOfWeek
|
dateForDay =
|
||||||
|
case model.weekDates of
|
||||||
|
Just wd ->
|
||||||
|
wd.dates
|
||||||
|
|> List.filter (\(day, _) -> day == String.fromInt dayOfWeek)
|
||||||
|
|> List.head
|
||||||
|
|> Maybe.map Tuple.second
|
||||||
|
|> Maybe.withDefault "N/A"
|
||||||
|
Nothing ->
|
||||||
|
"Laden..."
|
||||||
in
|
in
|
||||||
td [ class "has-background-light", style "vertical-align" "top", style "min-width" "150px" ]
|
td [ class "has-background-light", style "vertical-align" "top", style "min-width" "150px" ]
|
||||||
[ p [ class "has-text-centered has-text-weight-bold is-size-7 mb-2" ]
|
[ p [ class "has-text-centered has-text-weight-bold is-size-7 mb-2" ]
|
||||||
[ text dateForDay ]
|
[ text dateForDay ]
|
||||||
, div [] (List.map (viewScheduleItemWithDay model dayOfWeek) schedules)
|
, div [] (List.map (viewScheduleItemWithDay model dayOfWeek) schedules)
|
||||||
]
|
]
|
||||||
|
-- viewDayColumnWithWeek : Model -> (Int, List Schedule) -> Html Msg
|
||||||
|
-- viewDayColumnWithWeek model (dayOfWeek, schedules) =
|
||||||
|
-- let
|
||||||
|
-- dateForDay = getDateForWeekDay model.currentYear model.currentWeek dayOfWeek
|
||||||
|
-- in
|
||||||
|
-- td [ class "has-background-light", style "vertical-align" "top", style "min-width" "150px" ]
|
||||||
|
-- [ p [ class "has-text-centered has-text-weight-bold is-size-7 mb-2" ]
|
||||||
|
-- [ text dateForDay ]
|
||||||
|
-- , div [] (List.map (viewScheduleItemWithDay model dayOfWeek) schedules)
|
||||||
|
-- ]
|
||||||
|
|
||||||
viewScheduleItemWithDay : Model -> Int -> Schedule -> Html Msg
|
viewScheduleItemWithDay : Model -> Int -> Schedule -> Html Msg
|
||||||
viewScheduleItemWithDay model dayOfWeek schedule =
|
viewScheduleItemWithDay model dayOfWeek schedule =
|
||||||
let
|
let
|
||||||
isSelected = List.any (\e -> e.scheduleId == schedule.id && e.dayOfWeek == dayOfWeek) model.selectedEntries
|
isSelected = List.any (\e -> e.scheduleId == schedule.id && e.dayOfWeek == dayOfWeek) model.selectedEntries
|
||||||
boxClass = if isSelected then "box has-background-success-light" else "box has-background-white"
|
|
||||||
|
-- Prüfe ob dieser Eintrag bereits in der DB ist (nur relevant wenn Edit-Mode aktiv)
|
||||||
|
isLocked = model.hasEntriesForCurrentWeek && not model.weekEditMode
|
||||||
|
|
||||||
|
boxClass =
|
||||||
|
if isLocked then
|
||||||
|
if isSelected then "box has-background-success-light" else "box has-background-white"
|
||||||
|
else if isSelected then
|
||||||
|
"box has-background-success-light"
|
||||||
|
else
|
||||||
|
"box has-background-white"
|
||||||
|
|
||||||
typeText = if schedule.scheduleType == "break" then " (Pause)" else ""
|
typeText = if schedule.scheduleType == "break" then " (Pause)" else ""
|
||||||
|
|
||||||
|
cursorStyle = if isLocked then "not-allowed" else "pointer"
|
||||||
|
opacity = if isLocked && not isSelected then "0.6" else "1"
|
||||||
in
|
in
|
||||||
div
|
div
|
||||||
[ class boxClass
|
[ class boxClass
|
||||||
, onClick (ToggleScheduleSelection schedule.id dayOfWeek)
|
, onClick (if isLocked then Logout else ToggleScheduleSelection schedule.id dayOfWeek) -- Dummy onClick wenn locked
|
||||||
, style "cursor" "pointer"
|
, style "cursor" cursorStyle
|
||||||
, style "margin-bottom" "0.5rem"
|
, style "margin-bottom" "0.5rem"
|
||||||
, style "padding" "0.75rem"
|
, style "padding" "0.75rem"
|
||||||
|
, style "opacity" opacity
|
||||||
]
|
]
|
||||||
[ p [ class "has-text-weight-bold is-size-7" ]
|
[ p [ class "has-text-weight-bold is-size-7" ]
|
||||||
[ text (schedule.startTime ++ " - " ++ schedule.endTime) ]
|
[ text (schedule.startTime ++ " - " ++ schedule.endTime) ]
|
||||||
|
|
@ -897,6 +1443,9 @@ viewScheduleItemWithDay model dayOfWeek schedule =
|
||||||
[ text (schedule.title ++ typeText) ]
|
[ text (schedule.title ++ typeText) ]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
-- (Rest der View-Funktionen bleiben gleich wie in deiner Version)
|
||||||
|
-- viewScheduleForm, viewScheduleList, viewUserForm, viewUserList, viewWeeklyHoursSummary, viewTimeEntriesList
|
||||||
|
|
||||||
viewScheduleForm : Model -> Html Msg
|
viewScheduleForm : Model -> Html Msg
|
||||||
viewScheduleForm model =
|
viewScheduleForm model =
|
||||||
div [ class "box" ]
|
div [ class "box" ]
|
||||||
|
|
@ -1159,11 +1708,7 @@ viewTimeEntriesList model =
|
||||||
filteredEntries = List.filter
|
filteredEntries = List.filter
|
||||||
(\e ->
|
(\e ->
|
||||||
let
|
let
|
||||||
parts = String.split "-" e.date
|
(entryYear, entryWeek) = getYearWeekFromDate e.date
|
||||||
entryYear = parts |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 0
|
|
||||||
entryMonth = parts |> List.drop 1 |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 1
|
|
||||||
entryDay = parts |> List.drop 2 |> List.head |> Maybe.andThen String.toInt |> Maybe.withDefault 1
|
|
||||||
entryWeek = getISOWeek entryYear entryMonth entryDay
|
|
||||||
in
|
in
|
||||||
entryWeek == model.currentWeek && entryYear == model.currentYear
|
entryWeek == model.currentWeek && entryYear == model.currentYear
|
||||||
)
|
)
|
||||||
|
|
@ -1255,6 +1800,18 @@ scheduleDecoder =
|
||||||
(field "type" string)
|
(field "type" string)
|
||||||
(field "title" string)
|
(field "title" string)
|
||||||
|
|
||||||
|
fetchMyTimeEntries : String -> Cmd Msg
|
||||||
|
fetchMyTimeEntries token =
|
||||||
|
Http.request
|
||||||
|
{ method = "GET"
|
||||||
|
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||||
|
, url = "/api/my-time-entries"
|
||||||
|
, body = Http.emptyBody
|
||||||
|
, expect = Http.expectJson MyTimeEntriesReceived (Decode.list timeEntryDecoder)
|
||||||
|
, timeout = Nothing
|
||||||
|
, tracker = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
saveTimeEntriesForWeek : String -> List SelectedEntry -> Int -> Int -> List Schedule -> Cmd Msg
|
saveTimeEntriesForWeek : String -> List SelectedEntry -> Int -> Int -> List Schedule -> Cmd Msg
|
||||||
saveTimeEntriesForWeek token selectedEntries year week schedules =
|
saveTimeEntriesForWeek token selectedEntries year week schedules =
|
||||||
let
|
let
|
||||||
|
|
@ -1292,6 +1849,18 @@ saveTimeEntriesForWeek token selectedEntries year week schedules =
|
||||||
Just cmd -> cmd
|
Just cmd -> cmd
|
||||||
Nothing -> Cmd.none
|
Nothing -> Cmd.none
|
||||||
|
|
||||||
|
deleteWeekEntries : String -> Int -> Int -> Cmd Msg
|
||||||
|
deleteWeekEntries token year week =
|
||||||
|
Http.request
|
||||||
|
{ method = "DELETE"
|
||||||
|
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||||
|
, url = "/api/my-time-entries/week?year=" ++ String.fromInt year ++ "&week=" ++ String.fromInt week
|
||||||
|
, body = Http.emptyBody
|
||||||
|
, expect = Http.expectWhatever WeekEntriesDeleted
|
||||||
|
, timeout = Nothing
|
||||||
|
, tracker = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
createSchedule : String -> NewSchedule -> Cmd Msg
|
createSchedule : String -> NewSchedule -> Cmd Msg
|
||||||
createSchedule token schedule =
|
createSchedule token schedule =
|
||||||
case String.toInt schedule.dayOfWeek of
|
case String.toInt schedule.dayOfWeek of
|
||||||
|
|
@ -1419,3 +1988,36 @@ weeklyHoursDecoder =
|
||||||
(field "week" int)
|
(field "week" int)
|
||||||
(field "year" int)
|
(field "year" int)
|
||||||
(field "total_hours" float)
|
(field "total_hours" float)
|
||||||
|
|
||||||
|
fetchWeekDates : String -> Int -> Int -> Cmd Msg
|
||||||
|
fetchWeekDates token year week =
|
||||||
|
Http.request
|
||||||
|
{ method = "GET"
|
||||||
|
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||||
|
, url = "/api/week-dates?year=" ++ String.fromInt year ++ "&week=" ++ String.fromInt week
|
||||||
|
, body = Http.emptyBody
|
||||||
|
, expect = Http.expectJson WeekDatesReceived weekDatesDecoder
|
||||||
|
, timeout = Nothing
|
||||||
|
, tracker = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
weekDatesDecoder : Decoder WeekDates
|
||||||
|
weekDatesDecoder =
|
||||||
|
Decode.map4 WeekDates
|
||||||
|
(field "year" int)
|
||||||
|
(field "week" int)
|
||||||
|
(field "dates" (Decode.dict string) |> Decode.map Dict.toList)
|
||||||
|
(field "range" string)
|
||||||
|
|
||||||
|
checkWeekHasEntries : String -> Int -> Int -> Cmd Msg
|
||||||
|
checkWeekHasEntries token year week =
|
||||||
|
Http.request
|
||||||
|
{ method = "GET"
|
||||||
|
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||||
|
, url = "/api/week-has-entries?year=" ++ String.fromInt year ++ "&week=" ++ String.fromInt week
|
||||||
|
, body = Http.emptyBody
|
||||||
|
, expect = Http.expectJson WeekHasEntriesReceived (field "has_entries" bool)
|
||||||
|
, timeout = Nothing
|
||||||
|
, tracker = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue