Some predefined boxes for a dashboard

This commit is contained in:
eikek
2022-01-26 21:22:20 +01:00
parent 0337be98f9
commit 370679daed
25 changed files with 1004 additions and 80 deletions

View File

@ -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

View 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
}

View 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
}

View File

@ -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
]

View File

@ -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)

View File

@ -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"