From 370679daed002c304a0c55385f131548a006477d Mon Sep 17 00:00:00 2001 From: eikek Date: Wed, 26 Jan 2022 21:22:20 +0100 Subject: [PATCH] Some predefined boxes for a dashboard --- .../src/main/resources/docspell-openapi.yml | 2 +- modules/webapp/src/main/elm/Api.elm | 83 +++++++- modules/webapp/src/main/elm/App/Data.elm | 11 +- modules/webapp/src/main/elm/App/Update.elm | 8 +- modules/webapp/src/main/elm/Comp/Basic.elm | 2 +- .../webapp/src/main/elm/Comp/BoxQueryView.elm | 184 ++++++++++++++++++ .../src/main/elm/Comp/BoxSummaryView.elm | 160 +++++++++++++++ modules/webapp/src/main/elm/Comp/BoxView.elm | 166 +++++++++++++++- .../src/main/elm/Comp/DashboardView.elm | 54 +++-- .../src/main/elm/Comp/SearchStatsView.elm | 15 +- .../webapp/src/main/elm/Data/Bookmarks.elm | 7 + .../webapp/src/main/elm/Data/BoxContent.elm | 33 +++- .../webapp/src/main/elm/Data/UiSettings.elm | 11 ++ .../main/elm/Messages/Comp/BoxQueryView.elm | 46 +++++ .../main/elm/Messages/Comp/BoxSummaryView.elm | 32 +++ .../src/main/elm/Messages/Comp/BoxView.elm | 24 +++ .../main/elm/Messages/Comp/DashboardView.elm | 20 ++ .../src/main/elm/Messages/DateFormat.elm | 6 + .../src/main/elm/Messages/Page/Dashboard.elm | 8 + .../elm/Messages/Page/DefaultDashboard.elm | 57 ++++++ .../src/main/elm/Page/Dashboard/Data.elm | 20 +- .../elm/Page/Dashboard/DefaultDashboard.elm | 110 ++++++++--- .../src/main/elm/Page/Dashboard/Update.elm | 21 +- .../src/main/elm/Page/Dashboard/View.elm | 2 +- .../webapp/src/main/elm/Page/Search/View2.elm | 2 +- 25 files changed, 1004 insertions(+), 80 deletions(-) create mode 100644 modules/webapp/src/main/elm/Comp/BoxQueryView.elm create mode 100644 modules/webapp/src/main/elm/Comp/BoxSummaryView.elm create mode 100644 modules/webapp/src/main/elm/Messages/Comp/BoxQueryView.elm create mode 100644 modules/webapp/src/main/elm/Messages/Comp/BoxSummaryView.elm create mode 100644 modules/webapp/src/main/elm/Messages/Comp/BoxView.elm create mode 100644 modules/webapp/src/main/elm/Messages/Comp/DashboardView.elm create mode 100644 modules/webapp/src/main/elm/Messages/Page/DefaultDashboard.elm diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index a57308bd..c585b147 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1961,7 +1961,7 @@ paths: - $ref: "#/components/parameters/bookmarkId" delete: operationId: "sec-querybookmark-delete" - tags: [Query Bookmark] + tags: [Query Bookmarks] summary: Delete a bookmark. description: | Deletes a bookmarks by its id. diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 3b9f63d4..7c184135 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -101,7 +101,9 @@ module Api exposing , itemDetailShare , itemIndexSearch , itemSearch + , itemSearchBookmark , itemSearchStats + , itemSearchStatsBookmark , login , loginSession , logout @@ -2028,24 +2030,70 @@ itemIndexSearch flags query receive = } -itemSearch : Flags -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg -itemSearch flags search receive = - Http2.authPost +itemSearchTask : Flags -> ItemQuery -> Task.Task Http.Error ItemLightList +itemSearchTask flags search = + Http2.authTask { url = flags.config.baseUrl ++ "/api/v1/sec/item/search" + , method = "POST" + , headers = [] , account = getAccount flags , body = Http.jsonBody (Api.Model.ItemQuery.encode search) - , expect = Http.expectJson receive Api.Model.ItemLightList.decoder + , resolver = Http2.jsonResolver Api.Model.ItemLightList.decoder + , timeout = Nothing + } + + +itemSearch : Flags -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg +itemSearch flags search receive = + itemSearchTask flags search |> Task.attempt receive + + +{-| Same as `itemSearch` but interprets the `query` field as a bookmark id. +-} +itemSearchBookmark : Flags -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg +itemSearchBookmark flags bmSearch receive = + let + getBookmark = + getBookmarkByIdTask flags bmSearch.query + |> Task.map (\bm -> { bmSearch | query = bm.query }) + + search q = + itemSearchTask flags q + in + Task.andThen search getBookmark + |> Task.attempt receive + + +itemSearchStatsTask : Flags -> ItemQuery -> Task.Task Http.Error SearchStats +itemSearchStatsTask flags search = + Http2.authTask + { url = flags.config.baseUrl ++ "/api/v1/sec/item/searchStats" + , method = "POST" + , headers = [] + , account = getAccount flags + , body = Http.jsonBody (Api.Model.ItemQuery.encode search) + , resolver = Http2.jsonResolver Api.Model.SearchStats.decoder + , timeout = Nothing } itemSearchStats : Flags -> ItemQuery -> (Result Http.Error SearchStats -> msg) -> Cmd msg itemSearchStats flags search receive = - Http2.authPost - { url = flags.config.baseUrl ++ "/api/v1/sec/item/searchStats" - , account = getAccount flags - , body = Http.jsonBody (Api.Model.ItemQuery.encode search) - , expect = Http.expectJson receive Api.Model.SearchStats.decoder - } + itemSearchStatsTask flags search |> Task.attempt receive + + +itemSearchStatsBookmark : Flags -> ItemQuery -> (Result Http.Error SearchStats -> msg) -> Cmd msg +itemSearchStatsBookmark flags search receive = + let + getBookmark = + getBookmarkByIdTask flags search.query + |> Task.map (\bm -> { search | query = bm.query }) + + getStats q = + itemSearchStatsTask flags q + in + Task.andThen getStats getBookmark + |> Task.attempt receive itemDetail : Flags -> String -> (Result Http.Error ItemDetail -> msg) -> Cmd msg @@ -2335,6 +2383,21 @@ getBookmarksTask flags = } +getBookmarkByIdTask : Flags -> String -> Task.Task Http.Error BookmarkedQuery +getBookmarkByIdTask flags id = + let + findBm all = + Data.Bookmarks.findById id all + + mapNotFound maybeBookmark = + Maybe.map Task.succeed maybeBookmark + |> Maybe.withDefault (Task.fail (Http.BadStatus 404)) + in + getBookmarksTask flags + |> Task.map findBm + |> Task.andThen mapNotFound + + getBookmarks : Flags -> (Result Http.Error AllBookmarks -> msg) -> Cmd msg getBookmarks flags receive = let diff --git a/modules/webapp/src/main/elm/App/Data.elm b/modules/webapp/src/main/elm/App/Data.elm index d58c4456..ee63ea96 100644 --- a/modules/webapp/src/main/elm/App/Data.elm +++ b/modules/webapp/src/main/elm/App/Data.elm @@ -18,15 +18,18 @@ import Api.Model.BasicResult exposing (BasicResult) import Api.Model.VersionInfo exposing (VersionInfo) import Browser exposing (UrlRequest) import Browser.Navigation exposing (Key) +import Data.Dashboard exposing (Dashboard) import Data.Flags exposing (Flags) import Data.ServerEvent exposing (ServerEvent) import Data.UiSettings exposing (StoredUiSettings, UiSettings) import Data.UiTheme exposing (UiTheme) import Http +import Messages import Messages.UiLanguage exposing (UiLanguage) import Page exposing (Page(..)) import Page.CollectiveSettings.Data import Page.Dashboard.Data +import Page.Dashboard.DefaultDashboard import Page.ItemDetail.Data import Page.Login.Data import Page.ManageData.Data @@ -102,6 +105,7 @@ init key url flags_ settings = ( dbm, dbc ) = Page.Dashboard.Data.init flags + (Page.Dashboard.DefaultDashboard.getDefaultDashboard flags settings) searchViewMode = if settings.searchMenuVisible then @@ -214,9 +218,4 @@ defaultPage _ = getUiLanguage : Model -> UiLanguage getUiLanguage model = - case model.flags.account of - Just _ -> - model.uiSettings.uiLang - - Nothing -> - model.anonymousUiLang + Data.UiSettings.getUiLanguage model.flags model.uiSettings model.anonymousUiLang diff --git a/modules/webapp/src/main/elm/App/Update.elm b/modules/webapp/src/main/elm/App/Update.elm index 9d8e4754..394f87e0 100644 --- a/modules/webapp/src/main/elm/App/Update.elm +++ b/modules/webapp/src/main/elm/App/Update.elm @@ -364,6 +364,7 @@ applyClientSettings texts model settings = , setTheme , Sub.none ) + , updateDashboard texts Page.Dashboard.Data.reloadUiSettings , updateUserSettings texts Page.UserSettings.Data.UpdateSettings , updateSearch texts Page.Search.Data.UiSettingsUpdated , updateItemDetail texts Page.ItemDetail.Data.UiSettingsUpdated @@ -375,7 +376,12 @@ updateDashboard : Messages -> Page.Dashboard.Data.Msg -> Model -> ( Model, Cmd M updateDashboard texts lmsg model = let ( dbm, dbc, dbs ) = - Page.Dashboard.Update.update texts.dashboard model.key model.flags lmsg model.dashboardModel + Page.Dashboard.Update.update texts.dashboard + model.uiSettings + model.key + model.flags + lmsg + model.dashboardModel in ( { model | dashboardModel = dbm } , Cmd.map DashboardMsg dbc diff --git a/modules/webapp/src/main/elm/Comp/Basic.elm b/modules/webapp/src/main/elm/Comp/Basic.elm index a7f6874c..03441c5b 100644 --- a/modules/webapp/src/main/elm/Comp/Basic.elm +++ b/modules/webapp/src/main/elm/Comp/Basic.elm @@ -207,7 +207,7 @@ loadingDimmer : { label : String, active : Bool } -> Html msg loadingDimmer cfg = let content = - div [ class "text-gray-200" ] + div [ class "text-gray-200 " ] [ i [ class "fa fa-circle-notch animate-spin" ] [] , span [ class "ml-2" ] [ text cfg.label diff --git a/modules/webapp/src/main/elm/Comp/BoxQueryView.elm b/modules/webapp/src/main/elm/Comp/BoxQueryView.elm new file mode 100644 index 00000000..91108239 --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/BoxQueryView.elm @@ -0,0 +1,184 @@ +module Comp.BoxQueryView exposing (..) + +import Api +import Api.Model.ItemLight exposing (ItemLight) +import Api.Model.ItemLightList exposing (ItemLightList) +import Api.Model.ItemQuery exposing (ItemQuery) +import Comp.Basic +import Data.BoxContent exposing (QueryData, SearchQuery(..)) +import Data.Flags exposing (Flags) +import Data.ItemTemplate as IT +import Data.Items +import Data.SearchMode +import Html exposing (Html, a, div, i, table, tbody, td, text, th, thead, tr) +import Html.Attributes exposing (class, classList) +import Http +import Messages.Comp.BoxQueryView exposing (Texts) +import Page exposing (Page(..)) +import Styles + + +type alias Model = + { results : ViewResult + , meta : QueryData + } + + +type ViewResult + = Loading + | Loaded ItemLightList + | Failed Http.Error + + +type Msg + = ItemsResp (Result Http.Error ItemLightList) + + +init : Flags -> QueryData -> ( Model, Cmd Msg ) +init flags data = + ( { results = Loading + , meta = data + } + , case data.query of + SearchQueryString q -> + Api.itemSearch flags (mkQuery q data) ItemsResp + + SearchQueryBookmark bmId -> + Api.itemSearchBookmark flags (mkQuery bmId data) ItemsResp + ) + + + +--- Update + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + ItemsResp (Ok list) -> + ( { model | results = Loaded list }, Cmd.none ) + + ItemsResp (Err err) -> + ( { model | results = Failed err }, Cmd.none ) + + + +--- View + + +view : Texts -> Model -> Html Msg +view texts model = + case model.results of + Loading -> + div [ class "h-24 " ] + [ Comp.Basic.loadingDimmer + { label = "" + , active = True + } + ] + + Failed err -> + div + [ class "py-4" + , class Styles.errorMessage + ] + [ text texts.errorOccurred + , text ": " + , text (texts.httpError err) + ] + + Loaded list -> + if list.groups == [] then + viewEmpty texts + + else + viewItems texts model.meta list + + +viewItems : Texts -> QueryData -> ItemLightList -> Html Msg +viewItems texts meta list = + let + items = + Data.Items.flatten list + in + table [ class "w-full divide-y dark:divide-slate-500" ] + (viewItemHead meta ++ [ tbody [] <| List.map (viewItemRow texts meta) items ]) + + +viewItemHead : QueryData -> List (Html Msg) +viewItemHead meta = + case meta.header of + [] -> + [] + + labels -> + [ thead [] + [ tr [] + (List.map (\n -> th [ class "text-left text-sm" ] [ text n ]) labels) + ] + ] + + +viewItemRow : Texts -> QueryData -> ItemLight -> Html Msg +viewItemRow texts meta item = + let + ( col1, cols ) = + getColumns meta + + render tpl = + IT.render tpl texts.templateCtx item + + td1 = + td [ class "py-2 px-1" ] + [ a + [ class Styles.link + , Page.href (ItemDetailPage item.id) + ] + [ text (render col1) + ] + ] + + tdRem index col = + td + [ class "py-2 px-1" + , classList [ ( "hidden md:table-cell", index > 1 ) ] + ] + [ text (render col) + ] + in + tr [] + (td1 :: List.indexedMap tdRem cols) + + +viewEmpty : Texts -> Html Msg +viewEmpty texts = + div [ class "flex justify-center items-center h-full" ] + [ div [ class "px-4 py-4 text-center align-middle text-lg" ] + [ i [ class "fa fa-eraser mr-2" ] [] + , text texts.noResults + ] + ] + + + +--- Helpers + + +getColumns : QueryData -> ( IT.ItemTemplate, List IT.ItemTemplate ) +getColumns meta = + case meta.columns of + x :: xs -> + ( x, xs ) + + [] -> + ( IT.name, [ IT.correspondent, IT.dateShort ] ) + + +mkQuery : String -> QueryData -> ItemQuery +mkQuery q meta = + { query = q + , limit = Just meta.limit + , offset = Nothing + , searchMode = Just <| Data.SearchMode.asString Data.SearchMode.Normal + , withDetails = Just meta.details + } diff --git a/modules/webapp/src/main/elm/Comp/BoxSummaryView.elm b/modules/webapp/src/main/elm/Comp/BoxSummaryView.elm new file mode 100644 index 00000000..2dc68eca --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/BoxSummaryView.elm @@ -0,0 +1,160 @@ +module Comp.BoxSummaryView exposing (..) + +import Api +import Api.Model.ItemQuery exposing (ItemQuery) +import Api.Model.SearchStats exposing (SearchStats) +import Comp.Basic +import Comp.SearchStatsView +import Data.BoxContent exposing (SearchQuery(..), SummaryData, SummaryShow(..)) +import Data.Flags exposing (Flags) +import Html exposing (Html, div, text) +import Html.Attributes exposing (class) +import Http +import Messages.Comp.BoxSummaryView exposing (Texts) +import Styles +import Util.List + + +type alias Model = + { results : ViewResult + , show : SummaryShow + } + + +type ViewResult + = Loading + | Loaded SearchStats + | Failed Http.Error + + +type Msg + = StatsResp (Result Http.Error SearchStats) + + +init : Flags -> SummaryData -> ( Model, Cmd Msg ) +init flags data = + ( { results = Loading + , show = data.show + } + , case data.query of + SearchQueryString q -> + Api.itemSearchStats flags (mkQuery q) StatsResp + + SearchQueryBookmark bmId -> + Api.itemSearchStatsBookmark flags (mkQuery bmId) StatsResp + ) + + + +--- Update + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + StatsResp (Ok stats) -> + ( { model | results = Loaded stats }, Cmd.none ) + + StatsResp (Err err) -> + ( { model | results = Failed err }, Cmd.none ) + + + +--- View + + +view : Texts -> Model -> Html Msg +view texts model = + case model.results of + Loading -> + div [ class "h-24 " ] + [ Comp.Basic.loadingDimmer + { label = "" + , active = True + } + ] + + Failed err -> + div + [ class "py-4" + , class Styles.errorMessage + ] + [ text texts.errorOccurred + , text ": " + , text (texts.httpError err) + ] + + Loaded stats -> + case model.show of + Data.BoxContent.SummaryShowFields flag -> + Comp.SearchStatsView.view2 + texts.statsView + flag + "" + stats + + SummaryShowGeneral -> + viewGeneral texts stats + + +viewGeneral : Texts -> SearchStats -> Html Msg +viewGeneral texts stats = + let + tagCount = + List.length stats.tagCloud.items + + fieldCount = + List.length stats.fieldStats + + orgCount = + List.length stats.corrOrgStats + + persCount = + (stats.corrPersStats ++ stats.concPersStats) + |> List.map (.ref >> .id) + |> Util.List.distinct + |> List.length + + equipCount = + List.length stats.concEquipStats + + mklabel name = + div [ class "py-1 text-lg" ] [ text name ] + + value num = + div [ class "py-1 font-mono text-lg" ] [ text <| String.fromInt num ] + in + div [ class "opacity-90" ] + [ div [ class "flex flex-row" ] + [ div [ class "flex flex-col mr-4" ] + [ mklabel texts.basics.items + , mklabel texts.basics.tags + , mklabel texts.basics.customFields + , mklabel texts.basics.organization + , mklabel texts.basics.person + , mklabel texts.basics.equipment + ] + , div [ class "flex flex-col" ] + [ value stats.count + , value tagCount + , value fieldCount + , value orgCount + , value persCount + , value equipCount + ] + ] + ] + + + +--- Helpers + + +mkQuery : String -> ItemQuery +mkQuery query = + { query = query + , limit = Nothing + , offset = Nothing + , searchMode = Nothing + , withDetails = Nothing + } diff --git a/modules/webapp/src/main/elm/Comp/BoxView.elm b/modules/webapp/src/main/elm/Comp/BoxView.elm index b429cfe9..0e8bfcd0 100644 --- a/modules/webapp/src/main/elm/Comp/BoxView.elm +++ b/modules/webapp/src/main/elm/Comp/BoxView.elm @@ -1,28 +1,180 @@ module Comp.BoxView exposing (..) +import Comp.BoxQueryView +import Comp.BoxSummaryView import Data.Box exposing (Box) +import Data.BoxContent exposing (BoxContent(..), MessageData) import Data.Flags exposing (Flags) -import Html exposing (Html, div) +import Html exposing (Html, div, text) +import Html.Attributes exposing (class, classList) +import Markdown +import Messages.Comp.BoxView exposing (Texts) +import Styles as S type alias Model = - {} + { box : Box + , content : ContentModel + } + + +type ContentModel + = ContentMessage Data.BoxContent.MessageData + | ContentUpload (Maybe String) + | ContentQuery Comp.BoxQueryView.Model + | ContentSummary Comp.BoxSummaryView.Model type Msg - = Dummy + = QueryMsg Comp.BoxQueryView.Msg + | SummaryMsg Comp.BoxSummaryView.Msg init : Flags -> Box -> ( Model, Cmd Msg ) init flags box = - ( {}, Cmd.none ) + let + ( cm, cc ) = + contentInit flags box.content + in + ( { box = box + , content = cm + } + , cc + ) + + +contentInit : Flags -> BoxContent -> ( ContentModel, Cmd Msg ) +contentInit flags content = + case content of + BoxMessage data -> + ( ContentMessage data, Cmd.none ) + + BoxUpload source -> + ( ContentUpload source, Cmd.none ) + + BoxQuery data -> + let + ( qm, qc ) = + Comp.BoxQueryView.init flags data + in + ( ContentQuery qm, Cmd.map QueryMsg qc ) + + BoxSummary data -> + let + ( sm, sc ) = + Comp.BoxSummaryView.init flags data + in + ( ContentSummary sm, Cmd.map SummaryMsg sc ) --- Update + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + QueryMsg lm -> + case model.content of + ContentQuery qm -> + let + ( cm, cc ) = + Comp.BoxQueryView.update lm qm + in + ( { model | content = ContentQuery cm }, Cmd.map QueryMsg cc ) + + _ -> + unit model + + SummaryMsg lm -> + case model.content of + ContentSummary qm -> + let + ( cm, cc ) = + Comp.BoxSummaryView.update lm qm + in + ( { model | content = ContentSummary cm }, Cmd.map SummaryMsg cc ) + + _ -> + unit model + + +unit : Model -> ( Model, Cmd Msg ) +unit model = + ( model, Cmd.none ) + + + --- View -view : Model -> Html Msg -view model = - div [] [] +view : Texts -> Model -> Html Msg +view texts model = + div + [ classList [ ( S.box ++ "rounded", model.box.decoration ) ] + , class (spanStyle model.box) + , class "relative h-full" + , classList [ ( "hidden", not model.box.visible ) ] + ] + [ boxHeader model + , div [ class "px-2 py-1 h-5/6" ] + [ boxContent texts model + ] + ] + + +boxHeader : Model -> Html Msg +boxHeader model = + div + [ class "border-b dark:border-slate-500 flex flex-row py-1 bg-blue-50 dark:bg-slate-700 rounded-t" + , classList [ ( "hidden", not model.box.decoration || model.box.name == "" ) ] + ] + [ div [ class "flex text-lg tracking-medium italic px-2" ] + [ text model.box.name + ] + ] + + +boxContent : Texts -> Model -> Html Msg +boxContent texts model = + case model.content of + ContentMessage m -> + messageContent m + + ContentUpload sourceId -> + Debug.todo "not implemented" + + ContentQuery qm -> + Html.map QueryMsg + (Comp.BoxQueryView.view texts.queryView qm) + + ContentSummary qm -> + Html.map SummaryMsg + (Comp.BoxSummaryView.view texts.summaryView qm) + + +spanStyle : Box -> String +spanStyle box = + case box.colspan of + 1 -> + "" + + 2 -> + "col-span-1 md:col-span-2" + + 3 -> + "col-span-1 md:col-span-3" + + 4 -> + "col-span-1 md:col-span-4" + + _ -> + "col-span-1 md:col-span-5" + + +messageContent : MessageData -> Html msg +messageContent data = + div [ class "markdown-preview" ] + [ Markdown.toHtml [] data.title + , Markdown.toHtml [] data.body + ] diff --git a/modules/webapp/src/main/elm/Comp/DashboardView.elm b/modules/webapp/src/main/elm/Comp/DashboardView.elm index 974d0675..62e7576d 100644 --- a/modules/webapp/src/main/elm/Comp/DashboardView.elm +++ b/modules/webapp/src/main/elm/Comp/DashboardView.elm @@ -1,4 +1,4 @@ -module Comp.DashboardView exposing (Model, Msg, init, view, viewBox) +module Comp.DashboardView exposing (Model, Msg, init, update, view, viewBox) import Comp.BoxView import Data.Box exposing (Box) @@ -7,16 +7,17 @@ import Data.Flags exposing (Flags) import Dict exposing (Dict) import Html exposing (Html, div) import Html.Attributes exposing (class) +import Messages.Comp.DashboardView exposing (Texts) type alias Model = { dashboard : Dashboard - , boxModels : List Comp.BoxView.Model + , boxModels : Dict Int Comp.BoxView.Model } type Msg - = BoxMsg Comp.BoxView.Msg + = BoxMsg Int Comp.BoxView.Msg init : Flags -> Dashboard -> ( Model, Cmd Msg ) @@ -24,32 +25,61 @@ init flags db = let ( boxModels, cmds ) = List.map (Comp.BoxView.init flags) db.boxes - |> List.map (Tuple.mapSecond <| Cmd.map BoxMsg) + |> List.indexedMap (\a -> \( bm, bc ) -> ( bm, Cmd.map (BoxMsg a) bc )) |> List.unzip in ( { dashboard = db - , boxModels = boxModels + , boxModels = + List.indexedMap Tuple.pair boxModels + |> Dict.fromList } , Cmd.batch cmds ) +--- Update + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + BoxMsg index lm -> + case Dict.get index model.boxModels of + Just bm -> + let + ( cm, cc ) = + Comp.BoxView.update lm bm + in + ( { model | boxModels = Dict.insert index cm model.boxModels } + , Cmd.map (BoxMsg index) cc + ) + + Nothing -> + unit model + + +unit : Model -> ( Model, Cmd Msg ) +unit model = + ( model, Cmd.none ) + + + --- View -view : Model -> Html Msg -view model = +view : Texts -> Model -> Html Msg +view texts model = div [ class (gridStyle model.dashboard) ] - (List.map viewBox model.boxModels) + (List.indexedMap (viewBox texts) <| Dict.values model.boxModels) -viewBox : Comp.BoxView.Model -> Html Msg -viewBox box = - Html.map BoxMsg - (Comp.BoxView.view box) +viewBox : Texts -> Int -> Comp.BoxView.Model -> Html Msg +viewBox texts index box = + Html.map (BoxMsg index) + (Comp.BoxView.view texts.boxView box) diff --git a/modules/webapp/src/main/elm/Comp/SearchStatsView.elm b/modules/webapp/src/main/elm/Comp/SearchStatsView.elm index d091465e..1d3bb8ad 100644 --- a/modules/webapp/src/main/elm/Comp/SearchStatsView.elm +++ b/modules/webapp/src/main/elm/Comp/SearchStatsView.elm @@ -8,6 +8,7 @@ module Comp.SearchStatsView exposing ( nameOrLabel , sortFields + , view , view2 ) @@ -36,8 +37,13 @@ sortFields fields = --- View2 -view2 : Texts -> String -> SearchStats -> Html msg -view2 texts classes stats = +view : Texts -> String -> SearchStats -> Html msg +view texts classes stats = + view2 texts True classes stats + + +view2 : Texts -> Bool -> String -> SearchStats -> Html msg +view2 texts showCount classes stats = let isNumField f = f.sum > 0 @@ -78,7 +84,10 @@ view2 texts classes stats = in div [ class classes ] [ div [ class "flex flex-col md:flex-row" ] - [ div [ class "px-8 py-4" ] + [ div + [ class "px-8 py-4" + , classList [ ( "hidden", not showCount ) ] + ] [ B.stats { rootClass = "" , valueClass = "text-4xl" diff --git a/modules/webapp/src/main/elm/Data/Bookmarks.elm b/modules/webapp/src/main/elm/Data/Bookmarks.elm index e9c83c5c..2074f229 100644 --- a/modules/webapp/src/main/elm/Data/Bookmarks.elm +++ b/modules/webapp/src/main/elm/Data/Bookmarks.elm @@ -11,6 +11,7 @@ module Data.Bookmarks exposing , bookmarksDecoder , empty , exists + , findById , sort ) @@ -34,6 +35,12 @@ type alias Bookmarks = List BookmarkedQuery +findById : String -> Bookmarks -> Maybe BookmarkedQuery +findById id all = + List.filter (\e -> e.id == id) all + |> List.head + + {-| Checks wether a bookmark of this name already exists. -} exists : String -> Bookmarks -> Bool diff --git a/modules/webapp/src/main/elm/Data/BoxContent.elm b/modules/webapp/src/main/elm/Data/BoxContent.elm index 82f4c8e1..7385c916 100644 --- a/modules/webapp/src/main/elm/Data/BoxContent.elm +++ b/modules/webapp/src/main/elm/Data/BoxContent.elm @@ -1,10 +1,17 @@ -module Data.BoxContent exposing (BoxContent(..), MessageData, QueryData, SummaryData) +module Data.BoxContent exposing + ( BoxContent(..) + , MessageData + , QueryData + , SearchQuery(..) + , SummaryData + , SummaryShow(..) + ) -import Data.ItemArrange exposing (ItemArrange) +import Data.ItemTemplate exposing (ItemTemplate) type BoxContent - = BoxUpload + = BoxUpload (Maybe String) | BoxMessage MessageData | BoxQuery QueryData | BoxSummary SummaryData @@ -17,11 +24,25 @@ type alias MessageData = type alias QueryData = - { query : String - , view : ItemArrange + { query : SearchQuery + , limit : Int + , details : Bool + , header : List String + , columns : List ItemTemplate } type alias SummaryData = - { query : String + { query : SearchQuery + , show : SummaryShow } + + +type SummaryShow + = SummaryShowFields Bool + | SummaryShowGeneral + + +type SearchQuery + = SearchQueryString String + | SearchQueryBookmark String diff --git a/modules/webapp/src/main/elm/Data/UiSettings.elm b/modules/webapp/src/main/elm/Data/UiSettings.elm index 50e1a540..bf0d9a80 100644 --- a/modules/webapp/src/main/elm/Data/UiSettings.elm +++ b/modules/webapp/src/main/elm/Data/UiSettings.elm @@ -18,6 +18,7 @@ module Data.UiSettings exposing , defaults , fieldHidden , fieldVisible + , getUiLanguage , merge , mergeDefaults , pdfUrl @@ -444,6 +445,16 @@ pdfUrl settings flags originalUrl = Data.Pdf.serverUrl originalUrl +getUiLanguage : Flags -> UiSettings -> UiLanguage -> UiLanguage +getUiLanguage flags settings default = + case flags.account of + Just _ -> + settings.uiLang + + Nothing -> + default + + --- Helpers diff --git a/modules/webapp/src/main/elm/Messages/Comp/BoxQueryView.elm b/modules/webapp/src/main/elm/Messages/Comp/BoxQueryView.elm new file mode 100644 index 00000000..7b13ab8a --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/BoxQueryView.elm @@ -0,0 +1,46 @@ +module Messages.Comp.BoxQueryView exposing (Texts, de, gb) + +import Data.ItemTemplate as IT +import Http +import Messages.Basics +import Messages.Comp.HttpError +import Messages.Data.Direction +import Messages.DateFormat as DF +import Messages.UiLanguage + + +type alias Texts = + { httpError : Http.Error -> String + , errorOccurred : String + , basics : Messages.Basics.Texts + , noResults : String + , templateCtx : IT.TemplateContext + } + + +gb : Texts +gb = + { httpError = Messages.Comp.HttpError.gb + , errorOccurred = "Error retrieving data." + , basics = Messages.Basics.gb + , noResults = "No items found." + , templateCtx = + { dateFormatLong = DF.formatDateLong Messages.UiLanguage.English + , dateFormatShort = DF.formatDateShort Messages.UiLanguage.English + , directionLabel = Messages.Data.Direction.gb + } + } + + +de : Texts +de = + { httpError = Messages.Comp.HttpError.de + , errorOccurred = "Fehler beim Laden der Daten." + , basics = Messages.Basics.de + , noResults = "Keine Dokumente gefunden." + , templateCtx = + { dateFormatLong = DF.formatDateLong Messages.UiLanguage.German + , dateFormatShort = DF.formatDateShort Messages.UiLanguage.German + , directionLabel = Messages.Data.Direction.de + } + } diff --git a/modules/webapp/src/main/elm/Messages/Comp/BoxSummaryView.elm b/modules/webapp/src/main/elm/Messages/Comp/BoxSummaryView.elm new file mode 100644 index 00000000..865d5848 --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/BoxSummaryView.elm @@ -0,0 +1,32 @@ +module Messages.Comp.BoxSummaryView exposing (Texts, de, gb) + +import Http +import Messages.Basics +import Messages.Comp.HttpError +import Messages.Comp.SearchStatsView + + +type alias Texts = + { httpError : Http.Error -> String + , errorOccurred : String + , statsView : Messages.Comp.SearchStatsView.Texts + , basics : Messages.Basics.Texts + } + + +gb : Texts +gb = + { httpError = Messages.Comp.HttpError.gb + , errorOccurred = "Error retrieving data." + , statsView = Messages.Comp.SearchStatsView.gb + , basics = Messages.Basics.gb + } + + +de : Texts +de = + { httpError = Messages.Comp.HttpError.de + , errorOccurred = "Fehler beim Laden der Daten." + , statsView = Messages.Comp.SearchStatsView.de + , basics = Messages.Basics.de + } diff --git a/modules/webapp/src/main/elm/Messages/Comp/BoxView.elm b/modules/webapp/src/main/elm/Messages/Comp/BoxView.elm new file mode 100644 index 00000000..0dda16c8 --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/BoxView.elm @@ -0,0 +1,24 @@ +module Messages.Comp.BoxView exposing (Texts, de, gb) + +import Messages.Comp.BoxQueryView +import Messages.Comp.BoxSummaryView + + +type alias Texts = + { queryView : Messages.Comp.BoxQueryView.Texts + , summaryView : Messages.Comp.BoxSummaryView.Texts + } + + +gb : Texts +gb = + { queryView = Messages.Comp.BoxQueryView.gb + , summaryView = Messages.Comp.BoxSummaryView.gb + } + + +de : Texts +de = + { queryView = Messages.Comp.BoxQueryView.de + , summaryView = Messages.Comp.BoxSummaryView.de + } diff --git a/modules/webapp/src/main/elm/Messages/Comp/DashboardView.elm b/modules/webapp/src/main/elm/Messages/Comp/DashboardView.elm new file mode 100644 index 00000000..58544c03 --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/DashboardView.elm @@ -0,0 +1,20 @@ +module Messages.Comp.DashboardView exposing (Texts, de, gb) + +import Messages.Comp.BoxView + + +type alias Texts = + { boxView : Messages.Comp.BoxView.Texts + } + + +gb : Texts +gb = + { boxView = Messages.Comp.BoxView.gb + } + + +de : Texts +de = + { boxView = Messages.Comp.BoxView.de + } diff --git a/modules/webapp/src/main/elm/Messages/DateFormat.elm b/modules/webapp/src/main/elm/Messages/DateFormat.elm index 01ac198f..6b247b8f 100644 --- a/modules/webapp/src/main/elm/Messages/DateFormat.elm +++ b/modules/webapp/src/main/elm/Messages/DateFormat.elm @@ -10,6 +10,7 @@ module Messages.DateFormat exposing , formatDateLong , formatDateShort , formatDateTimeLong + , formatDateTimeShort ) import DateFormat exposing (Token) @@ -68,6 +69,11 @@ formatDateShort lang millis = format lang .dateShort millis +formatDateTimeShort : UiLanguage -> Int -> String +formatDateTimeShort lang millis = + format lang .dateTimeShort millis + + --- Language Definitions diff --git a/modules/webapp/src/main/elm/Messages/Page/Dashboard.elm b/modules/webapp/src/main/elm/Messages/Page/Dashboard.elm index 86c85fac..1ac4b4f0 100644 --- a/modules/webapp/src/main/elm/Messages/Page/Dashboard.elm +++ b/modules/webapp/src/main/elm/Messages/Page/Dashboard.elm @@ -1,6 +1,7 @@ module Messages.Page.Dashboard exposing (Texts, de, gb) import Messages.Comp.BookmarkChooser +import Messages.Comp.DashboardView import Messages.Comp.EquipmentManage import Messages.Comp.FolderManage import Messages.Comp.NotificationHookManage @@ -10,6 +11,7 @@ import Messages.Comp.PersonManage import Messages.Comp.ShareManage import Messages.Comp.SourceManage import Messages.Comp.TagManage +import Messages.Page.DefaultDashboard type alias Texts = @@ -23,6 +25,8 @@ type alias Texts = , equipManage : Messages.Comp.EquipmentManage.Texts , tagManage : Messages.Comp.TagManage.Texts , folderManage : Messages.Comp.FolderManage.Texts + , dashboard : Messages.Comp.DashboardView.Texts + , defaultDashboard : Messages.Page.DefaultDashboard.Texts } @@ -38,6 +42,8 @@ gb = , equipManage = Messages.Comp.EquipmentManage.gb , tagManage = Messages.Comp.TagManage.gb , folderManage = Messages.Comp.FolderManage.gb + , dashboard = Messages.Comp.DashboardView.gb + , defaultDashboard = Messages.Page.DefaultDashboard.gb } @@ -53,4 +59,6 @@ de = , equipManage = Messages.Comp.EquipmentManage.de , tagManage = Messages.Comp.TagManage.de , folderManage = Messages.Comp.FolderManage.de + , dashboard = Messages.Comp.DashboardView.de + , defaultDashboard = Messages.Page.DefaultDashboard.de } diff --git a/modules/webapp/src/main/elm/Messages/Page/DefaultDashboard.elm b/modules/webapp/src/main/elm/Messages/Page/DefaultDashboard.elm new file mode 100644 index 00000000..72203f1b --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Page/DefaultDashboard.elm @@ -0,0 +1,57 @@ +module Messages.Page.DefaultDashboard exposing (Texts, de, gb) + +import Messages.Basics + + +type alias Texts = + { basics : Messages.Basics.Texts + , default : String + , welcomeName : String + , welcomeTitle : String + , welcomeBody : String + , summaryName : String + , dueInDays : Int -> String + , dueHeaderColumns : List String + , newDocsName : String + } + + +gb : Texts +gb = + let + b = + Messages.Basics.gb + in + { basics = b + , default = "Default" + , welcomeName = "Welcome Message" + , welcomeTitle = "# Welcome to Docspell" + , welcomeBody = "Docspell keeps your documents organized." + , summaryName = "Summary" + , dueInDays = \n -> "Due in " ++ String.fromInt n ++ " days" + , dueHeaderColumns = dueHeaderCols b + , newDocsName = "New Documents" + } + + +de : Texts +de = + let + b = + Messages.Basics.de + in + { basics = b + , default = "Standard" + , welcomeName = "Willkommens-Nachricht" + , welcomeTitle = "# Willkommen zu Docspell" + , welcomeBody = "Docspell behält die Übersicht über deine Dokumene." + , summaryName = "Zahlen" + , dueInDays = \n -> "Fällig in " ++ String.fromInt n ++ " Tagen" + , newDocsName = "Neue Dokumente" + , dueHeaderColumns = dueHeaderCols b + } + + +dueHeaderCols : Messages.Basics.Texts -> List String +dueHeaderCols b = + [ b.name, b.correspondent, b.date ] diff --git a/modules/webapp/src/main/elm/Page/Dashboard/Data.elm b/modules/webapp/src/main/elm/Page/Dashboard/Data.elm index 1b959891..669bcd4b 100644 --- a/modules/webapp/src/main/elm/Page/Dashboard/Data.elm +++ b/modules/webapp/src/main/elm/Page/Dashboard/Data.elm @@ -11,6 +11,8 @@ module Page.Dashboard.Data exposing , Msg(..) , SideMenuModel , init + , reloadDashboard + , reloadUiSettings ) import Api @@ -26,8 +28,8 @@ import Comp.ShareManage import Comp.SourceManage import Comp.TagManage import Data.Bookmarks exposing (AllBookmarks) +import Data.Dashboard exposing (Dashboard) import Data.Flags exposing (Flags) -import Page.Dashboard.DefaultDashboard as DefaultDashboard type alias SideMenuModel = @@ -41,11 +43,11 @@ type alias Model = } -init : Flags -> ( Model, Cmd Msg ) -init flags = +init : Flags -> Dashboard -> ( Model, Cmd Msg ) +init flags db = let ( dm, dc ) = - Comp.DashboardView.init flags DefaultDashboard.value + Comp.DashboardView.init flags db in ( { sideMenu = { bookmarkChooser = Comp.BookmarkChooser.init Data.Bookmarks.empty @@ -69,6 +71,16 @@ initCmd flags = Api.getBookmarks flags ignoreBookmarkError +reloadDashboard : Msg +reloadDashboard = + InitDashboard + + +reloadUiSettings : Msg +reloadUiSettings = + InitDashboard + + type Msg = GetBookmarksResp AllBookmarks | BookmarkMsg Comp.BookmarkChooser.Msg diff --git a/modules/webapp/src/main/elm/Page/Dashboard/DefaultDashboard.elm b/modules/webapp/src/main/elm/Page/Dashboard/DefaultDashboard.elm index 8f12acee..640af035 100644 --- a/modules/webapp/src/main/elm/Page/Dashboard/DefaultDashboard.elm +++ b/modules/webapp/src/main/elm/Page/Dashboard/DefaultDashboard.elm @@ -1,57 +1,121 @@ -module Page.Dashboard.DefaultDashboard exposing (..) +module Page.Dashboard.DefaultDashboard exposing (getDefaultDashboard, value) import Data.Box exposing (Box) -import Data.BoxContent exposing (BoxContent(..)) +import Data.BoxContent exposing (BoxContent(..), SearchQuery(..), SummaryShow(..)) import Data.Dashboard exposing (Dashboard) -import Data.ItemArrange +import Data.Flags exposing (Flags) +import Data.ItemTemplate as IT +import Data.UiSettings exposing (UiSettings) +import Messages +import Messages.Page.DefaultDashboard exposing (Texts) +import Messages.UiLanguage -value : Dashboard -value = - { name = "Default" +value : Texts -> Dashboard +value texts = + { name = texts.default , columns = 2 , boxes = - [ messageBox - , newDocuments - , summary + [ messageBox texts + , summary2 + , newDocuments texts + , dueDocuments texts + , summary texts ] } -messageBox : Box -messageBox = - { name = "Welcome Message" +getDefaultDashboard : Flags -> UiSettings -> Dashboard +getDefaultDashboard flags settings = + let + lang = + Data.UiSettings.getUiLanguage flags settings Messages.UiLanguage.English + + texts = + Messages.get lang + in + value texts.dashboard.defaultDashboard + + + +--- Boxes + + +messageBox : Texts -> Box +messageBox texts = + { name = texts.welcomeName , visible = True , decoration = False , colspan = 2 , content = BoxMessage - { title = "Welcome to Docspell" - , body = "" + { title = texts.welcomeTitle + , body = texts.welcomeBody } } -newDocuments : Box -newDocuments = - { name = "New Documents" +newDocuments : Texts -> Box +newDocuments texts = + { name = texts.newDocsName , visible = True , decoration = True , colspan = 1 , content = BoxQuery - { query = "inbox:yes" - , view = Data.ItemArrange.List + { query = SearchQueryString "inbox:yes" + , limit = 5 + , details = True + , header = [] + , columns = [] } } -summary : Box -summary = - { name = "Summary" +dueDocuments : Texts -> Box +dueDocuments texts = + { name = texts.dueInDays 10 , visible = True , decoration = True , colspan = 1 , content = - BoxSummary { query = "" } + BoxQuery + { query = SearchQueryString "due>today;-10d due Box +summary texts = + { name = texts.summaryName + , visible = True + , decoration = True + , colspan = 1 + , content = + BoxSummary + { query = SearchQueryString "" + , show = SummaryShowGeneral + } + } + + +summary2 : Box +summary2 = + { name = "" + , visible = True + , decoration = True + , colspan = 2 + , content = + BoxSummary + { query = SearchQueryString "" + , show = SummaryShowFields False + } } diff --git a/modules/webapp/src/main/elm/Page/Dashboard/Update.elm b/modules/webapp/src/main/elm/Page/Dashboard/Update.elm index 4b2d95a8..593a2e1f 100644 --- a/modules/webapp/src/main/elm/Page/Dashboard/Update.elm +++ b/modules/webapp/src/main/elm/Page/Dashboard/Update.elm @@ -20,6 +20,7 @@ import Comp.ShareManage import Comp.SourceManage import Comp.TagManage import Data.Flags exposing (Flags) +import Data.UiSettings exposing (UiSettings) import Messages.Page.Dashboard exposing (Texts) import Page exposing (Page(..)) import Page.Dashboard.Data exposing (..) @@ -27,8 +28,8 @@ import Page.Dashboard.DefaultDashboard import Set -update : Texts -> Nav.Key -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg ) -update texts navKey flags msg model = +update : Texts -> UiSettings -> Nav.Key -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg ) +update texts settings navKey flags msg model = case msg of GetBookmarksResp list -> let @@ -59,8 +60,11 @@ update texts navKey flags msg model = InitDashboard -> let + board = + Page.Dashboard.DefaultDashboard.getDefaultDashboard flags settings + ( dm, dc ) = - Comp.DashboardView.init flags Page.Dashboard.DefaultDashboard.value + Comp.DashboardView.init flags board in ( { model | content = Home dm }, Cmd.map DashboardMsg dc, Sub.none ) @@ -242,7 +246,16 @@ update texts navKey flags msg model = unit model DashboardMsg lm -> - unit model + case model.content of + Home m -> + let + ( dm, dc ) = + Comp.DashboardView.update lm m + in + ( { model | content = Home dm }, Cmd.map DashboardMsg dc, Sub.none ) + + _ -> + unit model unit : Model -> ( Model, Cmd Msg, Sub Msg ) diff --git a/modules/webapp/src/main/elm/Page/Dashboard/View.elm b/modules/webapp/src/main/elm/Page/Dashboard/View.elm index 31a87018..bf3e90e0 100644 --- a/modules/webapp/src/main/elm/Page/Dashboard/View.elm +++ b/modules/webapp/src/main/elm/Page/Dashboard/View.elm @@ -48,7 +48,7 @@ viewContent texts flags settings model = [ case model.content of Home m -> Html.map DashboardMsg - (Comp.DashboardView.view m) + (Comp.DashboardView.view texts.dashboard m) Webhook m -> viewHookManage texts settings m diff --git a/modules/webapp/src/main/elm/Page/Search/View2.elm b/modules/webapp/src/main/elm/Page/Search/View2.elm index 3727bb49..8977bae4 100644 --- a/modules/webapp/src/main/elm/Page/Search/View2.elm +++ b/modules/webapp/src/main/elm/Page/Search/View2.elm @@ -563,7 +563,7 @@ editMenuBar texts model svm = searchStats : Texts -> Flags -> UiSettings -> Model -> List (Html Msg) searchStats texts _ settings model = if settings.searchStatsVisible then - [ Comp.SearchStatsView.view2 texts.searchStatsView "my-2" model.searchStats + [ Comp.SearchStatsView.view texts.searchStatsView "my-2" model.searchStats ] else