Allow to specify ordering when retrieving meta data

The query now searches in more fields. For example, when getting a
list of tags, the query is applied to the tag name *and* category.
When listing persons, the query now also looks in the associated
organization name.

This has been used to make some headers in the meta data tables
clickable to sort the list accordingly.

Refs: #965, #538
This commit is contained in:
eikek 2021-08-24 21:35:57 +02:00
parent 5926565267
commit cf88f5c2de
52 changed files with 1236 additions and 208 deletions

View File

@ -7,12 +7,13 @@
package docspell.backend.ops
import cats.data.EitherT
import cats.data.NonEmptyList
import cats.data.OptionT
import cats.data.{NonEmptyList => Nel}
import cats.effect._
import cats.implicits._
import docspell.backend.ops.OCustomFields.CustomFieldData
import docspell.backend.ops.OCustomFields.CustomFieldOrder
import docspell.backend.ops.OCustomFields.FieldValue
import docspell.backend.ops.OCustomFields.NewCustomField
import docspell.backend.ops.OCustomFields.RemoveValue
@ -33,7 +34,11 @@ import org.log4s.getLogger
trait OCustomFields[F[_]] {
/** Find all fields using an optional query on the name and label */
def findAll(coll: Ident, nameQuery: Option[String]): F[Vector[CustomFieldData]]
def findAll(
coll: Ident,
nameQuery: Option[String],
order: CustomFieldOrder
): F[Vector[CustomFieldData]]
/** Find one field by its id */
def findById(coll: Ident, fieldId: Ident): F[Option[CustomFieldData]]
@ -50,13 +55,13 @@ trait OCustomFields[F[_]] {
/** Sets a value given a field an an item. Existing values are overwritten. */
def setValue(item: Ident, value: SetValue): F[SetValueResult]
def setValueMultiple(items: NonEmptyList[Ident], value: SetValue): F[SetValueResult]
def setValueMultiple(items: Nel[Ident], value: SetValue): F[SetValueResult]
/** Deletes a value for a given field an item. */
def deleteValue(in: RemoveValue): F[UpdateResult]
/** Finds all values to the given items */
def findAllValues(itemIds: NonEmptyList[Ident]): F[List[FieldValue]]
def findAllValues(itemIds: Nel[Ident]): F[List[FieldValue]]
}
object OCustomFields {
@ -96,10 +101,48 @@ object OCustomFields {
case class RemoveValue(
field: Ident,
item: NonEmptyList[Ident],
item: Nel[Ident],
collective: Ident
)
sealed trait CustomFieldOrder
object CustomFieldOrder {
import docspell.store.qb.DSL._
final case object NameAsc extends CustomFieldOrder
final case object NameDesc extends CustomFieldOrder
final case object LabelAsc extends CustomFieldOrder
final case object LabelDesc extends CustomFieldOrder
final case object TypeAsc extends CustomFieldOrder
final case object TypeDesc extends CustomFieldOrder
def parse(str: String): Either[String, CustomFieldOrder] =
str.toLowerCase match {
case "name" => Right(NameAsc)
case "-name" => Right(NameDesc)
case "label" => Right(LabelAsc)
case "-label" => Right(LabelDesc)
case "type" => Right(TypeAsc)
case "-type" => Right(TypeDesc)
case _ => Left(s"Unknown sort property for custom field: $str")
}
def parseOrDefault(str: String): CustomFieldOrder =
parse(str).toOption.getOrElse(NameAsc)
private[ops] def apply(
order: CustomFieldOrder
)(field: RCustomField.Table) =
order match {
case NameAsc => Nel.of(field.name.asc)
case NameDesc => Nel.of(field.name.desc)
case LabelAsc => Nel.of(coalesce(field.label.s, field.name.s).asc)
case LabelDesc => Nel.of(coalesce(field.label.s, field.name.s).desc)
case TypeAsc => Nel.of(field.ftype.asc, field.name.asc)
case TypeDesc => Nel.of(field.ftype.desc, field.name.desc)
}
}
def apply[F[_]: Async](
store: Store[F]
): Resource[F, OCustomFields[F]] =
@ -107,14 +150,19 @@ object OCustomFields {
private[this] val logger = Logger.log4s[ConnectionIO](getLogger)
def findAllValues(itemIds: NonEmptyList[Ident]): F[List[FieldValue]] =
def findAllValues(itemIds: Nel[Ident]): F[List[FieldValue]] =
store.transact(QCustomField.findAllValues(itemIds))
def findAll(coll: Ident, nameQuery: Option[String]): F[Vector[CustomFieldData]] =
def findAll(
coll: Ident,
nameQuery: Option[String],
order: CustomFieldOrder
): F[Vector[CustomFieldData]] =
store.transact(
QCustomField.findAllLike(
coll,
nameQuery.map(WildcardString.apply).flatMap(_.both)
nameQuery.map(WildcardString.apply).flatMap(_.both),
CustomFieldOrder(order)
)
)
@ -149,10 +197,10 @@ object OCustomFields {
}
def setValue(item: Ident, value: SetValue): F[SetValueResult] =
setValueMultiple(NonEmptyList.of(item), value)
setValueMultiple(Nel.of(item), value)
def setValueMultiple(
items: NonEmptyList[Ident],
items: Nel[Ident],
value: SetValue
): F[SetValueResult] =
(for {

View File

@ -6,6 +6,7 @@
package docspell.backend.ops
import cats.data.NonEmptyList
import cats.effect.{Async, Resource}
import cats.implicits._
@ -15,7 +16,11 @@ import docspell.store.{AddResult, Store}
trait OEquipment[F[_]] {
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[REquipment]]
def findAll(
account: AccountId,
nameQuery: Option[String],
order: OEquipment.EquipmentOrder
): F[Vector[REquipment]]
def find(account: AccountId, id: Ident): F[Option[REquipment]]
@ -27,11 +32,39 @@ trait OEquipment[F[_]] {
}
object OEquipment {
import docspell.store.qb.DSL._
sealed trait EquipmentOrder
object EquipmentOrder {
final case object NameAsc extends EquipmentOrder
final case object NameDesc extends EquipmentOrder
def parse(str: String): Either[String, EquipmentOrder] =
str.toLowerCase match {
case "name" => Right(NameAsc)
case "-name" => Right(NameDesc)
case _ => Left(s"Unknown sort property for equipments: $str")
}
def parseOrDefault(str: String): EquipmentOrder =
parse(str).toOption.getOrElse(NameAsc)
private[ops] def apply(order: EquipmentOrder)(table: REquipment.Table) = order match {
case NameAsc => NonEmptyList.of(table.name.asc)
case NameDesc => NonEmptyList.of(table.name.desc)
}
}
def apply[F[_]: Async](store: Store[F]): Resource[F, OEquipment[F]] =
Resource.pure[F, OEquipment[F]](new OEquipment[F] {
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[REquipment]] =
store.transact(REquipment.findAll(account.collective, nameQuery, _.name))
def findAll(
account: AccountId,
nameQuery: Option[String],
order: EquipmentOrder
): F[Vector[REquipment]] =
store.transact(
REquipment.findAll(account.collective, nameQuery, EquipmentOrder(order))
)
def find(account: AccountId, id: Ident): F[Option[REquipment]] =
store.transact(REquipment.findById(id)).map(_.filter(_.cid == account.collective))

View File

@ -6,6 +6,7 @@
package docspell.backend.ops
import cats.data.{NonEmptyList => Nel}
import cats.effect._
import docspell.common._
@ -18,7 +19,8 @@ trait OFolder[F[_]] {
def findAll(
account: AccountId,
ownerLogin: Option[Ident],
nameQuery: Option[String]
query: Option[String],
order: OFolder.FolderOrder
): F[Vector[OFolder.FolderItem]]
def findById(id: Ident, account: AccountId): F[Option[OFolder.FolderDetail]]
@ -50,6 +52,7 @@ trait OFolder[F[_]] {
}
object OFolder {
import docspell.store.qb.DSL._
type FolderChangeResult = QFolder.FolderChangeResult
val FolderChangeResult = QFolder.FolderChangeResult
@ -60,14 +63,45 @@ object OFolder {
type FolderDetail = QFolder.FolderDetail
val FolderDetail = QFolder.FolderDetail
sealed trait FolderOrder
object FolderOrder {
final case object NameAsc extends FolderOrder
final case object NameDesc extends FolderOrder
final case object OwnerAsc extends FolderOrder
final case object OwnerDesc extends FolderOrder
def parse(str: String): Either[String, FolderOrder] =
str.toLowerCase match {
case "name" => Right(NameAsc)
case "-name" => Right(NameDesc)
case "owner" => Right(OwnerAsc)
case "-owner" => Right(OwnerDesc)
case _ => Left(s"Unknown sort property for folder: $str")
}
def parseOrDefault(str: String): FolderOrder =
parse(str).toOption.getOrElse(NameAsc)
private[ops] def apply(order: FolderOrder)(folder: RFolder.Table, user: RUser.Table) =
order match {
case NameAsc => Nel.of(folder.name.asc)
case NameDesc => Nel.of(folder.name.desc)
case OwnerAsc => Nel.of(user.login.asc, folder.name.asc)
case OwnerDesc => Nel.of(user.login.desc, folder.name.desc)
}
}
def apply[F[_]](store: Store[F]): Resource[F, OFolder[F]] =
Resource.pure[F, OFolder[F]](new OFolder[F] {
def findAll(
account: AccountId,
ownerLogin: Option[Ident],
nameQuery: Option[String]
query: Option[String],
order: FolderOrder
): F[Vector[FolderItem]] =
store.transact(QFolder.findAll(account, None, ownerLogin, nameQuery))
store.transact(
QFolder.findAll(account, None, ownerLogin, query, FolderOrder(order))
)
def findById(id: Ident, account: AccountId): F[Option[FolderDetail]] =
store.transact(QFolder.findById(id, account))

View File

@ -6,6 +6,7 @@
package docspell.backend.ops
import cats.data.NonEmptyList
import cats.effect.{Async, Resource}
import cats.implicits._
@ -16,10 +17,18 @@ import docspell.store.queries.QOrganization
import docspell.store.records._
trait OOrganization[F[_]] {
def findAllOrg(account: AccountId, query: Option[String]): F[Vector[OrgAndContacts]]
def findAllOrg(
account: AccountId,
query: Option[String],
order: OrganizationOrder
): F[Vector[OrgAndContacts]]
def findOrg(account: AccountId, orgId: Ident): F[Option[OrgAndContacts]]
def findAllOrgRefs(account: AccountId, nameQuery: Option[String]): F[Vector[IdRef]]
def findAllOrgRefs(
account: AccountId,
nameQuery: Option[String],
order: OrganizationOrder
): F[Vector[IdRef]]
def addOrg(s: OrgAndContacts): F[AddResult]
@ -27,12 +36,17 @@ trait OOrganization[F[_]] {
def findAllPerson(
account: AccountId,
query: Option[String]
query: Option[String],
order: PersonOrder
): F[Vector[PersonAndContacts]]
def findPerson(account: AccountId, persId: Ident): F[Option[PersonAndContacts]]
def findAllPersonRefs(account: AccountId, nameQuery: Option[String]): F[Vector[IdRef]]
def findAllPersonRefs(
account: AccountId,
nameQuery: Option[String],
order: PersonOrder
): F[Vector[IdRef]]
/** Add a new person with their contacts. The additional organization is ignored. */
def addPerson(s: PersonAndContacts): F[AddResult]
@ -46,6 +60,7 @@ trait OOrganization[F[_]] {
}
object OOrganization {
import docspell.store.qb.DSL._
case class OrgAndContacts(org: ROrganization, contacts: Seq[RContact])
@ -55,15 +70,79 @@ object OOrganization {
contacts: Seq[RContact]
)
sealed trait OrganizationOrder
object OrganizationOrder {
final case object NameAsc extends OrganizationOrder
final case object NameDesc extends OrganizationOrder
def parse(str: String): Either[String, OrganizationOrder] =
str.toLowerCase match {
case "name" => Right(NameAsc)
case "-name" => Right(NameDesc)
case _ => Left(s"Unknown sort property for organization: $str")
}
def parseOrDefault(str: String): OrganizationOrder =
parse(str).toOption.getOrElse(NameAsc)
private[ops] def apply(order: OrganizationOrder)(table: ROrganization.Table) =
order match {
case NameAsc => NonEmptyList.of(table.name.asc)
case NameDesc => NonEmptyList.of(table.name.desc)
}
}
sealed trait PersonOrder
object PersonOrder {
final case object NameAsc extends PersonOrder
final case object NameDesc extends PersonOrder
final case object OrgAsc extends PersonOrder
final case object OrgDesc extends PersonOrder
def parse(str: String): Either[String, PersonOrder] =
str.toLowerCase match {
case "name" => Right(NameAsc)
case "-name" => Right(NameDesc)
case "org" => Right(OrgAsc)
case "-org" => Right(OrgDesc)
case _ => Left(s"Unknown sort property for person: $str")
}
def parseOrDefault(str: String): PersonOrder =
parse(str).toOption.getOrElse(NameAsc)
private[ops] def apply(
order: PersonOrder
)(person: RPerson.Table, org: ROrganization.Table) =
order match {
case NameAsc => NonEmptyList.of(person.name.asc)
case NameDesc => NonEmptyList.of(person.name.desc)
case OrgAsc => NonEmptyList.of(org.name.asc)
case OrgDesc => NonEmptyList.of(org.name.desc)
}
private[ops] def nameOnly(order: PersonOrder)(person: RPerson.Table) =
order match {
case NameAsc => NonEmptyList.of(person.name.asc)
case NameDesc => NonEmptyList.of(person.name.desc)
case OrgAsc => NonEmptyList.of(person.name.asc)
case OrgDesc => NonEmptyList.of(person.name.asc)
}
}
def apply[F[_]: Async](store: Store[F]): Resource[F, OOrganization[F]] =
Resource.pure[F, OOrganization[F]](new OOrganization[F] {
def findAllOrg(
account: AccountId,
query: Option[String]
query: Option[String],
order: OrganizationOrder
): F[Vector[OrgAndContacts]] =
store
.transact(QOrganization.findOrgAndContact(account.collective, query, _.name))
.transact(
QOrganization
.findOrgAndContact(account.collective, query, OrganizationOrder(order))
)
.map { case (org, cont) => OrgAndContacts(org, cont) }
.compile
.toVector
@ -75,9 +154,16 @@ object OOrganization {
def findAllOrgRefs(
account: AccountId,
nameQuery: Option[String]
nameQuery: Option[String],
order: OrganizationOrder
): F[Vector[IdRef]] =
store.transact(ROrganization.findAllRef(account.collective, nameQuery, _.name))
store.transact(
ROrganization.findAllRef(
account.collective,
nameQuery,
OrganizationOrder(order)
)
)
def addOrg(s: OrgAndContacts): F[AddResult] =
QOrganization.addOrg(s.org, s.contacts, s.org.cid)(store)
@ -87,10 +173,14 @@ object OOrganization {
def findAllPerson(
account: AccountId,
query: Option[String]
query: Option[String],
order: PersonOrder
): F[Vector[PersonAndContacts]] =
store
.transact(QOrganization.findPersonAndContact(account.collective, query, _.name))
.transact(
QOrganization
.findPersonAndContact(account.collective, query, PersonOrder(order))
)
.map { case (person, org, cont) => PersonAndContacts(person, org, cont) }
.compile
.toVector
@ -102,9 +192,12 @@ object OOrganization {
def findAllPersonRefs(
account: AccountId,
nameQuery: Option[String]
nameQuery: Option[String],
order: PersonOrder
): F[Vector[IdRef]] =
store.transact(RPerson.findAllRef(account.collective, nameQuery, _.name))
store.transact(
RPerson.findAllRef(account.collective, nameQuery, PersonOrder.nameOnly(order))
)
def addPerson(s: PersonAndContacts): F[AddResult] =
QOrganization.addPerson(s.person, s.contacts, s.person.cid)(store)

View File

@ -6,6 +6,7 @@
package docspell.backend.ops
import cats.data.NonEmptyList
import cats.effect.{Async, Resource}
import cats.implicits._
@ -16,7 +17,11 @@ import docspell.store.{AddResult, Store}
trait OTag[F[_]] {
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[RTag]]
def findAll(
account: AccountId,
query: Option[String],
order: OTag.TagOrder
): F[Vector[RTag]]
def add(s: RTag): F[AddResult]
@ -30,11 +35,43 @@ trait OTag[F[_]] {
}
object OTag {
import docspell.store.qb.DSL._
sealed trait TagOrder
object TagOrder {
final case object NameAsc extends TagOrder
final case object NameDesc extends TagOrder
final case object CategoryAsc extends TagOrder
final case object CategoryDesc extends TagOrder
def parse(str: String): Either[String, TagOrder] =
str.toLowerCase match {
case "name" => Right(NameAsc)
case "-name" => Right(NameDesc)
case "category" => Right(CategoryAsc)
case "-category" => Right(CategoryDesc)
case _ => Left(s"Unknown sort property for tags: $str")
}
def parseOrDefault(str: String): TagOrder =
parse(str).toOption.getOrElse(NameAsc)
private[ops] def apply(order: TagOrder)(table: RTag.Table) = order match {
case NameAsc => NonEmptyList.of(table.name.asc)
case CategoryAsc => NonEmptyList.of(table.category.asc, table.name.asc)
case NameDesc => NonEmptyList.of(table.name.desc)
case CategoryDesc => NonEmptyList.of(table.category.desc, table.name.desc)
}
}
def apply[F[_]: Async](store: Store[F]): Resource[F, OTag[F]] =
Resource.pure[F, OTag[F]](new OTag[F] {
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[RTag]] =
store.transact(RTag.findAll(account.collective, nameQuery, _.name))
def findAll(
account: AccountId,
query: Option[String],
order: TagOrder
): F[Vector[RTag]] =
store.transact(RTag.findAll(account.collective, query, TagOrder(order)))
def add(t: RTag): F[AddResult] = {
def insert = RTag.insert(t)

View File

@ -501,11 +501,14 @@ paths:
tags: [ Tags ]
summary: Get a list of tags
description: |
Return a list of all configured tags.
Return a list of all configured tags. The `sort` query
parameter is optional and can specify how the list is sorted.
Possible values are: `name`, `-name`, `category`, `-category`.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/q"
- $ref: "#/components/parameters/sort"
responses:
200:
description: Ok
@ -579,12 +582,16 @@ paths:
tags: [ Organization ]
summary: Get a list of organizations.
description: |
Return a list of all organizations. Only name and id are returned.
Return a list of all organizations. Only name and id are
returned. If `full` is specified, the list contains all
organization data. The `sort` parameter can be either `name`
or `-name` to specify the order.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/full"
- $ref: "#/components/parameters/q"
- $ref: "#/components/parameters/sort"
responses:
200:
description: Ok
@ -677,12 +684,17 @@ paths:
tags: [ Person ]
summary: Get a list of persons.
description: |
Return a list of all persons. Only name and id are returned.
Return a list of all persons. Only name and id are returned
unless the `full` parameter is specified. The `sort` parameter
can be used to control the order of the result. Use one of:
`name`, `-name`, `org`, `-org`. Note that order by `org` only
works when retrieving the full list.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/full"
- $ref: "#/components/parameters/q"
- $ref: "#/components/parameters/sort"
responses:
200:
description: Ok
@ -775,11 +787,14 @@ paths:
tags: [ Equipment ]
summary: Get a list of equipments
description: |
Return a list of all configured equipments.
Return a list of all configured equipments. The sort query
parameter is optional and can be one of `name` or `-name` to
sort the list of equipments.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/q"
- $ref: "#/components/parameters/sort"
responses:
200:
description: Ok
@ -3771,11 +3786,15 @@ paths:
tags: [ Custom Fields ]
summary: Get all defined custom fields.
description: |
Get all custom fields defined for the current collective.
Get all custom fields defined for the current collective. The
`sort` parameter can be used to control the order of the
returned list. It can take a value from: `name`, `-name`,
`label`, `-label`, `type`, `-type`.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/q"
- $ref: "#/components/parameters/sort"
responses:
200:
description: Ok
@ -5985,6 +6004,14 @@ components:
schema:
type: integer
format: int32
sort:
name: sort
in: query
required: false
description: |
How to sort the returned list
schema:
type: string
withDetails:
name: withDetails
in: query

View File

@ -6,6 +6,11 @@
package docspell.restserver.http4s
import docspell.backend.ops.OCustomFields.CustomFieldOrder
import docspell.backend.ops.OEquipment.EquipmentOrder
import docspell.backend.ops.OFolder.FolderOrder
import docspell.backend.ops.OOrganization.{OrganizationOrder, PersonOrder}
import docspell.backend.ops.OTag.TagOrder
import docspell.common.ContactKind
import docspell.common.SearchMode
@ -29,6 +34,36 @@ object QueryParam {
SearchMode.fromString(str).left.map(s => ParseFailure(str, s))
)
implicit val tagOrderDecoder: QueryParamDecoder[TagOrder] =
QueryParamDecoder[String].emap(str =>
TagOrder.parse(str).left.map(s => ParseFailure(str, s))
)
implicit val euqipOrderDecoder: QueryParamDecoder[EquipmentOrder] =
QueryParamDecoder[String].emap(str =>
EquipmentOrder.parse(str).left.map(s => ParseFailure(str, s))
)
implicit val orgOrderDecoder: QueryParamDecoder[OrganizationOrder] =
QueryParamDecoder[String].emap(str =>
OrganizationOrder.parse(str).left.map(s => ParseFailure(str, s))
)
implicit val personOrderDecoder: QueryParamDecoder[PersonOrder] =
QueryParamDecoder[String].emap(str =>
PersonOrder.parse(str).left.map(s => ParseFailure(str, s))
)
implicit val folderOrderDecoder: QueryParamDecoder[FolderOrder] =
QueryParamDecoder[String].emap(str =>
FolderOrder.parse(str).left.map(s => ParseFailure(str, s))
)
implicit val customFieldOrderDecoder: QueryParamDecoder[CustomFieldOrder] =
QueryParamDecoder[String].emap(str =>
CustomFieldOrder.parse(str).left.map(s => ParseFailure(str, s))
)
object FullOpt extends OptionalQueryParamDecoderMatcher[Boolean]("full")
object OwningOpt extends OptionalQueryParamDecoderMatcher[Boolean]("owning")
@ -42,6 +77,12 @@ object QueryParam {
object Offset extends OptionalQueryParamDecoderMatcher[Int]("offset")
object WithDetails extends OptionalQueryParamDecoderMatcher[Boolean]("withDetails")
object SearchKind extends OptionalQueryParamDecoderMatcher[SearchMode]("searchMode")
object TagSort extends OptionalQueryParamDecoderMatcher[TagOrder]("sort")
object EquipSort extends OptionalQueryParamDecoderMatcher[EquipmentOrder]("sort")
object OrgSort extends OptionalQueryParamDecoderMatcher[OrganizationOrder]("sort")
object PersonSort extends OptionalQueryParamDecoderMatcher[PersonOrder]("sort")
object FolderSort extends OptionalQueryParamDecoderMatcher[FolderOrder]("sort")
object FieldSort extends OptionalQueryParamDecoderMatcher[CustomFieldOrder]("sort")
object WithFallback extends OptionalQueryParamDecoderMatcher[Boolean]("withFallback")
}

View File

@ -13,7 +13,7 @@ import cats.implicits._
import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OCustomFields
import docspell.backend.ops.OCustomFields.CustomFieldData
import docspell.backend.ops.OCustomFields.{CustomFieldData, CustomFieldOrder}
import docspell.common._
import docspell.restapi.model._
import docspell.restserver.conv.Conversions
@ -34,9 +34,14 @@ object CustomFieldRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root :? QueryParam.QueryOpt(param) =>
case GET -> Root :? QueryParam.QueryOpt(param) +& QueryParam.FieldSort(sort) =>
val order = sort.getOrElse(CustomFieldOrder.NameAsc)
for {
fs <- backend.customFields.findAll(user.account.collective, param.map(_.q))
fs <- backend.customFields.findAll(
user.account.collective,
param.map(_.q),
order
)
res <- Ok(CustomFieldList(fs.map(convertField).toList))
} yield res

View File

@ -12,6 +12,7 @@ import cats.implicits._
import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OEquipment
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
@ -29,9 +30,13 @@ object EquipmentRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root :? QueryParam.QueryOpt(q) =>
case GET -> Root :? QueryParam.QueryOpt(q) :? QueryParam.EquipSort(sort) =>
for {
data <- backend.equipment.findAll(user.account, q.map(_.q))
data <- backend.equipment.findAll(
user.account,
q.map(_.q),
sort.getOrElse(OEquipment.EquipmentOrder.NameAsc)
)
resp <- Ok(EquipmentList(data.map(mkEquipment).toList))
} yield resp

View File

@ -31,11 +31,13 @@ object FolderRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root :? QueryParam.QueryOpt(q) :? QueryParam.OwningOpt(owning) =>
case GET -> Root :? QueryParam.QueryOpt(q) :?
QueryParam.OwningOpt(owning) +& QueryParam.FolderSort(sort) =>
val order = sort.getOrElse(OFolder.FolderOrder.NameAsc)
val login =
owning.filter(identity).map(_ => user.account.user)
for {
all <- backend.folder.findAll(user.account, login, q.map(_.q))
all <- backend.folder.findAll(user.account, login, q.map(_.q), order)
resp <- Ok(FolderList(all.map(mkFolder).toList))
} yield resp

View File

@ -12,6 +12,7 @@ import cats.implicits._
import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OOrganization.OrganizationOrder
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
@ -29,15 +30,21 @@ object OrganizationRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root :? QueryParam.FullOpt(full) +& QueryParam.QueryOpt(q) =>
case GET -> Root :? QueryParam.FullOpt(full) +&
QueryParam.QueryOpt(q) +& QueryParam.OrgSort(sort) =>
val order = sort.getOrElse(OrganizationOrder.NameAsc)
if (full.getOrElse(false))
for {
data <- backend.organization.findAllOrg(user.account, q.map(_.q))
data <- backend.organization.findAllOrg(
user.account,
q.map(_.q),
order
)
resp <- Ok(OrganizationList(data.map(mkOrg).toList))
} yield resp
else
for {
data <- backend.organization.findAllOrgRefs(user.account, q.map(_.q))
data <- backend.organization.findAllOrgRefs(user.account, q.map(_.q), order)
resp <- Ok(ReferenceList(data.map(mkIdName).toList))
} yield resp

View File

@ -12,6 +12,7 @@ import cats.implicits._
import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OOrganization
import docspell.common.Ident
import docspell.common.syntax.all._
import docspell.restapi.model._
@ -32,15 +33,25 @@ object PersonRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root :? QueryParam.FullOpt(full) +& QueryParam.QueryOpt(q) =>
case GET -> Root :? QueryParam.FullOpt(full) +&
QueryParam.QueryOpt(q) +& QueryParam.PersonSort(sort) =>
val order = sort.getOrElse(OOrganization.PersonOrder.NameAsc)
if (full.getOrElse(false))
for {
data <- backend.organization.findAllPerson(user.account, q.map(_.q))
data <- backend.organization.findAllPerson(
user.account,
q.map(_.q),
order
)
resp <- Ok(PersonList(data.map(mkPerson).toList))
} yield resp
else
for {
data <- backend.organization.findAllPersonRefs(user.account, q.map(_.q))
data <- backend.organization.findAllPersonRefs(
user.account,
q.map(_.q),
order
)
resp <- Ok(ReferenceList(data.map(mkIdName).toList))
} yield resp

View File

@ -11,6 +11,7 @@ import cats.implicits._
import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OTag.TagOrder
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
@ -28,9 +29,13 @@ object TagRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root :? QueryParam.QueryOpt(q) =>
case GET -> Root :? QueryParam.QueryOpt(q) :? QueryParam.TagSort(sort) =>
for {
all <- backend.tag.findAll(user.account, q.map(_.q))
all <- backend.tag.findAll(
user.account,
q.map(_.q),
sort.getOrElse(TagOrder.NameAsc)
)
resp <- Ok(TagList(all.size, all.map(mkTag).toList))
} yield resp

View File

@ -12,6 +12,7 @@ case class Column[A](name: String, table: TableDef) {
def cast[B]: Column[B] =
this.asInstanceOf[Column[B]]
}
object Column {}

View File

@ -303,6 +303,12 @@ trait DSL extends DoobieMeta {
def as(otherCol: Column[_]): SelectExpr =
SelectExpr.SelectFun(dbf, Some(otherCol.name))
def asc: OrderBy =
OrderBy(SelectExpr.SelectFun(dbf, None), OrderBy.OrderType.Asc)
def desc: OrderBy =
OrderBy(SelectExpr.SelectFun(dbf, None), OrderBy.OrderType.Desc)
def ===[A](value: A)(implicit P: Put[A]): Condition =
Condition.CompareFVal(dbf.s, Operator.Eq, value)

View File

@ -135,6 +135,9 @@ object Select {
def orderBy(ob: OrderBy, obs: OrderBy*): Ordered =
Ordered(this, ob, obs.toVector)
def orderBy(ob: Nel[OrderBy]): Ordered =
Ordered(this, ob.head, ob.tail.toVector)
}
case class RawSelect(fragment: Fragment) extends Select {

View File

@ -24,9 +24,10 @@ object QCustomField {
def findAllLike(
coll: Ident,
nameQuery: Option[String]
nameQuery: Option[String],
order: RCustomField.Table => Nel[OrderBy]
): ConnectionIO[Vector[CustomFieldData]] =
findFragment(coll, nameQuery, None).build.query[CustomFieldData].to[Vector]
findFragment(coll, nameQuery, None, order).build.query[CustomFieldData].to[Vector]
def findById(field: Ident, collective: Ident): ConnectionIO[Option[CustomFieldData]] =
findFragment(collective, None, field.some).build.query[CustomFieldData].option
@ -34,7 +35,8 @@ object QCustomField {
private def findFragment(
coll: Ident,
nameQuery: Option[String],
fieldId: Option[Ident]
fieldId: Option[Ident],
order: RCustomField.Table => Nel[OrderBy] = t => Nel.of(t.name.asc)
): Select = {
val nameFilter = nameQuery.map { q =>
f.name.likes(q) || (f.label.isNotNull && f.label.like(q))
@ -46,7 +48,7 @@ object QCustomField {
.leftJoin(v, f.id === v.field),
f.cid === coll &&? nameFilter &&? fieldId.map(fid => f.id === fid),
GroupBy(f.all)
)
).orderBy(order(f))
}
final case class FieldValue(

View File

@ -7,6 +7,7 @@
package docspell.store.queries
import cats.data.OptionT
import cats.data.{NonEmptyList => Nel}
import cats.implicits._
import docspell.common._
@ -156,8 +157,11 @@ object QFolder {
).query[IdRef].to[Vector]
(for {
folder <- OptionT(findAll(account, Some(id), None, None).map(_.headOption))
memb <- OptionT.liftF(memberQ)
folder <- OptionT(
findAll(account, Some(id), None, None, (ft, _) => Nel.of(ft.name.asc))
.map(_.headOption)
)
memb <- OptionT.liftF(memberQ)
} yield folder.withMembers(memb.toList)).value
}
@ -165,7 +169,8 @@ object QFolder {
account: AccountId,
idQ: Option[Ident],
ownerLogin: Option[Ident],
nameQ: Option[String]
nameQ: Option[String],
order: (RFolder.Table, RUser.Table) => Nel[OrderBy]
): ConnectionIO[Vector[FolderItem]] = {
// with memberlogin as
// (select m.folder_id,u.login
@ -239,7 +244,7 @@ object QFolder {
nameQ.map(q => folder.name.like(s"%${q.toLowerCase}%")) &&?
ownerLogin.map(login => user.login === login)
)
).orderBy(folder.name.asc)
).orderBy(order(folder, user))
).build.query[FolderItem].to[Vector]
}

View File

@ -6,7 +6,7 @@
package docspell.store.queries
import cats.data.NonEmptyList
import cats.data.{NonEmptyList => Nel}
import cats.implicits._
import fs2._
@ -27,7 +27,7 @@ object QOrganization {
def findOrgAndContact(
coll: Ident,
query: Option[String],
order: ROrganization.Table => Column[_]
order: ROrganization.Table => Nel[OrderBy]
): Stream[ConnectionIO, (ROrganization, Vector[RContact])] = {
val valFilter = query.map { q =>
val v = s"%$q%"
@ -74,18 +74,18 @@ object QOrganization {
def findPersonAndContact(
coll: Ident,
query: Option[String],
order: RPerson.Table => Column[_]
order: (RPerson.Table, ROrganization.Table) => Nel[OrderBy]
): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = {
val valFilter = query
.map(s => s"%$s%")
.map(v => c.value.like(v) || p.name.like(v) || p.notes.like(v))
.map(v => c.value.like(v) || p.name.like(v) || org.name.like(v) || p.notes.like(v))
val sql = Select(
select(p.all, org.all, c.all),
from(p)
.leftJoin(org, org.oid === p.oid)
.leftJoin(c, c.personId === p.pid),
p.cid === coll &&? valFilter
).orderBy(order(p))
).orderBy(order(p, org))
sql.build
.query[(RPerson, Option[ROrganization], Option[RContact])]
@ -128,7 +128,7 @@ object QOrganization {
coll: Ident,
value: String,
ck: Option[ContactKind],
use: Option[NonEmptyList[PersonUse]]
use: Option[Nel[PersonUse]]
): Stream[ConnectionIO, RPerson] =
runDistinct(
select(p.all),

View File

@ -87,7 +87,7 @@ object REquipment {
def findAll(
coll: Ident,
nameQ: Option[String],
order: Table => Column[_]
order: Table => NonEmptyList[OrderBy]
): ConnectionIO[Vector[REquipment]] = {
val t = Table(None)

View File

@ -7,7 +7,7 @@
package docspell.store.records
import cats.Eq
import cats.data.NonEmptyList
import cats.data.{NonEmptyList => Nel}
import fs2.Stream
import docspell.common.{IdRef, _}
@ -52,7 +52,7 @@ object ROrganization {
val shortName = Column[String]("short_name", this)
val use = Column[OrgUse]("org_use", this)
val all =
NonEmptyList.of[Column[_]](
Nel.of[Column[_]](
oid,
cid,
name,
@ -122,7 +122,7 @@ object ROrganization {
def findLike(
coll: Ident,
orgName: String,
use: NonEmptyList[OrgUse]
use: Nel[OrgUse]
): ConnectionIO[Vector[IdRef]] =
run(
select(T.oid, T.name),
@ -163,7 +163,7 @@ object ROrganization {
def findAllRef(
coll: Ident,
nameQ: Option[String],
order: Table => Column[_]
order: Table => Nel[OrderBy]
): ConnectionIO[Vector[IdRef]] = {
val nameFilter = nameQ.map(s =>
T.name.like(s"%${s.toLowerCase}%") || T.shortName.like(s"%${s.toLowerCase}%")

View File

@ -7,7 +7,7 @@
package docspell.store.records
import cats.Eq
import cats.data.NonEmptyList
import cats.data.{NonEmptyList => Nel}
import cats.effect._
import fs2.Stream
@ -52,7 +52,7 @@ object RPerson {
val updated = Column[Timestamp]("updated", this)
val oid = Column[Ident]("oid", this)
val use = Column[PersonUse]("person_use", this)
val all = NonEmptyList.of[Column[_]](
val all = Nel.of[Column[_]](
pid,
cid,
name,
@ -122,7 +122,7 @@ object RPerson {
def findLike(
coll: Ident,
personName: String,
use: NonEmptyList[PersonUse]
use: Nel[PersonUse]
): ConnectionIO[Vector[IdRef]] =
run(
select(T.pid, T.name),
@ -134,7 +134,7 @@ object RPerson {
coll: Ident,
contactKind: ContactKind,
value: String,
use: NonEmptyList[PersonUse]
use: Nel[PersonUse]
): ConnectionIO[Vector[IdRef]] = {
val p = RPerson.as("p")
val c = RContact.as("c")
@ -162,7 +162,7 @@ object RPerson {
def findAllRef(
coll: Ident,
nameQ: Option[String],
order: Table => Column[_]
order: Table => Nel[OrderBy]
): ConnectionIO[Vector[IdRef]] = {
val nameFilter = nameQ.map(s => T.name.like(s"%${s.toLowerCase}%"))
@ -176,7 +176,7 @@ object RPerson {
DML.delete(T, T.pid === personId && T.cid === coll)
def findOrganization(ids: Set[Ident]): ConnectionIO[Vector[PersonRef]] =
NonEmptyList.fromList(ids.toList) match {
Nel.fromList(ids.toList) match {
case Some(nel) =>
run(select(T.pid, T.name, T.oid), from(T), T.pid.in(nel))
.query[PersonRef]

View File

@ -75,10 +75,11 @@ object RTag {
def findAll(
coll: Ident,
nameQ: Option[String],
order: Table => Column[_]
query: Option[String],
order: Table => NonEmptyList[OrderBy]
): ConnectionIO[Vector[RTag]] = {
val nameFilter = nameQ.map(s => T.name.like(s"%${s.toLowerCase}%"))
val nameFilter =
query.map(_.toLowerCase).map(s => T.name.like(s"%$s%") || T.category.like(s"%$s%"))
val sql =
Select(select(T.all), from(T), T.cid === coll &&? nameFilter).orderBy(order(T))
sql.build.query[RTag].to[Vector]

View File

@ -216,8 +216,14 @@ import Api.Model.UserList exposing (UserList)
import Api.Model.UserPass exposing (UserPass)
import Api.Model.VersionInfo exposing (VersionInfo)
import Data.ContactType exposing (ContactType)
import Data.CustomFieldOrder exposing (CustomFieldOrder)
import Data.EquipmentOrder exposing (EquipmentOrder)
import Data.Flags exposing (Flags)
import Data.FolderOrder exposing (FolderOrder)
import Data.OrganizationOrder exposing (OrganizationOrder)
import Data.PersonOrder exposing (PersonOrder)
import Data.Priority exposing (Priority)
import Data.TagOrder exposing (TagOrder)
import Data.UiSettings exposing (UiSettings)
import File exposing (File)
import Http
@ -291,13 +297,15 @@ putCustomValue flags item fieldValue receive =
}
getCustomFields : Flags -> String -> (Result Http.Error CustomFieldList -> msg) -> Cmd msg
getCustomFields flags query receive =
getCustomFields : Flags -> String -> CustomFieldOrder -> (Result Http.Error CustomFieldList -> msg) -> Cmd msg
getCustomFields flags query order receive =
Http2.authGet
{ url =
flags.config.baseUrl
++ "/api/v1/sec/customfield?q="
++ Url.percentEncode query
++ "&sort="
++ Data.CustomFieldOrder.asString order
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.CustomFieldList.decoder
}
@ -402,13 +410,21 @@ getFolderDetail flags id receive =
}
getFolders : Flags -> String -> Bool -> (Result Http.Error FolderList -> msg) -> Cmd msg
getFolders flags query owningOnly receive =
getFolders :
Flags
-> String
-> FolderOrder
-> Bool
-> (Result Http.Error FolderList -> msg)
-> Cmd msg
getFolders flags query order owningOnly receive =
Http2.authGet
{ url =
flags.config.baseUrl
++ "/api/v1/sec/folder?q="
++ Url.percentEncode query
++ "&sort="
++ Data.FolderOrder.asString order
++ (if owningOnly then
"&owning=true"
@ -1109,10 +1125,15 @@ getContacts flags kind q receive =
--- Tags
getTags : Flags -> String -> (Result Http.Error TagList -> msg) -> Cmd msg
getTags flags query receive =
getTags : Flags -> String -> TagOrder -> (Result Http.Error TagList -> msg) -> Cmd msg
getTags flags query order receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/tag?q=" ++ Url.percentEncode query
{ url =
flags.config.baseUrl
++ "/api/v1/sec/tag?sort="
++ Data.TagOrder.asString order
++ "&q="
++ Url.percentEncode query
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.TagList.decoder
}
@ -1148,10 +1169,15 @@ deleteTag flags tag receive =
--- Equipments
getEquipments : Flags -> String -> (Result Http.Error EquipmentList -> msg) -> Cmd msg
getEquipments flags query receive =
getEquipments : Flags -> String -> EquipmentOrder -> (Result Http.Error EquipmentList -> msg) -> Cmd msg
getEquipments flags query order receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/equipment?q=" ++ Url.percentEncode query
{ url =
flags.config.baseUrl
++ "/api/v1/sec/equipment?q="
++ Url.percentEncode query
++ "&sort="
++ Data.EquipmentOrder.asString order
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.EquipmentList.decoder
}
@ -1214,10 +1240,20 @@ getOrgFull id flags receive =
}
getOrganizations : Flags -> String -> (Result Http.Error OrganizationList -> msg) -> Cmd msg
getOrganizations flags query receive =
getOrganizations :
Flags
-> String
-> OrganizationOrder
-> (Result Http.Error OrganizationList -> msg)
-> Cmd msg
getOrganizations flags query order receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/organization?full=true&q=" ++ Url.percentEncode query
{ url =
flags.config.baseUrl
++ "/api/v1/sec/organization?full=true&q="
++ Url.percentEncode query
++ "&sort="
++ Data.OrganizationOrder.asString order
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.OrganizationList.decoder
}
@ -1271,10 +1307,15 @@ getPersonFull id flags receive =
}
getPersons : Flags -> String -> (Result Http.Error PersonList -> msg) -> Cmd msg
getPersons flags query receive =
getPersons : Flags -> String -> PersonOrder -> (Result Http.Error PersonList -> msg) -> Cmd msg
getPersons flags query order receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/person?full=true&q=" ++ Url.percentEncode query
{ url =
flags.config.baseUrl
++ "/api/v1/sec/person?full=true&q="
++ Url.percentEncode query
++ "&sort="
++ Data.PersonOrder.asString order
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.PersonList.decoder
}

View File

@ -25,6 +25,7 @@ import Data.CalEvent exposing (CalEvent)
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.ListType exposing (ListType)
import Data.TagOrder
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
@ -90,7 +91,7 @@ init flags sett =
Comp.FixedDropdown.init Data.ListType.all
}
, Cmd.batch
[ Api.getTags flags "" GetTagsResp
[ Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
, Cmd.map ScheduleMsg cec
]
)

View File

@ -21,6 +21,7 @@ import Comp.Basic as B
import Comp.CustomFieldForm
import Comp.CustomFieldTable
import Comp.MenuBar as MB
import Data.CustomFieldOrder exposing (CustomFieldOrder)
import Data.Flags exposing (Flags)
import Html exposing (..)
import Html.Attributes exposing (..)
@ -36,6 +37,7 @@ type alias Model =
, fields : List CustomField
, query : String
, loading : Bool
, order : CustomFieldOrder
}
@ -54,13 +56,14 @@ empty =
, fields = []
, query = ""
, loading = False
, order = Data.CustomFieldOrder.LabelAsc
}
init : Flags -> ( Model, Cmd Msg )
init flags =
( empty
, Api.getCustomFields flags empty.query CustomFieldListResp
, loadFields flags empty
)
@ -68,14 +71,22 @@ init flags =
--- Update
loadFields : Flags -> Model -> Cmd Msg
loadFields flags model =
Api.getCustomFields flags model.query model.order CustomFieldListResp
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update flags msg model =
case msg of
TableMsg lm ->
let
( tm, action ) =
( tm, action, maybeOrder ) =
Comp.CustomFieldTable.update lm model.tableModel
newOrder =
Maybe.withDefault model.order maybeOrder
detail =
case action of
Comp.CustomFieldTable.EditAction item ->
@ -83,8 +94,22 @@ update flags msg model =
Comp.CustomFieldTable.NoAction ->
model.detailModel
newModel =
{ model
| tableModel = tm
, detailModel = detail
, order = newOrder
}
( m1, c1 ) =
if model.order == newOrder then
( newModel, Cmd.none )
else
( newModel, loadFields flags newModel )
in
( { model | tableModel = tm, detailModel = detail }, Cmd.none )
( m1, c1 )
DetailMsg lm ->
case model.detailModel of
@ -95,7 +120,7 @@ update flags msg model =
cmd =
if back then
Api.getCustomFields flags model.query CustomFieldListResp
loadFields flags model
else
Cmd.none
@ -118,8 +143,12 @@ update flags msg model =
( model, Cmd.none )
SetQuery str ->
( { model | query = str }
, Api.getCustomFields flags str CustomFieldListResp
let
newModel =
{ model | query = str }
in
( newModel
, loadFields flags newModel
)
CustomFieldListResp (Ok sl) ->
@ -207,6 +236,7 @@ viewTable2 texts model =
}
, Html.map TableMsg
(Comp.CustomFieldTable.view2 texts.fieldTable
model.order
model.tableModel
model.fields
)

View File

@ -29,6 +29,7 @@ import Api.Model.ItemFieldValue exposing (ItemFieldValue)
import Comp.CustomFieldInput
import Comp.FixedDropdown
import Data.CustomFieldChange exposing (CustomFieldChange(..))
import Data.CustomFieldOrder
import Data.CustomFieldType
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
@ -116,7 +117,7 @@ init flags =
initCmd : Flags -> Cmd Msg
initCmd flags =
Api.getCustomFields flags "" CustomFieldResp
Api.getCustomFields flags "" Data.CustomFieldOrder.LabelAsc CustomFieldResp
setValues : List ItemFieldValue -> Msg

View File

@ -16,8 +16,10 @@ module Comp.CustomFieldTable exposing
import Api.Model.CustomField exposing (CustomField)
import Comp.Basic as B
import Data.CustomFieldOrder exposing (CustomFieldOrder)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Messages.Comp.CustomFieldTable exposing (Texts)
import Styles as S
@ -28,6 +30,7 @@ type alias Model =
type Msg
= EditItem CustomField
| ToggleOrder CustomFieldOrder
type Action
@ -35,31 +38,88 @@ type Action
| EditAction CustomField
type Header
= Label
| Format
init : Model
init =
{}
update : Msg -> Model -> ( Model, Action )
update : Msg -> Model -> ( Model, Action, Maybe CustomFieldOrder )
update msg model =
case msg of
EditItem item ->
( model, EditAction item )
( model, EditAction item, Nothing )
ToggleOrder order ->
( model, NoAction, Just order )
newOrder : Header -> CustomFieldOrder -> CustomFieldOrder
newOrder header current =
case ( header, current ) of
( Label, Data.CustomFieldOrder.LabelAsc ) ->
Data.CustomFieldOrder.LabelDesc
( Label, _ ) ->
Data.CustomFieldOrder.LabelAsc
( Format, Data.CustomFieldOrder.FormatAsc ) ->
Data.CustomFieldOrder.FormatDesc
( Format, _ ) ->
Data.CustomFieldOrder.FormatAsc
--- View2
view2 : Texts -> Model -> List CustomField -> Html Msg
view2 texts _ items =
view2 : Texts -> CustomFieldOrder -> Model -> List CustomField -> Html Msg
view2 texts order _ items =
let
labelSortIcon =
case order of
Data.CustomFieldOrder.LabelAsc ->
"fa fa-sort-alpha-up"
Data.CustomFieldOrder.LabelDesc ->
"fa fa-sort-alpha-down-alt"
_ ->
"invisible fa fa-sort-alpha-up"
formatSortIcon =
case order of
Data.CustomFieldOrder.FormatAsc ->
"fa fa-sort-alpha-up"
Data.CustomFieldOrder.FormatDesc ->
"fa fa-sort-alpha-down-alt"
_ ->
"invisible fa fa-sort-alpha-up"
in
div []
[ table [ class S.tableMain ]
[ thead []
[ tr []
[ th [] []
, th [ class "text-left" ] [ text texts.nameLabel ]
, th [ class "text-left" ] [ text texts.format ]
, th [ class "text-left" ]
[ a [ href "#", onClick (ToggleOrder <| newOrder Label order) ]
[ i [ class labelSortIcon, class "mr-1" ] []
, text texts.nameLabel
]
]
, th [ class "text-left" ]
[ a [ href "#", onClick (ToggleOrder <| newOrder Format order) ]
[ i [ class formatSortIcon, class "mr-1" ] []
, text texts.format
]
]
, th [ class "text-center hidden sm:table-cell" ] [ text texts.usageCount ]
, th [ class "text-center hidden sm:table-cell" ] [ text texts.basics.created ]
]

View File

@ -22,6 +22,7 @@ import Comp.EquipmentForm
import Comp.EquipmentTable
import Comp.MenuBar as MB
import Comp.YesNoDimmer
import Data.EquipmentOrder exposing (EquipmentOrder)
import Data.Flags exposing (Flags)
import Html exposing (..)
import Html.Attributes exposing (..)
@ -40,6 +41,7 @@ type alias Model =
, loading : Bool
, deleteConfirm : Comp.YesNoDimmer.Model
, query : String
, order : EquipmentOrder
}
@ -64,6 +66,7 @@ emptyModel =
, loading = False
, deleteConfirm = Comp.YesNoDimmer.emptyModel
, query = ""
, order = Data.EquipmentOrder.NameAsc
}
@ -86,9 +89,12 @@ update flags msg model =
case msg of
TableMsg m ->
let
( tm, tc ) =
( tm, tc, maybeOrder ) =
Comp.EquipmentTable.update flags m model.tableModel
newOrder =
Maybe.withDefault model.order maybeOrder
( m2, c2 ) =
( { model
| tableModel = tm
@ -99,6 +105,7 @@ update flags msg model =
else
model.formError
, order = newOrder
}
, Cmd.map TableMsg tc
)
@ -110,8 +117,15 @@ update flags msg model =
Nothing ->
( m2, Cmd.none )
( m4, c4 ) =
if model.order == newOrder then
( m3, Cmd.none )
else
update flags LoadEquipments m3
in
( m3, Cmd.batch [ c2, c3 ] )
( m4, Cmd.batch [ c2, c3, c4 ] )
FormMsg m ->
let
@ -121,7 +135,7 @@ update flags msg model =
( { model | formModel = m2 }, Cmd.map FormMsg c2 )
LoadEquipments ->
( { model | loading = True }, Api.getEquipments flags "" EquipmentResp )
( { model | loading = True }, Api.getEquipments flags model.query model.order EquipmentResp )
EquipmentResp (Ok equipments) ->
let
@ -211,7 +225,7 @@ update flags msg model =
m =
{ model | query = str }
in
( m, Api.getEquipments flags str EquipmentResp )
( m, Api.getEquipments flags str model.order EquipmentResp )
@ -251,6 +265,7 @@ viewTable2 texts model =
}
, Html.map TableMsg
(Comp.EquipmentTable.view2 texts.equipmentTable
model.order
model.tableModel
)
, div

View File

@ -15,10 +15,12 @@ module Comp.EquipmentTable exposing
import Api.Model.Equipment exposing (Equipment)
import Comp.Basic as B
import Data.EquipmentOrder exposing (EquipmentOrder)
import Data.EquipmentUse
import Data.Flags exposing (Flags)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Messages.Comp.EquipmentTable exposing (Texts)
import Styles as S
@ -40,27 +42,50 @@ type Msg
= SetEquipments (List Equipment)
| Select Equipment
| Deselect
| ToggleOrder EquipmentOrder
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe EquipmentOrder )
update _ msg model =
case msg of
SetEquipments list ->
( { model | equips = list, selected = Nothing }, Cmd.none )
( { model | equips = list, selected = Nothing }, Cmd.none, Nothing )
Select equip ->
( { model | selected = Just equip }, Cmd.none )
( { model | selected = Just equip }, Cmd.none, Nothing )
Deselect ->
( { model | selected = Nothing }, Cmd.none )
( { model | selected = Nothing }, Cmd.none, Nothing )
ToggleOrder order ->
( model, Cmd.none, Just order )
newOrder : EquipmentOrder -> EquipmentOrder
newOrder current =
case current of
Data.EquipmentOrder.NameAsc ->
Data.EquipmentOrder.NameDesc
Data.EquipmentOrder.NameDesc ->
Data.EquipmentOrder.NameAsc
--- View2
view2 : Texts -> Model -> Html Msg
view2 texts model =
view2 : Texts -> EquipmentOrder -> Model -> Html Msg
view2 texts order model =
let
nameSortIcon =
case order of
Data.EquipmentOrder.NameAsc ->
"fa fa-sort-alpha-up"
Data.EquipmentOrder.NameDesc ->
"fa fa-sort-alpha-down-alt"
in
table [ class S.tableMain ]
[ thead []
[ tr []
@ -68,7 +93,12 @@ view2 texts model =
, th [ class "text-left pr-1 md:px-2 w-20" ]
[ text texts.use
]
, th [ class "text-left" ] [ text texts.basics.name ]
, th [ class "text-left" ]
[ a [ href "#", onClick (ToggleOrder <| newOrder order) ]
[ i [ class nameSortIcon, class "mr-1" ] []
, text texts.basics.name
]
]
]
]
, tbody []

View File

@ -24,6 +24,7 @@ import Comp.FolderDetail
import Comp.FolderTable
import Comp.MenuBar as MB
import Data.Flags exposing (Flags)
import Data.FolderOrder exposing (FolderOrder)
import Html exposing (..)
import Html.Attributes exposing (..)
import Http
@ -39,6 +40,7 @@ type alias Model =
, query : String
, owningOnly : Bool
, loading : Bool
, order : FolderOrder
}
@ -62,6 +64,7 @@ empty =
, query = ""
, owningOnly = True
, loading = False
, order = Data.FolderOrder.NameAsc
}
@ -70,7 +73,7 @@ init flags =
( empty
, Cmd.batch
[ Api.getUsers flags UserListResp
, Api.getFolders flags empty.query empty.owningOnly FolderListResp
, loadFolders flags empty
]
)
@ -79,23 +82,41 @@ init flags =
--- Update
loadFolders : Flags -> Model -> Cmd Msg
loadFolders flags model =
Api.getFolders flags model.query model.order model.owningOnly FolderListResp
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update flags msg model =
case msg of
TableMsg lm ->
let
( tm, action ) =
( tm, action, maybeOrder ) =
Comp.FolderTable.update lm model.tableModel
cmd =
newOrder =
Maybe.withDefault model.order maybeOrder
newModel =
{ model | tableModel = tm, order = newOrder }
detailCmd =
case action of
Comp.FolderTable.EditAction item ->
Api.getFolderDetail flags item.id FolderDetailResp
Comp.FolderTable.NoAction ->
Cmd.none
refreshCmd =
if model.order == newOrder then
Cmd.none
else
loadFolders flags newModel
in
( { model | tableModel = tm }, cmd )
( newModel, Cmd.batch [ detailCmd, refreshCmd ] )
DetailMsg lm ->
case model.detailModel of
@ -106,7 +127,7 @@ update flags msg model =
cmd =
if back then
Api.getFolders flags model.query model.owningOnly FolderListResp
loadFolders flags model
else
Cmd.none
@ -129,17 +150,24 @@ update flags msg model =
( model, Cmd.none )
SetQuery str ->
( { model | query = str }
, Api.getFolders flags str model.owningOnly FolderListResp
let
nm =
{ model | query = str }
in
( nm
, loadFolders flags nm
)
ToggleOwningOnly ->
let
newOwning =
not model.owningOnly
nm =
{ model | owningOnly = newOwning }
in
( { model | owningOnly = newOwning }
, Api.getFolders flags model.query newOwning FolderListResp
( nm
, loadFolders flags nm
)
UserListResp (Ok ul) ->
@ -241,6 +269,7 @@ viewTable2 texts model =
, Html.map TableMsg
(Comp.FolderTable.view2
texts.folderTable
model.order
model.tableModel
model.folders
)

View File

@ -16,8 +16,10 @@ module Comp.FolderTable exposing
import Api.Model.FolderItem exposing (FolderItem)
import Comp.Basic as B
import Data.FolderOrder exposing (FolderOrder)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Messages.Comp.FolderTable exposing (Texts)
import Styles as S
@ -28,6 +30,7 @@ type alias Model =
type Msg
= EditItem FolderItem
| ToggleOrder FolderOrder
type Action
@ -35,32 +38,87 @@ type Action
| EditAction FolderItem
type Header
= Name
| Owner
init : Model
init =
{}
update : Msg -> Model -> ( Model, Action )
update : Msg -> Model -> ( Model, Action, Maybe FolderOrder )
update msg model =
case msg of
EditItem item ->
( model, EditAction item )
( model, EditAction item, Nothing )
ToggleOrder order ->
( model, NoAction, Just order )
newOrder : Header -> FolderOrder -> FolderOrder
newOrder header current =
case ( header, current ) of
( Name, Data.FolderOrder.NameAsc ) ->
Data.FolderOrder.NameDesc
( Name, _ ) ->
Data.FolderOrder.NameAsc
( Owner, Data.FolderOrder.OwnerAsc ) ->
Data.FolderOrder.OwnerDesc
( Owner, _ ) ->
Data.FolderOrder.OwnerAsc
--- View2
view2 : Texts -> Model -> List FolderItem -> Html Msg
view2 texts _ items =
view2 : Texts -> FolderOrder -> Model -> List FolderItem -> Html Msg
view2 texts order _ items =
let
nameSortIcon =
case order of
Data.FolderOrder.NameAsc ->
"fa fa-sort-alpha-up"
Data.FolderOrder.NameDesc ->
"fa fa-sort-alpha-down-alt"
_ ->
"invisible fa fa-sort-alpha-up"
ownerSortIcon =
case order of
Data.FolderOrder.OwnerAsc ->
"fa fa-sort-alpha-up"
Data.FolderOrder.OwnerDesc ->
"fa fa-sort-alpha-down-alt"
_ ->
"invisible fa fa-sort-alpha-up"
in
table [ class S.tableMain ]
[ thead []
[ tr []
[ th [ class "w-px whitespace-nowrap pr-1 md:pr-3" ] []
, th [ class "text-left" ]
[ text texts.basics.name
[ a [ href "#", onClick (ToggleOrder <| newOrder Name order) ]
[ i [ class nameSortIcon, class "mr-1" ] []
, text texts.basics.name
]
]
, th [ class "text-left hidden sm:table-cell" ]
[ a [ href "#", onClick (ToggleOrder <| newOrder Owner order) ]
[ i [ class ownerSortIcon, class "mr-1" ] []
, text texts.owner
]
]
, th [ class "text-left hidden sm:table-cell" ] [ text "Owner" ]
, th [ class "text-center" ]
[ span [ class "hidden sm:inline" ]
[ text texts.memberCount

View File

@ -35,10 +35,14 @@ import Comp.Tabs as TB
import Data.CustomFieldChange exposing (CustomFieldChange(..))
import Data.Direction exposing (Direction)
import Data.DropdownStyle
import Data.EquipmentOrder
import Data.Fields
import Data.Flags exposing (Flags)
import Data.FolderOrder
import Data.Icons as Icons
import Data.PersonOrder
import Data.PersonUse
import Data.TagOrder
import Data.UiSettings exposing (UiSettings)
import DatePicker exposing (DatePicker)
import Html exposing (..)
@ -157,11 +161,11 @@ loadModel flags =
Comp.DatePicker.init
in
Cmd.batch
[ Api.getTags flags "" GetTagsResp
[ Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
, Api.getOrgLight flags GetOrgResp
, Api.getPersons flags "" GetPersonResp
, Api.getEquipments flags "" GetEquipResp
, Api.getFolders flags "" False GetFolderResp
, Api.getPersons flags "" Data.PersonOrder.NameAsc GetPersonResp
, Api.getEquipments flags "" Data.EquipmentOrder.NameAsc GetEquipResp
, Api.getFolders flags "" Data.FolderOrder.NameAsc False GetFolderResp
, Cmd.map CustomFieldMsg (Comp.CustomFieldMultiInput.initCmd flags)
, Cmd.map ItemDatePickerMsg dpc
, Cmd.map DueDatePickerMsg dpc

View File

@ -59,10 +59,14 @@ import Comp.PersonForm
import Comp.SentMails
import Data.CustomFieldChange exposing (CustomFieldChange(..))
import Data.Direction
import Data.EquipmentOrder
import Data.Fields exposing (Field)
import Data.Flags exposing (Flags)
import Data.FolderOrder
import Data.ItemNav exposing (ItemNav)
import Data.PersonOrder
import Data.PersonUse
import Data.TagOrder
import Data.UiSettings exposing (UiSettings)
import DatePicker
import Dict
@ -265,7 +269,7 @@ update key flags inav settings msg model =
, getOptions flags
, proposalCmd
, Api.getSentMails flags item.id SentMailsResp
, Api.getPersons flags "" GetPersonResp
, Api.getPersons flags "" Data.PersonOrder.NameAsc GetPersonResp
, Cmd.map CustomFieldMsg (Comp.CustomFieldMultiInput.initCmd flags)
]
, sub =
@ -1642,11 +1646,11 @@ update key flags inav settings msg model =
getOptions : Flags -> Cmd Msg
getOptions flags =
Cmd.batch
[ Api.getTags flags "" GetTagsResp
[ Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
, Api.getOrgLight flags GetOrgResp
, Api.getPersons flags "" GetPersonResp
, Api.getEquipments flags "" GetEquipResp
, Api.getFolders flags "" False GetFolderResp
, Api.getPersons flags "" Data.PersonOrder.NameAsc GetPersonResp
, Api.getEquipments flags "" Data.EquipmentOrder.NameAsc GetEquipResp
, Api.getFolders flags "" Data.FolderOrder.NameAsc False GetFolderResp
]

View File

@ -30,6 +30,7 @@ import Comp.YesNoDimmer
import Data.CalEvent exposing (CalEvent)
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.TagOrder
import Data.UiSettings exposing (UiSettings)
import Data.Validated exposing (Validated(..))
import Html exposing (..)
@ -182,7 +183,7 @@ init flags =
}
, Cmd.batch
[ Api.getMailSettings flags "" ConnResp
, Api.getTags flags "" GetTagsResp
, Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
, Cmd.map CalEventMsg scmd
]
)

View File

@ -23,6 +23,7 @@ import Comp.OrgForm
import Comp.OrgTable
import Comp.YesNoDimmer
import Data.Flags exposing (Flags)
import Data.OrganizationOrder exposing (OrganizationOrder)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
@ -41,6 +42,7 @@ type alias Model =
, loading : Bool
, deleteConfirm : Comp.YesNoDimmer.Model
, query : String
, order : OrganizationOrder
}
@ -65,6 +67,7 @@ emptyModel =
, loading = False
, deleteConfirm = Comp.YesNoDimmer.emptyModel
, query = ""
, order = Data.OrganizationOrder.NameAsc
}
@ -87,7 +90,7 @@ update flags msg model =
case msg of
TableMsg m ->
let
( tm, tc ) =
( tm, tc, maybeOrder ) =
Comp.OrgTable.update flags m model.tableModel
( m2, c2 ) =
@ -100,6 +103,7 @@ update flags msg model =
else
model.formError
, order = Maybe.withDefault model.order maybeOrder
}
, Cmd.map TableMsg tc
)
@ -111,8 +115,15 @@ update flags msg model =
Nothing ->
( m2, Cmd.none )
( m4, c4 ) =
if maybeOrder /= Nothing && maybeOrder /= Just model.order then
update flags LoadOrgs m3
else
( m3, Cmd.none )
in
( m3, Cmd.batch [ c2, c3 ] )
( m4, Cmd.batch [ c2, c3, c4 ] )
FormMsg m ->
let
@ -122,7 +133,13 @@ update flags msg model =
( { model | formModel = m2 }, Cmd.map FormMsg c2 )
LoadOrgs ->
( { model | loading = True }, Api.getOrganizations flags model.query OrgResp )
( { model | loading = True }
, Api.getOrganizations
flags
model.query
model.order
OrgResp
)
OrgResp (Ok orgs) ->
let
@ -212,7 +229,7 @@ update flags msg model =
m =
{ model | query = str }
in
( m, Api.getOrganizations flags str OrgResp )
( m, Api.getOrganizations flags str model.order OrgResp )
@ -250,7 +267,7 @@ viewTable2 texts model =
]
, rootClasses = "mb-4"
}
, Html.map TableMsg (Comp.OrgTable.view2 texts.orgTable model.tableModel)
, Html.map TableMsg (Comp.OrgTable.view2 texts.orgTable model.order model.tableModel)
, B.loadingDimmer
{ active = model.loading
, label = texts.basics.loading

View File

@ -17,8 +17,10 @@ import Api.Model.Organization exposing (Organization)
import Comp.Basic as B
import Data.Flags exposing (Flags)
import Data.OrgUse
import Data.OrganizationOrder exposing (OrganizationOrder)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Messages.Comp.OrgTable exposing (Texts)
import Styles as S
import Util.Address
@ -42,27 +44,50 @@ type Msg
= SetOrgs (List Organization)
| Select Organization
| Deselect
| ToggleOrder OrganizationOrder
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe OrganizationOrder )
update _ msg model =
case msg of
SetOrgs list ->
( { model | orgs = list, selected = Nothing }, Cmd.none )
( { model | orgs = list, selected = Nothing }, Cmd.none, Nothing )
Select equip ->
( { model | selected = Just equip }, Cmd.none )
( { model | selected = Just equip }, Cmd.none, Nothing )
Deselect ->
( { model | selected = Nothing }, Cmd.none )
( { model | selected = Nothing }, Cmd.none, Nothing )
ToggleOrder order ->
( model, Cmd.none, Just order )
newOrder : OrganizationOrder -> OrganizationOrder
newOrder current =
case current of
Data.OrganizationOrder.NameAsc ->
Data.OrganizationOrder.NameDesc
Data.OrganizationOrder.NameDesc ->
Data.OrganizationOrder.NameAsc
--- View2
view2 : Texts -> Model -> Html Msg
view2 texts model =
view2 : Texts -> OrganizationOrder -> Model -> Html Msg
view2 texts order model =
let
nameSortIcon =
case order of
Data.OrganizationOrder.NameAsc ->
"fa fa-sort-alpha-up"
Data.OrganizationOrder.NameDesc ->
"fa fa-sort-alpha-down-alt"
in
table [ class S.tableMain ]
[ thead []
[ tr []
@ -71,7 +96,10 @@ view2 texts model =
[ text texts.use
]
, th [ class "text-left" ]
[ text texts.basics.name
[ a [ href "#", onClick (ToggleOrder <| newOrder order) ]
[ i [ class nameSortIcon, class "mr-1" ] []
, text texts.basics.name
]
]
, th [ class "text-left hidden md:table-cell" ]
[ text texts.address

View File

@ -24,6 +24,7 @@ import Comp.PersonForm
import Comp.PersonTable
import Comp.YesNoDimmer
import Data.Flags exposing (Flags)
import Data.PersonOrder exposing (PersonOrder)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
@ -42,6 +43,7 @@ type alias Model =
, loading : Int
, deleteConfirm : Comp.YesNoDimmer.Model
, query : String
, order : PersonOrder
}
@ -66,6 +68,7 @@ emptyModel =
, loading = 0
, deleteConfirm = Comp.YesNoDimmer.emptyModel
, query = ""
, order = Data.PersonOrder.NameAsc
}
@ -89,9 +92,12 @@ update flags msg model =
case msg of
TableMsg m ->
let
( tm, tc ) =
( tm, tc, maybeOrder ) =
Comp.PersonTable.update flags m model.tableModel
newOrder =
Maybe.withDefault model.order maybeOrder
( m2, c2 ) =
( { model
| tableModel = tm
@ -102,6 +108,7 @@ update flags msg model =
else
model.formError
, order = newOrder
}
, Cmd.map TableMsg tc
)
@ -113,8 +120,15 @@ update flags msg model =
Nothing ->
( m2, Cmd.none )
( m4, c4 ) =
if model.order == newOrder then
( m3, Cmd.none )
else
update flags LoadPersons m3
in
( m3, Cmd.batch [ c2, c3 ] )
( m4, Cmd.batch [ c2, c3, c4 ] )
FormMsg m ->
let
@ -126,7 +140,7 @@ update flags msg model =
LoadPersons ->
( { model | loading = model.loading + 2 }
, Cmd.batch
[ Api.getPersons flags model.query PersonResp
[ Api.getPersons flags model.query model.order PersonResp
, Api.getOrgLight flags GetOrgResp
]
)
@ -244,7 +258,7 @@ update flags msg model =
m =
{ model | query = str }
in
( m, Api.getPersons flags str PersonResp )
( m, Api.getPersons flags str model.order PersonResp )
isLoading : Model -> Bool
@ -287,7 +301,7 @@ viewTable2 texts model =
]
, rootClasses = "mb-4"
}
, Html.map TableMsg (Comp.PersonTable.view2 texts.personTable model.tableModel)
, Html.map TableMsg (Comp.PersonTable.view2 texts.personTable model.order model.tableModel)
, B.loadingDimmer
{ active = isLoading model
, label = texts.basics.loading

View File

@ -16,9 +16,11 @@ module Comp.PersonTable exposing
import Api.Model.Person exposing (Person)
import Comp.Basic as B
import Data.Flags exposing (Flags)
import Data.PersonOrder exposing (PersonOrder)
import Data.PersonUse
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Messages.Comp.PersonTable exposing (Texts)
import Styles as S
import Util.Contact
@ -41,27 +43,75 @@ type Msg
= SetPersons (List Person)
| Select Person
| Deselect
| ToggleOrder PersonOrder
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe PersonOrder )
update _ msg model =
case msg of
SetPersons list ->
( { model | equips = list, selected = Nothing }, Cmd.none )
( { model | equips = list, selected = Nothing }, Cmd.none, Nothing )
Select equip ->
( { model | selected = Just equip }, Cmd.none )
( { model | selected = Just equip }, Cmd.none, Nothing )
Deselect ->
( { model | selected = Nothing }, Cmd.none )
( { model | selected = Nothing }, Cmd.none, Nothing )
ToggleOrder order ->
( model, Cmd.none, Just order )
type Header
= Name
| Org
newOrder : Header -> PersonOrder -> PersonOrder
newOrder header current =
case ( header, current ) of
( Name, Data.PersonOrder.NameAsc ) ->
Data.PersonOrder.NameDesc
( Name, _ ) ->
Data.PersonOrder.NameAsc
( Org, Data.PersonOrder.OrgAsc ) ->
Data.PersonOrder.OrgDesc
( Org, _ ) ->
Data.PersonOrder.OrgAsc
--- View2
view2 : Texts -> Model -> Html Msg
view2 texts model =
view2 : Texts -> PersonOrder -> Model -> Html Msg
view2 texts order model =
let
nameSortIcon =
case order of
Data.PersonOrder.NameAsc ->
"fa fa-sort-alpha-up"
Data.PersonOrder.NameDesc ->
"fa fa-sort-alpha-down-alt"
_ ->
"invisible fa fa-sort-alpha-down-alt"
orgSortIcon =
case order of
Data.PersonOrder.OrgAsc ->
"fa fa-sort-alpha-up"
Data.PersonOrder.OrgDesc ->
"fa fa-sort-alpha-down-alt"
_ ->
"invisible fa fa-sort-alpha-down-alt"
in
table [ class S.tableMain ]
[ thead []
[ tr []
@ -69,8 +119,18 @@ view2 texts model =
, th [ class "text-left pr-1 md:px-2" ]
[ text texts.use
]
, th [ class "text-left" ] [ text texts.basics.name ]
, th [ class "text-left hidden sm:table-cell" ] [ text texts.basics.organization ]
, th [ class "text-left" ]
[ a [ href "#", onClick (ToggleOrder <| newOrder Name order) ]
[ i [ class nameSortIcon, class "mr-1" ] []
, text texts.basics.name
]
]
, th [ class "text-left hidden sm:table-cell" ]
[ a [ href "#", onClick (ToggleOrder <| newOrder Org order) ]
[ i [ class orgSortIcon, class "mr-1" ] []
, text texts.basics.organization
]
]
, th [ class "text-left hidden md:table-cell" ] [ text texts.contact ]
]
]

View File

@ -37,7 +37,9 @@ import Data.CalEvent exposing (CalEvent)
import Data.Direction exposing (Direction(..))
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.FolderOrder
import Data.Language exposing (Language)
import Data.TagOrder
import Data.UiSettings exposing (UiSettings)
import Data.Validated exposing (Validated(..))
import Html exposing (..)
@ -221,8 +223,8 @@ initWith flags s =
[ Api.getImapSettings flags "" ConnResp
, nc
, Cmd.map CalEventMsg sc
, Api.getFolders flags "" False GetFolderResp
, Api.getTags flags "" GetTagResp
, Api.getFolders flags "" Data.FolderOrder.NameAsc False GetFolderResp
, Api.getTags flags "" Data.TagOrder.NameAsc GetTagResp
]
)
@ -268,8 +270,8 @@ init flags =
}
, Cmd.batch
[ Api.getImapSettings flags "" ConnResp
, Api.getFolders flags "" False GetFolderResp
, Api.getTags flags "" GetTagResp
, Api.getFolders flags "" Data.FolderOrder.NameAsc False GetFolderResp
, Api.getTags flags "" Data.TagOrder.NameAsc GetTagResp
, Cmd.map CalEventMsg scmd
]
)

View File

@ -40,10 +40,12 @@ import Comp.TagSelect
import Data.CustomFieldChange exposing (CustomFieldValueCollect)
import Data.Direction exposing (Direction)
import Data.DropdownStyle as DS
import Data.EquipmentOrder
import Data.EquipmentUse
import Data.Fields
import Data.Flags exposing (Flags)
import Data.ItemQuery as Q exposing (ItemQuery)
import Data.PersonOrder
import Data.PersonUse
import Data.SearchMode exposing (SearchMode)
import Data.UiSettings exposing (UiSettings)
@ -441,8 +443,8 @@ updateDrop ddm flags settings msg model =
Cmd.batch
[ Api.itemSearchStats flags Api.Model.ItemQuery.empty GetAllTagsResp
, Api.getOrgLight flags GetOrgResp
, Api.getEquipments flags "" GetEquipResp
, Api.getPersons flags "" GetPersonResp
, Api.getEquipments flags "" Data.EquipmentOrder.NameAsc GetEquipResp
, Api.getPersons flags "" Data.PersonOrder.NameAsc GetPersonResp
, Cmd.map CustomFieldMsg (Comp.CustomFieldMultiInput.initCmd flags)
, cdp
]
@ -1088,7 +1090,7 @@ findTab tab =
Nothing
tabLook :UiSettings -> Model -> SearchTab -> Comp.Tabs.Look
tabLook : UiSettings -> Model -> SearchTab -> Comp.Tabs.Look
tabLook settings model tab =
let
isHidden f =
@ -1097,6 +1099,7 @@ tabLook settings model tab =
hiddenOr fields default =
if List.all isHidden fields then
Comp.Tabs.Hidden
else
default
@ -1126,41 +1129,41 @@ tabLook settings model tab =
activeWhen model.inboxCheckbox
TabTags ->
hiddenOr [Data.Fields.Tag]
hiddenOr [ Data.Fields.Tag ]
(activeWhenNotEmpty model.tagSelection.includeTags model.tagSelection.excludeTags)
TabTagCategories ->
hiddenOr [Data.Fields.Tag]
hiddenOr [ Data.Fields.Tag ]
(activeWhenNotEmpty model.tagSelection.includeCats model.tagSelection.excludeCats)
TabFolder ->
hiddenOr [Data.Fields.Folder]
hiddenOr [ Data.Fields.Folder ]
(activeWhenJust model.selectedFolder)
TabCorrespondent ->
hiddenOr [Data.Fields.CorrOrg, Data.Fields.CorrPerson] <|
hiddenOr [ Data.Fields.CorrOrg, Data.Fields.CorrPerson ] <|
activeWhenNotEmpty (Comp.Dropdown.getSelected model.orgModel)
(Comp.Dropdown.getSelected model.corrPersonModel)
TabConcerning ->
hiddenOr [Data.Fields.ConcPerson, Data.Fields.ConcEquip ] <|
hiddenOr [ Data.Fields.ConcPerson, Data.Fields.ConcEquip ] <|
activeWhenNotEmpty (Comp.Dropdown.getSelected model.concPersonModel)
(Comp.Dropdown.getSelected model.concEquipmentModel)
TabDate ->
hiddenOr [Data.Fields.Date] <|
activeWhenJust (Util.Maybe.or [model.fromDate, model.untilDate])
hiddenOr [ Data.Fields.Date ] <|
activeWhenJust (Util.Maybe.or [ model.fromDate, model.untilDate ])
TabDueDate ->
hiddenOr [Data.Fields.DueDate] <|
activeWhenJust (Util.Maybe.or [model.fromDueDate, model.untilDueDate])
hiddenOr [ Data.Fields.DueDate ] <|
activeWhenJust (Util.Maybe.or [ model.fromDueDate, model.untilDueDate ])
TabSource ->
hiddenOr [Data.Fields.SourceName] <|
hiddenOr [ Data.Fields.SourceName ] <|
activeWhenJust model.sourceModel
TabDirection ->
hiddenOr [Data.Fields.Direction] <|
hiddenOr [ Data.Fields.Direction ] <|
activeWhenNotEmpty (Comp.Dropdown.getSelected model.directionModel) []
TabTrashed ->
@ -1179,7 +1182,6 @@ searchTabState settings model tab =
searchTab =
findTab tab
folded =
if Set.member tab.name model.openTabs then
Comp.Tabs.Open
@ -1189,10 +1191,10 @@ searchTabState settings model tab =
state =
{ folded = folded
, look = Maybe.map (tabLook settings model) searchTab
|> Maybe.withDefault Comp.Tabs.Normal
, look =
Maybe.map (tabLook settings model) searchTab
|> Maybe.withDefault Comp.Tabs.Normal
}
in
( state, ToggleAkkordionTab tab.name )

View File

@ -27,8 +27,10 @@ import Comp.Dropdown exposing (isDropdownChangeMsg)
import Comp.FixedDropdown
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.FolderOrder
import Data.Language exposing (Language)
import Data.Priority exposing (Priority)
import Data.TagOrder
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
@ -89,8 +91,8 @@ init : Flags -> ( Model, Cmd Msg )
init flags =
( emptyModel
, Cmd.batch
[ Api.getFolders flags "" False GetFolderResp
, Api.getTags flags "" GetTagResp
[ Api.getFolders flags "" Data.FolderOrder.NameAsc False GetFolderResp
, Api.getTags flags "" Data.TagOrder.NameAsc GetTagResp
]
)

View File

@ -23,6 +23,7 @@ import Comp.TagForm
import Comp.TagTable
import Comp.YesNoDimmer
import Data.Flags exposing (Flags)
import Data.TagOrder exposing (TagOrder)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onSubmit)
@ -42,6 +43,7 @@ type alias Model =
, loading : Bool
, deleteConfirm : Comp.YesNoDimmer.Model
, query : String
, order : TagOrder
}
@ -66,6 +68,7 @@ emptyModel =
, loading = False
, deleteConfirm = Comp.YesNoDimmer.emptyModel
, query = ""
, order = Data.TagOrder.NameAsc
}
@ -88,12 +91,16 @@ update flags msg model =
case msg of
TableMsg m ->
let
( tm, tc ) =
( tm, tc, maybeOrder ) =
Comp.TagTable.update flags m model.tagTableModel
newOrder =
Maybe.withDefault model.order maybeOrder
( m2, c2 ) =
( { model
| tagTableModel = tm
, order = newOrder
, viewMode = Maybe.map (\_ -> Form) tm.selected |> Maybe.withDefault Table
, formError =
if Util.Maybe.nonEmpty tm.selected then
@ -112,8 +119,15 @@ update flags msg model =
Nothing ->
( m2, Cmd.none )
( m4, c4 ) =
if model.order == newOrder then
( m3, Cmd.none )
else
update flags LoadTags m3
in
( m3, Cmd.batch [ c2, c3 ] )
( m4, Cmd.batch [ c2, c3, c4 ] )
FormMsg m ->
let
@ -123,7 +137,9 @@ update flags msg model =
( { model | tagFormModel = m2 }, Cmd.map FormMsg c2 )
LoadTags ->
( { model | loading = True }, Api.getTags flags model.query (TagResp model.query) )
( { model | loading = True }
, Api.getTags flags model.query model.order (TagResp model.query)
)
TagResp query (Ok tags) ->
let
@ -224,7 +240,7 @@ update flags msg model =
m =
{ model | query = str }
in
( m, Api.getTags flags str (TagResp str) )
( m, Api.getTags flags str model.order (TagResp str) )
@ -262,7 +278,7 @@ viewTable2 texts model =
]
, rootClasses = "mb-4"
}
, Html.map TableMsg (Comp.TagTable.view2 texts.tagTable model.tagTableModel)
, Html.map TableMsg (Comp.TagTable.view2 texts.tagTable model.order model.tagTableModel)
, div
[ classList
[ ( "ui dimmer", True )

View File

@ -16,8 +16,10 @@ module Comp.TagTable exposing
import Api.Model.Tag exposing (Tag)
import Comp.Basic as B
import Data.Flags exposing (Flags)
import Data.TagOrder exposing (TagOrder)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Messages.Comp.TagTable exposing (Texts)
import Styles as S
@ -35,37 +37,108 @@ emptyModel =
}
type Header
= Name
| Category
type Msg
= SetTags (List Tag)
| Select Tag
| Deselect
| SortClick TagOrder
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe TagOrder )
update _ msg model =
case msg of
SetTags list ->
( { model | tags = list, selected = Nothing }, Cmd.none )
( { model | tags = list, selected = Nothing }, Cmd.none, Nothing )
Select tag ->
( { model | selected = Just tag }, Cmd.none )
( { model | selected = Just tag }, Cmd.none, Nothing )
Deselect ->
( { model | selected = Nothing }, Cmd.none )
( { model | selected = Nothing }, Cmd.none, Nothing )
SortClick order ->
( model, Cmd.none, Just order )
newOrder : Header -> TagOrder -> TagOrder
newOrder header current =
case ( header, current ) of
( Name, Data.TagOrder.NameAsc ) ->
Data.TagOrder.NameDesc
( Name, Data.TagOrder.NameDesc ) ->
Data.TagOrder.NameAsc
( Name, Data.TagOrder.CategoryAsc ) ->
Data.TagOrder.NameAsc
( Name, Data.TagOrder.CategoryDesc ) ->
Data.TagOrder.NameAsc
( Category, Data.TagOrder.NameAsc ) ->
Data.TagOrder.CategoryAsc
( Category, Data.TagOrder.NameDesc ) ->
Data.TagOrder.CategoryAsc
( Category, Data.TagOrder.CategoryAsc ) ->
Data.TagOrder.CategoryDesc
( Category, Data.TagOrder.CategoryDesc ) ->
Data.TagOrder.CategoryAsc
--- View2
view2 : Texts -> Model -> Html Msg
view2 texts model =
view2 : Texts -> TagOrder -> Model -> Html Msg
view2 texts order model =
let
nameSortIcon =
case order of
Data.TagOrder.NameAsc ->
"fa fa-sort-alpha-up"
Data.TagOrder.NameDesc ->
"fa fa-sort-alpha-down-alt"
_ ->
"invisible fa fa-sort-alpha-down"
catSortIcon =
case order of
Data.TagOrder.CategoryAsc ->
"fa fa-sort-alpha-up"
Data.TagOrder.CategoryDesc ->
"fa fa-sort-alpha-down-alt"
_ ->
"invisible fa fa-sort-alpha-down"
in
table [ class S.tableMain ]
[ thead []
[ tr []
[ th [ class "" ] []
, th [ class "text-left" ] [ text texts.basics.name ]
, th [ class "text-left" ] [ text texts.category ]
, th [ class "text-left" ]
[ a [ href "#", onClick (SortClick <| newOrder Name order) ]
[ i [ class nameSortIcon, class "mr-1" ] []
, text texts.basics.name
]
]
, th [ class "text-left" ]
[ a [ href "#", onClick (SortClick <| newOrder Category order) ]
[ i [ class catSortIcon, class "mr-1" ]
[]
, text texts.category
]
]
]
]
, tbody []

View File

@ -28,6 +28,7 @@ import Data.DropdownStyle as DS
import Data.Fields exposing (Field)
import Data.Flags exposing (Flags)
import Data.ItemTemplate as IT exposing (ItemTemplate)
import Data.TagOrder
import Data.UiSettings exposing (ItemPattern, Pos(..), UiSettings)
import Dict exposing (Dict)
import Html exposing (..)
@ -160,7 +161,7 @@ init flags settings =
Comp.FixedDropdown.init Messages.UiLanguage.all
, openTabs = Set.empty
}
, Api.getTags flags "" GetTagsResp
, Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
)

View File

@ -0,0 +1,31 @@
{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Data.CustomFieldOrder exposing (CustomFieldOrder(..), asString)
type CustomFieldOrder
= LabelAsc
| LabelDesc
| FormatAsc
| FormatDesc
asString : CustomFieldOrder -> String
asString order =
case order of
LabelAsc ->
"label"
LabelDesc ->
"-label"
FormatAsc ->
"type"
FormatDesc ->
"-type"

View File

@ -0,0 +1,23 @@
{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Data.EquipmentOrder exposing (EquipmentOrder(..), asString)
type EquipmentOrder
= NameAsc
| NameDesc
asString : EquipmentOrder -> String
asString order =
case order of
NameAsc ->
"name"
NameDesc ->
"-name"

View File

@ -0,0 +1,31 @@
{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Data.FolderOrder exposing (FolderOrder(..), asString)
type FolderOrder
= NameAsc
| NameDesc
| OwnerAsc
| OwnerDesc
asString : FolderOrder -> String
asString order =
case order of
NameAsc ->
"name"
NameDesc ->
"-name"
OwnerAsc ->
"owner"
OwnerDesc ->
"-owner"

View File

@ -0,0 +1,23 @@
{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Data.OrganizationOrder exposing (OrganizationOrder(..), asString)
type OrganizationOrder
= NameAsc
| NameDesc
asString : OrganizationOrder -> String
asString order =
case order of
NameAsc ->
"name"
NameDesc ->
"-name"

View File

@ -0,0 +1,31 @@
{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Data.PersonOrder exposing (PersonOrder(..), asString)
type PersonOrder
= NameAsc
| NameDesc
| OrgAsc
| OrgDesc
asString : PersonOrder -> String
asString order =
case order of
NameAsc ->
"name"
NameDesc ->
"-name"
OrgAsc ->
"org"
OrgDesc ->
"-org"

View File

@ -0,0 +1,31 @@
{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Data.TagOrder exposing (TagOrder(..), asString)
type TagOrder
= NameAsc
| NameDesc
| CategoryAsc
| CategoryDesc
asString : TagOrder -> String
asString order =
case order of
NameAsc ->
"name"
NameDesc ->
"-name"
CategoryAsc ->
"category"
CategoryDesc ->
"-category"

View File

@ -20,6 +20,7 @@ type alias Texts =
{ basics : Messages.Basics.Texts
, memberCount : String
, formatDateShort : Int -> String
, owner : String
}
@ -28,6 +29,7 @@ gb =
{ basics = Messages.Basics.gb
, memberCount = "#Member"
, formatDateShort = DF.formatDateShort Messages.UiLanguage.English
, owner = "Owner"
}
@ -36,4 +38,5 @@ de =
{ basics = Messages.Basics.de
, memberCount = "#Mitglieder"
, formatDateShort = DF.formatDateShort Messages.UiLanguage.German
, owner = "Besitzer"
}