module Web.View.Hubs.OperationalReviewBoard where import Web.Types import Generated.Types import IHP.Prelude import IHP.ViewPrelude import Application.Helper.HubHealth (healthScoreBadge) import Application.Helper.FrictionScore (scoreBand) import Web.View.Hubs.BottleneckDashboard (severityBadge) data OperationalReviewBoardView = OperationalReviewBoardView { hubs :: ![Hub] , allSnapshots :: ![HubHealthSnapshot] , topFrictionScores :: ![FrictionScore] , topWidgets :: ![Widget] , bottlenecks :: ![BottleneckRecord] , openPropagations :: ![CrossHubPropagation] } instance View OperationalReviewBoardView where html OperationalReviewBoardView { .. } = [hsx|

Operational Review Board

Hub Health Matrix

{if null hubs then [hsx|

No hubs registered.

|] else [hsx| {forEach hubs renderHubRow}
Hub Health Snapshot
|]}

Top Friction Widgets

{if null topFrictionScores then [hsx|

No friction scores computed yet.

|] else [hsx| {forEach (zip topFrictionScores topWidgets) renderFrictionRow}
Widget Score Type
|]}

Active Bottlenecks by Stage

{if null bottlenecks then [hsx|

No active bottlenecks.

|] else [hsx|
{forEach stages renderBottleneckStage}
|]}

Open Cross-Hub Propagations

{if null openPropagations then [hsx|

No open propagation events.

|] else [hsx|
{forEach openPropagations renderPropagationRow}
|]}
|] where stages = ["candidate", "requirement", "decision", "observation"] :: [Text] stageLabel s = case s of "candidate" -> "Candidate" "requirement" -> "Requirement" "decision" -> "Decision" "observation" -> "Observation" _ -> s latestSnapshotFor hub = find (\s -> s.hubId == hub.id) allSnapshots renderHubRow :: Hub -> Html renderHubRow h = let mSnap = latestSnapshotFor h in [hsx| {h.name} {case mSnap of Nothing -> [hsx||] Just s -> [hsx| healthScoreBadge s.healthScore}> {show s.healthScore} |]} {maybe "never" (\s -> show s.computedAt) mSnap} History |] renderFrictionRow :: (FrictionScore, Widget) -> Html renderFrictionRow (fs, w) = [hsx| {w.name} scoreBand fs.score}> {show fs.score} {w.widgetType} |] renderBottleneckStage :: Text -> Html renderBottleneckStage stage = let stageBNs = filter (\b -> b.stage == stage) bottlenecks cnt = length stageBNs hasCrit = any (\b -> b.severity == "critical") stageBNs colourCls = if cnt == 0 then "bg-gray-50 text-gray-400" else if hasCrit then "bg-red-50 text-red-700" else "bg-orange-50 text-orange-700" in [hsx|
colourCls}>
{show cnt}
{stageLabel stage}
|] renderPropagationRow :: CrossHubPropagation -> Html renderPropagationRow p = [hsx|
{p.patternType} {p.summary}

{show p.detectedAt}

Acknowledge Resolve
|]