From cbd31e9aa4edab4b256e5d6c8dba86223e09d701 Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Sun, 22 Mar 2026 16:55:41 -0400 Subject: [PATCH] ui: complete dashboard redesign - New modern dashboard layout with gradient background - Quick action cards with teal glow effects - Live trials banner with pulsing indicator - Stats grid with colored icons - Study list with bot icons - Quick links sidebar - Recent trials section with status badges - Proper null safety and type checking --- src/app/dashboard/page.tsx | 641 ++++++++++++++----------------------- 1 file changed, 245 insertions(+), 396 deletions(-) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 7089d59..b8735d8 100755 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -2,31 +2,21 @@ import * as React from "react"; import Link from "next/link"; -import { format } from "date-fns"; +import { useSession } from "~/lib/auth-client"; import { formatDistanceToNow } from "date-fns"; import { - Activity, - ArrowRight, - Calendar, - CheckCircle, - CheckCircle2, - Clock, - FlaskConical, - HelpCircle, - LayoutDashboard, - MoreHorizontal, Play, - PlayCircle, Plus, - Search, - Settings, + Activity, + Clock, + CheckCircle2, Users, - Radio, - Gamepad2, - AlertTriangle, + FlaskConical, + ChevronRight, Bot, - User, - MessageSquare, + Radio, + BarChart3, + Settings, } from "lucide-react"; import { Button } from "~/components/ui/button"; @@ -37,448 +27,307 @@ import { CardHeader, CardTitle, } from "~/components/ui/card"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "~/components/ui/dropdown-menu"; -import { Progress } from "~/components/ui/progress"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "~/components/ui/select"; import { Badge } from "~/components/ui/badge"; import { ScrollArea } from "~/components/ui/scroll-area"; import { api } from "~/trpc/react"; -import { useTour } from "~/components/onboarding/TourProvider"; -import { useSession } from "~/lib/auth-client"; export default function DashboardPage() { - const { startTour } = useTour(); const { data: session } = useSession(); - const [studyFilter, setStudyFilter] = React.useState(null); + const userName = session?.user?.name ?? "Researcher"; - // --- Data Fetching --- - const { data: userStudiesData } = api.studies.list.useQuery({ + const { data: userStudies } = api.studies.list.useQuery({ memberOnly: true, - limit: 100, + limit: 10, }); - const userStudies = userStudiesData?.studies ?? []; - const { data: stats } = api.dashboard.getStats.useQuery({ - studyId: studyFilter ?? undefined, + const { data: recentTrials } = api.trials.list.useQuery({ + limit: 5, + status: undefined, }); const { data: liveTrials } = api.dashboard.getLiveTrials.useQuery( - { studyId: studyFilter ?? undefined }, + {}, { refetchInterval: 5000 }, ); - const { data: recentActivity } = api.dashboard.getRecentActivity.useQuery({ - limit: 15, - studyId: studyFilter ?? undefined, - }); + const { data: stats } = api.dashboard.getStats.useQuery({}); - const { data: studyProgress } = api.dashboard.getStudyProgress.useQuery({ - limit: 5, - studyId: studyFilter ?? undefined, - }); - - const userName = session?.user?.name ?? "Researcher"; - - const getWelcomeMessage = () => { + const greeting = (() => { const hour = new Date().getHours(); - let greeting = "Good evening"; - if (hour < 12) greeting = "Good morning"; - else if (hour < 18) greeting = "Good afternoon"; - - return `${greeting}, ${userName.split(" ")[0]}`; - }; + if (hour < 12) return "Good morning"; + if (hour < 18) return "Good afternoon"; + return "Good evening"; + })(); return ( -
- {/* Header Section */} -
+
+ {/* Header */} +
-

- {getWelcomeMessage()} +

+ {greeting}, {userName.split(" ")[0]}

-

- Here's what's happening with your research today. +

+ Ready to run your next session?

-
- - - - +
- {/* Main Stats Grid */} -
- 0 && ( + + +
+ + +
+
+

+ {liveTrials.length} Active Session{liveTrials.length > 1 ? "s" : ""} +

+

+ {liveTrials.map((t) => t.participantCode).join(", ")} +

+
+ +
+
+ )} + + {/* Stats Row */} +
+ - - + -
- {/* Action Center & Recent Activity */} -
- {/* Quick Actions Card */} - - - Quick Actions - Common tasks to get you started - - - - - - - - - - - {/* Recent Activity Card */} - - - Recent Activity - - Your latest interactions across the platform - - -
- {recentActivity?.map((activity) => { - let eventColor = "bg-primary/30 ring-background"; - let Icon = Activity; - if (activity.type === "trial_started") { - eventColor = "bg-blue-500 ring-blue-100 dark:ring-blue-900"; - Icon = PlayCircle; - } else if (activity.type === "trial_completed") { - eventColor = - "bg-green-500 ring-green-100 dark:ring-green-900"; - Icon = CheckCircle; - } else if (activity.type === "error") { - eventColor = "bg-red-500 ring-red-100 dark:ring-red-900"; - Icon = AlertTriangle; - } else if (activity.type === "intervention") { - eventColor = - "bg-orange-500 ring-orange-100 dark:ring-orange-900"; - Icon = Gamepad2; - } else if (activity.type === "annotation") { - eventColor = - "bg-yellow-500 ring-yellow-100 dark:ring-yellow-900"; - Icon = MessageSquare; - } - - return ( -
- - - -
- {activity.title} -
-
- {activity.description} -
-
- {formatDistanceToNow(new Date(activity.time), { - addSuffix: true, - })} -
+
+ {userStudies?.studies?.slice(0, 5).map((study) => ( + +
+
+ +
+
+

+ {study.name} +

+

+ {study.status === "active" ? ( + Active + ) : ( + {study.status} + )} +

- ); - })} - {!recentActivity?.length && ( -
- -

No recent activity recorded.

-

- Start a trial to see experiment events stream here. -

- )} -
- + + + ))} + + {!userStudies?.studies?.length && ( +
+ +

No studies yet

+

+ Create your first study to get started +

+ +
+ )} +
-
-
- {/* Live Trials */} - 0 ? "border-primary bg-primary/5 shadow-sm" : "border-muted/40"} col-span-4 transition-colors duration-500`} - > - -
-
- - Live Sessions - {liveTrials && liveTrials.length > 0 && ( - - - - - )} - - - Currently running trials in the Wizard interface - -
- -
-
- - {!liveTrials?.length ? ( -
- -

- No trials are currently running. -

- -
- ) : ( -
- {liveTrials.map((trial) => ( -
-
-
- -
-
-

- {trial.participantCode} - - • {trial.experimentName} - -

-
- - Started{" "} - {trial.startedAt - ? formatDistanceToNow(new Date(trial.startedAt), { - addSuffix: true, - }) - : "just now"} -
-
-
- -
- ))} -
- )} -
-
+ + + + + - {/* Study Progress */} - - - Study Progress - - Completion tracking for active studies - - - - {studyProgress?.map((study) => ( -
-
-
{study.name}
-
- {study.participants} / {study.totalParticipants}{" "} - Participants -
+ {/* Recent Trials */} + + + + + Recent Trials + + + + +
+ {recentTrials?.slice(0, 5).map((trial) => ( + +
+ {trial.participant.participantCode} + + {trial.status.replace("_", " ")} + +
+

+ {trial.experiment.name} +

+ {trial.completedAt && ( +

+ {formatDistanceToNow(new Date(trial.completedAt), { addSuffix: true })} +

+ )} + + ))} + + {!recentTrials?.length && ( +

+ No trials yet +

+ )}
- -
- ))} - {!studyProgress?.length && ( -

- No active studies to track. -

- )} - - + + + +
); } -function StatsCard({ - title, +function StatCard({ + label, value, icon: Icon, - description, - trend, - iconColor, + color, }: { - title: string; - value: string | number; + label: string; + value: number; icon: React.ElementType; - description: string; - trend?: string; - iconColor?: string; + color: "teal" | "emerald" | "blue" | "violet"; }) { + const colorClasses = { + teal: "bg-teal-500/10 text-teal-600 dark:text-teal-400", + emerald: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400", + blue: "bg-blue-500/10 text-blue-600 dark:text-blue-400", + violet: "bg-violet-500/10 text-violet-600 dark:text-violet-400", + }; + return ( - - - {title} - - - -
{value}
-

- {description} - {trend && ( - - {trend} - - )} -

+ + +
+ +
+
+

{value}

+

{label}

+
);