Files
MOPC-Portal/src/app/(admin)/admin/dashboard-content.tsx

194 lines
6.0 KiB
TypeScript
Raw Normal View History

'use client'
import Link from 'next/link'
import { trpc } from '@/lib/trpc/client'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import {
CircleDot,
AlertTriangle,
Upload,
UserPlus,
} from 'lucide-react'
import { GeographicSummaryCard } from '@/components/charts'
import { AnimatedCard } from '@/components/shared/animated-container'
import { motion } from 'motion/react'
import { CompetitionPipeline } from '@/components/dashboard/competition-pipeline'
import { RoundStats } from '@/components/dashboard/round-stats'
import { ActiveRoundPanel } from '@/components/dashboard/active-round-panel'
import { SmartActions } from '@/components/dashboard/smart-actions'
import { ProjectListCompact } from '@/components/dashboard/project-list-compact'
import { ActivityFeed } from '@/components/dashboard/activity-feed'
import { CategoryBreakdown } from '@/components/dashboard/category-breakdown'
import { DashboardSkeleton } from '@/components/dashboard/dashboard-skeleton'
type DashboardContentProps = {
editionId: string
sessionName: string
}
export function DashboardContent({ editionId, sessionName }: DashboardContentProps) {
const { data, isLoading, error } = trpc.dashboard.getStats.useQuery(
{ editionId },
{ enabled: !!editionId, retry: 1, refetchInterval: 30_000 }
)
if (isLoading) {
return <DashboardSkeleton />
}
if (error) {
return (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
<AlertTriangle className="h-12 w-12 text-destructive/50" />
<p className="mt-2 font-medium">Failed to load dashboard</p>
<p className="text-sm text-muted-foreground">
{error.message || 'An unexpected error occurred. Please try refreshing the page.'}
</p>
</CardContent>
</Card>
)
}
if (!data) {
return (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
<CircleDot className="h-12 w-12 text-muted-foreground/50" />
<p className="mt-2 font-medium">Edition not found</p>
<p className="text-sm text-muted-foreground">
The selected edition could not be found
</p>
</CardContent>
</Card>
)
}
const {
edition,
pipelineRounds,
activeRoundId,
nextActions,
projectCount,
newProjectsThisWeek,
totalJurors,
activeJurors,
evaluationStats,
totalAssignments,
latestProjects,
categoryBreakdown,
oceanIssueBreakdown,
recentActivity,
} = data
const activeRound = activeRoundId
? pipelineRounds.find((r) => r.id === activeRoundId) ?? null
: null
return (
<>
{/* Page Header */}
<motion.div
initial={{ opacity: 0, y: -6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
>
<div>
<h1 className="text-xl font-bold tracking-tight md:text-2xl">
{edition.name} {edition.year}
</h1>
<p className="text-sm text-muted-foreground">
Welcome back, {sessionName}
</p>
</div>
<div className="flex gap-2">
<Link href="/admin/rounds">
<Button size="sm" variant="outline">
<CircleDot className="mr-1.5 h-3.5 w-3.5" />
Rounds
</Button>
</Link>
<Link href="/admin/projects/new">
<Button size="sm" variant="outline">
<Upload className="mr-1.5 h-3.5 w-3.5" />
Import
</Button>
</Link>
<Link href="/admin/members">
<Button size="sm" variant="outline">
<UserPlus className="mr-1.5 h-3.5 w-3.5" />
Invite
</Button>
</Link>
</div>
</motion.div>
{/* Competition Pipeline */}
<AnimatedCard index={0}>
<CompetitionPipeline rounds={pipelineRounds} />
</AnimatedCard>
{/* Round-Specific Stats */}
<AnimatedCard index={1}>
<RoundStats
activeRound={activeRound}
projectCount={projectCount}
newProjectsThisWeek={newProjectsThisWeek}
totalJurors={totalJurors}
activeJurors={activeJurors}
totalAssignments={totalAssignments}
evaluationStats={evaluationStats}
actionsCount={nextActions.length}
/>
</AnimatedCard>
{/* Two-Column Layout */}
<div className="grid gap-6 lg:grid-cols-12">
{/* Left Column */}
<div className="space-y-6 lg:col-span-8">
{activeRound && (
<AnimatedCard index={2}>
<ActiveRoundPanel round={activeRound} />
</AnimatedCard>
)}
<AnimatedCard index={3}>
<ProjectListCompact projects={latestProjects} />
</AnimatedCard>
</div>
{/* Right Column */}
<div className="space-y-6 lg:col-span-4">
<AnimatedCard index={4}>
<SmartActions actions={nextActions} />
</AnimatedCard>
<AnimatedCard index={5}>
<ActivityFeed activity={recentActivity} />
</AnimatedCard>
</div>
</div>
{/* Bottom Full Width */}
<div className="grid gap-6 lg:grid-cols-12">
<div className="lg:col-span-8">
<AnimatedCard index={6}>
<GeographicSummaryCard programId={editionId} />
</AnimatedCard>
</div>
<div className="lg:col-span-4">
<AnimatedCard index={7}>
<CategoryBreakdown
categories={categoryBreakdown}
issues={oceanIssueBreakdown}
/>
</AnimatedCard>
</div>
</div>
</>
)
}