diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OCustomFields.scala b/modules/backend/src/main/scala/docspell/backend/ops/OCustomFields.scala index c864b098..44485e56 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OCustomFields.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OCustomFields.scala @@ -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 { diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OEquipment.scala b/modules/backend/src/main/scala/docspell/backend/ops/OEquipment.scala index 984b351d..71e40201 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OEquipment.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OEquipment.scala @@ -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)) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OFolder.scala b/modules/backend/src/main/scala/docspell/backend/ops/OFolder.scala index b7e79766..f610d069 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OFolder.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OFolder.scala @@ -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)) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OOrganization.scala b/modules/backend/src/main/scala/docspell/backend/ops/OOrganization.scala index 4bd077ed..b9fa1157 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OOrganization.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OOrganization.scala @@ -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) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OTag.scala b/modules/backend/src/main/scala/docspell/backend/ops/OTag.scala index ce500aec..7bf6128e 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OTag.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OTag.scala @@ -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) diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index fc7d9e24..3ee2a5a9 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -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 diff --git a/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala b/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala index 23619cd3..95b2afb8 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala @@ -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") } diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/CustomFieldRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/CustomFieldRoutes.scala index 7a5bb8a0..22729db1 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/CustomFieldRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/CustomFieldRoutes.scala @@ -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 diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/EquipmentRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/EquipmentRoutes.scala index 92e858ea..b52c3504 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/EquipmentRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/EquipmentRoutes.scala @@ -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 diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/FolderRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/FolderRoutes.scala index 99475f0f..3c012681 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/FolderRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/FolderRoutes.scala @@ -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 diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/OrganizationRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/OrganizationRoutes.scala index 100f4ff3..b8f5c34d 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/OrganizationRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/OrganizationRoutes.scala @@ -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 diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/PersonRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/PersonRoutes.scala index fa677125..810c04af 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/PersonRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/PersonRoutes.scala @@ -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 diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/TagRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/TagRoutes.scala index 85e4c5f3..4e3f85a8 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/TagRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/TagRoutes.scala @@ -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 diff --git a/modules/store/src/main/scala/docspell/store/qb/Column.scala b/modules/store/src/main/scala/docspell/store/qb/Column.scala index fa065862..45748523 100644 --- a/modules/store/src/main/scala/docspell/store/qb/Column.scala +++ b/modules/store/src/main/scala/docspell/store/qb/Column.scala @@ -12,6 +12,7 @@ case class Column[A](name: String, table: TableDef) { def cast[B]: Column[B] = this.asInstanceOf[Column[B]] + } object Column {} diff --git a/modules/store/src/main/scala/docspell/store/qb/DSL.scala b/modules/store/src/main/scala/docspell/store/qb/DSL.scala index d9588357..b3c95dac 100644 --- a/modules/store/src/main/scala/docspell/store/qb/DSL.scala +++ b/modules/store/src/main/scala/docspell/store/qb/DSL.scala @@ -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) diff --git a/modules/store/src/main/scala/docspell/store/qb/Select.scala b/modules/store/src/main/scala/docspell/store/qb/Select.scala index 67cb4cdd..42e9c3f9 100644 --- a/modules/store/src/main/scala/docspell/store/qb/Select.scala +++ b/modules/store/src/main/scala/docspell/store/qb/Select.scala @@ -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 { diff --git a/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala b/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala index b2ce9836..c6cc4e73 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala @@ -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( diff --git a/modules/store/src/main/scala/docspell/store/queries/QFolder.scala b/modules/store/src/main/scala/docspell/store/queries/QFolder.scala index 819b6004..dc4cf257 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QFolder.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QFolder.scala @@ -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] } diff --git a/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala b/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala index 09acc340..2286fac3 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala @@ -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), diff --git a/modules/store/src/main/scala/docspell/store/records/REquipment.scala b/modules/store/src/main/scala/docspell/store/records/REquipment.scala index 0aa6fc34..517b589e 100644 --- a/modules/store/src/main/scala/docspell/store/records/REquipment.scala +++ b/modules/store/src/main/scala/docspell/store/records/REquipment.scala @@ -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) diff --git a/modules/store/src/main/scala/docspell/store/records/ROrganization.scala b/modules/store/src/main/scala/docspell/store/records/ROrganization.scala index 4e2f4a01..ca86c4ec 100644 --- a/modules/store/src/main/scala/docspell/store/records/ROrganization.scala +++ b/modules/store/src/main/scala/docspell/store/records/ROrganization.scala @@ -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}%") diff --git a/modules/store/src/main/scala/docspell/store/records/RPerson.scala b/modules/store/src/main/scala/docspell/store/records/RPerson.scala index e14b0ecf..fe740d65 100644 --- a/modules/store/src/main/scala/docspell/store/records/RPerson.scala +++ b/modules/store/src/main/scala/docspell/store/records/RPerson.scala @@ -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] diff --git a/modules/store/src/main/scala/docspell/store/records/RTag.scala b/modules/store/src/main/scala/docspell/store/records/RTag.scala index f9739b74..00ac43e8 100644 --- a/modules/store/src/main/scala/docspell/store/records/RTag.scala +++ b/modules/store/src/main/scala/docspell/store/records/RTag.scala @@ -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] diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index d816f551..9d65af14 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -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 } diff --git a/modules/webapp/src/main/elm/Comp/ClassifierSettingsForm.elm b/modules/webapp/src/main/elm/Comp/ClassifierSettingsForm.elm index 119e9dcd..7fd872d0 100644 --- a/modules/webapp/src/main/elm/Comp/ClassifierSettingsForm.elm +++ b/modules/webapp/src/main/elm/Comp/ClassifierSettingsForm.elm @@ -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 ] ) diff --git a/modules/webapp/src/main/elm/Comp/CustomFieldManage.elm b/modules/webapp/src/main/elm/Comp/CustomFieldManage.elm index e8ba37a7..6e1640c6 100644 --- a/modules/webapp/src/main/elm/Comp/CustomFieldManage.elm +++ b/modules/webapp/src/main/elm/Comp/CustomFieldManage.elm @@ -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 ) diff --git a/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm b/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm index b9ca6282..6686fc69 100644 --- a/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm +++ b/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Comp/CustomFieldTable.elm b/modules/webapp/src/main/elm/Comp/CustomFieldTable.elm index 1075a30e..7046a314 100644 --- a/modules/webapp/src/main/elm/Comp/CustomFieldTable.elm +++ b/modules/webapp/src/main/elm/Comp/CustomFieldTable.elm @@ -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 ] ] diff --git a/modules/webapp/src/main/elm/Comp/EquipmentManage.elm b/modules/webapp/src/main/elm/Comp/EquipmentManage.elm index 1d771639..645767db 100644 --- a/modules/webapp/src/main/elm/Comp/EquipmentManage.elm +++ b/modules/webapp/src/main/elm/Comp/EquipmentManage.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Comp/EquipmentTable.elm b/modules/webapp/src/main/elm/Comp/EquipmentTable.elm index 116c96af..2dadfae7 100644 --- a/modules/webapp/src/main/elm/Comp/EquipmentTable.elm +++ b/modules/webapp/src/main/elm/Comp/EquipmentTable.elm @@ -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 [] diff --git a/modules/webapp/src/main/elm/Comp/FolderManage.elm b/modules/webapp/src/main/elm/Comp/FolderManage.elm index 9917d7bd..b41196c0 100644 --- a/modules/webapp/src/main/elm/Comp/FolderManage.elm +++ b/modules/webapp/src/main/elm/Comp/FolderManage.elm @@ -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 ) diff --git a/modules/webapp/src/main/elm/Comp/FolderTable.elm b/modules/webapp/src/main/elm/Comp/FolderTable.elm index 1c9fea1d..6b7b9b97 100644 --- a/modules/webapp/src/main/elm/Comp/FolderTable.elm +++ b/modules/webapp/src/main/elm/Comp/FolderTable.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail/MultiEditMenu.elm b/modules/webapp/src/main/elm/Comp/ItemDetail/MultiEditMenu.elm index a9231590..84f18b59 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail/MultiEditMenu.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail/MultiEditMenu.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Comp/ItemDetail/Update.elm b/modules/webapp/src/main/elm/Comp/ItemDetail/Update.elm index 1289a52f..691d0639 100644 --- a/modules/webapp/src/main/elm/Comp/ItemDetail/Update.elm +++ b/modules/webapp/src/main/elm/Comp/ItemDetail/Update.elm @@ -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 ] diff --git a/modules/webapp/src/main/elm/Comp/NotificationForm.elm b/modules/webapp/src/main/elm/Comp/NotificationForm.elm index 32ea132e..da4906ad 100644 --- a/modules/webapp/src/main/elm/Comp/NotificationForm.elm +++ b/modules/webapp/src/main/elm/Comp/NotificationForm.elm @@ -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 ] ) diff --git a/modules/webapp/src/main/elm/Comp/OrgManage.elm b/modules/webapp/src/main/elm/Comp/OrgManage.elm index 0e46d760..5d8f4d47 100644 --- a/modules/webapp/src/main/elm/Comp/OrgManage.elm +++ b/modules/webapp/src/main/elm/Comp/OrgManage.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Comp/OrgTable.elm b/modules/webapp/src/main/elm/Comp/OrgTable.elm index fd75b0b1..b2d102a2 100644 --- a/modules/webapp/src/main/elm/Comp/OrgTable.elm +++ b/modules/webapp/src/main/elm/Comp/OrgTable.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Comp/PersonManage.elm b/modules/webapp/src/main/elm/Comp/PersonManage.elm index a58e18fa..b70dd990 100644 --- a/modules/webapp/src/main/elm/Comp/PersonManage.elm +++ b/modules/webapp/src/main/elm/Comp/PersonManage.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Comp/PersonTable.elm b/modules/webapp/src/main/elm/Comp/PersonTable.elm index bd2eb177..167b93ba 100644 --- a/modules/webapp/src/main/elm/Comp/PersonTable.elm +++ b/modules/webapp/src/main/elm/Comp/PersonTable.elm @@ -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 ] ] ] diff --git a/modules/webapp/src/main/elm/Comp/ScanMailboxForm.elm b/modules/webapp/src/main/elm/Comp/ScanMailboxForm.elm index d6e749e5..c6dfb4ec 100644 --- a/modules/webapp/src/main/elm/Comp/ScanMailboxForm.elm +++ b/modules/webapp/src/main/elm/Comp/ScanMailboxForm.elm @@ -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 ] ) diff --git a/modules/webapp/src/main/elm/Comp/SearchMenu.elm b/modules/webapp/src/main/elm/Comp/SearchMenu.elm index d74b532f..dc3e391b 100644 --- a/modules/webapp/src/main/elm/Comp/SearchMenu.elm +++ b/modules/webapp/src/main/elm/Comp/SearchMenu.elm @@ -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 ) diff --git a/modules/webapp/src/main/elm/Comp/SourceForm.elm b/modules/webapp/src/main/elm/Comp/SourceForm.elm index 3975018f..abf414d2 100644 --- a/modules/webapp/src/main/elm/Comp/SourceForm.elm +++ b/modules/webapp/src/main/elm/Comp/SourceForm.elm @@ -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 ] ) diff --git a/modules/webapp/src/main/elm/Comp/TagManage.elm b/modules/webapp/src/main/elm/Comp/TagManage.elm index 3ac89f82..e3782bde 100644 --- a/modules/webapp/src/main/elm/Comp/TagManage.elm +++ b/modules/webapp/src/main/elm/Comp/TagManage.elm @@ -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 ) diff --git a/modules/webapp/src/main/elm/Comp/TagTable.elm b/modules/webapp/src/main/elm/Comp/TagTable.elm index 6cab6c31..8f9e5858 100644 --- a/modules/webapp/src/main/elm/Comp/TagTable.elm +++ b/modules/webapp/src/main/elm/Comp/TagTable.elm @@ -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 [] diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm b/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm index c9e6fe4d..fd21d154 100644 --- a/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm +++ b/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm @@ -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 ) diff --git a/modules/webapp/src/main/elm/Data/CustomFieldOrder.elm b/modules/webapp/src/main/elm/Data/CustomFieldOrder.elm new file mode 100644 index 00000000..54a1cb05 --- /dev/null +++ b/modules/webapp/src/main/elm/Data/CustomFieldOrder.elm @@ -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" diff --git a/modules/webapp/src/main/elm/Data/EquipmentOrder.elm b/modules/webapp/src/main/elm/Data/EquipmentOrder.elm new file mode 100644 index 00000000..82fd6cee --- /dev/null +++ b/modules/webapp/src/main/elm/Data/EquipmentOrder.elm @@ -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" diff --git a/modules/webapp/src/main/elm/Data/FolderOrder.elm b/modules/webapp/src/main/elm/Data/FolderOrder.elm new file mode 100644 index 00000000..feadbd06 --- /dev/null +++ b/modules/webapp/src/main/elm/Data/FolderOrder.elm @@ -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" diff --git a/modules/webapp/src/main/elm/Data/OrganizationOrder.elm b/modules/webapp/src/main/elm/Data/OrganizationOrder.elm new file mode 100644 index 00000000..a34154ca --- /dev/null +++ b/modules/webapp/src/main/elm/Data/OrganizationOrder.elm @@ -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" diff --git a/modules/webapp/src/main/elm/Data/PersonOrder.elm b/modules/webapp/src/main/elm/Data/PersonOrder.elm new file mode 100644 index 00000000..c8c76a15 --- /dev/null +++ b/modules/webapp/src/main/elm/Data/PersonOrder.elm @@ -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" diff --git a/modules/webapp/src/main/elm/Data/TagOrder.elm b/modules/webapp/src/main/elm/Data/TagOrder.elm new file mode 100644 index 00000000..1bd2d48f --- /dev/null +++ b/modules/webapp/src/main/elm/Data/TagOrder.elm @@ -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" diff --git a/modules/webapp/src/main/elm/Messages/Comp/FolderTable.elm b/modules/webapp/src/main/elm/Messages/Comp/FolderTable.elm index 8e097a60..d742fae2 100644 --- a/modules/webapp/src/main/elm/Messages/Comp/FolderTable.elm +++ b/modules/webapp/src/main/elm/Messages/Comp/FolderTable.elm @@ -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" }