diff --git a/dashboards/tracker-web/src/app/roadmap/page.tsx b/dashboards/tracker-web/src/app/roadmap/page.tsx index 4d7b469c..5ed5dd87 100644 --- a/dashboards/tracker-web/src/app/roadmap/page.tsx +++ b/dashboards/tracker-web/src/app/roadmap/page.tsx @@ -1,7 +1,18 @@ 'use client'; import { useEffect, useState, useCallback } from 'react'; -import { SegmentedControl } from '@/components/ui/Primitives'; +import { + SegmentedControl, + Button, + Input, + Select, + Badge, + StatusDot, + MetricCard, + AlertBanner, + type BadgeProps, + type StatusTone, +} from '@/components/ui/Primitives'; import { getRoadmapItems, getRoadmapStats, @@ -11,57 +22,26 @@ import { type PublicRoadmapStats, } from '@/lib/tracker-client'; +type BadgeVariant = NonNullable; + // ── Status column config ──────────────────────────────────────────── const STATUS_COLUMNS = [ - { - key: 'open', - label: 'Planned', - color: 'bg-blue-500', - textColor: 'text-blue-700 dark:text-blue-300', - }, - { - key: 'in_progress', - label: 'In Progress', - color: 'bg-amber-500', - textColor: 'text-amber-700 dark:text-amber-300', - }, - { - key: 'done', - label: 'Complete', - color: 'bg-emerald-500', - textColor: 'text-emerald-700 dark:text-emerald-300', - }, + { key: 'open', label: 'Planned', tone: 'info' as StatusTone }, + { key: 'in_progress', label: 'In Progress', tone: 'warning' as StatusTone }, + { key: 'done', label: 'Complete', tone: 'success' as StatusTone }, ] as const; -const TYPE_BADGES: Record = { - feature: { - label: 'Feature', - className: 'bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300', - }, - bug: { label: 'Bug', className: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300' }, - task: { - label: 'Task', - className: 'bg-gray-100 text-gray-700 dark:bg-gray-900 dark:text-gray-300', - }, +const TYPE_BADGES: Record = { + feature: { label: 'Feature', variant: 'info' }, + bug: { label: 'Bug', variant: 'danger' }, + task: { label: 'Task', variant: 'neutral' }, }; -const PRIORITY_BADGES: Record = { - critical: { - label: 'Critical', - className: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', - }, - high: { - label: 'High', - className: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200', - }, - medium: { - label: 'Medium', - className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', - }, - low: { - label: 'Low', - className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', - }, +const PRIORITY_BADGES: Record = { + critical: { label: 'Critical', variant: 'danger' }, + high: { label: 'High', variant: 'warning' }, + medium: { label: 'Medium', variant: 'neutral' }, + low: { label: 'Low', variant: 'success' }, }; export default function RoadmapPage() { @@ -196,26 +176,21 @@ export default function RoadmapPage() { const itemsByStatus = (status: string) => items.filter(i => i.status === status); return ( -
+
{/* Header */} -
+
-

Product Roadmap

-

+

Product Roadmap

+

Vote on features, report bugs, and shape what we build next

- + Admin → @@ -225,46 +200,47 @@ export default function RoadmapPage() {
{loadError ? ( -
+ {loadError} -
+ ) : null} {voteError ? ( -
+ {voteError} -
+ ) : null} {/* Stats bar */} {stats && (
- - - - + + + +
)} {/* Filters */}
- setSearch(e.target.value)} - className="flex-1 px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" /> - + options={[ + { value: '', label: 'All Types' }, + { value: 'feature', label: 'Features' }, + { value: 'bug', label: 'Bugs' }, + { value: 'task', label: 'Tasks' }, + ]} + /> (
- -

+ +

{[1, 2, 3].map(i => ( -
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -316,19 +287,19 @@ export default function RoadmapPage() { {[1, 2, 3, 4, 5].map(i => (
-
-
+
+
-
-
+
+
-
-
+
+
))} @@ -340,14 +311,14 @@ export default function RoadmapPage() { {STATUS_COLUMNS.map(col => (
- -

{col.label}

- + +

{col.label}

+ {itemsByStatus(col.key).length}
{itemsByStatus(col.key).length === 0 ? ( -

No items

+

No items

) : ( itemsByStatus(col.key).map(item => ( {items.length === 0 ? ( -

No items found

+

No items found

) : ( items.map(item => ( -

{value}

-

{label}

-
- ); -} +const voteButtonClass = (hasVoted: boolean) => + `flex flex-col items-center min-w-[44px] py-1.5 px-2 rounded-lg border text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${ + hasVoted + ? 'border-primary bg-primary/10 text-primary' + : 'border-border bg-muted/50 text-muted-foreground hover:border-primary hover:text-primary' + }`; function ItemCard({ item, @@ -531,45 +500,29 @@ function ItemCard({ const hasVoted = votedItems.has(item.id); return ( -
+
-

- {item.title} -

+

{item.title}

{item.description && ( -

- {item.description} -

+

{item.description}

)} -
- - {typeBadge.label} - - - {priorityBadge.label} - +
+ {typeBadge.label} + {priorityBadge.label} {item.commentCount > 0 && ( - 💬 {item.commentCount} + 💬 {item.commentCount} )}
@@ -592,44 +545,34 @@ function ItemRow({ const hasVoted = votedItems.has(item.id); return ( -
+
-

{item.title}

+

{item.title}

{item.description && ( -

- {item.description} -

+

{item.description}

)}
- - {typeBadge.label} - + {typeBadge.label} {statusCol && ( - - + + {statusCol.label} )} {item.commentCount > 0 && ( - 💬 {item.commentCount} + 💬 {item.commentCount} )}