Files
MOPC-Portal/src/app/(admin)/admin/projects/import/page.tsx

243 lines
8.1 KiB
TypeScript
Raw Normal View History

'use client'
import { Suspense, useState } from 'react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { trpc } from '@/lib/trpc/client'
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 { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Skeleton } from '@/components/ui/skeleton'
import { CSVImportForm } from '@/components/forms/csv-import-form'
import { NotionImportForm } from '@/components/forms/notion-import-form'
import { TypeformImportForm } from '@/components/forms/typeform-import-form'
import { ArrowLeft, FileSpreadsheet, AlertCircle, Database, FileText } from 'lucide-react'
function ImportPageContent() {
const router = useRouter()
const utils = trpc.useUtils()
const searchParams = useSearchParams()
const roundIdParam = searchParams.get('stage')
const [selectedRoundId, setSelectedRoundId] = useState<string>(roundIdParam || '')
// Fetch active programs with stages
const { data: programs, isLoading: loadingPrograms } = trpc.program.list.useQuery({
status: 'ACTIVE',
includeStages: true,
})
// Get all stages from programs
const stages = programs?.flatMap((p) =>
((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({
...s,
programId: p.id,
programName: `${p.year} Edition`,
}))
) || []
const selectedRound = stages.find((s: { id: string }) => s.id === selectedRoundId)
if (loadingPrograms) {
return <ImportPageSkeleton />
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-4">
<Button variant="ghost" asChild className="-ml-4">
<Link href="/admin/projects">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Projects
</Link>
</Button>
</div>
<div>
<h1 className="text-2xl font-semibold tracking-tight">Import Projects</h1>
<p className="text-muted-foreground">
Import projects from a CSV file into a stage
</p>
</div>
{/* Stage selection */}
{!selectedRoundId && (
<Card>
<CardHeader>
<CardTitle>Select Stage</CardTitle>
<CardDescription>
Choose the stage you want to import projects into
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{stages.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-center">
<AlertCircle className="h-12 w-12 text-muted-foreground/50" />
<p className="mt-2 font-medium">No Active Rounds</p>
<p className="text-sm text-muted-foreground">
Create a competition with rounds before importing projects
</p>
<Button asChild className="mt-4">
<Link href="/admin/rounds">View Rounds</Link>
</Button>
</div>
) : (
<>
<Select value={selectedRoundId} onValueChange={setSelectedRoundId}>
<SelectTrigger>
<SelectValue placeholder="Select a stage" />
</SelectTrigger>
<SelectContent>
{stages.map((stage) => (
<SelectItem key={stage.id} value={stage.id}>
<div className="flex flex-col">
<span>{stage.name}</span>
<span className="text-xs text-muted-foreground">
{stage.programName}
</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
<Button
onClick={() => {
if (selectedRoundId) {
router.push(`/admin/projects/import?stage=${selectedRoundId}`)
}
}}
disabled={!selectedRoundId}
>
Continue
</Button>
</>
)}
</CardContent>
</Card>
)}
{/* Import form */}
{selectedRoundId && selectedRound && (
<div className="space-y-4">
<div className="flex items-center gap-4">
<FileSpreadsheet className="h-8 w-8 text-muted-foreground" />
<div>
<p className="font-medium">Importing into: {selectedRound.name}</p>
<p className="text-sm text-muted-foreground">
{selectedRound.programName}
</p>
</div>
<Button
variant="outline"
size="sm"
className="ml-auto"
onClick={() => {
setSelectedRoundId('')
router.push('/admin/projects/import')
}}
>
Change Stage
</Button>
</div>
<Tabs defaultValue="csv" className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="csv" className="flex items-center gap-2">
<FileSpreadsheet className="h-4 w-4" />
CSV
</TabsTrigger>
<TabsTrigger value="notion" className="flex items-center gap-2">
<Database className="h-4 w-4" />
Notion
</TabsTrigger>
<TabsTrigger value="typeform" className="flex items-center gap-2">
<FileText className="h-4 w-4" />
Typeform
</TabsTrigger>
</TabsList>
<TabsContent value="csv" className="mt-4">
<CSVImportForm
programId={selectedRound.programId}
stageName={selectedRound.name}
onSuccess={() => {
utils.project.list.invalidate()
utils.program.get.invalidate()
}}
/>
</TabsContent>
<TabsContent value="notion" className="mt-4">
<NotionImportForm
programId={selectedRound.programId}
stageName={selectedRound.name}
onSuccess={() => {
utils.project.list.invalidate()
utils.program.get.invalidate()
}}
/>
</TabsContent>
<TabsContent value="typeform" className="mt-4">
<TypeformImportForm
programId={selectedRound.programId}
stageName={selectedRound.name}
onSuccess={() => {
utils.project.list.invalidate()
utils.program.get.invalidate()
}}
/>
</TabsContent>
</Tabs>
</div>
)}
</div>
)
}
function ImportPageSkeleton() {
return (
<div className="space-y-6">
<div className="flex items-center gap-4">
<Skeleton className="h-9 w-36" />
</div>
<div>
<Skeleton className="h-8 w-48" />
<Skeleton className="mt-2 h-4 w-64" />
</div>
<Card>
<CardHeader>
<Skeleton className="h-5 w-32" />
<Skeleton className="h-4 w-64" />
</CardHeader>
<CardContent className="space-y-4">
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-24" />
</CardContent>
</Card>
</div>
)
}
export default function ImportProjectsPage() {
return (
<Suspense fallback={<ImportPageSkeleton />}>
<ImportPageContent />
</Suspense>
)
}