mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-11 14:44:44 -05:00
Update docs, continue route consolidation
This commit is contained in:
53
src/app/(dashboard)/not-found.tsx
Normal file
53
src/app/(dashboard)/not-found.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import Link from "next/link";
|
||||
import { AlertCircle, Home, ArrowLeft } from "lucide-react";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
|
||||
export default function DashboardNotFound() {
|
||||
return (
|
||||
<div className="flex min-h-[60vh] items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-red-50">
|
||||
<AlertCircle className="h-8 w-8 text-red-500" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">Page Not Found</CardTitle>
|
||||
<CardDescription>
|
||||
The page you're looking for doesn't exist or has been
|
||||
moved.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="text-muted-foreground space-y-2 text-center text-sm">
|
||||
<p>This might have happened because:</p>
|
||||
<ul className="space-y-1 text-left">
|
||||
<li>• The URL was typed incorrectly</li>
|
||||
<li>• The page was moved or deleted</li>
|
||||
<li>• You don't have permission to view this page</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 pt-4">
|
||||
<Button asChild className="w-full">
|
||||
<Link href="/dashboard">
|
||||
<Home className="mr-2 h-4 w-4" />
|
||||
Go to Dashboard
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<Link href="/studies">
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Browse Studies
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
329
src/app/(dashboard)/studies/[id]/analytics/page.tsx
Normal file
329
src/app/(dashboard)/studies/[id]/analytics/page.tsx
Normal file
@@ -0,0 +1,329 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import { Suspense, useEffect } from "react";
|
||||
import {
|
||||
Activity,
|
||||
BarChart3,
|
||||
Calendar,
|
||||
Download,
|
||||
Filter,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "~/components/ui/select";
|
||||
import { ManagementPageLayout } from "~/components/ui/page-layout";
|
||||
import { useStudyContext } from "~/lib/study-context";
|
||||
import { useSelectedStudyDetails } from "~/hooks/useSelectedStudyDetails";
|
||||
|
||||
// Mock chart component - replace with actual charting library
|
||||
function MockChart({ title, data }: { title: string; data: number[] }) {
|
||||
const maxValue = Math.max(...data);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium">{title}</h4>
|
||||
<div className="flex h-32 items-end space-x-1">
|
||||
{data.map((value, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-primary min-h-[4px] flex-1 rounded-t"
|
||||
style={{ height: `${(value / maxValue) * 100}%` }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AnalyticsOverview() {
|
||||
const metrics = [
|
||||
{
|
||||
title: "Total Trials This Month",
|
||||
value: "142",
|
||||
change: "+12%",
|
||||
trend: "up",
|
||||
description: "vs last month",
|
||||
icon: Activity,
|
||||
},
|
||||
{
|
||||
title: "Avg Trial Duration",
|
||||
value: "24.5m",
|
||||
change: "-3%",
|
||||
trend: "down",
|
||||
description: "vs last month",
|
||||
icon: Calendar,
|
||||
},
|
||||
{
|
||||
title: "Completion Rate",
|
||||
value: "94.2%",
|
||||
change: "+2.1%",
|
||||
trend: "up",
|
||||
description: "vs last month",
|
||||
icon: TrendingUp,
|
||||
},
|
||||
{
|
||||
title: "Participant Retention",
|
||||
value: "87.3%",
|
||||
change: "+5.4%",
|
||||
trend: "up",
|
||||
description: "vs last month",
|
||||
icon: BarChart3,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{metrics.map((metric) => (
|
||||
<Card key={metric.title}>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
{metric.title}
|
||||
</CardTitle>
|
||||
<metric.icon className="text-muted-foreground h-4 w-4" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{metric.value}</div>
|
||||
<div className="text-muted-foreground flex items-center space-x-2 text-xs">
|
||||
<span
|
||||
className={`flex items-center ${
|
||||
metric.trend === "up" ? "text-green-600" : "text-red-600"
|
||||
}`}
|
||||
>
|
||||
{metric.trend === "up" ? (
|
||||
<TrendingUp className="mr-1 h-3 w-3" />
|
||||
) : (
|
||||
<TrendingDown className="mr-1 h-3 w-3" />
|
||||
)}
|
||||
{metric.change}
|
||||
</span>
|
||||
<span>{metric.description}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChartsSection() {
|
||||
const trialData = [12, 19, 15, 27, 32, 28, 35, 42, 38, 41, 37, 44];
|
||||
const participantData = [8, 12, 10, 15, 18, 16, 20, 24, 22, 26, 23, 28];
|
||||
const completionData = [85, 88, 92, 89, 94, 91, 95, 92, 96, 94, 97, 94];
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 lg:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Trial Volume</CardTitle>
|
||||
<CardDescription>Monthly trial execution trends</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<MockChart title="Trials per Month" data={trialData} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Participant Enrollment</CardTitle>
|
||||
<CardDescription>New participants over time</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<MockChart title="New Participants" data={participantData} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Completion Rates</CardTitle>
|
||||
<CardDescription>Trial completion percentage</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<MockChart title="Completion %" data={completionData} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RecentInsights() {
|
||||
const insights = [
|
||||
{
|
||||
title: "Peak Performance Hours",
|
||||
description:
|
||||
"Participants show 23% better performance during 10-11 AM trials",
|
||||
type: "trend",
|
||||
severity: "info",
|
||||
},
|
||||
{
|
||||
title: "Attention Span Decline",
|
||||
description:
|
||||
"Average attention span has decreased by 8% over the last month",
|
||||
type: "alert",
|
||||
severity: "warning",
|
||||
},
|
||||
{
|
||||
title: "High Completion Rate",
|
||||
description: "Memory retention study achieved 98% completion rate",
|
||||
type: "success",
|
||||
severity: "success",
|
||||
},
|
||||
{
|
||||
title: "Equipment Utilization",
|
||||
description: "Robot interaction trials are at 85% capacity utilization",
|
||||
type: "info",
|
||||
severity: "info",
|
||||
},
|
||||
];
|
||||
|
||||
const getSeverityColor = (severity: string) => {
|
||||
switch (severity) {
|
||||
case "success":
|
||||
return "bg-green-50 text-green-700 border-green-200";
|
||||
case "warning":
|
||||
return "bg-yellow-50 text-yellow-700 border-yellow-200";
|
||||
case "info":
|
||||
return "bg-blue-50 text-blue-700 border-blue-200";
|
||||
default:
|
||||
return "bg-gray-50 text-gray-700 border-gray-200";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Insights</CardTitle>
|
||||
<CardDescription>
|
||||
AI-generated insights from your research data
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{insights.map((insight, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`rounded-lg border p-4 ${getSeverityColor(insight.severity)}`}
|
||||
>
|
||||
<h4 className="mb-1 font-medium">{insight.title}</h4>
|
||||
<p className="text-sm">{insight.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function AnalyticsContent({ studyId: _studyId }: { studyId: string }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header with time range controls */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Select defaultValue="30d">
|
||||
<SelectTrigger className="w-[120px]">
|
||||
<SelectValue placeholder="Time range" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="7d">Last 7 days</SelectItem>
|
||||
<SelectItem value="30d">Last 30 days</SelectItem>
|
||||
<SelectItem value="90d">Last 90 days</SelectItem>
|
||||
<SelectItem value="1y">Last year</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" size="sm">
|
||||
<Filter className="mr-2 h-4 w-4" />
|
||||
Filter
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overview Metrics */}
|
||||
<AnalyticsOverview />
|
||||
|
||||
{/* Charts */}
|
||||
<ChartsSection />
|
||||
|
||||
{/* Insights */}
|
||||
<div className="grid gap-4 lg:grid-cols-3">
|
||||
<div className="lg:col-span-2">
|
||||
<RecentInsights />
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Quick Actions</CardTitle>
|
||||
<CardDescription>Generate custom reports</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Button variant="outline" className="w-full justify-start">
|
||||
<BarChart3 className="mr-2 h-4 w-4" />
|
||||
Trial Performance Report
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full justify-start">
|
||||
<Activity className="mr-2 h-4 w-4" />
|
||||
Participant Engagement
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full justify-start">
|
||||
<TrendingUp className="mr-2 h-4 w-4" />
|
||||
Trend Analysis
|
||||
</Button>
|
||||
<Button variant="outline" className="w-full justify-start">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Custom Export
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StudyAnalyticsPage() {
|
||||
const params = useParams();
|
||||
const studyId: string = typeof params.id === "string" ? params.id : "";
|
||||
const { setSelectedStudyId, selectedStudyId } = useStudyContext();
|
||||
const { study } = useSelectedStudyDetails();
|
||||
|
||||
// Set the active study if it doesn't match the current route
|
||||
useEffect(() => {
|
||||
if (studyId && selectedStudyId !== studyId) {
|
||||
setSelectedStudyId(studyId);
|
||||
}
|
||||
}, [studyId, selectedStudyId, setSelectedStudyId]);
|
||||
|
||||
return (
|
||||
<ManagementPageLayout
|
||||
title="Analytics"
|
||||
description="Insights and data analysis for this study"
|
||||
breadcrumb={[
|
||||
{ label: "Dashboard", href: "/dashboard" },
|
||||
{ label: "Studies", href: "/studies" },
|
||||
{ label: study?.name ?? "Study", href: `/studies/${studyId}` },
|
||||
{ label: "Analytics" },
|
||||
]}
|
||||
>
|
||||
<Suspense fallback={<div>Loading analytics...</div>}>
|
||||
<AnalyticsContent studyId={studyId} />
|
||||
</Suspense>
|
||||
</ManagementPageLayout>
|
||||
);
|
||||
}
|
||||
3
src/app/dashboard/layout.tsx
Normal file
3
src/app/dashboard/layout.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import DashboardLayout from "../(dashboard)/layout";
|
||||
|
||||
export default DashboardLayout;
|
||||
352
src/app/dashboard/page.tsx
Normal file
352
src/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,352 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Building,
|
||||
FlaskConical,
|
||||
TestTube,
|
||||
Users,
|
||||
Calendar,
|
||||
Clock,
|
||||
AlertCircle,
|
||||
CheckCircle2,
|
||||
} from "lucide-react";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Progress } from "~/components/ui/progress";
|
||||
import { api } from "~/trpc/react";
|
||||
|
||||
// Dashboard Overview Cards
|
||||
function OverviewCards() {
|
||||
const { data: stats, isLoading } = api.dashboard.getStats.useQuery();
|
||||
|
||||
const cards = [
|
||||
{
|
||||
title: "Active Studies",
|
||||
value: stats?.totalStudies ?? 0,
|
||||
description: "Research studies you have access to",
|
||||
icon: Building,
|
||||
color: "text-blue-600",
|
||||
bg: "bg-blue-50",
|
||||
},
|
||||
{
|
||||
title: "Experiments",
|
||||
value: stats?.totalExperiments ?? 0,
|
||||
description: "Experiment protocols designed",
|
||||
icon: FlaskConical,
|
||||
color: "text-green-600",
|
||||
bg: "bg-green-50",
|
||||
},
|
||||
{
|
||||
title: "Participants",
|
||||
value: stats?.totalParticipants ?? 0,
|
||||
description: "Enrolled participants",
|
||||
icon: Users,
|
||||
color: "text-purple-600",
|
||||
bg: "bg-purple-50",
|
||||
},
|
||||
{
|
||||
title: "Trials",
|
||||
value: stats?.totalTrials ?? 0,
|
||||
description: "Total trials conducted",
|
||||
icon: TestTube,
|
||||
color: "text-orange-600",
|
||||
bg: "bg-orange-50",
|
||||
},
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Card key={i}>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<div className="bg-muted h-4 w-20 animate-pulse rounded" />
|
||||
<div className="bg-muted h-8 w-8 animate-pulse rounded" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="bg-muted h-8 w-12 animate-pulse rounded" />
|
||||
<div className="bg-muted mt-2 h-3 w-24 animate-pulse rounded" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{cards.map((card) => (
|
||||
<Card key={card.title}>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">{card.title}</CardTitle>
|
||||
<div className={`rounded-md p-2 ${card.bg}`}>
|
||||
<card.icon className={`h-4 w-4 ${card.color}`} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{card.value}</div>
|
||||
<p className="text-muted-foreground text-xs">{card.description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Recent Activity Component
|
||||
function RecentActivity() {
|
||||
const { data: activities = [], isLoading } =
|
||||
api.dashboard.getRecentActivity.useQuery({
|
||||
limit: 8,
|
||||
});
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case "success":
|
||||
return <CheckCircle2 className="h-4 w-4 text-green-600" />;
|
||||
case "pending":
|
||||
return <Clock className="h-4 w-4 text-yellow-600" />;
|
||||
case "error":
|
||||
return <AlertCircle className="h-4 w-4 text-red-600" />;
|
||||
default:
|
||||
return <AlertCircle className="h-4 w-4 text-blue-600" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="col-span-4">
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Activity</CardTitle>
|
||||
<CardDescription>
|
||||
Latest updates from your research platform
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<div className="space-y-4">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div key={i} className="flex items-center space-x-4">
|
||||
<div className="bg-muted h-4 w-4 animate-pulse rounded-full" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="bg-muted h-4 w-3/4 animate-pulse rounded" />
|
||||
<div className="bg-muted h-3 w-1/2 animate-pulse rounded" />
|
||||
</div>
|
||||
<div className="bg-muted h-3 w-16 animate-pulse rounded" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : activities.length === 0 ? (
|
||||
<div className="py-8 text-center">
|
||||
<AlertCircle className="text-muted-foreground mx-auto h-8 w-8" />
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
No recent activity
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{activities.map((activity) => (
|
||||
<div key={activity.id} className="flex items-center space-x-4">
|
||||
{getStatusIcon(activity.status)}
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm leading-none font-medium">
|
||||
{activity.title}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{activity.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{formatDistanceToNow(activity.time, { addSuffix: true })}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Quick Actions Component
|
||||
function QuickActions() {
|
||||
const actions = [
|
||||
{
|
||||
title: "Create Study",
|
||||
description: "Start a new research study",
|
||||
href: "/studies/new",
|
||||
icon: Building,
|
||||
color: "bg-blue-500 hover:bg-blue-600",
|
||||
},
|
||||
{
|
||||
title: "Browse Studies",
|
||||
description: "View and manage your studies",
|
||||
href: "/studies",
|
||||
icon: Building,
|
||||
color: "bg-green-500 hover:bg-green-600",
|
||||
},
|
||||
{
|
||||
title: "Create Experiment",
|
||||
description: "Design new experiment protocol",
|
||||
href: "/experiments/new",
|
||||
icon: FlaskConical,
|
||||
color: "bg-purple-500 hover:bg-purple-600",
|
||||
},
|
||||
{
|
||||
title: "Browse Experiments",
|
||||
description: "View experiment templates",
|
||||
href: "/experiments",
|
||||
icon: FlaskConical,
|
||||
color: "bg-orange-500 hover:bg-orange-600",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{actions.map((action) => (
|
||||
<Card
|
||||
key={action.title}
|
||||
className="group cursor-pointer transition-all hover:shadow-md"
|
||||
>
|
||||
<CardContent className="p-6">
|
||||
<Button asChild className={`w-full ${action.color} text-white`}>
|
||||
<Link href={action.href}>
|
||||
<action.icon className="mr-2 h-4 w-4" />
|
||||
{action.title}
|
||||
</Link>
|
||||
</Button>
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
{action.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Study Progress Component
|
||||
function StudyProgress() {
|
||||
const { data: studies = [], isLoading } =
|
||||
api.dashboard.getStudyProgress.useQuery({
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="col-span-3">
|
||||
<CardHeader>
|
||||
<CardTitle>Study Progress</CardTitle>
|
||||
<CardDescription>
|
||||
Current status of active research studies
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<div className="space-y-6">
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<div key={i} className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<div className="bg-muted h-4 w-32 animate-pulse rounded" />
|
||||
<div className="bg-muted h-3 w-24 animate-pulse rounded" />
|
||||
</div>
|
||||
<div className="bg-muted h-5 w-16 animate-pulse rounded" />
|
||||
</div>
|
||||
<div className="bg-muted h-2 w-full animate-pulse rounded" />
|
||||
<div className="bg-muted h-3 w-16 animate-pulse rounded" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : studies.length === 0 ? (
|
||||
<div className="py-8 text-center">
|
||||
<Building className="text-muted-foreground mx-auto h-8 w-8" />
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
No active studies found
|
||||
</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Create a study to get started
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{studies.map((study) => (
|
||||
<div key={study.id} className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm leading-none font-medium">
|
||||
{study.name}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{study.participants}/{study.totalParticipants} completed
|
||||
trials
|
||||
</p>
|
||||
</div>
|
||||
<Badge
|
||||
variant={
|
||||
study.status === "active" ? "default" : "secondary"
|
||||
}
|
||||
>
|
||||
{study.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<Progress value={study.progress} className="h-2" />
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{study.progress}% complete
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Dashboard</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Welcome to your HRI Studio research platform
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Calendar className="mr-1 h-3 w-3" />
|
||||
{new Date().toLocaleDateString()}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overview Cards */}
|
||||
<OverviewCards />
|
||||
|
||||
{/* Main Content Grid */}
|
||||
<div className="grid gap-4 lg:grid-cols-7">
|
||||
<StudyProgress />
|
||||
<div className="col-span-4 space-y-4">
|
||||
<RecentActivity />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold">Quick Actions</h2>
|
||||
<QuickActions />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user