summaryrefslogtreecommitdiff
path: root/Omni
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-26 06:01:07 -0500
committerBen Sima <ben@bensima.com>2025-11-26 06:01:07 -0500
commit240a055709657f04b838d18325a647644fea90df (patch)
treef937012d24da3c4400a18d1b0c3f323e37e568c5 /Omni
parent24176261ab893e9441ea9e4c3e0ead8a59b8c50e (diff)
Add task list view with mobile-friendly cards
Task-Id: t-1o2g8gugkr1.2
Diffstat (limited to 'Omni')
-rw-r--r--Omni/Jr/Web.hs82
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