QU5FW67RGCWOWT2YFM4NYMJFFHWIRPQANQBBAHBKZUY7UYMCSIMQC
PT4276XCOP5NJ3GRFJLIBZKVNVAOATAY5PLWV7FWK6RZW5FTEP5AC
NJNMO72S7VIUV22JXB4IFPZMHWTJAOTP6EC6Z4QSKYIROSXT52MQC
EA5BFM5GMM7KNMDLTVOSUKVKMSIDD72TAFVHDVGEOUY5VELECU3QC
WRPIYG3EUHZR6N6T74ZXZDXATRMIRLXAQ24UNUNSVTVYGMT2VDSQC
JXG3FCXYBDKMUD77DOM7RCIJYKB7BILC43OHHDZBE7YQRGAMUCCAC
BFZN4SUAGYNFFYVAP36BAX32DMO622PK4EPEVQQEAGC2IHTEAAPQC
RB2ETNIFLQUA6OA66DAEOXZ25ENMQGNKX5CZRSKEYHTD6BQ6NTFQC
CDHZL3RP2HGNSSBXD4VDSAW7M3SPBF7LBYB2BL6I3N6EI5URSOJAC
UOG5H2TW5R3FSHQPJCEMNFDQZS5APZUP7OM54FIBQG7ZP4HASQ7QC
NSRSSSTRMJPPUYQANYDWGI5D3NVM6RQEVZCDUUNQAOL3OWQTD27AC
-- import Halogen as H
-- import Halogen.HTML.Core (ClassName(..))
-- import Halogen.HTML as HH
-- import Halogen.HTML.CSS as CSS
-- import Halogen.HTML.Events as E
-- import Web.Event.Event as WE
-- import Halogen.HTML.Properties as P
import Aftok.Types (APIError(..))
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as E
import Halogen.HTML.Properties as P
import Effect.Class.Console (error)
newtype ProjectId = ProjectId UUID
pidStr :: ProjectId -> String
pidStr (ProjectId uuid) = show uuid
data APIError
= Forbidden
| ParseFailure Json String
| Error { status :: Maybe StatusCode, message :: String }
projectListComponent
:: forall query input m
. EC.MonadEffect m
=> Capability m
-> H.Component HH.HTML query input Project m
projectListComponent caps = H.mkComponent
{ initialState
, render
, eval: H.mkEval $ H.defaultEval
{ handleAction = eval
, initialize = Just Initialize
}
} where
initialState :: input -> ProjectCState
initialState _ = { projects: [] }
render :: forall slots. ProjectCState -> H.ComponentHTML ProjectAction slots m
render st =
let renderOption (Project' p) =
HH.option [P.value $ pidStr p.projectId] [HH.text p.projectName]
in HH.select
[E.onSelectedIndexChange (Just <<< Select)]
([HH.option [P.selected true, P.disabled true] [HH.text "Select a project"]] <> map renderOption st.projects)
eval :: ProjectAction -> H.HalogenM ProjectCState ProjectAction () Project m Unit
eval = case _ of
Initialize -> do
res <- lift caps.listProjects
case res of
Left _ -> error "Could not retrieve project list."
Right projects -> H.modify_ (_ { projects = projects })
Select i -> do
projects <- H.gets (_.projects)
traverse_ H.raise (index projects i)
projectName <- x .: "projectName"
inceptionDate <- x .: "inceptionDate"
initiatorStr <- x .: "initiator"
initiator <- note "Failed to decode initiator UUID" $ parseUUID initiatorStr
pure $ Project' { projectName, inceptionDate, initiator }
project <- x .: "project"
projectIdStr <- x .: "projectId"
projectId <- ProjectId <$> (note "Failed to decode project UUID" $ parseUUID projectIdStr)
component :: forall query input output. Capability Aff -> TimelineConfig -> H.Component HH.HTML query input output Aff
component caps conf = H.mkComponent
component
:: forall query input output
. Capability Aff
-> Project.Capability Aff
-> H.Component HH.HTML query input output Aff
component caps pcaps = H.mkComponent
HH.section
[P.classes (ClassName <$> ["section-border", "border-primary"])]
[HH.div
[P.classes (ClassName <$> ["container", "pt-6"])]
[HH.h1
[P.classes (ClassName <$> ["mb-0", "font-weight-bold", "text-center"])]
[HH.text "Time Tracker"]
,HH.p
[P.classes (ClassName <$> ["col-md-5", "text-muted", "text-center", "mx-auto"])]
[HH.text "Today's project timeline"]
,lineHtml (intervalHtml conf st.limits <$> st.history <> fromMaybe st.active)
,HH.div_
[HH.button
[P.classes (ClassName <$> ["btn", "btn-primary", "float-left"])
,E.onClick \_ -> Just Start
]
[HH.text "Start Work"]
,HH.button
[P.classes (ClassName <$> ["btn", "btn-primary", "float-right"])
,E.onClick \_ -> Just Stop
let lineForm =
[lineHtml (intervalHtml st.limits <$> st.history <> fromMaybe st.active)
,HH.div_
[HH.button
[P.classes (ClassName <$> ["btn", "btn-primary", "float-left"])
,E.onClick \_ -> Just Start
]
[HH.text "Start Work"]
,HH.button
[P.classes (ClassName <$> ["btn", "btn-primary", "float-right"])
,E.onClick \_ -> Just Stop
]
[HH.text "Stop Work"]
]
]
in HH.section
[P.classes (ClassName <$> ["section-border", "border-primary"])]
([HH.div
[P.classes (ClassName <$> ["container-fluid", "pt-6"])]
[HH.h1
[P.classes (ClassName <$> ["mb-0", "font-weight-bold", "text-center"])]
[HH.text "Time Tracker"]
,HH.p
[P.classes (ClassName <$> ["col-md-5", "text-muted", "text-center", "mx-auto"])]
[HH.text "Today's project timeline"]
,HH.div_
[HH.slot _projectList unit (Project.projectListComponent pcaps) unit (Just <<< ProjectSelected)]
]
] <> (if isJust st.selectedProject then lineForm else []))
t <- lift caps.logStart
H.modify_ (start t)
let withProject (Project' p) = do
logged <- lift $ caps.logStart p.projectId
case logged of
Left _ -> log "Failed to start timer."
Right t -> H.modify_ (start t)
project <- H.gets (_.selectedProject)
log $ "Project selected? " <> show (isJust project)
traverse_ withProject project
t <- lift caps.logEnd
H.modify_ (stop t)
let withProject (Project' p) = do
logged <- lift $ caps.logEnd p.projectId
case logged of
Left _ -> log "Failed to stop timer."
Right t -> H.modify_ (stop t)
project <- H.gets (_.selectedProject)
traverse_ withProject project
logStart :: ProjectId -> Aff (Either TimelineError Instant)
logStart (ProjectId pid) = do
let requestBody = Just <<< RB.Json <<< encodeJson $ { schemaVersion: "2.0" }
result <- post RF.json ("/api/projects/" <> UUID.toString pid <> "/logStart") requestBody
case result of
Left err -> pure <<< Left <<< LogFailure $ Error { status: Nothing, message: printError err }
Right r -> case r.status of
StatusCode 403 -> pure <<< Left <<< LogFailure $ Forbidden
StatusCode 200 -> Right <$> liftEffect now
other -> pure <<< Left <<< LogFailure $ Error { status: Just other, message: r.statusText }
logEnd :: ProjectId -> Aff (Either TimelineError Instant)
logEnd (ProjectId pid) = do
let requestBody = Just <<< RB.Json <<< encodeJson $ { schemaVersion: "2.0" }
result <- post RF.json ("/api/projects/" <> UUID.toString pid <> "/logEnd") requestBody
case result of
Left err -> pure <<< Left <<< LogFailure $ Error { status: Nothing, message: printError err }
Right r -> case r.status of
StatusCode 403 -> pure <<< Left <<< LogFailure $ Forbidden
StatusCode 200 -> Right <$> liftEffect now
other -> pure <<< Left <<< LogFailure $ Error { status: Just other, message: r.statusText }
apiCapability :: Capability Aff
apiCapability = { logStart, logEnd }
module Aftok.Types where
import Data.Argonaut.Core (Json)
import Data.Maybe (Maybe)
import Affjax.StatusCode (StatusCode)
data APIError
= Forbidden
| ParseFailure Json String
| Error { status :: Maybe StatusCode, message :: String }