diff options
Diffstat (limited to 'client/src/View')
| -rw-r--r-- | client/src/View/App.hs | 108 | ||||
| -rw-r--r-- | client/src/View/Category/Category.hs | 94 | ||||
| -rw-r--r-- | client/src/View/Category/Form.hs | 117 | ||||
| -rw-r--r-- | client/src/View/Category/Reducer.hs | 59 | ||||
| -rw-r--r-- | client/src/View/Category/Table.hs | 93 | ||||
| -rw-r--r-- | client/src/View/Header.hs | 123 | ||||
| -rw-r--r-- | client/src/View/Icon.hs | 71 | ||||
| -rw-r--r-- | client/src/View/Income/Form.hs | 119 | ||||
| -rw-r--r-- | client/src/View/Income/Header.hs | 77 | ||||
| -rw-r--r-- | client/src/View/Income/Income.hs | 75 | ||||
| -rw-r--r-- | client/src/View/Income/Reducer.hs | 59 | ||||
| -rw-r--r-- | client/src/View/Income/Table.hs | 93 | ||||
| -rw-r--r-- | client/src/View/NotFound.hs | 20 | ||||
| -rw-r--r-- | client/src/View/Payment/Form.hs | 199 | ||||
| -rw-r--r-- | client/src/View/Payment/HeaderForm.hs | 85 | ||||
| -rw-r--r-- | client/src/View/Payment/HeaderInfos.hs | 94 | ||||
| -rw-r--r-- | client/src/View/Payment/Payment.hs | 101 | ||||
| -rw-r--r-- | client/src/View/Payment/Reducer.hs | 110 | ||||
| -rw-r--r-- | client/src/View/Payment/Table.hs | 145 | ||||
| -rw-r--r-- | client/src/View/SignIn.hs | 82 | ||||
| -rw-r--r-- | client/src/View/Statistics/Chart.hs | 102 | ||||
| -rw-r--r-- | client/src/View/Statistics/Statistics.hs | 85 | 
22 files changed, 0 insertions, 2111 deletions
| diff --git a/client/src/View/App.hs b/client/src/View/App.hs deleted file mode 100644 index 71f0234..0000000 --- a/client/src/View/App.hs +++ /dev/null @@ -1,108 +0,0 @@ -module View.App -  ( widget -  ) where - -import qualified Data.Text                  as T -import           Prelude                    hiding (error, init) -import           Reflex.Dom                 (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom                 as R - -import           Common.Model               (Currency, Init (..), UserId) -import qualified Common.Msg                 as Msg - -import           Model.Route                (Route (..)) -import qualified Util.Reflex                as ReflexUtil -import qualified Util.Router                as Router -import qualified View.Category.Category     as Category -import qualified View.Header                as Header -import qualified View.Income.Income         as Income -import qualified View.NotFound              as NotFound -import qualified View.Payment.Payment       as Payment -import qualified View.SignIn                as SignIn -import qualified View.Statistics.Statistics as Statistics - -widget :: Maybe Init -> IO () -widget init = -  R.mainWidget $ R.divClass "app" $ do - -    route <- getRoute - -    rec -      header <- Header.view $ Header.In -        { Header._in_init = initState -        , Header._in_route = route -        } - -      initState <- -        R.foldDyn -          const -          init -          (R.leftmost $ -            [ initEvent -            , Nothing <$ (Header._out_signOut header) -            ]) - -      initEvent <- -        (R.dyn . R.ffor initState $ \case -          Nothing -> do -            signIn <- SignIn.view -            return (Just <$> SignIn._out_success signIn) - -          Just i -> do -            signedWidget i route -            return R.never) >>= ReflexUtil.flatten - -    return () - -signedWidget :: forall t m. MonadWidget t m => Init -> Dynamic t Route -> m () -signedWidget init route = do -  R.dyn . R.ffor route $ \case -    RootRoute -> -      Payment.view $ Payment.In -        { Payment._in_currentUser = _init_currentUser init -        , Payment._in_currency = _init_currency init -        , Payment._in_users = _init_users init -        } - -    IncomeRoute -> -      Income.view $ Income.In -        { Income._in_currentUser = _init_currentUser init -        , Income._in_currency = _init_currency init -        , Income._in_users = _init_users init -        } - -    CategoryRoute -> -      Category.view $ Category.In -        { Category._in_currentUser = _init_currentUser init -        , Category._in_currency = _init_currency init -        , Category._in_users = _init_users init -        } - -    StatisticsRoute -> -      Statistics.view $ Statistics.In -        { Statistics._in_currency = _init_currency init -        } - -    NotFoundRoute -> -      NotFound.view - -  return () - -getRoute :: forall t m. MonadWidget t m => m (Dynamic t Route) -getRoute = do -  r <- Router.partialPathRoute "" . R.switchPromptlyDyn =<< R.holdDyn R.never R.never -  return . R.ffor r $ \case -    [""] -> -      RootRoute - -    ["income"] -> -      IncomeRoute - -    ["category"] -> -      CategoryRoute - -    ["statistics"] -> -      StatisticsRoute - -    _ -> -      NotFoundRoute diff --git a/client/src/View/Category/Category.hs b/client/src/View/Category/Category.hs deleted file mode 100644 index 5b41bb6..0000000 --- a/client/src/View/Category/Category.hs +++ /dev/null @@ -1,94 +0,0 @@ -{-# LANGUAGE ExplicitForAll #-} - -module View.Category.Category -  ( view -  , In(..) -  ) where - -import           Data.Aeson            (FromJSON) -import qualified Data.Maybe            as Maybe -import qualified Data.Text             as T -import           Reflex.Dom            (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom            as R - -import           Common.Model          (Category, CategoryPage (..), Currency, -                                        User, UserId) -import qualified Common.Msg            as Msg - -import qualified Component.Button      as Button -import qualified Component.Modal       as Modal -import qualified Component.Pages       as Pages -import           Loadable              (Loadable (..)) -import qualified Loadable -import qualified Util.Ajax             as AjaxUtil -import qualified Util.Reflex           as ReflexUtil -import qualified Util.Reflex           as ReflexUtil -import qualified View.Category.Form    as Form -import qualified View.Category.Reducer as Reducer -import qualified View.Category.Table   as Table - -data In t = In -  { _in_users       :: [User] -  , _in_currentUser :: UserId -  , _in_currency    :: Currency -  } - -view :: forall t m. MonadWidget t m => In t -> m () -view input = do -  rec -    categoryPage <- Reducer.reducer $ Reducer.In -      { Reducer._in_page         = page -      , Reducer._in_addCategory    = R.leftmost [ headerAddCategory, tableAddCategory ] -      , Reducer._in_editCategory   = editCategory -      , Reducer._in_deleteCategory = deleteCategory -      } - -    let eventFromResult :: forall a. ((Event t (), Table.Out t, Pages.Out t) -> Event t a) -> m (Event t a) -        eventFromResult op = ReflexUtil.flatten $ (Maybe.fromMaybe R.never . fmap op) <$> result - -    page <- eventFromResult $ Pages._out_newPage . (\(_, _, c) -> c) -    headerAddCategory <- eventFromResult $ (\(a, _, _) -> a) -    tableAddCategory <- eventFromResult $ Table._out_add . (\(_, b, _) -> b) -    editCategory <- eventFromResult $ Table._out_edit . (\(_, b, _) -> b) -    deleteCategory <- eventFromResult $ Table._out_delete . (\(_, b, _) -> b) - -    result <- Loadable.viewShowValueWhileLoading categoryPage $ -      \(CategoryPage page categories usedCategories count) -> do -        header <- headerView - -        table <- Table.view $ Table.In -          { Table._in_currentUser = _in_currentUser input -          , Table._in_currency = _in_currency input -          , Table._in_categories = categories -          , Table._in_usedCategories = usedCategories -          , Table._in_users = _in_users input -          } - -        pages <- Pages.view $ Pages.In -          { Pages._in_total = R.constDyn count -          , Pages._in_perPage = Reducer.perPage -          , Pages._in_page = page -          } - -        return (header, table, pages) - -  return () - -headerView :: forall t m. MonadWidget t m => m (Event t ()) -headerView = -  R.divClass "withMargin" $ -    R.divClass "titleButton" $ do -      R.el "h1" $ -        R.text $ -          Msg.get Msg.Category_Title - -      addCategory <- Button._out_clic <$> -        (Button.view . Button.defaultIn . R.text $ -          Msg.get Msg.Category_Add) - -      addCategory <- Modal.view $ Modal.In -        { Modal._in_show    = addCategory -        , Modal._in_content = Form.view $ Form.In { Form._in_operation = Form.New } -        } - -      return addCategory diff --git a/client/src/View/Category/Form.hs b/client/src/View/Category/Form.hs deleted file mode 100644 index d91fc2e..0000000 --- a/client/src/View/Category/Form.hs +++ /dev/null @@ -1,117 +0,0 @@ -module View.Category.Form -  ( view -  , In(..) -  , Operation(..) -  ) where - -import           Control.Monad.IO.Class     (liftIO) -import           Data.Aeson                 (Value) -import qualified Data.Aeson                 as Aeson -import qualified Data.Maybe                 as Maybe -import           Data.Text                  (Text) -import qualified Data.Text                  as T -import qualified Data.Time.Calendar         as Calendar -import qualified Data.Time.Clock            as Time -import           Data.Validation            (Validation) -import qualified Data.Validation            as V -import           Reflex.Dom                 (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom                 as R - -import           Common.Model               (Category (..), -                                             CreateCategoryForm (..), -                                             EditCategoryForm (..)) -import qualified Common.Msg                 as Msg -import qualified Common.Util.Time           as TimeUtil -import qualified Common.Validation.Category as CategoryValidation -import qualified Component.Input            as Input -import qualified Component.Modal            as Modal -import qualified Component.ModalForm        as ModalForm -import qualified Util.Ajax                  as Ajax - -data In = In -  { _in_operation :: Operation -  } - -data Operation -  = New -  | Clone Category -  | Edit Category - -view :: forall t m a. MonadWidget t m => In -> Modal.Content t m -view input cancel = do - -  rec -    let reset = R.leftmost -          [ "" <$ ModalForm._out_cancel modalForm -          , "" <$ ModalForm._out_validate modalForm -          , "" <$ cancel -          ] - -    modalForm <- ModalForm.view $ ModalForm.In -      { ModalForm._in_headerLabel = headerLabel -      , ModalForm._in_ajax        = ajax "/api/category" -      , ModalForm._in_form        = form reset (ModalForm._out_confirm modalForm) -      } - -  return (ModalForm._out_hide modalForm, ModalForm._out_validate modalForm) - -  where - -    form -      :: Event t String -      -> Event t () -      -> m (Dynamic t (Validation Text Value)) -    form reset confirm = do -      name <- Input._out_raw <$> (Input.view -        (Input.defaultIn -          { Input._in_label = Msg.get Msg.Category_Name -          , Input._in_initialValue = name -          , Input._in_validation = CategoryValidation.name -          }) -        (name <$ reset) -        confirm) - -      color <- Input._out_raw <$> (Input.view -        (Input.defaultIn -          { Input._in_label = Msg.get Msg.Category_Color -          , Input._in_initialValue = color -          , Input._in_inputType = "color" -          , Input._in_hasResetButton = False -          , Input._in_validation = CategoryValidation.color -          }) -        (color <$ reset) -        confirm) - -      return $ do -        n <- name -        c <- color -        return . V.Success $ mkPayload n c - -    op = _in_operation input - -    name = -      case op of -        New     -> "" -        Clone c -> _category_name c -        Edit c  -> _category_name c - -    color = -      case op of -        New     -> "" -        Clone c -> _category_color c -        Edit c  -> _category_color c - -    ajax = -      case op of -        Edit _ -> Ajax.put -        _      -> Ajax.post - -    headerLabel = -      case op of -        Edit _ -> Msg.get Msg.Category_Edit -        _      -> Msg.get Msg.Category_Add - -    mkPayload = -      case op of -        Edit i -> \a b -> Aeson.toJSON $ EditCategoryForm (_category_id i) a b -        _      -> \a b -> Aeson.toJSON $ CreateCategoryForm a b diff --git a/client/src/View/Category/Reducer.hs b/client/src/View/Category/Reducer.hs deleted file mode 100644 index 5ad0ddb..0000000 --- a/client/src/View/Category/Reducer.hs +++ /dev/null @@ -1,59 +0,0 @@ -module View.Category.Reducer -  ( perPage -  , reducer -  , In(..) -  ) where - -import           Data.Text    (Text) -import qualified Data.Text    as T -import           Reflex.Dom   (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom   as R - -import           Common.Model (CategoryPage) - -import           Loadable     (Loadable (..)) -import qualified Loadable     as Loadable -import qualified Util.Ajax    as AjaxUtil -import qualified Util.Either  as EitherUtil - -perPage :: Int -perPage = 7 - -data In t a b c = In -  { _in_page           :: Event t Int -  , _in_addCategory    :: Event t a -  , _in_editCategory   :: Event t b -  , _in_deleteCategory :: Event t c -  } - -reducer :: forall t m a b c. MonadWidget t m => In t a b c -> m (Dynamic t (Loadable CategoryPage)) -reducer input = do - -  postBuild <- R.getPostBuild - -  currentPage <- R.holdDyn 1 (_in_page input) - -  let loadPage = -        R.leftmost -          [ 1 <$ postBuild -          , _in_page input -          , 1 <$ _in_addCategory input -          , R.tag (R.current currentPage) (_in_editCategory input) -          , R.tag (R.current currentPage) (_in_deleteCategory input) -          ] - -  getResult <- AjaxUtil.get $ fmap pageUrl loadPage - -  R.holdDyn -    Loading -    (R.leftmost -      [ Loading <$ loadPage -      , Loadable.fromEither <$> getResult -      ]) - -  where -    pageUrl p = -      "api/categories?page=" -      <> (T.pack . show $ p) -      <> "&perPage=" -      <> (T.pack . show $ perPage) diff --git a/client/src/View/Category/Table.hs b/client/src/View/Category/Table.hs deleted file mode 100644 index 90d013d..0000000 --- a/client/src/View/Category/Table.hs +++ /dev/null @@ -1,93 +0,0 @@ -module View.Category.Table -  ( view -  , In(..) -  , Out(..) -  ) where - -import qualified Data.Maybe              as Maybe -import           Data.Text               (Text) -import qualified Data.Text               as T -import           Reflex.Dom              (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom              as R - -import           Common.Model            (Category (..), CategoryId, Currency, -                                          User (..), UserId) -import qualified Common.Model            as CM -import qualified Common.Msg              as Msg -import qualified Common.View.Format      as Format - -import qualified Component.ConfirmDialog as ConfirmDialog -import qualified Component.Table         as Table -import qualified Component.Tag           as Tag -import qualified Util.Ajax               as Ajax -import qualified Util.Either             as EitherUtil -import qualified View.Category.Form      as Form - -data In t = In -  { _in_currentUser    :: UserId -  , _in_currency       :: Currency -  , _in_categories     :: [Category] -  , _in_usedCategories :: [CategoryId] -  , _in_users          :: [User] -  } - -data Out t = Out -  { _out_add    :: Event t () -  , _out_edit   :: Event t () -  , _out_delete :: Event t () -  } - -view :: forall t m. MonadWidget t m => In t -> m (Out t) -view input = do - -  table <- Table.view $ Table.In -    { Table._in_headerLabel = headerLabel -    , Table._in_rows = _in_categories input -    , Table._in_cell = cell (_in_users input) (_in_currency input) -    , Table._in_cloneModal = \category -> -      Form.view $ Form.In -        { Form._in_operation = Form.Clone category -        } -    , Table._in_editModal = \category -> -      Form.view $ Form.In -        { Form._in_operation = Form.Edit category -        } -    , Table._in_deleteModal = \category -> -      ConfirmDialog.view $ ConfirmDialog.In -        { ConfirmDialog._in_header  = Msg.get Msg.Category_DeleteConfirm -        , ConfirmDialog._in_confirm = \e -> do -          res <- Ajax.delete -            (R.constDyn $ T.concat ["/api/category/", T.pack . show $ _category_id category]) -            e -          return $ () <$ R.fmapMaybe EitherUtil.eitherToMaybe res -        } -    , Table._in_canEdit = const True -    , Table._in_canDelete = not . flip elem (_in_usedCategories input) . _category_id -    } - -  return $ Out -    { _out_add = Table._out_add table -    , _out_edit = Table._out_edit table -    , _out_delete = Table._out_delete table -    } - -data Header -  = NameHeader -  | ColorHeader -  deriving (Eq, Show, Bounded, Enum) - -headerLabel :: Header -> Text -headerLabel NameHeader  = Msg.get Msg.Category_Name -headerLabel ColorHeader = Msg.get Msg.Category_Color - -cell :: forall t m. MonadWidget t m => [User] -> Currency -> Header -> Category -> m () -cell users currency header category = -  case header of -    NameHeader -> -      R.text $ _category_name category - -    ColorHeader -> -      Tag.view $ Tag.In -        { Tag._in_text = _category_name category -        , Tag._in_color = _category_color category -        } diff --git a/client/src/View/Header.hs b/client/src/View/Header.hs deleted file mode 100644 index ff9f40a..0000000 --- a/client/src/View/Header.hs +++ /dev/null @@ -1,123 +0,0 @@ -module View.Header -  ( view -  , In(..) -  , Out(..) -  ) where - -import           Data.Map         (Map) -import qualified Data.Map         as M -import qualified Data.Maybe       as Maybe -import           Data.Text        (Text) -import qualified Data.Text        as T -import           Data.Time        (NominalDiffTime) -import           Prelude          hiding (error, init) -import           Reflex.Dom       (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom       as R - -import           Common.Model     (Init (..), User (..)) -import qualified Common.Model     as CM -import qualified Common.Msg       as Msg -import qualified Component.Button as Button -import qualified Component.Link   as Link -import           Model.Route      (Route (..)) -import qualified Util.Css         as CssUtil -import qualified Util.Reflex      as ReflexUtil -import qualified View.Icon        as Icon - -data In t = In -  { _in_init  :: Dynamic t (Maybe Init) -  , _in_route :: Dynamic t Route -  } - -data Out t = Out -  { _out_signOut :: Event t () -  } - -view :: forall t m. MonadWidget t m => (In t) -> m (Out t) -view input = -  R.el "header" $ do - -    R.divClass "title" $ -      R.text $ Msg.get Msg.App_Title - -    let showLinks = Maybe.isJust <$> _in_init input - -    signOut <- R.el "div" $ do -      ReflexUtil.visibleIfDyn showLinks R.blank (links $ _in_route input) -      (R.dyn $ nameSignOut <$> _in_init input) >>= ReflexUtil.flatten - -    return $ Out -      { _out_signOut = signOut -      } - -links :: forall t m. MonadWidget t m => Dynamic t Route -> m () -links route = do -  Link.view -    "/" -    (R.ffor route (attrs RootRoute)) -    (Msg.get Msg.Payment_Title) - -  Link.view -    "/income" -    (R.ffor route (attrs IncomeRoute)) -    (Msg.get Msg.Income_Title) - -  Link.view -    "/category" -    (R.ffor route (attrs CategoryRoute)) -    (Msg.get Msg.Category_Title) - -  Link.view -    "/statistics" -    (R.ffor route (attrs StatisticsRoute)) -    (Msg.get Msg.Statistics_Title) - -  where -    attrs linkRoute currentRoute = -      M.singleton "class" $ -        CssUtil.classes -          [ ("item", True) -          , ("current", linkRoute == currentRoute) -          ] - -nameSignOut :: forall t m. MonadWidget t m => Maybe Init -> m (Event t ()) -nameSignOut init = -  case init of -    Just init -> do -      rec -        attr <- R.holdDyn -          (M.singleton "class" "nameSignOut") -          (fmap (const $ M.fromList [("style", "visibility: hidden"), ("class", "nameSignOut")]) signOut) - -        signOut <- R.elDynAttr "nameSignOut" attr $ do -          case CM.findUser (_init_currentUser init) (_init_users init) of -            Just user -> R.divClass "name" $ R.text (_user_name user) -            Nothing   -> R.blank -          signOutButton - -      return signOut -    _ -> -      return R.never - -signOutButton :: forall t m. MonadWidget t m => m (Event t ()) -signOutButton = do -  rec -    signOut <- Button.view $ -      (Button.defaultIn Icon.signOut) -        { Button._in_class = R.constDyn "signOut item" -        , Button._in_waiting = waiting -        } -    let signOutClic = Button._out_clic signOut -        waiting = R.leftmost -          [ fmap (const True) signOutClic -          , fmap (const False) signOutSuccess -          ] -    signOutSuccess <- askSignOut signOutClic >>= R.debounce (0.5 :: NominalDiffTime) - -  return . fmap (const ()) . R.ffilter (== True) $ signOutSuccess - -  where askSignOut :: forall t m. MonadWidget t m => Event t () -> m (Event t Bool) -        askSignOut signOut = -          fmap getResult <$> R.performRequestAsync xhrRequest -          where xhrRequest = fmap (const $ R.postJson "/api/signOut" ()) signOut -                getResult = (== 200) . R._xhrResponse_status diff --git a/client/src/View/Icon.hs b/client/src/View/Icon.hs deleted file mode 100644 index cc2ef3f..0000000 --- a/client/src/View/Icon.hs +++ /dev/null @@ -1,71 +0,0 @@ -module View.Icon -  ( clone -  , cross -  , delete -  , edit -  , loading -  , doubleLeft -  , doubleLeftBar -  , doubleRight -  , doubleRightBar -  , signOut -  ) where - -import           Data.Map   (Map) -import qualified Data.Map   as M -import           Data.Text  (Text) -import           Reflex.Dom (MonadWidget) -import qualified Reflex.Dom as R - -clone :: forall t m. MonadWidget t m => m () -clone = -  svgAttr "svg" (M.fromList [ ("width", "24"), ("height", "24"), ("viewBox", "0 0 24 24") ]) $ -    svgAttr "path" (M.fromList [("d", "M15.143 13.244l.837-2.244 2.698 5.641-5.678 2.502.805-2.23s-8.055-3.538-7.708-10.913c2.715 5.938 9.046 7.244 9.046 7.244zm8.857-7.244v18h-18v-6h-6v-18h18v6h6zm-2 2h-12.112c-.562-.578-1.08-1.243-1.521-2h7.633v-4h-14v14h4v-3.124c.6.961 1.287 1.823 2 2.576v6.548h14v-14z")]) $ R.blank - -cross :: forall t m. MonadWidget t m => m () -cross = -  svgAttr "svg" (M.fromList [ ("width", "15"), ("height", "15"), ("viewBox", "0 0 1792 1792") ]) $ -    svgAttr "path" (M.fromList [("d", "M1490 1322q0 40-28 68l-136 136q-28 28-68 28t-68-28l-294-294-294 294q-28 28-68 28t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 294 294-294q28-28 68-28t68 28l136 136q28 28 28 68t-28 68l-294 294 294 294q28 28 28 68z")]) $ R.blank - -delete :: forall t m. MonadWidget t m => m () -delete = -  svgAttr "svg" (M.fromList [ ("width", "18"), ("height", "18"), ("viewBox", "0 0 1792 1792") ]) $ -    svgAttr "path" (M.fromList [("d", "M704 1376v-704q0-14-9-23t-23-9h-64q-14 0-23 9t-9 23v704q0 14 9 23t23 9h64q14 0 23-9t9-23zm256 0v-704q0-14-9-23t-23-9h-64q-14 0-23 9t-9 23v704q0 14 9 23t23 9h64q14 0 23-9t9-23zm256 0v-704q0-14-9-23t-23-9h-64q-14 0-23 9t-9 23v704q0 14 9 23t23 9h64q14 0 23-9t9-23zm-544-992h448l-48-117q-7-9-17-11h-317q-10 2-17 11zm928 32v64q0 14-9 23t-23 9h-96v948q0 83-47 143.5t-113 60.5h-832q-66 0-113-58.5t-47-141.5v-952h-96q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h309l70-167q15-37 54-63t79-26h320q40 0 79 26t54 63l70 167h309q14 0 23 9t9 23z")]) $ R.blank - -doubleLeft :: forall t m. MonadWidget t m => m () -doubleLeft = -  svgAttr "svg" (M.fromList [ ("width", "13"), ("height", "13"), ("viewBox", "0 0 1792 1792") ]) $ -    svgAttr "path" (M.fromList [("d", "M1683 141q19-19 32-13t13 32v1472q0 26-13 32t-32-13l-710-710q-8-9-13-19v710q0 26-13 32t-32-13l-710-710q-19-19-19-45t19-45l710-710q19-19 32-13t13 32v710q5-11 13-19z")]) $ R.blank - -doubleLeftBar :: forall t m. MonadWidget t m => m () -doubleLeftBar = -  svgAttr "svg" (M.fromList [ ("width", "13"), ("height", "13"), ("viewBox", "0 0 1792 1792") ]) $ -    svgAttr "path" (M.fromList [("d", "M1747 141q19-19 32-13t13 32v1472q0 26-13 32t-32-13l-710-710q-9-9-13-19v710q0 26-13 32t-32-13l-710-710q-9-9-13-19v678q0 26-19 45t-45 19h-128q-26 0-45-19t-19-45v-1408q0-26 19-45t45-19h128q26 0 45 19t19 45v678q4-11 13-19l710-710q19-19 32-13t13 32v710q4-11 13-19z")]) $ R.blank - -doubleRight :: forall t m. MonadWidget t m => m () -doubleRight = -  svgAttr "svg" (M.fromList [ ("width", "13"), ("height", "13"), ("viewBox", "0 0 1792 1792") ]) $ -    svgAttr "path" (M.fromList [("d", "M109 1651q-19 19-32 13t-13-32v-1472q0-26 13-32t32 13l710 710q8 8 13 19v-710q0-26 13-32t32 13l710 710q19 19 19 45t-19 45l-710 710q-19 19-32 13t-13-32v-710q-5 10-13 19z")]) $ R.blank - -doubleRightBar :: forall t m. MonadWidget t m => m () -doubleRightBar = -  svgAttr "svg" (M.fromList [ ("width", "13"), ("height", "13"), ("viewBox", "0 0 1792 1792") ]) $ -    svgAttr "path" (M.fromList [("d", "M45 1651q-19 19-32 13t-13-32v-1472q0-26 13-32t32 13l710 710q8 8 13 19v-710q0-26 13-32t32 13l710 710q8 8 13 19v-678q0-26 19-45t45-19h128q26 0 45 19t19 45v1408q0 26-19 45t-45 19h-128q-26 0-45-19t-19-45v-678q-5 10-13 19l-710 710q-19 19-32 13t-13-32v-710q-5 10-13 19z")]) $ R.blank - -edit :: forall t m. MonadWidget t m => m () -edit = -  svgAttr "svg" (M.fromList [ ("width", "18"), ("height", "18"), ("viewBox", "0 0 1792 1792") ]) $ -    svgAttr "path" (M.fromList [("d", "M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z")]) $ R.blank - -loading :: forall t m. MonadWidget t m => m () -loading = -  svgAttr "svg" (M.fromList [ ("width", "24"), ("height", "24"), ("viewBox", "0 0 24 24"), ("class", "loader"), ("fill", "currentColor") ]) $ -    svgAttr "path" (M.fromList [("d", "M13.75 22c0 .966-.783 1.75-1.75 1.75s-1.75-.784-1.75-1.75.783-1.75 1.75-1.75 1.75.784 1.75 1.75zm-1.75-22c-1.104 0-2 .896-2 2s.896 2 2 2 2-.896 2-2-.896-2-2-2zm10 10.75c.689 0 1.249.561 1.249 1.25 0 .69-.56 1.25-1.249 1.25-.69 0-1.249-.559-1.249-1.25 0-.689.559-1.25 1.249-1.25zm-22 1.25c0 1.105.896 2 2 2s2-.895 2-2c0-1.104-.896-2-2-2s-2 .896-2 2zm19-8c.551 0 1 .449 1 1 0 .553-.449 1.002-1 1-.551 0-1-.447-1-.998 0-.553.449-1.002 1-1.002zm0 13.5c.828 0 1.5.672 1.5 1.5s-.672 1.501-1.502 1.5c-.826 0-1.498-.671-1.498-1.499 0-.829.672-1.501 1.5-1.501zm-14-14.5c1.104 0 2 .896 2 2s-.896 2-2.001 2c-1.103 0-1.999-.895-1.999-2s.896-2 2-2zm0 14c1.104 0 2 .896 2 2s-.896 2-2.001 2c-1.103 0-1.999-.895-1.999-2s.896-2 2-2z")]) $ R.blank - -signOut :: forall t m. MonadWidget t m => m () -signOut = -  svgAttr "svg" (M.fromList [ ("width", "30"), ("height", "30"), ("viewBox", "0 0 1792 1792") ]) $ -    svgAttr "path" (M.fromList [("d", "M1664 896q0 156-61 298t-164 245-245 164-298 61-298-61-245-164-164-245-61-298q0-182 80.5-343t226.5-270q43-32 95.5-25t83.5 50q32 42 24.5 94.5t-49.5 84.5q-98 74-151.5 181t-53.5 228q0 104 40.5 198.5t109.5 163.5 163.5 109.5 198.5 40.5 198.5-40.5 163.5-109.5 109.5-163.5 40.5-198.5q0-121-53.5-228t-151.5-181q-42-32-49.5-84.5t24.5-94.5q31-43 84-50t95 25q146 109 226.5 270t80.5 343zm-640-768v640q0 52-38 90t-90 38-90-38-38-90v-640q0-52 38-90t90-38 90 38 38 90z")]) $ R.blank - -svgAttr :: forall t m a. MonadWidget t m => Text -> Map Text Text -> m a -> m a -svgAttr elementTag attrs child = R.elWith elementTag (R.ElConfig (Just "http://www.w3.org/2000/svg") attrs) child diff --git a/client/src/View/Income/Form.hs b/client/src/View/Income/Form.hs deleted file mode 100644 index 59f6a0d..0000000 --- a/client/src/View/Income/Form.hs +++ /dev/null @@ -1,119 +0,0 @@ -module View.Income.Form -  ( view -  , In(..) -  , Operation(..) -  ) where - -import           Control.Monad.IO.Class   (liftIO) -import           Data.Aeson               (Value) -import qualified Data.Aeson               as Aeson -import qualified Data.Maybe               as Maybe -import           Data.Text                (Text) -import qualified Data.Text                as T -import qualified Data.Time.Calendar       as Calendar -import qualified Data.Time.Clock          as Time -import           Data.Validation          (Validation) -import qualified Data.Validation          as V -import           Reflex.Dom               (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom               as R - -import           Common.Model             (CreateIncomeForm (..), -                                           EditIncomeForm (..), Income (..)) -import qualified Common.Msg               as Msg -import qualified Common.Util.Time         as TimeUtil -import qualified Common.Validation.Income as IncomeValidation -import qualified Component.Input          as Input -import qualified Component.Modal          as Modal -import qualified Component.ModalForm      as ModalForm -import qualified Util.Ajax                as Ajax - -data In = In -  { _in_operation :: Operation -  } - -data Operation -  = New -  | Clone Income -  | Edit Income - -view :: forall t m a. MonadWidget t m => In -> Modal.Content t m -view input cancel = do - -  rec -    let reset = R.leftmost -          [ "" <$ ModalForm._out_cancel modalForm -          , "" <$ ModalForm._out_validate modalForm -          , "" <$ cancel -          ] - -    modalForm <- ModalForm.view $ ModalForm.In -      { ModalForm._in_headerLabel = headerLabel -      , ModalForm._in_ajax        = ajax "/api/income" -      , ModalForm._in_form        = form reset (ModalForm._out_confirm modalForm) -      } - -  return (ModalForm._out_hide modalForm, ModalForm._out_validate modalForm) - -  where - -    form -      :: Event t String -      -> Event t () -      -> m (Dynamic t (Validation Text Value)) -    form reset confirm = do -      amount <- Input._out_raw <$> (Input.view -        (Input.defaultIn -          { Input._in_label = Msg.get Msg.Income_Amount -          , Input._in_initialValue = amount -          , Input._in_validation = IncomeValidation.amount -          }) -        (amount <$ reset) -        confirm) - -      currentDay <- liftIO $ Time.getCurrentTime >>= TimeUtil.timeToDay - -      let initialDate = T.pack . Calendar.showGregorian $ date currentDay - -      date <- Input._out_raw <$> (Input.view -        (Input.defaultIn -          { Input._in_label = Msg.get Msg.Income_Date -          , Input._in_initialValue = initialDate -          , Input._in_inputType = "date" -          , Input._in_hasResetButton = False -          , Input._in_validation = IncomeValidation.date -          }) -        (initialDate <$ reset) -        confirm) - -      return $ do -        a <- amount -        d <- date -        return . V.Success $ mkPayload a d - -    op = _in_operation input - -    amount = -      case op of -        New     -> "" -        Clone i -> T.pack . show . _income_amount $ i -        Edit i  -> T.pack . show . _income_amount $ i - -    date currentDay = -      case op of -        Edit i -> _income_date i -        _      -> currentDay - -    ajax = -      case op of -        Edit _ -> Ajax.put -        _      -> Ajax.post - -    headerLabel = -      case op of -        Edit _ -> Msg.get Msg.Income_Edit -        _      -> Msg.get Msg.Income_AddLong - -    mkPayload = -      case op of -        Edit i -> \a b -> Aeson.toJSON $ EditIncomeForm (_income_id i) a b -        _      -> \a b -> Aeson.toJSON $ CreateIncomeForm a b diff --git a/client/src/View/Income/Header.hs b/client/src/View/Income/Header.hs deleted file mode 100644 index a26e16a..0000000 --- a/client/src/View/Income/Header.hs +++ /dev/null @@ -1,77 +0,0 @@ -module View.Income.Header -  ( view -  , In(..) -  , Out(..) -  ) where - -import           Control.Monad.IO.Class (liftIO) -import qualified Data.Map               as M -import qualified Data.Maybe             as Maybe -import qualified Data.Text              as T -import qualified Data.Time.Clock        as Clock -import           Reflex.Dom             (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom             as R - -import           Common.Model           (Currency, Income (..), -                                         IncomeHeader (..), User (..)) -import qualified Common.Model           as CM -import qualified Common.Msg             as Msg -import qualified Common.View.Format     as Format - -import qualified Component.Button       as Button -import qualified Component.Modal        as Modal -import qualified View.Income.Form       as Form - -data In t = In -  { _in_users    :: [User] -  , _in_header   :: IncomeHeader -  , _in_currency :: Currency -  } - -data Out t = Out -  { _out_add :: Event t () -  } - -view :: forall t m. MonadWidget t m => In t -> m (Out t) -view input = -  R.divClass "withMargin" $ do - -    currentTime <- liftIO Clock.getCurrentTime - -    case _incomeHeader_since $ _in_header input of -      Nothing -> -        R.blank - -      Just since -> -        R.el "div" $ do - -          R.el "h1" $ do -            R.text $ Msg.get (Msg.Income_CumulativeSince (Format.longDay since)) - -          R.el "ul" $ -            flip mapM_ (M.toList . _incomeHeader_byUser $ _in_header input) $ \(userId, amount) -> -              R.el "li" $ -                R.text $ -                  T.intercalate " " -                    [ Maybe.fromMaybe "" . fmap _user_name $ CM.findUser userId (_in_users input) -                    , "−" -                    , Format.price (_in_currency input) amount -                    ] - -    R.divClass "titleButton" $ do -      R.el "h1" $ -        R.text $ -          Msg.get Msg.Income_MonthlyNet - -      addIncome <- Button._out_clic <$> -        (Button.view . Button.defaultIn . R.text $ -          Msg.get Msg.Income_AddLong) - -      addIncome <- Modal.view $ Modal.In -        { Modal._in_show    = addIncome -        , Modal._in_content = Form.view $ Form.In { Form._in_operation = Form.New } -        } - -      return $ Out -        { _out_add = addIncome -        } diff --git a/client/src/View/Income/Income.hs b/client/src/View/Income/Income.hs deleted file mode 100644 index 7be8091..0000000 --- a/client/src/View/Income/Income.hs +++ /dev/null @@ -1,75 +0,0 @@ -{-# LANGUAGE ExplicitForAll #-} - -module View.Income.Income -  ( view -  , In(..) -  ) where - -import           Data.Aeson          (FromJSON) -import qualified Data.Maybe          as Maybe -import qualified Data.Text           as T -import           Reflex.Dom          (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom          as R - -import           Common.Model        (Currency, Income (..), IncomePage (..), -                                      User, UserId) - -import qualified Component.Pages     as Pages -import           Loadable            (Loadable (..)) -import qualified Loadable -import qualified Util.Ajax           as AjaxUtil -import qualified Util.Reflex         as ReflexUtil -import qualified Util.Reflex         as ReflexUtil -import qualified View.Income.Header  as Header -import qualified View.Income.Reducer as Reducer -import qualified View.Income.Table   as Table - -data In t = In -  { _in_users       :: [User] -  , _in_currentUser :: UserId -  , _in_currency    :: Currency -  } - -view :: forall t m. MonadWidget t m => In t -> m () -view input = do -  rec -    incomePage <- Reducer.reducer $ Reducer.In -      { Reducer._in_page         = page -      , Reducer._in_addIncome    = R.leftmost [headerAddIncome, tableAddIncome] -      , Reducer._in_editIncome   = editIncome -      , Reducer._in_deleteIncome = deleteIncome -      } - -    let eventFromResult :: forall a. ((Header.Out t, Table.Out t, Pages.Out t) -> Event t a) -> m (Event t a) -        eventFromResult op = ReflexUtil.flatten . fmap (Maybe.fromMaybe R.never . fmap op) $ result - -    page <- eventFromResult $ Pages._out_newPage . (\(_, _, c) -> c) -    headerAddIncome <- eventFromResult $ Header._out_add . (\(a, _, _) -> a) -    tableAddIncome <- eventFromResult $ Table._out_add . (\(_, b, _) -> b) -    editIncome <- eventFromResult $ Table._out_edit . (\(_, b, _) -> b) -    deleteIncome <- eventFromResult $ Table._out_delete . (\(_, b, _) -> b) - -    result <- Loadable.viewShowValueWhileLoading incomePage $ -      \(IncomePage page header incomes count) -> do -        header <- Header.view $ Header.In -          { Header._in_users = _in_users input -          , Header._in_header = header -          , Header._in_currency = _in_currency input -          } - -        table <- Table.view $ Table.In -          { Table._in_currentUser = _in_currentUser input -          , Table._in_currency = _in_currency input -          , Table._in_incomes = incomes -          , Table._in_users = _in_users input -          } - -        pages <- Pages.view $ Pages.In -          { Pages._in_total = R.constDyn count -          , Pages._in_perPage = Reducer.perPage -          , Pages._in_page = page -          } - -        return (header, table, pages) - -  return () diff --git a/client/src/View/Income/Reducer.hs b/client/src/View/Income/Reducer.hs deleted file mode 100644 index ea9f664..0000000 --- a/client/src/View/Income/Reducer.hs +++ /dev/null @@ -1,59 +0,0 @@ -module View.Income.Reducer -  ( perPage -  , reducer -  , In(..) -  ) where - -import           Data.Text    (Text) -import qualified Data.Text    as T -import           Reflex.Dom   (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom   as R - -import           Common.Model (IncomePage) - -import           Loadable     (Loadable (..)) -import qualified Loadable     as Loadable -import qualified Util.Ajax    as AjaxUtil -import qualified Util.Either  as EitherUtil - -perPage :: Int -perPage = 7 - -data In t a b c = In -  { _in_page         :: Event t Int -  , _in_addIncome    :: Event t a -  , _in_editIncome   :: Event t b -  , _in_deleteIncome :: Event t c -  } - -reducer :: forall t m a b c. MonadWidget t m => In t a b c -> m (Dynamic t (Loadable IncomePage)) -reducer input = do - -  postBuild <- R.getPostBuild - -  currentPage <- R.holdDyn 1 (_in_page input) - -  let loadPage = -        R.leftmost -          [ 1 <$ postBuild -          , _in_page input -          , 1 <$ _in_addIncome input -          , R.tag (R.current currentPage) (_in_editIncome input) -          , R.tag (R.current currentPage) (_in_deleteIncome input) -          ] - -  getResult <- AjaxUtil.get $ fmap pageUrl loadPage - -  R.holdDyn -    Loading -    (R.leftmost -      [ Loading <$ loadPage -      , Loadable.fromEither <$> getResult -      ]) - -  where -    pageUrl p = -      "api/incomes?page=" -      <> (T.pack . show $ p) -      <> "&perPage=" -      <> (T.pack . show $ perPage) diff --git a/client/src/View/Income/Table.hs b/client/src/View/Income/Table.hs deleted file mode 100644 index 7b7940d..0000000 --- a/client/src/View/Income/Table.hs +++ /dev/null @@ -1,93 +0,0 @@ -module View.Income.Table -  ( view -  , In(..) -  , Out(..) -  ) where - -import qualified Data.Maybe              as Maybe -import           Data.Text               (Text) -import qualified Data.Text               as T -import           Reflex.Dom              (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom              as R - -import           Common.Model            (Currency, Income (..), User (..), -                                          UserId) -import qualified Common.Model            as CM -import qualified Common.Msg              as Msg -import qualified Common.View.Format      as Format - -import qualified Component.ConfirmDialog as ConfirmDialog -import qualified Component.Table         as Table -import qualified Util.Ajax               as Ajax -import qualified Util.Either             as EitherUtil -import qualified View.Income.Form        as Form - -data In t = In -  { _in_currentUser :: UserId -  , _in_currency    :: Currency -  , _in_incomes     :: [Income] -  , _in_users       :: [User] -  } - -data Out t = Out -  { _out_add    :: Event t () -  , _out_edit   :: Event t () -  , _out_delete :: Event t () -  } - -view :: forall t m. MonadWidget t m => In t -> m (Out t) -view input = do - -  table <- Table.view $ Table.In -    { Table._in_headerLabel = headerLabel -    , Table._in_rows = _in_incomes input -    , Table._in_cell = cell (_in_users input) (_in_currency input) -    , Table._in_cloneModal = \income -> -      Form.view $ Form.In -        { Form._in_operation = Form.Clone income -        } -    , Table._in_editModal = \income -> -      Form.view $ Form.In -        { Form._in_operation = Form.Edit income -        } -    , Table._in_deleteModal = \income -> -      ConfirmDialog.view $ ConfirmDialog.In -        { ConfirmDialog._in_header  = Msg.get Msg.Income_DeleteConfirm -        , ConfirmDialog._in_confirm = \e -> do -          res <- Ajax.delete -            (R.constDyn $ T.concat ["/api/income/", T.pack . show $ _income_id income]) -            e -          return $ () <$ R.fmapMaybe EitherUtil.eitherToMaybe res -        } -    , Table._in_canEdit = (== (_in_currentUser input)) . _income_userId -    , Table._in_canDelete = (== (_in_currentUser input)) . _income_userId -    } - -  return $ Out -    { _out_add = Table._out_add table -    , _out_edit = Table._out_edit table -    , _out_delete = Table._out_delete table -    } - -data Header -  = UserHeader -  | AmountHeader -  | DateHeader -  deriving (Eq, Show, Bounded, Enum) - -headerLabel :: Header -> Text -headerLabel UserHeader   = Msg.get Msg.Income_Name -headerLabel DateHeader   = Msg.get Msg.Income_Date -headerLabel AmountHeader = Msg.get Msg.Income_Amount - -cell :: forall t m. MonadWidget t m => [User] -> Currency -> Header -> Income -> m () -cell users currency header income = -  case header of -    UserHeader -> -      R.text . Maybe.fromMaybe "" . fmap _user_name $ CM.findUser (_income_userId income) users - -    DateHeader -> -      R.text . Format.longDay . _income_date $ income - -    AmountHeader -> -      R.text . Format.price currency . _income_amount $ income diff --git a/client/src/View/NotFound.hs b/client/src/View/NotFound.hs deleted file mode 100644 index 1597849..0000000 --- a/client/src/View/NotFound.hs +++ /dev/null @@ -1,20 +0,0 @@ -module View.NotFound -  ( view -  ) where - -import qualified Data.Map       as M -import           Reflex.Dom     (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom     as R - -import qualified Common.Msg     as Msg -import qualified Component.Link as Link - -view :: forall t m. MonadWidget t m => m () -view = -  R.divClass "notfound" $ do -    R.text (Msg.get Msg.NotFound_Message) - -    Link.view -      "/" -      (R.constDyn $ M.singleton "class" "link") -      (Msg.get Msg.NotFound_LinkMessage) diff --git a/client/src/View/Payment/Form.hs b/client/src/View/Payment/Form.hs deleted file mode 100644 index 6c31fad..0000000 --- a/client/src/View/Payment/Form.hs +++ /dev/null @@ -1,199 +0,0 @@ -module View.Payment.Form -  ( view -  , In(..) -  , Operation(..) -  ) where - -import           Control.Monad             (join) -import           Control.Monad.IO.Class    (liftIO) -import           Data.Aeson                (Value) -import qualified Data.Aeson                as Aeson -import qualified Data.List                 as L -import           Data.List.NonEmpty        (NonEmpty) -import qualified Data.Map                  as M -import qualified Data.Maybe                as Maybe -import           Data.Text                 (Text) -import qualified Data.Text                 as T -import           Data.Time                 (NominalDiffTime) -import           Data.Time.Calendar        (Day) -import qualified Data.Time.Calendar        as Calendar -import qualified Data.Time.Clock           as Clock -import           Data.Validation           (Validation) -import qualified Data.Validation           as V -import           Reflex.Dom                (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom                as R -import qualified Text.Read                 as T - -import           Common.Model              (Category (..), CategoryId, -                                            CreatePaymentForm (..), -                                            EditPaymentForm (..), -                                            Frequency (..), Payment (..)) -import qualified Common.Msg                as Msg -import qualified Common.Util.Time          as TimeUtil -import qualified Common.Validation.Payment as PaymentValidation - -import qualified Component.Input           as Input -import qualified Component.Modal           as Modal -import qualified Component.ModalForm       as ModalForm -import qualified Component.Select          as Select -import qualified Util.Ajax                 as Ajax -import qualified Util.Either               as EitherUtil -import qualified Util.Validation           as ValidationUtil - -data In t = In -  { _in_categories :: [Category] -  , _in_operation  :: Operation t -  , _in_frequency  :: Frequency -  } - -data Operation t -  = New -  | Clone Payment -  | Edit Payment - -view :: forall t m a. MonadWidget t m => In t -> Modal.Content t m -view input cancel = do -  rec -    let reset = R.leftmost -          [ "" <$ ModalForm._out_cancel modalForm -          , "" <$ ModalForm._out_validate modalForm -          , "" <$ cancel -          ] - -    modalForm <- ModalForm.view $ ModalForm.In -      { ModalForm._in_headerLabel = headerLabel -      , ModalForm._in_ajax        = ajax "/api/payment" -      , ModalForm._in_form        = form reset (ModalForm._out_confirm modalForm) -      } - -  return (ModalForm._out_hide modalForm, ModalForm._out_validate modalForm) - -  where - -    form -      :: Event t String -      -> Event t () -      -> m (Dynamic t (Validation (NonEmpty Text) Value)) -    form reset confirm = do -      name <- Input.view -        (Input.defaultIn -          { Input._in_label = Msg.get Msg.Payment_Name -          , Input._in_initialValue = name -          , Input._in_validation = PaymentValidation.name -          }) -        (name <$ reset) -        confirm - -      cost <- Input._out_raw <$> (Input.view -        (Input.defaultIn -          { Input._in_label = Msg.get Msg.Payment_Cost -          , Input._in_initialValue = cost -          , Input._in_validation = PaymentValidation.cost -          }) -        (cost <$ reset) -        confirm) - -      currentDate <- date - -      date <- -        case frequency of -          Punctual -> do -            Input._out_raw <$> (Input.view -              (Input.defaultIn -                { Input._in_label = Msg.get Msg.Payment_Date -                , Input._in_initialValue = currentDate -                , Input._in_inputType = "date" -                , Input._in_hasResetButton = False -                , Input._in_validation = PaymentValidation.date -                }) -              (currentDate <$ reset) -              confirm) -          Monthly -> -            return . R.constDyn $ currentDate - -      setCategory <- -        R.debounce  (1 :: NominalDiffTime) (R.updated $ Input._out_raw name) -          >>= (return . R.ffilter (\name -> T.length name >= 3)) -          >>= (Ajax.get . (fmap ("/api/payment/category?name=" <>))) -          >>= (return . R.mapMaybe (join . EitherUtil.eitherToMaybe)) - -      category <- Select._out_value <$> (Select.view $ Select.In -        { Select._in_label = Msg.get Msg.Payment_Category -        , Select._in_initialValue = category -        , Select._in_value = setCategory -        , Select._in_values = R.constDyn categories -        , Select._in_reset = category <$ reset -        , Select._in_isValid = PaymentValidation.category (map _category_id $ _in_categories input) -        , Select._in_validate = confirm -        }) - -      return $ do -        n <- Input._out_value name -        c <- cost -        d <- date -        cat <- category -        return (mkPayload -          <$> ValidationUtil.nelError n -          <*> V.Success c -          <*> V.Success d -          <*> ValidationUtil.nelError cat -          <*> V.Success frequency) - -    frequencies = -      M.fromList -        [ (Punctual, Msg.get Msg.Payment_PunctualMale) -        , (Monthly, Msg.get Msg.Payment_MonthlyMale) -        ] - -    categories = M.fromList . flip map (_in_categories input) $ \c -> -      (_category_id c, _category_name c) - -    category = -      case op of -        New     -> -1 -        Clone p -> _payment_category p -        Edit p  -> _payment_category p - -    op = _in_operation input - -    name = -      case op of -        New     -> "" -        Clone p -> _payment_name p -        Edit p  -> _payment_name p - -    cost = -      case op of -        New     -> "" -        Clone p -> T.pack . show . _payment_cost $ p -        Edit p  -> T.pack . show . _payment_cost $ p - -    date = do -      currentDay <- liftIO $ Clock.getCurrentTime >>= TimeUtil.timeToDay -      return . T.pack . Calendar.showGregorian $ -        case op of -          New     -> currentDay -          Clone p -> currentDay -          Edit p  -> _payment_date p - -    frequency = -      case op of -        New     -> _in_frequency input -        Clone p -> _payment_frequency p -        Edit p  -> _payment_frequency p - -    headerLabel = -      case op of -        New     -> Msg.get Msg.Payment_Add -        Clone _ -> Msg.get Msg.Payment_CloneLong -        Edit _  -> Msg.get Msg.Payment_EditLong - -    ajax = -      case op of -        Edit _ -> Ajax.put -        _      -> Ajax.post - -    mkPayload = -      case op of -        Edit p -> \a b c d e -> Aeson.toJSON $ EditPaymentForm (_payment_id p) a b c d e -        _      -> \a b c d e -> Aeson.toJSON $ CreatePaymentForm a b c d e diff --git a/client/src/View/Payment/HeaderForm.hs b/client/src/View/Payment/HeaderForm.hs deleted file mode 100644 index 1915841..0000000 --- a/client/src/View/Payment/HeaderForm.hs +++ /dev/null @@ -1,85 +0,0 @@ -module View.Payment.HeaderForm -  ( view -  , In(..) -  , Out(..) -  ) where - -import qualified Data.Map          as M -import           Data.Text         (Text) -import qualified Data.Validation   as V -import           Reflex.Dom        (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom        as R - -import           Common.Model      (Category, Currency, Frequency (..), -                                    Income (..), Payment (..), User (..)) -import qualified Common.Msg        as Msg - -import qualified Component.Button  as Button -import qualified Component.Input   as Input -import qualified Component.Modal   as Modal -import qualified Component.Select  as Select -import qualified Util.Reflex       as ReflexUtil -import qualified View.Payment.Form as Form - -data In t = In -  { _in_reset      :: Event t () -  , _in_categories :: [Category] -  } - -data Out t = Out -  { _out_search     :: Event t Text -  , _out_frequency  :: Event t Frequency -  , _out_addPayment :: Event t () -  } - -view :: forall t m. MonadWidget t m => In t -> m (Out t) -view input = -  R.divClass "g-PaymentHeaderForm" $ do - -    (searchName, frequency) <- R.el "div" $ do - -      searchName <- Input._out_raw <$> (Input.view -        ( Input.defaultIn { Input._in_label = Msg.get Msg.Search_Name }) -        ("" <$ _in_reset input) -        R.never) - -      let frequencies = M.fromList -            [ (Punctual, Msg.get Msg.Payment_PunctualMale) -            , (Monthly, Msg.get Msg.Payment_MonthlyMale) -            ] - -      frequency <- Select._out_raw <$> (Select.view $ Select.In -          { Select._in_label        = "" -          , Select._in_initialValue = Punctual -          , Select._in_value        = R.never -          , Select._in_values       = R.constDyn frequencies -          , Select._in_reset        = R.never -          , Select._in_isValid      = V.Success -          , Select._in_validate     = R.never -          }) - -      return (searchName, frequency) - -    addPaymentButton <- Button._out_clic <$> -      (Button.view $ -        (Button.defaultIn (R.text $ Msg.get Msg.Payment_Add)) -          { Button._in_class = R.constDyn "addPayment" -          }) - -    addPayment <- -      (R.dyn . R.ffor frequency $ \frequency -> -        Modal.view $ Modal.In -          { Modal._in_show    = addPaymentButton -          , Modal._in_content = -            Form.view $ Form.In -              { Form._in_categories = _in_categories input -              , Form._in_operation = Form.New -              , Form._in_frequency = frequency -              } -          }) >>= ReflexUtil.flatten - -    return $ Out -      { _out_search = R.updated searchName -      , _out_frequency = R.updated frequency -      , _out_addPayment = addPayment -      } diff --git a/client/src/View/Payment/HeaderInfos.hs b/client/src/View/Payment/HeaderInfos.hs deleted file mode 100644 index f84ee1f..0000000 --- a/client/src/View/Payment/HeaderInfos.hs +++ /dev/null @@ -1,94 +0,0 @@ -module View.Payment.HeaderInfos -  ( view -  , In(..) -  ) where - -import           Control.Monad.IO.Class (liftIO) -import qualified Data.List              as L hiding (groupBy) -import           Data.Map               (Map) -import qualified Data.Map               as M -import           Data.Maybe             (fromMaybe) -import           Data.Text              (Text) -import qualified Data.Text              as T -import qualified Data.Time              as Time -import           Reflex.Dom             (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom             as R - -import           Common.Model           (Currency, ExceedingPayer (..), -                                         Payment (..), PaymentHeader (..), -                                         User (..), UserId) -import qualified Common.Model           as CM -import qualified Common.Msg             as Msg -import qualified Common.View.Format     as Format - -data In t = In -  { _in_users        :: [User] -  , _in_currency     :: Currency -  , _in_header       :: PaymentHeader -  , _in_paymentCount :: Int -  } - -view :: forall t m. MonadWidget t m => In t -> m () -view input = -  R.divClass "g-PaymentHeaderInfos" $ do -    exceedingPayers -      (_in_users input) -      (_in_currency input) -      (_paymentHeader_exceedingPayers header) - -    infos -      (_in_users input) -      (_in_currency input) -      (_paymentHeader_repartition header) -      (_in_paymentCount input) - -  where -    header = _in_header input - -exceedingPayers -  :: forall t m. MonadWidget t m -  => [User] -  -> Currency -  -> [ExceedingPayer] -  -> m () -exceedingPayers users currency payers = -  R.divClass "g-PaymentHeaderInfos__ExceedingPayers" $ -    flip mapM_ payers $ \payer -> -      R.elClass "span" "exceedingPayer" $ do -        R.elClass "span" "userName" $ -          R.text $ -            fromMaybe "" . fmap _user_name $ CM.findUser (_exceedingPayer_userId payer) users -        R.elClass "span" "amount" $ do -          R.text "+ " -          R.text . Format.price currency $ _exceedingPayer_amount payer - -infos -  :: forall t m. MonadWidget t m -  => [User] -  -> Currency -  -> Map UserId Int -  -> Int -  -> m () -infos users currency repartition paymentCount = -  R.divClass "g-PaymentHeaderInfos__Repartition" $ do - -    R.elClass "span" "total" $ do -      R.text $ -        Msg.get $ Msg.Payment_Worth -          (T.intercalate " " -            [ (Format.number paymentCount) -            , if paymentCount > 1 -                then Msg.get Msg.Payment_Many -                else Msg.get Msg.Payment_One -            ]) -          (Format.price currency (M.foldl (+) 0 repartition)) - -    R.elClass "span" "partition" . R.text $ -      let totalByUser = -            L.sortBy (\(_, t1) (_, t2) -> compare t2 t1) -              . M.toList -              $ repartition -      in  T.intercalate ", " . flip map totalByUser $ \(userId, userTotal) -> -            Msg.get $ Msg.Payment_By -              (fromMaybe "" . fmap _user_name $ CM.findUser userId users) -              (Format.price currency userTotal) diff --git a/client/src/View/Payment/Payment.hs b/client/src/View/Payment/Payment.hs deleted file mode 100644 index 26444d7..0000000 --- a/client/src/View/Payment/Payment.hs +++ /dev/null @@ -1,101 +0,0 @@ -module View.Payment.Payment -  ( view -  , In(..) -  ) where - -import           Control.Monad.IO.Class   (liftIO) -import qualified Data.Maybe               as Maybe -import           Data.Text                (Text) -import qualified Data.Text                as T -import           Data.Time.Clock          (NominalDiffTime) -import           Prelude                  hiding (init) -import           Reflex.Dom               (Dynamic, Event, MonadWidget, Reflex) -import qualified Reflex.Dom               as R - -import           Common.Model             (Currency, Frequency, Income (..), -                                           Payment (..), PaymentId, -                                           PaymentPage (..), User, UserId) -import qualified Common.Util.Text         as T - -import qualified Component.Pages          as Pages -import           Loadable                 (Loadable (..)) -import qualified Loadable -import qualified Util.Ajax                as AjaxUtil -import qualified Util.Reflex              as ReflexUtil -import qualified View.Payment.HeaderForm  as HeaderForm -import qualified View.Payment.HeaderInfos as HeaderInfos -import qualified View.Payment.Reducer     as Reducer -import qualified View.Payment.Table       as Table - -data In t = In -  { _in_currentUser :: UserId -  , _in_users       :: [User] -  , _in_currency    :: Currency -  } - -view :: forall t m. MonadWidget t m => In t -> m () -view input = do - -  categories <- AjaxUtil.getNow "api/allCategories" - -  R.dyn . R.ffor categories . Loadable.viewHideValueWhileLoading $ \categories -> do - -    rec -      paymentPage <- Reducer.reducer $ Reducer.In -        { Reducer._in_page          = page -        , Reducer._in_search        = HeaderForm._out_search form -        , Reducer._in_frequency     = HeaderForm._out_frequency form -        , Reducer._in_addPayment    = addPayment -        , Reducer._in_editPayment   = editPayment -        , Reducer._in_deletePayment = deletePayment -        } - -      let eventFromResult :: forall a. ((Table.Out t, Pages.Out t) -> Event t a) -> m (Event t a) -          eventFromResult op = ReflexUtil.flatten . fmap (Maybe.fromMaybe R.never . fmap op) $ result - -      let addPayment = -            R.leftmost -              [ tableAddPayment -              , HeaderForm._out_addPayment form -              ] - -      page <- eventFromResult $ Pages._out_newPage . snd -      tableAddPayment <- eventFromResult $ Table._out_add . fst -      editPayment <- eventFromResult $ Table._out_edit . fst -      deletePayment <- eventFromResult $ Table._out_delete . fst - -      form <- HeaderForm.view $ HeaderForm.In -        { HeaderForm._in_reset             = () <$ addPayment -        , HeaderForm._in_categories        = categories -        } - -      result <- Loadable.viewShowValueWhileLoading paymentPage $ -        \(PaymentPage page frequency header payments count) -> do - -          HeaderInfos.view $ HeaderInfos.In -            { HeaderInfos._in_users = _in_users input -            , HeaderInfos._in_currency = _in_currency input -            , HeaderInfos._in_header = header -            , HeaderInfos._in_paymentCount = count -            } - -          table <- Table.view $ Table.In -            { Table._in_users = _in_users input -            , Table._in_currentUser = _in_currentUser input -            , Table._in_categories = categories -            , Table._in_currency = _in_currency input -            , Table._in_payments = payments -            , Table._in_frequency = frequency -            } - -          pages <- Pages.view $ Pages.In -            { Pages._in_total = R.constDyn count -            , Pages._in_perPage = Reducer.perPage -            , Pages._in_page = page -            } - -          return (table, pages) - -    return () - -  return () diff --git a/client/src/View/Payment/Reducer.hs b/client/src/View/Payment/Reducer.hs deleted file mode 100644 index 3fe59b2..0000000 --- a/client/src/View/Payment/Reducer.hs +++ /dev/null @@ -1,110 +0,0 @@ -module View.Payment.Reducer -  ( perPage -  , reducer -  , In(..) -  , Params(..) -  ) where - -import           Data.Text    (Text) -import qualified Data.Text    as T -import           Data.Time    (NominalDiffTime) -import           Reflex.Dom   (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom   as R - -import           Common.Model (Frequency (..), PaymentPage) - -import           Loadable     (Loadable (..)) -import qualified Loadable     as Loadable -import qualified Util.Ajax    as AjaxUtil -import qualified Util.Either  as EitherUtil - -perPage :: Int -perPage = 7 - -data In t a b c = In -  { _in_page          :: Event t Int -  , _in_search        :: Event t Text -  , _in_frequency     :: Event t Frequency -  , _in_addPayment    :: Event t a -  , _in_editPayment   :: Event t b -  , _in_deletePayment :: Event t c -  } - -data Params = Params -  { _params_page      :: Int -  , _params_search    :: Text -  , _params_frequency :: Frequency -  } deriving (Show) - -initParams = Params 1 "" Punctual - -data Msg -  = Page Int -  | Search Text -  | Frequency Common.Model.Frequency -  | ResetSearch -  deriving Show - -reducer :: forall t m a b c. MonadWidget t m => In t a b c -> m (Dynamic t (Loadable PaymentPage)) -reducer input = do - -  postBuild <- R.getPostBuild - -  debouncedSearch <- R.debounce (1 :: NominalDiffTime) (_in_search input) - -  params <- R.foldDynMaybe -    (\msg params -> case msg of -      Page page     -> -        Just $ params { _params_page = page } - -      Search ""     -> -        if _params_search params == "" then -          Nothing - -        else -          Just $ initParams { _params_frequency = _params_frequency params } - -      Search search -> -        Just $ params { _params_search = search, _params_page = _params_page initParams } - -      Frequency frequency -> -        Just $ params { _params_frequency = frequency, _params_page = _params_page initParams } - -      ResetSearch   -> -        Just $ initParams { _params_frequency = _params_frequency params } -    ) -    initParams -    (R.leftmost -      [ Page <$> _in_page input -      , Search <$> debouncedSearch -      , Frequency <$> _in_frequency input -      , ResetSearch <$ _in_addPayment input -      ]) - -  let paramsEvent = -        R.leftmost -          [ initParams <$ postBuild -          , R.updated params -          , R.tag (R.current params) (_in_editPayment input) -          , R.tag (R.current params) (_in_deletePayment input) -          ] - -  getResult <- AjaxUtil.get (pageUrl <$> paramsEvent) - -  R.holdDyn -    Loading -    (R.leftmost -      [ Loading <$ paramsEvent -      , Loadable.fromEither <$> getResult -      ]) - -  where -    pageUrl (Params page search frequency) = -      "api/payments?page=" -      <> (T.pack . show $ page) -      <> "&perPage=" -      <> (T.pack . show $ perPage) -      <> "&search=" -      <> search -      <> "&frequency=" -      <> (T.pack $ show frequency) diff --git a/client/src/View/Payment/Table.hs b/client/src/View/Payment/Table.hs deleted file mode 100644 index 66065af..0000000 --- a/client/src/View/Payment/Table.hs +++ /dev/null @@ -1,145 +0,0 @@ -module View.Payment.Table -  ( view -  , In(..) -  , Out(..) -  ) where - -import qualified Data.List               as L -import qualified Data.Map                as M -import qualified Data.Maybe              as Maybe -import           Data.Text               (Text) -import qualified Data.Text               as T -import           Reflex.Dom              (Dynamic, Event, MonadWidget) -import qualified Reflex.Dom              as R - -import           Common.Model            (Category (..), Currency, -                                          Frequency (..), Payment (..), -                                          User (..), UserId) -import qualified Common.Model            as CM -import qualified Common.Msg              as Msg -import qualified Common.View.Format      as Format - -import qualified Component.ConfirmDialog as ConfirmDialog -import qualified Component.Table         as Table -import qualified Component.Tag           as Tag -import qualified Util.Ajax               as Ajax -import qualified Util.Either             as EitherUtil -import qualified View.Payment.Form       as Form - -data In t = In -  { _in_users       :: [User] -  , _in_currentUser :: UserId -  , _in_categories  :: [Category] -  , _in_currency    :: Currency -  , _in_payments    :: [Payment] -  , _in_frequency   :: Frequency -  } - -data Out t = Out -  { _out_add    :: Event t () -  , _out_edit   :: Event t () -  , _out_delete :: Event t () -  } - -view :: forall t m. MonadWidget t m => In t -> m (Out t) -view input = do - -  table <- Table.view $ Table.In -    { Table._in_headerLabel = headerLabel (_in_frequency input) -    , Table._in_rows = _in_payments input -    , Table._in_cell = -      cell -        (_in_users input) -        (_in_categories input) -        (_in_frequency input) -        (_in_currency input) -    , Table._in_cloneModal = \payment -> -      Form.view $ Form.In -        { Form._in_categories = _in_categories input -        , Form._in_operation = Form.Clone payment -        , Form._in_frequency = _in_frequency input -        } -    , Table._in_editModal = \payment -> -      Form.view $ Form.In -        { Form._in_categories = _in_categories input -        , Form._in_operation = Form.Edit payment -        , Form._in_frequency = _in_frequency input -        } -    , Table._in_deleteModal = \payment -> -      ConfirmDialog.view $ ConfirmDialog.In -        { ConfirmDialog._in_header  = Msg.get Msg.Payment_DeleteConfirm -        , ConfirmDialog._in_confirm = \e -> do -          res <- Ajax.delete -            (R.constDyn $ T.concat ["/api/payment/", T.pack . show $ _payment_id payment]) -            e -          return $ () <$ R.fmapMaybe EitherUtil.eitherToMaybe res -        } -    , Table._in_canEdit = (== (_in_currentUser input)) . _payment_user -    , Table._in_canDelete = (== (_in_currentUser input)) . _payment_user -    } - -  return $ Out -    { _out_add = Table._out_add table -    , _out_edit = Table._out_edit table -    , _out_delete = Table._out_delete table -    } - -data Header -  = NameHeader -  | CostHeader -  | UserHeader -  | CategoryHeader -  | DateHeader -  deriving (Eq, Show, Bounded, Enum) - -headerLabel :: Frequency -> Header -> Text -headerLabel _ NameHeader        = Msg.get Msg.Payment_Name -headerLabel _ CostHeader        = Msg.get Msg.Payment_Cost -headerLabel _ UserHeader        = Msg.get Msg.Payment_User -headerLabel _ CategoryHeader    = Msg.get Msg.Payment_Category -headerLabel Punctual DateHeader = Msg.get Msg.Payment_Date -headerLabel Monthly DateHeader  = "" - -cell -  :: forall t m. MonadWidget t m -  => [User] -  -> [Category] -  -> Frequency -  -> Currency -  -> Header -  -> Payment -  -> m () -cell users categories frequency currency header payment = -  case header of -    NameHeader -> -      R.text $ _payment_name payment - -    CostHeader -> -      R.divClass (if amount < 0 then "g-Payment__Refund" else "") $ -        R.text $ Format.price currency amount -      where amount = _payment_cost payment - -    UserHeader -> -      R.text . Maybe.fromMaybe "" . fmap _user_name $ CM.findUser (_payment_user payment) users - -    CategoryHeader -> -      let -        category = -          L.find ((== (_payment_category payment)) . _category_id) categories -      in -        Maybe.fromMaybe R.blank . flip fmap category $ \c -> -          Tag.view $ Tag.In -            { Tag._in_text = _category_name c -            , Tag._in_color = _category_color c -            } - -    DateHeader -> -      if frequency == Punctual then -        do -          R.elClass "span" "shortDate" $ -            R.text . Format.shortDay . _payment_date $ payment - -          R.elClass "span" "longDate" $ -            R.text . Format.longDay . _payment_date $ payment -      else -        R.blank diff --git a/client/src/View/SignIn.hs b/client/src/View/SignIn.hs deleted file mode 100644 index e68755f..0000000 --- a/client/src/View/SignIn.hs +++ /dev/null @@ -1,82 +0,0 @@ -module View.SignIn -  ( view -  , Out(..) -  ) where - -import qualified Data.Either              as Either -import qualified Data.Maybe               as Maybe -import           Data.Text                (Text) -import qualified Data.Validation          as V -import           Reflex.Dom               (Event, MonadWidget) -import qualified Reflex.Dom               as R - -import           Common.Model             (Init, SignInForm (SignInForm)) -import qualified Common.Msg               as Msg -import qualified Common.Validation.SignIn as SignInValidation - -import qualified Component.Button         as Button -import qualified Component.Form           as Form -import qualified Component.Input          as Input -import qualified Util.Ajax                as Ajax -import qualified Util.Validation          as ValidationUtil -import qualified Util.WaitFor             as WaitFor - -data Out t = Out -  { _out_success       :: Event t Init -  } - -view :: forall t m. MonadWidget t m => m (Out t) -view = do -  signInResult <- R.divClass "signIn" $ -    Form.view $ do -      rec -        let resetForm = ("" <$ R.ffilter Either.isRight signInResult) - -        email <- Input._out_raw <$> (Input.view -          (Input.defaultIn -            { Input._in_label = Msg.get Msg.SignIn_EmailLabel -            , Input._in_validation = SignInValidation.email -            }) -          resetForm -          validate) - -        password <- Input._out_raw <$> (Input.view -          (Input.defaultIn -            { Input._in_label = Msg.get Msg.SignIn_PasswordLabel -            , Input._in_validation = SignInValidation.password -            , Input._in_inputType = "password" -            }) -          resetForm -          validate) - -        validate <- Button._out_clic <$> (Button.view $ -          (Button.defaultIn (R.text $ Msg.get Msg.SignIn_Button)) -            { Button._in_class = R.constDyn "validate" -            , Button._in_waiting = waiting -            , Button._in_submit = True -            }) - -        let form = do -              e <- email -              p <- password -              return . V.Success $ SignInForm e p - -        (signInResult, waiting) <- WaitFor.waitFor -          (Ajax.postAndParseResult "/api/signIn") -          (ValidationUtil.fireValidation form validate) - -      showSignInResult signInResult - -      return signInResult - -  return $ Out -    { _out_success = R.filterRight signInResult -    } - -showSignInResult :: forall t m. MonadWidget t m => Event t (Either Text Init) -> m () -showSignInResult signInResult = do -  _ <- R.widgetHold R.blank $ showResult <$> signInResult -  R.blank - -  where showResult (Left error) = R.divClass "error" . R.text $ error -        showResult (Right _)    = R.blank diff --git a/client/src/View/Statistics/Chart.hs b/client/src/View/Statistics/Chart.hs deleted file mode 100644 index 63df2a1..0000000 --- a/client/src/View/Statistics/Chart.hs +++ /dev/null @@ -1,102 +0,0 @@ -{-# LANGUAGE CPP           #-} -{-# LANGUAGE JavaScriptFFI #-} - -module View.Statistics.Chart -  ( view -  , In(..) -  , Dataset(..) -  ) where - -import qualified Control.Concurrent                as Concurrent -import           Control.Monad                     (void) -import           Control.Monad.IO.Class            (liftIO) -import           Data.Aeson                        ((.=)) -import qualified Data.Aeson                        as AE -import qualified Data.Map                          as M -import           Data.Text                         (Text) -import           Language.Javascript.JSaddle       (JSString, JSVal) -import qualified Language.Javascript.JSaddle.Value as JSValue -import           Reflex.Dom                        (MonadWidget) -import qualified Reflex.Dom                        as R --- import GHCJS.Foreign.Callback - - -#ifdef __GHCJS__ -foreign import javascript unsafe "new Chart(document.getElementById($1), $2);" drawChart :: JSString -> JSVal -> IO () -#else -drawChart = error "drawChart: only available from JavaScript" -#endif - -data In = In -  { _in_title    :: Text -  , _in_labels   :: [Text] -  , _in_datasets :: [Dataset] -  } - -data Dataset = Dataset -  { _dataset_label :: Text -  , _dataset_data  :: [Int] -  , _dataset_color :: Text -  } - -view :: forall t m. MonadWidget t m => In -> m () -view input = do -  R.divClass "g-Chart" $ -    R.elAttr "canvas" (M.singleton "id" "chart") $ -      R.blank - -  liftIO $ Concurrent.forkIO $ do -    Concurrent.threadDelay 500000 -    config <- JSValue.valMakeJSON (configToJson input) -    drawChart "chart" config - -  return () - -configToJson (In title labels datasets) = -  AE.object -    [ "type" .= AE.String "line" -    , "data" .= -      AE.object -        [ "labels" .= labels -        , "datasets" .= map datasetToJson datasets -        ] -    , "options" .= -      AE.object -        [ "responsive" .= True -        , "title" .= -          AE.object -            [ "display" .= True -            , "text" .= title -            ] -        , "tooltips" .= -          AE.object -            [ "mode" .= AE.String "nearest" -            , "intersect" .= False -            ] -        , "hover" .= -          AE.object -            [ "mode" .= AE.String "nearest" -            , "intersect" .= True -            ] -        , "scales" .= -          AE.object -            [ "yAxes" .= -              [ [ AE.object -                  [ "ticks" .= -                    AE.object -                      [ "beginAtZero" .= True ] -                  ] -                ] -              ] -            ] -        ] -      ] - -datasetToJson (Dataset label data_ color) = -  AE.object -    [ "label" .= label -    , "data" .= data_ -    , "fill" .= False -    , "backgroundColor" .= color -    , "borderColor" .= color -    ] diff --git a/client/src/View/Statistics/Statistics.hs b/client/src/View/Statistics/Statistics.hs deleted file mode 100644 index d931b2b..0000000 --- a/client/src/View/Statistics/Statistics.hs +++ /dev/null @@ -1,85 +0,0 @@ -module View.Statistics.Statistics -  ( view -  , In(..) -  ) where - -import           Control.Monad         (void) -import           Data.Map              (Map) -import qualified Data.Map              as M -import qualified Data.Text             as T -import           Data.Time.Calendar    (Day) -import qualified Data.Time.Calendar    as Calendar -import           Loadable              (Loadable) -import qualified Loadable -import           Reflex.Dom            (Dynamic, MonadWidget) -import qualified Reflex.Dom            as R -import qualified Util.Ajax             as AjaxUtil -import qualified View.Statistics.Chart as Chart - -import           Common.Model          (Category (..), Currency, Income, -                                        MonthStats (..), Stats, User (..)) -import qualified Common.Msg            as Msg -import qualified Common.View.Format    as Format - -data In = In -  { _in_currency    :: Currency -  } - -view :: forall t m. MonadWidget t m => In -> m () -view input = do - -  users <- AjaxUtil.getNow "api/users" -  categories <- AjaxUtil.getNow "api/allCategories" -  statistics <- AjaxUtil.getNow "api/statistics" - -  let loadable = (\u c s -> (,,) <$> u <*> c <*> s) <$> users <*> categories <*> statistics - -  R.divClass "withMargin" $ -    R.divClass "titleButton" $ -      R.el "h1" $ -        R.text $ Msg.get Msg.Statistics_Title - -  void . R.dyn . R.ffor loadable . Loadable.viewHideValueWhileLoading $ -    stats (_in_currency input) - -stats :: forall t m. MonadWidget t m => Currency -> ([User], [Category], Stats) -> m () -stats currency (users, categories, stats) = -  Chart.view $ Chart.In -    { Chart._in_title = Msg.get (Msg.Statistics_ByMonthsAndMean averagePayment averageIncome) -    , Chart._in_labels = map (Format.monthAndYear . _monthStats_start) stats -    , Chart._in_datasets = totalIncomeDataset : totalPaymentDataset : (map categoryDataset categories) -    } - -  where -    averageIncome = -      Format.price currency $ sum  totalIncomes `div` length stats - -    totalIncomeDataset = -      Chart.Dataset -        { Chart._dataset_label = Msg.get Msg.Statistics_TotalIncomes -        , Chart._dataset_data  = totalIncomes -        , Chart._dataset_color = "#222222" -        } - -    totalIncomes = -      map (sum . map snd . M.toList . _monthStats_incomeByUser) stats - -    averagePayment = -      Format.price currency $ sum totalPayments `div` length stats - -    totalPaymentDataset = -      Chart.Dataset -        { Chart._dataset_label = Msg.get Msg.Statistics_TotalPayments -        , Chart._dataset_data  = totalPayments -        , Chart._dataset_color = "#555555" -        } - -    totalPayments = -      map (sum . map snd . M.toList . _monthStats_paymentsByCategory) stats - -    categoryDataset category = -      Chart.Dataset -        { Chart._dataset_label = _category_name category -        , Chart._dataset_data  = map (M.findWithDefault 0 (_category_id category) . _monthStats_paymentsByCategory) stats -        , Chart._dataset_color = _category_color category -        } | 
