Improve item detail view

- Separate page (permalink) for item details

- Use available space and hide search menu

- Disable item navigation links if there is nothing to go to

- Show notes more prominently and allow to hide them
This commit is contained in:
Eike Kettner 2019-12-31 00:56:41 +01:00
parent 36a6fdd746
commit c73cdd82ab
12 changed files with 342 additions and 183 deletions

View File

@ -15,6 +15,7 @@ import Http
import Page exposing (Page(..)) import Page exposing (Page(..))
import Page.CollectiveSettings.Data import Page.CollectiveSettings.Data
import Page.Home.Data import Page.Home.Data
import Page.ItemDetail.Data
import Page.Login.Data import Page.Login.Data
import Page.ManageData.Data import Page.ManageData.Data
import Page.NewInvite.Data import Page.NewInvite.Data
@ -39,6 +40,7 @@ type alias Model =
, registerModel : Page.Register.Data.Model , registerModel : Page.Register.Data.Model
, uploadModel : Page.Upload.Data.Model , uploadModel : Page.Upload.Data.Model
, newInviteModel : Page.NewInvite.Data.Model , newInviteModel : Page.NewInvite.Data.Model
, itemDetailModel : Page.ItemDetail.Data.Model
, navMenuOpen : Bool , navMenuOpen : Bool
, subs : Sub Msg , subs : Sub Msg
} }
@ -64,6 +66,7 @@ init key url flags =
, registerModel = Page.Register.Data.emptyModel , registerModel = Page.Register.Data.emptyModel
, uploadModel = Page.Upload.Data.emptyModel , uploadModel = Page.Upload.Data.emptyModel
, newInviteModel = Page.NewInvite.Data.emptyModel , newInviteModel = Page.NewInvite.Data.emptyModel
, itemDetailModel = Page.ItemDetail.Data.emptyModel
, navMenuOpen = False , navMenuOpen = False
, subs = Sub.none , subs = Sub.none
} }
@ -82,6 +85,7 @@ type Msg
| RegisterMsg Page.Register.Data.Msg | RegisterMsg Page.Register.Data.Msg
| UploadMsg Page.Upload.Data.Msg | UploadMsg Page.Upload.Data.Msg
| NewInviteMsg Page.NewInvite.Data.Msg | NewInviteMsg Page.NewInvite.Data.Msg
| ItemDetailMsg Page.ItemDetail.Data.Msg
| Logout | Logout
| LogoutResp (Result Http.Error ()) | LogoutResp (Result Http.Error ())
| SessionCheckResp (Result Http.Error AuthResult) | SessionCheckResp (Result Http.Error AuthResult)

View File

@ -13,6 +13,8 @@ import Page.CollectiveSettings.Data
import Page.CollectiveSettings.Update import Page.CollectiveSettings.Update
import Page.Home.Data import Page.Home.Data
import Page.Home.Update import Page.Home.Update
import Page.ItemDetail.Data
import Page.ItemDetail.Update
import Page.Login.Data import Page.Login.Data
import Page.Login.Update import Page.Login.Update
import Page.ManageData.Data import Page.ManageData.Data
@ -71,6 +73,9 @@ updateWithSub msg model =
NewInviteMsg m -> NewInviteMsg m ->
updateNewInvite m model |> noSub updateNewInvite m model |> noSub
ItemDetailMsg m ->
updateItemDetail m model |> noSub
VersionResp (Ok info) -> VersionResp (Ok info) ->
( { model | version = info }, Cmd.none ) |> noSub ( { model | version = info }, Cmd.none ) |> noSub
@ -170,6 +175,20 @@ updateWithSub msg model =
( { model | navMenuOpen = not model.navMenuOpen }, Cmd.none, Sub.none ) ( { model | navMenuOpen = not model.navMenuOpen }, Cmd.none, Sub.none )
updateItemDetail : Page.ItemDetail.Data.Msg -> Model -> ( Model, Cmd Msg )
updateItemDetail lmsg model =
let
inav =
Page.Home.Data.itemNav model.itemDetailModel.detail.item.id model.homeModel
( lm, lc ) =
Page.ItemDetail.Update.update model.key model.flags inav.next lmsg model.itemDetailModel
in
( { model | itemDetailModel = lm }
, Cmd.map ItemDetailMsg lc
)
updateNewInvite : Page.NewInvite.Data.Msg -> Model -> ( Model, Cmd Msg ) updateNewInvite : Page.NewInvite.Data.Msg -> Model -> ( Model, Cmd Msg )
updateNewInvite lmsg model = updateNewInvite lmsg model =
let let
@ -265,7 +284,7 @@ updateHome : Page.Home.Data.Msg -> Model -> ( Model, Cmd Msg )
updateHome lmsg model = updateHome lmsg model =
let let
( lm, lc ) = ( lm, lc ) =
Page.Home.Update.update model.flags lmsg model.homeModel Page.Home.Update.update model.key model.flags lmsg model.homeModel
in in
( { model | homeModel = lm } ( { model | homeModel = lm }
, Cmd.map HomeMsg lc , Cmd.map HomeMsg lc
@ -321,6 +340,9 @@ initPage model page =
NewInvitePage -> NewInvitePage ->
updateQueue Page.Queue.Data.StopRefresh model updateQueue Page.Queue.Data.StopRefresh model
ItemDetailPage id ->
updateItemDetail (Page.ItemDetail.Data.Init id) model
noSub : ( Model, Cmd Msg ) -> ( Model, Cmd Msg, Sub Msg ) noSub : ( Model, Cmd Msg ) -> ( Model, Cmd Msg, Sub Msg )
noSub ( m, c ) = noSub ( m, c ) =

View File

@ -6,7 +6,9 @@ import Html.Attributes exposing (..)
import Html.Events exposing (onClick) import Html.Events exposing (onClick)
import Page exposing (Page(..)) import Page exposing (Page(..))
import Page.CollectiveSettings.View import Page.CollectiveSettings.View
import Page.Home.Data
import Page.Home.View import Page.Home.View
import Page.ItemDetail.View
import Page.Login.View import Page.Login.View
import Page.ManageData.View import Page.ManageData.View
import Page.NewInvite.View import Page.NewInvite.View
@ -105,11 +107,23 @@ defaultLayout model =
NewInvitePage -> NewInvitePage ->
viewNewInvite model viewNewInvite model
ItemDetailPage id ->
viewItemDetail id model
] ]
, footer model , footer model
] ]
viewItemDetail : String -> Model -> Html Msg
viewItemDetail id model =
let
inav =
Page.Home.Data.itemNav id model.homeModel
in
Html.map ItemDetailMsg (Page.ItemDetail.View.view inav model.itemDetailModel)
viewNewInvite : Model -> Html Msg viewNewInvite : Model -> Html Msg
viewNewInvite model = viewNewInvite model =
Html.map NewInviteMsg (Page.NewInvite.View.view model.flags model.newInviteModel) Html.map NewInviteMsg (Page.NewInvite.View.view model.flags model.newInviteModel)

View File

@ -1,7 +1,6 @@
module Comp.ItemDetail exposing module Comp.ItemDetail exposing
( Model ( Model
, Msg(..) , Msg(..)
, UserNav(..)
, emptyModel , emptyModel
, update , update
, view , view
@ -20,6 +19,7 @@ import Api.Model.OptionalText exposing (OptionalText)
import Api.Model.ReferenceList exposing (ReferenceList) import Api.Model.ReferenceList exposing (ReferenceList)
import Api.Model.Tag exposing (Tag) import Api.Model.Tag exposing (Tag)
import Api.Model.TagList exposing (TagList) import Api.Model.TagList exposing (TagList)
import Browser.Navigation as Nav
import Comp.DatePicker import Comp.DatePicker
import Comp.Dropdown exposing (isDropdownChangeMsg) import Comp.Dropdown exposing (isDropdownChangeMsg)
import Comp.YesNoDimmer import Comp.YesNoDimmer
@ -31,6 +31,7 @@ import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput) import Html.Events exposing (onClick, onInput)
import Http import Http
import Markdown import Markdown
import Page exposing (Page(..))
import Util.Maybe import Util.Maybe
import Util.Size import Util.Size
import Util.String import Util.String
@ -49,6 +50,7 @@ type alias Model =
, concEquipModel : Comp.Dropdown.Model IdName , concEquipModel : Comp.Dropdown.Model IdName
, nameModel : String , nameModel : String
, notesModel : Maybe String , notesModel : Maybe String
, notesHidden : Bool
, deleteConfirm : Comp.YesNoDimmer.Model , deleteConfirm : Comp.YesNoDimmer.Model
, itemDatePicker : DatePicker , itemDatePicker : DatePicker
, itemDate : Maybe Int , itemDate : Maybe Int
@ -76,7 +78,11 @@ emptyModel =
} }
, directionModel = , directionModel =
Comp.Dropdown.makeSingleList Comp.Dropdown.makeSingleList
{ makeOption = \entry -> { value = Data.Direction.toString entry, text = Data.Direction.toString entry } { makeOption =
\entry ->
{ value = Data.Direction.toString entry
, text = Data.Direction.toString entry
}
, options = Data.Direction.all , options = Data.Direction.all
, placeholder = "Choose a direction" , placeholder = "Choose a direction"
, selected = Nothing , selected = Nothing
@ -103,6 +109,7 @@ emptyModel =
} }
, nameModel = "" , nameModel = ""
, notesModel = Nothing , notesModel = Nothing
, notesHidden = False
, deleteConfirm = Comp.YesNoDimmer.emptyModel , deleteConfirm = Comp.YesNoDimmer.emptyModel
, itemDatePicker = Comp.DatePicker.emptyModel , itemDatePicker = Comp.DatePicker.emptyModel
, itemDate = Nothing , itemDate = Nothing
@ -112,26 +119,12 @@ emptyModel =
} }
type UserNav
= NavBack
| NavPrev
| NavNext
| NavNone
| NavNextOrBack
noNav : ( Model, Cmd Msg ) -> ( Model, Cmd Msg, UserNav )
noNav ( model, cmd ) =
( model, cmd, NavNone )
type Msg type Msg
= ToggleMenu = ToggleMenu
| ReloadItem | ReloadItem
| Init | Init
| SetItem ItemDetail | SetItem ItemDetail
| SetActiveAttachment Int | SetActiveAttachment Int
| NavClick UserNav
| TagDropdownMsg (Comp.Dropdown.Msg Tag) | TagDropdownMsg (Comp.Dropdown.Msg Tag)
| DirDropdownMsg (Comp.Dropdown.Msg Direction) | DirDropdownMsg (Comp.Dropdown.Msg Direction)
| OrgDropdownMsg (Comp.Dropdown.Msg IdName) | OrgDropdownMsg (Comp.Dropdown.Msg IdName)
@ -145,6 +138,7 @@ type Msg
| SetName String | SetName String
| SaveName | SaveName
| SetNotes String | SetNotes String
| ToggleNotes
| SaveNotes | SaveNotes
| ConfirmItem | ConfirmItem
| UnconfirmItem | UnconfirmItem
@ -281,8 +275,8 @@ setDueDate flags model date =
Api.setItemDueDate flags model.item.id (OptionalDate date) SaveResp Api.setItemDueDate flags model.item.id (OptionalDate date) SaveResp
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, UserNav ) update : Nav.Key -> Flags -> Maybe String -> Msg -> Model -> ( Model, Cmd Msg )
update flags msg model = update key flags next msg model =
case msg of case msg of
Init -> Init ->
let let
@ -295,16 +289,17 @@ update flags msg model =
, Cmd.map ItemDatePickerMsg dpc , Cmd.map ItemDatePickerMsg dpc
, Cmd.map DueDatePickerMsg dpc , Cmd.map DueDatePickerMsg dpc
] ]
, NavNone
) )
SetItem item -> SetItem item ->
let let
( m1, c1, _ ) = ( m1, c1 ) =
update flags (TagDropdownMsg (Comp.Dropdown.SetSelection item.tags)) model update key flags next (TagDropdownMsg (Comp.Dropdown.SetSelection item.tags)) model
( m2, c2, _ ) = ( m2, c2 ) =
update flags update key
flags
next
(DirDropdownMsg (DirDropdownMsg
(Comp.Dropdown.SetSelection (Comp.Dropdown.SetSelection
(Data.Direction.fromString item.direction (Data.Direction.fromString item.direction
@ -315,8 +310,10 @@ update flags msg model =
) )
m1 m1
( m3, c3, _ ) = ( m3, c3 ) =
update flags update key
flags
next
(OrgDropdownMsg (OrgDropdownMsg
(Comp.Dropdown.SetSelection (Comp.Dropdown.SetSelection
(item.corrOrg (item.corrOrg
@ -327,8 +324,10 @@ update flags msg model =
) )
m2 m2
( m4, c4, _ ) = ( m4, c4 ) =
update flags update key
flags
next
(CorrPersonMsg (CorrPersonMsg
(Comp.Dropdown.SetSelection (Comp.Dropdown.SetSelection
(item.corrPerson (item.corrPerson
@ -339,8 +338,10 @@ update flags msg model =
) )
m3 m3
( m5, c5, _ ) = ( m5, c5 ) =
update flags update key
flags
next
(ConcPersonMsg (ConcPersonMsg
(Comp.Dropdown.SetSelection (Comp.Dropdown.SetSelection
(item.concPerson (item.concPerson
@ -358,26 +359,28 @@ update flags msg model =
else else
Cmd.none Cmd.none
in in
( { m5 | item = item, nameModel = item.name, notesModel = item.notes, itemDate = item.itemDate, dueDate = item.dueDate } ( { m5
| item = item
, nameModel = item.name
, notesModel = item.notes
, itemDate = item.itemDate
, dueDate = item.dueDate
}
, Cmd.batch [ c1, c2, c3, c4, c5, getOptions flags, proposalCmd ] , Cmd.batch [ c1, c2, c3, c4, c5, getOptions flags, proposalCmd ]
) )
|> noNav
SetActiveAttachment pos -> SetActiveAttachment pos ->
( { model | visibleAttach = pos }, Cmd.none, NavNone ) ( { model | visibleAttach = pos }, Cmd.none )
NavClick nav ->
( model, Cmd.none, nav )
ToggleMenu -> ToggleMenu ->
( { model | menuOpen = not model.menuOpen }, Cmd.none, NavNone ) ( { model | menuOpen = not model.menuOpen }, Cmd.none )
ReloadItem -> ReloadItem ->
if model.item.id == "" then if model.item.id == "" then
( model, Cmd.none, NavNone ) ( model, Cmd.none )
else else
( model, Api.itemDetail flags model.item.id GetItemResp, NavNone ) ( model, Api.itemDetail flags model.item.id GetItemResp )
TagDropdownMsg m -> TagDropdownMsg m ->
let let
@ -394,7 +397,7 @@ update flags msg model =
else else
Cmd.none Cmd.none
in in
( newModel, Cmd.batch [ save, Cmd.map TagDropdownMsg c2 ], NavNone ) ( newModel, Cmd.batch [ save, Cmd.map TagDropdownMsg c2 ] )
DirDropdownMsg m -> DirDropdownMsg m ->
let let
@ -411,7 +414,7 @@ update flags msg model =
else else
Cmd.none Cmd.none
in in
( newModel, Cmd.batch [ save, Cmd.map DirDropdownMsg c2 ] ) |> noNav ( newModel, Cmd.batch [ save, Cmd.map DirDropdownMsg c2 ] )
OrgDropdownMsg m -> OrgDropdownMsg m ->
let let
@ -431,7 +434,7 @@ update flags msg model =
else else
Cmd.none Cmd.none
in in
( newModel, Cmd.batch [ save, Cmd.map OrgDropdownMsg c2 ] ) |> noNav ( newModel, Cmd.batch [ save, Cmd.map OrgDropdownMsg c2 ] )
CorrPersonMsg m -> CorrPersonMsg m ->
let let
@ -451,7 +454,7 @@ update flags msg model =
else else
Cmd.none Cmd.none
in in
( newModel, Cmd.batch [ save, Cmd.map CorrPersonMsg c2 ] ) |> noNav ( newModel, Cmd.batch [ save, Cmd.map CorrPersonMsg c2 ] )
ConcPersonMsg m -> ConcPersonMsg m ->
let let
@ -471,7 +474,7 @@ update flags msg model =
else else
Cmd.none Cmd.none
in in
( newModel, Cmd.batch [ save, Cmd.map ConcPersonMsg c2 ] ) |> noNav ( newModel, Cmd.batch [ save, Cmd.map ConcPersonMsg c2 ] )
ConcEquipMsg m -> ConcEquipMsg m ->
let let
@ -491,13 +494,13 @@ update flags msg model =
else else
Cmd.none Cmd.none
in in
( newModel, Cmd.batch [ save, Cmd.map ConcEquipMsg c2 ] ) |> noNav ( newModel, Cmd.batch [ save, Cmd.map ConcEquipMsg c2 ] )
SetName str -> SetName str ->
( { model | nameModel = str }, Cmd.none ) |> noNav ( { model | nameModel = str }, Cmd.none )
SaveName -> SaveName ->
( model, setName flags model ) |> noNav ( model, setName flags model )
SetNotes str -> SetNotes str ->
( { model ( { model
@ -510,16 +513,20 @@ update flags msg model =
} }
, Cmd.none , Cmd.none
) )
|> noNav
ToggleNotes ->
( { model | notesHidden = not model.notesHidden }
, Cmd.none
)
SaveNotes -> SaveNotes ->
( model, setNotes flags model ) |> noNav ( model, setNotes flags model )
ConfirmItem -> ConfirmItem ->
( model, Api.setConfirmed flags model.item.id SaveResp ) |> noNav ( model, Api.setConfirmed flags model.item.id SaveResp )
UnconfirmItem -> UnconfirmItem ->
( model, Api.setUnconfirmed flags model.item.id SaveResp ) |> noNav ( model, Api.setUnconfirmed flags model.item.id SaveResp )
ItemDatePickerMsg m -> ItemDatePickerMsg m ->
let let
@ -532,13 +539,13 @@ update flags msg model =
newModel = newModel =
{ model | itemDatePicker = dp, itemDate = Just (Comp.DatePicker.midOfDay date) } { model | itemDatePicker = dp, itemDate = Just (Comp.DatePicker.midOfDay date) }
in in
( newModel, setDate flags newModel newModel.itemDate ) |> noNav ( newModel, setDate flags newModel newModel.itemDate )
_ -> _ ->
( { model | itemDatePicker = dp }, Cmd.none ) |> noNav ( { model | itemDatePicker = dp }, Cmd.none )
RemoveDate -> RemoveDate ->
( { model | itemDate = Nothing }, setDate flags model Nothing ) |> noNav ( { model | itemDate = Nothing }, setDate flags model Nothing )
DueDatePickerMsg m -> DueDatePickerMsg m ->
let let
@ -551,13 +558,13 @@ update flags msg model =
newModel = newModel =
{ model | dueDatePicker = dp, dueDate = Just (Comp.DatePicker.midOfDay date) } { model | dueDatePicker = dp, dueDate = Just (Comp.DatePicker.midOfDay date) }
in in
( newModel, setDueDate flags newModel newModel.dueDate ) |> noNav ( newModel, setDueDate flags newModel newModel.dueDate )
_ -> _ ->
( { model | dueDatePicker = dp }, Cmd.none ) |> noNav ( { model | dueDatePicker = dp }, Cmd.none )
RemoveDueDate -> RemoveDueDate ->
( { model | dueDate = Nothing }, setDueDate flags model Nothing ) |> noNav ( { model | dueDate = Nothing }, setDueDate flags model Nothing )
YesNoMsg m -> YesNoMsg m ->
let let
@ -571,67 +578,67 @@ update flags msg model =
else else
Cmd.none Cmd.none
in in
( { model | deleteConfirm = cm }, cmd ) |> noNav ( { model | deleteConfirm = cm }, cmd )
RequestDelete -> RequestDelete ->
update flags (YesNoMsg Comp.YesNoDimmer.activate) model update key flags next (YesNoMsg Comp.YesNoDimmer.activate) model
SetCorrOrgSuggestion idname -> SetCorrOrgSuggestion idname ->
( model, setCorrOrg flags model (Just idname) ) |> noNav ( model, setCorrOrg flags model (Just idname) )
SetCorrPersonSuggestion idname -> SetCorrPersonSuggestion idname ->
( model, setCorrPerson flags model (Just idname) ) |> noNav ( model, setCorrPerson flags model (Just idname) )
SetConcPersonSuggestion idname -> SetConcPersonSuggestion idname ->
( model, setConcPerson flags model (Just idname) ) |> noNav ( model, setConcPerson flags model (Just idname) )
SetConcEquipSuggestion idname -> SetConcEquipSuggestion idname ->
( model, setConcEquip flags model (Just idname) ) |> noNav ( model, setConcEquip flags model (Just idname) )
SetItemDateSuggestion date -> SetItemDateSuggestion date ->
( model, setDate flags model (Just date) ) |> noNav ( model, setDate flags model (Just date) )
SetDueDateSuggestion date -> SetDueDateSuggestion date ->
( model, setDueDate flags model (Just date) ) |> noNav ( model, setDueDate flags model (Just date) )
GetTagsResp (Ok tags) -> GetTagsResp (Ok tags) ->
let let
tagList = tagList =
Comp.Dropdown.SetOptions tags.items Comp.Dropdown.SetOptions tags.items
( m1, c1, _ ) = ( m1, c1 ) =
update flags (TagDropdownMsg tagList) model update key flags next (TagDropdownMsg tagList) model
in in
( m1, c1 ) |> noNav ( m1, c1 )
GetTagsResp (Err _) -> GetTagsResp (Err _) ->
( model, Cmd.none ) |> noNav ( model, Cmd.none )
GetOrgResp (Ok orgs) -> GetOrgResp (Ok orgs) ->
let let
opts = opts =
Comp.Dropdown.SetOptions orgs.items Comp.Dropdown.SetOptions orgs.items
in in
update flags (OrgDropdownMsg opts) model update key flags next (OrgDropdownMsg opts) model
GetOrgResp (Err _) -> GetOrgResp (Err _) ->
( model, Cmd.none ) |> noNav ( model, Cmd.none )
GetPersonResp (Ok ps) -> GetPersonResp (Ok ps) ->
let let
opts = opts =
Comp.Dropdown.SetOptions ps.items Comp.Dropdown.SetOptions ps.items
( m1, c1, _ ) = ( m1, c1 ) =
update flags (CorrPersonMsg opts) model update key flags next (CorrPersonMsg opts) model
( m2, c2, _ ) = ( m2, c2 ) =
update flags (ConcPersonMsg opts) m1 update key flags next (ConcPersonMsg opts) m1
in in
( m2, Cmd.batch [ c1, c2 ] ) |> noNav ( m2, Cmd.batch [ c1, c2 ] )
GetPersonResp (Err _) -> GetPersonResp (Err _) ->
( model, Cmd.none ) |> noNav ( model, Cmd.none )
GetEquipResp (Ok equips) -> GetEquipResp (Ok equips) ->
let let
@ -641,42 +648,47 @@ update flags msg model =
equips.items equips.items
) )
in in
update flags (ConcEquipMsg opts) model update key flags next (ConcEquipMsg opts) model
GetEquipResp (Err _) -> GetEquipResp (Err _) ->
( model, Cmd.none ) |> noNav ( model, Cmd.none )
SaveResp (Ok res) -> SaveResp (Ok res) ->
if res.success then if res.success then
( model, Api.itemDetail flags model.item.id GetItemResp ) |> noNav ( model, Api.itemDetail flags model.item.id GetItemResp )
else else
( model, Cmd.none ) |> noNav ( model, Cmd.none )
SaveResp (Err _) -> SaveResp (Err _) ->
( model, Cmd.none ) |> noNav ( model, Cmd.none )
DeleteResp (Ok res) -> DeleteResp (Ok res) ->
if res.success then if res.success then
( model, Cmd.none, NavNextOrBack ) case next of
Just id ->
( model, Page.set key (ItemDetailPage id) )
Nothing ->
( model, Page.set key HomePage )
else else
( model, Cmd.none ) |> noNav ( model, Cmd.none )
DeleteResp (Err _) -> DeleteResp (Err _) ->
( model, Cmd.none ) |> noNav ( model, Cmd.none )
GetItemResp (Ok item) -> GetItemResp (Ok item) ->
update flags (SetItem item) model update key flags next (SetItem item) model
GetItemResp (Err _) -> GetItemResp (Err _) ->
( model, Cmd.none ) |> noNav ( model, Cmd.none )
GetProposalResp (Ok ip) -> GetProposalResp (Ok ip) ->
( { model | itemProposals = ip }, Cmd.none ) |> noNav ( { model | itemProposals = ip }, Cmd.none )
GetProposalResp (Err _) -> GetProposalResp (Err _) ->
( model, Cmd.none ) |> noNav ( model, Cmd.none )
@ -692,21 +704,38 @@ actionInputDatePicker =
{ ds | containerClassList = [ ( "ui action input", True ) ] } { ds | containerClassList = [ ( "ui action input", True ) ] }
view : Model -> Html Msg view : { prev : Maybe String, next : Maybe String } -> Model -> Html Msg
view model = view inav model =
div [] div []
[ div [ renderItemInfo model
, div
[ classList [ classList
[ ( "ui ablue-comp menu", True ) [ ( "ui ablue-comp menu", True )
] ]
] ]
[ a [ class "item", href "", onClick (NavClick NavBack) ] [ a [ class "item", Page.href HomePage ]
[ i [ class "arrow left icon" ] [] [ i [ class "arrow left icon" ] []
] ]
, a [ class "item", href "", onClick (NavClick NavPrev) ] , a
[ classList
[ ( "item", True )
, ( "disabled", inav.prev == Nothing )
]
, Maybe.map ItemDetailPage inav.prev
|> Maybe.map Page.href
|> Maybe.withDefault (href "#")
]
[ i [ class "caret square left outline icon" ] [] [ i [ class "caret square left outline icon" ] []
] ]
, a [ class "item", href "", onClick (NavClick NavNext) ] , a
[ classList
[ ( "item", True )
, ( "disabled", inav.next == Nothing )
]
, Maybe.map ItemDetailPage inav.next
|> Maybe.map Page.href
|> Maybe.withDefault (href "#")
]
[ i [ class "caret square right outline icon" ] [] [ i [ class "caret square right outline icon" ] []
] ]
, a , a
@ -722,9 +751,10 @@ view model =
] ]
] ]
, div [ class "ui grid" ] , div [ class "ui grid" ]
[ div [ Html.map YesNoMsg (Comp.YesNoDimmer.view model.deleteConfirm)
, div
[ classList [ classList
[ ( "six wide column", True ) [ ( "four wide column", True )
, ( "invisible", not model.menuOpen ) , ( "invisible", not model.menuOpen )
] ]
] ]
@ -736,17 +766,17 @@ view model =
) )
, div , div
[ classList [ classList
[ ( "ten", model.menuOpen ) [ ( "twelve", model.menuOpen )
, ( "sixteen", not model.menuOpen ) , ( "sixteen", not model.menuOpen )
, ( "wide column", True ) , ( "wide column", True )
] ]
] ]
<| <|
List.concat List.concat
[ [ renderItemInfo model ] [ renderNotes model
, [ renderAttachmentsTabMenu model ] , [ renderAttachmentsTabMenu model
]
, renderAttachmentsTabBody model , renderAttachmentsTabBody model
, renderNotes model
, renderIdInfo model , renderIdInfo model
] ]
] ]
@ -776,11 +806,31 @@ renderNotes model =
[] []
Just str -> Just str ->
[ h3 [ class "ui header" ] if model.notesHidden then
[ text "Notes" [ div [ class "ui segment" ]
[ a
[ class "ui top left attached label"
, onClick ToggleNotes
, href "#"
]
[ i [ class "eye icon" ] []
, text "Show notes"
]
]
]
else
[ div [ class "ui segment" ]
[ Markdown.toHtml [ class "item-notes" ] str
, a
[ class "ui right corner label"
, onClick ToggleNotes
, href "#"
]
[ i [ class "delete icon" ] []
]
]
] ]
, Markdown.toHtml [ class "item-notes" ] str
]
renderAttachmentsTabMenu : Model -> Html Msg renderAttachmentsTabMenu : Model -> Html Msg
@ -829,12 +879,6 @@ renderAttachmentsTabBody model =
renderItemInfo : Model -> Html Msg renderItemInfo : Model -> Html Msg
renderItemInfo model = renderItemInfo model =
let let
name =
div [ class "item" ]
[ i [ class (Data.Direction.iconFromString model.item.direction) ] []
, text model.item.name
]
date = date =
div [ class "item" ] div [ class "item" ]
[ Maybe.withDefault model.item.created model.item.itemDate [ Maybe.withDefault model.item.created model.item.itemDate
@ -876,7 +920,7 @@ renderItemInfo model =
] ]
in in
div [ class "ui fluid container" ] div [ class "ui fluid container" ]
([ h2 [ class "ui header" ] (h2 [ class "ui header" ]
[ i [ class (Data.Direction.iconFromString model.item.direction) ] [] [ i [ class (Data.Direction.iconFromString model.item.direction) ] []
, div [ class "content" ] , div [ class "content" ]
[ text model.item.name [ text model.item.name
@ -905,8 +949,7 @@ renderItemInfo model =
] ]
] ]
] ]
] :: renderTags model
++ renderTags model
) )
@ -973,8 +1016,7 @@ renderEditButtons model =
renderEditForm : Model -> Html Msg renderEditForm : Model -> Html Msg
renderEditForm model = renderEditForm model =
div [ class "ui attached segment" ] div [ class "ui attached segment" ]
[ Html.map YesNoMsg (Comp.YesNoDimmer.view model.deleteConfirm) [ div [ class "ui form" ]
, div [ class "ui form" ]
[ div [ class "field" ] [ div [ class "field" ]
[ label [] [ label []
[ i [ class "tags icon" ] [] [ i [ class "tags icon" ] []
@ -986,7 +1028,12 @@ renderEditForm model =
[ label [] [ text "Name" ] [ label [] [ text "Name" ]
, div [ class "ui action input" ] , div [ class "ui action input" ]
[ input [ type_ "text", value model.nameModel, onInput SetName ] [] [ input [ type_ "text", value model.nameModel, onInput SetName ] []
, button [ class "ui icon button", onClick SaveName ] [ i [ class "save outline icon" ] [] ] , button
[ class "ui icon button"
, onClick SaveName
]
[ i [ class "save outline icon" ] []
]
] ]
] ]
, div [ class "field" ] , div [ class "field" ]
@ -996,7 +1043,12 @@ renderEditForm model =
, div [ class " field" ] , div [ class " field" ]
[ label [] [ text "Date" ] [ label [] [ text "Date" ]
, div [ class "ui action input" ] , div [ class "ui action input" ]
[ Html.map ItemDatePickerMsg (Comp.DatePicker.viewTime model.itemDate actionInputDatePicker model.itemDatePicker) [ Html.map ItemDatePickerMsg
(Comp.DatePicker.viewTime
model.itemDate
actionInputDatePicker
model.itemDatePicker
)
, a [ class "ui icon button", href "", onClick RemoveDate ] , a [ class "ui icon button", href "", onClick RemoveDate ]
[ i [ class "trash alternate outline icon" ] [] [ i [ class "trash alternate outline icon" ] []
] ]
@ -1006,7 +1058,12 @@ renderEditForm model =
, div [ class " field" ] , div [ class " field" ]
[ label [] [ text "Due Date" ] [ label [] [ text "Due Date" ]
, div [ class "ui action input" ] , div [ class "ui action input" ]
[ Html.map DueDatePickerMsg (Comp.DatePicker.viewTime model.dueDate actionInputDatePicker model.dueDatePicker) [ Html.map DueDatePickerMsg
(Comp.DatePicker.viewTime
model.dueDate
actionInputDatePicker
model.dueDatePicker
)
, a [ class "ui icon button", href "", onClick RemoveDueDate ] , a [ class "ui icon button", href "", onClick RemoveDueDate ]
[ i [ class "trash alternate outline icon" ] [] ] [ i [ class "trash alternate outline icon" ] [] ]
] ]

View File

@ -10,6 +10,7 @@ module Page exposing
, pageFromString , pageFromString
, pageName , pageName
, pageToString , pageToString
, set
, uploadId , uploadId
) )
@ -31,6 +32,7 @@ type Page
| RegisterPage | RegisterPage
| UploadPage (Maybe String) | UploadPage (Maybe String)
| NewInvitePage | NewInvitePage
| ItemDetailPage String
isSecured : Page -> Bool isSecured : Page -> Bool
@ -63,6 +65,9 @@ isSecured page =
UploadPage arg -> UploadPage arg ->
Util.Maybe.isEmpty arg Util.Maybe.isEmpty arg
ItemDetailPage _ ->
True
isOpen : Page -> Bool isOpen : Page -> Bool
isOpen page = isOpen page =
@ -114,6 +119,9 @@ pageName page =
Nothing -> Nothing ->
"Upload" "Upload"
ItemDetailPage _ ->
"Item"
loginPageReferrer : Page -> Maybe Page loginPageReferrer : Page -> Maybe Page
loginPageReferrer page = loginPageReferrer page =
@ -169,6 +177,9 @@ pageToString page =
NewInvitePage -> NewInvitePage ->
"/app/newinvite" "/app/newinvite"
ItemDetailPage id ->
"/app/item/" ++ id
pageFromString : String -> Maybe Page pageFromString : String -> Maybe Page
pageFromString str = pageFromString str =
@ -191,6 +202,11 @@ href page =
Attr.href (pageToString page) Attr.href (pageToString page)
set : Nav.Key -> Page -> Cmd msg
set key page =
Nav.pushUrl key (pageToString page)
goto : Page -> Cmd msg goto : Page -> Cmd msg
goto page = goto page =
Nav.load (pageToString page) Nav.load (pageToString page)
@ -215,6 +231,7 @@ parser =
, Parser.map (\s -> UploadPage (Just s)) (s pathPrefix </> s "upload" </> string) , Parser.map (\s -> UploadPage (Just s)) (s pathPrefix </> s "upload" </> string)
, Parser.map (UploadPage Nothing) (s pathPrefix </> s "upload") , Parser.map (UploadPage Nothing) (s pathPrefix </> s "upload")
, Parser.map NewInvitePage (s pathPrefix </> s "newinvite") , Parser.map NewInvitePage (s pathPrefix </> s "newinvite")
, Parser.map ItemDetailPage (s pathPrefix </> s "item" </> string)
] ]

View File

@ -3,11 +3,11 @@ module Page.Home.Data exposing
, Msg(..) , Msg(..)
, ViewMode(..) , ViewMode(..)
, emptyModel , emptyModel
, itemNav
) )
import Api.Model.ItemDetail exposing (ItemDetail) import Api.Model.ItemDetail exposing (ItemDetail)
import Api.Model.ItemLightList exposing (ItemLightList) import Api.Model.ItemLightList exposing (ItemLightList)
import Comp.ItemDetail
import Comp.ItemList import Comp.ItemList
import Comp.SearchMenu import Comp.SearchMenu
import Http import Http
@ -17,7 +17,6 @@ type alias Model =
{ searchMenuModel : Comp.SearchMenu.Model { searchMenuModel : Comp.SearchMenu.Model
, itemListModel : Comp.ItemList.Model , itemListModel : Comp.ItemList.Model
, searchInProgress : Bool , searchInProgress : Bool
, itemDetailModel : Comp.ItemDetail.Model
, viewMode : ViewMode , viewMode : ViewMode
} }
@ -26,7 +25,6 @@ emptyModel : Model
emptyModel = emptyModel =
{ searchMenuModel = Comp.SearchMenu.emptyModel { searchMenuModel = Comp.SearchMenu.emptyModel
, itemListModel = Comp.ItemList.emptyModel , itemListModel = Comp.ItemList.emptyModel
, itemDetailModel = Comp.ItemDetail.emptyModel
, searchInProgress = False , searchInProgress = False
, viewMode = Listing , viewMode = Listing
} }
@ -38,10 +36,22 @@ type Msg
| ItemListMsg Comp.ItemList.Msg | ItemListMsg Comp.ItemList.Msg
| ItemSearchResp (Result Http.Error ItemLightList) | ItemSearchResp (Result Http.Error ItemLightList)
| DoSearch | DoSearch
| ItemDetailMsg Comp.ItemDetail.Msg
| ItemDetailResp (Result Http.Error ItemDetail)
type ViewMode type ViewMode
= Listing = Listing
| Detail | Detail
itemNav : String -> Model -> { prev : Maybe String, next : Maybe String }
itemNav id model =
let
prev =
Comp.ItemList.prevItem model.itemListModel id
next =
Comp.ItemList.nextItem model.itemListModel id
in
{ prev = Maybe.map .id prev
, next = Maybe.map .id next
}

View File

@ -1,21 +1,22 @@
module Page.Home.Update exposing (update) module Page.Home.Update exposing (update)
import Api import Api
import Browser.Navigation as Nav
import Comp.ItemDetail import Comp.ItemDetail
import Comp.ItemList import Comp.ItemList
import Comp.SearchMenu import Comp.SearchMenu
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Page exposing (Page(..))
import Page.Home.Data exposing (..) import Page.Home.Data exposing (..)
import Util.Update import Util.Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg ) update : Nav.Key -> Flags -> Msg -> Model -> ( Model, Cmd Msg )
update flags msg model = update key flags msg model =
case msg of case msg of
Init -> Init ->
Util.Update.andThen1 Util.Update.andThen1
[ update flags (SearchMenuMsg Comp.SearchMenu.Init) [ update key flags (SearchMenuMsg Comp.SearchMenu.Init)
, update flags (ItemDetailMsg Comp.ItemDetail.Init)
, doSearch flags , doSearch flags
] ]
model model
@ -45,7 +46,7 @@ update flags msg model =
cmd = cmd =
case mitem of case mitem of
Just item -> Just item ->
Api.itemDetail flags item.id ItemDetailResp Page.set key (ItemDetailPage item.id)
Nothing -> Nothing ->
Cmd.none Cmd.none
@ -57,7 +58,7 @@ update flags msg model =
m = m =
{ model | searchInProgress = False, viewMode = Listing } { model | searchInProgress = False, viewMode = Listing }
in in
update flags (ItemListMsg (Comp.ItemList.SetResults list)) m update key flags (ItemListMsg (Comp.ItemList.SetResults list)) m
ItemSearchResp (Err _) -> ItemSearchResp (Err _) ->
( { model | searchInProgress = False }, Cmd.none ) ( { model | searchInProgress = False }, Cmd.none )
@ -65,58 +66,6 @@ update flags msg model =
DoSearch -> DoSearch ->
doSearch flags model doSearch flags model
ItemDetailMsg m ->
let
( m2, c2, nav ) =
Comp.ItemDetail.update flags m model.itemDetailModel
newModel =
{ model | itemDetailModel = m2 }
newCmd =
Cmd.map ItemDetailMsg c2
in
case nav of
Comp.ItemDetail.NavBack ->
doSearch flags newModel
Comp.ItemDetail.NavPrev ->
case Comp.ItemList.prevItem model.itemListModel m2.item.id of
Just n ->
( newModel, Cmd.batch [ newCmd, Api.itemDetail flags n.id ItemDetailResp ] )
Nothing ->
( newModel, newCmd )
Comp.ItemDetail.NavNext ->
case Comp.ItemList.nextItem model.itemListModel m2.item.id of
Just n ->
( newModel, Cmd.batch [ newCmd, Api.itemDetail flags n.id ItemDetailResp ] )
Nothing ->
( newModel, newCmd )
Comp.ItemDetail.NavNextOrBack ->
case Comp.ItemList.nextItem model.itemListModel m2.item.id of
Just n ->
( newModel, Cmd.batch [ newCmd, Api.itemDetail flags n.id ItemDetailResp ] )
Nothing ->
doSearch flags newModel
Comp.ItemDetail.NavNone ->
( newModel, newCmd )
ItemDetailResp (Ok item) ->
let
m =
{ model | viewMode = Detail }
in
update flags (ItemDetailMsg (Comp.ItemDetail.SetItem item)) m
ItemDetailResp (Err _) ->
( model, Cmd.none )
doSearch : Flags -> Model -> ( Model, Cmd Msg ) doSearch : Flags -> Model -> ( Model, Cmd Msg )
doSearch flags model = doSearch flags model =

View File

@ -42,7 +42,7 @@ view model =
Html.map ItemListMsg (Comp.ItemList.view model.itemListModel) Html.map ItemListMsg (Comp.ItemList.view model.itemListModel)
Detail -> Detail ->
Html.map ItemDetailMsg (Comp.ItemDetail.view model.itemDetailModel) div [] []
] ]
] ]

View File

@ -0,0 +1,22 @@
module Page.ItemDetail.Data exposing (Model, Msg(..), emptyModel)
import Api.Model.ItemDetail exposing (ItemDetail)
import Comp.ItemDetail
import Http
type alias Model =
{ detail : Comp.ItemDetail.Model
}
emptyModel : Model
emptyModel =
{ detail = Comp.ItemDetail.emptyModel
}
type Msg
= Init String
| ItemDetailMsg Comp.ItemDetail.Msg
| ItemResp (Result Http.Error ItemDetail)

View File

@ -0,0 +1,39 @@
module Page.ItemDetail.Update exposing (update)
import Api
import Browser.Navigation as Nav
import Comp.ItemDetail
import Data.Flags exposing (Flags)
import Page.ItemDetail.Data exposing (Model, Msg(..))
update : Nav.Key -> Flags -> Maybe String -> Msg -> Model -> ( Model, Cmd Msg )
update key flags next msg model =
case msg of
Init id ->
let
( lm, lc ) =
Comp.ItemDetail.update key flags next Comp.ItemDetail.Init model.detail
in
( { model | detail = lm }
, Cmd.batch [ Api.itemDetail flags id ItemResp, Cmd.map ItemDetailMsg lc ]
)
ItemDetailMsg lmsg ->
let
( lm, lc ) =
Comp.ItemDetail.update key flags next lmsg model.detail
in
( { model | detail = lm }
, Cmd.map ItemDetailMsg lc
)
ItemResp (Ok item) ->
let
lmsg =
Comp.ItemDetail.SetItem item
in
update key flags next (ItemDetailMsg lmsg) model
ItemResp (Err err) ->
( model, Cmd.none )

View File

@ -0,0 +1,19 @@
module Page.ItemDetail.View exposing (view)
import Comp.ItemDetail
import Html exposing (..)
import Html.Attributes exposing (..)
import Page.ItemDetail.Data exposing (Model, Msg(..))
type alias ItemNav =
{ prev : Maybe String
, next : Maybe String
}
view : ItemNav -> Model -> Html Msg
view inav model =
div [ class "ui fluid container item-detail-page" ]
[ Html.map ItemDetailMsg (Comp.ItemDetail.view inav model.detail)
]

View File

@ -59,6 +59,12 @@
background: aliceblue; background: aliceblue;
} }
.default-layout .ui.fluid.container.item-detail-page {
padding-top: 1em;
padding-left: 1em;
padding-right: 1em;
}
.ui.search.dropdown.open { .ui.search.dropdown.open {
z-index: 20; z-index: 20;
} }