feat: initial project commit

This commit is contained in:
Patryk Hegenberg 2025-11-04 22:20:09 +01:00
commit 2c4fc7869a
10 changed files with 1632 additions and 0 deletions

25
frontend/elm.json Normal file
View file

@ -0,0 +1,25 @@
{
"type": "application",
"source-directories": ["src"],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
"elm/time": "1.0.0"
},
"indirect": {
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.3"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

950
frontend/src/Main.elm Normal file
View file

@ -0,0 +1,950 @@
module Main exposing (..)
import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Http
import Json.Decode as Decode exposing (Decoder, field, int, string, bool, list)
import Json.Encode as Encode
import Time
-- MAIN
main : Program () Model Msg
main =
Browser.element
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}
-- MODEL
type alias Model =
{ page : Page
, username : String
, password : String
, token : Maybe String
, isAdmin : Bool
, schedules : List Schedule
, users : List User
, timeEntries : List TimeEntry
, selectedEntries : List Int
, currentDate : String
, newSchedule : NewSchedule
, newUser : NewUser
, error : Maybe String
}
type Page
= LoginPage
| UserDashboard
| AdminDashboard
type alias Schedule =
{ id : Int
, dayOfWeek : Int
, startTime : String
, endTime : String
, scheduleType : String
, title : String
}
type alias User =
{ id : Int
, username : String
, isAdmin : Bool
}
type alias TimeEntry =
{ id : Int
, userId : Int
, scheduleId : Int
, date : String
, entryType : String
}
type alias NewSchedule =
{ dayOfWeek : String
, startTime : String
, endTime : String
, scheduleType : String
, title : String
}
type alias NewUser =
{ username : String
, password : String
, isAdmin : Bool
}
init : () -> (Model, Cmd Msg)
init _ =
( { page = LoginPage
, username = ""
, password = ""
, token = Nothing
, isAdmin = False
, schedules = []
, users = []
, timeEntries = []
, selectedEntries = []
, currentDate = ""
, newSchedule = NewSchedule "" "" "" "lesson" ""
, newUser = NewUser "" "" False
, error = Nothing
}
, Cmd.none
)
-- UPDATE
type Msg
= UpdateUsername String
| UpdatePassword String
| Login
| LoginResponse (Result Http.Error LoginResult)
| Logout
| FetchSchedules
| SchedulesReceived (Result Http.Error (List Schedule))
| ToggleScheduleSelection Int
| SaveTimeEntries
| TimeEntriesSaved (Result Http.Error ())
| UpdateNewScheduleDay String
| UpdateNewScheduleStart String
| UpdateNewScheduleEnd String
| UpdateNewScheduleType String
| UpdateNewScheduleTitle String
| CreateSchedule
| ScheduleCreated (Result Http.Error ())
| DeleteSchedule Int
| ScheduleDeleted (Result Http.Error ())
| UpdateNewUsername String
| UpdateNewPassword String
| UpdateNewUserAdmin Bool
| CreateUser
| UserCreated (Result Http.Error ())
| FetchUsers
| UsersReceived (Result Http.Error (List User))
| FetchAllTimeEntries
| AllTimeEntriesReceived (Result Http.Error (List TimeEntry))
| UpdateCurrentDate String
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
UpdateUsername username ->
({ model | username = username }, Cmd.none)
UpdatePassword password ->
({ model | password = password }, Cmd.none)
Login ->
(model, loginRequest model.username model.password)
LoginResponse (Ok result) ->
let
newPage = if result.isAdmin then AdminDashboard else UserDashboard
in
({ model
| token = Just result.token
, isAdmin = result.isAdmin
, page = newPage
, error = Nothing
}, fetchSchedules (Just result.token))
LoginResponse (Err _) ->
({ model | error = Just "Login fehlgeschlagen" }, Cmd.none)
Logout ->
init ()
FetchSchedules ->
(model, fetchSchedules model.token)
SchedulesReceived (Ok schedules) ->
({ model | schedules = schedules }, Cmd.none)
SchedulesReceived (Err _) ->
({ model | error = Just "Fehler beim Laden des Stundenplans" }, Cmd.none)
ToggleScheduleSelection scheduleId ->
let
newSelected =
if List.member scheduleId model.selectedEntries then
List.filter (\id -> id /= scheduleId) model.selectedEntries
else
scheduleId :: model.selectedEntries
in
({ model | selectedEntries = newSelected }, Cmd.none)
SaveTimeEntries ->
case model.token of
Just token ->
(model, saveTimeEntries token model.selectedEntries model.currentDate)
Nothing ->
(model, Cmd.none)
TimeEntriesSaved (Ok _) ->
({ model | selectedEntries = [], error = Nothing }, Cmd.none)
TimeEntriesSaved (Err _) ->
({ model | error = Just "Fehler beim Speichern" }, Cmd.none)
UpdateNewScheduleDay day ->
let
oldSchedule = model.newSchedule
newSchedule = { oldSchedule | dayOfWeek = day }
in
({ model | newSchedule = newSchedule }, Cmd.none)
UpdateNewScheduleStart time ->
let
oldSchedule = model.newSchedule
newSchedule = { oldSchedule | startTime = time }
in
({ model | newSchedule = newSchedule }, Cmd.none)
UpdateNewScheduleEnd time ->
let
oldSchedule = model.newSchedule
newSchedule = { oldSchedule | endTime = time }
in
({ model | newSchedule = newSchedule }, Cmd.none)
UpdateNewScheduleType scheduleType ->
let
oldSchedule = model.newSchedule
newSchedule = { oldSchedule | scheduleType = scheduleType }
in
({ model | newSchedule = newSchedule }, Cmd.none)
UpdateNewScheduleTitle title ->
let
oldSchedule = model.newSchedule
newSchedule = { oldSchedule | title = title }
in
({ model | newSchedule = newSchedule }, Cmd.none)
CreateSchedule ->
case model.token of
Just token ->
(model, createSchedule token model.newSchedule)
Nothing ->
(model, Cmd.none)
ScheduleCreated (Ok _) ->
let
emptySchedule = NewSchedule "" "" "" "lesson" ""
in
({ model | newSchedule = emptySchedule }, fetchSchedules model.token)
ScheduleCreated (Err _) ->
({ model | error = Just "Fehler beim Erstellen" }, Cmd.none)
DeleteSchedule scheduleId ->
case model.token of
Just token ->
(model, deleteSchedule token scheduleId)
Nothing ->
(model, Cmd.none)
ScheduleDeleted (Ok _) ->
(model, fetchSchedules model.token)
ScheduleDeleted (Err _) ->
({ model | error = Just "Fehler beim Löschen" }, Cmd.none)
UpdateNewUsername username ->
let
oldUser = model.newUser
newUser = { oldUser | username = username }
in
({ model | newUser = newUser }, Cmd.none)
UpdateNewPassword password ->
let
oldUser = model.newUser
newUser = { oldUser | password = password }
in
({ model | newUser = newUser }, Cmd.none)
UpdateNewUserAdmin isAdmin ->
let
oldUser = model.newUser
newUser = { oldUser | isAdmin = isAdmin }
in
({ model | newUser = newUser }, Cmd.none)
CreateUser ->
case model.token of
Just token ->
(model, createUser token model.newUser)
Nothing ->
(model, Cmd.none)
UserCreated (Ok _) ->
let
emptyUser = NewUser "" "" False
in
case model.token of
Just token ->
({ model | newUser = emptyUser }, fetchUsers token)
Nothing ->
({ model | error = Just "Kein Token vorhanden" }, Cmd.none)
-- ({ model | newUser = emptyUser }, fetchUsers model.token)
UserCreated (Err _) ->
({ model | error = Just "Fehler beim Erstellen des Benutzers" }, Cmd.none)
FetchUsers ->
case model.token of
Just token ->
(model, fetchUsers token)
Nothing ->
(model, Cmd.none)
UsersReceived (Ok users) ->
({ model | users = users }, Cmd.none)
UsersReceived (Err _) ->
({ model | error = Just "Fehler beim Laden der Benutzer" }, Cmd.none)
FetchAllTimeEntries ->
case model.token of
Just token ->
(model, fetchAllTimeEntries token)
Nothing ->
(model, Cmd.none)
AllTimeEntriesReceived (Ok entries) ->
({ model | timeEntries = entries }, Cmd.none)
AllTimeEntriesReceived (Err _) ->
({ model | error = Just "Fehler beim Laden der Zeiteinträge" }, Cmd.none)
UpdateCurrentDate date ->
({ model | currentDate = date }, Cmd.none)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Html Msg
view model =
div [ class "container" ]
[ case model.page of
LoginPage ->
viewLogin model
UserDashboard ->
viewUserDashboard model
AdminDashboard ->
viewAdminDashboard model
]
viewLogin : Model -> Html Msg
viewLogin model =
section [ class "section" ]
[ div [ class "container" ]
[ div [ class "columns is-centered" ]
[ div [ class "column is-5-tablet is-4-desktop is-3-widescreen" ]
[ div [ class "box" ]
[ h1 [ class "title has-text-centered" ] [ text "Zeiterfassung Login" ]
, case model.error of
Just err ->
div [ class "notification is-danger" ] [ text err ]
Nothing ->
text ""
, div [ class "field" ]
[ label [ class "label" ] [ text "Benutzername" ]
, div [ class "control" ]
[ input
[ class "input"
, type_ "text"
, placeholder "Benutzername"
, value model.username
, onInput UpdateUsername
] []
]
]
, div [ class "field" ]
[ label [ class "label" ] [ text "Passwort" ]
, div [ class "control" ]
[ input
[ class "input"
, type_ "password"
, placeholder "Passwort"
, value model.password
, onInput UpdatePassword
] []
]
]
, div [ class "field" ]
[ div [ class "control" ]
[ button
[ class "button is-primary is-fullwidth"
, onClick Login
] [ text "Anmelden" ]
]
]
]
]
]
]
]
viewUserDashboard : Model -> Html Msg
viewUserDashboard model =
div []
[ nav [ class "navbar is-primary" ]
[ div [ class "navbar-brand" ]
[ div [ class "navbar-item" ]
[ h1 [ class "title is-4 has-text-white" ] [ text "Zeiterfassung" ]
]
]
, div [ class "navbar-menu" ]
[ div [ class "navbar-end" ]
[ div [ class "navbar-item" ]
[ button [ class "button is-light", onClick Logout ] [ text "Abmelden" ]
]
]
]
]
, section [ class "section" ]
[ div [ class "container" ]
[ h2 [ class "title" ] [ text "Stundenplan" ]
, div [ class "field" ]
[ label [ class "label" ] [ text "Datum" ]
, div [ class "control" ]
[ input
[ class "input"
, type_ "date"
, value model.currentDate
, onInput UpdateCurrentDate
] []
]
]
, viewScheduleGrid model
, div [ class "field" ]
[ div [ class "control" ]
[ button
[ class "button is-primary is-large is-fullwidth"
, onClick SaveTimeEntries
, disabled (List.isEmpty model.selectedEntries || String.isEmpty model.currentDate)
] [ text "Speichern" ]
]
]
, case model.error of
Just err ->
div [ class "notification is-danger" ] [ text err ]
Nothing ->
text ""
]
]
]
viewAdminDashboard : Model -> Html Msg
viewAdminDashboard model =
div []
[ nav [ class "navbar is-danger" ]
[ div [ class "navbar-brand" ]
[ div [ class "navbar-item" ]
[ h1 [ class "title is-4 has-text-white" ] [ text "Admin Dashboard" ]
]
]
, div [ class "navbar-menu" ]
[ div [ class "navbar-end" ]
[ div [ class "navbar-item" ]
[ button [ class "button is-light", onClick Logout ] [ text "Abmelden" ]
]
]
]
]
, section [ class "section" ]
[ div [ class "container" ]
[ div [ class "tabs is-boxed" ]
[ ul []
[ li [ class "is-active" ] [ a [] [ text "Stundenplan" ] ]
, li [] [ a [ onClick FetchUsers ] [ text "Benutzer" ] ]
, li [] [ a [ onClick FetchAllTimeEntries ] [ text "Zeiteinträge" ] ]
]
]
, h2 [ class "title" ] [ text "Stundenplan verwalten" ]
, viewScheduleForm model
, viewScheduleList model
, h2 [ class "title" ] [ text "Benutzer anlegen" ]
, viewUserForm model
, if not (List.isEmpty model.users) then
viewUserList model
else
text ""
, if not (List.isEmpty model.timeEntries) then
viewTimeEntriesList model
else
text ""
]
]
]
viewScheduleGrid : Model -> Html Msg
viewScheduleGrid model =
let
days = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag"]
groupedSchedules = List.range 0 4
|> List.map (\day ->
List.filter (\s -> s.dayOfWeek == day) model.schedules
)
in
div [ class "table-container" ]
[ table [ class "table is-bordered is-fullwidth" ]
[ thead []
[ tr [] (List.map (\day -> th [] [ text day ]) days)
]
, tbody []
[ tr []
(List.map (viewDayColumn model) groupedSchedules)
]
]
]
viewDayColumn : Model -> List Schedule -> Html Msg
viewDayColumn model schedules =
td [ class "has-background-light" ]
(List.map (viewScheduleItem model) schedules)
viewScheduleItem : Model -> Schedule -> Html Msg
viewScheduleItem model schedule =
let
isSelected = List.member schedule.id model.selectedEntries
boxClass = if isSelected then "box has-background-primary-light" else "box"
typeText = if schedule.scheduleType == "break" then " (Pause)" else ""
in
div
[ class boxClass
, onClick (ToggleScheduleSelection schedule.id)
, style "cursor" "pointer"
, style "margin-bottom" "0.5rem"
]
[ p [ class "has-text-weight-bold" ] [ text (schedule.startTime ++ " - " ++ schedule.endTime) ]
, p [] [ text (schedule.title ++ typeText) ]
]
viewScheduleForm : Model -> Html Msg
viewScheduleForm model =
div [ class "box" ]
[ div [ class "columns" ]
[ div [ class "column" ]
[ div [ class "field" ]
[ label [ class "label" ] [ text "Wochentag" ]
, div [ class "control" ]
[ div [ class "select is-fullwidth" ]
[ select [ onInput UpdateNewScheduleDay ]
[ option [ value "" ] [ text "Wochentag wählen" ]
, option [ value "0" ] [ text "Montag" ]
, option [ value "1" ] [ text "Dienstag" ]
, option [ value "2" ] [ text "Mittwoch" ]
, option [ value "3" ] [ text "Donnerstag" ]
, option [ value "4" ] [ text "Freitag" ]
]
]
]
]
]
, div [ class "column" ]
[ div [ class "field" ]
[ label [ class "label" ] [ text "Startzeit" ]
, div [ class "control" ]
[ input
[ class "input"
, type_ "time"
, value model.newSchedule.startTime
, onInput UpdateNewScheduleStart
] []
]
]
]
, div [ class "column" ]
[ div [ class "field" ]
[ label [ class "label" ] [ text "Endzeit" ]
, div [ class "control" ]
[ input
[ class "input"
, type_ "time"
, value model.newSchedule.endTime
, onInput UpdateNewScheduleEnd
] []
]
]
]
]
, div [ class "columns" ]
[ div [ class "column" ]
[ div [ class "field" ]
[ label [ class "label" ] [ text "Typ" ]
, div [ class "control" ]
[ div [ class "select is-fullwidth" ]
[ select [ onInput UpdateNewScheduleType, value model.newSchedule.scheduleType ]
[ option [ value "lesson" ] [ text "Unterricht" ]
, option [ value "break" ] [ text "Pause" ]
]
]
]
]
]
, div [ class "column" ]
[ div [ class "field" ]
[ label [ class "label" ] [ text "Titel" ]
, div [ class "control" ]
[ input
[ class "input"
, type_ "text"
, placeholder "z.B. Mathematik"
, value model.newSchedule.title
, onInput UpdateNewScheduleTitle
] []
]
]
]
]
, div [ class "field" ]
[ div [ class "control" ]
[ button [ class "button is-primary", onClick CreateSchedule ] [ text "Hinzufügen" ]
]
]
]
viewScheduleList : Model -> Html Msg
viewScheduleList model =
div [ class "box" ]
[ h3 [ class "subtitle" ] [ text "Aktueller Stundenplan" ]
, table [ class "table is-fullwidth is-striped" ]
[ thead []
[ tr []
[ th [] [ text "Tag" ]
, th [] [ text "Zeit" ]
, th [] [ text "Typ" ]
, th [] [ text "Titel" ]
, th [] [ text "Aktion" ]
]
]
, tbody []
(List.map viewScheduleRow model.schedules)
]
]
viewScheduleRow : Schedule -> Html Msg
viewScheduleRow schedule =
let
dayName = case schedule.dayOfWeek of
0 -> "Montag"
1 -> "Dienstag"
2 -> "Mittwoch"
3 -> "Donnerstag"
4 -> "Freitag"
_ -> "Unbekannt"
typeName = if schedule.scheduleType == "break" then "Pause" else "Unterricht"
in
tr []
[ td [] [ text dayName ]
, td [] [ text (schedule.startTime ++ " - " ++ schedule.endTime) ]
, td [] [ text typeName ]
, td [] [ text schedule.title ]
, td []
[ button
[ class "button is-small is-danger"
, onClick (DeleteSchedule schedule.id)
] [ text "Löschen" ]
]
]
viewUserForm : Model -> Html Msg
viewUserForm model =
div [ class "box" ]
[ div [ class "columns" ]
[ div [ class "column" ]
[ div [ class "field" ]
[ label [ class "label" ] [ text "Benutzername" ]
, div [ class "control" ]
[ input
[ class "input"
, type_ "text"
, placeholder "Benutzername"
, value model.newUser.username
, onInput UpdateNewUsername
] []
]
]
]
, div [ class "column" ]
[ div [ class "field" ]
[ label [ class "label" ] [ text "Passwort" ]
, div [ class "control" ]
[ input
[ class "input"
, type_ "password"
, placeholder "Passwort"
, value model.newUser.password
, onInput UpdateNewPassword
] []
]
]
]
, div [ class "column is-narrow" ]
[ div [ class "field" ]
[ label [ class "label" ] [ text "Admin" ]
, div [ class "control" ]
[ label [ class "checkbox" ]
[ input
[ type_ "checkbox"
, checked model.newUser.isAdmin
, onCheck UpdateNewUserAdmin
] []
, text " Admin-Rechte"
]
]
]
]
]
, div [ class "field" ]
[ div [ class "control" ]
[ button [ class "button is-primary", onClick CreateUser ] [ text "Benutzer anlegen" ]
]
]
]
viewUserList : Model -> Html Msg
viewUserList model =
div [ class "box" ]
[ h3 [ class "subtitle" ] [ text "Benutzer" ]
, table [ class "table is-fullwidth is-striped" ]
[ thead []
[ tr []
[ th [] [ text "ID" ]
, th [] [ text "Benutzername" ]
, th [] [ text "Rolle" ]
]
]
, tbody []
(List.map viewUserRow model.users)
]
]
viewUserRow : User -> Html Msg
viewUserRow user =
tr []
[ td [] [ text (String.fromInt user.id) ]
, td [] [ text user.username ]
, td [] [ text (if user.isAdmin then "Admin" else "Benutzer") ]
]
viewTimeEntriesList : Model -> Html Msg
viewTimeEntriesList model =
div [ class "box" ]
[ h3 [ class "subtitle" ] [ text "Alle Zeiteinträge" ]
, table [ class "table is-fullwidth is-striped" ]
[ thead []
[ tr []
[ th [] [ text "Benutzer ID" ]
, th [] [ text "Stundenplan ID" ]
, th [] [ text "Datum" ]
, th [] [ text "Typ" ]
]
]
, tbody []
(List.map viewTimeEntryRow model.timeEntries)
]
]
viewTimeEntryRow : TimeEntry -> Html Msg
viewTimeEntryRow entry =
tr []
[ td [] [ text (String.fromInt entry.userId) ]
, td [] [ text (String.fromInt entry.scheduleId) ]
, td [] [ text entry.date ]
, td [] [ text entry.entryType ]
]
-- HTTP
type alias LoginResult =
{ token : String
, username : String
, isAdmin : Bool
}
loginRequest : String -> String -> Cmd Msg
loginRequest username password =
Http.post
{ url = "/api/login"
, body = Http.jsonBody <|
Encode.object
[ ("username", Encode.string username)
, ("password", Encode.string password)
]
, expect = Http.expectJson LoginResponse loginDecoder
}
loginDecoder : Decoder LoginResult
loginDecoder =
Decode.map3 LoginResult
(field "token" string)
(field "username" string)
(field "is_admin" bool)
fetchSchedules : Maybe String -> Cmd Msg
fetchSchedules maybeToken =
case maybeToken of
Just token ->
Http.request
{ method = "GET"
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
, url = "/api/schedules"
, body = Http.emptyBody
, expect = Http.expectJson SchedulesReceived (Decode.list scheduleDecoder)
, timeout = Nothing
, tracker = Nothing
}
Nothing ->
Cmd.none
scheduleDecoder : Decoder Schedule
scheduleDecoder =
Decode.map6 Schedule
(field "id" int)
(field "day_of_week" int)
(field "start_time" string)
(field "end_time" string)
(field "type" string)
(field "title" string)
saveTimeEntries : String -> List Int -> String -> Cmd Msg
saveTimeEntries token scheduleIds date =
let
requests = List.map (\scheduleId ->
Http.request
{ method = "POST"
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
, url = "/api/time-entries"
, body = Http.jsonBody <|
Encode.object
[ ("schedule_id", Encode.int scheduleId)
, ("date", Encode.string date)
, ("type", Encode.string "lesson")
]
, expect = Http.expectWhatever TimeEntriesSaved
, timeout = Nothing
, tracker = Nothing
}
) scheduleIds
in
case List.head requests of
Just cmd -> cmd
Nothing -> Cmd.none
createSchedule : String -> NewSchedule -> Cmd Msg
createSchedule token schedule =
case String.toInt schedule.dayOfWeek of
Just day ->
Http.request
{ method = "POST"
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
, url = "/api/admin/schedules"
, body = Http.jsonBody <|
Encode.object
[ ("day_of_week", Encode.int day)
, ("start_time", Encode.string schedule.startTime)
, ("end_time", Encode.string schedule.endTime)
, ("type", Encode.string schedule.scheduleType)
, ("title", Encode.string schedule.title)
]
, expect = Http.expectWhatever ScheduleCreated
, timeout = Nothing
, tracker = Nothing
}
Nothing ->
Cmd.none
deleteSchedule : String -> Int -> Cmd Msg
deleteSchedule token scheduleId =
Http.request
{ method = "DELETE"
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
, url = "/api/admin/schedules/delete?id=" ++ String.fromInt scheduleId
, body = Http.emptyBody
, expect = Http.expectWhatever ScheduleDeleted
, timeout = Nothing
, tracker = Nothing
}
createUser : String -> NewUser -> Cmd Msg
createUser token user =
Http.request
{ method = "POST"
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
, url = "/api/admin/users"
, body = Http.jsonBody <|
Encode.object
[ ("username", Encode.string user.username)
, ("password", Encode.string user.password)
, ("is_admin", Encode.bool user.isAdmin)
]
, expect = Http.expectWhatever UserCreated
, timeout = Nothing
, tracker = Nothing
}
fetchUsers : String -> Cmd Msg
fetchUsers token =
Http.request
{ method = "GET"
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
, url = "/api/admin/users/list"
, body = Http.emptyBody
, expect = Http.expectJson UsersReceived (Decode.list userDecoder)
, timeout = Nothing
, tracker = Nothing
}
userDecoder : Decoder User
userDecoder =
Decode.map3 User
(field "id" int)
(field "username" string)
(field "is_admin" bool)
fetchAllTimeEntries : String -> Cmd Msg
fetchAllTimeEntries token =
Http.request
{ method = "GET"
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
, url = "/api/admin/time-entries"
, body = Http.emptyBody
, expect = Http.expectJson AllTimeEntriesReceived (Decode.list timeEntryDecoder)
, timeout = Nothing
, tracker = Nothing
}
timeEntryDecoder : Decoder TimeEntry
timeEntryDecoder =
Decode.map5 TimeEntry
(field "id" int)
(field "user_id" int)
(field "schedule_id" int)
(field "date" string)
(field "type" string)