Table.purs 10 KB
Newer Older
Nicolas Pouillard's avatar
Nicolas Pouillard committed
1 2 3 4
module Gargantext.Components.Table where

import Data.Array (filter)
import Data.Maybe (Maybe(..), maybe)
Nicolas Pouillard's avatar
Nicolas Pouillard committed
5
import Data.Either (Either(..))
6 7
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow)
Nicolas Pouillard's avatar
Nicolas Pouillard committed
8
import Effect (Effect)
9
import Effect.Class (liftEffect)
Nicolas Pouillard's avatar
Nicolas Pouillard committed
10
import React (ReactElement, ReactClass, Children, createElement)
11 12
import React.DOM (a, b, b', p, i, h3, hr, div, option, select, span, table, tbody, td, text, th, thead, tr)
import React.DOM.Props (className, href, onChange, onClick, scope, selected, value, style)
13
import Thermite (PerformAction, Render, Spec, modifyState_, simpleSpec, StateCoTransformer, createClass)
Nicolas Pouillard's avatar
Nicolas Pouillard committed
14 15 16 17
import Unsafe.Coerce (unsafeCoerce)

import Gargantext.Prelude

18 19 20 21 22 23 24 25
type TableContainerProps =
  { pageSizeControl     :: ReactElement
  , pageSizeDescription :: ReactElement
  , paginationLinks     :: ReactElement
  , tableHead           :: ReactElement
  , tableBody           :: Array ReactElement
  }

Nicolas Pouillard's avatar
Nicolas Pouillard committed
26 27 28 29
type Rows = Array { row    :: Array ReactElement
                  , delete :: Boolean
                  }

30 31
type OrderBy = Maybe (OrderByDirection ColumnName)

32
type Params = { offset :: Int, limit :: Int, orderBy :: OrderBy }
33 34 35

newtype ColumnName = ColumnName String

36 37 38 39 40
derive instance genericColumnName :: Generic ColumnName _

instance showColumnName :: Show ColumnName where
  show = genericShow

41 42 43 44 45 46
derive instance eqColumnName :: Eq ColumnName

columnName :: ColumnName -> String
columnName (ColumnName c) = c

data OrderByDirection a = ASC a | DESC a
Nicolas Pouillard's avatar
Nicolas Pouillard committed
47

48 49 50 51 52
derive instance genericOrderByDirection :: Generic (OrderByDirection a) _

instance showOrderByDirection :: Show a => Show (OrderByDirection a) where
  show = genericShow

53 54
derive instance eqOrderByDirection :: Eq a => Eq (OrderByDirection a)

Nicolas Pouillard's avatar
Nicolas Pouillard committed
55
type Props' =
56
  ( colNames     :: Array ColumnName
Nicolas Pouillard's avatar
Nicolas Pouillard committed
57
  , totalRecords :: Int
58 59
  , setParams    :: Params -> Effect Unit
  , rows         :: Rows
60
  , container    :: TableContainerProps -> Array ReactElement
Nicolas Pouillard's avatar
Nicolas Pouillard committed
61 62 63 64 65
  )

type Props = Record Props'

type State =
66
  { currentPage :: Int
Nicolas Pouillard's avatar
Nicolas Pouillard committed
67
  , pageSize    :: PageSizes
68
  , orderBy     :: OrderBy
Nicolas Pouillard's avatar
Nicolas Pouillard committed
69 70
  }

71 72 73
initialState :: State
initialState =
  { currentPage  : 1
Nicolas Pouillard's avatar
Nicolas Pouillard committed
74
  , pageSize     : PS10
75
  , orderBy      : Nothing
Nicolas Pouillard's avatar
Nicolas Pouillard committed
76 77
  }

78 79 80
initialParams :: Params
initialParams = stateParams initialState

Nicolas Pouillard's avatar
Nicolas Pouillard committed
81 82 83
data Action
  = ChangePageSize PageSizes
  | ChangePage     Int
84
  | ChangeOrderBy  OrderBy
Nicolas Pouillard's avatar
Nicolas Pouillard committed
85 86 87 88 89

type ChangePageAction = Int -> Effect Unit

-- | Action
-- ChangePageSize
90 91
changePageSize :: PageSizes -> State -> State
changePageSize ps td =
Nicolas Pouillard's avatar
Nicolas Pouillard committed
92 93 94 95
  td { pageSize      = ps
     , currentPage   = 1
     }

96 97 98 99 100 101 102 103 104 105 106 107 108 109
-- TODO: Not sure this is the right place for this function.
renderTableHeaderLayout :: { title :: String
                           , desc  :: String
                           , query :: String
                           , date  :: String
                           , user  :: String
                           } -> Array ReactElement
renderTableHeaderLayout {title, desc, query, date, user} =
  [ div [className "row"]
    [ div [className "col-md-3"] [ h3 [] [text title] ]
    , div [className "col-md-9"] [ hr [style {height : "2px",backgroundColor : "black"}] ]
    ]
  , div [className "row"] [ div [className "jumbotron1", style {padding : "12px 0px 20px 12px"}]
        [ div [ className "col-md-8 content"]
110
              [ p [] [ i [className "glyphicon glyphicon-globe"] []
111 112
                     , text $ " " <> desc
                     ]
113
              , p [] [ i [className "glyphicon glyphicon-zoom-in"] []
114 115 116 117
                     , text $ " " <> query
                     ]
              ]
        , div [ className "col-md-4 content"]
118
              [ p [] [ i [className "glyphicon glyphicon-calendar"] []
119 120
                     , text $ " " <> date
                     ]
121
              , p [] [ i [className "glyphicon glyphicon-user"] []
122 123 124 125 126 127 128
                     , text $ " " <> user
                     ]
              ]
        ]
    ]
  ]

Nicolas Pouillard's avatar
Nicolas Pouillard committed
129 130 131
tableSpec :: Spec State Props Action
tableSpec = simpleSpec performAction render
  where
132
    modifyStateAndReload :: (State -> State) -> Props -> State -> StateCoTransformer State Unit
133
    modifyStateAndReload f {setParams} state = do
134
      logs "modifyStateAndReload" -- TODO rename
135 136
      modifyState_ f
      liftEffect $ setParams $ stateParams $ f state
137

Nicolas Pouillard's avatar
Nicolas Pouillard committed
138
    performAction :: PerformAction State Props Action
139 140 141 142
    performAction (ChangePageSize ps) =
      modifyStateAndReload $ changePageSize ps
    performAction (ChangePage p) =
      modifyStateAndReload $ _ { currentPage = p }
143 144 145 146 147 148 149 150 151 152 153 154 155
    performAction (ChangeOrderBy mc) =
      modifyStateAndReload $ _ { orderBy = mc }

    renderColHeader :: (OrderBy -> Effect Unit)
                    -> OrderBy
                    -> ColumnName -> ReactElement
    renderColHeader changeOrderBy currentOrderBy c =
      th [scope "col"] [ b' cs ]
      where
        lnk mc = effectLink (changeOrderBy mc)
        cs :: Array ReactElement
        cs =
          case currentOrderBy of
156 157
            Just (ASC d)  | c == d -> [lnk (Just (DESC c)) "DESC ",  lnk Nothing (columnName c)]
            Just (DESC d) | c == d -> [lnk (Just (ASC  c)) "ASC ", lnk Nothing (columnName c)]
158
            _ -> [lnk (Just (ASC c)) (columnName c)]
Nicolas Pouillard's avatar
Nicolas Pouillard committed
159 160

    render :: Render State Props Action
161 162
    render dispatch {container, colNames, totalRecords, rows}
                    {pageSize, currentPage, orderBy} _ =
163 164 165 166 167 168 169
      container
        { pageSizeControl: sizeDD pageSize dispatch
        , pageSizeDescription: textDescription currentPage pageSize totalRecords
        , paginationLinks: pagination (dispatch <<< ChangePage) totalPages currentPage
        , tableHead:
            tr [] (renderColHeader (dispatch <<< ChangeOrderBy) orderBy <$> colNames)
        , tableBody:
170
            map (tr [] <<< map (\c -> td [] [c]) <<< _.row) rows
171 172
        }
      where
173 174
        ps = pageSizes2Int pageSize
        totalPages = (totalRecords / ps) + min 1 (totalRecords `mod` ps)
175 176 177 178

defaultContainer :: {title :: String} -> TableContainerProps -> Array ReactElement
defaultContainer {title} props =
  [ div [className "row"]
179 180 181
    [ div [className "col-md-4"] [props.pageSizeDescription]
    , div [className "col-md-4"] [props.paginationLinks]
    , div [className "col-md-4"] [props.pageSizeControl]
182 183 184 185 186 187
    ]
  , table [ className "table"]
    [ thead [className "thead-dark"] [ props.tableHead ]
    , tbody [] props.tableBody
    ]
  ]
Nicolas Pouillard's avatar
Nicolas Pouillard committed
188

189
-- TODO: this needs to be in Gargantext.Pages.Corpus.Graph.Tabs
190 191
graphContainer :: {title :: String} -> TableContainerProps -> Array ReactElement
graphContainer {title} props =
192 193
  [ -- TODO title in tabs name (above)
    table [ className "table"]
194 195 196
    [ thead [className "thead-dark"] [ props.tableHead ]
    , tbody [] props.tableBody
    ]
197 198 199 200
   -- TODO better rendering of the paginationLinks
   -- , props.pageSizeControl
   -- , props.pageSizeDescription
   -- , props.paginationLinks
201 202 203 204
  ]



205 206 207 208 209
stateParams :: State -> Params
stateParams {pageSize, currentPage, orderBy} = {offset, limit, orderBy}
  where
    limit = pageSizes2Int pageSize
    offset = limit * (currentPage - 1)
Nicolas Pouillard's avatar
Nicolas Pouillard committed
210

Nicolas Pouillard's avatar
Nicolas Pouillard committed
211
tableClass :: ReactClass {children :: Children | Props'}
212
tableClass = createClass "Table" tableSpec (const initialState)
Nicolas Pouillard's avatar
Nicolas Pouillard committed
213 214 215 216 217 218 219

tableElt :: Props -> ReactElement
tableElt props = createElement tableClass props []

sizeDD :: PageSizes -> (Action -> Effect Unit) -> ReactElement
sizeDD ps d
  = span []
220 221 222
    [ select [ className "form-control"
             , onChange (\e -> d (ChangePageSize $ string2PageSize $ (unsafeCoerce e).target.value))
             ] $ map (optps ps) aryPS
Nicolas Pouillard's avatar
Nicolas Pouillard committed
223 224 225 226 227
    ]

textDescription :: Int -> PageSizes -> Int -> ReactElement
textDescription currPage pageSize totalRecords
  =  div [className "row1"]
228
          [ div [className ""] -- TODO or col-md-6 ?
Nicolas Pouillard's avatar
Nicolas Pouillard committed
229 230 231 232 233 234 235
                [ text $ "Showing " <> show start <> " to " <> show end <> " of " <> show totalRecords ]
          ]
    where
      start = (currPage - 1) * pageSizes2Int pageSize + 1
      end' = currPage * pageSizes2Int pageSize
      end  = if end' > totalRecords then totalRecords else end'

236
effectLink :: Effect Unit -> String -> ReactElement
237
effectLink eff msg = a [onClick $ const eff] [text msg]
238

Nicolas Pouillard's avatar
Nicolas Pouillard committed
239 240 241
pagination :: ChangePageAction -> Int -> Int -> ReactElement
pagination changePage tp cp
  = span [] $
242
    [ text " ", prev, first, ldots]
Nicolas Pouillard's avatar
Nicolas Pouillard committed
243 244 245 246 247 248 249 250 251 252
    <>
    lnums
    <>
    [b' [text $ " " <> show cp <> " "]]
    <>
    rnums
    <>
    [ rdots, last, next ]
    where
      prev = if cp == 1 then
253
               text " Prev. "
254
             else
255
               changePageLink (cp - 1) "Prev."
Nicolas Pouillard's avatar
Nicolas Pouillard committed
256 257
      next = if cp == tp then
               text " Next "
258 259
             else
               changePageLink (cp + 1) "Next"
Nicolas Pouillard's avatar
Nicolas Pouillard committed
260 261
      first = if cp == 1 then
                text ""
262 263
              else
                changePageLink' 1
Nicolas Pouillard's avatar
Nicolas Pouillard committed
264 265 266
      last = if cp == tp then
               text ""
             else
267
               changePageLink' tp
Nicolas Pouillard's avatar
Nicolas Pouillard committed
268 269 270 271 272 273 274 275
      ldots = if cp >= 5 then
                text " ... "
                else
                text ""
      rdots = if cp + 3 < tp then
                text " ... "
                else
                text ""
276 277
      lnums = map changePageLink' $ filter (1  < _) [cp - 2, cp - 1]
      rnums = map changePageLink' $ filter (tp > _) [cp + 1, cp + 2]
Nicolas Pouillard's avatar
Nicolas Pouillard committed
278

279 280 281 282 283 284 285 286 287
      changePageLink :: Int -> String -> ReactElement
      changePageLink i s = span []
          [ text " "
          , effectLink (changePage i) s
          , text " "
          ]

      changePageLink' :: Int -> ReactElement
      changePageLink' i = changePageLink i (show i)
Nicolas Pouillard's avatar
Nicolas Pouillard committed
288

289
data PageSizes = PS10 | PS20 | PS50 | PS100 | PS200
Nicolas Pouillard's avatar
Nicolas Pouillard committed
290 291 292 293 294 295 296 297

derive instance eqPageSizes :: Eq PageSizes

instance showPageSize :: Show PageSizes where
  show PS10  = "10"
  show PS20  = "20"
  show PS50  = "50"
  show PS100 = "100"
298
  show PS200 = "200"
Nicolas Pouillard's avatar
Nicolas Pouillard committed
299 300 301 302 303 304

pageSizes2Int :: PageSizes -> Int
pageSizes2Int PS10  = 10
pageSizes2Int PS20  = 20
pageSizes2Int PS50  = 50
pageSizes2Int PS100 = 100
305
pageSizes2Int PS200 = 200
Nicolas Pouillard's avatar
Nicolas Pouillard committed
306 307

aryPS :: Array PageSizes
308
aryPS = [PS10, PS20, PS50, PS100, PS200]
Nicolas Pouillard's avatar
Nicolas Pouillard committed
309 310 311 312 313 314

string2PageSize :: String -> PageSizes
string2PageSize "10" = PS10
string2PageSize "20" = PS20
string2PageSize "50" = PS50
string2PageSize "100" = PS100
315
string2PageSize "200" = PS200
Nicolas Pouillard's avatar
Nicolas Pouillard committed
316 317 318 319
string2PageSize _    = PS10

optps :: PageSizes -> PageSizes -> ReactElement
optps cv val = option [ selected (cv == val), value $ show val ] [text $ show val]