mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-11-03 18:00:11 +00:00 
			
		
		
		
	@@ -1,5 +1,6 @@
 | 
				
			|||||||
package docspell.backend.ops
 | 
					package docspell.backend.ops
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cats.data.NonEmptyList
 | 
				
			||||||
import cats.data.OptionT
 | 
					import cats.data.OptionT
 | 
				
			||||||
import cats.effect.{Effect, Resource}
 | 
					import cats.effect.{Effect, Resource}
 | 
				
			||||||
import cats.implicits._
 | 
					import cats.implicits._
 | 
				
			||||||
@@ -13,62 +14,124 @@ import docspell.store.queue.JobQueue
 | 
				
			|||||||
import docspell.store.records._
 | 
					import docspell.store.records._
 | 
				
			||||||
import docspell.store.{AddResult, Store}
 | 
					import docspell.store.{AddResult, Store}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import doobie._
 | 
					 | 
				
			||||||
import doobie.implicits._
 | 
					import doobie.implicits._
 | 
				
			||||||
import org.log4s.getLogger
 | 
					import org.log4s.getLogger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait OItem[F[_]] {
 | 
					trait OItem[F[_]] {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Sets the given tags (removing all existing ones). */
 | 
					  /** Sets the given tags (removing all existing ones). */
 | 
				
			||||||
  def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult]
 | 
					  def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Sets tags for multiple items. The tags of the items will be
 | 
				
			||||||
 | 
					    * replaced with the given ones. Same as `setTags` but for multiple
 | 
				
			||||||
 | 
					    * items.
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					  def setTagsMultipleItems(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      tags: List[Ident],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Create a new tag and add it to the item. */
 | 
					  /** Create a new tag and add it to the item. */
 | 
				
			||||||
  def addNewTag(item: Ident, tag: RTag): F[AddResult]
 | 
					  def addNewTag(item: Ident, tag: RTag): F[AddResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Apply all tags to the given item. Tags must exist, but can be IDs or names. */
 | 
					  /** Apply all tags to the given item. Tags must exist, but can be IDs
 | 
				
			||||||
 | 
					    * or names. Existing tags on the item are left unchanged.
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
  def linkTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
 | 
					  def linkTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def linkTagsMultipleItems(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      tags: List[String],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Toggles tags of the given item. Tags must exist, but can be IDs or names. */
 | 
					  /** Toggles tags of the given item. Tags must exist, but can be IDs or names. */
 | 
				
			||||||
  def toggleTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
 | 
					  def toggleTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setDirection(item: Ident, direction: Direction, collective: Ident): F[AddResult]
 | 
					  def setDirection(
 | 
				
			||||||
 | 
					      item: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      direction: Direction,
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setFolder(item: Ident, folder: Option[Ident], collective: Ident): F[AddResult]
 | 
					  def setFolder(item: Ident, folder: Option[Ident], collective: Ident): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setCorrOrg(item: Ident, org: Option[Ident], collective: Ident): F[AddResult]
 | 
					  def setFolderMultiple(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      folder: Option[Ident],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def setCorrOrg(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      org: Option[Ident],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def addCorrOrg(item: Ident, org: OOrganization.OrgAndContacts): F[AddResult]
 | 
					  def addCorrOrg(item: Ident, org: OOrganization.OrgAndContacts): F[AddResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setCorrPerson(item: Ident, person: Option[Ident], collective: Ident): F[AddResult]
 | 
					  def setCorrPerson(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      person: Option[Ident],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def addCorrPerson(item: Ident, person: OOrganization.PersonAndContacts): F[AddResult]
 | 
					  def addCorrPerson(item: Ident, person: OOrganization.PersonAndContacts): F[AddResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setConcPerson(item: Ident, person: Option[Ident], collective: Ident): F[AddResult]
 | 
					  def setConcPerson(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      person: Option[Ident],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def addConcPerson(item: Ident, person: OOrganization.PersonAndContacts): F[AddResult]
 | 
					  def addConcPerson(item: Ident, person: OOrganization.PersonAndContacts): F[AddResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setConcEquip(item: Ident, equip: Option[Ident], collective: Ident): F[AddResult]
 | 
					  def setConcEquip(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      equip: Option[Ident],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def addConcEquip(item: Ident, equip: REquipment): F[AddResult]
 | 
					  def addConcEquip(item: Ident, equip: REquipment): F[AddResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setNotes(item: Ident, notes: Option[String], collective: Ident): F[AddResult]
 | 
					  def setNotes(item: Ident, notes: Option[String], collective: Ident): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setName(item: Ident, name: String, collective: Ident): F[AddResult]
 | 
					  def setName(item: Ident, name: String, collective: Ident): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult]
 | 
					  def setNameMultiple(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      name: String,
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setItemDate(item: Ident, date: Option[Timestamp], collective: Ident): F[AddResult]
 | 
					  def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] =
 | 
				
			||||||
 | 
					    setStates(NonEmptyList.of(item), state, collective)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def setItemDueDate(
 | 
					  def setStates(
 | 
				
			||||||
      item: Ident,
 | 
					      item: NonEmptyList[Ident],
 | 
				
			||||||
      date: Option[Timestamp],
 | 
					      state: ItemState,
 | 
				
			||||||
      collective: Ident
 | 
					      collective: Ident
 | 
				
			||||||
  ): F[AddResult]
 | 
					  ): F[AddResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def setItemDate(
 | 
				
			||||||
 | 
					      item: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      date: Option[Timestamp],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def setItemDueDate(
 | 
				
			||||||
 | 
					      item: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      date: Option[Timestamp],
 | 
				
			||||||
 | 
					      collective: Ident
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def getProposals(item: Ident, collective: Ident): F[MetaProposalList]
 | 
					  def getProposals(item: Ident, collective: Ident): F[MetaProposalList]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def deleteItem(itemId: Ident, collective: Ident): F[Int]
 | 
					  def deleteItem(itemId: Ident, collective: Ident): F[Int]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def deleteItemMultiple(items: NonEmptyList[Ident], collective: Ident): F[Int]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def deleteAttachment(id: Ident, collective: Ident): F[Int]
 | 
					  def deleteAttachment(id: Ident, collective: Ident): F[Int]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def moveAttachmentBefore(itemId: Ident, source: Ident, target: Ident): F[AddResult]
 | 
					  def moveAttachmentBefore(itemId: Ident, source: Ident, target: Ident): F[AddResult]
 | 
				
			||||||
@@ -77,7 +140,7 @@ trait OItem[F[_]] {
 | 
				
			|||||||
      attachId: Ident,
 | 
					      attachId: Ident,
 | 
				
			||||||
      name: Option[String],
 | 
					      name: Option[String],
 | 
				
			||||||
      collective: Ident
 | 
					      collective: Ident
 | 
				
			||||||
  ): F[AddResult]
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Submits the item for re-processing. The list of attachment ids can
 | 
					  /** Submits the item for re-processing. The list of attachment ids can
 | 
				
			||||||
    * be used to only re-process a subset of the item's attachments.
 | 
					    * be used to only re-process a subset of the item's attachments.
 | 
				
			||||||
@@ -91,6 +154,12 @@ trait OItem[F[_]] {
 | 
				
			|||||||
      notifyJoex: Boolean
 | 
					      notifyJoex: Boolean
 | 
				
			||||||
  ): F[UpdateResult]
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def reprocessAll(
 | 
				
			||||||
 | 
					      items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      account: AccountId,
 | 
				
			||||||
 | 
					      notifyJoex: Boolean
 | 
				
			||||||
 | 
					  ): F[UpdateResult]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Submits a task that finds all non-converted pdfs and triggers
 | 
					  /** Submits a task that finds all non-converted pdfs and triggers
 | 
				
			||||||
    * converting them using ocrmypdf. Each file is converted by a
 | 
					    * converting them using ocrmypdf. Each file is converted by a
 | 
				
			||||||
    * separate task.
 | 
					    * separate task.
 | 
				
			||||||
@@ -130,21 +199,30 @@ object OItem {
 | 
				
			|||||||
            item: Ident,
 | 
					            item: Ident,
 | 
				
			||||||
            tags: List[String],
 | 
					            tags: List[String],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
 | 
					          linkTagsMultipleItems(NonEmptyList.of(item), tags, collective)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def linkTagsMultipleItems(
 | 
				
			||||||
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					            tags: List[String],
 | 
				
			||||||
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[UpdateResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          tags.distinct match {
 | 
					          tags.distinct match {
 | 
				
			||||||
            case Nil => UpdateResult.success.pure[F]
 | 
					            case Nil => UpdateResult.success.pure[F]
 | 
				
			||||||
            case kws =>
 | 
					            case ws =>
 | 
				
			||||||
              val db =
 | 
					              store.transact {
 | 
				
			||||||
                (for {
 | 
					                (for {
 | 
				
			||||||
                  _     <- OptionT(RItem.checkByIdAndCollective(item, collective))
 | 
					                  itemIds <- OptionT
 | 
				
			||||||
                  given <- OptionT.liftF(RTag.findAllByNameOrId(kws, collective))
 | 
					                    .liftF(RItem.filterItems(items, collective))
 | 
				
			||||||
                  exist <- OptionT.liftF(RTagItem.findAllIn(item, given.map(_.tagId)))
 | 
					                    .filter(_.nonEmpty)
 | 
				
			||||||
 | 
					                  given <- OptionT.liftF(RTag.findAllByNameOrId(ws, collective))
 | 
				
			||||||
                  _ <- OptionT.liftF(
 | 
					                  _ <- OptionT.liftF(
 | 
				
			||||||
                    RTagItem.setAllTags(item, given.map(_.tagId).diff(exist.map(_.tagId)))
 | 
					                    itemIds.traverse(item =>
 | 
				
			||||||
 | 
					                      RTagItem.appendTags(item, given.map(_.tagId).toList)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
                } yield UpdateResult.success).getOrElse(UpdateResult.notFound)
 | 
					                } yield UpdateResult.success).getOrElse(UpdateResult.notFound)
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
              store.transact(db)
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def toggleTags(
 | 
					        def toggleTags(
 | 
				
			||||||
@@ -169,20 +247,23 @@ object OItem {
 | 
				
			|||||||
              store.transact(db)
 | 
					              store.transact(db)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult] = {
 | 
					        def setTags(
 | 
				
			||||||
          val db = for {
 | 
					            item: Ident,
 | 
				
			||||||
            cid <- RItem.getCollective(item)
 | 
					            tagIds: List[Ident],
 | 
				
			||||||
            nd <-
 | 
					            collective: Ident
 | 
				
			||||||
              if (cid.contains(collective)) RTagItem.deleteItemTags(item)
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
              else 0.pure[ConnectionIO]
 | 
					          setTagsMultipleItems(NonEmptyList.of(item), tagIds, collective)
 | 
				
			||||||
            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 setTagsMultipleItems(
 | 
				
			||||||
        }
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					            tags: List[Ident],
 | 
				
			||||||
 | 
					            collective: Ident
 | 
				
			||||||
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
 | 
					          UpdateResult.fromUpdate(store.transact(for {
 | 
				
			||||||
 | 
					            k   <- RTagItem.deleteItemTags(items, collective)
 | 
				
			||||||
 | 
					            res <- items.traverse(i => RTagItem.setAllTags(i, tags))
 | 
				
			||||||
 | 
					            n = res.fold
 | 
				
			||||||
 | 
					          } yield k + n))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def addNewTag(item: Ident, tag: RTag): F[AddResult] =
 | 
					        def addNewTag(item: Ident, tag: RTag): F[AddResult] =
 | 
				
			||||||
          (for {
 | 
					          (for {
 | 
				
			||||||
@@ -192,7 +273,7 @@ object OItem {
 | 
				
			|||||||
            _ <- addres match {
 | 
					            _ <- addres match {
 | 
				
			||||||
              case AddResult.Success =>
 | 
					              case AddResult.Success =>
 | 
				
			||||||
                OptionT.liftF(
 | 
					                OptionT.liftF(
 | 
				
			||||||
                  store.transact(RTagItem.insertItemTags(item, List(tag.tagId)))
 | 
					                  store.transact(RTagItem.setAllTags(item, List(tag.tagId)))
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
              case AddResult.EntityExists(_) =>
 | 
					              case AddResult.EntityExists(_) =>
 | 
				
			||||||
                OptionT.pure[F](0)
 | 
					                OptionT.pure[F](0)
 | 
				
			||||||
@@ -203,33 +284,59 @@ object OItem {
 | 
				
			|||||||
            .getOrElse(AddResult.Failure(new Exception("Collective mismatch")))
 | 
					            .getOrElse(AddResult.Failure(new Exception("Collective mismatch")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setDirection(
 | 
					        def setDirection(
 | 
				
			||||||
            item: Ident,
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
            direction: Direction,
 | 
					            direction: Direction,
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult.fromUpdate(
 | 
				
			||||||
            .transact(RItem.updateDirection(item, collective, direction))
 | 
					            store
 | 
				
			||||||
            .attempt
 | 
					              .transact(RItem.updateDirection(items, collective, direction))
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setFolder(
 | 
					        def setFolder(
 | 
				
			||||||
            item: Ident,
 | 
					            item: Ident,
 | 
				
			||||||
            folder: Option[Ident],
 | 
					            folder: Option[Ident],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult
 | 
				
			||||||
            .transact(RItem.updateFolder(item, collective, folder))
 | 
					            .fromUpdate(
 | 
				
			||||||
            .attempt
 | 
					              store
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					                .transact(RItem.updateFolder(item, collective, folder))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .flatTap(
 | 
					            .flatTap(
 | 
				
			||||||
              onSuccessIgnoreError(fts.updateFolder(logger, item, collective, folder))
 | 
					              onSuccessIgnoreError(fts.updateFolder(logger, item, collective, folder))
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setCorrOrg(item: Ident, org: Option[Ident], collective: Ident): F[AddResult] =
 | 
					        def setFolderMultiple(
 | 
				
			||||||
          store
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
            .transact(RItem.updateCorrOrg(item, collective, org))
 | 
					            folder: Option[Ident],
 | 
				
			||||||
            .attempt
 | 
					            collective: Ident
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
 | 
					          for {
 | 
				
			||||||
 | 
					            results <- items.traverse(i => setFolder(i, folder, collective))
 | 
				
			||||||
 | 
					            err <- results.traverse {
 | 
				
			||||||
 | 
					              case UpdateResult.NotFound =>
 | 
				
			||||||
 | 
					                logger.info("An item was not found when updating the folder") *> 0.pure[F]
 | 
				
			||||||
 | 
					              case UpdateResult.Failure(err) =>
 | 
				
			||||||
 | 
					                logger.error(err)("An item failed to update its folder") *> 1.pure[F]
 | 
				
			||||||
 | 
					              case UpdateResult.Success =>
 | 
				
			||||||
 | 
					                0.pure[F]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            res =
 | 
				
			||||||
 | 
					              if (results.size == err.fold)
 | 
				
			||||||
 | 
					                UpdateResult.failure(new Exception("All items failed to update"))
 | 
				
			||||||
 | 
					              else UpdateResult.success
 | 
				
			||||||
 | 
					          } yield res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def setCorrOrg(
 | 
				
			||||||
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					            org: Option[Ident],
 | 
				
			||||||
 | 
					            collective: Ident
 | 
				
			||||||
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
 | 
					          UpdateResult.fromUpdate(
 | 
				
			||||||
 | 
					            store
 | 
				
			||||||
 | 
					              .transact(RItem.updateCorrOrg(items, collective, org))
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def addCorrOrg(item: Ident, org: OOrganization.OrgAndContacts): F[AddResult] =
 | 
					        def addCorrOrg(item: Ident, org: OOrganization.OrgAndContacts): F[AddResult] =
 | 
				
			||||||
          (for {
 | 
					          (for {
 | 
				
			||||||
@@ -240,7 +347,11 @@ object OItem {
 | 
				
			|||||||
              case AddResult.Success =>
 | 
					              case AddResult.Success =>
 | 
				
			||||||
                OptionT.liftF(
 | 
					                OptionT.liftF(
 | 
				
			||||||
                  store.transact(
 | 
					                  store.transact(
 | 
				
			||||||
                    RItem.updateCorrOrg(item, org.org.cid, Some(org.org.oid))
 | 
					                    RItem.updateCorrOrg(
 | 
				
			||||||
 | 
					                      NonEmptyList.of(item),
 | 
				
			||||||
 | 
					                      org.org.cid,
 | 
				
			||||||
 | 
					                      Some(org.org.oid)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
              case AddResult.EntityExists(_) =>
 | 
					              case AddResult.EntityExists(_) =>
 | 
				
			||||||
@@ -252,14 +363,14 @@ object OItem {
 | 
				
			|||||||
            .getOrElse(AddResult.Failure(new Exception("Collective mismatch")))
 | 
					            .getOrElse(AddResult.Failure(new Exception("Collective mismatch")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setCorrPerson(
 | 
					        def setCorrPerson(
 | 
				
			||||||
            item: Ident,
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
            person: Option[Ident],
 | 
					            person: Option[Ident],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult.fromUpdate(
 | 
				
			||||||
            .transact(RItem.updateCorrPerson(item, collective, person))
 | 
					            store
 | 
				
			||||||
            .attempt
 | 
					              .transact(RItem.updateCorrPerson(items, collective, person))
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def addCorrPerson(
 | 
					        def addCorrPerson(
 | 
				
			||||||
            item: Ident,
 | 
					            item: Ident,
 | 
				
			||||||
@@ -274,7 +385,11 @@ object OItem {
 | 
				
			|||||||
                OptionT.liftF(
 | 
					                OptionT.liftF(
 | 
				
			||||||
                  store.transact(
 | 
					                  store.transact(
 | 
				
			||||||
                    RItem
 | 
					                    RItem
 | 
				
			||||||
                      .updateCorrPerson(item, person.person.cid, Some(person.person.pid))
 | 
					                      .updateCorrPerson(
 | 
				
			||||||
 | 
					                        NonEmptyList.of(item),
 | 
				
			||||||
 | 
					                        person.person.cid,
 | 
				
			||||||
 | 
					                        Some(person.person.pid)
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
              case AddResult.EntityExists(_) =>
 | 
					              case AddResult.EntityExists(_) =>
 | 
				
			||||||
@@ -286,14 +401,14 @@ object OItem {
 | 
				
			|||||||
            .getOrElse(AddResult.Failure(new Exception("Collective mismatch")))
 | 
					            .getOrElse(AddResult.Failure(new Exception("Collective mismatch")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setConcPerson(
 | 
					        def setConcPerson(
 | 
				
			||||||
            item: Ident,
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
            person: Option[Ident],
 | 
					            person: Option[Ident],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult.fromUpdate(
 | 
				
			||||||
            .transact(RItem.updateConcPerson(item, collective, person))
 | 
					            store
 | 
				
			||||||
            .attempt
 | 
					              .transact(RItem.updateConcPerson(items, collective, person))
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def addConcPerson(
 | 
					        def addConcPerson(
 | 
				
			||||||
            item: Ident,
 | 
					            item: Ident,
 | 
				
			||||||
@@ -308,7 +423,11 @@ object OItem {
 | 
				
			|||||||
                OptionT.liftF(
 | 
					                OptionT.liftF(
 | 
				
			||||||
                  store.transact(
 | 
					                  store.transact(
 | 
				
			||||||
                    RItem
 | 
					                    RItem
 | 
				
			||||||
                      .updateConcPerson(item, person.person.cid, Some(person.person.pid))
 | 
					                      .updateConcPerson(
 | 
				
			||||||
 | 
					                        NonEmptyList.of(item),
 | 
				
			||||||
 | 
					                        person.person.cid,
 | 
				
			||||||
 | 
					                        Some(person.person.pid)
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
              case AddResult.EntityExists(_) =>
 | 
					              case AddResult.EntityExists(_) =>
 | 
				
			||||||
@@ -320,14 +439,14 @@ object OItem {
 | 
				
			|||||||
            .getOrElse(AddResult.Failure(new Exception("Collective mismatch")))
 | 
					            .getOrElse(AddResult.Failure(new Exception("Collective mismatch")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setConcEquip(
 | 
					        def setConcEquip(
 | 
				
			||||||
            item: Ident,
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
            equip: Option[Ident],
 | 
					            equip: Option[Ident],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult.fromUpdate(
 | 
				
			||||||
            .transact(RItem.updateConcEquip(item, collective, equip))
 | 
					            store
 | 
				
			||||||
            .attempt
 | 
					              .transact(RItem.updateConcEquip(items, collective, equip))
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def addConcEquip(item: Ident, equip: REquipment): F[AddResult] =
 | 
					        def addConcEquip(item: Ident, equip: REquipment): F[AddResult] =
 | 
				
			||||||
          (for {
 | 
					          (for {
 | 
				
			||||||
@@ -338,7 +457,8 @@ object OItem {
 | 
				
			|||||||
              case AddResult.Success =>
 | 
					              case AddResult.Success =>
 | 
				
			||||||
                OptionT.liftF(
 | 
					                OptionT.liftF(
 | 
				
			||||||
                  store.transact(
 | 
					                  store.transact(
 | 
				
			||||||
                    RItem.updateConcEquip(item, equip.cid, Some(equip.eid))
 | 
					                    RItem
 | 
				
			||||||
 | 
					                      .updateConcEquip(NonEmptyList.of(item), equip.cid, Some(equip.eid))
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
              case AddResult.EntityExists(_) =>
 | 
					              case AddResult.EntityExists(_) =>
 | 
				
			||||||
@@ -353,55 +473,89 @@ object OItem {
 | 
				
			|||||||
            item: Ident,
 | 
					            item: Ident,
 | 
				
			||||||
            notes: Option[String],
 | 
					            notes: Option[String],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult
 | 
				
			||||||
            .transact(RItem.updateNotes(item, collective, notes))
 | 
					            .fromUpdate(
 | 
				
			||||||
            .attempt
 | 
					              store
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					                .transact(RItem.updateNotes(item, collective, notes))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .flatTap(
 | 
					            .flatTap(
 | 
				
			||||||
              onSuccessIgnoreError(fts.updateItemNotes(logger, item, collective, notes))
 | 
					              onSuccessIgnoreError(fts.updateItemNotes(logger, item, collective, notes))
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setName(item: Ident, name: String, collective: Ident): F[AddResult] =
 | 
					        def setName(item: Ident, name: String, collective: Ident): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult
 | 
				
			||||||
            .transact(RItem.updateName(item, collective, name))
 | 
					            .fromUpdate(
 | 
				
			||||||
            .attempt
 | 
					              store
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					                .transact(RItem.updateName(item, collective, name))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .flatTap(
 | 
					            .flatTap(
 | 
				
			||||||
              onSuccessIgnoreError(fts.updateItemName(logger, item, collective, name))
 | 
					              onSuccessIgnoreError(fts.updateItemName(logger, item, collective, name))
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] =
 | 
					        def setNameMultiple(
 | 
				
			||||||
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					            name: String,
 | 
				
			||||||
 | 
					            collective: Ident
 | 
				
			||||||
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
 | 
					          for {
 | 
				
			||||||
 | 
					            results <- items.traverse(i => setName(i, name, collective))
 | 
				
			||||||
 | 
					            err <- results.traverse {
 | 
				
			||||||
 | 
					              case UpdateResult.NotFound =>
 | 
				
			||||||
 | 
					                logger.info("An item was not found when updating the name") *> 0.pure[F]
 | 
				
			||||||
 | 
					              case UpdateResult.Failure(err) =>
 | 
				
			||||||
 | 
					                logger.error(err)("An item failed to update its name") *> 1.pure[F]
 | 
				
			||||||
 | 
					              case UpdateResult.Success =>
 | 
				
			||||||
 | 
					                0.pure[F]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            res =
 | 
				
			||||||
 | 
					              if (results.size == err.fold)
 | 
				
			||||||
 | 
					                UpdateResult.failure(new Exception("All items failed to update"))
 | 
				
			||||||
 | 
					              else UpdateResult.success
 | 
				
			||||||
 | 
					          } yield res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def setStates(
 | 
				
			||||||
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					            state: ItemState,
 | 
				
			||||||
 | 
					            collective: Ident
 | 
				
			||||||
 | 
					        ): F[AddResult] =
 | 
				
			||||||
          store
 | 
					          store
 | 
				
			||||||
            .transact(RItem.updateStateForCollective(item, state, collective))
 | 
					            .transact(RItem.updateStateForCollective(items, state, collective))
 | 
				
			||||||
            .attempt
 | 
					            .attempt
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					            .map(AddResult.fromUpdate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setItemDate(
 | 
					        def setItemDate(
 | 
				
			||||||
            item: Ident,
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
            date: Option[Timestamp],
 | 
					            date: Option[Timestamp],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult.fromUpdate(
 | 
				
			||||||
            .transact(RItem.updateDate(item, collective, date))
 | 
					            store
 | 
				
			||||||
            .attempt
 | 
					              .transact(RItem.updateDate(items, collective, date))
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def setItemDueDate(
 | 
					        def setItemDueDate(
 | 
				
			||||||
            item: Ident,
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
            date: Option[Timestamp],
 | 
					            date: Option[Timestamp],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult.fromUpdate(
 | 
				
			||||||
            .transact(RItem.updateDueDate(item, collective, date))
 | 
					            store
 | 
				
			||||||
            .attempt
 | 
					              .transact(RItem.updateDueDate(items, collective, date))
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def deleteItem(itemId: Ident, collective: Ident): F[Int] =
 | 
					        def deleteItem(itemId: Ident, collective: Ident): F[Int] =
 | 
				
			||||||
          QItem
 | 
					          QItem
 | 
				
			||||||
            .delete(store)(itemId, collective)
 | 
					            .delete(store)(itemId, collective)
 | 
				
			||||||
            .flatTap(_ => fts.removeItem(logger, itemId))
 | 
					            .flatTap(_ => fts.removeItem(logger, itemId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def deleteItemMultiple(items: NonEmptyList[Ident], collective: Ident): F[Int] =
 | 
				
			||||||
 | 
					          for {
 | 
				
			||||||
 | 
					            itemIds <- store.transact(RItem.filterItems(items, collective))
 | 
				
			||||||
 | 
					            results <- itemIds.traverse(item => deleteItem(item, collective))
 | 
				
			||||||
 | 
					            n = results.fold(0)(_ + _)
 | 
				
			||||||
 | 
					          } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def getProposals(item: Ident, collective: Ident): F[MetaProposalList] =
 | 
					        def getProposals(item: Ident, collective: Ident): F[MetaProposalList] =
 | 
				
			||||||
          store.transact(QAttachment.getMetaProposals(item, collective))
 | 
					          store.transact(QAttachment.getMetaProposals(item, collective))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -414,11 +568,12 @@ object OItem {
 | 
				
			|||||||
            attachId: Ident,
 | 
					            attachId: Ident,
 | 
				
			||||||
            name: Option[String],
 | 
					            name: Option[String],
 | 
				
			||||||
            collective: Ident
 | 
					            collective: Ident
 | 
				
			||||||
        ): F[AddResult] =
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
          store
 | 
					          UpdateResult
 | 
				
			||||||
            .transact(RAttachment.updateName(attachId, collective, name))
 | 
					            .fromUpdate(
 | 
				
			||||||
            .attempt
 | 
					              store
 | 
				
			||||||
            .map(AddResult.fromUpdate)
 | 
					                .transact(RAttachment.updateName(attachId, collective, name))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .flatTap(
 | 
					            .flatTap(
 | 
				
			||||||
              onSuccessIgnoreError(
 | 
					              onSuccessIgnoreError(
 | 
				
			||||||
                OptionT(store.transact(RAttachment.findItemId(attachId)))
 | 
					                OptionT(store.transact(RAttachment.findItemId(attachId)))
 | 
				
			||||||
@@ -447,6 +602,20 @@ object OItem {
 | 
				
			|||||||
            _ <- OptionT.liftF(if (notifyJoex) joex.notifyAllNodes else ().pure[F])
 | 
					            _ <- OptionT.liftF(if (notifyJoex) joex.notifyAllNodes else ().pure[F])
 | 
				
			||||||
          } yield UpdateResult.success).getOrElse(UpdateResult.notFound)
 | 
					          } yield UpdateResult.success).getOrElse(UpdateResult.notFound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def reprocessAll(
 | 
				
			||||||
 | 
					            items: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					            account: AccountId,
 | 
				
			||||||
 | 
					            notifyJoex: Boolean
 | 
				
			||||||
 | 
					        ): F[UpdateResult] =
 | 
				
			||||||
 | 
					          UpdateResult.fromUpdate(for {
 | 
				
			||||||
 | 
					            items <- store.transact(RItem.filterItems(items, account.collective))
 | 
				
			||||||
 | 
					            jobs <- items
 | 
				
			||||||
 | 
					              .map(item => ReProcessItemArgs(item, Nil))
 | 
				
			||||||
 | 
					              .traverse(arg => JobFactory.reprocessItem[F](arg, account, Priority.Low))
 | 
				
			||||||
 | 
					            _ <- queue.insertAllIfNew(jobs)
 | 
				
			||||||
 | 
					            _ <- if (notifyJoex) joex.notifyAllNodes else ().pure[F]
 | 
				
			||||||
 | 
					          } yield items.size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def convertAllPdf(
 | 
					        def convertAllPdf(
 | 
				
			||||||
            collective: Option[Ident],
 | 
					            collective: Option[Ident],
 | 
				
			||||||
            account: AccountId,
 | 
					            account: AccountId,
 | 
				
			||||||
@@ -458,17 +627,17 @@ object OItem {
 | 
				
			|||||||
            _   <- if (notifyJoex) joex.notifyAllNodes else ().pure[F]
 | 
					            _   <- if (notifyJoex) joex.notifyAllNodes else ().pure[F]
 | 
				
			||||||
          } yield UpdateResult.success
 | 
					          } yield UpdateResult.success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private def onSuccessIgnoreError(update: F[Unit])(ar: AddResult): F[Unit] =
 | 
					        private def onSuccessIgnoreError(update: F[Unit])(ar: UpdateResult): F[Unit] =
 | 
				
			||||||
          ar match {
 | 
					          ar match {
 | 
				
			||||||
            case AddResult.Success =>
 | 
					            case UpdateResult.Success =>
 | 
				
			||||||
              update.attempt.flatMap {
 | 
					              update.attempt.flatMap {
 | 
				
			||||||
                case Right(()) => ().pure[F]
 | 
					                case Right(()) => ().pure[F]
 | 
				
			||||||
                case Left(ex) =>
 | 
					                case Left(ex) =>
 | 
				
			||||||
                  logger.warn(s"Error updating full-text index: ${ex.getMessage}")
 | 
					                  logger.warn(s"Error updating full-text index: ${ex.getMessage}")
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            case AddResult.Failure(_) =>
 | 
					            case UpdateResult.Failure(_) =>
 | 
				
			||||||
              ().pure[F]
 | 
					              ().pure[F]
 | 
				
			||||||
            case AddResult.EntityExists(_) =>
 | 
					            case UpdateResult.NotFound =>
 | 
				
			||||||
              ().pure[F]
 | 
					              ().pure[F]
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
package docspell.joex.process
 | 
					package docspell.joex.process
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cats.data.NonEmptyList
 | 
				
			||||||
import cats.effect.Sync
 | 
					import cats.effect.Sync
 | 
				
			||||||
import cats.implicits._
 | 
					import cats.implicits._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -65,22 +66,38 @@ object LinkProposal {
 | 
				
			|||||||
      case MetaProposalType.CorrOrg =>
 | 
					      case MetaProposalType.CorrOrg =>
 | 
				
			||||||
        ctx.logger.debug(s"Updating item organization with: ${value.id}") *>
 | 
					        ctx.logger.debug(s"Updating item organization with: ${value.id}") *>
 | 
				
			||||||
          ctx.store.transact(
 | 
					          ctx.store.transact(
 | 
				
			||||||
            RItem.updateCorrOrg(itemId, ctx.args.meta.collective, Some(value))
 | 
					            RItem.updateCorrOrg(
 | 
				
			||||||
 | 
					              NonEmptyList.of(itemId),
 | 
				
			||||||
 | 
					              ctx.args.meta.collective,
 | 
				
			||||||
 | 
					              Some(value)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
      case MetaProposalType.ConcPerson =>
 | 
					      case MetaProposalType.ConcPerson =>
 | 
				
			||||||
        ctx.logger.debug(s"Updating item concerning person with: $value") *>
 | 
					        ctx.logger.debug(s"Updating item concerning person with: $value") *>
 | 
				
			||||||
          ctx.store.transact(
 | 
					          ctx.store.transact(
 | 
				
			||||||
            RItem.updateConcPerson(itemId, ctx.args.meta.collective, Some(value))
 | 
					            RItem.updateConcPerson(
 | 
				
			||||||
 | 
					              NonEmptyList.of(itemId),
 | 
				
			||||||
 | 
					              ctx.args.meta.collective,
 | 
				
			||||||
 | 
					              Some(value)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
      case MetaProposalType.CorrPerson =>
 | 
					      case MetaProposalType.CorrPerson =>
 | 
				
			||||||
        ctx.logger.debug(s"Updating item correspondent person with: $value") *>
 | 
					        ctx.logger.debug(s"Updating item correspondent person with: $value") *>
 | 
				
			||||||
          ctx.store.transact(
 | 
					          ctx.store.transact(
 | 
				
			||||||
            RItem.updateCorrPerson(itemId, ctx.args.meta.collective, Some(value))
 | 
					            RItem.updateCorrPerson(
 | 
				
			||||||
 | 
					              NonEmptyList.of(itemId),
 | 
				
			||||||
 | 
					              ctx.args.meta.collective,
 | 
				
			||||||
 | 
					              Some(value)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
      case MetaProposalType.ConcEquip =>
 | 
					      case MetaProposalType.ConcEquip =>
 | 
				
			||||||
        ctx.logger.debug(s"Updating item concerning equipment with: $value") *>
 | 
					        ctx.logger.debug(s"Updating item concerning equipment with: $value") *>
 | 
				
			||||||
          ctx.store.transact(
 | 
					          ctx.store.transact(
 | 
				
			||||||
            RItem.updateConcEquip(itemId, ctx.args.meta.collective, Some(value))
 | 
					            RItem.updateConcEquip(
 | 
				
			||||||
 | 
					              NonEmptyList.of(itemId),
 | 
				
			||||||
 | 
					              ctx.args.meta.collective,
 | 
				
			||||||
 | 
					              Some(value)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
      case MetaProposalType.DocDate =>
 | 
					      case MetaProposalType.DocDate =>
 | 
				
			||||||
        MetaProposal.parseDate(value) match {
 | 
					        MetaProposal.parseDate(value) match {
 | 
				
			||||||
@@ -88,7 +105,11 @@ object LinkProposal {
 | 
				
			|||||||
            val ts = Timestamp.from(ld.atStartOfDay(Timestamp.UTC))
 | 
					            val ts = Timestamp.from(ld.atStartOfDay(Timestamp.UTC))
 | 
				
			||||||
            ctx.logger.debug(s"Updating item date ${value.id}") *>
 | 
					            ctx.logger.debug(s"Updating item date ${value.id}") *>
 | 
				
			||||||
              ctx.store.transact(
 | 
					              ctx.store.transact(
 | 
				
			||||||
                RItem.updateDate(itemId, ctx.args.meta.collective, Some(ts))
 | 
					                RItem.updateDate(
 | 
				
			||||||
 | 
					                  NonEmptyList.of(itemId),
 | 
				
			||||||
 | 
					                  ctx.args.meta.collective,
 | 
				
			||||||
 | 
					                  Some(ts)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
              )
 | 
					              )
 | 
				
			||||||
          case None =>
 | 
					          case None =>
 | 
				
			||||||
            ctx.logger.info(s"Cannot read value '${value.id}' into a date.") *>
 | 
					            ctx.logger.info(s"Cannot read value '${value.id}' into a date.") *>
 | 
				
			||||||
@@ -100,7 +121,11 @@ object LinkProposal {
 | 
				
			|||||||
            val ts = Timestamp.from(ld.atStartOfDay(Timestamp.UTC))
 | 
					            val ts = Timestamp.from(ld.atStartOfDay(Timestamp.UTC))
 | 
				
			||||||
            ctx.logger.debug(s"Updating item due-date suggestion ${value.id}") *>
 | 
					            ctx.logger.debug(s"Updating item due-date suggestion ${value.id}") *>
 | 
				
			||||||
              ctx.store.transact(
 | 
					              ctx.store.transact(
 | 
				
			||||||
                RItem.updateDueDate(itemId, ctx.args.meta.collective, Some(ts))
 | 
					                RItem.updateDueDate(
 | 
				
			||||||
 | 
					                  NonEmptyList.of(itemId),
 | 
				
			||||||
 | 
					                  ctx.args.meta.collective,
 | 
				
			||||||
 | 
					                  Some(ts)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
              )
 | 
					              )
 | 
				
			||||||
          case None =>
 | 
					          case None =>
 | 
				
			||||||
            ctx.logger.info(s"Cannot read value '${value.id}' into a date.") *>
 | 
					            ctx.logger.info(s"Cannot read value '${value.id}' into a date.") *>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1384,7 +1384,9 @@ paths:
 | 
				
			|||||||
      tags: [ Item ]
 | 
					      tags: [ Item ]
 | 
				
			||||||
      summary: Set new set of tags.
 | 
					      summary: Set new set of tags.
 | 
				
			||||||
      description: |
 | 
					      description: |
 | 
				
			||||||
        Update the tags associated to an item.
 | 
					        Update the tags associated to an item. This will remove all
 | 
				
			||||||
 | 
					        existing ones and sets the given tags, such that after this
 | 
				
			||||||
 | 
					        returns, the item has exactly the tags as given.
 | 
				
			||||||
      security:
 | 
					      security:
 | 
				
			||||||
        - authTokenHeader: []
 | 
					        - authTokenHeader: []
 | 
				
			||||||
      parameters:
 | 
					      parameters:
 | 
				
			||||||
@@ -1845,6 +1847,7 @@ paths:
 | 
				
			|||||||
            application/json:
 | 
					            application/json:
 | 
				
			||||||
              schema:
 | 
					              schema:
 | 
				
			||||||
                $ref: "#/components/schemas/ItemProposals"
 | 
					                $ref: "#/components/schemas/ItemProposals"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /sec/item/{itemId}/reprocess:
 | 
					  /sec/item/{itemId}/reprocess:
 | 
				
			||||||
    post:
 | 
					    post:
 | 
				
			||||||
      tags: [ Item ]
 | 
					      tags: [ Item ]
 | 
				
			||||||
@@ -1895,6 +1898,359 @@ paths:
 | 
				
			|||||||
              schema:
 | 
					              schema:
 | 
				
			||||||
                $ref: "#/components/schemas/BasicResult"
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/deleteAll:
 | 
				
			||||||
 | 
					    post:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Delete multiple items.
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given a list of item ids, deletes all of them.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/IdList"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/tags:
 | 
				
			||||||
 | 
					    post:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Add tags to multiple items
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Add the given tags to all given items. The tags that are
 | 
				
			||||||
 | 
					        currently attached to the items are not changed. If there are
 | 
				
			||||||
 | 
					        new tags in the given list, then they are added. Otherwise,
 | 
				
			||||||
 | 
					        the item is left unchanged.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndRefs"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Sets tags to multiple items
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Sets the given tags to all given items. If the tag list is
 | 
				
			||||||
 | 
					        empty, then all tags are removed from the items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndRefs"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/name:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Change the name of multiple items
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Sets the name of multiple items at once. The name must not be
 | 
				
			||||||
 | 
					        empty.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndName"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/folder:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Sets a folder to multiple items.
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given a folder id, sets it on all given items. If the folder
 | 
				
			||||||
 | 
					        reference is not present, the folder is removed from all
 | 
				
			||||||
 | 
					        items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndRef"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/direction:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Set the direction of multiple items
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given multiple item ids and a direction value, sets it to all
 | 
				
			||||||
 | 
					        items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndDirection"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/date:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Set the date of multiple items
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given multiple item ids and a date, sets it to all items as
 | 
				
			||||||
 | 
					        the item date. If no date is present, remove the date from the
 | 
				
			||||||
 | 
					        items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndDate"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/duedate:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Set the direction of multiple items
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given multiple item ids and a date value, sets it to all items
 | 
				
			||||||
 | 
					        as the due date. If the date is missing, remove the due-date
 | 
				
			||||||
 | 
					        from the items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndDate"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/corrOrg:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Sets an organization to multiple items.
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given an organization id, sets it on all given items. If the
 | 
				
			||||||
 | 
					        organization is missing, the reference is removed from all
 | 
				
			||||||
 | 
					        items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndRef"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/corrPerson:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Sets an correspondent person to multiple items.
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given an person id, sets it on all given items as
 | 
				
			||||||
 | 
					        correspondent person. If the person is missing, the reference
 | 
				
			||||||
 | 
					        is removed from all items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndRef"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/concPerson:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Sets an concerning person to multiple items.
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given an person id, sets it on all given items as concerning
 | 
				
			||||||
 | 
					        person. If the person is missing, it is removed from all
 | 
				
			||||||
 | 
					        items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndRef"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/concEquipment:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Sets an equipment to multiple items.
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given an equipment id, sets it on all given items. If no
 | 
				
			||||||
 | 
					        equipment is given, the reference is removed from all given
 | 
				
			||||||
 | 
					        items.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/ItemsAndRef"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/confirm:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Confirm multiple items.
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given a list of item ids, confirm all of them.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/IdList"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/unconfirm:
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Un-confirm multiple items.
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given a list of item ids, un-confirm all of them.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/IdList"
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/items/reprocess:
 | 
				
			||||||
 | 
					    post:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - Item (Multi Edit)
 | 
				
			||||||
 | 
					      summary: Submit multiple items to re-processing
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Given a list of item-ids, submits all these items for
 | 
				
			||||||
 | 
					        reprocessing. All attachments of these items will be
 | 
				
			||||||
 | 
					        reprocessed. Item metadata is not changed.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              $ref: "#/components/schemas/IdList"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /sec/attachment/{id}:
 | 
					  /sec/attachment/{id}:
 | 
				
			||||||
    delete:
 | 
					    delete:
 | 
				
			||||||
      tags: [ Attachment ]
 | 
					      tags: [ Attachment ]
 | 
				
			||||||
@@ -2702,6 +3058,84 @@ paths:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
components:
 | 
					components:
 | 
				
			||||||
  schemas:
 | 
					  schemas:
 | 
				
			||||||
 | 
					    ItemsAndRefs:
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Holds a list of item ids and a list of ids of some other
 | 
				
			||||||
 | 
					        related entity (e.g. tags).
 | 
				
			||||||
 | 
					      required:
 | 
				
			||||||
 | 
					        - items
 | 
				
			||||||
 | 
					        - refs
 | 
				
			||||||
 | 
					      properties:
 | 
				
			||||||
 | 
					        items:
 | 
				
			||||||
 | 
					          type: array
 | 
				
			||||||
 | 
					          items:
 | 
				
			||||||
 | 
					            type: string
 | 
				
			||||||
 | 
					            format: ident
 | 
				
			||||||
 | 
					        refs:
 | 
				
			||||||
 | 
					          type: array
 | 
				
			||||||
 | 
					          items:
 | 
				
			||||||
 | 
					            type: string
 | 
				
			||||||
 | 
					            format: ident
 | 
				
			||||||
 | 
					    ItemsAndRef:
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Holds a list of item ids and a single optional id of some
 | 
				
			||||||
 | 
					        other related entity (e.g. person, org).
 | 
				
			||||||
 | 
					      required:
 | 
				
			||||||
 | 
					        - items
 | 
				
			||||||
 | 
					      properties:
 | 
				
			||||||
 | 
					        items:
 | 
				
			||||||
 | 
					          type: array
 | 
				
			||||||
 | 
					          items:
 | 
				
			||||||
 | 
					            type: string
 | 
				
			||||||
 | 
					            format: ident
 | 
				
			||||||
 | 
					        ref:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					          format: ident
 | 
				
			||||||
 | 
					    ItemsAndName:
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Holds a list of item ids and an item name.
 | 
				
			||||||
 | 
					      required:
 | 
				
			||||||
 | 
					        - items
 | 
				
			||||||
 | 
					        - name
 | 
				
			||||||
 | 
					      properties:
 | 
				
			||||||
 | 
					        items:
 | 
				
			||||||
 | 
					          type: array
 | 
				
			||||||
 | 
					          items:
 | 
				
			||||||
 | 
					            type: string
 | 
				
			||||||
 | 
					            format: ident
 | 
				
			||||||
 | 
					        name:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					    ItemsAndDirection:
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Holds a list of item ids and a direction value.
 | 
				
			||||||
 | 
					      required:
 | 
				
			||||||
 | 
					        - items
 | 
				
			||||||
 | 
					        - direction
 | 
				
			||||||
 | 
					      properties:
 | 
				
			||||||
 | 
					        items:
 | 
				
			||||||
 | 
					          type: array
 | 
				
			||||||
 | 
					          items:
 | 
				
			||||||
 | 
					            type: string
 | 
				
			||||||
 | 
					            format: ident
 | 
				
			||||||
 | 
					        direction:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					          format: direction
 | 
				
			||||||
 | 
					    ItemsAndDate:
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Holds a list of item ids and a date value.
 | 
				
			||||||
 | 
					      required:
 | 
				
			||||||
 | 
					        - items
 | 
				
			||||||
 | 
					      properties:
 | 
				
			||||||
 | 
					        items:
 | 
				
			||||||
 | 
					          type: array
 | 
				
			||||||
 | 
					          items:
 | 
				
			||||||
 | 
					            type: string
 | 
				
			||||||
 | 
					            format: ident
 | 
				
			||||||
 | 
					        date:
 | 
				
			||||||
 | 
					          type: integer
 | 
				
			||||||
 | 
					          format: date-time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    JobPriority:
 | 
					    JobPriority:
 | 
				
			||||||
      description: |
 | 
					      description: |
 | 
				
			||||||
        Transfer the priority of a job.
 | 
					        Transfer the priority of a job.
 | 
				
			||||||
@@ -3828,7 +4262,7 @@ components:
 | 
				
			|||||||
          format: date-time
 | 
					          format: date-time
 | 
				
			||||||
    ReferenceList:
 | 
					    ReferenceList:
 | 
				
			||||||
      description:
 | 
					      description:
 | 
				
			||||||
        Listing of items.
 | 
					        Listing of entities with their id and a name.
 | 
				
			||||||
      required:
 | 
					      required:
 | 
				
			||||||
        - items
 | 
					        - items
 | 
				
			||||||
      properties:
 | 
					      properties:
 | 
				
			||||||
@@ -4077,6 +4511,8 @@ components:
 | 
				
			|||||||
        dueDateUntil:
 | 
					        dueDateUntil:
 | 
				
			||||||
          type: integer
 | 
					          type: integer
 | 
				
			||||||
          format: date-time
 | 
					          format: date-time
 | 
				
			||||||
 | 
					        itemSubset:
 | 
				
			||||||
 | 
					          $ref: "#/components/schemas/IdList"
 | 
				
			||||||
    ItemLight:
 | 
					    ItemLight:
 | 
				
			||||||
      description: |
 | 
					      description: |
 | 
				
			||||||
        An item with only a few important properties.
 | 
					        An item with only a few important properties.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -73,6 +73,7 @@ object RestServer {
 | 
				
			|||||||
      "collective"              -> CollectiveRoutes(restApp.backend, token),
 | 
					      "collective"              -> CollectiveRoutes(restApp.backend, token),
 | 
				
			||||||
      "queue"                   -> JobQueueRoutes(restApp.backend, token),
 | 
					      "queue"                   -> JobQueueRoutes(restApp.backend, token),
 | 
				
			||||||
      "item"                    -> ItemRoutes(cfg, restApp.backend, token),
 | 
					      "item"                    -> ItemRoutes(cfg, restApp.backend, token),
 | 
				
			||||||
 | 
					      "items"                   -> ItemMultiRoutes(restApp.backend, token),
 | 
				
			||||||
      "attachment"              -> AttachmentRoutes(restApp.backend, token),
 | 
					      "attachment"              -> AttachmentRoutes(restApp.backend, token),
 | 
				
			||||||
      "upload"                  -> UploadRoutes.secured(restApp.backend, cfg, token),
 | 
					      "upload"                  -> UploadRoutes.secured(restApp.backend, cfg, token),
 | 
				
			||||||
      "checkfile"               -> CheckFileRoutes.secured(restApp.backend, token),
 | 
					      "checkfile"               -> CheckFileRoutes.secured(restApp.backend, token),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -134,7 +134,9 @@ trait Conversions {
 | 
				
			|||||||
      m.dueDateFrom,
 | 
					      m.dueDateFrom,
 | 
				
			||||||
      m.dueDateUntil,
 | 
					      m.dueDateUntil,
 | 
				
			||||||
      m.allNames,
 | 
					      m.allNames,
 | 
				
			||||||
      None,
 | 
					      m.itemSubset
 | 
				
			||||||
 | 
					        .map(_.ids.flatMap(i => Ident.fromString(i).toOption).toSet)
 | 
				
			||||||
 | 
					        .filter(_.nonEmpty),
 | 
				
			||||||
      None
 | 
					      None
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,203 @@
 | 
				
			|||||||
 | 
					package docspell.restserver.routes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cats.ApplicativeError
 | 
				
			||||||
 | 
					import cats.MonadError
 | 
				
			||||||
 | 
					import cats.data.NonEmptyList
 | 
				
			||||||
 | 
					import cats.effect._
 | 
				
			||||||
 | 
					import cats.implicits._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import docspell.backend.BackendApp
 | 
				
			||||||
 | 
					import docspell.backend.auth.AuthToken
 | 
				
			||||||
 | 
					import docspell.common.{Ident, ItemState}
 | 
				
			||||||
 | 
					import docspell.restapi.model._
 | 
				
			||||||
 | 
					import docspell.restserver.conv.Conversions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.circe.DecodingFailure
 | 
				
			||||||
 | 
					import org.http4s.HttpRoutes
 | 
				
			||||||
 | 
					import org.http4s.circe.CirceEntityDecoder._
 | 
				
			||||||
 | 
					import org.http4s.circe.CirceEntityEncoder._
 | 
				
			||||||
 | 
					import org.http4s.dsl.Http4sDsl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object ItemMultiRoutes {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def apply[F[_]: Effect](
 | 
				
			||||||
 | 
					      backend: BackendApp[F],
 | 
				
			||||||
 | 
					      user: AuthToken
 | 
				
			||||||
 | 
					  ): HttpRoutes[F] = {
 | 
				
			||||||
 | 
					    val dsl = new Http4sDsl[F] {}
 | 
				
			||||||
 | 
					    import dsl._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HttpRoutes.of {
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "confirm" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json <- req.as[IdList]
 | 
				
			||||||
 | 
					          data <- readIds[F](json.ids)
 | 
				
			||||||
 | 
					          res <- backend.item.setStates(
 | 
				
			||||||
 | 
					            data,
 | 
				
			||||||
 | 
					            ItemState.Confirmed,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(Conversions.basicResult(res, "Item data confirmed"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "unconfirm" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json <- req.as[IdList]
 | 
				
			||||||
 | 
					          data <- readIds[F](json.ids)
 | 
				
			||||||
 | 
					          res <- backend.item.setStates(
 | 
				
			||||||
 | 
					            data,
 | 
				
			||||||
 | 
					            ItemState.Created,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(Conversions.basicResult(res, "Item back to created."))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "tags" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndRefs]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          tags  <- json.refs.traverse(readId[F])
 | 
				
			||||||
 | 
					          res   <- backend.item.setTagsMultipleItems(items, tags, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Tags updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ POST -> Root / "tags" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndRefs]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res <- backend.item.linkTagsMultipleItems(
 | 
				
			||||||
 | 
					            items,
 | 
				
			||||||
 | 
					            json.refs,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(Conversions.basicResult(res, "Tags added."))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "name" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndName]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res <- backend.item.setNameMultiple(
 | 
				
			||||||
 | 
					            items,
 | 
				
			||||||
 | 
					            json.name.notEmpty.getOrElse(""),
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(Conversions.basicResult(res, "Name updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "folder" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndRef]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res   <- backend.item.setFolderMultiple(items, json.ref, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Folder updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "direction" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndDirection]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res   <- backend.item.setDirection(items, json.direction, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Direction updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "date" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndDate]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res   <- backend.item.setItemDate(items, json.date, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Item date updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "duedate" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndDate]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res   <- backend.item.setItemDueDate(items, json.date, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Item due date updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "corrOrg" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndRef]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res   <- backend.item.setCorrOrg(items, json.ref, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Correspondent organization updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "corrPerson" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndRef]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res   <- backend.item.setCorrPerson(items, json.ref, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Correspondent person updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "concPerson" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndRef]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res   <- backend.item.setConcPerson(items, json.ref, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Concerned person updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / "concEquipment" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[ItemsAndRef]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.items)
 | 
				
			||||||
 | 
					          res   <- backend.item.setConcEquip(items, json.ref, user.account.collective)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ POST -> Root / "reprocess" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[IdList]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.ids)
 | 
				
			||||||
 | 
					          res   <- backend.item.reprocessAll(items, user.account, true)
 | 
				
			||||||
 | 
					          resp  <- Ok(Conversions.basicResult(res, "Re-process task(s) submitted."))
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case req @ POST -> Root / "deleteAll" =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          json  <- req.as[IdList]
 | 
				
			||||||
 | 
					          items <- readIds[F](json.ids)
 | 
				
			||||||
 | 
					          n     <- backend.item.deleteItemMultiple(items, user.account.collective)
 | 
				
			||||||
 | 
					          res = BasicResult(
 | 
				
			||||||
 | 
					            n > 0,
 | 
				
			||||||
 | 
					            if (n > 0) "Item(s) deleted" else "Item deletion failed."
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(res)
 | 
				
			||||||
 | 
					        } yield resp
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  implicit final class OptionString(opt: Option[String]) {
 | 
				
			||||||
 | 
					    def notEmpty: Option[String] =
 | 
				
			||||||
 | 
					      opt.map(_.trim).filter(_.nonEmpty)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  implicit final class StringOps(str: String) {
 | 
				
			||||||
 | 
					    def notEmpty: Option[String] =
 | 
				
			||||||
 | 
					      Option(str).notEmpty
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private def readId[F[_]](
 | 
				
			||||||
 | 
					      id: String
 | 
				
			||||||
 | 
					  )(implicit F: ApplicativeError[F, Throwable]): F[Ident] =
 | 
				
			||||||
 | 
					    Ident
 | 
				
			||||||
 | 
					      .fromString(id)
 | 
				
			||||||
 | 
					      .fold(
 | 
				
			||||||
 | 
					        err => F.raiseError(DecodingFailure(err, Nil)),
 | 
				
			||||||
 | 
					        F.pure
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private def readIds[F[_]](ids: List[String])(implicit
 | 
				
			||||||
 | 
					      F: MonadError[F, Throwable]
 | 
				
			||||||
 | 
					  ): F[NonEmptyList[Ident]] =
 | 
				
			||||||
 | 
					    ids.traverse(readId[F]).map(NonEmptyList.fromList).flatMap {
 | 
				
			||||||
 | 
					      case Some(nel) => nel.pure[F]
 | 
				
			||||||
 | 
					      case None =>
 | 
				
			||||||
 | 
					        F.raiseError(
 | 
				
			||||||
 | 
					          DecodingFailure("Empty list found, at least one element required", Nil)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
package docspell.restserver.routes
 | 
					package docspell.restserver.routes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cats.data.NonEmptyList
 | 
				
			||||||
import cats.effect._
 | 
					import cats.effect._
 | 
				
			||||||
import cats.implicits._
 | 
					import cats.implicits._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,8 +166,12 @@ object ItemRoutes {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      case req @ PUT -> Root / Ident(id) / "direction" =>
 | 
					      case req @ PUT -> Root / Ident(id) / "direction" =>
 | 
				
			||||||
        for {
 | 
					        for {
 | 
				
			||||||
          dir  <- req.as[DirectionValue]
 | 
					          dir <- req.as[DirectionValue]
 | 
				
			||||||
          res  <- backend.item.setDirection(id, dir.direction, user.account.collective)
 | 
					          res <- backend.item.setDirection(
 | 
				
			||||||
 | 
					            NonEmptyList.of(id),
 | 
				
			||||||
 | 
					            dir.direction,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
          resp <- Ok(Conversions.basicResult(res, "Direction updated"))
 | 
					          resp <- Ok(Conversions.basicResult(res, "Direction updated"))
 | 
				
			||||||
        } yield resp
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -180,8 +185,12 @@ object ItemRoutes {
 | 
				
			|||||||
      case req @ PUT -> Root / Ident(id) / "corrOrg" =>
 | 
					      case req @ PUT -> Root / Ident(id) / "corrOrg" =>
 | 
				
			||||||
        for {
 | 
					        for {
 | 
				
			||||||
          idref <- req.as[OptionalId]
 | 
					          idref <- req.as[OptionalId]
 | 
				
			||||||
          res   <- backend.item.setCorrOrg(id, idref.id, user.account.collective)
 | 
					          res <- backend.item.setCorrOrg(
 | 
				
			||||||
          resp  <- Ok(Conversions.basicResult(res, "Correspondent organization updated"))
 | 
					            NonEmptyList.of(id),
 | 
				
			||||||
 | 
					            idref.id,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(Conversions.basicResult(res, "Correspondent organization updated"))
 | 
				
			||||||
        } yield resp
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case req @ POST -> Root / Ident(id) / "corrOrg" =>
 | 
					      case req @ POST -> Root / Ident(id) / "corrOrg" =>
 | 
				
			||||||
@@ -195,8 +204,12 @@ object ItemRoutes {
 | 
				
			|||||||
      case req @ PUT -> Root / Ident(id) / "corrPerson" =>
 | 
					      case req @ PUT -> Root / Ident(id) / "corrPerson" =>
 | 
				
			||||||
        for {
 | 
					        for {
 | 
				
			||||||
          idref <- req.as[OptionalId]
 | 
					          idref <- req.as[OptionalId]
 | 
				
			||||||
          res   <- backend.item.setCorrPerson(id, idref.id, user.account.collective)
 | 
					          res <- backend.item.setCorrPerson(
 | 
				
			||||||
          resp  <- Ok(Conversions.basicResult(res, "Correspondent person updated"))
 | 
					            NonEmptyList.of(id),
 | 
				
			||||||
 | 
					            idref.id,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(Conversions.basicResult(res, "Correspondent person updated"))
 | 
				
			||||||
        } yield resp
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case req @ POST -> Root / Ident(id) / "corrPerson" =>
 | 
					      case req @ POST -> Root / Ident(id) / "corrPerson" =>
 | 
				
			||||||
@@ -210,8 +223,12 @@ object ItemRoutes {
 | 
				
			|||||||
      case req @ PUT -> Root / Ident(id) / "concPerson" =>
 | 
					      case req @ PUT -> Root / Ident(id) / "concPerson" =>
 | 
				
			||||||
        for {
 | 
					        for {
 | 
				
			||||||
          idref <- req.as[OptionalId]
 | 
					          idref <- req.as[OptionalId]
 | 
				
			||||||
          res   <- backend.item.setConcPerson(id, idref.id, user.account.collective)
 | 
					          res <- backend.item.setConcPerson(
 | 
				
			||||||
          resp  <- Ok(Conversions.basicResult(res, "Concerned person updated"))
 | 
					            NonEmptyList.of(id),
 | 
				
			||||||
 | 
					            idref.id,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(Conversions.basicResult(res, "Concerned person updated"))
 | 
				
			||||||
        } yield resp
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case req @ POST -> Root / Ident(id) / "concPerson" =>
 | 
					      case req @ POST -> Root / Ident(id) / "concPerson" =>
 | 
				
			||||||
@@ -225,8 +242,12 @@ object ItemRoutes {
 | 
				
			|||||||
      case req @ PUT -> Root / Ident(id) / "concEquipment" =>
 | 
					      case req @ PUT -> Root / Ident(id) / "concEquipment" =>
 | 
				
			||||||
        for {
 | 
					        for {
 | 
				
			||||||
          idref <- req.as[OptionalId]
 | 
					          idref <- req.as[OptionalId]
 | 
				
			||||||
          res   <- backend.item.setConcEquip(id, idref.id, user.account.collective)
 | 
					          res <- backend.item.setConcEquip(
 | 
				
			||||||
          resp  <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
 | 
					            NonEmptyList.of(id),
 | 
				
			||||||
 | 
					            idref.id,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
 | 
				
			||||||
        } yield resp
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case req @ POST -> Root / Ident(id) / "concEquipment" =>
 | 
					      case req @ POST -> Root / Ident(id) / "concEquipment" =>
 | 
				
			||||||
@@ -259,7 +280,11 @@ object ItemRoutes {
 | 
				
			|||||||
        for {
 | 
					        for {
 | 
				
			||||||
          date <- req.as[OptionalDate]
 | 
					          date <- req.as[OptionalDate]
 | 
				
			||||||
          _    <- logger.fdebug(s"Setting item due date to ${date.date}")
 | 
					          _    <- logger.fdebug(s"Setting item due date to ${date.date}")
 | 
				
			||||||
          res  <- backend.item.setItemDueDate(id, date.date, user.account.collective)
 | 
					          res <- backend.item.setItemDueDate(
 | 
				
			||||||
 | 
					            NonEmptyList.of(id),
 | 
				
			||||||
 | 
					            date.date,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
          resp <- Ok(Conversions.basicResult(res, "Item due date updated"))
 | 
					          resp <- Ok(Conversions.basicResult(res, "Item due date updated"))
 | 
				
			||||||
        } yield resp
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -267,7 +292,11 @@ object ItemRoutes {
 | 
				
			|||||||
        for {
 | 
					        for {
 | 
				
			||||||
          date <- req.as[OptionalDate]
 | 
					          date <- req.as[OptionalDate]
 | 
				
			||||||
          _    <- logger.fdebug(s"Setting item date to ${date.date}")
 | 
					          _    <- logger.fdebug(s"Setting item date to ${date.date}")
 | 
				
			||||||
          res  <- backend.item.setItemDate(id, date.date, user.account.collective)
 | 
					          res <- backend.item.setItemDate(
 | 
				
			||||||
 | 
					            NonEmptyList.of(id),
 | 
				
			||||||
 | 
					            date.date,
 | 
				
			||||||
 | 
					            user.account.collective
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
          resp <- Ok(Conversions.basicResult(res, "Item date updated"))
 | 
					          resp <- Ok(Conversions.basicResult(res, "Item date updated"))
 | 
				
			||||||
        } yield resp
 | 
					        } yield resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,7 +57,12 @@ case class Column(name: String, ns: String = "", alias: String = "") {
 | 
				
			|||||||
    f ++ fr"IN (" ++ commas(values) ++ fr")"
 | 
					    f ++ fr"IN (" ++ commas(values) ++ fr")"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def isIn[A: Put](values: NonEmptyList[A]): Fragment =
 | 
					  def isIn[A: Put](values: NonEmptyList[A]): Fragment =
 | 
				
			||||||
    isIn(values.map(a => sql"$a").toList)
 | 
					    values.tail match {
 | 
				
			||||||
 | 
					      case Nil =>
 | 
				
			||||||
 | 
					        is(values.head)
 | 
				
			||||||
 | 
					      case _ =>
 | 
				
			||||||
 | 
					        isIn(values.map(a => sql"$a").toList)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def isLowerIn[A: Put](values: NonEmptyList[A]): Fragment =
 | 
					  def isLowerIn[A: Put](values: NonEmptyList[A]): Fragment =
 | 
				
			||||||
    fr"lower(" ++ f ++ fr") IN (" ++ commas(values.map(a => sql"$a").toList) ++ fr")"
 | 
					    fr"lower(" ++ f ++ fr") IN (" ++ commas(values.map(a => sql"$a").toList) ++ fr")"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,7 +132,7 @@ object RItem {
 | 
				
			|||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateStateForCollective(
 | 
					  def updateStateForCollective(
 | 
				
			||||||
      itemId: Ident,
 | 
					      itemIds: NonEmptyList[Ident],
 | 
				
			||||||
      itemState: ItemState,
 | 
					      itemState: ItemState,
 | 
				
			||||||
      coll: Ident
 | 
					      coll: Ident
 | 
				
			||||||
  ): ConnectionIO[Int] =
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
@@ -140,27 +140,35 @@ object RItem {
 | 
				
			|||||||
      t <- currentTime
 | 
					      t <- currentTime
 | 
				
			||||||
      n <- updateRow(
 | 
					      n <- updateRow(
 | 
				
			||||||
        table,
 | 
					        table,
 | 
				
			||||||
        and(id.is(itemId), cid.is(coll)),
 | 
					        and(id.isIn(itemIds), cid.is(coll)),
 | 
				
			||||||
        commas(state.setTo(itemState), updated.setTo(t))
 | 
					        commas(state.setTo(itemState), updated.setTo(t))
 | 
				
			||||||
      ).update.run
 | 
					      ).update.run
 | 
				
			||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateDirection(itemId: Ident, coll: Ident, dir: Direction): ConnectionIO[Int] =
 | 
					  def updateDirection(
 | 
				
			||||||
 | 
					      itemIds: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      coll: Ident,
 | 
				
			||||||
 | 
					      dir: Direction
 | 
				
			||||||
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
    for {
 | 
					    for {
 | 
				
			||||||
      t <- currentTime
 | 
					      t <- currentTime
 | 
				
			||||||
      n <- updateRow(
 | 
					      n <- updateRow(
 | 
				
			||||||
        table,
 | 
					        table,
 | 
				
			||||||
        and(id.is(itemId), cid.is(coll)),
 | 
					        and(id.isIn(itemIds), cid.is(coll)),
 | 
				
			||||||
        commas(incoming.setTo(dir), updated.setTo(t))
 | 
					        commas(incoming.setTo(dir), updated.setTo(t))
 | 
				
			||||||
      ).update.run
 | 
					      ).update.run
 | 
				
			||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateCorrOrg(itemId: Ident, coll: Ident, org: Option[Ident]): ConnectionIO[Int] =
 | 
					  def updateCorrOrg(
 | 
				
			||||||
 | 
					      itemIds: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      coll: Ident,
 | 
				
			||||||
 | 
					      org: Option[Ident]
 | 
				
			||||||
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
    for {
 | 
					    for {
 | 
				
			||||||
      t <- currentTime
 | 
					      t <- currentTime
 | 
				
			||||||
      n <- updateRow(
 | 
					      n <- updateRow(
 | 
				
			||||||
        table,
 | 
					        table,
 | 
				
			||||||
        and(id.is(itemId), cid.is(coll)),
 | 
					        and(id.isIn(itemIds), cid.is(coll)),
 | 
				
			||||||
        commas(corrOrg.setTo(org), updated.setTo(t))
 | 
					        commas(corrOrg.setTo(org), updated.setTo(t))
 | 
				
			||||||
      ).update.run
 | 
					      ).update.run
 | 
				
			||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
@@ -176,7 +184,7 @@ object RItem {
 | 
				
			|||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateCorrPerson(
 | 
					  def updateCorrPerson(
 | 
				
			||||||
      itemId: Ident,
 | 
					      itemIds: NonEmptyList[Ident],
 | 
				
			||||||
      coll: Ident,
 | 
					      coll: Ident,
 | 
				
			||||||
      person: Option[Ident]
 | 
					      person: Option[Ident]
 | 
				
			||||||
  ): ConnectionIO[Int] =
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
@@ -184,7 +192,7 @@ object RItem {
 | 
				
			|||||||
      t <- currentTime
 | 
					      t <- currentTime
 | 
				
			||||||
      n <- updateRow(
 | 
					      n <- updateRow(
 | 
				
			||||||
        table,
 | 
					        table,
 | 
				
			||||||
        and(id.is(itemId), cid.is(coll)),
 | 
					        and(id.isIn(itemIds), cid.is(coll)),
 | 
				
			||||||
        commas(corrPerson.setTo(person), updated.setTo(t))
 | 
					        commas(corrPerson.setTo(person), updated.setTo(t))
 | 
				
			||||||
      ).update.run
 | 
					      ).update.run
 | 
				
			||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
@@ -200,7 +208,7 @@ object RItem {
 | 
				
			|||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateConcPerson(
 | 
					  def updateConcPerson(
 | 
				
			||||||
      itemId: Ident,
 | 
					      itemIds: NonEmptyList[Ident],
 | 
				
			||||||
      coll: Ident,
 | 
					      coll: Ident,
 | 
				
			||||||
      person: Option[Ident]
 | 
					      person: Option[Ident]
 | 
				
			||||||
  ): ConnectionIO[Int] =
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
@@ -208,7 +216,7 @@ object RItem {
 | 
				
			|||||||
      t <- currentTime
 | 
					      t <- currentTime
 | 
				
			||||||
      n <- updateRow(
 | 
					      n <- updateRow(
 | 
				
			||||||
        table,
 | 
					        table,
 | 
				
			||||||
        and(id.is(itemId), cid.is(coll)),
 | 
					        and(id.isIn(itemIds), cid.is(coll)),
 | 
				
			||||||
        commas(concPerson.setTo(person), updated.setTo(t))
 | 
					        commas(concPerson.setTo(person), updated.setTo(t))
 | 
				
			||||||
      ).update.run
 | 
					      ).update.run
 | 
				
			||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
@@ -224,7 +232,7 @@ object RItem {
 | 
				
			|||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateConcEquip(
 | 
					  def updateConcEquip(
 | 
				
			||||||
      itemId: Ident,
 | 
					      itemIds: NonEmptyList[Ident],
 | 
				
			||||||
      coll: Ident,
 | 
					      coll: Ident,
 | 
				
			||||||
      equip: Option[Ident]
 | 
					      equip: Option[Ident]
 | 
				
			||||||
  ): ConnectionIO[Int] =
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
@@ -232,7 +240,7 @@ object RItem {
 | 
				
			|||||||
      t <- currentTime
 | 
					      t <- currentTime
 | 
				
			||||||
      n <- updateRow(
 | 
					      n <- updateRow(
 | 
				
			||||||
        table,
 | 
					        table,
 | 
				
			||||||
        and(id.is(itemId), cid.is(coll)),
 | 
					        and(id.isIn(itemIds), cid.is(coll)),
 | 
				
			||||||
        commas(concEquipment.setTo(equip), updated.setTo(t))
 | 
					        commas(concEquipment.setTo(equip), updated.setTo(t))
 | 
				
			||||||
      ).update.run
 | 
					      ).update.run
 | 
				
			||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
@@ -281,18 +289,8 @@ object RItem {
 | 
				
			|||||||
      ).update.run
 | 
					      ).update.run
 | 
				
			||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateDate(itemId: Ident, coll: Ident, date: Option[Timestamp]): ConnectionIO[Int] =
 | 
					  def updateDate(
 | 
				
			||||||
    for {
 | 
					      itemIds: NonEmptyList[Ident],
 | 
				
			||||||
      t <- currentTime
 | 
					 | 
				
			||||||
      n <- updateRow(
 | 
					 | 
				
			||||||
        table,
 | 
					 | 
				
			||||||
        and(id.is(itemId), cid.is(coll)),
 | 
					 | 
				
			||||||
        commas(itemDate.setTo(date), updated.setTo(t))
 | 
					 | 
				
			||||||
      ).update.run
 | 
					 | 
				
			||||||
    } yield n
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def updateDueDate(
 | 
					 | 
				
			||||||
      itemId: Ident,
 | 
					 | 
				
			||||||
      coll: Ident,
 | 
					      coll: Ident,
 | 
				
			||||||
      date: Option[Timestamp]
 | 
					      date: Option[Timestamp]
 | 
				
			||||||
  ): ConnectionIO[Int] =
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
@@ -300,7 +298,21 @@ object RItem {
 | 
				
			|||||||
      t <- currentTime
 | 
					      t <- currentTime
 | 
				
			||||||
      n <- updateRow(
 | 
					      n <- updateRow(
 | 
				
			||||||
        table,
 | 
					        table,
 | 
				
			||||||
        and(id.is(itemId), cid.is(coll)),
 | 
					        and(id.isIn(itemIds), cid.is(coll)),
 | 
				
			||||||
 | 
					        commas(itemDate.setTo(date), updated.setTo(t))
 | 
				
			||||||
 | 
					      ).update.run
 | 
				
			||||||
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def updateDueDate(
 | 
				
			||||||
 | 
					      itemIds: NonEmptyList[Ident],
 | 
				
			||||||
 | 
					      coll: Ident,
 | 
				
			||||||
 | 
					      date: Option[Timestamp]
 | 
				
			||||||
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
 | 
					    for {
 | 
				
			||||||
 | 
					      t <- currentTime
 | 
				
			||||||
 | 
					      n <- updateRow(
 | 
				
			||||||
 | 
					        table,
 | 
				
			||||||
 | 
					        and(id.isIn(itemIds), cid.is(coll)),
 | 
				
			||||||
        commas(dueDate.setTo(date), updated.setTo(t))
 | 
					        commas(dueDate.setTo(date), updated.setTo(t))
 | 
				
			||||||
      ).update.run
 | 
					      ).update.run
 | 
				
			||||||
    } yield n
 | 
					    } yield n
 | 
				
			||||||
@@ -324,4 +336,10 @@ object RItem {
 | 
				
			|||||||
    val empty: Option[Ident] = None
 | 
					    val empty: Option[Ident] = None
 | 
				
			||||||
    updateRow(table, folder.is(folderId), folder.setTo(empty)).update.run
 | 
					    updateRow(table, folder.is(folderId), folder.setTo(empty)).update.run
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def filterItemsFragment(items: NonEmptyList[Ident], coll: Ident): Fragment =
 | 
				
			||||||
 | 
					    selectSimple(Seq(id), table, and(cid.is(coll), id.isIn(items)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def filterItems(items: NonEmptyList[Ident], coll: Ident): ConnectionIO[Vector[Ident]] =
 | 
				
			||||||
 | 
					    filterItemsFragment(items, coll).query[Ident].to[Vector]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,18 +30,17 @@ object RTagItem {
 | 
				
			|||||||
  def deleteItemTags(item: Ident): ConnectionIO[Int] =
 | 
					  def deleteItemTags(item: Ident): ConnectionIO[Int] =
 | 
				
			||||||
    deleteFrom(table, itemId.is(item)).update.run
 | 
					    deleteFrom(table, itemId.is(item)).update.run
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def deleteItemTags(items: NonEmptyList[Ident], cid: Ident): ConnectionIO[Int] = {
 | 
				
			||||||
 | 
					    val itemsFiltered =
 | 
				
			||||||
 | 
					      RItem.filterItemsFragment(items, cid)
 | 
				
			||||||
 | 
					    val sql = fr"DELETE FROM" ++ table ++ fr"WHERE" ++ itemId.isIn(itemsFiltered)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sql.update.run
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def deleteTag(tid: Ident): ConnectionIO[Int] =
 | 
					  def deleteTag(tid: Ident): ConnectionIO[Int] =
 | 
				
			||||||
    deleteFrom(table, tagId.is(tid)).update.run
 | 
					    deleteFrom(table, tagId.is(tid)).update.run
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def insertItemTags(item: Ident, tags: Seq[Ident]): ConnectionIO[Int] =
 | 
					 | 
				
			||||||
    for {
 | 
					 | 
				
			||||||
      tagValues <- tags.toList.traverse(id =>
 | 
					 | 
				
			||||||
        Ident.randomId[ConnectionIO].map(rid => RTagItem(rid, item, id))
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
      tagFrag = tagValues.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
 | 
					 | 
				
			||||||
      ins <- insertRows(table, all, tagFrag).update.run
 | 
					 | 
				
			||||||
    } yield ins
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def findByItem(item: Ident): ConnectionIO[Vector[RTagItem]] =
 | 
					  def findByItem(item: Ident): ConnectionIO[Vector[RTagItem]] =
 | 
				
			||||||
    selectSimple(all, table, itemId.is(item)).query[RTagItem].to[Vector]
 | 
					    selectSimple(all, table, itemId.is(item)).query[RTagItem].to[Vector]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -76,4 +75,12 @@ object RTagItem {
 | 
				
			|||||||
          entities.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
 | 
					          entities.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
 | 
				
			||||||
        ).update.run
 | 
					        ).update.run
 | 
				
			||||||
      } yield n
 | 
					      } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def appendTags(item: Ident, tags: List[Ident]): ConnectionIO[Int] =
 | 
				
			||||||
 | 
					    for {
 | 
				
			||||||
 | 
					      existing <- findByItem(item)
 | 
				
			||||||
 | 
					      toadd = tags.toSet.diff(existing.map(_.tagId).toSet)
 | 
				
			||||||
 | 
					      n <- setAllTags(item, toadd.toSeq)
 | 
				
			||||||
 | 
					    } yield n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ module Api exposing
 | 
				
			|||||||
    , addCorrPerson
 | 
					    , addCorrPerson
 | 
				
			||||||
    , addMember
 | 
					    , addMember
 | 
				
			||||||
    , addTag
 | 
					    , addTag
 | 
				
			||||||
 | 
					    , addTagsMultiple
 | 
				
			||||||
    , cancelJob
 | 
					    , cancelJob
 | 
				
			||||||
    , changeFolderName
 | 
					    , changeFolderName
 | 
				
			||||||
    , changePassword
 | 
					    , changePassword
 | 
				
			||||||
@@ -14,6 +15,7 @@ module Api exposing
 | 
				
			|||||||
    , createNewFolder
 | 
					    , createNewFolder
 | 
				
			||||||
    , createNotifyDueItems
 | 
					    , createNotifyDueItems
 | 
				
			||||||
    , createScanMailbox
 | 
					    , createScanMailbox
 | 
				
			||||||
 | 
					    , deleteAllItems
 | 
				
			||||||
    , deleteAttachment
 | 
					    , deleteAttachment
 | 
				
			||||||
    , deleteEquip
 | 
					    , deleteEquip
 | 
				
			||||||
    , deleteFolder
 | 
					    , deleteFolder
 | 
				
			||||||
@@ -76,18 +78,28 @@ module Api exposing
 | 
				
			|||||||
    , setAttachmentName
 | 
					    , setAttachmentName
 | 
				
			||||||
    , setCollectiveSettings
 | 
					    , setCollectiveSettings
 | 
				
			||||||
    , setConcEquip
 | 
					    , setConcEquip
 | 
				
			||||||
 | 
					    , setConcEquipmentMultiple
 | 
				
			||||||
    , setConcPerson
 | 
					    , setConcPerson
 | 
				
			||||||
 | 
					    , setConcPersonMultiple
 | 
				
			||||||
    , setConfirmed
 | 
					    , setConfirmed
 | 
				
			||||||
    , setCorrOrg
 | 
					    , setCorrOrg
 | 
				
			||||||
 | 
					    , setCorrOrgMultiple
 | 
				
			||||||
    , setCorrPerson
 | 
					    , setCorrPerson
 | 
				
			||||||
 | 
					    , setCorrPersonMultiple
 | 
				
			||||||
 | 
					    , setDateMultiple
 | 
				
			||||||
    , setDirection
 | 
					    , setDirection
 | 
				
			||||||
 | 
					    , setDirectionMultiple
 | 
				
			||||||
 | 
					    , setDueDateMultiple
 | 
				
			||||||
    , setFolder
 | 
					    , setFolder
 | 
				
			||||||
 | 
					    , setFolderMultiple
 | 
				
			||||||
    , setItemDate
 | 
					    , setItemDate
 | 
				
			||||||
    , setItemDueDate
 | 
					    , setItemDueDate
 | 
				
			||||||
    , setItemName
 | 
					    , setItemName
 | 
				
			||||||
    , setItemNotes
 | 
					    , setItemNotes
 | 
				
			||||||
    , setJobPrio
 | 
					    , setJobPrio
 | 
				
			||||||
 | 
					    , setNameMultiple
 | 
				
			||||||
    , setTags
 | 
					    , setTags
 | 
				
			||||||
 | 
					    , setTagsMultiple
 | 
				
			||||||
    , setUnconfirmed
 | 
					    , setUnconfirmed
 | 
				
			||||||
    , startClassifier
 | 
					    , startClassifier
 | 
				
			||||||
    , startOnceNotifyDueItems
 | 
					    , startOnceNotifyDueItems
 | 
				
			||||||
@@ -119,6 +131,7 @@ import Api.Model.EquipmentList exposing (EquipmentList)
 | 
				
			|||||||
import Api.Model.FolderDetail exposing (FolderDetail)
 | 
					import Api.Model.FolderDetail exposing (FolderDetail)
 | 
				
			||||||
import Api.Model.FolderList exposing (FolderList)
 | 
					import Api.Model.FolderList exposing (FolderList)
 | 
				
			||||||
import Api.Model.GenInvite exposing (GenInvite)
 | 
					import Api.Model.GenInvite exposing (GenInvite)
 | 
				
			||||||
 | 
					import Api.Model.IdList exposing (IdList)
 | 
				
			||||||
import Api.Model.IdResult exposing (IdResult)
 | 
					import Api.Model.IdResult exposing (IdResult)
 | 
				
			||||||
import Api.Model.ImapSettings exposing (ImapSettings)
 | 
					import Api.Model.ImapSettings exposing (ImapSettings)
 | 
				
			||||||
import Api.Model.ImapSettingsList exposing (ImapSettingsList)
 | 
					import Api.Model.ImapSettingsList exposing (ImapSettingsList)
 | 
				
			||||||
@@ -130,6 +143,11 @@ import Api.Model.ItemLightList exposing (ItemLightList)
 | 
				
			|||||||
import Api.Model.ItemProposals exposing (ItemProposals)
 | 
					import Api.Model.ItemProposals exposing (ItemProposals)
 | 
				
			||||||
import Api.Model.ItemSearch exposing (ItemSearch)
 | 
					import Api.Model.ItemSearch exposing (ItemSearch)
 | 
				
			||||||
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
 | 
					import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndDate exposing (ItemsAndDate)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndDirection exposing (ItemsAndDirection)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndName exposing (ItemsAndName)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndRef exposing (ItemsAndRef)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndRefs exposing (ItemsAndRefs)
 | 
				
			||||||
import Api.Model.JobPriority exposing (JobPriority)
 | 
					import Api.Model.JobPriority exposing (JobPriority)
 | 
				
			||||||
import Api.Model.JobQueueState exposing (JobQueueState)
 | 
					import Api.Model.JobQueueState exposing (JobQueueState)
 | 
				
			||||||
import Api.Model.MoveAttachment exposing (MoveAttachment)
 | 
					import Api.Model.MoveAttachment exposing (MoveAttachment)
 | 
				
			||||||
@@ -166,6 +184,7 @@ import Data.Priority exposing (Priority)
 | 
				
			|||||||
import File exposing (File)
 | 
					import File exposing (File)
 | 
				
			||||||
import Http
 | 
					import Http
 | 
				
			||||||
import Json.Encode as JsonEncode
 | 
					import Json.Encode as JsonEncode
 | 
				
			||||||
 | 
					import Set exposing (Set)
 | 
				
			||||||
import Task
 | 
					import Task
 | 
				
			||||||
import Url
 | 
					import Url
 | 
				
			||||||
import Util.File
 | 
					import Util.File
 | 
				
			||||||
@@ -1262,6 +1281,178 @@ getJobQueueStateTask flags =
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- Item (Mulit Edit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setTagsMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndRefs
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setTagsMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/tags"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndRefs.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addTagsMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndRefs
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					addTagsMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPost
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/tags"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndRefs.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setNameMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndName
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setNameMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/name"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndName.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setFolderMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndRef
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setFolderMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/folder"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndRef.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setDirectionMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndDirection
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setDirectionMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/direction"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndDirection.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setDateMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndDate
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setDateMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/date"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndDate.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setDueDateMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndDate
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setDueDateMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/duedate"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndDate.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setCorrOrgMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndRef
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setCorrOrgMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/corrOrg"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndRef.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setCorrPersonMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndRef
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setCorrPersonMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/corrPerson"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndRef.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setConcPersonMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndRef
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setConcPersonMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/concPerson"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndRef.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setConcEquipmentMultiple :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> ItemsAndRef
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					setConcEquipmentMultiple flags data receive =
 | 
				
			||||||
 | 
					    Http2.authPut
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/concEquipment"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.ItemsAndRef.encode data)
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					deleteAllItems :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> Set String
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					deleteAllItems flags ids receive =
 | 
				
			||||||
 | 
					    Http2.authPost
 | 
				
			||||||
 | 
					        { url = flags.config.baseUrl ++ "/api/v1/sec/items/deleteAll"
 | 
				
			||||||
 | 
					        , account = getAccount flags
 | 
				
			||||||
 | 
					        , body = Http.jsonBody (Api.Model.IdList.encode (IdList (Set.toList ids)))
 | 
				
			||||||
 | 
					        , expect = Http.expectJson receive Api.Model.BasicResult.decoder
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- Item
 | 
					--- Item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
module Comp.ItemCardList exposing
 | 
					module Comp.ItemCardList exposing
 | 
				
			||||||
    ( Model
 | 
					    ( Model
 | 
				
			||||||
    , Msg(..)
 | 
					    , Msg(..)
 | 
				
			||||||
 | 
					    , ViewConfig
 | 
				
			||||||
    , init
 | 
					    , init
 | 
				
			||||||
    , nextItem
 | 
					    , nextItem
 | 
				
			||||||
    , prevItem
 | 
					    , prevItem
 | 
				
			||||||
@@ -17,12 +18,16 @@ import Data.Direction
 | 
				
			|||||||
import Data.Fields
 | 
					import Data.Fields
 | 
				
			||||||
import Data.Flags exposing (Flags)
 | 
					import Data.Flags exposing (Flags)
 | 
				
			||||||
import Data.Icons as Icons
 | 
					import Data.Icons as Icons
 | 
				
			||||||
 | 
					import Data.ItemSelection exposing (ItemSelection)
 | 
				
			||||||
import Data.Items
 | 
					import Data.Items
 | 
				
			||||||
import Data.UiSettings exposing (UiSettings)
 | 
					import Data.UiSettings exposing (UiSettings)
 | 
				
			||||||
import Html exposing (..)
 | 
					import Html exposing (..)
 | 
				
			||||||
import Html.Attributes exposing (..)
 | 
					import Html.Attributes exposing (..)
 | 
				
			||||||
 | 
					import Html.Events exposing (onClick)
 | 
				
			||||||
import Markdown
 | 
					import Markdown
 | 
				
			||||||
import Page exposing (Page(..))
 | 
					import Page exposing (Page(..))
 | 
				
			||||||
 | 
					import Set exposing (Set)
 | 
				
			||||||
 | 
					import Util.Html
 | 
				
			||||||
import Util.ItemDragDrop as DD
 | 
					import Util.ItemDragDrop as DD
 | 
				
			||||||
import Util.List
 | 
					import Util.List
 | 
				
			||||||
import Util.String
 | 
					import Util.String
 | 
				
			||||||
@@ -38,6 +43,7 @@ type Msg
 | 
				
			|||||||
    = SetResults ItemLightList
 | 
					    = SetResults ItemLightList
 | 
				
			||||||
    | AddResults ItemLightList
 | 
					    | AddResults ItemLightList
 | 
				
			||||||
    | ItemDDMsg DD.Msg
 | 
					    | ItemDDMsg DD.Msg
 | 
				
			||||||
 | 
					    | ToggleSelectItem (Set String) String
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init : Model
 | 
					init : Model
 | 
				
			||||||
@@ -75,6 +81,7 @@ type alias UpdateResult =
 | 
				
			|||||||
    { model : Model
 | 
					    { model : Model
 | 
				
			||||||
    , cmd : Cmd Msg
 | 
					    , cmd : Cmd Msg
 | 
				
			||||||
    , dragModel : DD.Model
 | 
					    , dragModel : DD.Model
 | 
				
			||||||
 | 
					    , selection : ItemSelection
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -91,51 +98,78 @@ updateDrag dm _ msg model =
 | 
				
			|||||||
                newModel =
 | 
					                newModel =
 | 
				
			||||||
                    { model | results = list }
 | 
					                    { model | results = list }
 | 
				
			||||||
            in
 | 
					            in
 | 
				
			||||||
            UpdateResult newModel Cmd.none dm
 | 
					            UpdateResult newModel Cmd.none dm Data.ItemSelection.Inactive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        AddResults list ->
 | 
					        AddResults list ->
 | 
				
			||||||
            if list.groups == [] then
 | 
					            if list.groups == [] then
 | 
				
			||||||
                UpdateResult model Cmd.none dm
 | 
					                UpdateResult model Cmd.none dm Data.ItemSelection.Inactive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
                let
 | 
					                let
 | 
				
			||||||
                    newModel =
 | 
					                    newModel =
 | 
				
			||||||
                        { model | results = Data.Items.concat model.results list }
 | 
					                        { model | results = Data.Items.concat model.results list }
 | 
				
			||||||
                in
 | 
					                in
 | 
				
			||||||
                UpdateResult newModel Cmd.none dm
 | 
					                UpdateResult newModel Cmd.none dm Data.ItemSelection.Inactive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ItemDDMsg lm ->
 | 
					        ItemDDMsg lm ->
 | 
				
			||||||
            let
 | 
					            let
 | 
				
			||||||
                ddd =
 | 
					                ddd =
 | 
				
			||||||
                    DD.update lm dm
 | 
					                    DD.update lm dm
 | 
				
			||||||
            in
 | 
					            in
 | 
				
			||||||
            UpdateResult model Cmd.none ddd.model
 | 
					            UpdateResult model Cmd.none ddd.model Data.ItemSelection.Inactive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ToggleSelectItem ids id ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                newSet =
 | 
				
			||||||
 | 
					                    if Set.member id ids then
 | 
				
			||||||
 | 
					                        Set.remove id ids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        Set.insert id ids
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            UpdateResult model Cmd.none dm (Data.ItemSelection.Active newSet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- View
 | 
					--- View
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
view : Maybe String -> UiSettings -> Model -> Html Msg
 | 
					type alias ViewConfig =
 | 
				
			||||||
view current settings model =
 | 
					    { current : Maybe String
 | 
				
			||||||
 | 
					    , selection : ItemSelection
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					isSelected : ViewConfig -> String -> Bool
 | 
				
			||||||
 | 
					isSelected cfg id =
 | 
				
			||||||
 | 
					    case cfg.selection of
 | 
				
			||||||
 | 
					        Data.ItemSelection.Active ids ->
 | 
				
			||||||
 | 
					            Set.member id ids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Data.ItemSelection.Inactive ->
 | 
				
			||||||
 | 
					            False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					view : ViewConfig -> UiSettings -> Model -> Html Msg
 | 
				
			||||||
 | 
					view cfg settings model =
 | 
				
			||||||
    div [ class "ui container" ]
 | 
					    div [ class "ui container" ]
 | 
				
			||||||
        (List.map (viewGroup current settings) model.results.groups)
 | 
					        (List.map (viewGroup cfg settings) model.results.groups)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
viewGroup : Maybe String -> UiSettings -> ItemLightGroup -> Html Msg
 | 
					viewGroup : ViewConfig -> UiSettings -> ItemLightGroup -> Html Msg
 | 
				
			||||||
viewGroup current settings group =
 | 
					viewGroup cfg settings group =
 | 
				
			||||||
    div [ class "item-group" ]
 | 
					    div [ class "item-group" ]
 | 
				
			||||||
        [ div [ class "ui horizontal divider header item-list" ]
 | 
					        [ div [ class "ui horizontal divider header item-list" ]
 | 
				
			||||||
            [ i [ class "calendar alternate outline icon" ] []
 | 
					            [ i [ class "calendar alternate outline icon" ] []
 | 
				
			||||||
            , text group.name
 | 
					            , text group.name
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        , div [ class "ui stackable three cards" ]
 | 
					        , div [ class "ui stackable three cards" ]
 | 
				
			||||||
            (List.map (viewItem current settings) group.items)
 | 
					            (List.map (viewItem cfg settings) group.items)
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
viewItem : Maybe String -> UiSettings -> ItemLight -> Html Msg
 | 
					viewItem : ViewConfig -> UiSettings -> ItemLight -> Html Msg
 | 
				
			||||||
viewItem current settings item =
 | 
					viewItem cfg settings item =
 | 
				
			||||||
    let
 | 
					    let
 | 
				
			||||||
        dirIcon =
 | 
					        dirIcon =
 | 
				
			||||||
            i [ class (Data.Direction.iconFromMaybe item.direction) ] []
 | 
					            i [ class (Data.Direction.iconFromMaybe item.direction) ] []
 | 
				
			||||||
@@ -163,43 +197,69 @@ viewItem current settings item =
 | 
				
			|||||||
        isConfirmed =
 | 
					        isConfirmed =
 | 
				
			||||||
            item.state /= "created"
 | 
					            item.state /= "created"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        newColor =
 | 
					        cardColor =
 | 
				
			||||||
            "blue"
 | 
					            if isSelected cfg item.id then
 | 
				
			||||||
 | 
					                "purple"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else if not isConfirmed then
 | 
				
			||||||
 | 
					                "blue"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fieldHidden f =
 | 
					        fieldHidden f =
 | 
				
			||||||
            Data.UiSettings.fieldHidden settings f
 | 
					            Data.UiSettings.fieldHidden settings f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cardAction =
 | 
				
			||||||
 | 
					            case cfg.selection of
 | 
				
			||||||
 | 
					                Data.ItemSelection.Inactive ->
 | 
				
			||||||
 | 
					                    Page.href (ItemDetailPage item.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Data.ItemSelection.Active ids ->
 | 
				
			||||||
 | 
					                    onClick (ToggleSelectItem ids item.id)
 | 
				
			||||||
    in
 | 
					    in
 | 
				
			||||||
    a
 | 
					    a
 | 
				
			||||||
        ([ classList
 | 
					        ([ classList
 | 
				
			||||||
            [ ( "ui fluid card", True )
 | 
					            [ ( "ui fluid card", True )
 | 
				
			||||||
            , ( newColor, not isConfirmed )
 | 
					            , ( cardColor, True )
 | 
				
			||||||
            , ( "current", current == Just item.id )
 | 
					            , ( "current", cfg.current == Just item.id )
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
         , id item.id
 | 
					         , id item.id
 | 
				
			||||||
         , Page.href (ItemDetailPage item.id)
 | 
					         , href "#"
 | 
				
			||||||
 | 
					         , cardAction
 | 
				
			||||||
         ]
 | 
					         ]
 | 
				
			||||||
            ++ DD.draggable ItemDDMsg item.id
 | 
					            ++ DD.draggable ItemDDMsg item.id
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        [ div [ class "content" ]
 | 
					        [ div [ class "content" ]
 | 
				
			||||||
            [ if fieldHidden Data.Fields.Direction then
 | 
					            [ case cfg.selection of
 | 
				
			||||||
                div [ class "header" ]
 | 
					                Data.ItemSelection.Active ids ->
 | 
				
			||||||
                    [ Util.String.underscoreToSpace item.name |> text
 | 
					                    div [ class "header" ]
 | 
				
			||||||
                    ]
 | 
					                        [ Util.Html.checkbox (Set.member item.id ids)
 | 
				
			||||||
 | 
					                        , dirIcon
 | 
				
			||||||
 | 
					                        , Util.String.underscoreToSpace item.name
 | 
				
			||||||
 | 
					                            |> text
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              else
 | 
					                Data.ItemSelection.Inactive ->
 | 
				
			||||||
                div
 | 
					                    if fieldHidden Data.Fields.Direction then
 | 
				
			||||||
                    [ class "header"
 | 
					                        div [ class "header" ]
 | 
				
			||||||
                    , Data.Direction.labelFromMaybe item.direction
 | 
					                            [ Util.String.underscoreToSpace item.name |> text
 | 
				
			||||||
                        |> title
 | 
					                            ]
 | 
				
			||||||
                    ]
 | 
					
 | 
				
			||||||
                    [ dirIcon
 | 
					                    else
 | 
				
			||||||
                    , Util.String.underscoreToSpace item.name
 | 
					                        div
 | 
				
			||||||
                        |> text
 | 
					                            [ class "header"
 | 
				
			||||||
                    ]
 | 
					                            , Data.Direction.labelFromMaybe item.direction
 | 
				
			||||||
 | 
					                                |> title
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
 | 
					                            [ dirIcon
 | 
				
			||||||
 | 
					                            , Util.String.underscoreToSpace item.name
 | 
				
			||||||
 | 
					                                |> text
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
            , div
 | 
					            , div
 | 
				
			||||||
                [ classList
 | 
					                [ classList
 | 
				
			||||||
                    [ ( "ui right corner label", True )
 | 
					                    [ ( "ui right corner label", True )
 | 
				
			||||||
                    , ( newColor, True )
 | 
					                    , ( cardColor, True )
 | 
				
			||||||
                    , ( "invisible", isConfirmed )
 | 
					                    , ( "invisible", isConfirmed )
 | 
				
			||||||
                    ]
 | 
					                    ]
 | 
				
			||||||
                , title "New"
 | 
					                , title "New"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										693
									
								
								modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										693
									
								
								modules/webapp/src/main/elm/Comp/ItemDetail/EditMenu.elm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,693 @@
 | 
				
			|||||||
 | 
					module Comp.ItemDetail.EditMenu exposing
 | 
				
			||||||
 | 
					    ( Model
 | 
				
			||||||
 | 
					    , Msg
 | 
				
			||||||
 | 
					    , SaveNameState(..)
 | 
				
			||||||
 | 
					    , defaultViewConfig
 | 
				
			||||||
 | 
					    , init
 | 
				
			||||||
 | 
					    , loadModel
 | 
				
			||||||
 | 
					    , update
 | 
				
			||||||
 | 
					    , view
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Api
 | 
				
			||||||
 | 
					import Api.Model.EquipmentList exposing (EquipmentList)
 | 
				
			||||||
 | 
					import Api.Model.FolderItem exposing (FolderItem)
 | 
				
			||||||
 | 
					import Api.Model.FolderList exposing (FolderList)
 | 
				
			||||||
 | 
					import Api.Model.IdName exposing (IdName)
 | 
				
			||||||
 | 
					import Api.Model.ItemProposals exposing (ItemProposals)
 | 
				
			||||||
 | 
					import Api.Model.ReferenceList exposing (ReferenceList)
 | 
				
			||||||
 | 
					import Api.Model.Tag exposing (Tag)
 | 
				
			||||||
 | 
					import Api.Model.TagList exposing (TagList)
 | 
				
			||||||
 | 
					import Comp.DatePicker
 | 
				
			||||||
 | 
					import Comp.DetailEdit
 | 
				
			||||||
 | 
					import Comp.Dropdown exposing (isDropdownChangeMsg)
 | 
				
			||||||
 | 
					import Comp.ItemDetail.FormChange exposing (FormChange(..))
 | 
				
			||||||
 | 
					import Data.Direction exposing (Direction)
 | 
				
			||||||
 | 
					import Data.Fields
 | 
				
			||||||
 | 
					import Data.Flags exposing (Flags)
 | 
				
			||||||
 | 
					import Data.Icons as Icons
 | 
				
			||||||
 | 
					import Data.UiSettings exposing (UiSettings)
 | 
				
			||||||
 | 
					import DatePicker exposing (DatePicker)
 | 
				
			||||||
 | 
					import Html exposing (..)
 | 
				
			||||||
 | 
					import Html.Attributes exposing (..)
 | 
				
			||||||
 | 
					import Html.Events exposing (onClick, onInput)
 | 
				
			||||||
 | 
					import Http
 | 
				
			||||||
 | 
					import Markdown
 | 
				
			||||||
 | 
					import Page exposing (Page(..))
 | 
				
			||||||
 | 
					import Task
 | 
				
			||||||
 | 
					import Throttle exposing (Throttle)
 | 
				
			||||||
 | 
					import Time
 | 
				
			||||||
 | 
					import Util.Folder exposing (mkFolderOption)
 | 
				
			||||||
 | 
					import Util.List
 | 
				
			||||||
 | 
					import Util.Maybe
 | 
				
			||||||
 | 
					import Util.Tag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SaveNameState
 | 
				
			||||||
 | 
					    = Saving
 | 
				
			||||||
 | 
					    | SaveSuccess
 | 
				
			||||||
 | 
					    | SaveFailed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type alias Model =
 | 
				
			||||||
 | 
					    { tagModel : Comp.Dropdown.Model Tag
 | 
				
			||||||
 | 
					    , nameModel : String
 | 
				
			||||||
 | 
					    , nameSaveThrottle : Throttle Msg
 | 
				
			||||||
 | 
					    , folderModel : Comp.Dropdown.Model IdName
 | 
				
			||||||
 | 
					    , allFolders : List FolderItem
 | 
				
			||||||
 | 
					    , directionModel : Comp.Dropdown.Model Direction
 | 
				
			||||||
 | 
					    , itemDatePicker : DatePicker
 | 
				
			||||||
 | 
					    , itemDate : Maybe Int
 | 
				
			||||||
 | 
					    , itemProposals : ItemProposals
 | 
				
			||||||
 | 
					    , dueDate : Maybe Int
 | 
				
			||||||
 | 
					    , dueDatePicker : DatePicker
 | 
				
			||||||
 | 
					    , corrOrgModel : Comp.Dropdown.Model IdName
 | 
				
			||||||
 | 
					    , corrPersonModel : Comp.Dropdown.Model IdName
 | 
				
			||||||
 | 
					    , concPersonModel : Comp.Dropdown.Model IdName
 | 
				
			||||||
 | 
					    , concEquipModel : Comp.Dropdown.Model IdName
 | 
				
			||||||
 | 
					    , modalEdit : Maybe Comp.DetailEdit.Model
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Msg
 | 
				
			||||||
 | 
					    = ItemDatePickerMsg Comp.DatePicker.Msg
 | 
				
			||||||
 | 
					    | DueDatePickerMsg Comp.DatePicker.Msg
 | 
				
			||||||
 | 
					    | SetName String
 | 
				
			||||||
 | 
					    | SaveName
 | 
				
			||||||
 | 
					    | UpdateThrottle
 | 
				
			||||||
 | 
					    | RemoveDueDate
 | 
				
			||||||
 | 
					    | RemoveDate
 | 
				
			||||||
 | 
					    | FolderDropdownMsg (Comp.Dropdown.Msg IdName)
 | 
				
			||||||
 | 
					    | TagDropdownMsg (Comp.Dropdown.Msg Tag)
 | 
				
			||||||
 | 
					    | DirDropdownMsg (Comp.Dropdown.Msg Direction)
 | 
				
			||||||
 | 
					    | OrgDropdownMsg (Comp.Dropdown.Msg IdName)
 | 
				
			||||||
 | 
					    | CorrPersonMsg (Comp.Dropdown.Msg IdName)
 | 
				
			||||||
 | 
					    | ConcPersonMsg (Comp.Dropdown.Msg IdName)
 | 
				
			||||||
 | 
					    | ConcEquipMsg (Comp.Dropdown.Msg IdName)
 | 
				
			||||||
 | 
					    | GetTagsResp (Result Http.Error TagList)
 | 
				
			||||||
 | 
					    | GetOrgResp (Result Http.Error ReferenceList)
 | 
				
			||||||
 | 
					    | GetPersonResp (Result Http.Error ReferenceList)
 | 
				
			||||||
 | 
					    | GetEquipResp (Result Http.Error EquipmentList)
 | 
				
			||||||
 | 
					    | GetFolderResp (Result Http.Error FolderList)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					init : Model
 | 
				
			||||||
 | 
					init =
 | 
				
			||||||
 | 
					    { tagModel =
 | 
				
			||||||
 | 
					        Util.Tag.makeDropdownModel
 | 
				
			||||||
 | 
					    , directionModel =
 | 
				
			||||||
 | 
					        Comp.Dropdown.makeSingleList
 | 
				
			||||||
 | 
					            { makeOption =
 | 
				
			||||||
 | 
					                \entry ->
 | 
				
			||||||
 | 
					                    { value = Data.Direction.toString entry
 | 
				
			||||||
 | 
					                    , text = Data.Direction.toString entry
 | 
				
			||||||
 | 
					                    , additional = ""
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					            , options = Data.Direction.all
 | 
				
			||||||
 | 
					            , placeholder = "Choose a direction…"
 | 
				
			||||||
 | 
					            , selected = Nothing
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    , corrOrgModel =
 | 
				
			||||||
 | 
					        Comp.Dropdown.makeSingle
 | 
				
			||||||
 | 
					            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
 | 
				
			||||||
 | 
					            , placeholder = ""
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    , corrPersonModel =
 | 
				
			||||||
 | 
					        Comp.Dropdown.makeSingle
 | 
				
			||||||
 | 
					            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
 | 
				
			||||||
 | 
					            , placeholder = ""
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    , concPersonModel =
 | 
				
			||||||
 | 
					        Comp.Dropdown.makeSingle
 | 
				
			||||||
 | 
					            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
 | 
				
			||||||
 | 
					            , placeholder = ""
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    , concEquipModel =
 | 
				
			||||||
 | 
					        Comp.Dropdown.makeSingle
 | 
				
			||||||
 | 
					            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
 | 
				
			||||||
 | 
					            , placeholder = ""
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    , folderModel =
 | 
				
			||||||
 | 
					        Comp.Dropdown.makeSingle
 | 
				
			||||||
 | 
					            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
 | 
				
			||||||
 | 
					            , placeholder = ""
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    , allFolders = []
 | 
				
			||||||
 | 
					    , nameModel = ""
 | 
				
			||||||
 | 
					    , nameSaveThrottle = Throttle.create 1
 | 
				
			||||||
 | 
					    , itemDatePicker = Comp.DatePicker.emptyModel
 | 
				
			||||||
 | 
					    , itemDate = Nothing
 | 
				
			||||||
 | 
					    , itemProposals = Api.Model.ItemProposals.empty
 | 
				
			||||||
 | 
					    , dueDate = Nothing
 | 
				
			||||||
 | 
					    , dueDatePicker = Comp.DatePicker.emptyModel
 | 
				
			||||||
 | 
					    , modalEdit = Nothing
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					loadModel : Flags -> Cmd Msg
 | 
				
			||||||
 | 
					loadModel flags =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        ( _, dpc ) =
 | 
				
			||||||
 | 
					            Comp.DatePicker.init
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    Cmd.batch
 | 
				
			||||||
 | 
					        [ Api.getTags flags "" GetTagsResp
 | 
				
			||||||
 | 
					        , Api.getOrgLight flags GetOrgResp
 | 
				
			||||||
 | 
					        , Api.getPersonsLight flags GetPersonResp
 | 
				
			||||||
 | 
					        , Api.getEquipments flags "" GetEquipResp
 | 
				
			||||||
 | 
					        , Api.getFolders flags "" False GetFolderResp
 | 
				
			||||||
 | 
					        , Cmd.map ItemDatePickerMsg dpc
 | 
				
			||||||
 | 
					        , Cmd.map DueDatePickerMsg dpc
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					isFolderMember : Model -> Bool
 | 
				
			||||||
 | 
					isFolderMember model =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        selected =
 | 
				
			||||||
 | 
					            Comp.Dropdown.getSelected model.folderModel
 | 
				
			||||||
 | 
					                |> List.head
 | 
				
			||||||
 | 
					                |> Maybe.map .id
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    Util.Folder.isFolderMember model.allFolders selected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- Update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type alias UpdateResult =
 | 
				
			||||||
 | 
					    { model : Model
 | 
				
			||||||
 | 
					    , cmd : Cmd Msg
 | 
				
			||||||
 | 
					    , sub : Sub Msg
 | 
				
			||||||
 | 
					    , change : FormChange
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					resultNoCmd : FormChange -> Model -> UpdateResult
 | 
				
			||||||
 | 
					resultNoCmd change model =
 | 
				
			||||||
 | 
					    UpdateResult model Cmd.none Sub.none change
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					resultNone : Model -> UpdateResult
 | 
				
			||||||
 | 
					resultNone model =
 | 
				
			||||||
 | 
					    resultNoCmd NoFormChange model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					update : Flags -> Msg -> Model -> UpdateResult
 | 
				
			||||||
 | 
					update flags msg model =
 | 
				
			||||||
 | 
					    case msg of
 | 
				
			||||||
 | 
					        TagDropdownMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( m2, _ ) =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.update m model.tagModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                newModel =
 | 
				
			||||||
 | 
					                    { model | tagModel = m2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                change =
 | 
				
			||||||
 | 
					                    if isDropdownChangeMsg m then
 | 
				
			||||||
 | 
					                        Comp.Dropdown.getSelected newModel.tagModel
 | 
				
			||||||
 | 
					                            |> Util.List.distinct
 | 
				
			||||||
 | 
					                            |> List.map (\t -> IdName t.id t.name)
 | 
				
			||||||
 | 
					                            |> ReferenceList
 | 
				
			||||||
 | 
					                            |> TagChange
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        NoFormChange
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            resultNoCmd change newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetTagsResp (Ok tags) ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                tagList =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.SetOptions tags.items
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            update flags (TagDropdownMsg tagList) model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetTagsResp (Err _) ->
 | 
				
			||||||
 | 
					            resultNone model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        FolderDropdownMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( m2, _ ) =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.update m model.folderModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                newModel =
 | 
				
			||||||
 | 
					                    { model | folderModel = m2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                idref =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.getSelected m2 |> List.head
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                change =
 | 
				
			||||||
 | 
					                    if isDropdownChangeMsg m then
 | 
				
			||||||
 | 
					                        FolderChange idref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        NoFormChange
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            resultNoCmd change newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetFolderResp (Ok fs) ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                model_ =
 | 
				
			||||||
 | 
					                    { model
 | 
				
			||||||
 | 
					                        | allFolders = fs.items
 | 
				
			||||||
 | 
					                        , folderModel =
 | 
				
			||||||
 | 
					                            Comp.Dropdown.setMkOption
 | 
				
			||||||
 | 
					                                (mkFolderOption flags fs.items)
 | 
				
			||||||
 | 
					                                model.folderModel
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                mkIdName fitem =
 | 
				
			||||||
 | 
					                    IdName fitem.id fitem.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                opts =
 | 
				
			||||||
 | 
					                    fs.items
 | 
				
			||||||
 | 
					                        |> List.map mkIdName
 | 
				
			||||||
 | 
					                        |> Comp.Dropdown.SetOptions
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            update flags (FolderDropdownMsg opts) model_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetFolderResp (Err _) ->
 | 
				
			||||||
 | 
					            resultNone model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DirDropdownMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( m2, _ ) =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.update m model.directionModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                newModel =
 | 
				
			||||||
 | 
					                    { model | directionModel = m2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                change =
 | 
				
			||||||
 | 
					                    if isDropdownChangeMsg m then
 | 
				
			||||||
 | 
					                        let
 | 
				
			||||||
 | 
					                            dir =
 | 
				
			||||||
 | 
					                                Comp.Dropdown.getSelected m2 |> List.head
 | 
				
			||||||
 | 
					                        in
 | 
				
			||||||
 | 
					                        case dir of
 | 
				
			||||||
 | 
					                            Just d ->
 | 
				
			||||||
 | 
					                                DirectionChange d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Nothing ->
 | 
				
			||||||
 | 
					                                NoFormChange
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        NoFormChange
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            resultNoCmd change newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        OrgDropdownMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( m2, _ ) =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.update m model.corrOrgModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                newModel =
 | 
				
			||||||
 | 
					                    { model | corrOrgModel = m2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                idref =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.getSelected m2 |> List.head
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                change =
 | 
				
			||||||
 | 
					                    if isDropdownChangeMsg m then
 | 
				
			||||||
 | 
					                        OrgChange idref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        NoFormChange
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            resultNoCmd change newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetOrgResp (Ok orgs) ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                opts =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.SetOptions orgs.items
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            update flags (OrgDropdownMsg opts) model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetOrgResp (Err _) ->
 | 
				
			||||||
 | 
					            resultNone model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        CorrPersonMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( m2, _ ) =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.update m model.corrPersonModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                newModel =
 | 
				
			||||||
 | 
					                    { model | corrPersonModel = m2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                idref =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.getSelected m2 |> List.head
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                change =
 | 
				
			||||||
 | 
					                    if isDropdownChangeMsg m then
 | 
				
			||||||
 | 
					                        CorrPersonChange idref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        NoFormChange
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            resultNoCmd change newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ConcPersonMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( m2, _ ) =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.update m model.concPersonModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                newModel =
 | 
				
			||||||
 | 
					                    { model | concPersonModel = m2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                idref =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.getSelected m2 |> List.head
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                change =
 | 
				
			||||||
 | 
					                    if isDropdownChangeMsg m then
 | 
				
			||||||
 | 
					                        ConcPersonChange idref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        NoFormChange
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            resultNoCmd change newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetPersonResp (Ok ps) ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                opts =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.SetOptions ps.items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                res1 =
 | 
				
			||||||
 | 
					                    update flags (CorrPersonMsg opts) model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                res2 =
 | 
				
			||||||
 | 
					                    update flags (ConcPersonMsg opts) res1.model
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            res2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetPersonResp (Err _) ->
 | 
				
			||||||
 | 
					            resultNone model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ConcEquipMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( m2, _ ) =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.update m model.concEquipModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                newModel =
 | 
				
			||||||
 | 
					                    { model | concEquipModel = m2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                idref =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.getSelected m2 |> List.head
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                change =
 | 
				
			||||||
 | 
					                    if isDropdownChangeMsg m then
 | 
				
			||||||
 | 
					                        EquipChange idref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        NoFormChange
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            resultNoCmd change newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetEquipResp (Ok equips) ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                opts =
 | 
				
			||||||
 | 
					                    Comp.Dropdown.SetOptions
 | 
				
			||||||
 | 
					                        (List.map (\e -> IdName e.id e.name)
 | 
				
			||||||
 | 
					                            equips.items
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            update flags (ConcEquipMsg opts) model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GetEquipResp (Err _) ->
 | 
				
			||||||
 | 
					            resultNone model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ItemDatePickerMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( dp, event ) =
 | 
				
			||||||
 | 
					                    Comp.DatePicker.updateDefault m model.itemDatePicker
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            case event of
 | 
				
			||||||
 | 
					                DatePicker.Picked date ->
 | 
				
			||||||
 | 
					                    let
 | 
				
			||||||
 | 
					                        newModel =
 | 
				
			||||||
 | 
					                            { model | itemDatePicker = dp, itemDate = Just (Comp.DatePicker.midOfDay date) }
 | 
				
			||||||
 | 
					                    in
 | 
				
			||||||
 | 
					                    resultNoCmd (ItemDateChange newModel.itemDate) newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    resultNone { model | itemDatePicker = dp }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RemoveDate ->
 | 
				
			||||||
 | 
					            resultNoCmd (ItemDateChange Nothing) { model | itemDate = Nothing }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DueDatePickerMsg m ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( dp, event ) =
 | 
				
			||||||
 | 
					                    Comp.DatePicker.updateDefault m model.dueDatePicker
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            case event of
 | 
				
			||||||
 | 
					                DatePicker.Picked date ->
 | 
				
			||||||
 | 
					                    let
 | 
				
			||||||
 | 
					                        newModel =
 | 
				
			||||||
 | 
					                            { model | dueDatePicker = dp, dueDate = Just (Comp.DatePicker.midOfDay date) }
 | 
				
			||||||
 | 
					                    in
 | 
				
			||||||
 | 
					                    resultNoCmd (DueDateChange newModel.dueDate) newModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    resultNone { model | dueDatePicker = dp }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RemoveDueDate ->
 | 
				
			||||||
 | 
					            resultNoCmd (DueDateChange Nothing) { model | dueDate = Nothing }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SetName str ->
 | 
				
			||||||
 | 
					            case Util.Maybe.fromString str of
 | 
				
			||||||
 | 
					                Just newName ->
 | 
				
			||||||
 | 
					                    let
 | 
				
			||||||
 | 
					                        cmd_ =
 | 
				
			||||||
 | 
					                            Task.succeed ()
 | 
				
			||||||
 | 
					                                |> Task.perform (\_ -> SaveName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        ( newThrottle, cmd ) =
 | 
				
			||||||
 | 
					                            Throttle.try cmd_ model.nameSaveThrottle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        newModel =
 | 
				
			||||||
 | 
					                            { model
 | 
				
			||||||
 | 
					                                | nameSaveThrottle = newThrottle
 | 
				
			||||||
 | 
					                                , nameModel = newName
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        sub =
 | 
				
			||||||
 | 
					                            nameThrottleSub newModel
 | 
				
			||||||
 | 
					                    in
 | 
				
			||||||
 | 
					                    UpdateResult newModel cmd sub NoFormChange
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Nothing ->
 | 
				
			||||||
 | 
					                    resultNone { model | nameModel = str }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SaveName ->
 | 
				
			||||||
 | 
					            case Util.Maybe.fromString model.nameModel of
 | 
				
			||||||
 | 
					                Just n ->
 | 
				
			||||||
 | 
					                    resultNoCmd (NameChange n) model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Nothing ->
 | 
				
			||||||
 | 
					                    resultNone model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        UpdateThrottle ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( newThrottle, cmd ) =
 | 
				
			||||||
 | 
					                    Throttle.update model.nameSaveThrottle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                newModel =
 | 
				
			||||||
 | 
					                    { model | nameSaveThrottle = newThrottle }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                sub =
 | 
				
			||||||
 | 
					                    nameThrottleSub newModel
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            UpdateResult newModel cmd sub NoFormChange
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nameThrottleSub : Model -> Sub Msg
 | 
				
			||||||
 | 
					nameThrottleSub model =
 | 
				
			||||||
 | 
					    Throttle.ifNeeded
 | 
				
			||||||
 | 
					        (Time.every 400 (\_ -> UpdateThrottle))
 | 
				
			||||||
 | 
					        model.nameSaveThrottle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- View
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type alias ViewConfig =
 | 
				
			||||||
 | 
					    { menuClass : String
 | 
				
			||||||
 | 
					    , nameState : SaveNameState
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defaultViewConfig : ViewConfig
 | 
				
			||||||
 | 
					defaultViewConfig =
 | 
				
			||||||
 | 
					    { menuClass = "ui vertical segment"
 | 
				
			||||||
 | 
					    , nameState = SaveSuccess
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					view : ViewConfig -> UiSettings -> Model -> Html Msg
 | 
				
			||||||
 | 
					view =
 | 
				
			||||||
 | 
					    renderEditForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					renderEditForm : ViewConfig -> UiSettings -> Model -> Html Msg
 | 
				
			||||||
 | 
					renderEditForm cfg settings model =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        fieldVisible field =
 | 
				
			||||||
 | 
					            Data.UiSettings.fieldVisible settings field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        optional fields html =
 | 
				
			||||||
 | 
					            if
 | 
				
			||||||
 | 
					                List.map fieldVisible fields
 | 
				
			||||||
 | 
					                    |> List.foldl (||) False
 | 
				
			||||||
 | 
					            then
 | 
				
			||||||
 | 
					                html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                span [ class "invisible hidden" ] []
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    div [ class cfg.menuClass ]
 | 
				
			||||||
 | 
					        [ div [ class "ui form warning" ]
 | 
				
			||||||
 | 
					            [ optional [ Data.Fields.Tag ] <|
 | 
				
			||||||
 | 
					                div [ class "field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.tagsIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Tags"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel)
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , div [ class " field" ]
 | 
				
			||||||
 | 
					                [ label [] [ text "Name" ]
 | 
				
			||||||
 | 
					                , div [ class "ui icon input" ]
 | 
				
			||||||
 | 
					                    [ input [ type_ "text", value model.nameModel, onInput SetName ] []
 | 
				
			||||||
 | 
					                    , i
 | 
				
			||||||
 | 
					                        [ classList
 | 
				
			||||||
 | 
					                            [ ( "green check icon", cfg.nameState == SaveSuccess )
 | 
				
			||||||
 | 
					                            , ( "red exclamation triangle icon", cfg.nameState == SaveFailed )
 | 
				
			||||||
 | 
					                            , ( "sync loading icon", cfg.nameState == Saving )
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                        []
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.Folder ] <|
 | 
				
			||||||
 | 
					                div [ class "field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.folderIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Folder"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , Html.map FolderDropdownMsg (Comp.Dropdown.view settings model.folderModel)
 | 
				
			||||||
 | 
					                    , div
 | 
				
			||||||
 | 
					                        [ classList
 | 
				
			||||||
 | 
					                            [ ( "ui warning message", True )
 | 
				
			||||||
 | 
					                            , ( "hidden", isFolderMember model )
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                        [ Markdown.toHtml [] """
 | 
				
			||||||
 | 
					You are **not a member** of this folder. This item will be **hidden**
 | 
				
			||||||
 | 
					from any search now. Use a folder where you are a member of to make this
 | 
				
			||||||
 | 
					item visible. This message will disappear then.
 | 
				
			||||||
 | 
					                      """
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.Direction ] <|
 | 
				
			||||||
 | 
					                div [ class "field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.directionIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Direction"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , Html.map DirDropdownMsg (Comp.Dropdown.view settings model.directionModel)
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.Date ] <|
 | 
				
			||||||
 | 
					                div [ class "field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.dateIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Date"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , div [ class "ui action input" ]
 | 
				
			||||||
 | 
					                        [ Html.map ItemDatePickerMsg
 | 
				
			||||||
 | 
					                            (Comp.DatePicker.viewTime
 | 
				
			||||||
 | 
					                                model.itemDate
 | 
				
			||||||
 | 
					                                actionInputDatePicker
 | 
				
			||||||
 | 
					                                model.itemDatePicker
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        , a [ class "ui icon button", href "", onClick RemoveDate ]
 | 
				
			||||||
 | 
					                            [ i [ class "trash alternate outline icon" ] []
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.DueDate ] <|
 | 
				
			||||||
 | 
					                div [ class " field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.dueDateIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Due Date"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , div [ class "ui action input" ]
 | 
				
			||||||
 | 
					                        [ Html.map DueDatePickerMsg
 | 
				
			||||||
 | 
					                            (Comp.DatePicker.viewTime
 | 
				
			||||||
 | 
					                                model.dueDate
 | 
				
			||||||
 | 
					                                actionInputDatePicker
 | 
				
			||||||
 | 
					                                model.dueDatePicker
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        , a [ class "ui icon button", href "", onClick RemoveDueDate ]
 | 
				
			||||||
 | 
					                            [ i [ class "trash alternate outline icon" ] [] ]
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.CorrOrg, Data.Fields.CorrPerson ] <|
 | 
				
			||||||
 | 
					                h4 [ class "ui dividing header" ]
 | 
				
			||||||
 | 
					                    [ Icons.correspondentIcon ""
 | 
				
			||||||
 | 
					                    , text "Correspondent"
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.CorrOrg ] <|
 | 
				
			||||||
 | 
					                div [ class "field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.organizationIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Organization"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , Html.map OrgDropdownMsg (Comp.Dropdown.view settings model.corrOrgModel)
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.CorrPerson ] <|
 | 
				
			||||||
 | 
					                div [ class "field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.personIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Person"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , Html.map CorrPersonMsg (Comp.Dropdown.view settings model.corrPersonModel)
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.ConcPerson, Data.Fields.ConcEquip ] <|
 | 
				
			||||||
 | 
					                h4 [ class "ui dividing header" ]
 | 
				
			||||||
 | 
					                    [ Icons.concernedIcon
 | 
				
			||||||
 | 
					                    , text "Concerning"
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.ConcPerson ] <|
 | 
				
			||||||
 | 
					                div [ class "field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.personIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Person"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , Html.map ConcPersonMsg (Comp.Dropdown.view settings model.concPersonModel)
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            , optional [ Data.Fields.ConcEquip ] <|
 | 
				
			||||||
 | 
					                div [ class "field" ]
 | 
				
			||||||
 | 
					                    [ label []
 | 
				
			||||||
 | 
					                        [ Icons.equipmentIcon "grey"
 | 
				
			||||||
 | 
					                        , text "Equipment"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , Html.map ConcEquipMsg (Comp.Dropdown.view settings model.concEquipModel)
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actionInputDatePicker : DatePicker.Settings
 | 
				
			||||||
 | 
					actionInputDatePicker =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        ds =
 | 
				
			||||||
 | 
					            Comp.DatePicker.defaultSettings
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    { ds | containerClassList = [ ( "ui action input", True ) ] }
 | 
				
			||||||
							
								
								
									
										118
									
								
								modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								modules/webapp/src/main/elm/Comp/ItemDetail/FormChange.elm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					module Comp.ItemDetail.FormChange exposing
 | 
				
			||||||
 | 
					    ( FormChange(..)
 | 
				
			||||||
 | 
					    , multiUpdate
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Api
 | 
				
			||||||
 | 
					import Api.Model.BasicResult exposing (BasicResult)
 | 
				
			||||||
 | 
					import Api.Model.IdName exposing (IdName)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndDate exposing (ItemsAndDate)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndDirection exposing (ItemsAndDirection)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndName exposing (ItemsAndName)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndRef exposing (ItemsAndRef)
 | 
				
			||||||
 | 
					import Api.Model.ItemsAndRefs exposing (ItemsAndRefs)
 | 
				
			||||||
 | 
					import Api.Model.ReferenceList exposing (ReferenceList)
 | 
				
			||||||
 | 
					import Data.Direction exposing (Direction)
 | 
				
			||||||
 | 
					import Data.Flags exposing (Flags)
 | 
				
			||||||
 | 
					import Http
 | 
				
			||||||
 | 
					import Set exposing (Set)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FormChange
 | 
				
			||||||
 | 
					    = NoFormChange
 | 
				
			||||||
 | 
					    | TagChange ReferenceList
 | 
				
			||||||
 | 
					    | FolderChange (Maybe IdName)
 | 
				
			||||||
 | 
					    | DirectionChange Direction
 | 
				
			||||||
 | 
					    | OrgChange (Maybe IdName)
 | 
				
			||||||
 | 
					    | CorrPersonChange (Maybe IdName)
 | 
				
			||||||
 | 
					    | ConcPersonChange (Maybe IdName)
 | 
				
			||||||
 | 
					    | EquipChange (Maybe IdName)
 | 
				
			||||||
 | 
					    | ItemDateChange (Maybe Int)
 | 
				
			||||||
 | 
					    | DueDateChange (Maybe Int)
 | 
				
			||||||
 | 
					    | NameChange String
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					multiUpdate :
 | 
				
			||||||
 | 
					    Flags
 | 
				
			||||||
 | 
					    -> Set String
 | 
				
			||||||
 | 
					    -> FormChange
 | 
				
			||||||
 | 
					    -> (Result Http.Error BasicResult -> msg)
 | 
				
			||||||
 | 
					    -> Cmd msg
 | 
				
			||||||
 | 
					multiUpdate flags ids change receive =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        items =
 | 
				
			||||||
 | 
					            Set.toList ids
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    case change of
 | 
				
			||||||
 | 
					        TagChange tags ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndRefs items (List.map .id tags.items)
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setTagsMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        NameChange name ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndName items name
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setNameMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        FolderChange id ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndRef items (Maybe.map .id id)
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setFolderMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DirectionChange dir ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndDirection items (Data.Direction.toString dir)
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setDirectionMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ItemDateChange date ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndDate items date
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setDateMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DueDateChange date ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndDate items date
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setDueDateMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        OrgChange ref ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndRef items (Maybe.map .id ref)
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setCorrOrgMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        CorrPersonChange ref ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndRef items (Maybe.map .id ref)
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setCorrPersonMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ConcPersonChange ref ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndRef items (Maybe.map .id ref)
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setConcPersonMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        EquipChange ref ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                data =
 | 
				
			||||||
 | 
					                    ItemsAndRef items (Maybe.map .id ref)
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            Api.setConcEquipmentMultiple flags data receive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        NoFormChange ->
 | 
				
			||||||
 | 
					            Cmd.none
 | 
				
			||||||
@@ -24,7 +24,6 @@ import File exposing (File)
 | 
				
			|||||||
import Html exposing (..)
 | 
					import Html exposing (..)
 | 
				
			||||||
import Html.Attributes exposing (..)
 | 
					import Html.Attributes exposing (..)
 | 
				
			||||||
import Html.Events exposing (onCheck, onClick, onInput)
 | 
					import Html.Events exposing (onCheck, onClick, onInput)
 | 
				
			||||||
import Html5.DragDrop as DD
 | 
					 | 
				
			||||||
import Markdown
 | 
					import Markdown
 | 
				
			||||||
import Page exposing (Page(..))
 | 
					import Page exposing (Page(..))
 | 
				
			||||||
import Set
 | 
					import Set
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,8 @@ module Comp.YesNoDimmer exposing
 | 
				
			|||||||
    , defaultSettings
 | 
					    , defaultSettings
 | 
				
			||||||
    , disable
 | 
					    , disable
 | 
				
			||||||
    , emptyModel
 | 
					    , emptyModel
 | 
				
			||||||
 | 
					    , initActive
 | 
				
			||||||
 | 
					    , initInactive
 | 
				
			||||||
    , update
 | 
					    , update
 | 
				
			||||||
    , view
 | 
					    , view
 | 
				
			||||||
    , view2
 | 
					    , view2
 | 
				
			||||||
@@ -27,6 +29,18 @@ emptyModel =
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					initInactive : Model
 | 
				
			||||||
 | 
					initInactive =
 | 
				
			||||||
 | 
					    { active = False
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					initActive : Model
 | 
				
			||||||
 | 
					initActive =
 | 
				
			||||||
 | 
					    { active = True
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Msg
 | 
					type Msg
 | 
				
			||||||
    = Activate
 | 
					    = Activate
 | 
				
			||||||
    | Disable
 | 
					    | Disable
 | 
				
			||||||
@@ -40,6 +54,7 @@ type alias Settings =
 | 
				
			|||||||
    , confirmButton : String
 | 
					    , confirmButton : String
 | 
				
			||||||
    , cancelButton : String
 | 
					    , cancelButton : String
 | 
				
			||||||
    , invertedDimmer : Bool
 | 
					    , invertedDimmer : Bool
 | 
				
			||||||
 | 
					    , extraClass : String
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,6 +66,7 @@ defaultSettings =
 | 
				
			|||||||
    , confirmButton = "Yes, do it!"
 | 
					    , confirmButton = "Yes, do it!"
 | 
				
			||||||
    , cancelButton = "No"
 | 
					    , cancelButton = "No"
 | 
				
			||||||
    , invertedDimmer = False
 | 
					    , invertedDimmer = False
 | 
				
			||||||
 | 
					    , extraClass = ""
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,6 +103,7 @@ view2 active settings model =
 | 
				
			|||||||
    div
 | 
					    div
 | 
				
			||||||
        [ classList
 | 
					        [ classList
 | 
				
			||||||
            [ ( "ui dimmer", True )
 | 
					            [ ( "ui dimmer", True )
 | 
				
			||||||
 | 
					            , ( settings.extraClass, True )
 | 
				
			||||||
            , ( "inverted", settings.invertedDimmer )
 | 
					            , ( "inverted", settings.invertedDimmer )
 | 
				
			||||||
            , ( "active", active && model.active )
 | 
					            , ( "active", active && model.active )
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								modules/webapp/src/main/elm/Data/ItemSelection.elm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								modules/webapp/src/main/elm/Data/ItemSelection.elm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					module Data.ItemSelection exposing
 | 
				
			||||||
 | 
					    ( ItemSelection(..)
 | 
				
			||||||
 | 
					    , isActive
 | 
				
			||||||
 | 
					    , isSelected
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Set exposing (Set)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ItemSelection
 | 
				
			||||||
 | 
					    = Inactive
 | 
				
			||||||
 | 
					    | Active (Set String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					isSelected : String -> ItemSelection -> Bool
 | 
				
			||||||
 | 
					isSelected id set =
 | 
				
			||||||
 | 
					    case set of
 | 
				
			||||||
 | 
					        Inactive ->
 | 
				
			||||||
 | 
					            False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Active ids ->
 | 
				
			||||||
 | 
					            Set.member id ids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					isActive : ItemSelection -> Bool
 | 
				
			||||||
 | 
					isActive sel =
 | 
				
			||||||
 | 
					    case sel of
 | 
				
			||||||
 | 
					        Active _ ->
 | 
				
			||||||
 | 
					            True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Inactive ->
 | 
				
			||||||
 | 
					            False
 | 
				
			||||||
@@ -1,12 +1,16 @@
 | 
				
			|||||||
module Data.Items exposing
 | 
					module Data.Items exposing
 | 
				
			||||||
    ( concat
 | 
					    ( concat
 | 
				
			||||||
    , first
 | 
					    , first
 | 
				
			||||||
 | 
					    , idSet
 | 
				
			||||||
    , length
 | 
					    , length
 | 
				
			||||||
 | 
					    , replaceIn
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Api.Model.ItemLight exposing (ItemLight)
 | 
					import Api.Model.ItemLight exposing (ItemLight)
 | 
				
			||||||
import Api.Model.ItemLightGroup exposing (ItemLightGroup)
 | 
					import Api.Model.ItemLightGroup exposing (ItemLightGroup)
 | 
				
			||||||
import Api.Model.ItemLightList exposing (ItemLightList)
 | 
					import Api.Model.ItemLightList exposing (ItemLightList)
 | 
				
			||||||
 | 
					import Dict exposing (Dict)
 | 
				
			||||||
 | 
					import Set exposing (Set)
 | 
				
			||||||
import Util.List
 | 
					import Util.List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -65,3 +69,54 @@ lastGroup : ItemLightList -> Maybe ItemLightGroup
 | 
				
			|||||||
lastGroup list =
 | 
					lastGroup list =
 | 
				
			||||||
    List.reverse list.groups
 | 
					    List.reverse list.groups
 | 
				
			||||||
        |> List.head
 | 
					        |> List.head
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					idSet : ItemLightList -> Set String
 | 
				
			||||||
 | 
					idSet items =
 | 
				
			||||||
 | 
					    List.map idSetGroup items.groups
 | 
				
			||||||
 | 
					        |> List.foldl Set.union Set.empty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					idSetGroup : ItemLightGroup -> Set String
 | 
				
			||||||
 | 
					idSetGroup group =
 | 
				
			||||||
 | 
					    List.map .id group.items
 | 
				
			||||||
 | 
					        |> Set.fromList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					replaceIn : ItemLightList -> ItemLightList -> ItemLightList
 | 
				
			||||||
 | 
					replaceIn origin replacements =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        newItems =
 | 
				
			||||||
 | 
					            mkItemDict replacements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        replaceItem item =
 | 
				
			||||||
 | 
					            case Dict.get item.id newItems of
 | 
				
			||||||
 | 
					                Just ni ->
 | 
				
			||||||
 | 
					                    ni
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Nothing ->
 | 
				
			||||||
 | 
					                    item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        replaceGroup g =
 | 
				
			||||||
 | 
					            List.map replaceItem g.items
 | 
				
			||||||
 | 
					                |> ItemLightGroup g.name
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    List.map replaceGroup origin.groups
 | 
				
			||||||
 | 
					        |> ItemLightList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- Helper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkItemDict : ItemLightList -> Dict String ItemLight
 | 
				
			||||||
 | 
					mkItemDict list =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        insertItems : Dict String ItemLight -> List ItemLight -> Dict String ItemLight
 | 
				
			||||||
 | 
					        insertItems dict items =
 | 
				
			||||||
 | 
					            List.foldl (\i -> \d -> Dict.insert i.id i d) dict items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        insertGroup dict groups =
 | 
				
			||||||
 | 
					            List.foldl (\g -> \d -> insertItems d g.items) dict groups
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    insertGroup Dict.empty list.groups
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,26 +2,36 @@ module Page.Home.Data exposing
 | 
				
			|||||||
    ( Model
 | 
					    ( Model
 | 
				
			||||||
    , Msg(..)
 | 
					    , Msg(..)
 | 
				
			||||||
    , SearchType(..)
 | 
					    , SearchType(..)
 | 
				
			||||||
 | 
					    , SelectActionMode(..)
 | 
				
			||||||
 | 
					    , SelectViewModel
 | 
				
			||||||
 | 
					    , ViewMode(..)
 | 
				
			||||||
    , defaultSearchType
 | 
					    , defaultSearchType
 | 
				
			||||||
    , doSearchCmd
 | 
					    , doSearchCmd
 | 
				
			||||||
    , init
 | 
					    , init
 | 
				
			||||||
 | 
					    , initSelectViewModel
 | 
				
			||||||
    , itemNav
 | 
					    , itemNav
 | 
				
			||||||
 | 
					    , menuCollapsed
 | 
				
			||||||
    , resultsBelowLimit
 | 
					    , resultsBelowLimit
 | 
				
			||||||
    , searchTypeString
 | 
					    , searchTypeString
 | 
				
			||||||
 | 
					    , selectActive
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Api
 | 
					import Api
 | 
				
			||||||
 | 
					import Api.Model.BasicResult exposing (BasicResult)
 | 
				
			||||||
import Api.Model.ItemLightList exposing (ItemLightList)
 | 
					import Api.Model.ItemLightList exposing (ItemLightList)
 | 
				
			||||||
import Api.Model.ItemSearch
 | 
					import Api.Model.ItemSearch
 | 
				
			||||||
import Browser.Dom as Dom
 | 
					import Browser.Dom as Dom
 | 
				
			||||||
import Comp.FixedDropdown
 | 
					import Comp.FixedDropdown
 | 
				
			||||||
import Comp.ItemCardList
 | 
					import Comp.ItemCardList
 | 
				
			||||||
 | 
					import Comp.ItemDetail.EditMenu
 | 
				
			||||||
import Comp.SearchMenu
 | 
					import Comp.SearchMenu
 | 
				
			||||||
 | 
					import Comp.YesNoDimmer
 | 
				
			||||||
import Data.Flags exposing (Flags)
 | 
					import Data.Flags exposing (Flags)
 | 
				
			||||||
import Data.ItemNav exposing (ItemNav)
 | 
					import Data.ItemNav exposing (ItemNav)
 | 
				
			||||||
import Data.Items
 | 
					import Data.Items
 | 
				
			||||||
import Data.UiSettings exposing (UiSettings)
 | 
					import Data.UiSettings exposing (UiSettings)
 | 
				
			||||||
import Http
 | 
					import Http
 | 
				
			||||||
 | 
					import Set exposing (Set)
 | 
				
			||||||
import Throttle exposing (Throttle)
 | 
					import Throttle exposing (Throttle)
 | 
				
			||||||
import Util.Html exposing (KeyCode(..))
 | 
					import Util.Html exposing (KeyCode(..))
 | 
				
			||||||
import Util.ItemDragDrop as DD
 | 
					import Util.ItemDragDrop as DD
 | 
				
			||||||
@@ -31,7 +41,7 @@ type alias Model =
 | 
				
			|||||||
    { searchMenuModel : Comp.SearchMenu.Model
 | 
					    { searchMenuModel : Comp.SearchMenu.Model
 | 
				
			||||||
    , itemListModel : Comp.ItemCardList.Model
 | 
					    , itemListModel : Comp.ItemCardList.Model
 | 
				
			||||||
    , searchInProgress : Bool
 | 
					    , searchInProgress : Bool
 | 
				
			||||||
    , menuCollapsed : Bool
 | 
					    , viewMode : ViewMode
 | 
				
			||||||
    , searchOffset : Int
 | 
					    , searchOffset : Int
 | 
				
			||||||
    , moreAvailable : Bool
 | 
					    , moreAvailable : Bool
 | 
				
			||||||
    , moreInProgress : Bool
 | 
					    , moreInProgress : Bool
 | 
				
			||||||
@@ -45,6 +55,29 @@ type alias Model =
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type alias SelectViewModel =
 | 
				
			||||||
 | 
					    { ids : Set String
 | 
				
			||||||
 | 
					    , action : SelectActionMode
 | 
				
			||||||
 | 
					    , deleteAllConfirm : Comp.YesNoDimmer.Model
 | 
				
			||||||
 | 
					    , editModel : Comp.ItemDetail.EditMenu.Model
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					initSelectViewModel : SelectViewModel
 | 
				
			||||||
 | 
					initSelectViewModel =
 | 
				
			||||||
 | 
					    { ids = Set.empty
 | 
				
			||||||
 | 
					    , action = NoneAction
 | 
				
			||||||
 | 
					    , deleteAllConfirm = Comp.YesNoDimmer.initActive
 | 
				
			||||||
 | 
					    , editModel = Comp.ItemDetail.EditMenu.init
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ViewMode
 | 
				
			||||||
 | 
					    = SimpleView
 | 
				
			||||||
 | 
					    | SearchView
 | 
				
			||||||
 | 
					    | SelectView SelectViewModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init : Flags -> Model
 | 
					init : Flags -> Model
 | 
				
			||||||
init flags =
 | 
					init flags =
 | 
				
			||||||
    let
 | 
					    let
 | 
				
			||||||
@@ -58,7 +91,6 @@ init flags =
 | 
				
			|||||||
    { searchMenuModel = Comp.SearchMenu.init
 | 
					    { searchMenuModel = Comp.SearchMenu.init
 | 
				
			||||||
    , itemListModel = Comp.ItemCardList.init
 | 
					    , itemListModel = Comp.ItemCardList.init
 | 
				
			||||||
    , searchInProgress = False
 | 
					    , searchInProgress = False
 | 
				
			||||||
    , menuCollapsed = True
 | 
					 | 
				
			||||||
    , searchOffset = 0
 | 
					    , searchOffset = 0
 | 
				
			||||||
    , moreAvailable = True
 | 
					    , moreAvailable = True
 | 
				
			||||||
    , moreInProgress = False
 | 
					    , moreInProgress = False
 | 
				
			||||||
@@ -72,6 +104,7 @@ init flags =
 | 
				
			|||||||
    , dragDropData =
 | 
					    , dragDropData =
 | 
				
			||||||
        DD.DragDropData DD.init Nothing
 | 
					        DD.DragDropData DD.init Nothing
 | 
				
			||||||
    , scrollToCard = Nothing
 | 
					    , scrollToCard = Nothing
 | 
				
			||||||
 | 
					    , viewMode = SimpleView
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,6 +117,32 @@ defaultSearchType flags =
 | 
				
			|||||||
        BasicSearch
 | 
					        BasicSearch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					menuCollapsed : Model -> Bool
 | 
				
			||||||
 | 
					menuCollapsed model =
 | 
				
			||||||
 | 
					    case model.viewMode of
 | 
				
			||||||
 | 
					        SimpleView ->
 | 
				
			||||||
 | 
					            True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SearchView ->
 | 
				
			||||||
 | 
					            False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SelectView _ ->
 | 
				
			||||||
 | 
					            False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					selectActive : Model -> Bool
 | 
				
			||||||
 | 
					selectActive model =
 | 
				
			||||||
 | 
					    case model.viewMode of
 | 
				
			||||||
 | 
					        SimpleView ->
 | 
				
			||||||
 | 
					            False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SearchView ->
 | 
				
			||||||
 | 
					            False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SelectView _ ->
 | 
				
			||||||
 | 
					            True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Msg
 | 
					type Msg
 | 
				
			||||||
    = Init
 | 
					    = Init
 | 
				
			||||||
    | SearchMenuMsg Comp.SearchMenu.Msg
 | 
					    | SearchMenuMsg Comp.SearchMenu.Msg
 | 
				
			||||||
@@ -93,6 +152,7 @@ type Msg
 | 
				
			|||||||
    | ItemSearchAddResp (Result Http.Error ItemLightList)
 | 
					    | ItemSearchAddResp (Result Http.Error ItemLightList)
 | 
				
			||||||
    | DoSearch
 | 
					    | DoSearch
 | 
				
			||||||
    | ToggleSearchMenu
 | 
					    | ToggleSearchMenu
 | 
				
			||||||
 | 
					    | ToggleSelectView
 | 
				
			||||||
    | LoadMore
 | 
					    | LoadMore
 | 
				
			||||||
    | UpdateThrottle
 | 
					    | UpdateThrottle
 | 
				
			||||||
    | SetBasicSearch String
 | 
					    | SetBasicSearch String
 | 
				
			||||||
@@ -101,6 +161,15 @@ type Msg
 | 
				
			|||||||
    | SetContentOnly String
 | 
					    | SetContentOnly String
 | 
				
			||||||
    | ScrollResult (Result Dom.Error ())
 | 
					    | ScrollResult (Result Dom.Error ())
 | 
				
			||||||
    | ClearItemDetailId
 | 
					    | ClearItemDetailId
 | 
				
			||||||
 | 
					    | SelectAllItems
 | 
				
			||||||
 | 
					    | SelectNoItems
 | 
				
			||||||
 | 
					    | RequestDeleteSelected
 | 
				
			||||||
 | 
					    | DeleteSelectedConfirmMsg Comp.YesNoDimmer.Msg
 | 
				
			||||||
 | 
					    | EditSelectedItems
 | 
				
			||||||
 | 
					    | EditMenuMsg Comp.ItemDetail.EditMenu.Msg
 | 
				
			||||||
 | 
					    | MultiUpdateResp (Result Http.Error BasicResult)
 | 
				
			||||||
 | 
					    | ReplaceChangedItemsResp (Result Http.Error ItemLightList)
 | 
				
			||||||
 | 
					    | DeleteAllResp (Result Http.Error BasicResult)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SearchType
 | 
					type SearchType
 | 
				
			||||||
@@ -109,6 +178,12 @@ type SearchType
 | 
				
			|||||||
    | ContentOnlySearch
 | 
					    | ContentOnlySearch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SelectActionMode
 | 
				
			||||||
 | 
					    = NoneAction
 | 
				
			||||||
 | 
					    | DeleteSelected
 | 
				
			||||||
 | 
					    | EditSelected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
searchTypeString : SearchType -> String
 | 
					searchTypeString : SearchType -> String
 | 
				
			||||||
searchTypeString st =
 | 
					searchTypeString st =
 | 
				
			||||||
    case st of
 | 
					    case st of
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,25 @@
 | 
				
			|||||||
module Page.Home.Update exposing (update)
 | 
					module Page.Home.Update exposing (update)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Api
 | 
				
			||||||
 | 
					import Api.Model.IdList exposing (IdList)
 | 
				
			||||||
 | 
					import Api.Model.ItemLightList exposing (ItemLightList)
 | 
				
			||||||
 | 
					import Api.Model.ItemSearch exposing (ItemSearch)
 | 
				
			||||||
import Browser.Navigation as Nav
 | 
					import Browser.Navigation as Nav
 | 
				
			||||||
import Comp.FixedDropdown
 | 
					import Comp.FixedDropdown
 | 
				
			||||||
import Comp.ItemCardList
 | 
					import Comp.ItemCardList
 | 
				
			||||||
 | 
					import Comp.ItemDetail.EditMenu
 | 
				
			||||||
 | 
					import Comp.ItemDetail.FormChange
 | 
				
			||||||
import Comp.SearchMenu
 | 
					import Comp.SearchMenu
 | 
				
			||||||
 | 
					import Comp.YesNoDimmer
 | 
				
			||||||
import Data.Flags exposing (Flags)
 | 
					import Data.Flags exposing (Flags)
 | 
				
			||||||
 | 
					import Data.ItemSelection
 | 
				
			||||||
 | 
					import Data.Items
 | 
				
			||||||
import Data.UiSettings exposing (UiSettings)
 | 
					import Data.UiSettings exposing (UiSettings)
 | 
				
			||||||
import Page exposing (Page(..))
 | 
					import Page exposing (Page(..))
 | 
				
			||||||
import Page.Home.Data exposing (..)
 | 
					import Page.Home.Data exposing (..)
 | 
				
			||||||
import Process
 | 
					import Process
 | 
				
			||||||
import Scroll
 | 
					import Scroll
 | 
				
			||||||
 | 
					import Set exposing (Set)
 | 
				
			||||||
import Task
 | 
					import Task
 | 
				
			||||||
import Throttle
 | 
					import Throttle
 | 
				
			||||||
import Time
 | 
					import Time
 | 
				
			||||||
@@ -82,10 +92,19 @@ update mId key flags settings msg model =
 | 
				
			|||||||
                        flags
 | 
					                        flags
 | 
				
			||||||
                        m
 | 
					                        m
 | 
				
			||||||
                        model.itemListModel
 | 
					                        model.itemListModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                nextView =
 | 
				
			||||||
 | 
					                    case ( model.viewMode, result.selection ) of
 | 
				
			||||||
 | 
					                        ( SelectView svm, Data.ItemSelection.Active ids ) ->
 | 
				
			||||||
 | 
					                            SelectView { svm | ids = ids }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        ( v, _ ) ->
 | 
				
			||||||
 | 
					                            v
 | 
				
			||||||
            in
 | 
					            in
 | 
				
			||||||
            withSub
 | 
					            withSub
 | 
				
			||||||
                ( { model
 | 
					                ( { model
 | 
				
			||||||
                    | itemListModel = result.model
 | 
					                    | itemListModel = result.model
 | 
				
			||||||
 | 
					                    , viewMode = nextView
 | 
				
			||||||
                    , dragDropData = DD.DragDropData result.dragModel Nothing
 | 
					                    , dragDropData = DD.DragDropData result.dragModel Nothing
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
                , Cmd.batch [ Cmd.map ItemCardListMsg result.cmd ]
 | 
					                , Cmd.batch [ Cmd.map ItemCardListMsg result.cmd ]
 | 
				
			||||||
@@ -159,11 +178,43 @@ update mId key flags settings msg model =
 | 
				
			|||||||
                doSearch flags settings False nm
 | 
					                doSearch flags settings False nm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ToggleSearchMenu ->
 | 
					        ToggleSearchMenu ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                nextView =
 | 
				
			||||||
 | 
					                    case model.viewMode of
 | 
				
			||||||
 | 
					                        SimpleView ->
 | 
				
			||||||
 | 
					                            SearchView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        SearchView ->
 | 
				
			||||||
 | 
					                            SimpleView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        SelectView _ ->
 | 
				
			||||||
 | 
					                            SimpleView
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
            withSub
 | 
					            withSub
 | 
				
			||||||
                ( { model | menuCollapsed = not model.menuCollapsed }
 | 
					                ( { model | viewMode = nextView }
 | 
				
			||||||
                , Cmd.none
 | 
					                , Cmd.none
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ToggleSelectView ->
 | 
				
			||||||
 | 
					            let
 | 
				
			||||||
 | 
					                ( nextView, cmd ) =
 | 
				
			||||||
 | 
					                    case model.viewMode of
 | 
				
			||||||
 | 
					                        SimpleView ->
 | 
				
			||||||
 | 
					                            ( SelectView initSelectViewModel, loadEditModel flags )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        SearchView ->
 | 
				
			||||||
 | 
					                            ( SelectView initSelectViewModel, loadEditModel flags )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        SelectView _ ->
 | 
				
			||||||
 | 
					                            ( SearchView, Cmd.none )
 | 
				
			||||||
 | 
					            in
 | 
				
			||||||
 | 
					            withSub
 | 
				
			||||||
 | 
					                ( { model
 | 
				
			||||||
 | 
					                    | viewMode = nextView
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                , cmd
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        LoadMore ->
 | 
					        LoadMore ->
 | 
				
			||||||
            if model.moreAvailable then
 | 
					            if model.moreAvailable then
 | 
				
			||||||
                doSearchMore flags settings model |> withSub
 | 
					                doSearchMore flags settings model |> withSub
 | 
				
			||||||
@@ -253,11 +304,223 @@ update mId key flags settings msg model =
 | 
				
			|||||||
        ClearItemDetailId ->
 | 
					        ClearItemDetailId ->
 | 
				
			||||||
            noSub ( { model | scrollToCard = Nothing }, Cmd.none )
 | 
					            noSub ( { model | scrollToCard = Nothing }, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SelectAllItems ->
 | 
				
			||||||
 | 
					            case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    let
 | 
				
			||||||
 | 
					                        visible =
 | 
				
			||||||
 | 
					                            Data.Items.idSet model.itemListModel.results
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        svm_ =
 | 
				
			||||||
 | 
					                            { svm | ids = Set.union svm.ids visible }
 | 
				
			||||||
 | 
					                    in
 | 
				
			||||||
 | 
					                    noSub
 | 
				
			||||||
 | 
					                        ( { model | viewMode = SelectView svm_ }
 | 
				
			||||||
 | 
					                        , Cmd.none
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SelectNoItems ->
 | 
				
			||||||
 | 
					            case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    let
 | 
				
			||||||
 | 
					                        svm_ =
 | 
				
			||||||
 | 
					                            { svm | ids = Set.empty }
 | 
				
			||||||
 | 
					                    in
 | 
				
			||||||
 | 
					                    noSub
 | 
				
			||||||
 | 
					                        ( { model | viewMode = SelectView svm_ }
 | 
				
			||||||
 | 
					                        , Cmd.none
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DeleteSelectedConfirmMsg lmsg ->
 | 
				
			||||||
 | 
					            case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    let
 | 
				
			||||||
 | 
					                        ( confirmModel, confirmed ) =
 | 
				
			||||||
 | 
					                            Comp.YesNoDimmer.update lmsg svm.deleteAllConfirm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        cmd =
 | 
				
			||||||
 | 
					                            if confirmed then
 | 
				
			||||||
 | 
					                                Api.deleteAllItems flags svm.ids DeleteAllResp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            else
 | 
				
			||||||
 | 
					                                Cmd.none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        act =
 | 
				
			||||||
 | 
					                            if confirmModel.active || confirmed then
 | 
				
			||||||
 | 
					                                DeleteSelected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            else
 | 
				
			||||||
 | 
					                                NoneAction
 | 
				
			||||||
 | 
					                    in
 | 
				
			||||||
 | 
					                    noSub
 | 
				
			||||||
 | 
					                        ( { model
 | 
				
			||||||
 | 
					                            | viewMode =
 | 
				
			||||||
 | 
					                                SelectView
 | 
				
			||||||
 | 
					                                    { svm
 | 
				
			||||||
 | 
					                                        | deleteAllConfirm = confirmModel
 | 
				
			||||||
 | 
					                                        , action = act
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                        , cmd
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DeleteAllResp (Ok res) ->
 | 
				
			||||||
 | 
					            if res.success then
 | 
				
			||||||
 | 
					                let
 | 
				
			||||||
 | 
					                    nm =
 | 
				
			||||||
 | 
					                        { model | viewMode = SearchView }
 | 
				
			||||||
 | 
					                in
 | 
				
			||||||
 | 
					                doSearch flags settings False nm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DeleteAllResp (Err _) ->
 | 
				
			||||||
 | 
					            noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        RequestDeleteSelected ->
 | 
				
			||||||
 | 
					            case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    if svm.ids == Set.empty then
 | 
				
			||||||
 | 
					                        noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        let
 | 
				
			||||||
 | 
					                            lmsg =
 | 
				
			||||||
 | 
					                                DeleteSelectedConfirmMsg Comp.YesNoDimmer.activate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            model_ =
 | 
				
			||||||
 | 
					                                { model | viewMode = SelectView { svm | action = DeleteSelected } }
 | 
				
			||||||
 | 
					                        in
 | 
				
			||||||
 | 
					                        update mId key flags settings lmsg model_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        EditSelectedItems ->
 | 
				
			||||||
 | 
					            case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    if svm.action == EditSelected then
 | 
				
			||||||
 | 
					                        noSub
 | 
				
			||||||
 | 
					                            ( { model | viewMode = SelectView { svm | action = NoneAction } }
 | 
				
			||||||
 | 
					                            , Cmd.none
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else if svm.ids == Set.empty then
 | 
				
			||||||
 | 
					                        noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        noSub
 | 
				
			||||||
 | 
					                            ( { model | viewMode = SelectView { svm | action = EditSelected } }
 | 
				
			||||||
 | 
					                            , Cmd.none
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        EditMenuMsg lmsg ->
 | 
				
			||||||
 | 
					            case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    let
 | 
				
			||||||
 | 
					                        res =
 | 
				
			||||||
 | 
					                            Comp.ItemDetail.EditMenu.update flags lmsg svm.editModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        svm_ =
 | 
				
			||||||
 | 
					                            { svm | editModel = res.model }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        cmd_ =
 | 
				
			||||||
 | 
					                            Cmd.map EditMenuMsg res.cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        sub_ =
 | 
				
			||||||
 | 
					                            Sub.map EditMenuMsg res.sub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        upCmd =
 | 
				
			||||||
 | 
					                            Comp.ItemDetail.FormChange.multiUpdate flags
 | 
				
			||||||
 | 
					                                svm.ids
 | 
				
			||||||
 | 
					                                res.change
 | 
				
			||||||
 | 
					                                MultiUpdateResp
 | 
				
			||||||
 | 
					                    in
 | 
				
			||||||
 | 
					                    ( { model | viewMode = SelectView svm_ }
 | 
				
			||||||
 | 
					                    , Cmd.batch [ cmd_, upCmd ]
 | 
				
			||||||
 | 
					                    , sub_
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MultiUpdateResp (Ok res) ->
 | 
				
			||||||
 | 
					            if res.success then
 | 
				
			||||||
 | 
					                case model.viewMode of
 | 
				
			||||||
 | 
					                    SelectView svm ->
 | 
				
			||||||
 | 
					                        -- replace changed items in the view
 | 
				
			||||||
 | 
					                        noSub ( model, loadChangedItems flags svm.ids )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _ ->
 | 
				
			||||||
 | 
					                        noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MultiUpdateResp (Err _) ->
 | 
				
			||||||
 | 
					            noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ReplaceChangedItemsResp (Ok items) ->
 | 
				
			||||||
 | 
					            noSub ( replaceItems model items, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ReplaceChangedItemsResp (Err _) ->
 | 
				
			||||||
 | 
					            noSub ( model, Cmd.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- Helpers
 | 
					--- Helpers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					replaceItems : Model -> ItemLightList -> Model
 | 
				
			||||||
 | 
					replaceItems model newItems =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        listModel =
 | 
				
			||||||
 | 
					            model.itemListModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        changed =
 | 
				
			||||||
 | 
					            Data.Items.replaceIn listModel.results newItems
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        newList =
 | 
				
			||||||
 | 
					            { listModel | results = changed }
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    { model | itemListModel = newList }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					loadChangedItems : Flags -> Set String -> Cmd Msg
 | 
				
			||||||
 | 
					loadChangedItems flags ids =
 | 
				
			||||||
 | 
					    if Set.isEmpty ids then
 | 
				
			||||||
 | 
					        Cmd.none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        let
 | 
				
			||||||
 | 
					            searchInit =
 | 
				
			||||||
 | 
					                Api.Model.ItemSearch.empty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            idList =
 | 
				
			||||||
 | 
					                IdList (Set.toList ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            search =
 | 
				
			||||||
 | 
					                { searchInit
 | 
				
			||||||
 | 
					                    | itemSubset = Just idList
 | 
				
			||||||
 | 
					                    , limit = Set.size ids
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					        in
 | 
				
			||||||
 | 
					        Api.itemSearch flags search ReplaceChangedItemsResp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
scrollToCard : Maybe String -> Model -> ( Model, Cmd Msg, Sub Msg )
 | 
					scrollToCard : Maybe String -> Model -> ( Model, Cmd Msg, Sub Msg )
 | 
				
			||||||
scrollToCard mId model =
 | 
					scrollToCard mId model =
 | 
				
			||||||
    let
 | 
					    let
 | 
				
			||||||
@@ -275,12 +538,17 @@ scrollToCard mId model =
 | 
				
			|||||||
            ( model, Cmd.none, Sub.none )
 | 
					            ( model, Cmd.none, Sub.none )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					loadEditModel : Flags -> Cmd Msg
 | 
				
			||||||
 | 
					loadEditModel flags =
 | 
				
			||||||
 | 
					    Cmd.map EditMenuMsg (Comp.ItemDetail.EditMenu.loadModel flags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
doSearch : Flags -> UiSettings -> Bool -> Model -> ( Model, Cmd Msg, Sub Msg )
 | 
					doSearch : Flags -> UiSettings -> Bool -> Model -> ( Model, Cmd Msg, Sub Msg )
 | 
				
			||||||
doSearch flags settings scroll model =
 | 
					doSearch flags settings scroll model =
 | 
				
			||||||
    let
 | 
					    let
 | 
				
			||||||
        stype =
 | 
					        stype =
 | 
				
			||||||
            if
 | 
					            if
 | 
				
			||||||
                not model.menuCollapsed
 | 
					                not (menuCollapsed model)
 | 
				
			||||||
                    || Util.String.isNothingOrBlank model.contentOnlySearch
 | 
					                    || Util.String.isNothingOrBlank model.contentOnlySearch
 | 
				
			||||||
            then
 | 
					            then
 | 
				
			||||||
                BasicSearch
 | 
					                BasicSearch
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,26 +3,51 @@ module Page.Home.View exposing (view)
 | 
				
			|||||||
import Api.Model.ItemSearch
 | 
					import Api.Model.ItemSearch
 | 
				
			||||||
import Comp.FixedDropdown
 | 
					import Comp.FixedDropdown
 | 
				
			||||||
import Comp.ItemCardList
 | 
					import Comp.ItemCardList
 | 
				
			||||||
 | 
					import Comp.ItemDetail.EditMenu
 | 
				
			||||||
import Comp.SearchMenu
 | 
					import Comp.SearchMenu
 | 
				
			||||||
 | 
					import Comp.YesNoDimmer
 | 
				
			||||||
import Data.Flags exposing (Flags)
 | 
					import Data.Flags exposing (Flags)
 | 
				
			||||||
 | 
					import Data.ItemSelection
 | 
				
			||||||
import Data.UiSettings exposing (UiSettings)
 | 
					import Data.UiSettings exposing (UiSettings)
 | 
				
			||||||
import Html exposing (..)
 | 
					import Html exposing (..)
 | 
				
			||||||
import Html.Attributes exposing (..)
 | 
					import Html.Attributes exposing (..)
 | 
				
			||||||
import Html.Events exposing (onClick, onInput)
 | 
					import Html.Events exposing (onClick, onInput)
 | 
				
			||||||
import Page exposing (Page(..))
 | 
					import Page exposing (Page(..))
 | 
				
			||||||
import Page.Home.Data exposing (..)
 | 
					import Page.Home.Data exposing (..)
 | 
				
			||||||
 | 
					import Set
 | 
				
			||||||
import Util.Html
 | 
					import Util.Html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
view : Flags -> UiSettings -> Model -> Html Msg
 | 
					view : Flags -> UiSettings -> Model -> Html Msg
 | 
				
			||||||
view flags settings model =
 | 
					view flags settings model =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        itemViewCfg =
 | 
				
			||||||
 | 
					            case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    Comp.ItemCardList.ViewConfig
 | 
				
			||||||
 | 
					                        model.scrollToCard
 | 
				
			||||||
 | 
					                        (Data.ItemSelection.Active svm.ids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    Comp.ItemCardList.ViewConfig
 | 
				
			||||||
 | 
					                        model.scrollToCard
 | 
				
			||||||
 | 
					                        Data.ItemSelection.Inactive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        selectAction =
 | 
				
			||||||
 | 
					            case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    svm.action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    NoneAction
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
    div [ class "home-page ui padded grid" ]
 | 
					    div [ class "home-page ui padded grid" ]
 | 
				
			||||||
        [ div
 | 
					        [ div
 | 
				
			||||||
            [ classList
 | 
					            [ classList
 | 
				
			||||||
                [ ( "sixteen wide mobile six wide tablet four wide computer search-menu column"
 | 
					                [ ( "sixteen wide mobile six wide tablet four wide computer search-menu column"
 | 
				
			||||||
                  , True
 | 
					                  , True
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
                , ( "invisible hidden", model.menuCollapsed )
 | 
					                , ( "invisible hidden", menuCollapsed model )
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
            [ div
 | 
					            [ div
 | 
				
			||||||
@@ -38,6 +63,17 @@ view flags settings model =
 | 
				
			|||||||
                    ]
 | 
					                    ]
 | 
				
			||||||
                , div [ class "right floated menu" ]
 | 
					                , div [ class "right floated menu" ]
 | 
				
			||||||
                    [ a
 | 
					                    [ a
 | 
				
			||||||
 | 
					                        [ classList
 | 
				
			||||||
 | 
					                            [ ( "borderless item", True )
 | 
				
			||||||
 | 
					                            , ( "active", selectActive model )
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
 | 
					                        , href "#"
 | 
				
			||||||
 | 
					                        , title "Toggle select items"
 | 
				
			||||||
 | 
					                        , onClick ToggleSelectView
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                        [ i [ class "tasks icon" ] []
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , a
 | 
				
			||||||
                        [ class "borderless item"
 | 
					                        [ class "borderless item"
 | 
				
			||||||
                        , onClick ResetSearch
 | 
					                        , onClick ResetSearch
 | 
				
			||||||
                        , title "Reset form"
 | 
					                        , title "Reset form"
 | 
				
			||||||
@@ -63,26 +99,30 @@ view flags settings model =
 | 
				
			|||||||
                    ]
 | 
					                    ]
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            , div [ class "" ]
 | 
					            , div [ class "" ]
 | 
				
			||||||
                [ Html.map SearchMenuMsg
 | 
					                (viewLeftMenu flags settings model)
 | 
				
			||||||
                    (Comp.SearchMenu.viewDrop model.dragDropData
 | 
					 | 
				
			||||||
                        flags
 | 
					 | 
				
			||||||
                        settings
 | 
					 | 
				
			||||||
                        model.searchMenuModel
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                ]
 | 
					 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        , div
 | 
					        , div
 | 
				
			||||||
            [ classList
 | 
					            [ classList
 | 
				
			||||||
                [ ( "sixteen wide mobile ten wide tablet twelve wide computer column"
 | 
					                [ ( "sixteen wide mobile ten wide tablet twelve wide computer column"
 | 
				
			||||||
                  , not model.menuCollapsed
 | 
					                  , not (menuCollapsed model)
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
                , ( "sixteen wide column", model.menuCollapsed )
 | 
					                , ( "sixteen wide column", menuCollapsed model )
 | 
				
			||||||
                , ( "item-card-list", True )
 | 
					                , ( "item-card-list", True )
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
            [ viewSearchBar flags model
 | 
					            [ viewBar flags model
 | 
				
			||||||
 | 
					            , case model.viewMode of
 | 
				
			||||||
 | 
					                SelectView svm ->
 | 
				
			||||||
 | 
					                    Html.map DeleteSelectedConfirmMsg
 | 
				
			||||||
 | 
					                        (Comp.YesNoDimmer.view2 (selectAction == DeleteSelected)
 | 
				
			||||||
 | 
					                            deleteAllDimmer
 | 
				
			||||||
 | 
					                            svm.deleteAllConfirm
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    span [ class "invisible" ] []
 | 
				
			||||||
            , Html.map ItemCardListMsg
 | 
					            , Html.map ItemCardListMsg
 | 
				
			||||||
                (Comp.ItemCardList.view model.scrollToCard settings model.itemListModel)
 | 
					                (Comp.ItemCardList.view itemViewCfg settings model.itemListModel)
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        , div
 | 
					        , div
 | 
				
			||||||
            [ classList
 | 
					            [ classList
 | 
				
			||||||
@@ -117,6 +157,113 @@ view flags settings model =
 | 
				
			|||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					viewLeftMenu : Flags -> UiSettings -> Model -> List (Html Msg)
 | 
				
			||||||
 | 
					viewLeftMenu flags settings model =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        searchMenu =
 | 
				
			||||||
 | 
					            [ Html.map SearchMenuMsg
 | 
				
			||||||
 | 
					                (Comp.SearchMenu.viewDrop model.dragDropData
 | 
				
			||||||
 | 
					                    flags
 | 
				
			||||||
 | 
					                    settings
 | 
				
			||||||
 | 
					                    model.searchMenuModel
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    case model.viewMode of
 | 
				
			||||||
 | 
					        SelectView svm ->
 | 
				
			||||||
 | 
					            case svm.action of
 | 
				
			||||||
 | 
					                EditSelected ->
 | 
				
			||||||
 | 
					                    let
 | 
				
			||||||
 | 
					                        cfg =
 | 
				
			||||||
 | 
					                            Comp.ItemDetail.EditMenu.defaultViewConfig
 | 
				
			||||||
 | 
					                    in
 | 
				
			||||||
 | 
					                    [ div [ class "ui dividing header" ]
 | 
				
			||||||
 | 
					                        [ text "Multi-Edit"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , div [ class "ui info message" ]
 | 
				
			||||||
 | 
					                        [ text "Note that a change here immediatly affects all selected items on the right!"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					                    , Html.map EditMenuMsg
 | 
				
			||||||
 | 
					                        (Comp.ItemDetail.EditMenu.view cfg settings svm.editModel)
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ ->
 | 
				
			||||||
 | 
					                    searchMenu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _ ->
 | 
				
			||||||
 | 
					            searchMenu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					viewBar : Flags -> Model -> Html Msg
 | 
				
			||||||
 | 
					viewBar flags model =
 | 
				
			||||||
 | 
					    case model.viewMode of
 | 
				
			||||||
 | 
					        SimpleView ->
 | 
				
			||||||
 | 
					            viewSearchBar flags model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SearchView ->
 | 
				
			||||||
 | 
					            div [ class "hidden invisible" ] []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        SelectView svm ->
 | 
				
			||||||
 | 
					            viewActionBar flags svm model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					viewActionBar : Flags -> SelectViewModel -> Model -> Html Msg
 | 
				
			||||||
 | 
					viewActionBar _ svm model =
 | 
				
			||||||
 | 
					    let
 | 
				
			||||||
 | 
					        selectCount =
 | 
				
			||||||
 | 
					            Set.size svm.ids |> String.fromInt
 | 
				
			||||||
 | 
					    in
 | 
				
			||||||
 | 
					    div
 | 
				
			||||||
 | 
					        [ class "ui ablue-comp icon menu"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        [ a
 | 
				
			||||||
 | 
					            [ classList
 | 
				
			||||||
 | 
					                [ ( "borderless item", True )
 | 
				
			||||||
 | 
					                , ( "active", svm.action == EditSelected )
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            , href "#"
 | 
				
			||||||
 | 
					            , title <| "Edit " ++ selectCount ++ " selected items"
 | 
				
			||||||
 | 
					            , onClick EditSelectedItems
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            [ i [ class "ui edit icon" ] []
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        , a
 | 
				
			||||||
 | 
					            [ classList
 | 
				
			||||||
 | 
					                [ ( "borderless item", True )
 | 
				
			||||||
 | 
					                , ( "active", svm.action == DeleteSelected )
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            , href "#"
 | 
				
			||||||
 | 
					            , title <| "Delete " ++ selectCount ++ " selected items"
 | 
				
			||||||
 | 
					            , onClick RequestDeleteSelected
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            [ i [ class "trash icon" ] []
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        , div [ class "right menu" ]
 | 
				
			||||||
 | 
					            [ a
 | 
				
			||||||
 | 
					                [ class "item"
 | 
				
			||||||
 | 
					                , href "#"
 | 
				
			||||||
 | 
					                , onClick SelectAllItems
 | 
				
			||||||
 | 
					                , title "Select all"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					                [ i [ class "check square outline icon" ] []
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            , a
 | 
				
			||||||
 | 
					                [ class "borderless item"
 | 
				
			||||||
 | 
					                , href "#"
 | 
				
			||||||
 | 
					                , title "Select none"
 | 
				
			||||||
 | 
					                , onClick SelectNoItems
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					                [ i [ class "square outline icon" ] []
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            , div [ class "borderless label item" ]
 | 
				
			||||||
 | 
					                [ div [ class "ui circular purple icon label" ]
 | 
				
			||||||
 | 
					                    [ text selectCount
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
viewSearchBar : Flags -> Model -> Html Msg
 | 
					viewSearchBar : Flags -> Model -> Html Msg
 | 
				
			||||||
viewSearchBar flags model =
 | 
					viewSearchBar flags model =
 | 
				
			||||||
    let
 | 
					    let
 | 
				
			||||||
@@ -145,7 +292,7 @@ viewSearchBar flags model =
 | 
				
			|||||||
    in
 | 
					    in
 | 
				
			||||||
    div
 | 
					    div
 | 
				
			||||||
        [ classList
 | 
					        [ classList
 | 
				
			||||||
            [ ( "invisible hidden", not model.menuCollapsed )
 | 
					            [ ( "invisible hidden", not (menuCollapsed model) )
 | 
				
			||||||
            , ( "ui secondary stackable menu container", True )
 | 
					            , ( "ui secondary stackable menu container", True )
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
@@ -221,3 +368,15 @@ hasMoreSearch model =
 | 
				
			|||||||
                    Api.Model.ItemSearch.empty
 | 
					                    Api.Model.ItemSearch.empty
 | 
				
			||||||
    in
 | 
					    in
 | 
				
			||||||
    is_ /= Api.Model.ItemSearch.empty
 | 
					    is_ /= Api.Model.ItemSearch.empty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					deleteAllDimmer : Comp.YesNoDimmer.Settings
 | 
				
			||||||
 | 
					deleteAllDimmer =
 | 
				
			||||||
 | 
					    { message = "Really delete all selected items?"
 | 
				
			||||||
 | 
					    , headerIcon = "exclamation icon"
 | 
				
			||||||
 | 
					    , headerClass = "ui inverted icon header"
 | 
				
			||||||
 | 
					    , confirmButton = "Yes"
 | 
				
			||||||
 | 
					    , cancelButton = "No"
 | 
				
			||||||
 | 
					    , invertedDimmer = False
 | 
				
			||||||
 | 
					    , extraClass = "top aligned"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,9 @@
 | 
				
			|||||||
    margin: 0 1em;
 | 
					    margin: 0 1em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.default-layout .ui.icon.menu .label.item {
 | 
				
			||||||
 | 
					    padding: 0 0.5em 0 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.default-layout .right-float {
 | 
					.default-layout .right-float {
 | 
				
			||||||
    float: right;
 | 
					    float: right;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user