From a3b482fa138e2da0d3b2c6046b11363474aa1247 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Tue, 9 Jun 2020 21:10:49 +0200 Subject: [PATCH 01/10] Fix some icons --- .../webapp/src/main/elm/Comp/ItemDetail.elm | 41 ++++++--- modules/webapp/src/main/elm/Data/Icons.elm | 84 +++++++++++++++++++ .../src/main/elm/Page/ManageData/View.elm | 17 ++-- 3 files changed, 124 insertions(+), 18 deletions(-) diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail.elm b/modules/webapp/src/main/elm/Comp/ItemDetail.elm index d5b20791..26fd79de 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail.elm @@ -1820,7 +1820,7 @@ renderEditForm settings model = [ div [ class "ui form" ] [ div [ class "field" ] [ label [] - [ i [ class "tags icon" ] [] + [ Icons.tagsIcon , text "Tags" ] , Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel) @@ -1838,11 +1838,17 @@ renderEditForm settings model = ] ] , div [ class "field" ] - [ label [] [ text "Direction" ] + [ label [] + [ Icons.directionIcon + , text "Direction" + ] , Html.map DirDropdownMsg (Comp.Dropdown.view settings model.directionModel) ] , div [ class " field" ] - [ label [] [ text "Date" ] + [ label [] + [ Icons.dateIcon + , text "Date" + ] , div [ class "ui action input" ] [ Html.map ItemDatePickerMsg (Comp.DatePicker.viewTime @@ -1857,7 +1863,10 @@ renderEditForm settings model = , renderItemDateSuggestions model ] , div [ class " field" ] - [ label [] [ text "Due Date" ] + [ label [] + [ Icons.dueDateIcon + , text "Due Date" + ] , div [ class "ui action input" ] [ Html.map DueDatePickerMsg (Comp.DatePicker.viewTime @@ -1871,30 +1880,42 @@ renderEditForm settings model = , renderDueDateSuggestions model ] , h4 [ class "ui dividing header" ] - [ i [ class "tiny envelope outline icon" ] [] + [ Icons.correspondentIcon , text "Correspondent" ] , div [ class "field" ] - [ label [] [ text "Organization" ] + [ label [] + [ Icons.organizationIcon + , text "Organization" + ] , Html.map OrgDropdownMsg (Comp.Dropdown.view settings model.corrOrgModel) , renderOrgSuggestions model ] , div [ class "field" ] - [ label [] [ text "Person" ] + [ label [] + [ Icons.personIcon + , text "Person" + ] , Html.map CorrPersonMsg (Comp.Dropdown.view settings model.corrPersonModel) , renderCorrPersonSuggestions model ] , h4 [ class "ui dividing header" ] - [ i [ class "tiny comment outline icon" ] [] + [ Icons.concernedIcon , text "Concerning" ] , div [ class "field" ] - [ label [] [ text "Person" ] + [ label [] + [ Icons.personIcon + , text "Person" + ] , Html.map ConcPersonMsg (Comp.Dropdown.view settings model.concPersonModel) , renderConcPersonSuggestions model ] , div [ class "field" ] - [ label [] [ text "Equipment" ] + [ label [] + [ Icons.equipmentIcon + , text "Equipment" + ] , Html.map ConcEquipMsg (Comp.Dropdown.view settings model.concEquipModel) , renderConcEquipSuggestions model ] diff --git a/modules/webapp/src/main/elm/Data/Icons.elm b/modules/webapp/src/main/elm/Data/Icons.elm index 13d63503..bade841f 100644 --- a/modules/webapp/src/main/elm/Data/Icons.elm +++ b/modules/webapp/src/main/elm/Data/Icons.elm @@ -5,10 +5,24 @@ module Data.Icons exposing , concernedIcon , correspondent , correspondentIcon + , date + , dateIcon + , direction + , directionIcon , dueDate , dueDateIcon , editNotes , editNotesIcon + , equipment + , equipmentIcon + , organization + , organizationIcon + , person + , personIcon + , tag + , tagIcon + , tags + , tagsIcon ) import Html exposing (Html, i) @@ -35,6 +49,16 @@ correspondentIcon = i [ class correspondent ] [] +date : String +date = + "calendar outline icon" + + +dateIcon : Html msg +dateIcon = + i [ class date ] [] + + dueDate : String dueDate = "bell icon" @@ -63,3 +87,63 @@ addFiles = addFilesIcon : Html msg addFilesIcon = i [ class addFiles ] [] + + +tag : String +tag = + "tag icon" + + +tagIcon : Html msg +tagIcon = + i [ class tag ] [] + + +tags : String +tags = + "tags icon" + + +tagsIcon : Html msg +tagsIcon = + i [ class tags ] [] + + +direction : String +direction = + "exchange icon" + + +directionIcon : Html msg +directionIcon = + i [ class direction ] [] + + +person : String +person = + "user icon" + + +personIcon : Html msg +personIcon = + i [ class person ] [] + + +organization : String +organization = + "factory icon" + + +organizationIcon : Html msg +organizationIcon = + i [ class organization ] [] + + +equipment : String +equipment = + "box icon" + + +equipmentIcon : Html msg +equipmentIcon = + i [ class equipment ] [] diff --git a/modules/webapp/src/main/elm/Page/ManageData/View.elm b/modules/webapp/src/main/elm/Page/ManageData/View.elm index ed9586af..fabdc56b 100644 --- a/modules/webapp/src/main/elm/Page/ManageData/View.elm +++ b/modules/webapp/src/main/elm/Page/ManageData/View.elm @@ -4,6 +4,7 @@ import Comp.EquipmentManage import Comp.OrgManage import Comp.PersonManage import Comp.TagManage +import Data.Icons as Icons import Data.UiSettings exposing (UiSettings) import Html exposing (..) import Html.Attributes exposing (..) @@ -25,28 +26,28 @@ view settings model = [ classActive (model.currentTab == Just TagTab) "link icon item" , onClick (SetTab TagTab) ] - [ i [ class "tag icon" ] [] + [ Icons.tagIcon , text "Tag" ] , div [ classActive (model.currentTab == Just EquipTab) "link icon item" , onClick (SetTab EquipTab) ] - [ i [ class "box icon" ] [] + [ Icons.equipmentIcon , text "Equipment" ] , div [ classActive (model.currentTab == Just OrgTab) "link icon item" , onClick (SetTab OrgTab) ] - [ i [ class "factory icon" ] [] + [ Icons.organizationIcon , text "Organization" ] , div [ classActive (model.currentTab == Just PersonTab) "link icon item" , onClick (SetTab PersonTab) ] - [ i [ class "user icon" ] [] + [ Icons.personIcon , text "Person" ] ] @@ -77,7 +78,7 @@ view settings model = viewTags : Model -> List (Html Msg) viewTags model = [ h2 [ class "ui header" ] - [ i [ class "ui tag icon" ] [] + [ Icons.tagIcon , div [ class "content" ] [ text "Tags" ] @@ -89,7 +90,7 @@ viewTags model = viewEquip : Model -> List (Html Msg) viewEquip model = [ h2 [ class "ui header" ] - [ i [ class "ui box icon" ] [] + [ Icons.equipmentIcon , div [ class "content" ] [ text "Equipment" ] @@ -101,7 +102,7 @@ viewEquip model = viewOrg : UiSettings -> Model -> List (Html Msg) viewOrg settings model = [ h2 [ class "ui header" ] - [ i [ class "ui factory icon" ] [] + [ Icons.organizationIcon , div [ class "content" ] [ text "Organizations" ] @@ -113,7 +114,7 @@ viewOrg settings model = viewPerson : UiSettings -> Model -> List (Html Msg) viewPerson settings model = [ h2 [ class "ui header" ] - [ i [ class "ui user icon" ] [] + [ Icons.personIcon , div [ class "content" ] [ text "Person" ] From 80131522dec5d82b16547c6bae4a67890a0d834b Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Tue, 9 Jun 2020 22:57:21 +0200 Subject: [PATCH 02/10] Use same limit for searchable dropdowns --- modules/webapp/src/main/elm/Comp/Dropdown.elm | 4 ++-- modules/webapp/src/main/elm/Util/Tag.elm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/webapp/src/main/elm/Comp/Dropdown.elm b/modules/webapp/src/main/elm/Comp/Dropdown.elm index 11bf5309..97e8c775 100644 --- a/modules/webapp/src/main/elm/Comp/Dropdown.elm +++ b/modules/webapp/src/main/elm/Comp/Dropdown.elm @@ -88,7 +88,7 @@ makeSingle : makeSingle opts = makeModel { multiple = False - , searchable = \n -> n > 8 + , searchable = \n -> n > 5 , makeOption = opts.makeOption , labelColor = \_ -> \_ -> "" , placeholder = opts.placeholder @@ -126,7 +126,7 @@ makeMultiple : makeMultiple opts = makeModel { multiple = True - , searchable = \n -> n > 8 + , searchable = \n -> n > 5 , makeOption = opts.makeOption , labelColor = opts.labelColor , placeholder = "" diff --git a/modules/webapp/src/main/elm/Util/Tag.elm b/modules/webapp/src/main/elm/Util/Tag.elm index 87d1831f..413a09eb 100644 --- a/modules/webapp/src/main/elm/Util/Tag.elm +++ b/modules/webapp/src/main/elm/Util/Tag.elm @@ -9,7 +9,7 @@ makeDropdownModel : Comp.Dropdown.Model Tag makeDropdownModel = Comp.Dropdown.makeModel { multiple = True - , searchable = \n -> n > 4 + , searchable = \n -> n > 5 , makeOption = \tag -> { value = tag.id, text = tag.name } , labelColor = \tag -> From d440247857780b08207f5d93fff34f52048553d2 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Tue, 9 Jun 2020 23:20:58 +0200 Subject: [PATCH 03/10] Change modify item routes to use http put --- .../src/main/resources/docspell-openapi.yml | 20 +++++++++---------- .../restserver/routes/ItemRoutes.scala | 20 +++++++++---------- modules/webapp/src/main/elm/Api.elm | 20 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index bd85e136..25abffca 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1060,7 +1060,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/tags: - post: + put: tags: [ Item ] summary: Set new set of tags. description: | @@ -1082,7 +1082,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/direction: - post: + put: tags: [ Item ] summary: Set the direction of an item. description: | @@ -1104,7 +1104,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/corrOrg: - post: + put: tags: [ Item ] summary: Set the correspondent organization of an item. description: | @@ -1126,7 +1126,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/corrPerson: - post: + put: tags: [ Item ] summary: Set the correspondent person of an item. description: | @@ -1148,7 +1148,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/concPerson: - post: + put: tags: [ Item ] summary: Set the concerning person of an item. description: | @@ -1170,7 +1170,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/concEquipment: - post: + put: tags: [ Item ] summary: Set the concering equipment of an item. description: | @@ -1192,7 +1192,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/notes: - post: + put: tags: [ Item ] summary: Set notes of an item. description: | @@ -1214,7 +1214,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/name: - post: + put: tags: [ Item ] summary: Set the name of an item. description: | @@ -1272,7 +1272,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/date: - post: + put: tags: [ Item ] summary: Sets the item date. description: | @@ -1294,7 +1294,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/duedate: - post: + put: tags: [ Item ] summary: Sets the items due date. description: | diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala index e2070046..f2511f2e 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala @@ -71,56 +71,56 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Item back to created.")) } yield resp - case req @ POST -> Root / Ident(id) / "tags" => + case req @ PUT -> Root / Ident(id) / "tags" => for { tags <- req.as[ReferenceList].map(_.items) res <- backend.item.setTags(id, tags.map(_.id), user.account.collective) resp <- Ok(Conversions.basicResult(res, "Tags updated")) } yield resp - case req @ POST -> Root / Ident(id) / "direction" => + case req @ PUT -> Root / Ident(id) / "direction" => for { dir <- req.as[DirectionValue] res <- backend.item.setDirection(id, dir.direction, user.account.collective) resp <- Ok(Conversions.basicResult(res, "Direction updated")) } yield resp - case req @ POST -> Root / Ident(id) / "corrOrg" => + case req @ PUT -> Root / Ident(id) / "corrOrg" => for { idref <- req.as[OptionalId] res <- backend.item.setCorrOrg(id, idref.id, user.account.collective) resp <- Ok(Conversions.basicResult(res, "Correspondent organization updated")) } yield resp - case req @ POST -> Root / Ident(id) / "corrPerson" => + case req @ PUT -> Root / Ident(id) / "corrPerson" => for { idref <- req.as[OptionalId] res <- backend.item.setCorrPerson(id, idref.id, user.account.collective) resp <- Ok(Conversions.basicResult(res, "Correspondent person updated")) } yield resp - case req @ POST -> Root / Ident(id) / "concPerson" => + case req @ PUT -> Root / Ident(id) / "concPerson" => for { idref <- req.as[OptionalId] res <- backend.item.setConcPerson(id, idref.id, user.account.collective) resp <- Ok(Conversions.basicResult(res, "Concerned person updated")) } yield resp - case req @ POST -> Root / Ident(id) / "concEquipment" => + case req @ PUT -> Root / Ident(id) / "concEquipment" => for { idref <- req.as[OptionalId] res <- backend.item.setConcEquip(id, idref.id, user.account.collective) resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated")) } yield resp - case req @ POST -> Root / Ident(id) / "notes" => + case req @ PUT -> Root / Ident(id) / "notes" => for { text <- req.as[OptionalText] res <- backend.item.setNotes(id, text.text.notEmpty, user.account.collective) resp <- Ok(Conversions.basicResult(res, "Notes updated")) } yield resp - case req @ POST -> Root / Ident(id) / "name" => + case req @ PUT -> Root / Ident(id) / "name" => for { text <- req.as[OptionalText] res <- backend.item.setName( @@ -131,7 +131,7 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Name updated")) } yield resp - case req @ POST -> Root / Ident(id) / "duedate" => + case req @ PUT -> Root / Ident(id) / "duedate" => for { date <- req.as[OptionalDate] _ <- logger.fdebug(s"Setting item due date to ${date.date}") @@ -139,7 +139,7 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Item due date updated")) } yield resp - case req @ POST -> Root / Ident(id) / "date" => + case req @ PUT -> Root / Ident(id) / "date" => for { date <- req.as[OptionalDate] _ <- logger.fdebug(s"Setting item date to ${date.date}") diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index f0d51946..760b3324 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -1047,7 +1047,7 @@ itemDetail flags id receive = setTags : Flags -> String -> ReferenceList -> (Result Http.Error BasicResult -> msg) -> Cmd msg setTags flags item tags receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/tags" , account = getAccount flags , body = Http.jsonBody (Api.Model.ReferenceList.encode tags) @@ -1057,7 +1057,7 @@ setTags flags item tags receive = setDirection : Flags -> String -> DirectionValue -> (Result Http.Error BasicResult -> msg) -> Cmd msg setDirection flags item dir receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/direction" , account = getAccount flags , body = Http.jsonBody (Api.Model.DirectionValue.encode dir) @@ -1067,7 +1067,7 @@ setDirection flags item dir receive = setCorrOrg : Flags -> String -> OptionalId -> (Result Http.Error BasicResult -> msg) -> Cmd msg setCorrOrg flags item id receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/corrOrg" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalId.encode id) @@ -1077,7 +1077,7 @@ setCorrOrg flags item id receive = setCorrPerson : Flags -> String -> OptionalId -> (Result Http.Error BasicResult -> msg) -> Cmd msg setCorrPerson flags item id receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/corrPerson" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalId.encode id) @@ -1087,7 +1087,7 @@ setCorrPerson flags item id receive = setConcPerson : Flags -> String -> OptionalId -> (Result Http.Error BasicResult -> msg) -> Cmd msg setConcPerson flags item id receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/concPerson" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalId.encode id) @@ -1097,7 +1097,7 @@ setConcPerson flags item id receive = setConcEquip : Flags -> String -> OptionalId -> (Result Http.Error BasicResult -> msg) -> Cmd msg setConcEquip flags item id receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/concEquipment" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalId.encode id) @@ -1107,7 +1107,7 @@ setConcEquip flags item id receive = setItemName : Flags -> String -> OptionalText -> (Result Http.Error BasicResult -> msg) -> Cmd msg setItemName flags item text receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/name" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalText.encode text) @@ -1117,7 +1117,7 @@ setItemName flags item text receive = setItemNotes : Flags -> String -> OptionalText -> (Result Http.Error BasicResult -> msg) -> Cmd msg setItemNotes flags item text receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/notes" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalText.encode text) @@ -1127,7 +1127,7 @@ setItemNotes flags item text receive = setItemDate : Flags -> String -> OptionalDate -> (Result Http.Error BasicResult -> msg) -> Cmd msg setItemDate flags item date receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/date" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalDate.encode date) @@ -1137,7 +1137,7 @@ setItemDate flags item date receive = setItemDueDate : Flags -> String -> OptionalDate -> (Result Http.Error BasicResult -> msg) -> Cmd msg setItemDueDate flags item date receive = - Http2.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/duedate" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalDate.encode date) From f407f08ed3bc1345df9e257fb01100b907ebaca1 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Wed, 10 Jun 2020 00:25:24 +0200 Subject: [PATCH 04/10] Add a route to add a new tag and associate it to an item --- .../scala/docspell/backend/ops/OItem.scala | 416 ++++++++++-------- .../src/main/resources/docspell-openapi.yml | 25 ++ .../restserver/routes/ItemRoutes.scala | 8 + 3 files changed, 255 insertions(+), 194 deletions(-) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala index a108f0bd..6b1ecd98 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala @@ -43,8 +43,12 @@ trait OItem[F[_]] { collective: Ident ): F[Option[AttachmentArchiveData[F]]] + /** Sets the given tags (removing all existing ones). */ def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult] + /** Create a new tag and add it to the item. */ + def addNewTag(item: Ident, tag: RTag): F[AddResult] + def setDirection(item: Ident, direction: Direction, collective: Ident): F[AddResult] def setCorrOrg(item: Ident, org: Option[Ident], collective: Ident): F[AddResult] @@ -132,220 +136,244 @@ object OItem { } def apply[F[_]: Effect](store: Store[F]): Resource[F, OItem[F]] = - Resource.pure[F, OItem[F]](new OItem[F] { + for { + otag <- OTag(store) + oitem <- Resource.pure[F, OItem[F]](new OItem[F] { + def moveAttachmentBefore( + itemId: Ident, + source: Ident, + target: Ident + ): F[AddResult] = + store + .transact(QItem.moveAttachmentBefore(itemId, source, target)) + .attempt + .map(AddResult.fromUpdate) - def moveAttachmentBefore( - itemId: Ident, - source: Ident, - target: Ident - ): F[AddResult] = - store - .transact(QItem.moveAttachmentBefore(itemId, source, target)) - .attempt - .map(AddResult.fromUpdate) + def findItem(id: Ident, collective: Ident): F[Option[ItemData]] = + store + .transact(QItem.findItem(id)) + .map(opt => opt.flatMap(_.filterCollective(collective))) - def findItem(id: Ident, collective: Ident): F[Option[ItemData]] = - store - .transact(QItem.findItem(id)) - .map(opt => opt.flatMap(_.filterCollective(collective))) + def findItems(q: Query, batch: Batch): F[Vector[ListItem]] = + store + .transact(QItem.findItems(q, batch).take(batch.limit.toLong)) + .compile + .toVector - def findItems(q: Query, batch: Batch): F[Vector[ListItem]] = - store - .transact(QItem.findItems(q, batch).take(batch.limit.toLong)) - .compile - .toVector + def findItemsWithTags(q: Query, batch: Batch): F[Vector[ListItemWithTags]] = + store + .transact(QItem.findItemsWithTags(q, batch).take(batch.limit.toLong)) + .compile + .toVector - def findItemsWithTags(q: Query, batch: Batch): F[Vector[ListItemWithTags]] = - store - .transact(QItem.findItemsWithTags(q, batch).take(batch.limit.toLong)) - .compile - .toVector + def findAttachment(id: Ident, collective: Ident): F[Option[AttachmentData[F]]] = + store + .transact(RAttachment.findByIdAndCollective(id, collective)) + .flatMap({ + case Some(ra) => + makeBinaryData(ra.fileId) { m => + AttachmentData[F]( + ra, + m, + store.bitpeace.fetchData2(RangeDef.all)(Stream.emit(m)) + ) + } - def findAttachment(id: Ident, collective: Ident): F[Option[AttachmentData[F]]] = - store - .transact(RAttachment.findByIdAndCollective(id, collective)) - .flatMap({ - case Some(ra) => - makeBinaryData(ra.fileId) { m => - AttachmentData[F]( - ra, - m, - store.bitpeace.fetchData2(RangeDef.all)(Stream.emit(m)) + case None => + (None: Option[AttachmentData[F]]).pure[F] + }) + + def findAttachmentSource( + id: Ident, + collective: Ident + ): F[Option[AttachmentSourceData[F]]] = + store + .transact(RAttachmentSource.findByIdAndCollective(id, collective)) + .flatMap({ + case Some(ra) => + makeBinaryData(ra.fileId) { m => + AttachmentSourceData[F]( + ra, + m, + store.bitpeace.fetchData2(RangeDef.all)(Stream.emit(m)) + ) + } + + case None => + (None: Option[AttachmentSourceData[F]]).pure[F] + }) + + def findAttachmentArchive( + id: Ident, + collective: Ident + ): F[Option[AttachmentArchiveData[F]]] = + store + .transact(RAttachmentArchive.findByIdAndCollective(id, collective)) + .flatMap({ + case Some(ra) => + makeBinaryData(ra.fileId) { m => + AttachmentArchiveData[F]( + ra, + m, + store.bitpeace.fetchData2(RangeDef.all)(Stream.emit(m)) + ) + } + + case None => + (None: Option[AttachmentArchiveData[F]]).pure[F] + }) + + private def makeBinaryData[A](fileId: Ident)(f: FileMeta => A): F[Option[A]] = + store.bitpeace + .get(fileId.id) + .unNoneTerminate + .compile + .last + .map( + _.map(m => f(m)) + ) + + def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult] = { + val db = for { + cid <- RItem.getCollective(item) + nd <- + if (cid.contains(collective)) RTagItem.deleteItemTags(item) + else 0.pure[ConnectionIO] + ni <- + if (tagIds.nonEmpty && cid.contains(collective)) + RTagItem.insertItemTags(item, tagIds) + else 0.pure[ConnectionIO] + } yield nd + ni + + store.transact(db).attempt.map(AddResult.fromUpdate) + } + + def addNewTag(item: Ident, tag: RTag): F[AddResult] = + (for { + _ <- OptionT(store.transact(RItem.getCollective(item))) + .filter(_ == tag.collective) + addres <- OptionT.liftF(otag.add(tag)) + _ <- addres match { + case AddResult.Success => + OptionT.liftF( + store.transact(RTagItem.insertItemTags(item, List(tag.tagId))) ) - } + case AddResult.EntityExists(_) => + OptionT.pure[F](0) + case AddResult.Failure(_) => + OptionT.pure[F](0) + } + } yield addres) + .getOrElse(AddResult.Failure(new Exception("Collective mismatch"))) - case None => - (None: Option[AttachmentData[F]]).pure[F] - }) + def setDirection( + item: Ident, + direction: Direction, + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateDirection(item, collective, direction)) + .attempt + .map(AddResult.fromUpdate) - def findAttachmentSource( - id: Ident, - collective: Ident - ): F[Option[AttachmentSourceData[F]]] = - store - .transact(RAttachmentSource.findByIdAndCollective(id, collective)) - .flatMap({ - case Some(ra) => - makeBinaryData(ra.fileId) { m => - AttachmentSourceData[F]( - ra, - m, - store.bitpeace.fetchData2(RangeDef.all)(Stream.emit(m)) - ) - } + def setCorrOrg(item: Ident, org: Option[Ident], collective: Ident): F[AddResult] = + store + .transact(RItem.updateCorrOrg(item, collective, org)) + .attempt + .map(AddResult.fromUpdate) - case None => - (None: Option[AttachmentSourceData[F]]).pure[F] - }) + def setCorrPerson( + item: Ident, + person: Option[Ident], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateCorrPerson(item, collective, person)) + .attempt + .map(AddResult.fromUpdate) - def findAttachmentArchive( - id: Ident, - collective: Ident - ): F[Option[AttachmentArchiveData[F]]] = - store - .transact(RAttachmentArchive.findByIdAndCollective(id, collective)) - .flatMap({ - case Some(ra) => - makeBinaryData(ra.fileId) { m => - AttachmentArchiveData[F]( - ra, - m, - store.bitpeace.fetchData2(RangeDef.all)(Stream.emit(m)) - ) - } + def setConcPerson( + item: Ident, + person: Option[Ident], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateConcPerson(item, collective, person)) + .attempt + .map(AddResult.fromUpdate) - case None => - (None: Option[AttachmentArchiveData[F]]).pure[F] - }) + def setConcEquip( + item: Ident, + equip: Option[Ident], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateConcEquip(item, collective, equip)) + .attempt + .map(AddResult.fromUpdate) - private def makeBinaryData[A](fileId: Ident)(f: FileMeta => A): F[Option[A]] = - store.bitpeace - .get(fileId.id) - .unNoneTerminate - .compile - .last - .map( - _.map(m => f(m)) - ) + def setNotes( + item: Ident, + notes: Option[String], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateNotes(item, collective, notes)) + .attempt + .map(AddResult.fromUpdate) - def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult] = { - val db = for { - cid <- RItem.getCollective(item) - nd <- - if (cid.contains(collective)) RTagItem.deleteItemTags(item) - else 0.pure[ConnectionIO] - ni <- - if (tagIds.nonEmpty && cid.contains(collective)) - RTagItem.insertItemTags(item, tagIds) - else 0.pure[ConnectionIO] - } yield nd + ni + def setName(item: Ident, name: String, collective: Ident): F[AddResult] = + store + .transact(RItem.updateName(item, collective, name)) + .attempt + .map(AddResult.fromUpdate) - store.transact(db).attempt.map(AddResult.fromUpdate) - } + def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] = + store + .transact(RItem.updateStateForCollective(item, state, collective)) + .attempt + .map(AddResult.fromUpdate) - def setDirection( - item: Ident, - direction: Direction, - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateDirection(item, collective, direction)) - .attempt - .map(AddResult.fromUpdate) + def setItemDate( + item: Ident, + date: Option[Timestamp], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateDate(item, collective, date)) + .attempt + .map(AddResult.fromUpdate) - def setCorrOrg(item: Ident, org: Option[Ident], collective: Ident): F[AddResult] = - store - .transact(RItem.updateCorrOrg(item, collective, org)) - .attempt - .map(AddResult.fromUpdate) + def setItemDueDate( + item: Ident, + date: Option[Timestamp], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateDueDate(item, collective, date)) + .attempt + .map(AddResult.fromUpdate) - def setCorrPerson( - item: Ident, - person: Option[Ident], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateCorrPerson(item, collective, person)) - .attempt - .map(AddResult.fromUpdate) + def deleteItem(itemId: Ident, collective: Ident): F[Int] = + QItem.delete(store)(itemId, collective) - def setConcPerson( - item: Ident, - person: Option[Ident], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateConcPerson(item, collective, person)) - .attempt - .map(AddResult.fromUpdate) + def getProposals(item: Ident, collective: Ident): F[MetaProposalList] = + store.transact(QAttachment.getMetaProposals(item, collective)) - def setConcEquip( - item: Ident, - equip: Option[Ident], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateConcEquip(item, collective, equip)) - .attempt - .map(AddResult.fromUpdate) + def findAttachmentMeta(id: Ident, collective: Ident): F[Option[RAttachmentMeta]] = + store.transact(QAttachment.getAttachmentMeta(id, collective)) - def setNotes(item: Ident, notes: Option[String], collective: Ident): F[AddResult] = - store - .transact(RItem.updateNotes(item, collective, notes)) - .attempt - .map(AddResult.fromUpdate) + def findByFileCollective(checksum: String, collective: Ident): F[Vector[RItem]] = + store.transact(QItem.findByChecksum(checksum, collective)) - def setName(item: Ident, name: String, collective: Ident): F[AddResult] = - store - .transact(RItem.updateName(item, collective, name)) - .attempt - .map(AddResult.fromUpdate) + def findByFileSource(checksum: String, sourceId: Ident): F[Vector[RItem]] = + store.transact((for { + coll <- OptionT(RSource.findCollective(sourceId)) + items <- OptionT.liftF(QItem.findByChecksum(checksum, coll)) + } yield items).getOrElse(Vector.empty)) - def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] = - store - .transact(RItem.updateStateForCollective(item, state, collective)) - .attempt - .map(AddResult.fromUpdate) - - def setItemDate( - item: Ident, - date: Option[Timestamp], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateDate(item, collective, date)) - .attempt - .map(AddResult.fromUpdate) - - def setItemDueDate( - item: Ident, - date: Option[Timestamp], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateDueDate(item, collective, date)) - .attempt - .map(AddResult.fromUpdate) - - def deleteItem(itemId: Ident, collective: Ident): F[Int] = - QItem.delete(store)(itemId, collective) - - def getProposals(item: Ident, collective: Ident): F[MetaProposalList] = - store.transact(QAttachment.getMetaProposals(item, collective)) - - def findAttachmentMeta(id: Ident, collective: Ident): F[Option[RAttachmentMeta]] = - store.transact(QAttachment.getAttachmentMeta(id, collective)) - - def findByFileCollective(checksum: String, collective: Ident): F[Vector[RItem]] = - store.transact(QItem.findByChecksum(checksum, collective)) - - def findByFileSource(checksum: String, sourceId: Ident): F[Vector[RItem]] = - store.transact((for { - coll <- OptionT(RSource.findCollective(sourceId)) - items <- OptionT.liftF(QItem.findByChecksum(checksum, coll)) - } yield items).getOrElse(Vector.empty)) - - def deleteAttachment(id: Ident, collective: Ident): F[Int] = - QAttachment.deleteSingleAttachment(store)(id, collective) - }) + def deleteAttachment(id: Ident, collective: Ident): F[Int] = + QAttachment.deleteSingleAttachment(store)(id, collective) + }) + } yield oitem } diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 25abffca..cb32158e 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1081,6 +1081,31 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" + post: + tags: [ Item ] + summary: Add a new tag to an item. + description: | + Creates a new tag and associates it to the given item. + + The tag's `id` and `created` are generated and not used from + the given data, so it can be left empty. Only `name` and + `category` are used, where `category` is optional. + security: + - authTokenHeader: [] + parameters: + - $ref: "#/components/parameters/id" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Tag" + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" /sec/item/{id}/direction: put: tags: [ Item ] diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala index f2511f2e..60a7e3a7 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala @@ -78,6 +78,14 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Tags updated")) } yield resp + case req @ POST -> Root / Ident(id) / "tags" => + for { + data <- req.as[Tag] + rtag <- Conversions.newTag(data, user.account.collective) + res <- backend.item.addNewTag(id, rtag) + resp <- Ok(Conversions.basicResult(res, "Tag added.")) + } yield resp + case req @ PUT -> Root / Ident(id) / "direction" => for { dir <- req.as[DirectionValue] From 459647f83f07999e4df067da185ab2f70746de1a Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Thu, 11 Jun 2020 21:52:10 +0200 Subject: [PATCH 05/10] Add new tags in item detail view --- modules/webapp/src/main/elm/Api.elm | 29 +- .../webapp/src/main/elm/Comp/DetailEdit.elm | 362 ++++++++++++++++++ .../webapp/src/main/elm/Comp/ItemDetail.elm | 57 ++- 3 files changed, 436 insertions(+), 12 deletions(-) create mode 100644 modules/webapp/src/main/elm/Comp/DetailEdit.elm diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 760b3324..8ad6ec93 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -1,5 +1,6 @@ module Api exposing - ( cancelJob + ( addTag + , cancelJob , changePassword , checkCalEvent , createImapSettings @@ -693,7 +694,7 @@ getContacts flags kind q receive = --- Tags +--- Tags getTags : Flags -> String -> (Result Http.Error TagList -> msg) -> Cmd msg @@ -732,7 +733,7 @@ deleteTag flags tag receive = --- Equipments +--- Equipments getEquipments : Flags -> String -> (Result Http.Error EquipmentList -> msg) -> Cmd msg @@ -771,7 +772,7 @@ deleteEquip flags equip receive = --- Organization +--- Organization getOrgLight : Flags -> (Result Http.Error ReferenceList -> msg) -> Cmd msg @@ -819,7 +820,7 @@ deleteOrg flags org receive = --- Person +--- Person getPersonsLight : Flags -> (Result Http.Error ReferenceList -> msg) -> Cmd msg @@ -906,7 +907,7 @@ deleteSource flags src receive = --- Users +--- Users getUsers : Flags -> (Result Http.Error UserList -> msg) -> Cmd msg @@ -958,7 +959,7 @@ deleteUser flags user receive = --- Job Queue +--- Job Queue cancelJob : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg @@ -1008,7 +1009,7 @@ getJobQueueStateTask flags = --- Item +--- Item moveAttachmentBefore : @@ -1055,6 +1056,16 @@ setTags flags item tags receive = } +addTag : Flags -> String -> Tag -> (Result Http.Error BasicResult -> msg) -> Cmd msg +addTag flags item tag receive = + Http2.authPost + { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/tags" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.Tag.encode tag) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + setDirection : Flags -> String -> DirectionValue -> (Result Http.Error BasicResult -> msg) -> Cmd msg setDirection flags item dir receive = Http2.authPut @@ -1184,7 +1195,7 @@ getItemProposals flags item receive = --- Helper +--- Helper getAccount : Flags -> AuthResult diff --git a/modules/webapp/src/main/elm/Comp/DetailEdit.elm b/modules/webapp/src/main/elm/Comp/DetailEdit.elm new file mode 100644 index 00000000..225f6af5 --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/DetailEdit.elm @@ -0,0 +1,362 @@ +module Comp.DetailEdit exposing + ( Model + , Msg + , Value(..) + , fold + , initEquip + , initOrg + , initPerson + , initTag + , initTagByName + , update + , view + , viewModal + ) + +{-| Module for allowing to edit metadata in the item-edit menu. + +It is only possible to edit one thing at a time, suitable for being +rendered in a modal. + +-} + +import Api +import Api.Model.BasicResult exposing (BasicResult) +import Api.Model.Equipment exposing (Equipment) +import Api.Model.Organization exposing (Organization) +import Api.Model.Person exposing (Person) +import Api.Model.Tag exposing (Tag) +import Comp.EquipmentForm +import Comp.OrgForm +import Comp.PersonForm +import Comp.TagForm +import Data.Flags exposing (Flags) +import Data.Icons as Icons +import Data.UiSettings exposing (UiSettings) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (onClick) +import Http +import Util.Http + + +type alias Model = + { form : FormModel + , itemId : String + , submitting : Bool + , result : Maybe BasicResult + } + + +type FormModel + = TM Comp.TagForm.Model + | PM Comp.PersonForm.Model + | OM Comp.OrgForm.Model + | EM Comp.EquipmentForm.Model + + +fold : + (Comp.TagForm.Model -> a) + -> (Comp.PersonForm.Model -> a) + -> (Comp.OrgForm.Model -> a) + -> (Comp.EquipmentForm.Model -> a) + -> FormModel + -> a +fold ft fp fo fe model = + case model of + TM tm -> + ft tm + + PM pm -> + fp pm + + OM om -> + fo om + + EM em -> + fe em + + +init : String -> FormModel -> Model +init itemId fm = + { form = fm + , itemId = itemId + , submitting = False + , result = Nothing + } + + +initEquip : String -> Comp.EquipmentForm.Model -> Model +initEquip itemId em = + init itemId (EM em) + + +initOrg : String -> Comp.OrgForm.Model -> Model +initOrg itemId om = + init itemId (OM om) + + +initPerson : String -> Comp.PersonForm.Model -> Model +initPerson itemId pm = + init itemId (PM pm) + + +initTag : String -> Comp.TagForm.Model -> Model +initTag itemId tm = + init itemId (TM tm) + + +initTagByName : String -> String -> Model +initTagByName itemId name = + let + tm = + Comp.TagForm.emptyModel + + tm_ = + { tm | name = name } + in + initTag itemId tm_ + + +type Msg + = TagMsg Comp.TagForm.Msg + | PersonMsg Comp.PersonForm.Msg + | OrgMsg Comp.OrgForm.Msg + | EquipMsg Comp.EquipmentForm.Msg + | Submit + | Cancel + | SubmitResp (Result Http.Error BasicResult) + + +type Value + = SubmitTag Tag + | SubmitPerson Person + | SubmitOrg Organization + | SubmitEquip Equipment + | CancelForm + + +makeValue : FormModel -> Value +makeValue fm = + case fm of + TM tm -> + SubmitTag (Comp.TagForm.getTag tm) + + PM pm -> + SubmitPerson (Comp.PersonForm.getPerson pm) + + OM om -> + SubmitOrg (Comp.OrgForm.getOrg om) + + EM em -> + SubmitEquip (Comp.EquipmentForm.getEquipment em) + + + +--- Update + + +update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe Value ) +update flags msg model = + case msg of + Cancel -> + ( model, Cmd.none, Just CancelForm ) + + SubmitResp (Ok res) -> + ( { model + | result = Just res + , submitting = False + } + , Cmd.none + , Just (makeValue model.form) + ) + + SubmitResp (Err err) -> + ( { model + | result = Just (BasicResult False (Util.Http.errorToString err)) + , submitting = False + } + , Cmd.none + , Nothing + ) + + Submit -> + case model.form of + TM tm -> + let + tag = + Comp.TagForm.getTag tm + in + if Comp.TagForm.isValid tm then + ( { model | submitting = True } + , Api.addTag flags model.itemId tag SubmitResp + , Nothing + ) + + else + ( model, Cmd.none, Nothing ) + + _ -> + Debug.todo "implement" + + TagMsg lm -> + case model.form of + TM tm -> + let + ( tm_, tc_ ) = + Comp.TagForm.update flags lm tm + in + ( { model | form = TM tm_ } + , Cmd.map TagMsg tc_ + , Nothing + ) + + _ -> + ( model, Cmd.none, Nothing ) + + PersonMsg lm -> + case model.form of + PM pm -> + let + ( pm_, pc_ ) = + Comp.PersonForm.update flags lm pm + in + ( { model | form = PM pm_ } + , Cmd.map PersonMsg pc_ + , Nothing + ) + + _ -> + ( model, Cmd.none, Nothing ) + + OrgMsg lm -> + case model.form of + OM om -> + let + ( om_, oc_ ) = + Comp.OrgForm.update flags lm om + in + ( { model | form = OM om_ } + , Cmd.map OrgMsg oc_ + , Nothing + ) + + _ -> + ( model, Cmd.none, Nothing ) + + EquipMsg lm -> + case model.form of + EM em -> + let + ( em_, ec_ ) = + Comp.EquipmentForm.update flags lm em + in + ( { model | form = EM em_ } + , Cmd.map EquipMsg ec_ + , Nothing + ) + + _ -> + ( model, Cmd.none, Nothing ) + + + +--- View + + +view : UiSettings -> Model -> Html Msg +view settings model = + div [] + [ case model.form of + TM tm -> + Html.map TagMsg (Comp.TagForm.view tm) + + PM pm -> + Html.map PersonMsg (Comp.PersonForm.view settings pm) + + OM om -> + Html.map OrgMsg (Comp.OrgForm.view settings om) + + EM em -> + Html.map EquipMsg (Comp.EquipmentForm.view em) + , div [ class "ui divider" ] [] + , button + [ class "ui primary button" + , href "#" + , onClick Submit + , disabled model.submitting + ] + [ if model.submitting then + i [ class "ui spinner loading icon" ] [] + + else + text "Submit" + ] + , button + [ class "ui button" + , href "#" + , onClick Cancel + ] + [ text "Cancel" + ] + , div + [ classList + [ ( "ui message", True ) + , ( "error", Maybe.map .success model.result == Just False ) + , ( "success", Maybe.map .success model.result == Just True ) + , ( "invisible hidden", model.result == Nothing ) + ] + ] + [ Maybe.map .message model.result + |> Maybe.withDefault "" + |> text + ] + ] + + +viewModal : UiSettings -> Maybe Model -> Html Msg +viewModal settings mm = + let + hidden = + mm == Nothing + + heading = + fold (\_ -> "Add Tag") + (\_ -> "Add Person") + (\_ -> "Add Organization") + (\_ -> "Add Equipment") + + headIcon = + fold (\_ -> Icons.tagIcon) + (\_ -> Icons.personIcon) + (\_ -> Icons.organizationIcon) + (\_ -> Icons.equipmentIcon) + in + div + [ classList + [ ( "ui inverted modals page dimmer", True ) + , ( "invisibe hidden", hidden ) + , ( "active", not hidden ) + ] + , style "display" "flex !important" + ] + [ div [ class "ui modal active" ] + [ div [ class "header" ] + [ Maybe.map .form mm + |> Maybe.map headIcon + |> Maybe.withDefault (i [] []) + , Maybe.map .form mm + |> Maybe.map heading + |> Maybe.withDefault "" + |> text + ] + , div [ class "content" ] + [ case mm of + Just model -> + view settings model + + Nothing -> + span [] [] + ] + ] + ] diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail.elm b/modules/webapp/src/main/elm/Comp/ItemDetail.elm index 26fd79de..be3b214c 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail.elm @@ -25,6 +25,7 @@ import Api.Model.TagList exposing (TagList) import Browser.Navigation as Nav import Comp.AttachmentMeta import Comp.DatePicker +import Comp.DetailEdit import Comp.Dropdown exposing (isDropdownChangeMsg) import Comp.Dropzone import Comp.ItemMail @@ -93,6 +94,7 @@ type alias Model = , errored : Set String , loading : Set String , attachDD : DD.Model String String + , modalEdit : Maybe Comp.DetailEdit.Model } @@ -179,6 +181,7 @@ emptyModel = , errored = Set.empty , loading = Set.empty , attachDD = DD.init + , modalEdit = Nothing } @@ -242,10 +245,13 @@ type Msg | AddFilesProgress String Http.Progress | AddFilesReset | AttachDDMsg (DD.Msg String String) + | ModalEditMsg Comp.DetailEdit.Msg + | StartTagModal + | CloseModal --- update +--- Update getOptions : Flags -> Cmd Msg @@ -511,6 +517,7 @@ update key flags next msg model = , itemDate = item.itemDate , dueDate = item.dueDate , visibleAttach = 0 + , modalEdit = Nothing } , Cmd.batch [ c1 @@ -1202,9 +1209,43 @@ update key flags next msg model = in noSub ( { model | attachDD = model_ }, cmd ) + ModalEditMsg lm -> + case model.modalEdit of + Just mm -> + let + ( mm_, mc_, mv ) = + Comp.DetailEdit.update flags lm mm + + ( model_, cmd_ ) = + case mv of + Just Comp.DetailEdit.CancelForm -> + ( { model | modalEdit = Nothing }, Cmd.none ) + + Just _ -> + ( model, Api.itemDetail flags model.item.id GetItemResp ) + + Nothing -> + ( { model | modalEdit = Just mm_ }, Cmd.none ) + in + noSub ( model_, Cmd.batch [ cmd_, Cmd.map ModalEditMsg mc_ ] ) + + Nothing -> + noSub ( model, Cmd.none ) + + StartTagModal -> + noSub + ( { model + | modalEdit = Just (Comp.DetailEdit.initTagByName model.item.id "") + } + , Cmd.none + ) + + CloseModal -> + noSub ( { model | modalEdit = Nothing }, Cmd.none ) --- view + +--- View actionInputDatePicker : DatePicker.Settings @@ -1219,7 +1260,8 @@ actionInputDatePicker = view : { prev : Maybe String, next : Maybe String } -> UiSettings -> Model -> Html Msg view inav settings model = div [] - [ renderItemInfo settings model + [ Html.map ModalEditMsg (Comp.DetailEdit.viewModal settings model.modalEdit) + , renderItemInfo settings model , div [ classList [ ( "ui ablue-comp menu", True ) @@ -1822,6 +1864,15 @@ renderEditForm settings model = [ label [] [ Icons.tagsIcon , text "Tags" + , span [ class "right-float" ] + [ a + [ class "icon link" + , href "#" + , onClick StartTagModal + ] + [ i [ class "add link icon" ] [] + ] + ] ] , Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel) ] From c6accca0ff41da4177245a6ea0dc272dfca4407d Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Thu, 11 Jun 2020 22:04:08 +0200 Subject: [PATCH 06/10] Add route to create and associate correspondent org --- .../scala/docspell/backend/ops/OItem.scala | 23 +++++++++++++++++++ .../src/main/resources/docspell-openapi.yml | 22 ++++++++++++++++++ .../restserver/routes/ItemRoutes.scala | 8 +++++++ 3 files changed, 53 insertions(+) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala index 6b1ecd98..5310eda4 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala @@ -53,6 +53,8 @@ trait OItem[F[_]] { def setCorrOrg(item: Ident, org: Option[Ident], collective: Ident): F[AddResult] + def addCorrOrg(item: Ident, org: OOrganization.OrgAndContacts): F[AddResult] + def setCorrPerson(item: Ident, person: Option[Ident], collective: Ident): F[AddResult] def setConcPerson(item: Ident, person: Option[Ident], collective: Ident): F[AddResult] @@ -138,6 +140,7 @@ object OItem { def apply[F[_]: Effect](store: Store[F]): Resource[F, OItem[F]] = for { otag <- OTag(store) + oorg <- OOrganization(store) oitem <- Resource.pure[F, OItem[F]](new OItem[F] { def moveAttachmentBefore( itemId: Ident, @@ -282,6 +285,26 @@ object OItem { .attempt .map(AddResult.fromUpdate) + def addCorrOrg(item: Ident, org: OOrganization.OrgAndContacts): F[AddResult] = + (for { + _ <- OptionT(store.transact(RItem.getCollective(item))) + .filter(_ == org.org.cid) + addres <- OptionT.liftF(oorg.addOrg(org)) + _ <- addres match { + case AddResult.Success => + OptionT.liftF( + store.transact( + RItem.updateCorrOrg(item, org.org.cid, Some(org.org.oid)) + ) + ) + case AddResult.EntityExists(_) => + OptionT.pure[F](0) + case AddResult.Failure(_) => + OptionT.pure[F](0) + } + } yield addres) + .getOrElse(AddResult.Failure(new Exception("Collective mismatch"))) + def setCorrPerson( item: Ident, person: Option[Ident], diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index cb32158e..41adcebd 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1150,6 +1150,28 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" + post: + tags: [ Item ] + summary: Set a new correspondent organization of an item. + description: | + Create a new organization and update the correspondent + organization of an item. + security: + - authTokenHeader: [] + parameters: + - $ref: "#/components/parameters/id" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Organization" + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" /sec/item/{id}/corrPerson: put: tags: [ Item ] diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala index 60a7e3a7..f9e73790 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala @@ -100,6 +100,14 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Correspondent organization updated")) } yield resp + case req @ POST -> Root / Ident(id) / "corrOrg" => + for { + data <- req.as[Organization] + org <- Conversions.newOrg(data, user.account.collective) + res <- backend.item.addCorrOrg(id, org) + resp <- Ok(Conversions.basicResult(res, "Correspondent organization updated")) + } yield resp + case req @ PUT -> Root / Ident(id) / "corrPerson" => for { idref <- req.as[OptionalId] From a4d60c0d92dea11ab5891dba3413821175cd1066 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Thu, 11 Jun 2020 22:10:48 +0200 Subject: [PATCH 07/10] Add correspondent organizations in item edit view --- modules/webapp/src/main/elm/Api.elm | 13 ++++++++++++- modules/webapp/src/main/elm/Comp/DetailEdit.elm | 14 ++++++++++++++ modules/webapp/src/main/elm/Comp/ItemDetail.elm | 17 +++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 8ad6ec93..138466d5 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -1,5 +1,6 @@ module Api exposing - ( addTag + ( addCorrOrg + , addTag , cancelJob , changePassword , checkCalEvent @@ -1086,6 +1087,16 @@ setCorrOrg flags item id receive = } +addCorrOrg : Flags -> String -> Organization -> (Result Http.Error BasicResult -> msg) -> Cmd msg +addCorrOrg flags item org receive = + Http2.authPost + { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/corrOrg" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.Organization.encode org) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + setCorrPerson : Flags -> String -> OptionalId -> (Result Http.Error BasicResult -> msg) -> Cmd msg setCorrPerson flags item id receive = Http2.authPut diff --git a/modules/webapp/src/main/elm/Comp/DetailEdit.elm b/modules/webapp/src/main/elm/Comp/DetailEdit.elm index 225f6af5..d48ae157 100644 --- a/modules/webapp/src/main/elm/Comp/DetailEdit.elm +++ b/modules/webapp/src/main/elm/Comp/DetailEdit.elm @@ -196,6 +196,20 @@ update flags msg model = else ( model, Cmd.none, Nothing ) + OM om -> + let + org = + Comp.OrgForm.getOrg om + in + if Comp.OrgForm.isValid om then + ( { model | submitting = True } + , Api.addCorrOrg flags model.itemId org SubmitResp + , Nothing + ) + + else + ( model, Cmd.none, Nothing ) + _ -> Debug.todo "implement" diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail.elm b/modules/webapp/src/main/elm/Comp/ItemDetail.elm index be3b214c..d44728a3 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail.elm @@ -30,6 +30,7 @@ import Comp.Dropdown exposing (isDropdownChangeMsg) import Comp.Dropzone import Comp.ItemMail import Comp.MarkdownInput +import Comp.OrgForm import Comp.SentMails import Comp.YesNoDimmer import Data.Direction exposing (Direction) @@ -247,6 +248,7 @@ type Msg | AttachDDMsg (DD.Msg String String) | ModalEditMsg Comp.DetailEdit.Msg | StartTagModal + | StartCorrOrgModal | CloseModal @@ -1240,6 +1242,14 @@ update key flags next msg model = , Cmd.none ) + StartCorrOrgModal -> + noSub + ( { model + | modalEdit = Just (Comp.DetailEdit.initOrg model.item.id Comp.OrgForm.emptyModel) + } + , Cmd.none + ) + CloseModal -> noSub ( { model | modalEdit = Nothing }, Cmd.none ) @@ -1938,6 +1948,13 @@ renderEditForm settings model = [ label [] [ Icons.organizationIcon , text "Organization" + , a + [ class "right-float" + , href "#" + , onClick StartCorrOrgModal + ] + [ i [ class "add link icon" ] [] + ] ] , Html.map OrgDropdownMsg (Comp.Dropdown.view settings model.corrOrgModel) , renderOrgSuggestions model From 363eb81aff5d475c8669318404558dee0c16232f Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Thu, 11 Jun 2020 22:28:31 +0200 Subject: [PATCH 08/10] Add remaining routes to create and update item meta data --- .../scala/docspell/backend/ops/OItem.scala | 79 ++++++++++++++++++- .../src/main/resources/docspell-openapi.yml | 66 ++++++++++++++++ .../restserver/routes/ItemRoutes.scala | 24 ++++++ 3 files changed, 167 insertions(+), 2 deletions(-) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala index 5310eda4..607dc3a3 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala @@ -57,10 +57,16 @@ trait OItem[F[_]] { def setCorrPerson(item: Ident, person: Option[Ident], collective: Ident): F[AddResult] + def addCorrPerson(item: Ident, person: OOrganization.PersonAndContacts): F[AddResult] + def setConcPerson(item: Ident, person: Option[Ident], collective: Ident): F[AddResult] + def addConcPerson(item: Ident, person: OOrganization.PersonAndContacts): F[AddResult] + def setConcEquip(item: Ident, equip: Option[Ident], collective: Ident): F[AddResult] + def addConcEquip(item: Ident, equip: REquipment): F[AddResult] + def setNotes(item: Ident, notes: Option[String], collective: Ident): F[AddResult] def setName(item: Ident, notes: String, collective: Ident): F[AddResult] @@ -139,8 +145,9 @@ object OItem { def apply[F[_]: Effect](store: Store[F]): Resource[F, OItem[F]] = for { - otag <- OTag(store) - oorg <- OOrganization(store) + otag <- OTag(store) + oorg <- OOrganization(store) + oequip <- OEquipment(store) oitem <- Resource.pure[F, OItem[F]](new OItem[F] { def moveAttachmentBefore( itemId: Ident, @@ -315,6 +322,30 @@ object OItem { .attempt .map(AddResult.fromUpdate) + def addCorrPerson( + item: Ident, + person: OOrganization.PersonAndContacts + ): F[AddResult] = + (for { + _ <- OptionT(store.transact(RItem.getCollective(item))) + .filter(_ == person.person.cid) + addres <- OptionT.liftF(oorg.addPerson(person)) + _ <- addres match { + case AddResult.Success => + OptionT.liftF( + store.transact( + RItem + .updateCorrPerson(item, person.person.cid, Some(person.person.pid)) + ) + ) + case AddResult.EntityExists(_) => + OptionT.pure[F](0) + case AddResult.Failure(_) => + OptionT.pure[F](0) + } + } yield addres) + .getOrElse(AddResult.Failure(new Exception("Collective mismatch"))) + def setConcPerson( item: Ident, person: Option[Ident], @@ -325,6 +356,30 @@ object OItem { .attempt .map(AddResult.fromUpdate) + def addConcPerson( + item: Ident, + person: OOrganization.PersonAndContacts + ): F[AddResult] = + (for { + _ <- OptionT(store.transact(RItem.getCollective(item))) + .filter(_ == person.person.cid) + addres <- OptionT.liftF(oorg.addPerson(person)) + _ <- addres match { + case AddResult.Success => + OptionT.liftF( + store.transact( + RItem + .updateConcPerson(item, person.person.cid, Some(person.person.pid)) + ) + ) + case AddResult.EntityExists(_) => + OptionT.pure[F](0) + case AddResult.Failure(_) => + OptionT.pure[F](0) + } + } yield addres) + .getOrElse(AddResult.Failure(new Exception("Collective mismatch"))) + def setConcEquip( item: Ident, equip: Option[Ident], @@ -335,6 +390,26 @@ object OItem { .attempt .map(AddResult.fromUpdate) + def addConcEquip(item: Ident, equip: REquipment): F[AddResult] = + (for { + _ <- OptionT(store.transact(RItem.getCollective(item))) + .filter(_ == equip.cid) + addres <- OptionT.liftF(oequip.add(equip)) + _ <- addres match { + case AddResult.Success => + OptionT.liftF( + store.transact( + RItem.updateConcEquip(item, equip.cid, Some(equip.eid)) + ) + ) + case AddResult.EntityExists(_) => + OptionT.pure[F](0) + case AddResult.Failure(_) => + OptionT.pure[F](0) + } + } yield addres) + .getOrElse(AddResult.Failure(new Exception("Collective mismatch"))) + def setNotes( item: Ident, notes: Option[String], diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 41adcebd..4c3fb7f0 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1194,6 +1194,28 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" + post: + tags: [ Item ] + summary: Create and set the correspondent person of an item. + description: | + Creates a new person and updates the correspondent person of + an item. + security: + - authTokenHeader: [] + parameters: + - $ref: "#/components/parameters/id" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Person" + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" /sec/item/{id}/concPerson: put: tags: [ Item ] @@ -1216,6 +1238,28 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" + post: + tags: [ Item ] + summary: Create and set the concerning person of an item. + description: | + Creates a new person and updates the concerning person of an + item. + security: + - authTokenHeader: [] + parameters: + - $ref: "#/components/parameters/id" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Person" + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" /sec/item/{id}/concEquipment: put: tags: [ Item ] @@ -1238,6 +1282,28 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" + post: + tags: [ Item ] + summary: Create and set a new the concering equipment of an item. + description: | + Creates a new equipment and sets it as the concering equipment + of an item. + security: + - authTokenHeader: [] + parameters: + - $ref: "#/components/parameters/id" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Equipment" + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" /sec/item/{id}/notes: put: tags: [ Item ] diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala index f9e73790..23c5e51f 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala @@ -115,6 +115,14 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Correspondent person updated")) } yield resp + case req @ POST -> Root / Ident(id) / "corrPerson" => + for { + data <- req.as[Person] + pers <- Conversions.newPerson(data, user.account.collective) + res <- backend.item.addCorrPerson(id, pers) + resp <- Ok(Conversions.basicResult(res, "Correspondent person updated")) + } yield resp + case req @ PUT -> Root / Ident(id) / "concPerson" => for { idref <- req.as[OptionalId] @@ -122,6 +130,14 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Concerned person updated")) } yield resp + case req @ POST -> Root / Ident(id) / "concPerson" => + for { + data <- req.as[Person] + pers <- Conversions.newPerson(data, user.account.collective) + res <- backend.item.addConcPerson(id, pers) + resp <- Ok(Conversions.basicResult(res, "Concerned person updated")) + } yield resp + case req @ PUT -> Root / Ident(id) / "concEquipment" => for { idref <- req.as[OptionalId] @@ -129,6 +145,14 @@ object ItemRoutes { resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated")) } yield resp + case req @ POST -> Root / Ident(id) / "concEquipment" => + for { + data <- req.as[Equipment] + equip <- Conversions.newEquipment(data, user.account.collective) + res <- backend.item.addConcEquip(id, equip) + resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated")) + } yield resp + case req @ PUT -> Root / Ident(id) / "notes" => for { text <- req.as[OptionalText] From 936177a910e2884a84d169e6dee62f5fc7f2cd22 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Thu, 11 Jun 2020 22:52:30 +0200 Subject: [PATCH 09/10] Allow to add remaining metadata in item edit view --- modules/webapp/src/main/elm/Api.elm | 35 +++++++- .../webapp/src/main/elm/Comp/DetailEdit.elm | 90 ++++++++++++++++--- .../webapp/src/main/elm/Comp/ItemDetail.elm | 86 ++++++++++++++++-- 3 files changed, 188 insertions(+), 23 deletions(-) diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 138466d5..284df71b 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -1,5 +1,8 @@ module Api exposing - ( addCorrOrg + ( addConcEquip + , addConcPerson + , addCorrOrg + , addCorrPerson , addTag , cancelJob , changePassword @@ -1107,6 +1110,16 @@ setCorrPerson flags item id receive = } +addCorrPerson : Flags -> String -> Person -> (Result Http.Error BasicResult -> msg) -> Cmd msg +addCorrPerson flags item person receive = + Http2.authPost + { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/corrPerson" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.Person.encode person) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + setConcPerson : Flags -> String -> OptionalId -> (Result Http.Error BasicResult -> msg) -> Cmd msg setConcPerson flags item id receive = Http2.authPut @@ -1117,6 +1130,16 @@ setConcPerson flags item id receive = } +addConcPerson : Flags -> String -> Person -> (Result Http.Error BasicResult -> msg) -> Cmd msg +addConcPerson flags item person receive = + Http2.authPost + { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/concPerson" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.Person.encode person) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + setConcEquip : Flags -> String -> OptionalId -> (Result Http.Error BasicResult -> msg) -> Cmd msg setConcEquip flags item id receive = Http2.authPut @@ -1127,6 +1150,16 @@ setConcEquip flags item id receive = } +addConcEquip : Flags -> String -> Equipment -> (Result Http.Error BasicResult -> msg) -> Cmd msg +addConcEquip flags item equip receive = + Http2.authPost + { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/concEquipment" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.Equipment.encode equip) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + setItemName : Flags -> String -> OptionalText -> (Result Http.Error BasicResult -> msg) -> Cmd msg setItemName flags item text receive = Http2.authPut diff --git a/modules/webapp/src/main/elm/Comp/DetailEdit.elm b/modules/webapp/src/main/elm/Comp/DetailEdit.elm index d48ae157..7eae2160 100644 --- a/modules/webapp/src/main/elm/Comp/DetailEdit.elm +++ b/modules/webapp/src/main/elm/Comp/DetailEdit.elm @@ -2,10 +2,10 @@ module Comp.DetailEdit exposing ( Model , Msg , Value(..) - , fold + , initConcPerson + , initCorrPerson , initEquip , initOrg - , initPerson , initTag , initTagByName , update @@ -50,7 +50,8 @@ type alias Model = type FormModel = TM Comp.TagForm.Model - | PM Comp.PersonForm.Model + | PMR Comp.PersonForm.Model + | PMC Comp.PersonForm.Model | OM Comp.OrgForm.Model | EM Comp.EquipmentForm.Model @@ -67,7 +68,10 @@ fold ft fp fo fe model = TM tm -> ft tm - PM pm -> + PMR pm -> + fp pm + + PMC pm -> fp pm OM om -> @@ -96,9 +100,14 @@ initOrg itemId om = init itemId (OM om) -initPerson : String -> Comp.PersonForm.Model -> Model -initPerson itemId pm = - init itemId (PM pm) +initCorrPerson : String -> Comp.PersonForm.Model -> Model +initCorrPerson itemId pm = + init itemId (PMR pm) + + +initConcPerson : String -> Comp.PersonForm.Model -> Model +initConcPerson itemId pm = + init itemId (PMC pm) initTag : String -> Comp.TagForm.Model -> Model @@ -142,7 +151,10 @@ makeValue fm = TM tm -> SubmitTag (Comp.TagForm.getTag tm) - PM pm -> + PMR pm -> + SubmitPerson (Comp.PersonForm.getPerson pm) + + PMC pm -> SubmitPerson (Comp.PersonForm.getPerson pm) OM om -> @@ -210,8 +222,47 @@ update flags msg model = else ( model, Cmd.none, Nothing ) - _ -> - Debug.todo "implement" + PMC pm -> + let + pers = + Comp.PersonForm.getPerson pm + in + if Comp.PersonForm.isValid pm then + ( { model | submitting = True } + , Api.addConcPerson flags model.itemId pers SubmitResp + , Nothing + ) + + else + ( model, Cmd.none, Nothing ) + + PMR pm -> + let + pers = + Comp.PersonForm.getPerson pm + in + if Comp.PersonForm.isValid pm then + ( { model | submitting = True } + , Api.addCorrPerson flags model.itemId pers SubmitResp + , Nothing + ) + + else + ( model, Cmd.none, Nothing ) + + EM em -> + let + equip = + Comp.EquipmentForm.getEquipment em + in + if Comp.EquipmentForm.isValid em then + ( { model | submitting = True } + , Api.addConcEquip flags model.itemId equip SubmitResp + , Nothing + ) + + else + ( model, Cmd.none, Nothing ) TagMsg lm -> case model.form of @@ -230,12 +281,22 @@ update flags msg model = PersonMsg lm -> case model.form of - PM pm -> + PMR pm -> let ( pm_, pc_ ) = Comp.PersonForm.update flags lm pm in - ( { model | form = PM pm_ } + ( { model | form = PMR pm_ } + , Cmd.map PersonMsg pc_ + , Nothing + ) + + PMC pm -> + let + ( pm_, pc_ ) = + Comp.PersonForm.update flags lm pm + in + ( { model | form = PMC pm_ } , Cmd.map PersonMsg pc_ , Nothing ) @@ -285,7 +346,10 @@ view settings model = TM tm -> Html.map TagMsg (Comp.TagForm.view tm) - PM pm -> + PMR pm -> + Html.map PersonMsg (Comp.PersonForm.view settings pm) + + PMC pm -> Html.map PersonMsg (Comp.PersonForm.view settings pm) OM om -> diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail.elm b/modules/webapp/src/main/elm/Comp/ItemDetail.elm index d44728a3..1065f085 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail.elm @@ -28,9 +28,11 @@ import Comp.DatePicker import Comp.DetailEdit import Comp.Dropdown exposing (isDropdownChangeMsg) import Comp.Dropzone +import Comp.EquipmentForm import Comp.ItemMail import Comp.MarkdownInput import Comp.OrgForm +import Comp.PersonForm import Comp.SentMails import Comp.YesNoDimmer import Data.Direction exposing (Direction) @@ -249,6 +251,9 @@ type Msg | ModalEditMsg Comp.DetailEdit.Msg | StartTagModal | StartCorrOrgModal + | StartCorrPersonModal + | StartConcPersonModal + | StartEquipModal | CloseModal @@ -1245,7 +1250,51 @@ update key flags next msg model = StartCorrOrgModal -> noSub ( { model - | modalEdit = Just (Comp.DetailEdit.initOrg model.item.id Comp.OrgForm.emptyModel) + | modalEdit = + Just + (Comp.DetailEdit.initOrg + model.item.id + Comp.OrgForm.emptyModel + ) + } + , Cmd.none + ) + + StartCorrPersonModal -> + noSub + ( { model + | modalEdit = + Just + (Comp.DetailEdit.initCorrPerson + model.item.id + Comp.PersonForm.emptyModel + ) + } + , Cmd.none + ) + + StartConcPersonModal -> + noSub + ( { model + | modalEdit = + Just + (Comp.DetailEdit.initConcPerson + model.item.id + Comp.PersonForm.emptyModel + ) + } + , Cmd.none + ) + + StartEquipModal -> + noSub + ( { model + | modalEdit = + Just + (Comp.DetailEdit.initEquip + model.item.id + Comp.EquipmentForm.emptyModel + ) } , Cmd.none ) @@ -1874,14 +1923,12 @@ renderEditForm settings model = [ label [] [ Icons.tagsIcon , text "Tags" - , span [ class "right-float" ] - [ a - [ class "icon link" - , href "#" - , onClick StartTagModal - ] - [ i [ class "add link icon" ] [] - ] + , a + [ class "right-float" + , href "#" + , onClick StartTagModal + ] + [ i [ class "add link icon" ] [] ] ] , Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel) @@ -1963,6 +2010,13 @@ renderEditForm settings model = [ label [] [ Icons.personIcon , text "Person" + , a + [ class "right-float" + , href "#" + , onClick StartCorrPersonModal + ] + [ i [ class "add link icon" ] [] + ] ] , Html.map CorrPersonMsg (Comp.Dropdown.view settings model.corrPersonModel) , renderCorrPersonSuggestions model @@ -1975,6 +2029,13 @@ renderEditForm settings model = [ label [] [ Icons.personIcon , text "Person" + , a + [ class "right-float" + , href "#" + , onClick StartConcPersonModal + ] + [ i [ class "add link icon" ] [] + ] ] , Html.map ConcPersonMsg (Comp.Dropdown.view settings model.concPersonModel) , renderConcPersonSuggestions model @@ -1983,6 +2044,13 @@ renderEditForm settings model = [ label [] [ Icons.equipmentIcon , text "Equipment" + , a + [ class "right-float" + , href "#" + , onClick StartEquipModal + ] + [ i [ class "add link icon" ] [] + ] ] , Html.map ConcEquipMsg (Comp.Dropdown.view settings model.concEquipModel) , renderConcEquipSuggestions model From 9658b2780b7a322fbd2af454a2988ae41e2d06c9 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Fri, 12 Jun 2020 00:17:26 +0200 Subject: [PATCH 10/10] Prettify modal dialogs a bit --- .../webapp/src/main/elm/Comp/DetailEdit.elm | 191 ++++++++++++------ .../webapp/src/main/elm/Comp/ItemCardList.elm | 2 +- .../webapp/src/main/elm/Comp/ItemDetail.elm | 69 +++---- modules/webapp/src/main/elm/Data/Icons.elm | 48 ++--- .../src/main/elm/Page/ManageData/View.elm | 16 +- 5 files changed, 182 insertions(+), 144 deletions(-) diff --git a/modules/webapp/src/main/elm/Comp/DetailEdit.elm b/modules/webapp/src/main/elm/Comp/DetailEdit.elm index 7eae2160..2e4f9f44 100644 --- a/modules/webapp/src/main/elm/Comp/DetailEdit.elm +++ b/modules/webapp/src/main/elm/Comp/DetailEdit.elm @@ -193,6 +193,10 @@ update flags msg model = ) Submit -> + let + failMsg = + Just (BasicResult False "Please fill required fields.") + in case model.form of TM tm -> let @@ -206,7 +210,10 @@ update flags msg model = ) else - ( model, Cmd.none, Nothing ) + ( { model | result = failMsg } + , Cmd.none + , Nothing + ) OM om -> let @@ -220,7 +227,10 @@ update flags msg model = ) else - ( model, Cmd.none, Nothing ) + ( { model | result = failMsg } + , Cmd.none + , Nothing + ) PMC pm -> let @@ -234,7 +244,10 @@ update flags msg model = ) else - ( model, Cmd.none, Nothing ) + ( { model | result = failMsg } + , Cmd.none + , Nothing + ) PMR pm -> let @@ -248,7 +261,10 @@ update flags msg model = ) else - ( model, Cmd.none, Nothing ) + ( { model | result = failMsg } + , Cmd.none + , Nothing + ) EM em -> let @@ -262,7 +278,10 @@ update flags msg model = ) else - ( model, Cmd.none, Nothing ) + ( { model | result = failMsg } + , Cmd.none + , Nothing + ) TagMsg lm -> case model.form of @@ -271,7 +290,10 @@ update flags msg model = ( tm_, tc_ ) = Comp.TagForm.update flags lm tm in - ( { model | form = TM tm_ } + ( { model + | form = TM tm_ + , result = Nothing + } , Cmd.map TagMsg tc_ , Nothing ) @@ -286,7 +308,10 @@ update flags msg model = ( pm_, pc_ ) = Comp.PersonForm.update flags lm pm in - ( { model | form = PMR pm_ } + ( { model + | form = PMR pm_ + , result = Nothing + } , Cmd.map PersonMsg pc_ , Nothing ) @@ -296,7 +321,10 @@ update flags msg model = ( pm_, pc_ ) = Comp.PersonForm.update flags lm pm in - ( { model | form = PMC pm_ } + ( { model + | form = PMC pm_ + , result = Nothing + } , Cmd.map PersonMsg pc_ , Nothing ) @@ -311,7 +339,10 @@ update flags msg model = ( om_, oc_ ) = Comp.OrgForm.update flags lm om in - ( { model | form = OM om_ } + ( { model + | form = OM om_ + , result = Nothing + } , Cmd.map OrgMsg oc_ , Nothing ) @@ -326,7 +357,10 @@ update flags msg model = ( em_, ec_ ) = Comp.EquipmentForm.update flags lm em in - ( { model | form = EM em_ } + ( { model + | form = EM em_ + , result = Nothing + } , Cmd.map EquipMsg ec_ , Nothing ) @@ -339,57 +373,72 @@ update flags msg model = --- View +viewButtons : Model -> List (Html Msg) +viewButtons model = + [ button + [ class "ui primary button" + , href "#" + , onClick Submit + , disabled model.submitting + ] + [ if model.submitting then + i [ class "ui spinner loading icon" ] [] + + else + text "Submit" + ] + , button + [ class "ui button" + , href "#" + , onClick Cancel + ] + [ text "Cancel" + ] + ] + + +viewIntern : UiSettings -> Bool -> Model -> List (Html Msg) +viewIntern settings withButtons model = + [ div + [ classList + [ ( "ui message", True ) + , ( "error", Maybe.map .success model.result == Just False ) + , ( "success", Maybe.map .success model.result == Just True ) + , ( "invisible hidden", model.result == Nothing ) + ] + ] + [ Maybe.map .message model.result + |> Maybe.withDefault "" + |> text + ] + , case model.form of + TM tm -> + Html.map TagMsg (Comp.TagForm.view tm) + + PMR pm -> + Html.map PersonMsg (Comp.PersonForm.view settings pm) + + PMC pm -> + Html.map PersonMsg (Comp.PersonForm.view settings pm) + + OM om -> + Html.map OrgMsg (Comp.OrgForm.view settings om) + + EM em -> + Html.map EquipMsg (Comp.EquipmentForm.view em) + ] + ++ (if withButtons then + div [ class "ui divider" ] [] :: viewButtons model + + else + [] + ) + + view : UiSettings -> Model -> Html Msg view settings model = div [] - [ case model.form of - TM tm -> - Html.map TagMsg (Comp.TagForm.view tm) - - PMR pm -> - Html.map PersonMsg (Comp.PersonForm.view settings pm) - - PMC pm -> - Html.map PersonMsg (Comp.PersonForm.view settings pm) - - OM om -> - Html.map OrgMsg (Comp.OrgForm.view settings om) - - EM em -> - Html.map EquipMsg (Comp.EquipmentForm.view em) - , div [ class "ui divider" ] [] - , button - [ class "ui primary button" - , href "#" - , onClick Submit - , disabled model.submitting - ] - [ if model.submitting then - i [ class "ui spinner loading icon" ] [] - - else - text "Submit" - ] - , button - [ class "ui button" - , href "#" - , onClick Cancel - ] - [ text "Cancel" - ] - , div - [ classList - [ ( "ui message", True ) - , ( "error", Maybe.map .success model.result == Just False ) - , ( "success", Maybe.map .success model.result == Just True ) - , ( "invisible hidden", model.result == Nothing ) - ] - ] - [ Maybe.map .message model.result - |> Maybe.withDefault "" - |> text - ] - ] + (viewIntern settings True model) viewModal : UiSettings -> Maybe Model -> Html Msg @@ -405,10 +454,10 @@ viewModal settings mm = (\_ -> "Add Equipment") headIcon = - fold (\_ -> Icons.tagIcon) - (\_ -> Icons.personIcon) - (\_ -> Icons.organizationIcon) - (\_ -> Icons.equipmentIcon) + fold (\_ -> Icons.tagIcon "") + (\_ -> Icons.personIcon "") + (\_ -> Icons.organizationIcon "") + (\_ -> Icons.equipmentIcon "") in div [ classList @@ -428,13 +477,21 @@ viewModal settings mm = |> Maybe.withDefault "" |> text ] - , div [ class "content" ] - [ case mm of + , div [ class "scrolling content" ] + (case mm of Just model -> - view settings model + viewIntern settings False model Nothing -> - span [] [] - ] + [] + ) + , div [ class "actions" ] + (case mm of + Just model -> + viewButtons model + + Nothing -> + [] + ) ] ] diff --git a/modules/webapp/src/main/elm/Comp/ItemCardList.elm b/modules/webapp/src/main/elm/Comp/ItemCardList.elm index 30236bd0..de2b36ae 100644 --- a/modules/webapp/src/main/elm/Comp/ItemCardList.elm +++ b/modules/webapp/src/main/elm/Comp/ItemCardList.elm @@ -240,7 +240,7 @@ viewItem settings item = [ div [ class "ui basic grey label" ] - [ Icons.dueDateIcon + [ Icons.dueDateIcon "" , text (" " ++ dueDate) ] ] diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail.elm b/modules/webapp/src/main/elm/Comp/ItemDetail.elm index 1065f085..9a65d7af 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail.elm @@ -1775,7 +1775,7 @@ renderItemInfo settings model = [ class "item" , title "Due Date" ] - [ Icons.dueDateIcon + [ Icons.dueDateIcon "grey" , Maybe.map Util.Time.formatDate model.item.dueDate |> Maybe.withDefault "" |> text @@ -1917,19 +1917,24 @@ renderEditButtons model = renderEditForm : UiSettings -> Model -> Html Msg renderEditForm settings model = + let + addIconLink tip m = + a + [ class "right-float" + , href "#" + , title tip + , onClick m + ] + [ i [ class "grey plus link icon" ] [] + ] + in div [ class "ui attached segment" ] [ div [ class "ui form" ] [ div [ class "field" ] [ label [] - [ Icons.tagsIcon + [ Icons.tagsIcon "grey" , text "Tags" - , a - [ class "right-float" - , href "#" - , onClick StartTagModal - ] - [ i [ class "add link icon" ] [] - ] + , addIconLink "Add new tag" StartTagModal ] , Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel) ] @@ -1947,14 +1952,14 @@ renderEditForm settings model = ] , div [ class "field" ] [ label [] - [ Icons.directionIcon + [ Icons.directionIcon "grey" , text "Direction" ] , Html.map DirDropdownMsg (Comp.Dropdown.view settings model.directionModel) ] , div [ class " field" ] [ label [] - [ Icons.dateIcon + [ Icons.dateIcon "grey" , text "Date" ] , div [ class "ui action input" ] @@ -1972,7 +1977,7 @@ renderEditForm settings model = ] , div [ class " field" ] [ label [] - [ Icons.dueDateIcon + [ Icons.dueDateIcon "grey" , text "Due Date" ] , div [ class "ui action input" ] @@ -1993,30 +1998,18 @@ renderEditForm settings model = ] , div [ class "field" ] [ label [] - [ Icons.organizationIcon + [ Icons.organizationIcon "grey" , text "Organization" - , a - [ class "right-float" - , href "#" - , onClick StartCorrOrgModal - ] - [ i [ class "add link icon" ] [] - ] + , addIconLink "Add new organization" StartCorrOrgModal ] , Html.map OrgDropdownMsg (Comp.Dropdown.view settings model.corrOrgModel) , renderOrgSuggestions model ] , div [ class "field" ] [ label [] - [ Icons.personIcon + [ Icons.personIcon "grey" , text "Person" - , a - [ class "right-float" - , href "#" - , onClick StartCorrPersonModal - ] - [ i [ class "add link icon" ] [] - ] + , addIconLink "Add new correspondent person" StartCorrPersonModal ] , Html.map CorrPersonMsg (Comp.Dropdown.view settings model.corrPersonModel) , renderCorrPersonSuggestions model @@ -2027,30 +2020,18 @@ renderEditForm settings model = ] , div [ class "field" ] [ label [] - [ Icons.personIcon + [ Icons.personIcon "grey" , text "Person" - , a - [ class "right-float" - , href "#" - , onClick StartConcPersonModal - ] - [ i [ class "add link icon" ] [] - ] + , addIconLink "Add new concerning person" StartConcPersonModal ] , Html.map ConcPersonMsg (Comp.Dropdown.view settings model.concPersonModel) , renderConcPersonSuggestions model ] , div [ class "field" ] [ label [] - [ Icons.equipmentIcon + [ Icons.equipmentIcon "grey" , text "Equipment" - , a - [ class "right-float" - , href "#" - , onClick StartEquipModal - ] - [ i [ class "add link icon" ] [] - ] + , addIconLink "Add new equipment" StartEquipModal ] , Html.map ConcEquipMsg (Comp.Dropdown.view settings model.concEquipModel) , renderConcEquipSuggestions model diff --git a/modules/webapp/src/main/elm/Data/Icons.elm b/modules/webapp/src/main/elm/Data/Icons.elm index bade841f..a52f0c4b 100644 --- a/modules/webapp/src/main/elm/Data/Icons.elm +++ b/modules/webapp/src/main/elm/Data/Icons.elm @@ -54,9 +54,9 @@ date = "calendar outline icon" -dateIcon : Html msg -dateIcon = - i [ class date ] [] +dateIcon : String -> Html msg +dateIcon classes = + i [ class (date ++ " " ++ classes) ] [] dueDate : String @@ -64,9 +64,9 @@ dueDate = "bell icon" -dueDateIcon : Html msg -dueDateIcon = - i [ class dueDate ] [] +dueDateIcon : String -> Html msg +dueDateIcon classes = + i [ class (dueDate ++ " " ++ classes) ] [] editNotes : String @@ -94,9 +94,9 @@ tag = "tag icon" -tagIcon : Html msg -tagIcon = - i [ class tag ] [] +tagIcon : String -> Html msg +tagIcon classes = + i [ class (tag ++ " " ++ classes) ] [] tags : String @@ -104,9 +104,9 @@ tags = "tags icon" -tagsIcon : Html msg -tagsIcon = - i [ class tags ] [] +tagsIcon : String -> Html msg +tagsIcon classes = + i [ class (tags ++ " " ++ classes) ] [] direction : String @@ -114,9 +114,9 @@ direction = "exchange icon" -directionIcon : Html msg -directionIcon = - i [ class direction ] [] +directionIcon : String -> Html msg +directionIcon classes = + i [ class (direction ++ " " ++ classes) ] [] person : String @@ -124,9 +124,9 @@ person = "user icon" -personIcon : Html msg -personIcon = - i [ class person ] [] +personIcon : String -> Html msg +personIcon classes = + i [ class (person ++ " " ++ classes) ] [] organization : String @@ -134,9 +134,9 @@ organization = "factory icon" -organizationIcon : Html msg -organizationIcon = - i [ class organization ] [] +organizationIcon : String -> Html msg +organizationIcon classes = + i [ class (organization ++ " " ++ classes) ] [] equipment : String @@ -144,6 +144,6 @@ equipment = "box icon" -equipmentIcon : Html msg -equipmentIcon = - i [ class equipment ] [] +equipmentIcon : String -> Html msg +equipmentIcon classes = + i [ class (equipment ++ " " ++ classes) ] [] diff --git a/modules/webapp/src/main/elm/Page/ManageData/View.elm b/modules/webapp/src/main/elm/Page/ManageData/View.elm index fabdc56b..9d048d2a 100644 --- a/modules/webapp/src/main/elm/Page/ManageData/View.elm +++ b/modules/webapp/src/main/elm/Page/ManageData/View.elm @@ -26,28 +26,28 @@ view settings model = [ classActive (model.currentTab == Just TagTab) "link icon item" , onClick (SetTab TagTab) ] - [ Icons.tagIcon + [ Icons.tagIcon "" , text "Tag" ] , div [ classActive (model.currentTab == Just EquipTab) "link icon item" , onClick (SetTab EquipTab) ] - [ Icons.equipmentIcon + [ Icons.equipmentIcon "" , text "Equipment" ] , div [ classActive (model.currentTab == Just OrgTab) "link icon item" , onClick (SetTab OrgTab) ] - [ Icons.organizationIcon + [ Icons.organizationIcon "" , text "Organization" ] , div [ classActive (model.currentTab == Just PersonTab) "link icon item" , onClick (SetTab PersonTab) ] - [ Icons.personIcon + [ Icons.personIcon "" , text "Person" ] ] @@ -78,7 +78,7 @@ view settings model = viewTags : Model -> List (Html Msg) viewTags model = [ h2 [ class "ui header" ] - [ Icons.tagIcon + [ Icons.tagIcon "" , div [ class "content" ] [ text "Tags" ] @@ -90,7 +90,7 @@ viewTags model = viewEquip : Model -> List (Html Msg) viewEquip model = [ h2 [ class "ui header" ] - [ Icons.equipmentIcon + [ Icons.equipmentIcon "" , div [ class "content" ] [ text "Equipment" ] @@ -102,7 +102,7 @@ viewEquip model = viewOrg : UiSettings -> Model -> List (Html Msg) viewOrg settings model = [ h2 [ class "ui header" ] - [ Icons.organizationIcon + [ Icons.organizationIcon "" , div [ class "content" ] [ text "Organizations" ] @@ -114,7 +114,7 @@ viewOrg settings model = viewPerson : UiSettings -> Model -> List (Html Msg) viewPerson settings model = [ h2 [ class "ui header" ] - [ Icons.personIcon + [ Icons.personIcon "" , div [ class "content" ] [ text "Person" ]