From ff30ed555851890923ea81d4957bd425f219a92a Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Sun, 22 Nov 2020 12:03:28 +0100
Subject: [PATCH] Add custom fields to multi-edit form

---
 modules/webapp/src/main/elm/Api.elm           | 31 ++++++++
 .../main/elm/Comp/CustomFieldMultiInput.elm   | 42 ++++++++---
 .../src/main/elm/Comp/ItemDetail/EditMenu.elm | 71 ++++++++++++++++---
 .../main/elm/Comp/ItemDetail/FormChange.elm   | 19 +++++
 .../src/main/elm/Comp/ItemDetail/View.elm     |  9 ++-
 modules/webapp/src/main/elm/Data/Icons.elm    | 15 ++++
 6 files changed, 165 insertions(+), 22 deletions(-)

diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm
index 2ef008d5..c36c709f 100644
--- a/modules/webapp/src/main/elm/Api.elm
+++ b/modules/webapp/src/main/elm/Api.elm
@@ -21,6 +21,7 @@ module Api exposing
     , deleteAttachment
     , deleteCustomField
     , deleteCustomValue
+    , deleteCustomValueMultiple
     , deleteEquip
     , deleteFolder
     , deleteImapSettings
@@ -80,6 +81,7 @@ module Api exposing
     , postTag
     , putCustomField
     , putCustomValue
+    , putCustomValueMultiple
     , putUser
     , refreshSession
     , register
@@ -159,6 +161,7 @@ import Api.Model.ItemSearch exposing (ItemSearch)
 import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
 import Api.Model.ItemsAndDate exposing (ItemsAndDate)
 import Api.Model.ItemsAndDirection exposing (ItemsAndDirection)
+import Api.Model.ItemsAndFieldValue exposing (ItemsAndFieldValue)
 import Api.Model.ItemsAndName exposing (ItemsAndName)
 import Api.Model.ItemsAndRef exposing (ItemsAndRef)
 import Api.Model.ItemsAndRefs exposing (ItemsAndRefs)
@@ -211,6 +214,34 @@ import Util.Http as Http2
 --- Custom Fields
 
 
+putCustomValueMultiple :
+    Flags
+    -> ItemsAndFieldValue
+    -> (Result Http.Error BasicResult -> msg)
+    -> Cmd msg
+putCustomValueMultiple flags data receive =
+    Http2.authPut
+        { url = flags.config.baseUrl ++ "/api/v1/sec/items/customfield"
+        , account = getAccount flags
+        , body = Http.jsonBody (Api.Model.ItemsAndFieldValue.encode data)
+        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
+        }
+
+
+deleteCustomValueMultiple :
+    Flags
+    -> ItemsAndName
+    -> (Result Http.Error BasicResult -> msg)
+    -> Cmd msg
+deleteCustomValueMultiple flags data receive =
+    Http2.authPost
+        { url = flags.config.baseUrl ++ "/api/v1/sec/items/customfieldremove"
+        , account = getAccount flags
+        , body = Http.jsonBody (Api.Model.ItemsAndName.encode data)
+        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
+        }
+
+
 deleteCustomValue :
     Flags
     -> String
diff --git a/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm b/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm
index 4eb85ced..a64413d8 100644
--- a/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm
+++ b/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm
@@ -3,6 +3,7 @@ module Comp.CustomFieldMultiInput exposing
     , Model
     , Msg
     , UpdateResult
+    , ViewSettings
     , init
     , initCmd
     , initWith
@@ -287,25 +288,46 @@ update msg model =
             UpdateResult model_ (Cmd.batch cmdList) Sub.none NoResult
 
 
-view : String -> Model -> Html Msg
-view classes model =
-    div [ class classes ]
-        (viewMenuBar model
+
+--- View
+
+
+type alias ViewSettings =
+    { showAddButton : Bool
+    , classes : String
+    }
+
+
+view : ViewSettings -> Model -> Html Msg
+view viewSettings model =
+    div [ class viewSettings.classes ]
+        (viewMenuBar viewSettings model
             :: List.map (viewCustomField model) model.visibleFields
         )
 
 
-viewMenuBar : Model -> Html Msg
-viewMenuBar model =
+viewMenuBar : ViewSettings -> Model -> Html Msg
+viewMenuBar viewSettings model =
     let
         { dropdown, selected } =
             model.fieldSelect
     in
-    div [ class "ui action input field" ]
-        [ Html.map FieldSelectMsg
-            (Comp.FixedDropdown.viewStyled "fluid" (Maybe.map mkItem selected) dropdown)
-        , addFieldLink "" model
+    div
+        [ classList
+            [ ( "field", True )
+            , ( "ui action input", viewSettings.showAddButton )
+            ]
         ]
+        (Html.map FieldSelectMsg
+            (Comp.FixedDropdown.viewStyled "fluid" (Maybe.map mkItem selected) dropdown)
+            :: (if viewSettings.showAddButton then
+                    [ addFieldLink "" model
+                    ]
+
+                else
+                    []
+               )
+        )
 
 
 viewCustomField : Model -> CustomField -> Html Msg
diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm b/modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm
index 161b5871..c919fe92 100644
--- a/modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm
+++ b/modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm
@@ -18,6 +18,7 @@ import Api.Model.ItemProposals exposing (ItemProposals)
 import Api.Model.ReferenceList exposing (ReferenceList)
 import Api.Model.Tag exposing (Tag)
 import Api.Model.TagList exposing (TagList)
+import Comp.CustomFieldMultiInput exposing (FieldResult(..))
 import Comp.DatePicker
 import Comp.DetailEdit
 import Comp.Dropdown exposing (isDropdownChangeMsg)
@@ -77,6 +78,7 @@ type alias Model =
     , concEquipModel : Comp.Dropdown.Model IdName
     , modalEdit : Maybe Comp.DetailEdit.Model
     , tagEditMode : TagEditMode
+    , customFieldModel : Comp.CustomFieldMultiInput.Model
     }
 
 
@@ -102,6 +104,7 @@ type Msg
     | GetPersonResp (Result Http.Error ReferenceList)
     | GetEquipResp (Result Http.Error EquipmentList)
     | GetFolderResp (Result Http.Error FolderList)
+    | CustomFieldMsg Comp.CustomFieldMultiInput.Msg
 
 
 init : Model
@@ -155,6 +158,7 @@ init =
     , dueDatePicker = Comp.DatePicker.emptyModel
     , modalEdit = Nothing
     , tagEditMode = AddTags
+    , customFieldModel = Comp.CustomFieldMultiInput.initWith []
     }
 
 
@@ -170,6 +174,7 @@ loadModel flags =
         , Api.getPersonsLight flags GetPersonResp
         , Api.getEquipments flags "" GetEquipResp
         , Api.getFolders flags "" False GetFolderResp
+        , Cmd.map CustomFieldMsg (Comp.CustomFieldMultiInput.initCmd flags)
         , Cmd.map ItemDatePickerMsg dpc
         , Cmd.map DueDatePickerMsg dpc
         ]
@@ -547,6 +552,36 @@ update flags msg model =
             in
             UpdateResult newModel cmd sub NoFormChange
 
+        CustomFieldMsg lm ->
+            let
+                res =
+                    Comp.CustomFieldMultiInput.update lm model.customFieldModel
+
+                model_ =
+                    { model | customFieldModel = res.model }
+
+                cmd_ =
+                    Cmd.map CustomFieldMsg res.cmd
+
+                sub_ =
+                    Sub.map CustomFieldMsg res.subs
+
+                change =
+                    case res.result of
+                        NoResult ->
+                            NoFormChange
+
+                        FieldValueRemove cf ->
+                            RemoveCustomValue cf
+
+                        FieldValueChange cf value ->
+                            CustomValueChange cf value
+
+                        FieldCreateNew ->
+                            NoFormChange
+            in
+            UpdateResult model_ cmd_ sub_ change
+
 
 nameThrottleSub : Model -> Sub Msg
 nameThrottleSub model =
@@ -614,6 +649,9 @@ renderEditForm cfg settings model =
 
                 ReplaceTags ->
                     "Tags chosen here *replace* those on selected items."
+
+        customFieldSettings =
+            Comp.CustomFieldMultiInput.ViewSettings False "field"
     in
     div [ class cfg.menuClass ]
         [ div [ class "ui form warning" ]
@@ -687,13 +725,18 @@ item visible. This message will disappear then.
                       """
                         ]
                     ]
-            , optional [ Data.Fields.Direction ] <|
-                div [ class "field" ]
-                    [ label []
-                        [ Icons.directionIcon "grey"
-                        , text "Direction"
-                        ]
-                    , Html.map DirDropdownMsg (Comp.Dropdown.view settings model.directionModel)
+            , optional [ Data.Fields.CustomFields ] <|
+                h4 [ class "ui dividing header" ]
+                    [ Icons.customFieldIcon ""
+                    , text "Custom Fields"
+                    ]
+            , optional [ Data.Fields.CustomFields ] <|
+                Html.map CustomFieldMsg
+                    (Comp.CustomFieldMultiInput.view customFieldSettings model.customFieldModel)
+            , optional [ Data.Fields.Date, Data.Fields.DueDate ] <|
+                h4 [ class "ui dividing header" ]
+                    [ Icons.itemDatesIcon ""
+                    , text "Item Dates"
                     ]
             , optional [ Data.Fields.Date ] <|
                 div [ class "field" ]
@@ -701,7 +744,7 @@ item visible. This message will disappear then.
                         [ Icons.dateIcon "grey"
                         , text "Date"
                         ]
-                    , div [ class "ui action input" ]
+                    , div [ class "ui left icon action input" ]
                         [ Html.map ItemDatePickerMsg
                             (Comp.DatePicker.viewTime
                                 model.itemDate
@@ -711,6 +754,7 @@ item visible. This message will disappear then.
                         , a [ class "ui icon button", href "", onClick RemoveDate ]
                             [ i [ class "trash alternate outline icon" ] []
                             ]
+                        , Icons.dateIcon ""
                         ]
                     ]
             , optional [ Data.Fields.DueDate ] <|
@@ -719,7 +763,7 @@ item visible. This message will disappear then.
                         [ Icons.dueDateIcon "grey"
                         , text "Due Date"
                         ]
-                    , div [ class "ui action input" ]
+                    , div [ class "ui left icon action input" ]
                         [ Html.map DueDatePickerMsg
                             (Comp.DatePicker.viewTime
                                 model.dueDate
@@ -728,6 +772,7 @@ item visible. This message will disappear then.
                             )
                         , a [ class "ui icon button", href "", onClick RemoveDueDate ]
                             [ i [ class "trash alternate outline icon" ] [] ]
+                        , Icons.dueDateIcon ""
                         ]
                     ]
             , optional [ Data.Fields.CorrOrg, Data.Fields.CorrPerson ] <|
@@ -772,6 +817,14 @@ item visible. This message will disappear then.
                         ]
                     , Html.map ConcEquipMsg (Comp.Dropdown.view settings model.concEquipModel)
                     ]
+            , optional [ Data.Fields.Direction ] <|
+                div [ class "field" ]
+                    [ label []
+                        [ Icons.directionIcon "grey"
+                        , text "Direction"
+                        ]
+                    , Html.map DirDropdownMsg (Comp.Dropdown.view settings model.directionModel)
+                    ]
             ]
         ]
 
diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm b/modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm
index b7521ba4..2bb39e91 100644
--- a/modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm
+++ b/modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm
@@ -5,9 +5,12 @@ module Comp.ItemDetail.FormChange exposing
 
 import Api
 import Api.Model.BasicResult exposing (BasicResult)
+import Api.Model.CustomField exposing (CustomField)
+import Api.Model.CustomFieldValue exposing (CustomFieldValue)
 import Api.Model.IdName exposing (IdName)
 import Api.Model.ItemsAndDate exposing (ItemsAndDate)
 import Api.Model.ItemsAndDirection exposing (ItemsAndDirection)
+import Api.Model.ItemsAndFieldValue exposing (ItemsAndFieldValue)
 import Api.Model.ItemsAndName exposing (ItemsAndName)
 import Api.Model.ItemsAndRef exposing (ItemsAndRef)
 import Api.Model.ItemsAndRefs exposing (ItemsAndRefs)
@@ -33,6 +36,8 @@ type FormChange
     | DueDateChange (Maybe Int)
     | NameChange String
     | ConfirmChange Bool
+    | CustomValueChange CustomField String
+    | RemoveCustomValue CustomField
 
 
 multiUpdate :
@@ -47,6 +52,20 @@ multiUpdate flags ids change receive =
             Set.toList ids
     in
     case change of
+        CustomValueChange field value ->
+            let
+                data =
+                    ItemsAndFieldValue items (CustomFieldValue field.id value)
+            in
+            Api.putCustomValueMultiple flags data receive
+
+        RemoveCustomValue field ->
+            let
+                data =
+                    ItemsAndName items field.id
+            in
+            Api.deleteCustomValueMultiple flags data receive
+
         ReplaceTagChange tags ->
             let
                 data =
diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail/View.elm b/modules/webapp/src/main/elm/Comp/ItemDetail/View.elm
index 7b4e43ec..ddff5af6 100644
--- a/modules/webapp/src/main/elm/Comp/ItemDetail/View.elm
+++ b/modules/webapp/src/main/elm/Comp/ItemDetail/View.elm
@@ -728,6 +728,9 @@ renderEditForm settings model =
 
             else
                 span [ class "invisible hidden" ] []
+
+        customFieldSettings =
+            Comp.CustomFieldMultiInput.ViewSettings True "field"
     in
     div [ class "ui attached segment" ]
         [ div [ class "ui form warning" ]
@@ -781,11 +784,11 @@ item visible. This message will disappear then.
                     ]
             , optional [ Data.Fields.CustomFields ] <|
                 Html.map CustomFieldMsg
-                    (Comp.CustomFieldMultiInput.view "field" model.customFieldsModel)
+                    (Comp.CustomFieldMultiInput.view customFieldSettings model.customFieldsModel)
             , optional [ Data.Fields.DueDate, Data.Fields.Date ] <|
                 h4 [ class "ui dividing header" ]
-                    [ Icons.dateIcon ""
-                    , text "Dates"
+                    [ Icons.itemDatesIcon ""
+                    , text "Item Dates"
                     ]
             , optional [ Data.Fields.Date ] <|
                 div [ class "field" ]
diff --git a/modules/webapp/src/main/elm/Data/Icons.elm b/modules/webapp/src/main/elm/Data/Icons.elm
index f970ae2f..375f11ef 100644
--- a/modules/webapp/src/main/elm/Data/Icons.elm
+++ b/modules/webapp/src/main/elm/Data/Icons.elm
@@ -19,6 +19,7 @@ module Data.Icons exposing
     , equipmentIcon
     , folder
     , folderIcon
+    , itemDatesIcon
     , organization
     , organizationIcon
     , person
@@ -85,6 +86,20 @@ correspondentIcon classes =
     i [ class (correspondent ++ " " ++ classes) ] []
 
 
+itemDates : String
+itemDates =
+    "calendar alternate outline icon"
+
+
+itemDatesIcon : String -> Html msg
+itemDatesIcon classes =
+    i
+        [ class classes
+        , class itemDates
+        ]
+        []
+
+
 date : String
 date =
     "calendar outline icon"