mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-23 10:58:26 +00:00
Some predefined boxes for a dashboard
This commit is contained in:
@ -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
|
||||
|
184
modules/webapp/src/main/elm/Comp/BoxQueryView.elm
Normal file
184
modules/webapp/src/main/elm/Comp/BoxQueryView.elm
Normal file
@ -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
|
||||
}
|
160
modules/webapp/src/main/elm/Comp/BoxSummaryView.elm
Normal file
160
modules/webapp/src/main/elm/Comp/BoxSummaryView.elm
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
]
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
Reference in New Issue
Block a user