From 57fa96188230a98003566469a13fb4e79ff8bb24 Mon Sep 17 00:00:00 2001 From: Tataihono Nikora Date: Thu, 9 Apr 2026 05:01:26 +0000 Subject: [PATCH] feat(roadmap): make stat cards clickable with status filtering Homepage stat cards now link to the roadmap page filtered by status. Dashboard stat cards act as toggle filters with active state and a "Clear filter" indicator. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/roadmap/app/(dashboard)/roadmap/page.tsx | 149 ++++++++++++++++-- apps/roadmap/app/(public)/page.tsx | 38 +++-- 2 files changed, 157 insertions(+), 30 deletions(-) diff --git a/apps/roadmap/app/(dashboard)/roadmap/page.tsx b/apps/roadmap/app/(dashboard)/roadmap/page.tsx index 1836fef9e..8fa863dc5 100644 --- a/apps/roadmap/app/(dashboard)/roadmap/page.tsx +++ b/apps/roadmap/app/(dashboard)/roadmap/page.tsx @@ -1,3 +1,4 @@ +import Link from "next/link" import { getAllFeatures, getStatusCounts, @@ -6,13 +7,88 @@ import { getLaneLabel, getOwnerProfile, type Lane, + type FeatureStatus, } from "@/lib/features" import RoadmapTimeline from "@/components/RoadmapTimeline" -export default function DashboardPage() { - const features = getAllFeatures() - const totals = getStatusCounts(features) +const VALID_STATUSES: FeatureStatus[] = [ + "not-started", + "in-progress", + "complete", + "blocked", +] + +const STATUS_LABELS: Record = { + "not-started": "Not Started", + "in-progress": "In Progress", + complete: "Complete", + blocked: "Blocked", +} + +function formatRoadmapRange( + features: ReturnType, +): string { + const datedFeatures = features.filter((feature) => feature.start_date) + if (datedFeatures.length === 0) return "No scheduled range" + + let minDate = new Date(datedFeatures[0].start_date + "T00:00:00") + let maxDate = new Date( + minDate.getTime() + (datedFeatures[0].duration - 1) * 86400000, + ) + + for (const feature of datedFeatures) { + const start = new Date(feature.start_date + "T00:00:00") + const end = new Date(start.getTime() + (feature.duration - 1) * 86400000) + if (start < minDate) minDate = start + if (end > maxDate) maxDate = end + } + + const sameYear = minDate.getFullYear() === maxDate.getFullYear() + const sameMonth = sameYear && minDate.getMonth() === maxDate.getMonth() + + if (sameMonth) { + return minDate.toLocaleDateString("en-US", { + month: "long", + year: "numeric", + }) + } + + if (sameYear) { + return `${minDate.toLocaleDateString("en-US", { + month: "long", + })} – ${maxDate.toLocaleDateString("en-US", { + month: "long", + year: "numeric", + })}` + } + + return `${minDate.toLocaleDateString("en-US", { + month: "long", + year: "numeric", + })} – ${maxDate.toLocaleDateString("en-US", { + month: "long", + year: "numeric", + })}` +} + +export default async function DashboardPage({ + searchParams, +}: { + searchParams: Promise<{ status?: string }> +}) { + const { status: statusParam } = await searchParams + const activeStatus = + statusParam && VALID_STATUSES.includes(statusParam as FeatureStatus) + ? (statusParam as FeatureStatus) + : null + + const allFeatures = getAllFeatures() + const totals = getStatusCounts(allFeatures) + const features = activeStatus + ? allFeatures.filter((f) => f.status === activeStatus) + : allFeatures const owners = getAllOwners() + const roadmapRange = formatRoadmapRange(allFeatures) const laneLabels = Object.fromEntries( ALL_LANES.map((l) => [l, getLaneLabel(l)]), ) as Record @@ -24,8 +100,9 @@ export default function DashboardPage() {

Roadmap

-

- April – May 2026 · {features.length} features +

+ {roadmapRange} · {features.length} features + {activeStatus && ` · Filtered: ${STATUS_LABELS[activeStatus]}`}

@@ -34,26 +111,57 @@ export default function DashboardPage() { + -
+ {/* Active filter indicator */} + {activeStatus && ( +
+ + Showing{" "} + + {STATUS_LABELS[activeStatus]} + {" "} + tickets + + + Clear filter + +
+ )} + {/* Legend */} -
+
- Not - Started + {" "} + Not Started In @@ -67,10 +175,10 @@ export default function DashboardPage() { {" "} Blocked - + P0 P1 - P2 + P2
@@ -90,15 +198,26 @@ function StatCard({ label, count, color, + href, + active, }: { label: string count: number color: string + href: string + active: boolean }) { return ( -
+
{count}
-
{label}
-
+
{label}
+ ) } diff --git a/apps/roadmap/app/(public)/page.tsx b/apps/roadmap/app/(public)/page.tsx index 53b867659..896f4e9cd 100644 --- a/apps/roadmap/app/(public)/page.tsx +++ b/apps/roadmap/app/(public)/page.tsx @@ -37,7 +37,7 @@ export default function HomePage() { {/* Warm signpost */} -
+
Looking for Jesus Film?{" "} through the power of AI -

+

The Digital Strategies Department is building trusted AI capabilities that help people discover gospel content, engage meaningfully with Scripture, and take faithful next steps.

-

+

This is our public roadmap. See what we're building, what we've shipped, and where we're headed.

@@ -76,7 +76,7 @@ export default function HomePage() { {/* Momentum stats */}
-

+

Progress at a glance

@@ -84,16 +84,19 @@ export default function HomePage() { label="Features Shipped" count={totals.complete} color="text-green-400" + href="/roadmap?status=complete" />
@@ -109,13 +112,13 @@ export default function HomePage() { className="rounded-lg border border-[var(--color-border)] bg-[var(--color-card)]" > -