feat: add schoolyear based calculation

This commit is contained in:
Patryk Hegenberg 2025-11-06 07:18:23 +01:00
parent e65ba85c43
commit c07019e3eb
5 changed files with 675 additions and 44 deletions

View file

@ -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)