Merge pull request #124 from eikek/item-list

Item list
This commit is contained in:
eikek
2020-05-17 22:30:45 +02:00
committed by GitHub
15 changed files with 337 additions and 26 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -0,0 +1,199 @@
module Comp.ItemCardList exposing
( Model
, Msg(..)
, init
, nextItem
, prevItem
, update
, view
)
import Api.Model.ItemLight exposing (ItemLight)
import Api.Model.ItemLightGroup exposing (ItemLightGroup)
import Api.Model.ItemLightList exposing (ItemLightList)
import Data.Direction
import Data.Flags exposing (Flags)
import Data.Icons as Icons
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Util.List
import Util.String
import Util.Time
type alias Model =
{ results : ItemLightList
}
type Msg
= SetResults ItemLightList
| SelectItem ItemLight
init : Model
init =
{ results = Api.Model.ItemLightList.empty
}
nextItem : Model -> String -> Maybe ItemLight
nextItem model id =
List.concatMap .items model.results.groups
|> Util.List.findNext (\i -> i.id == id)
prevItem : Model -> String -> Maybe ItemLight
prevItem model id =
List.concatMap .items model.results.groups
|> Util.List.findPrev (\i -> i.id == id)
--- Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe ItemLight )
update _ msg model =
case msg of
SetResults list ->
let
newModel =
{ model | results = list }
in
( newModel, Cmd.none, Nothing )
SelectItem item ->
( model, Cmd.none, Just item )
--- View
view : Model -> Html Msg
view model =
div [ class "ui container" ]
(List.map viewGroup model.results.groups)
viewGroup : ItemLightGroup -> Html Msg
viewGroup group =
div [ class "item-group" ]
[ div [ class "ui horizontal divider header item-list" ]
[ i [ class "calendar alternate outline icon" ] []
, text group.name
]
, div [ class "ui stackable three cards" ]
(List.map viewItem group.items)
]
viewItem : ItemLight -> Html Msg
viewItem item =
let
dirIcon =
i [ class (Data.Direction.iconFromMaybe item.direction) ] []
corr =
List.filterMap identity [ item.corrOrg, item.corrPerson ]
|> List.map .name
|> List.intersperse ", "
|> String.concat
conc =
List.filterMap identity [ item.concPerson, item.concEquip ]
|> List.map .name
|> List.intersperse ", "
|> String.concat
dueDate =
Maybe.map Util.Time.formatDateShort item.dueDate
|> Maybe.withDefault ""
isConfirmed =
item.state /= "created"
newColor =
"blue"
in
a
[ classList
[ ( "ui fluid card", True )
, ( newColor, not isConfirmed )
]
, href "#"
, onClick (SelectItem item)
]
[ div [ class "content" ]
[ div
[ class "header"
, Data.Direction.labelFromMaybe item.direction
|> title
]
[ dirIcon
, Util.String.underscoreToSpace item.name
|> text
]
, span [ class "meta" ]
[ div
[ classList
[ ( "ui ribbon label", True )
, ( newColor, True )
, ( "invisible", isConfirmed )
]
]
[ i [ class "exclamation icon" ] []
, text " New"
]
]
, span [ class "right floated meta" ]
[ Util.Time.formatDate item.date |> text
]
]
, div [ class "content" ]
[ div [ class "ui horizontal list" ]
[ div
[ class "item"
, title "Correspondent"
]
[ Icons.correspondentIcon
, text " "
, Util.String.withDefault "-" corr |> text
]
, div
[ class "item"
, title "Concerning"
]
[ Icons.concernedIcon
, text " "
, Util.String.withDefault "-" conc |> text
]
]
, div [ class "right floated meta" ]
[ div [ class "ui horizontal list" ]
[ div
[ class "item"
, title "Source"
]
[ text item.source
]
, div
[ class "item"
, title ("Due on " ++ dueDate)
]
[ div
[ classList
[ ( "ui basic grey label", True )
, ( "invisible hidden", item.dueDate == Nothing )
]
]
[ Icons.dueDateIcon
, text (" " ++ dueDate)
]
]
]
]
]
]

View File

@ -31,6 +31,7 @@ import Comp.SentMails
import Comp.YesNoDimmer import Comp.YesNoDimmer
import Data.Direction exposing (Direction) import Data.Direction exposing (Direction)
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.Icons as Icons
import DatePicker exposing (DatePicker) import DatePicker exposing (DatePicker)
import Dict exposing (Dict) import Dict exposing (Dict)
import Html exposing (..) import Html exposing (..)
@ -1071,7 +1072,7 @@ view inav model =
[ Html.map DeleteItemConfirm (Comp.YesNoDimmer.view model.deleteItemConfirm) [ Html.map DeleteItemConfirm (Comp.YesNoDimmer.view model.deleteItemConfirm)
, div , div
[ classList [ classList
[ ( "four wide column", True ) [ ( "sixteen wide mobile six wide tablet five wide computer column", True )
, ( "invisible", not model.menuOpen ) , ( "invisible", not model.menuOpen )
] ]
] ]
@ -1083,7 +1084,7 @@ view inav model =
) )
, div , div
[ classList [ classList
[ ( "twelve", model.menuOpen ) [ ( "sixteen wide mobile ten wide tablet eleven wide computer column", model.menuOpen )
, ( "sixteen", not model.menuOpen ) , ( "sixteen", not model.menuOpen )
, ( "wide column", True ) , ( "wide column", True )
] ]
@ -1416,7 +1417,7 @@ renderItemInfo model =
[ class "item" [ class "item"
, title "Due Date" , title "Due Date"
] ]
[ i [ class "bell icon" ] [] [ Icons.dueDateIcon
, Maybe.map Util.Time.formatDate model.item.dueDate , Maybe.map Util.Time.formatDate model.item.dueDate
|> Maybe.withDefault "" |> Maybe.withDefault ""
|> text |> text
@ -1427,7 +1428,7 @@ renderItemInfo model =
[ class "item" [ class "item"
, title "Correspondent" , title "Correspondent"
] ]
[ i [ class "envelope outline icon" ] [] [ Icons.correspondentIcon
, List.filterMap identity [ model.item.corrOrg, model.item.corrPerson ] , List.filterMap identity [ model.item.corrOrg, model.item.corrPerson ]
|> List.map .name |> List.map .name
|> String.join ", " |> String.join ", "
@ -1440,7 +1441,7 @@ renderItemInfo model =
[ class "item" [ class "item"
, title "Concerning" , title "Concerning"
] ]
[ i [ class "comment outline icon" ] [] [ Icons.concernedIcon
, List.filterMap identity [ model.item.concPerson, model.item.concEquipment ] , List.filterMap identity [ model.item.concPerson, model.item.concEquipment ]
|> List.map .name |> List.map .name
|> String.join ", " |> String.join ", "

View File

@ -5,6 +5,7 @@ module Data.Direction exposing
, icon , icon
, iconFromMaybe , iconFromMaybe
, iconFromString , iconFromString
, labelFromMaybe
, toString , toString
) )
@ -70,3 +71,10 @@ iconFromMaybe : Maybe String -> String
iconFromMaybe ms = iconFromMaybe ms =
Maybe.map iconFromString ms Maybe.map iconFromString ms
|> Maybe.withDefault unknownIcon |> Maybe.withDefault unknownIcon
labelFromMaybe : Maybe String -> String
labelFromMaybe ms =
Maybe.andThen fromString ms
|> Maybe.map toString
|> Maybe.withDefault "Direction"

View File

@ -0,0 +1,41 @@
module Data.Icons exposing
( concerned
, concernedIcon
, correspondent
, correspondentIcon
, dueDate
, dueDateIcon
)
import Html exposing (Html, i)
import Html.Attributes exposing (class)
concerned : String
concerned =
"crosshairs icon"
concernedIcon : Html msg
concernedIcon =
i [ class concerned ] []
correspondent : String
correspondent =
"address card outline icon"
correspondentIcon : Html msg
correspondentIcon =
i [ class correspondent ] []
dueDate : String
dueDate =
"bell icon"
dueDateIcon : Html msg
dueDateIcon =
i [ class dueDate ] []

View File

@ -7,25 +7,27 @@ module Page.Home.Data exposing
) )
import Api.Model.ItemLightList exposing (ItemLightList) import Api.Model.ItemLightList exposing (ItemLightList)
import Comp.ItemList import Comp.ItemCardList
import Comp.SearchMenu import Comp.SearchMenu
import Http import Http
type alias Model = type alias Model =
{ searchMenuModel : Comp.SearchMenu.Model { searchMenuModel : Comp.SearchMenu.Model
, itemListModel : Comp.ItemList.Model , itemListModel : Comp.ItemCardList.Model
, searchInProgress : Bool , searchInProgress : Bool
, viewMode : ViewMode , viewMode : ViewMode
, menuCollapsed : Bool
} }
emptyModel : Model emptyModel : Model
emptyModel = emptyModel =
{ searchMenuModel = Comp.SearchMenu.emptyModel { searchMenuModel = Comp.SearchMenu.emptyModel
, itemListModel = Comp.ItemList.emptyModel , itemListModel = Comp.ItemCardList.init
, searchInProgress = False , searchInProgress = False
, viewMode = Listing , viewMode = Listing
, menuCollapsed = False
} }
@ -33,9 +35,10 @@ type Msg
= Init = Init
| SearchMenuMsg Comp.SearchMenu.Msg | SearchMenuMsg Comp.SearchMenu.Msg
| ResetSearch | ResetSearch
| ItemListMsg Comp.ItemList.Msg | ItemCardListMsg Comp.ItemCardList.Msg
| ItemSearchResp (Result Http.Error ItemLightList) | ItemSearchResp (Result Http.Error ItemLightList)
| DoSearch | DoSearch
| ToggleSearchMenu
type ViewMode type ViewMode
@ -47,10 +50,10 @@ itemNav : String -> Model -> { prev : Maybe String, next : Maybe String }
itemNav id model = itemNav id model =
let let
prev = prev =
Comp.ItemList.prevItem model.itemListModel id Comp.ItemCardList.prevItem model.itemListModel id
next = next =
Comp.ItemList.nextItem model.itemListModel id Comp.ItemCardList.nextItem model.itemListModel id
in in
{ prev = Maybe.map .id prev { prev = Maybe.map .id prev
, next = Maybe.map .id next , next = Maybe.map .id next

View File

@ -2,7 +2,7 @@ module Page.Home.Update exposing (update)
import Api import Api
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Comp.ItemList import Comp.ItemCardList
import Comp.SearchMenu import Comp.SearchMenu
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Page exposing (Page(..)) import Page exposing (Page(..))
@ -40,10 +40,10 @@ update key flags msg model =
in in
( m2, Cmd.batch [ c2, Cmd.map SearchMenuMsg (Tuple.second nextState.modelCmd) ] ) ( m2, Cmd.batch [ c2, Cmd.map SearchMenuMsg (Tuple.second nextState.modelCmd) ] )
ItemListMsg m -> ItemCardListMsg m ->
let let
( m2, c2, mitem ) = ( m2, c2, mitem ) =
Comp.ItemList.update flags m model.itemListModel Comp.ItemCardList.update flags m model.itemListModel
cmd = cmd =
case mitem of case mitem of
@ -53,14 +53,14 @@ update key flags msg model =
Nothing -> Nothing ->
Cmd.none Cmd.none
in in
( { model | itemListModel = m2 }, Cmd.batch [ Cmd.map ItemListMsg c2, cmd ] ) ( { model | itemListModel = m2 }, Cmd.batch [ Cmd.map ItemCardListMsg c2, cmd ] )
ItemSearchResp (Ok list) -> ItemSearchResp (Ok list) ->
let let
m = m =
{ model | searchInProgress = False, viewMode = Listing } { model | searchInProgress = False, viewMode = Listing }
in in
update key flags (ItemListMsg (Comp.ItemList.SetResults list)) m update key flags (ItemCardListMsg (Comp.ItemCardList.SetResults list)) m
ItemSearchResp (Err _) -> ItemSearchResp (Err _) ->
( { model | searchInProgress = False }, Cmd.none ) ( { model | searchInProgress = False }, Cmd.none )
@ -68,6 +68,11 @@ update key flags msg model =
DoSearch -> DoSearch ->
doSearch flags model doSearch flags model
ToggleSearchMenu ->
( { model | menuCollapsed = not model.menuCollapsed }
, Cmd.none
)
doSearch : Flags -> Model -> ( Model, Cmd Msg ) doSearch : Flags -> Model -> ( Model, Cmd Msg )
doSearch flags model = doSearch flags model =

View File

@ -1,6 +1,6 @@
module Page.Home.View exposing (view) module Page.Home.View exposing (view)
import Comp.ItemList import Comp.ItemCardList
import Comp.SearchMenu import Comp.SearchMenu
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@ -12,22 +12,39 @@ import Page.Home.Data exposing (..)
view : Model -> Html Msg view : Model -> Html Msg
view model = view model =
div [ class "home-page ui padded grid" ] div [ class "home-page ui padded grid" ]
[ div [ class "four wide column" ] [ div
[ div [ class "ui top attached ablue-comp menu" ] [ classList
[ h4 [ class "header item" ] [ ( "sixteen wide mobile six wide tablet four wide computer column"
[ text "Search" , True
)
, ( "invisible hidden", model.menuCollapsed )
]
]
[ div
[ class "ui top attached ablue-comp menu"
]
[ a
[ class "item"
, href "#"
, onClick ToggleSearchMenu
, title "Hide menu"
]
[ i [ class "ui angle down icon" ] []
, text "Search"
] ]
, div [ class "right floated menu" ] , div [ class "right floated menu" ]
[ a [ a
[ class "item" [ class "icon item"
, onClick ResetSearch , onClick ResetSearch
, title "Reset form"
, href "#" , href "#"
] ]
[ i [ class "undo icon" ] [] [ i [ class "undo icon" ] []
] ]
, a , a
[ class "item" [ class "icon item"
, onClick DoSearch , onClick DoSearch
, title "Run search query"
, href "" , href ""
] ]
[ i [ class "ui search icon" ] [] [ i [ class "ui search icon" ] []
@ -38,14 +55,37 @@ view model =
[ Html.map SearchMenuMsg (Comp.SearchMenu.view model.searchMenuModel) [ Html.map SearchMenuMsg (Comp.SearchMenu.view model.searchMenuModel)
] ]
] ]
, div [ class "twelve wide column" ] , div
[ case model.viewMode of [ classList
[ ( "sixteen wide mobile ten wide tablet twelve wide computer column"
, not model.menuCollapsed
)
, ( "sixteen wide column", model.menuCollapsed )
]
]
[ div
[ classList
[ ( "invisible hidden", not model.menuCollapsed )
, ( "ui segment container", True )
]
]
[ a
[ class "ui basic large circular label"
, onClick ToggleSearchMenu
, href "#"
]
[ i [ class "search icon" ] []
, text "Search Menu"
]
]
, case model.viewMode of
Listing -> Listing ->
if model.searchInProgress then if model.searchInProgress then
resultPlaceholder resultPlaceholder
else else
Html.map ItemListMsg (Comp.ItemList.view model.itemListModel) Html.map ItemCardListMsg
(Comp.ItemCardList.view model.itemListModel)
Detail -> Detail ->
div [] [] div [] []

View File

@ -1,6 +1,7 @@
module Util.String exposing module Util.String exposing
( crazyEncode ( crazyEncode
, ellipsis , ellipsis
, underscoreToSpace
, withDefault , withDefault
) )
@ -40,3 +41,8 @@ withDefault default str =
else else
str str
underscoreToSpace : String -> String
underscoreToSpace str =
String.replace "_" " " str

View File

@ -132,6 +132,14 @@ textarea.markdown-editor {
padding-right: 1em; padding-right: 1em;
} }
.default-layout .item-group:not(:first-child) > .ui.horizontal.divider.header.item-list {
margin-top: 1.5em;
}
.default-layout .ui.horizontal.divider.header.item-list {
margin-bottom: 1em;
background: rgba(240,248,255,0.4);
}
label span.muted { label span.muted {
font-size: smaller; font-size: smaller;
color: rgba(0,0,0,0.6); color: rgba(0,0,0,0.6);