feat: add schoolyear based calculation
This commit is contained in:
parent
e65ba85c43
commit
c07019e3eb
5 changed files with 675 additions and 44 deletions
|
|
@ -93,6 +93,10 @@ type alias Model =
|
|||
, isProcessing : Bool
|
||||
, mobileMenuOpen : Bool
|
||||
, adminManualEntryForm : AdminManualEntry
|
||||
, schoolYears : List SchoolYear
|
||||
, newSchoolYear : NewSchoolYear
|
||||
, activeSchoolYear : Maybe SchoolYear
|
||||
, editingSchoolYearId : Maybe Int
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -106,6 +110,7 @@ type AdminTab
|
|||
= ScheduleTab
|
||||
| UsersTab
|
||||
| TimeEntriesTab
|
||||
| SchoolYearsTab
|
||||
|
||||
|
||||
type alias Schedule =
|
||||
|
|
@ -221,6 +226,22 @@ type alias AdminManualEntry =
|
|||
}
|
||||
|
||||
|
||||
type alias SchoolYear =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, startDate : String
|
||||
, endDate : String
|
||||
, isActive : Bool
|
||||
}
|
||||
|
||||
|
||||
type alias NewSchoolYear =
|
||||
{ name : String
|
||||
, startDate : String
|
||||
, endDate : String
|
||||
}
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
let
|
||||
|
|
@ -273,6 +294,10 @@ init flags =
|
|||
, isProcessing = False
|
||||
, mobileMenuOpen = False
|
||||
, adminManualEntryForm = AdminManualEntry Nothing "" "" "" "lesson"
|
||||
, schoolYears = []
|
||||
, newSchoolYear = NewSchoolYear "" "" ""
|
||||
, activeSchoolYear = Nothing
|
||||
, editingSchoolYearId = Nothing
|
||||
}
|
||||
|
||||
cmd =
|
||||
|
|
@ -283,7 +308,7 @@ init flags =
|
|||
, fetchSchedules (Just token)
|
||||
, fetchYearlyHoursSummary token
|
||||
, if flags.isAdmin then
|
||||
Cmd.none
|
||||
fetchSchoolYears token
|
||||
|
||||
else
|
||||
fetchMyInfo token
|
||||
|
|
@ -394,6 +419,19 @@ type Msg
|
|||
| AdminTimeEntrySaved (Result Http.Error ())
|
||||
| FetchMyInfo
|
||||
| MyInfoReceived (Result Http.Error User)
|
||||
| FetchSchoolYears
|
||||
| SchoolYearsReceived (Result Http.Error (List SchoolYear))
|
||||
| FetchActiveSchoolYear
|
||||
| ActiveSchoolYearReceived (Result Http.Error SchoolYear)
|
||||
| UpdateNewSchoolYearName String
|
||||
| UpdateNewSchoolYearStart String
|
||||
| UpdateNewSchoolYearEnd String
|
||||
| CreateSchoolYear
|
||||
| SchoolYearCreated (Result Http.Error ())
|
||||
| ActivateSchoolYear Int
|
||||
| SchoolYearActivated (Result Http.Error ())
|
||||
| DeleteSchoolYear Int
|
||||
| SchoolYearDeleted (Result Http.Error ())
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
|
|
@ -732,6 +770,17 @@ update msg model =
|
|||
Nothing ->
|
||||
Cmd.none
|
||||
|
||||
SchoolYearsTab ->
|
||||
case model.token of
|
||||
Just token ->
|
||||
Cmd.batch
|
||||
[ fetchSchoolYears token
|
||||
, fetchActiveSchoolYear token
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
Cmd.none
|
||||
|
||||
_ ->
|
||||
Cmd.none
|
||||
in
|
||||
|
|
@ -1452,6 +1501,145 @@ update msg model =
|
|||
MyInfoReceived (Err _) ->
|
||||
( { model | error = Just "Fehler beim Laden deiner Daten" }, Cmd.none )
|
||||
|
||||
FetchSchoolYears ->
|
||||
case model.token of
|
||||
Just token ->
|
||||
( model, fetchSchoolYears token )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SchoolYearsReceived (Ok years) ->
|
||||
( { model | schoolYears = years }, Cmd.none )
|
||||
|
||||
SchoolYearsReceived (Err _) ->
|
||||
( { model | error = Just "Fehler beim Laden der Schuljahre" }, Cmd.none )
|
||||
|
||||
FetchActiveSchoolYear ->
|
||||
case model.token of
|
||||
Just token ->
|
||||
( model, fetchActiveSchoolYear token )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
ActiveSchoolYearReceived (Ok year) ->
|
||||
( { model | activeSchoolYear = Just year }, Cmd.none )
|
||||
|
||||
ActiveSchoolYearReceived (Err _) ->
|
||||
( { model | activeSchoolYear = Nothing }, Cmd.none )
|
||||
|
||||
UpdateNewSchoolYearName name ->
|
||||
let
|
||||
old =
|
||||
model.newSchoolYear
|
||||
|
||||
new =
|
||||
{ old | name = name }
|
||||
in
|
||||
( { model | newSchoolYear = new }, Cmd.none )
|
||||
|
||||
UpdateNewSchoolYearStart date ->
|
||||
let
|
||||
old =
|
||||
model.newSchoolYear
|
||||
|
||||
new =
|
||||
{ old | startDate = date }
|
||||
in
|
||||
( { model | newSchoolYear = new }, Cmd.none )
|
||||
|
||||
UpdateNewSchoolYearEnd date ->
|
||||
let
|
||||
old =
|
||||
model.newSchoolYear
|
||||
|
||||
new =
|
||||
{ old | endDate = date }
|
||||
in
|
||||
( { model | newSchoolYear = new }, Cmd.none )
|
||||
|
||||
CreateSchoolYear ->
|
||||
if
|
||||
String.isEmpty model.newSchoolYear.name
|
||||
|| String.isEmpty model.newSchoolYear.startDate
|
||||
|| String.isEmpty model.newSchoolYear.endDate
|
||||
then
|
||||
( { model | error = Just "Bitte alle Felder ausfüllen" }, Cmd.none )
|
||||
|
||||
else
|
||||
case model.token of
|
||||
Just token ->
|
||||
( { model | isProcessing = True }, createSchoolYear token model.newSchoolYear )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SchoolYearCreated (Ok _) ->
|
||||
case model.token of
|
||||
Just token ->
|
||||
( { model
|
||||
| newSchoolYear = NewSchoolYear "" "" ""
|
||||
, error = Nothing
|
||||
, isProcessing = False
|
||||
}
|
||||
, fetchSchoolYears token
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SchoolYearCreated (Err _) ->
|
||||
( { model
|
||||
| error = Just "Fehler beim Erstellen des Schuljahres"
|
||||
, isProcessing = False
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ActivateSchoolYear id ->
|
||||
case model.token of
|
||||
Just token ->
|
||||
( model, activateSchoolYear token id )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SchoolYearActivated (Ok _) ->
|
||||
case model.token of
|
||||
Just token ->
|
||||
( { model | error = Nothing }
|
||||
, Cmd.batch
|
||||
[ fetchSchoolYears token
|
||||
, fetchActiveSchoolYear token
|
||||
]
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SchoolYearActivated (Err _) ->
|
||||
( { model | error = Just "Fehler beim Aktivieren" }, Cmd.none )
|
||||
|
||||
DeleteSchoolYear id ->
|
||||
case model.token of
|
||||
Just token ->
|
||||
( model, deleteSchoolYear token id )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SchoolYearDeleted (Ok _) ->
|
||||
case model.token of
|
||||
Just token ->
|
||||
( { model | error = Nothing }, fetchSchoolYears token )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SchoolYearDeleted (Err _) ->
|
||||
( { model | error = Just "Fehler beim Löschen" }, Cmd.none )
|
||||
|
||||
|
||||
|
||||
-- SUBSCRIPTIONS
|
||||
|
|
@ -2115,6 +2303,8 @@ viewAdminDashboard model =
|
|||
[ a [ onClick (SwitchTab UsersTab) ] [ text "Benutzer" ] ]
|
||||
, li [ classList [ ( "is-active", model.activeTab == TimeEntriesTab ) ] ]
|
||||
[ a [ onClick (SwitchTab TimeEntriesTab) ] [ text "Zeiteinträge" ] ]
|
||||
, li [ classList [ ( "is-active", model.activeTab == SchoolYearsTab ) ] ]
|
||||
[ a [ onClick (SwitchTab SchoolYearsTab) ] [ text "Schuljahre" ] ]
|
||||
]
|
||||
]
|
||||
, case model.activeTab of
|
||||
|
|
@ -2126,6 +2316,9 @@ viewAdminDashboard model =
|
|||
|
||||
TimeEntriesTab ->
|
||||
viewTimeEntriesTab model
|
||||
|
||||
SchoolYearsTab ->
|
||||
viewSchoolYearsTab model
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
@ -3344,6 +3537,159 @@ viewTimeEntryRowWithActions model entry =
|
|||
]
|
||||
|
||||
|
||||
viewSchoolYearsTab : Model -> Html Msg
|
||||
viewSchoolYearsTab model =
|
||||
div []
|
||||
[ h2 [ class "title" ] [ text "Schuljahre verwalten" ]
|
||||
, case model.activeSchoolYear of
|
||||
Just schoolYear ->
|
||||
div [ class "notification is-info is-light mb-4" ]
|
||||
[ p [ class "has-text-weight-bold" ]
|
||||
[ text ("Aktives Schuljahr: " ++ schoolYear.name) ]
|
||||
, p [ class "is-size-7" ]
|
||||
[ text (schoolYear.startDate ++ " bis " ++ schoolYear.endDate) ]
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
div [ class "notification is-warning is-light mb-4" ]
|
||||
[ text "⚠️ Kein Schuljahr aktiv! Bitte eines aktivieren." ]
|
||||
, viewSchoolYearForm model
|
||||
, viewSchoolYearsList model
|
||||
]
|
||||
|
||||
|
||||
viewSchoolYearForm : Model -> Html Msg
|
||||
viewSchoolYearForm model =
|
||||
div [ class "box" ]
|
||||
[ h3 [ class "subtitle" ] [ text "Neues Schuljahr erstellen" ]
|
||||
, div [ class "columns" ]
|
||||
[ div [ class "column is-4" ]
|
||||
[ div [ class "field" ]
|
||||
[ label [ class "label" ] [ text "Name (z.B. 2024/2025)" ]
|
||||
, div [ class "control" ]
|
||||
[ input
|
||||
[ class "input"
|
||||
, type_ "text"
|
||||
, placeholder "2024/2025"
|
||||
, value model.newSchoolYear.name
|
||||
, onInput UpdateNewSchoolYearName
|
||||
, disabled model.isProcessing
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
, div [ class "column is-4" ]
|
||||
[ div [ class "field" ]
|
||||
[ label [ class "label" ] [ text "Startdatum" ]
|
||||
, div [ class "control" ]
|
||||
[ input
|
||||
[ class "input"
|
||||
, type_ "date"
|
||||
, value model.newSchoolYear.startDate
|
||||
, onInput UpdateNewSchoolYearStart
|
||||
, disabled model.isProcessing
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
, div [ class "column is-4" ]
|
||||
[ div [ class "field" ]
|
||||
[ label [ class "label" ] [ text "Enddatum" ]
|
||||
, div [ class "control" ]
|
||||
[ input
|
||||
[ class "input"
|
||||
, type_ "date"
|
||||
, value model.newSchoolYear.endDate
|
||||
, onInput UpdateNewSchoolYearEnd
|
||||
, disabled model.isProcessing
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
, div [ class "field" ]
|
||||
[ div [ class "control" ]
|
||||
[ button
|
||||
[ class "button is-primary"
|
||||
, onClick CreateSchoolYear
|
||||
, disabled
|
||||
(String.isEmpty model.newSchoolYear.name
|
||||
|| String.isEmpty model.newSchoolYear.startDate
|
||||
|| String.isEmpty model.newSchoolYear.endDate
|
||||
|| model.isProcessing
|
||||
)
|
||||
]
|
||||
[ if model.isProcessing then
|
||||
span [ class "icon" ] [ i [ class "fas fa-spinner fa-pulse" ] [] ]
|
||||
|
||||
else
|
||||
text ""
|
||||
, text " Schuljahr erstellen"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewSchoolYearsList : Model -> Html Msg
|
||||
viewSchoolYearsList model =
|
||||
div [ class "box mt-4" ]
|
||||
[ h3 [ class "subtitle" ] [ text "Vorhandene Schuljahre" ]
|
||||
, if List.isEmpty model.schoolYears then
|
||||
p [ class "has-text-centered has-text-grey" ] [ text "Keine Schuljahre vorhanden" ]
|
||||
|
||||
else
|
||||
table [ class "table is-fullwidth is-striped is-hoverable" ]
|
||||
[ thead []
|
||||
[ tr []
|
||||
[ th [] [ text "Name" ]
|
||||
, th [] [ text "Startdatum" ]
|
||||
, th [] [ text "Enddatum" ]
|
||||
, th [ class "has-text-centered" ] [ text "Status" ]
|
||||
, th [ class "has-text-centered" ] [ text "Aktionen" ]
|
||||
]
|
||||
]
|
||||
, tbody []
|
||||
(List.map viewSchoolYearRow model.schoolYears)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewSchoolYearRow : SchoolYear -> Html Msg
|
||||
viewSchoolYearRow schoolYear =
|
||||
tr []
|
||||
[ td [] [ text schoolYear.name ]
|
||||
, td [] [ text schoolYear.startDate ]
|
||||
, td [] [ text schoolYear.endDate ]
|
||||
, td [ class "has-text-centered" ]
|
||||
[ if schoolYear.isActive then
|
||||
span [ class "tag is-success" ] [ text "Aktiv" ]
|
||||
|
||||
else
|
||||
span [ class "tag is-light" ] [ text "Inaktiv" ]
|
||||
]
|
||||
, td [ class "has-text-centered" ]
|
||||
[ if not schoolYear.isActive then
|
||||
button
|
||||
[ class "button is-small is-info mr-2"
|
||||
, onClick (ActivateSchoolYear schoolYear.id)
|
||||
]
|
||||
[ text "Aktivieren" ]
|
||||
|
||||
else
|
||||
text ""
|
||||
, button
|
||||
[ class "button is-small is-danger"
|
||||
, onClick (DeleteSchoolYear schoolYear.id)
|
||||
]
|
||||
[ text "Löschen" ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- HTTP
|
||||
|
||||
|
|
@ -3795,3 +4141,84 @@ fetchMyInfo token =
|
|||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
fetchSchoolYears : String -> Cmd Msg
|
||||
fetchSchoolYears token =
|
||||
Http.request
|
||||
{ method = "GET"
|
||||
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||
, url = "/api/admin/school-years"
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectJson SchoolYearsReceived (Decode.list schoolYearDecoder)
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
fetchActiveSchoolYear : String -> Cmd Msg
|
||||
fetchActiveSchoolYear token =
|
||||
Http.request
|
||||
{ method = "GET"
|
||||
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||
, url = "/api/school-year/active"
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectJson ActiveSchoolYearReceived schoolYearDecoder
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
createSchoolYear : String -> NewSchoolYear -> Cmd Msg
|
||||
createSchoolYear token schoolYear =
|
||||
Http.request
|
||||
{ method = "POST"
|
||||
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||
, url = "/api/admin/school-years"
|
||||
, body =
|
||||
Http.jsonBody <|
|
||||
Encode.object
|
||||
[ ( "name", Encode.string schoolYear.name )
|
||||
, ( "start_date", Encode.string schoolYear.startDate )
|
||||
, ( "end_date", Encode.string schoolYear.endDate )
|
||||
]
|
||||
, expect = Http.expectWhatever SchoolYearCreated
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
activateSchoolYear : String -> Int -> Cmd Msg
|
||||
activateSchoolYear token id =
|
||||
Http.request
|
||||
{ method = "PUT"
|
||||
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||
, url = "/api/admin/school-years/" ++ String.fromInt id ++ "/activate"
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectWhatever SchoolYearActivated
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
deleteSchoolYear : String -> Int -> Cmd Msg
|
||||
deleteSchoolYear token id =
|
||||
Http.request
|
||||
{ method = "DELETE"
|
||||
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ]
|
||||
, url = "/api/admin/school-years/" ++ String.fromInt id
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectWhatever SchoolYearDeleted
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
schoolYearDecoder : Decoder SchoolYear
|
||||
schoolYearDecoder =
|
||||
Decode.map5 SchoolYear
|
||||
(field "id" int)
|
||||
(field "name" string)
|
||||
(field "start_date" string)
|
||||
(field "end_date" string)
|
||||
(field "is_active" bool)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue