2022-02-25 22:47:49 +01:00

620 lines
17 KiB
Elm

{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.DueItemsTaskForm exposing
( Action(..)
, Model
, Msg
, init
, initWith
, update
, view2
)
import Api
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
import Api.Model.TagList exposing (TagList)
import Comp.Basic as B
import Comp.CalEventInput
import Comp.ChannelRefInput
import Comp.IntField
import Comp.MenuBar as MB
import Comp.TagDropdown
import Comp.YesNoDimmer
import Data.CalEvent exposing (CalEvent)
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.TagOrder
import Data.UiSettings exposing (UiSettings)
import Data.Validated exposing (Validated(..))
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import Http
import Markdown
import Messages.Comp.DueItemsTaskForm exposing (Texts)
import Styles as S
import Util.Maybe
type alias Model =
{ settings : PeriodicDueItemsSettings
, channelModel : Comp.ChannelRefInput.Model
, tagInclModel : Comp.TagDropdown.Model
, tagExclModel : Comp.TagDropdown.Model
, remindDays : Maybe Int
, remindDaysModel : Comp.IntField.Model
, capOverdue : Bool
, enabled : Bool
, schedule : Maybe CalEvent
, scheduleModel : Comp.CalEventInput.Model
, formState : FormState
, loading : Int
, yesNoDelete : Comp.YesNoDimmer.Model
, summary : Maybe String
}
type FormState
= FormStateInitial
| FormStateHttpError Http.Error
| FormStateInvalid ValidateError
type ValidateError
= ValidateRemindDaysRequired
| ValidateCalEventInvalid
| ValidateChannelRequired
type Action
= SubmitAction PeriodicDueItemsSettings
| StartOnceAction PeriodicDueItemsSettings
| CancelAction
| DeleteAction String
| NoAction
type Msg
= Submit
| TagIncMsg Comp.TagDropdown.Msg
| TagExcMsg Comp.TagDropdown.Msg
| GetTagsResp (Result Http.Error TagList)
| RemindDaysMsg Comp.IntField.Msg
| ToggleEnabled
| ToggleCapOverdue
| CalEventMsg Comp.CalEventInput.Msg
| StartOnce
| Cancel
| RequestDelete
| YesNoDeleteMsg Comp.YesNoDimmer.Msg
| SetSummary String
| ChannelMsg Comp.ChannelRefInput.Msg
initWith : Flags -> PeriodicDueItemsSettings -> ( Model, Cmd Msg )
initWith flags s =
let
( im, ic ) =
init flags
newSchedule =
Data.CalEvent.fromEvent s.schedule
|> Maybe.withDefault Data.CalEvent.everyMonth
( sm, sc ) =
Comp.CalEventInput.init flags newSchedule
( cfm, cfc ) =
Comp.ChannelRefInput.initSelected flags s.channels
in
( { im
| settings = s
, channelModel = cfm
, tagExclModel = Comp.TagDropdown.initWith [] s.tagsExclude
, tagInclModel = Comp.TagDropdown.initWith [] s.tagsInclude
, remindDays = Just s.remindDays
, enabled = s.enabled
, capOverdue = s.capOverdue
, schedule = Just newSchedule
, scheduleModel = sm
, formState = FormStateInitial
, loading = im.loading
, yesNoDelete = Comp.YesNoDimmer.emptyModel
, summary = s.summary
}
, Cmd.batch
[ ic
, Cmd.map CalEventMsg sc
, Cmd.map ChannelMsg cfc
]
)
init : Flags -> ( Model, Cmd Msg )
init flags =
let
initialSchedule =
Data.CalEvent.everyMonth
( sm, scmd ) =
Comp.CalEventInput.init flags initialSchedule
( cfm, cfc ) =
Comp.ChannelRefInput.init flags
in
( { settings = Api.Model.PeriodicDueItemsSettings.empty
, channelModel = cfm
, tagInclModel = Comp.TagDropdown.initWith [] []
, tagExclModel = Comp.TagDropdown.initWith [] []
, remindDays = Just 1
, remindDaysModel = Comp.IntField.init (Just 1) Nothing True
, enabled = False
, capOverdue = False
, schedule = Just initialSchedule
, scheduleModel = sm
, formState = FormStateInitial
, loading = 1
, yesNoDelete = Comp.YesNoDimmer.emptyModel
, summary = Nothing
}
, Cmd.batch
[ Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
, Cmd.map CalEventMsg scmd
, Cmd.map ChannelMsg cfc
]
)
--- Update
makeSettings : Model -> Result ValidateError PeriodicDueItemsSettings
makeSettings model =
let
prev =
model.settings
rmdays =
Maybe.map Ok model.remindDays
|> Maybe.withDefault (Err ValidateRemindDaysRequired)
schedule_ =
case model.schedule of
Just s ->
Ok s
Nothing ->
Err ValidateCalEventInvalid
channelM =
let
list =
Comp.ChannelRefInput.getSelected model.channelModel
in
if list == [] then
Err ValidateChannelRequired
else
Ok list
make days timer channels =
{ prev
| tagsInclude = Comp.TagDropdown.getSelected model.tagInclModel
, tagsExclude = Comp.TagDropdown.getSelected model.tagExclModel
, remindDays = days
, capOverdue = model.capOverdue
, enabled = model.enabled
, schedule = Data.CalEvent.makeEvent timer
, summary = model.summary
, channels = channels
}
in
Result.map3 make
rmdays
schedule_
channelM
withValidSettings : (PeriodicDueItemsSettings -> Action) -> Model -> ( Model, Action, Cmd Msg )
withValidSettings mkcmd model =
case makeSettings model of
Ok set ->
( { model | formState = FormStateInitial }
, mkcmd set
, Cmd.none
)
Err errs ->
( { model | formState = FormStateInvalid errs }
, NoAction
, Cmd.none
)
update : Flags -> Msg -> Model -> ( Model, Action, Cmd Msg )
update flags msg model =
case msg of
ChannelMsg lm ->
let
( cfm, cfc ) =
Comp.ChannelRefInput.update lm model.channelModel
in
( { model | channelModel = cfm }
, NoAction
, Cmd.map ChannelMsg cfc
)
CalEventMsg lmsg ->
let
( cm, cc, cs ) =
Comp.CalEventInput.update flags
model.schedule
lmsg
model.scheduleModel
in
( { model
| schedule = cs
, scheduleModel = cm
, formState = FormStateInitial
}
, NoAction
, Cmd.map CalEventMsg cc
)
TagIncMsg m ->
let
( m2, c2 ) =
Comp.TagDropdown.update m model.tagInclModel
in
( { model
| tagInclModel = m2
, formState = FormStateInitial
}
, NoAction
, Cmd.map TagIncMsg c2
)
TagExcMsg m ->
let
( m2, c2 ) =
Comp.TagDropdown.update m model.tagExclModel
in
( { model
| tagExclModel = m2
, formState = FormStateInitial
}
, NoAction
, Cmd.map TagExcMsg c2
)
GetTagsResp (Ok tags) ->
let
incModel =
Comp.TagDropdown.initWith tags.items model.settings.tagsInclude
excModel =
Comp.TagDropdown.initWith tags.items model.settings.tagsExclude
newModel =
{ model
| loading = model.loading - 1
, tagExclModel = excModel
, tagInclModel = incModel
}
in
( newModel, NoAction, Cmd.none )
GetTagsResp (Err err) ->
( { model
| loading = model.loading - 1
, formState = FormStateHttpError err
}
, NoAction
, Cmd.none
)
RemindDaysMsg m ->
let
( pm, val ) =
Comp.IntField.update m model.remindDaysModel
in
( { model
| remindDaysModel = pm
, remindDays = val
, formState = FormStateInitial
}
, NoAction
, Cmd.none
)
ToggleEnabled ->
( { model
| enabled = not model.enabled
, formState = FormStateInitial
}
, NoAction
, Cmd.none
)
ToggleCapOverdue ->
( { model
| capOverdue = not model.capOverdue
, formState = FormStateInitial
}
, NoAction
, Cmd.none
)
Submit ->
withValidSettings
SubmitAction
model
StartOnce ->
withValidSettings
StartOnceAction
model
Cancel ->
( model, CancelAction, Cmd.none )
RequestDelete ->
let
( ym, _ ) =
Comp.YesNoDimmer.update
Comp.YesNoDimmer.activate
model.yesNoDelete
in
( { model | yesNoDelete = ym }
, NoAction
, Cmd.none
)
YesNoDeleteMsg lm ->
let
( ym, flag ) =
Comp.YesNoDimmer.update lm model.yesNoDelete
act =
if flag then
DeleteAction model.settings.id
else
NoAction
in
( { model | yesNoDelete = ym }
, act
, Cmd.none
)
SetSummary str ->
( { model | summary = Util.Maybe.fromString str }
, NoAction
, Cmd.none
)
--- View2
isFormError : Model -> Bool
isFormError model =
case model.formState of
FormStateInitial ->
False
_ ->
True
isFormSuccess : Model -> Bool
isFormSuccess model =
not (isFormError model)
view2 : Texts -> String -> UiSettings -> Model -> Html Msg
view2 texts extraClasses settings model =
let
dimmerSettings =
Comp.YesNoDimmer.defaultSettings texts.reallyDeleteTask
texts.basics.yes
texts.basics.no
startOnceBtn =
MB.SecondaryButton
{ tagger = StartOnce
, label = texts.startOnce
, title = texts.startTaskNow
, icon = Just "fa fa-play"
}
formHeader txt =
h2 [ class S.formHeader, class "mt-2" ]
[ text txt
]
in
div
[ class "flex flex-col md:relative"
, class extraClasses
]
[ Html.map YesNoDeleteMsg
(Comp.YesNoDimmer.viewN True
dimmerSettings
model.yesNoDelete
)
, B.loadingDimmer
{ active = model.loading > 0
, label = texts.basics.loading
}
, MB.view
{ start =
[ MB.PrimaryButton
{ tagger = Submit
, label = texts.basics.submit
, title = texts.basics.submitThisForm
, icon = Just "fa fa-save"
}
, MB.SecondaryButton
{ tagger = Cancel
, label = texts.basics.backToList
, title = texts.basics.backToList
, icon = Just "fa fa-arrow-left"
}
]
, end =
if model.settings.id /= "" then
[ startOnceBtn
, MB.DeleteButton
{ tagger = RequestDelete
, label = texts.basics.delete
, title = texts.deleteThisTask
, icon = Just "fa fa-trash"
}
]
else
[ startOnceBtn
]
, rootClasses = "mb-4"
, sticky = True
}
, div
[ classList
[ ( S.successMessage, isFormSuccess model )
, ( S.errorMessage, isFormError model )
, ( "hidden", model.formState == FormStateInitial )
]
, class "mb-4"
]
[ case model.formState of
FormStateInitial ->
text ""
FormStateHttpError err ->
text (texts.httpError err)
FormStateInvalid ValidateCalEventInvalid ->
text texts.invalidCalEvent
FormStateInvalid ValidateRemindDaysRequired ->
text texts.remindDaysRequired
FormStateInvalid ValidateChannelRequired ->
text texts.channelRequired
]
, div [ class "mb-4" ]
[ MB.viewItem <|
MB.Checkbox
{ tagger = \_ -> ToggleEnabled
, label = texts.enableDisable
, value = model.enabled
, id = "notify-enabled"
}
]
, div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ text texts.summary
]
, input
[ type_ "text"
, onInput SetSummary
, class S.textInput
, Maybe.withDefault "" model.summary
|> value
]
[]
, span [ class "opacity-50 text-sm" ]
[ text texts.summaryInfo
]
]
, div [ class "mb-4" ]
[ formHeader texts.channelHeader
, Html.map ChannelMsg
(Comp.ChannelRefInput.view texts.channelRef settings model.channelModel)
]
, formHeader texts.queryLabel
, div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ text texts.tagsInclude ]
, Html.map TagIncMsg
(Comp.TagDropdown.view
texts.tagDropdown
settings
DS.mainStyle
model.tagInclModel
)
, span [ class "opacity-50 text-sm" ]
[ text texts.tagsIncludeInfo
]
]
, div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ text texts.tagsExclude ]
, Html.map TagExcMsg
(Comp.TagDropdown.view
texts.tagDropdown
settings
DS.mainStyle
model.tagExclModel
)
, span [ class "opacity-50 text-sm" ]
[ text texts.tagsExcludeInfo
]
]
, Html.map RemindDaysMsg
(Comp.IntField.view
{ label = texts.remindDaysLabel
, info = texts.remindDaysInfo
, number = model.remindDays
, classes = "mb-4"
}
model.remindDaysModel
)
, div [ class "mb-4" ]
[ MB.viewItem <|
MB.Checkbox
{ tagger = \_ -> ToggleCapOverdue
, id = "notify-toggle-cap-overdue"
, value = model.capOverdue
, label = texts.capOverdue
}
, div [ class "opacity-50 text-sm" ]
[ Markdown.toHtml [] texts.capOverdueInfo
]
]
, div [ class "mb-4" ]
[ formHeader texts.schedule
, label [ class S.inputLabel ]
[ text texts.schedule
, a
[ class "float-right"
, class S.link
, href "https://github.com/eikek/calev#what-are-calendar-events"
, target "_blank"
]
[ i [ class "fa fa-question" ] []
, span [ class "pl-2" ]
[ text texts.scheduleClickForHelp
]
]
]
, Html.map CalEventMsg
(Comp.CalEventInput.view2
texts.calEventInput
""
model.schedule
model.scheduleModel
)
, span [ class "opacity-50 text-sm" ]
[ text texts.scheduleInfo
]
]
]