diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-26 06:01:07 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-26 06:01:07 -0500 |
| commit | 240a055709657f04b838d18325a647644fea90df (patch) | |
| tree | f937012d24da3c4400a18d1b0c3f323e37e568c5 | |
| parent | 24176261ab893e9441ea9e4c3e0ead8a59b8c50e (diff) | |
Add task list view with mobile-friendly cards
Task-Id: t-1o2g8gugkr1.2
| -rw-r--r-- | Omni/Jr/Web.hs | 82 |
1 files changed, 80 insertions, 2 deletions
diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs index 0a00f54..f0036d1 100644 --- a/Omni/Jr/Web.hs +++ b/Omni/Jr/Web.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeOperators #-} {-# LANGUAGE NoImplicitPrelude #-} -- : dep warp @@ -15,16 +16,21 @@ where import Alpha import qualified Lucid import qualified Network.Wai.Handler.Warp as Warp +import qualified Omni.Task.Core as TaskCore import Servant import qualified Servant.HTML.Lucid as Lucid defaultPort :: Warp.Port defaultPort = 8080 -type API = Get '[Lucid.HTML] HomePage +type API = + Get '[Lucid.HTML] HomePage + :<|> "tasks" :> Get '[Lucid.HTML] TaskListPage newtype HomePage = HomePage () +newtype TaskListPage = TaskListPage [TaskCore.Task] + instance Lucid.ToHtml HomePage where toHtmlRaw = Lucid.toHtml toHtml (HomePage ()) = @@ -38,12 +44,84 @@ instance Lucid.ToHtml HomePage where ] Lucid.body_ <| do Lucid.h1_ "Jr Web UI" + Lucid.p_ <| Lucid.a_ [Lucid.href_ "/tasks"] "View Tasks" + +instance Lucid.ToHtml TaskListPage where + toHtmlRaw = Lucid.toHtml + toHtml (TaskListPage tasks) = + Lucid.doctypehtml_ <| do + Lucid.head_ <| do + Lucid.title_ "Tasks - Jr Web UI" + Lucid.meta_ [Lucid.charset_ "utf-8"] + Lucid.meta_ + [ Lucid.name_ "viewport", + Lucid.content_ "width=device-width, initial-scale=1" + ] + Lucid.style_ styles + Lucid.body_ <| do + Lucid.h1_ "Tasks" + Lucid.div_ [Lucid.class_ "task-list"] <| do + traverse_ renderTask tasks + where + styles :: Text + styles = + "* { box-sizing: border-box; } \ + \body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; \ + \ margin: 0; padding: 16px; background: #f5f5f5; } \ + \h1 { margin: 0 0 16px 0; } \ + \.task-list { display: flex; flex-direction: column; gap: 8px; } \ + \.task-card { background: white; border-radius: 8px; padding: 16px; \ + \ box-shadow: 0 1px 3px rgba(0,0,0,0.1); } \ + \.task-header { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 8px; } \ + \.task-id { font-family: monospace; color: #0066cc; text-decoration: none; \ + \ font-size: 14px; padding: 4px 0; } \ + \.task-id:hover { text-decoration: underline; } \ + \.badge { display: inline-block; padding: 4px 8px; border-radius: 4px; \ + \ font-size: 12px; font-weight: 500; } \ + \.badge-open { background: #fef3c7; color: #92400e; } \ + \.badge-inprogress { background: #dbeafe; color: #1e40af; } \ + \.badge-review { background: #ede9fe; color: #6b21a8; } \ + \.badge-approved { background: #d1fae5; color: #065f46; } \ + \.badge-done { background: #d1fae5; color: #065f46; } \ + \.priority { font-size: 12px; color: #6b7280; } \ + \.task-title { font-size: 16px; margin: 0; }" + + renderTask :: (Monad m) => TaskCore.Task -> Lucid.HtmlT m () + renderTask t = + Lucid.div_ [Lucid.class_ "task-card"] <| do + Lucid.div_ [Lucid.class_ "task-header"] <| do + Lucid.a_ + [ Lucid.class_ "task-id", + Lucid.href_ ("/tasks/" <> TaskCore.taskId t) + ] + (Lucid.toHtml (TaskCore.taskId t)) + statusBadge (TaskCore.taskStatus t) + Lucid.span_ [Lucid.class_ "priority"] (Lucid.toHtml (tshow (TaskCore.taskPriority t))) + Lucid.p_ [Lucid.class_ "task-title"] (Lucid.toHtml (TaskCore.taskTitle t)) + + statusBadge :: (Monad m) => TaskCore.Status -> Lucid.HtmlT m () + statusBadge status = + let (cls, label) = case status of + TaskCore.Open -> ("badge badge-open", "Open") + TaskCore.InProgress -> ("badge badge-inprogress", "In Progress") + TaskCore.Review -> ("badge badge-review", "Review") + TaskCore.Approved -> ("badge badge-approved", "Approved") + TaskCore.Done -> ("badge badge-done", "Done") + in Lucid.span_ [Lucid.class_ cls] label api :: Proxy API api = Proxy server :: Server API -server = pure (HomePage ()) +server = homeHandler :<|> taskListHandler + where + homeHandler :: Servant.Handler HomePage + homeHandler = pure (HomePage ()) + + taskListHandler :: Servant.Handler TaskListPage + taskListHandler = do + tasks <- liftIO TaskCore.loadTasks + pure (TaskListPage tasks) app :: Application app = serve api server |
