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..607dc3a3 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala @@ -43,18 +43,30 @@ 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] + def addCorrOrg(item: Ident, org: OOrganization.OrgAndContacts): F[AddResult] + 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] @@ -132,220 +144,334 @@ 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) + oorg <- OOrganization(store) + oequip <- OEquipment(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) + + 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"))) - 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 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"))) - case None => - (None: Option[AttachmentArchiveData[F]]).pure[F] - }) + def setConcPerson( + item: Ident, + person: Option[Ident], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateConcPerson(item, collective, person)) + .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 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 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 setConcEquip( + item: Ident, + equip: Option[Ident], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateConcEquip(item, collective, equip)) + .attempt + .map(AddResult.fromUpdate) - store.transact(db).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 setDirection( - item: Ident, - direction: Direction, - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateDirection(item, collective, direction)) - .attempt - .map(AddResult.fromUpdate) + def setNotes( + item: Ident, + notes: Option[String], + collective: Ident + ): F[AddResult] = + store + .transact(RItem.updateNotes(item, collective, notes)) + .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 setName(item: Ident, name: String, collective: Ident): F[AddResult] = + store + .transact(RItem.updateName(item, collective, name)) + .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 setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] = + store + .transact(RItem.updateStateForCollective(item, state, collective)) + .attempt + .map(AddResult.fromUpdate) - def setConcPerson( - item: Ident, - person: Option[Ident], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateConcPerson(item, collective, person)) - .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 setConcEquip( - item: Ident, - equip: Option[Ident], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateConcEquip(item, collective, equip)) - .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 setNotes(item: Ident, notes: Option[String], collective: Ident): F[AddResult] = - store - .transact(RItem.updateNotes(item, collective, notes)) - .attempt - .map(AddResult.fromUpdate) + def deleteItem(itemId: Ident, collective: Ident): F[Int] = + QItem.delete(store)(itemId, collective) - def setName(item: Ident, name: String, collective: Ident): F[AddResult] = - store - .transact(RItem.updateName(item, collective, name)) - .attempt - .map(AddResult.fromUpdate) + def getProposals(item: Ident, collective: Ident): F[MetaProposalList] = + store.transact(QAttachment.getMetaProposals(item, collective)) - def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] = - store - .transact(RItem.updateStateForCollective(item, state, collective)) - .attempt - .map(AddResult.fromUpdate) + def findAttachmentMeta(id: Ident, collective: Ident): F[Option[RAttachmentMeta]] = + store.transact(QAttachment.getAttachmentMeta(id, collective)) - def setItemDate( - item: Ident, - date: Option[Timestamp], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateDate(item, collective, date)) - .attempt - .map(AddResult.fromUpdate) + def findByFileCollective(checksum: String, collective: Ident): F[Vector[RItem]] = + store.transact(QItem.findByChecksum(checksum, collective)) - def setItemDueDate( - item: Ident, - date: Option[Timestamp], - collective: Ident - ): F[AddResult] = - store - .transact(RItem.updateDueDate(item, collective, date)) - .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 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 bd85e136..4c3fb7f0 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: | @@ -1081,8 +1081,33 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" - /sec/item/{id}/direction: 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 ] summary: Set the direction of an item. description: | @@ -1104,7 +1129,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/corrOrg: - post: + put: tags: [ Item ] summary: Set the correspondent organization of an item. description: | @@ -1125,8 +1150,30 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" - /sec/item/{id}/corrPerson: 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 ] summary: Set the correspondent person of an item. description: | @@ -1147,8 +1194,30 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" - /sec/item/{id}/concPerson: 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 ] summary: Set the concerning person of an item. description: | @@ -1169,8 +1238,30 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" - /sec/item/{id}/concEquipment: 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 ] summary: Set the concering equipment of an item. description: | @@ -1191,8 +1282,30 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" - /sec/item/{id}/notes: 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 ] summary: Set notes of an item. description: | @@ -1214,7 +1327,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 +1385,7 @@ paths: schema: $ref: "#/components/schemas/BasicResult" /sec/item/{id}/date: - post: + put: tags: [ Item ] summary: Sets the item date. description: | @@ -1294,7 +1407,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..23c5e51f 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,96 @@ 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 @ 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] 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 @ 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] 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 @ 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] 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 @ 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] 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 @ 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] 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 +171,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 +179,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..284df71b 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -1,5 +1,10 @@ module Api exposing - ( cancelJob + ( addConcEquip + , addConcPerson + , addCorrOrg + , addCorrPerson + , addTag + , cancelJob , changePassword , checkCalEvent , createImapSettings @@ -693,7 +698,7 @@ getContacts flags kind q receive = --- Tags +--- Tags getTags : Flags -> String -> (Result Http.Error TagList -> msg) -> Cmd msg @@ -732,7 +737,7 @@ deleteTag flags tag receive = --- Equipments +--- Equipments getEquipments : Flags -> String -> (Result Http.Error EquipmentList -> msg) -> Cmd msg @@ -771,7 +776,7 @@ deleteEquip flags equip receive = --- Organization +--- Organization getOrgLight : Flags -> (Result Http.Error ReferenceList -> msg) -> Cmd msg @@ -819,7 +824,7 @@ deleteOrg flags org receive = --- Person +--- Person getPersonsLight : Flags -> (Result Http.Error ReferenceList -> msg) -> Cmd msg @@ -906,7 +911,7 @@ deleteSource flags src receive = --- Users +--- Users getUsers : Flags -> (Result Http.Error UserList -> msg) -> Cmd msg @@ -958,7 +963,7 @@ deleteUser flags user receive = --- Job Queue +--- Job Queue cancelJob : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg @@ -1008,7 +1013,7 @@ getJobQueueStateTask flags = --- Item +--- Item moveAttachmentBefore : @@ -1047,7 +1052,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) @@ -1055,9 +1060,19 @@ 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.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 +1082,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) @@ -1075,9 +1090,19 @@ 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.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/corrPerson" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalId.encode id) @@ -1085,9 +1110,19 @@ 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.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/concPerson" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalId.encode id) @@ -1095,9 +1130,19 @@ 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.authPost + Http2.authPut { url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ item ++ "/concEquipment" , account = getAccount flags , body = Http.jsonBody (Api.Model.OptionalId.encode id) @@ -1105,9 +1150,19 @@ 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.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 +1172,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 +1182,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 +1192,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) @@ -1184,7 +1239,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..2e4f9f44 --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/DetailEdit.elm @@ -0,0 +1,497 @@ +module Comp.DetailEdit exposing + ( Model + , Msg + , Value(..) + , initConcPerson + , initCorrPerson + , initEquip + , initOrg + , 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 + | PMR Comp.PersonForm.Model + | PMC 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 + + PMR pm -> + fp pm + + PMC 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) + + +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 +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) + + PMR pm -> + SubmitPerson (Comp.PersonForm.getPerson pm) + + PMC 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 -> + let + failMsg = + Just (BasicResult False "Please fill required fields.") + in + 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 | result = failMsg } + , 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 | result = failMsg } + , Cmd.none + , Nothing + ) + + 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 | result = failMsg } + , 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 | result = failMsg } + , 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 | result = failMsg } + , Cmd.none + , Nothing + ) + + TagMsg lm -> + case model.form of + TM tm -> + let + ( tm_, tc_ ) = + Comp.TagForm.update flags lm tm + in + ( { model + | form = TM tm_ + , result = Nothing + } + , Cmd.map TagMsg tc_ + , Nothing + ) + + _ -> + ( model, Cmd.none, Nothing ) + + PersonMsg lm -> + case model.form of + PMR pm -> + let + ( pm_, pc_ ) = + Comp.PersonForm.update flags lm pm + in + ( { model + | form = PMR pm_ + , result = Nothing + } + , Cmd.map PersonMsg pc_ + , Nothing + ) + + PMC pm -> + let + ( pm_, pc_ ) = + Comp.PersonForm.update flags lm pm + in + ( { model + | form = PMC pm_ + , result = Nothing + } + , 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_ + , result = Nothing + } + , 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_ + , result = Nothing + } + , Cmd.map EquipMsg ec_ + , Nothing + ) + + _ -> + ( model, Cmd.none, Nothing ) + + + +--- 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 [] + (viewIntern settings True model) + + +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 "scrolling content" ] + (case mm of + Just model -> + viewIntern settings False model + + Nothing -> + [] + ) + , div [ class "actions" ] + (case mm of + Just model -> + viewButtons model + + Nothing -> + [] + ) + ] + ] 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/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 d5b20791..9a65d7af 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail.elm @@ -25,10 +25,14 @@ 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.EquipmentForm import Comp.ItemMail import Comp.MarkdownInput +import Comp.OrgForm +import Comp.PersonForm import Comp.SentMails import Comp.YesNoDimmer import Data.Direction exposing (Direction) @@ -93,6 +97,7 @@ type alias Model = , errored : Set String , loading : Set String , attachDD : DD.Model String String + , modalEdit : Maybe Comp.DetailEdit.Model } @@ -179,6 +184,7 @@ emptyModel = , errored = Set.empty , loading = Set.empty , attachDD = DD.init + , modalEdit = Nothing } @@ -242,10 +248,17 @@ type Msg | AddFilesProgress String Http.Progress | AddFilesReset | AttachDDMsg (DD.Msg String String) + | ModalEditMsg Comp.DetailEdit.Msg + | StartTagModal + | StartCorrOrgModal + | StartCorrPersonModal + | StartConcPersonModal + | StartEquipModal + | CloseModal --- update +--- Update getOptions : Flags -> Cmd Msg @@ -511,6 +524,7 @@ update key flags next msg model = , itemDate = item.itemDate , dueDate = item.dueDate , visibleAttach = 0 + , modalEdit = Nothing } , Cmd.batch [ c1 @@ -1202,9 +1216,95 @@ 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 + ) + + StartCorrOrgModal -> + noSub + ( { model + | 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 + ) + + CloseModal -> + noSub ( { model | modalEdit = Nothing }, Cmd.none ) --- view + +--- View actionInputDatePicker : DatePicker.Settings @@ -1219,7 +1319,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 ) @@ -1674,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 @@ -1816,12 +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 [] - [ i [ class "tags icon" ] [] + [ Icons.tagsIcon "grey" , text "Tags" + , addIconLink "Add new tag" StartTagModal ] , Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel) ] @@ -1838,11 +1951,17 @@ renderEditForm settings model = ] ] , div [ class "field" ] - [ label [] [ text "Direction" ] + [ label [] + [ Icons.directionIcon "grey" + , text "Direction" + ] , Html.map DirDropdownMsg (Comp.Dropdown.view settings model.directionModel) ] , div [ class " field" ] - [ label [] [ text "Date" ] + [ label [] + [ Icons.dateIcon "grey" + , text "Date" + ] , div [ class "ui action input" ] [ Html.map ItemDatePickerMsg (Comp.DatePicker.viewTime @@ -1857,7 +1976,10 @@ renderEditForm settings model = , renderItemDateSuggestions model ] , div [ class " field" ] - [ label [] [ text "Due Date" ] + [ label [] + [ Icons.dueDateIcon "grey" + , text "Due Date" + ] , div [ class "ui action input" ] [ Html.map DueDatePickerMsg (Comp.DatePicker.viewTime @@ -1871,30 +1993,46 @@ 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 "grey" + , text "Organization" + , addIconLink "Add new organization" StartCorrOrgModal + ] , Html.map OrgDropdownMsg (Comp.Dropdown.view settings model.corrOrgModel) , renderOrgSuggestions model ] , div [ class "field" ] - [ label [] [ text "Person" ] + [ label [] + [ Icons.personIcon "grey" + , text "Person" + , addIconLink "Add new correspondent person" StartCorrPersonModal + ] , 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 "grey" + , text "Person" + , addIconLink "Add new concerning person" StartConcPersonModal + ] , Html.map ConcPersonMsg (Comp.Dropdown.view settings model.concPersonModel) , renderConcPersonSuggestions model ] , div [ class "field" ] - [ label [] [ text "Equipment" ] + [ label [] + [ Icons.equipmentIcon "grey" + , text "Equipment" + , 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 13d63503..a52f0c4b 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,14 +49,24 @@ correspondentIcon = i [ class correspondent ] [] +date : String +date = + "calendar outline icon" + + +dateIcon : String -> Html msg +dateIcon classes = + i [ class (date ++ " " ++ classes) ] [] + + dueDate : String dueDate = "bell icon" -dueDateIcon : Html msg -dueDateIcon = - i [ class dueDate ] [] +dueDateIcon : String -> Html msg +dueDateIcon classes = + i [ class (dueDate ++ " " ++ classes) ] [] editNotes : String @@ -63,3 +87,63 @@ addFiles = addFilesIcon : Html msg addFilesIcon = i [ class addFiles ] [] + + +tag : String +tag = + "tag icon" + + +tagIcon : String -> Html msg +tagIcon classes = + i [ class (tag ++ " " ++ classes) ] [] + + +tags : String +tags = + "tags icon" + + +tagsIcon : String -> Html msg +tagsIcon classes = + i [ class (tags ++ " " ++ classes) ] [] + + +direction : String +direction = + "exchange icon" + + +directionIcon : String -> Html msg +directionIcon classes = + i [ class (direction ++ " " ++ classes) ] [] + + +person : String +person = + "user icon" + + +personIcon : String -> Html msg +personIcon classes = + i [ class (person ++ " " ++ classes) ] [] + + +organization : String +organization = + "factory icon" + + +organizationIcon : String -> Html msg +organizationIcon classes = + i [ class (organization ++ " " ++ classes) ] [] + + +equipment : String +equipment = + "box icon" + + +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 ed9586af..9d048d2a 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" ] 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 ->