diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 46a4766a..0c0dd351 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -5305,6 +5305,10 @@ components: - tagCategoryCloud - fieldStats - folderStats + - corrOrgStats + - corrPersStats + - concPersStats + - concEquipStats properties: count: type: integer @@ -5321,6 +5325,23 @@ components: type: array items: $ref: "#/components/schemas/FolderStats" + corrOrgStats: + type: array + items: + $ref: "#/components/schemas/IdRefStats" + corrPersStats: + type: array + items: + $ref: "#/components/schemas/IdRefStats" + concPersStats: + type: array + items: + $ref: "#/components/schemas/IdRefStats" + concEquipStats: + type: array + items: + $ref: "#/components/schemas/IdRefStats" + ItemInsights: description: | Information about the items in docspell. @@ -5454,6 +5475,19 @@ components: type: integer format: int32 + IdRefStats: + description: | + Counting some objects that have an id and a name. + required: + - ref + - count + properties: + ref: + $ref: "#/components/schemas/IdName" + count: + type: integer + format: int32 + AttachmentMeta: description: | Extracted meta data of an attachment. diff --git a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala index 7cc03c6b..be89e955 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala @@ -7,11 +7,9 @@ package docspell.restserver.conv import java.time.{LocalDate, ZoneId} - import cats.effect.{Async, Sync} import cats.implicits._ import fs2.Stream - import docspell.backend.ops.OCollective.{InsightData, PassChangeResult} import docspell.backend.ops.OCustomFields.SetValueResult import docspell.backend.ops.OJob.JobCancelResult @@ -22,10 +20,9 @@ import docspell.common.syntax.all._ import docspell.ftsclient.FtsResult import docspell.restapi.model._ import docspell.restserver.conv.Conversions._ -import docspell.store.queries.{AttachmentLight => QAttachmentLight} +import docspell.store.queries.{AttachmentLight => QAttachmentLight, IdRefCount} import docspell.store.records._ import docspell.store.{AddResult, UpdateResult} - import org.http4s.headers.`Content-Type` import org.http4s.multipart.Multipart import org.log4s.Logger @@ -38,9 +35,16 @@ trait Conversions { mkTagCloud(sum.tags), mkTagCategoryCloud(sum.cats), sum.fields.map(mkFieldStats), - sum.folders.map(mkFolderStats) + sum.folders.map(mkFolderStats), + sum.corrOrgs.map(mkIdRefStats), + sum.corrPers.map(mkIdRefStats), + sum.concPers.map(mkIdRefStats), + sum.concEquip.map(mkIdRefStats) ) + def mkIdRefStats(s: IdRefCount): IdRefStats = + IdRefStats(mkIdName(s.ref), s.count) + def mkFolderStats(fs: docspell.store.queries.FolderCount): FolderStats = FolderStats(fs.id, fs.name, mkIdName(fs.owner), fs.count) diff --git a/modules/store/src/main/scala/docspell/store/queries/IdRefCount.scala b/modules/store/src/main/scala/docspell/store/queries/IdRefCount.scala new file mode 100644 index 00000000..20c2fbdf --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/IdRefCount.scala @@ -0,0 +1,5 @@ +package docspell.store.queries + +import docspell.common._ + +final case class IdRefCount(ref: IdRef, count: Int) {} diff --git a/modules/store/src/main/scala/docspell/store/queries/QItem.scala b/modules/store/src/main/scala/docspell/store/queries/QItem.scala index 2671fcaa..623b68e0 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QItem.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QItem.scala @@ -192,7 +192,21 @@ object QItem { cats <- searchTagCategorySummary(today)(q) fields <- searchFieldSummary(today)(q) folders <- searchFolderSummary(today)(q) - } yield SearchSummary(count, tags, cats, fields, folders) + orgs <- searchCorrOrgSummary(today)(q) + corrPers <- searchCorrPersonSummary(today)(q) + concPers <- searchConcPersonSummary(today)(q) + concEquip <- searchConcEquipSummary(today)(q) + } yield SearchSummary( + count, + tags, + cats, + fields, + folders, + orgs, + corrPers, + concPers, + concEquip + ) def searchTagCategorySummary( today: LocalDate @@ -251,6 +265,40 @@ object QItem { .query[Int] .unique + def searchCorrOrgSummary(today: LocalDate)(q: Query): ConnectionIO[List[IdRefCount]] = + searchIdRefSummary(org.oid, org.name, i.corrOrg, today)(q) + + def searchCorrPersonSummary(today: LocalDate)( + q: Query + ): ConnectionIO[List[IdRefCount]] = + searchIdRefSummary(pers0.pid, pers0.name, i.corrPerson, today)(q) + + def searchConcPersonSummary(today: LocalDate)( + q: Query + ): ConnectionIO[List[IdRefCount]] = + searchIdRefSummary(pers1.pid, pers1.name, i.concPerson, today)(q) + + def searchConcEquipSummary(today: LocalDate)( + q: Query + ): ConnectionIO[List[IdRefCount]] = + searchIdRefSummary(equip.eid, equip.name, i.concEquipment, today)(q) + + private def searchIdRefSummary( + idCol: Column[Ident], + nameCol: Column[String], + fkCol: Column[Ident], + today: LocalDate + )(q: Query): ConnectionIO[List[IdRefCount]] = + findItemsBase(q.fix, today, 0).unwrap + .withSelect(select(idCol, nameCol).append(count(idCol).as("num"))) + .changeWhere(c => + c && fkCol.isNotNull && queryCondition(today, q.fix.account.collective, q.cond) + ) + .groupBy(idCol, nameCol) + .build + .query[IdRefCount] + .to[List] + def searchFolderSummary(today: LocalDate)(q: Query): ConnectionIO[List[FolderCount]] = { val fu = RUser.as("fu") findItemsBase(q.fix, today, 0).unwrap diff --git a/modules/store/src/main/scala/docspell/store/queries/SearchSummary.scala b/modules/store/src/main/scala/docspell/store/queries/SearchSummary.scala index 1eeaef2e..c6bff383 100644 --- a/modules/store/src/main/scala/docspell/store/queries/SearchSummary.scala +++ b/modules/store/src/main/scala/docspell/store/queries/SearchSummary.scala @@ -11,7 +11,11 @@ case class SearchSummary( tags: List[TagCount], cats: List[CategoryCount], fields: List[FieldStats], - folders: List[FolderCount] + folders: List[FolderCount], + corrOrgs: List[IdRefCount], + corrPers: List[IdRefCount], + concPers: List[IdRefCount], + concEquip: List[IdRefCount] ) { def onlyExisting: SearchSummary = @@ -20,6 +24,10 @@ case class SearchSummary( tags.filter(_.count > 0), cats.filter(_.count > 0), fields.filter(_.count > 0), - folders.filter(_.count > 0) + folders.filter(_.count > 0), + corrOrgs = corrOrgs.filter(_.count > 0), + corrPers = corrPers.filter(_.count > 0), + concPers = concPers.filter(_.count > 0), + concEquip = concEquip.filter(_.count > 0) ) } diff --git a/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm b/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm index 27d11480..6a60260e 100644 --- a/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm +++ b/modules/webapp/src/main/elm/Comp/CustomFieldMultiInput.elm @@ -16,6 +16,7 @@ module Comp.CustomFieldMultiInput exposing , isEmpty , nonEmpty , reset + , setOptions , setValues , update , updateSearch @@ -125,6 +126,11 @@ setValues values = SetValues values +setOptions : List CustomField -> Msg +setOptions fields = + CustomFieldResp (Ok (CustomFieldList fields)) + + reset : Model -> Model reset model = let diff --git a/modules/webapp/src/main/elm/Comp/SearchMenu.elm b/modules/webapp/src/main/elm/Comp/SearchMenu.elm index c70a1c3d..890cf25b 100644 --- a/modules/webapp/src/main/elm/Comp/SearchMenu.elm +++ b/modules/webapp/src/main/elm/Comp/SearchMenu.elm @@ -60,6 +60,7 @@ import Http import Messages.Comp.SearchMenu exposing (Texts) import Set exposing (Set) import Styles as S +import Util.CustomField import Util.Html exposing (KeyCode(..)) import Util.ItemDragDrop as DD import Util.Maybe @@ -564,6 +565,42 @@ updateDrop ddm flags settings msg model = selectModel = Comp.TagSelect.modifyCount model.tagSelectModel tagCount catCount + orgOpts = + Comp.Dropdown.update (Comp.Dropdown.SetOptions (List.map .ref stats.corrOrgStats)) + model.orgModel + |> Tuple.first + + corrPersOpts = + Comp.Dropdown.update (Comp.Dropdown.SetOptions (List.map .ref stats.corrPersStats)) + model.corrPersonModel + |> Tuple.first + + concPersOpts = + Comp.Dropdown.update (Comp.Dropdown.SetOptions (List.map .ref stats.concPersStats)) + model.concPersonModel + |> Tuple.first + + concEquipOpts = + let + mkEquip ref = + Equipment ref.id ref.name 0 Nothing "" + in + Comp.Dropdown.update + (Comp.Dropdown.SetOptions + (List.map (.ref >> mkEquip) stats.concEquipStats) + ) + model.concEquipmentModel + |> Tuple.first + + fields = + Util.CustomField.statsToFields stats + + fieldOpts = + Comp.CustomFieldMultiInput.update flags + (Comp.CustomFieldMultiInput.setOptions fields) + model.customFieldModel + |> .model + model_ = { model | tagSelectModel = selectModel @@ -571,6 +608,11 @@ updateDrop ddm flags settings msg model = Comp.FolderSelect.modify model.selectedFolder model.folderList stats.folderStats + , orgModel = orgOpts + , corrPersonModel = corrPersOpts + , concPersonModel = concPersOpts + , concEquipmentModel = concEquipOpts + , customFieldModel = fieldOpts } in { model = model_ diff --git a/modules/webapp/src/main/elm/Util/CustomField.elm b/modules/webapp/src/main/elm/Util/CustomField.elm index fc121f62..cfe58d92 100644 --- a/modules/webapp/src/main/elm/Util/CustomField.elm +++ b/modules/webapp/src/main/elm/Util/CustomField.elm @@ -10,9 +10,12 @@ module Util.CustomField exposing , nameOrLabel , renderValue , renderValue2 + , statsToFields ) +import Api.Model.CustomField exposing (CustomField) import Api.Model.ItemFieldValue exposing (ItemFieldValue) +import Api.Model.SearchStats exposing (SearchStats) import Data.CustomFieldType import Data.Icons as Icons import Html exposing (..) @@ -20,6 +23,15 @@ import Html.Attributes exposing (..) import Html.Events exposing (onClick) +statsToFields : SearchStats -> List CustomField +statsToFields stats = + let + mkField fs = + CustomField fs.id fs.name fs.label fs.ftype fs.count 0 + in + List.map mkField stats.fieldStats + + {-| This is how the server wants the value to a bool custom field -} boolValue : Bool -> String