115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
|
|
'use client'
|
||
|
|
|
||
|
|
import Link from 'next/link'
|
||
|
|
import { motion } from 'motion/react'
|
||
|
|
import { ClipboardList, ArrowRight } from 'lucide-react'
|
||
|
|
import {
|
||
|
|
Card,
|
||
|
|
CardContent,
|
||
|
|
CardDescription,
|
||
|
|
CardHeader,
|
||
|
|
CardTitle,
|
||
|
|
} from '@/components/ui/card'
|
||
|
|
import { StatusBadge } from '@/components/shared/status-badge'
|
||
|
|
import { ProjectLogo } from '@/components/shared/project-logo'
|
||
|
|
import { getCountryName } from '@/lib/countries'
|
||
|
|
import { formatDateOnly, truncate } from '@/lib/utils'
|
||
|
|
|
||
|
|
type ProjectListCompactProps = {
|
||
|
|
projects: Array<{
|
||
|
|
id: string
|
||
|
|
title: string
|
||
|
|
teamName: string | null
|
||
|
|
country: string | null
|
||
|
|
competitionCategory: string | null
|
||
|
|
oceanIssue: string | null
|
||
|
|
logoKey: string | null
|
||
|
|
createdAt: Date
|
||
|
|
submittedAt: Date | null
|
||
|
|
status: string
|
||
|
|
}>
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ProjectListCompact({ projects }: ProjectListCompactProps) {
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader className="pb-3">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div className="flex items-center gap-2.5">
|
||
|
|
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-brand-blue/10">
|
||
|
|
<ClipboardList className="h-4 w-4 text-brand-blue" />
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<CardTitle className="text-base">Recent Projects</CardTitle>
|
||
|
|
<CardDescription className="text-xs">Latest submissions</CardDescription>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<Link
|
||
|
|
href="/admin/projects"
|
||
|
|
className="flex items-center gap-1 text-xs font-semibold uppercase tracking-wider text-brand-teal hover:text-brand-teal-light transition-colors"
|
||
|
|
>
|
||
|
|
All projects <ArrowRight className="h-3 w-3" />
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
{projects.length === 0 ? (
|
||
|
|
<div className="flex flex-col items-center justify-center py-10 text-center">
|
||
|
|
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-muted">
|
||
|
|
<ClipboardList className="h-7 w-7 text-muted-foreground/40" />
|
||
|
|
</div>
|
||
|
|
<p className="mt-3 text-sm font-medium text-muted-foreground">
|
||
|
|
No projects submitted yet
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<div className="divide-y">
|
||
|
|
{projects.map((project, idx) => (
|
||
|
|
<motion.div
|
||
|
|
key={project.id}
|
||
|
|
initial={{ opacity: 0 }}
|
||
|
|
animate={{ opacity: 1 }}
|
||
|
|
transition={{ duration: 0.25, delay: 0.15 + idx * 0.04 }}
|
||
|
|
>
|
||
|
|
<Link
|
||
|
|
href={`/admin/projects/${project.id}`}
|
||
|
|
className="block"
|
||
|
|
>
|
||
|
|
<div className="flex items-center gap-3 py-3 px-1 transition-colors hover:bg-muted/40 rounded-lg group">
|
||
|
|
<ProjectLogo
|
||
|
|
project={project}
|
||
|
|
size="sm"
|
||
|
|
fallback="initials"
|
||
|
|
/>
|
||
|
|
<div className="flex-1 min-w-0">
|
||
|
|
<div className="flex items-center justify-between gap-2">
|
||
|
|
<p className="text-sm font-medium truncate group-hover:text-brand-blue transition-colors">
|
||
|
|
{truncate(project.title, 50)}
|
||
|
|
</p>
|
||
|
|
<StatusBadge
|
||
|
|
status={project.status ?? 'SUBMITTED'}
|
||
|
|
size="sm"
|
||
|
|
className="shrink-0"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||
|
|
{[
|
||
|
|
project.teamName,
|
||
|
|
project.country ? getCountryName(project.country) : null,
|
||
|
|
formatDateOnly(project.submittedAt || project.createdAt),
|
||
|
|
]
|
||
|
|
.filter(Boolean)
|
||
|
|
.join(' \u00b7 ')}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</Link>
|
||
|
|
</motion.div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)
|
||
|
|
}
|