feat: initial project commit
This commit is contained in:
commit
2c4fc7869a
10 changed files with 1632 additions and 0 deletions
25
frontend/elm.json
Normal file
25
frontend/elm.json
Normal 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
950
frontend/src/Main.elm
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue