'use client' import { useState } from 'react' import { trpc } from '@/lib/trpc/client' import { toast } from 'sonner' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog' import { Plus, Lock, Unlock, LockKeyhole, Loader2, Pencil, Trash2 } from 'lucide-react' import { cn } from '@/lib/utils' import { format } from 'date-fns' type SubmissionWindowManagerProps = { competitionId: string roundId: string } export function SubmissionWindowManager({ competitionId, roundId }: SubmissionWindowManagerProps) { const [isCreateOpen, setIsCreateOpen] = useState(false) const [editingWindow, setEditingWindow] = useState(null) const [deletingWindow, setDeletingWindow] = useState(null) // Create form state const [createForm, setCreateForm] = useState({ name: '', slug: '', roundNumber: 1, windowOpenAt: '', windowCloseAt: '', deadlinePolicy: 'HARD_DEADLINE' as 'HARD_DEADLINE' | 'FLAG' | 'GRACE', graceHours: 0, lockOnClose: true, }) // Edit form state const [editForm, setEditForm] = useState({ name: '', slug: '', roundNumber: 1, windowOpenAt: '', windowCloseAt: '', deadlinePolicy: 'HARD_DEADLINE' as 'HARD_DEADLINE' | 'FLAG' | 'GRACE', graceHours: 0, lockOnClose: true, sortOrder: 1, }) const utils = trpc.useUtils() const { data: competition, isLoading } = trpc.competition.getById.useQuery({ id: competitionId, }) const createWindowMutation = trpc.round.createSubmissionWindow.useMutation({ onSuccess: () => { utils.competition.getById.invalidate({ id: competitionId }) toast.success('Submission window created') setIsCreateOpen(false) // Reset form setCreateForm({ name: '', slug: '', roundNumber: 1, windowOpenAt: '', windowCloseAt: '', deadlinePolicy: 'HARD_DEADLINE', graceHours: 0, lockOnClose: true, }) }, onError: (err) => toast.error(err.message), }) const updateWindowMutation = trpc.round.updateSubmissionWindow.useMutation({ onSuccess: () => { utils.competition.getById.invalidate({ id: competitionId }) toast.success('Submission window updated') setEditingWindow(null) }, onError: (err) => toast.error(err.message), }) const deleteWindowMutation = trpc.round.deleteSubmissionWindow.useMutation({ onSuccess: () => { utils.competition.getById.invalidate({ id: competitionId }) toast.success('Submission window deleted') setDeletingWindow(null) }, onError: (err) => toast.error(err.message), }) const openWindowMutation = trpc.round.openSubmissionWindow.useMutation({ onSuccess: () => { utils.competition.getById.invalidate({ id: competitionId }) toast.success('Window opened') }, onError: (err) => toast.error(err.message), }) const closeWindowMutation = trpc.round.closeSubmissionWindow.useMutation({ onSuccess: () => { utils.competition.getById.invalidate({ id: competitionId }) toast.success('Window closed') }, onError: (err) => toast.error(err.message), }) const lockWindowMutation = trpc.round.lockSubmissionWindow.useMutation({ onSuccess: () => { utils.competition.getById.invalidate({ id: competitionId }) toast.success('Window locked') }, onError: (err) => toast.error(err.message), }) const handleCreateNameChange = (value: string) => { const autoSlug = value.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '') setCreateForm({ ...createForm, name: value, slug: autoSlug }) } const handleEditNameChange = (value: string) => { const autoSlug = value.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '') setEditForm({ ...editForm, name: value, slug: autoSlug }) } const handleCreate = () => { if (!createForm.name || !createForm.slug) { toast.error('Name and slug are required') return } createWindowMutation.mutate({ competitionId, name: createForm.name, slug: createForm.slug, roundNumber: createForm.roundNumber, windowOpenAt: createForm.windowOpenAt ? new Date(createForm.windowOpenAt) : undefined, windowCloseAt: createForm.windowCloseAt ? new Date(createForm.windowCloseAt) : undefined, deadlinePolicy: createForm.deadlinePolicy, graceHours: createForm.deadlinePolicy === 'GRACE' ? createForm.graceHours : undefined, lockOnClose: createForm.lockOnClose, }) } const handleEdit = () => { if (!editingWindow) return if (!editForm.name || !editForm.slug) { toast.error('Name and slug are required') return } updateWindowMutation.mutate({ id: editingWindow, name: editForm.name, slug: editForm.slug, roundNumber: editForm.roundNumber, windowOpenAt: editForm.windowOpenAt ? new Date(editForm.windowOpenAt) : null, windowCloseAt: editForm.windowCloseAt ? new Date(editForm.windowCloseAt) : null, deadlinePolicy: editForm.deadlinePolicy, graceHours: editForm.deadlinePolicy === 'GRACE' ? editForm.graceHours : null, lockOnClose: editForm.lockOnClose, sortOrder: editForm.sortOrder, }) } const handleDelete = () => { if (!deletingWindow) return deleteWindowMutation.mutate({ id: deletingWindow }) } const openEditDialog = (window: any) => { setEditForm({ name: window.name, slug: window.slug, roundNumber: window.roundNumber, windowOpenAt: window.windowOpenAt ? new Date(window.windowOpenAt).toISOString().slice(0, 16) : '', windowCloseAt: window.windowCloseAt ? new Date(window.windowCloseAt).toISOString().slice(0, 16) : '', deadlinePolicy: window.deadlinePolicy ?? 'HARD_DEADLINE', graceHours: window.graceHours ?? 0, lockOnClose: window.lockOnClose ?? true, sortOrder: window.sortOrder ?? 1, }) setEditingWindow(window.id) } const formatDate = (date: Date | null | undefined) => { if (!date) return 'Not set' return format(new Date(date), 'MMM d, yyyy h:mm a') } const windows = competition?.submissionWindows ?? [] return (
Submission Windows

File upload windows for this round

Create Submission Window
handleCreateNameChange(e.target.value)} />
setCreateForm({ ...createForm, slug: e.target.value })} />
setCreateForm({ ...createForm, roundNumber: parseInt(e.target.value, 10) || 1 })} />
setCreateForm({ ...createForm, windowOpenAt: e.target.value })} />
setCreateForm({ ...createForm, windowCloseAt: e.target.value })} />
{createForm.deadlinePolicy === 'GRACE' && (
setCreateForm({ ...createForm, graceHours: parseInt(e.target.value, 10) || 0 })} />
)}
setCreateForm({ ...createForm, lockOnClose: checked })} />
{isLoading ? (
Loading windows...
) : windows.length === 0 ? (
No submission windows yet. Create one to enable file uploads.
) : (
{windows.map((window) => { const isPending = !window.windowOpenAt const isOpen = window.windowOpenAt && !window.windowCloseAt const isClosed = window.windowCloseAt && !window.isLocked const isLocked = window.isLocked return (

{window.name}

{isPending && ( Pending )} {isOpen && ( Open )} {isClosed && ( Closed )} {isLocked && ( Locked )}

{window.slug}

Round {window.roundNumber} {window._count.fileRequirements} requirements {window._count.projectFiles} files
Open: {formatDate(window.windowOpenAt)} Close: {formatDate(window.windowCloseAt)}
{isPending && ( )} {isOpen && ( )} {isClosed && ( )}
) })}
)}
{/* Edit Dialog */} !open && setEditingWindow(null)}> Edit Submission Window
handleEditNameChange(e.target.value)} />
setEditForm({ ...editForm, slug: e.target.value })} />
setEditForm({ ...editForm, roundNumber: parseInt(e.target.value, 10) || 1 })} />
setEditForm({ ...editForm, windowOpenAt: e.target.value })} />
setEditForm({ ...editForm, windowCloseAt: e.target.value })} />
{editForm.deadlinePolicy === 'GRACE' && (
setEditForm({ ...editForm, graceHours: parseInt(e.target.value, 10) || 0 })} />
)}
setEditForm({ ...editForm, lockOnClose: checked })} />
setEditForm({ ...editForm, sortOrder: parseInt(e.target.value, 10) || 1 })} />
{/* Delete Confirmation Dialog */} !open && setDeletingWindow(null)}> Delete Submission Window Are you sure you want to delete this submission window? This action cannot be undone. {(windows.find(w => w.id === deletingWindow)?._count?.projectFiles ?? 0) > 0 && ( Warning: This window has uploaded files and cannot be deleted until they are removed. )}
) }