feat(analytics): refine timeline visualization and add print support

This commit is contained in:
2026-02-17 21:17:11 -05:00
parent 568d408587
commit 72971a4b49
82 changed files with 6670 additions and 2448 deletions

View File

@@ -1,125 +0,0 @@
"use client";
import { Activity, Calendar, CheckCircle, FlaskConical } from "lucide-react";
import { DashboardOverviewLayout } from "~/components/ui/page-layout";
interface DashboardContentProps {
userName: string;
userRole: string;
totalStudies: number;
activeTrials: number;
scheduledTrials: number;
completedToday: number;
canControl: boolean;
canManage: boolean;
_recentTrials: unknown[];
}
export function DashboardContent({
userName,
userRole,
totalStudies,
activeTrials,
scheduledTrials,
completedToday,
canControl,
canManage,
_recentTrials,
}: DashboardContentProps) {
const getWelcomeMessage = () => {
switch (userRole) {
case "wizard":
return "Ready to control trials";
case "researcher":
return "Your research platform awaits";
case "administrator":
return "System management dashboard";
default:
return "Welcome to HRIStudio";
}
};
const quickActions = [
...(canManage
? [
{
title: "Create Study",
description: "Start a new research study",
icon: FlaskConical,
href: "/studies/new",
variant: "primary" as const,
},
]
: []),
...(canControl
? [
{
title: "Browse Studies",
description: "View and manage studies",
icon: Calendar,
href: "/studies",
variant: "default" as const,
},
]
: []),
];
const stats = [
{
title: "Studies",
value: totalStudies,
description: "Research studies",
icon: FlaskConical,
variant: "primary" as const,
action: {
label: "View All",
href: "/studies",
},
},
{
title: "Active Trials",
value: activeTrials,
description: "Currently running",
icon: Activity,
variant: "success" as const,
...(canControl && {
action: {
label: "View",
href: "/studies",
},
}),
},
{
title: "Scheduled",
value: scheduledTrials,
description: "Upcoming trials",
icon: Calendar,
variant: "default" as const,
},
{
title: "Completed Today",
value: completedToday,
description: "Finished trials",
icon: CheckCircle,
variant: "success" as const,
},
];
const alerts: never[] = [];
const recentActivity = null;
return (
<DashboardOverviewLayout
title={`${getWelcomeMessage()}, ${userName}`}
description="Monitor your HRI research activities and manage ongoing studies"
userName={userName}
userRole={userRole}
breadcrumb={[{ label: "Dashboard" }]}
quickActions={quickActions}
stats={stats}
alerts={alerts}
recentActivity={recentActivity}
/>
);
}

View File

@@ -1,12 +1,13 @@
"use client";
import React, { useEffect } from "react";
import React, { useEffect, useRef } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { signOut, useSession } from "next-auth/react";
import { toast } from "sonner";
import {
BarChart3,
BookOpen,
Building,
ChevronDown,
FlaskConical,
@@ -113,6 +114,14 @@ const adminItems = [
},
];
const helpItems = [
{
title: "Help Center",
url: "/help",
icon: BookOpen, // Make sure to import this from lucide-react
},
];
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
userRole?: string;
}
@@ -126,9 +135,38 @@ export function AppSidebar({
const isAdmin = userRole === "administrator";
const { state: sidebarState } = useSidebar();
const isCollapsed = sidebarState === "collapsed";
const { selectedStudyId, userStudies, selectStudy, refreshStudyData } =
const { selectedStudyId, userStudies, selectStudy, refreshStudyData, isLoadingUserStudies } =
useStudyManagement();
// Reference to track if we've already attempted auto-selection to avoid fighting with manual clearing
const hasAutoSelected = useRef(false);
// Auto-select most recently touched study if none selected
useEffect(() => {
// Only run if not loading, no study selected, and we have studies available
// And only run once per session (using ref) to allow user to clear selection if desired
if (
!isLoadingUserStudies &&
!selectedStudyId &&
userStudies.length > 0 &&
!hasAutoSelected.current
) {
// userStudies is sorted by updatedAt desc from the API, so the first one is the most recent
// userStudies is sorted by updatedAt desc from the API, so the first one is the most recent
const mostRecent = userStudies[0];
if (mostRecent) {
console.log("Auto-selecting most recent study:", mostRecent.name);
void selectStudy(mostRecent.id);
hasAutoSelected.current = true;
}
}
}, [
isLoadingUserStudies,
selectedStudyId,
userStudies,
selectStudy,
]);
// Debug API call
const { data: debugData } = api.dashboard.debug.useQuery(undefined, {
enabled: process.env.NODE_ENV === "development",
@@ -520,6 +558,44 @@ export function AppSidebar({
)}
</SidebarContent>
{/* Help Section */}
<SidebarGroup>
<SidebarGroupLabel>Support</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{helpItems.map((item) => {
const isActive = pathname.startsWith(item.url);
const menuButton = (
<SidebarMenuButton asChild isActive={isActive}>
<Link href={item.url}>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
);
return (
<SidebarMenuItem key={item.title}>
{isCollapsed ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>{menuButton}</TooltipTrigger>
<TooltipContent side="right" className="text-sm">
{item.title}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
menuButton
)}
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
{/* Debug info moved to footer tooltip button */}
<SidebarFooter>