mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-03-28 17:55:06 +00:00
commit
d3f5e4782f
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user