summaryrefslogtreecommitdiff
path: root/Omni/Jr
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-27 10:43:34 -0500
committerBen Sima <ben@bensima.com>2025-11-27 10:43:34 -0500
commit2f50ccd89227fd0562209b9107dd87adb2d46a58 (patch)
tree0e203cfb95bd0e8a5220b884187f1f620ccca8e8 /Omni/Jr
parenta32a0210d17ed78ef653ee07d384041dbd10e5d2 (diff)
Add HTMX script and first partial endpoints
All handlers are wired in. The implementation is complete: 1. **HTMX script tag added** to `pageHead` (CDN at unpkg.com with integr 2. **Partial routes created** in API type: `/partials/recent-activity` a 3. **Handlers implemented** for both partials 4. **HTMX attributes added** to the HomePage: - Ready Queue count: `hx-get="/partials/ready-count"` with `hx-trigge - Recent Activity div: `hx-get="/partials/recent-activity"` with `hx- Task-Id: t-151.1
Diffstat (limited to 'Omni/Jr')
-rw-r--r--Omni/Jr/Web.hs66
1 files changed, 59 insertions, 7 deletions
diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs
index 6c30be3..17849f4 100644
--- a/Omni/Jr/Web.hs
+++ b/Omni/Jr/Web.hs
@@ -23,6 +23,7 @@ import qualified Data.Text.Lazy as LazyText
import qualified Data.Text.Lazy.Encoding as LazyText
import Data.Time (UTCTime, defaultTimeLocale, formatTime)
import qualified Lucid
+import qualified Lucid.Base as Lucid
import qualified Network.Wai.Handler.Warp as Warp
import qualified Omni.Jr.Web.Style as Style
import qualified Omni.Task.Core as TaskCore
@@ -62,6 +63,8 @@ type API =
:<|> "tasks" :> Capture "id" Text :> "review" :> Get '[Lucid.HTML] TaskReviewPage
:<|> "tasks" :> Capture "id" Text :> "accept" :> PostRedirect
:<|> "tasks" :> Capture "id" Text :> "reject" :> ReqBody '[FormUrlEncoded] RejectForm :> PostRedirect
+ :<|> "partials" :> "recent-activity" :> Get '[Lucid.HTML] RecentActivityPartial
+ :<|> "partials" :> "ready-count" :> Get '[Lucid.HTML] ReadyCountPartial
data CSS
@@ -96,6 +99,10 @@ data ReviewInfo
data StatsPage = StatsPage TaskCore.TaskStats (Maybe Text)
+newtype RecentActivityPartial = RecentActivityPartial [TaskCore.Task]
+
+newtype ReadyCountPartial = ReadyCountPartial Int
+
newtype RejectForm = RejectForm (Maybe Text)
instance FromForm RejectForm where
@@ -127,6 +134,12 @@ pageHead title =
Lucid.content_ "width=device-width, initial-scale=1"
]
Lucid.link_ [Lucid.rel_ "stylesheet", Lucid.href_ "/style.css"]
+ Lucid.script_
+ [ Lucid.src_ "https://unpkg.com/htmx.org@2.0.4",
+ Lucid.integrity_ "sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+",
+ Lucid.crossorigin_ "anonymous"
+ ]
+ ("" :: Text)
navbar :: (Monad m) => Lucid.HtmlT m ()
navbar =
@@ -190,8 +203,14 @@ instance Lucid.ToHtml HomePage where
Lucid.h2_ <| do
"Ready Queue "
- Lucid.a_ [Lucid.href_ "/ready", Lucid.class_ "ready-link"]
- <| Lucid.toHtml ("(" <> tshow (length readyTasks) <> " tasks)")
+ Lucid.span_
+ [ Lucid.class_ "ready-count",
+ Lucid.makeAttribute "hx-get" "/partials/ready-count",
+ Lucid.makeAttribute "hx-trigger" "every 5s"
+ ]
+ <| do
+ Lucid.a_ [Lucid.href_ "/ready", Lucid.class_ "ready-link"]
+ <| Lucid.toHtml ("(" <> tshow (length readyTasks) <> " tasks)")
if null readyTasks
then Lucid.p_ [Lucid.class_ "empty-msg"] "No tasks ready for work."
else
@@ -199,11 +218,16 @@ instance Lucid.ToHtml HomePage where
<| traverse_ renderTaskCard (take 5 readyTasks)
Lucid.h2_ "Recent Activity"
- if null recentTasks
- then Lucid.p_ [Lucid.class_ "empty-msg"] "No recent tasks."
- else
- Lucid.div_ [Lucid.class_ "task-list"]
- <| traverse_ renderTaskCard recentTasks
+ Lucid.div_
+ [ Lucid.class_ "recent-activity",
+ Lucid.makeAttribute "hx-get" "/partials/recent-activity",
+ Lucid.makeAttribute "hx-trigger" "every 10s"
+ ]
+ <| if null recentTasks
+ then Lucid.p_ [Lucid.class_ "empty-msg"] "No recent tasks."
+ else
+ Lucid.div_ [Lucid.class_ "task-list"]
+ <| traverse_ renderTaskCard recentTasks
where
statCard :: (Monad m) => Text -> Int -> Text -> Text -> Lucid.HtmlT m ()
statCard label count badgeClass href =
@@ -688,6 +712,21 @@ instance Lucid.ToHtml StatsPage where
""
Lucid.span_ [Lucid.class_ "stats-count"] (Lucid.toHtml (tshow count))
+instance Lucid.ToHtml RecentActivityPartial where
+ toHtmlRaw = Lucid.toHtml
+ toHtml (RecentActivityPartial recentTasks) =
+ if null recentTasks
+ then Lucid.p_ [Lucid.class_ "empty-msg"] "No recent tasks."
+ else
+ Lucid.div_ [Lucid.class_ "task-list"]
+ <| traverse_ renderTaskCard recentTasks
+
+instance Lucid.ToHtml ReadyCountPartial where
+ toHtmlRaw = Lucid.toHtml
+ toHtml (ReadyCountPartial count) =
+ Lucid.a_ [Lucid.href_ "/ready", Lucid.class_ "ready-link"]
+ <| Lucid.toHtml ("(" <> tshow count <> " tasks)")
+
-- | Simple markdown renderer for epic descriptions
-- Supports: headers (#, ##, ###), lists (- or *), code blocks (```), inline code (`)
renderMarkdown :: (Monad m) => Text -> Lucid.HtmlT m ()
@@ -817,6 +856,8 @@ server =
:<|> taskReviewHandler
:<|> taskAcceptHandler
:<|> taskRejectHandler
+ :<|> recentActivityHandler
+ :<|> readyCountHandler
where
styleHandler :: Servant.Handler LazyText.Text
styleHandler = pure Style.css
@@ -949,6 +990,17 @@ server =
TaskCore.updateTaskStatus tid TaskCore.Open []
pure <| addHeader ("/tasks/" <> tid) NoContent
+ recentActivityHandler :: Servant.Handler RecentActivityPartial
+ recentActivityHandler = do
+ allTasks <- liftIO TaskCore.loadTasks
+ let recentTasks = take 5 <| List.sortBy (flip compare `on` TaskCore.taskUpdatedAt) allTasks
+ pure (RecentActivityPartial recentTasks)
+
+ readyCountHandler :: Servant.Handler ReadyCountPartial
+ readyCountHandler = do
+ readyTasks <- liftIO TaskCore.getReadyTasks
+ pure (ReadyCountPartial (length readyTasks))
+
getReviewInfo :: Text -> IO ReviewInfo
getReviewInfo tid = do
maybeCommit <- findCommitForTask tid