UI simplification: remove redundant pages, reorganize Quick Actions
Phase 2 UI Cleanup: - Delete empty programs/[id]/settings page - Delete redundant projects/[id]/assignments page (use round-level management) - Delete unused /users redirect directory - Remove Settings button from program detail page - Reorganize Round Quick Actions into visual groups (Project Management, Round Management) Note: Mentor inline assignment deferred - the existing mentor page has complex AI suggestion functionality that would need careful refactoring. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d068d9b6f6
commit
406ec46c81
|
|
@ -18,7 +18,7 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table'
|
} from '@/components/ui/table'
|
||||||
import { ArrowLeft, Pencil, Plus, Settings } from 'lucide-react'
|
import { ArrowLeft, Pencil, Plus } from 'lucide-react'
|
||||||
import { formatDateOnly } from '@/lib/utils'
|
import { formatDateOnly } from '@/lib/utils'
|
||||||
|
|
||||||
interface ProgramDetailPageProps {
|
interface ProgramDetailPageProps {
|
||||||
|
|
@ -64,20 +64,12 @@ export default async function ProgramDetailPage({ params }: ProgramDetailPagePro
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<Button variant="outline" asChild>
|
||||||
<Button variant="outline" asChild>
|
<Link href={`/admin/programs/${id}/edit`}>
|
||||||
<Link href={`/admin/programs/${id}/edit`}>
|
<Pencil className="mr-2 h-4 w-4" />
|
||||||
<Pencil className="mr-2 h-4 w-4" />
|
Edit
|
||||||
Edit
|
</Link>
|
||||||
</Link>
|
</Button>
|
||||||
</Button>
|
|
||||||
<Button variant="outline" asChild>
|
|
||||||
<Link href={`/admin/programs/${id}/settings`}>
|
|
||||||
<Settings className="mr-2 h-4 w-4" />
|
|
||||||
Settings
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{program.description && (
|
{program.description && (
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { useParams } from 'next/navigation'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { trpc } from '@/lib/trpc/client'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card'
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
|
||||||
import { ArrowLeft } from 'lucide-react'
|
|
||||||
|
|
||||||
export default function ProgramSettingsPage() {
|
|
||||||
const params = useParams()
|
|
||||||
const id = params.id as string
|
|
||||||
|
|
||||||
const { data: program, isLoading } = trpc.program.get.useQuery({ id })
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Skeleton className="h-10 w-10" />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Skeleton className="h-8 w-48" />
|
|
||||||
<Skeleton className="h-4 w-32" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Card>
|
|
||||||
<CardContent className="space-y-4 pt-6">
|
|
||||||
<Skeleton className="h-10 w-full" />
|
|
||||||
<Skeleton className="h-10 w-full" />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link href={`/admin/programs/${id}`}>
|
|
||||||
<Button variant="ghost" size="icon">
|
|
||||||
<ArrowLeft className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold">Program Settings</h1>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Configure settings for {program?.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Program Configuration</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Advanced settings for this program
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="py-8 text-center text-muted-foreground">
|
|
||||||
Program-specific settings will be available in a future update.
|
|
||||||
<br />
|
|
||||||
For now, manage rounds and projects through the program detail page.
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<Button asChild>
|
|
||||||
<Link href={`/admin/programs/${id}`}>
|
|
||||||
Back to Program
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { useParams } from 'next/navigation'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { trpc } from '@/lib/trpc/client'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card'
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table'
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
|
||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
} from '@/components/ui/alert-dialog'
|
|
||||||
import { ArrowLeft, Plus, UserMinus } from 'lucide-react'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
export default function ProjectAssignmentsPage() {
|
|
||||||
const params = useParams()
|
|
||||||
const id = params.id as string
|
|
||||||
|
|
||||||
const { data: project, isLoading: projectLoading } = trpc.project.get.useQuery({ id })
|
|
||||||
const { data: assignments = [], isLoading: assignmentsLoading } = trpc.assignment.listByProject.useQuery({ projectId: id })
|
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
|
||||||
|
|
||||||
const removeAssignment = trpc.assignment.delete.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success('Assignment removed')
|
|
||||||
utils.assignment.listByProject.invalidate({ projectId: id })
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(error.message || 'Failed to remove assignment')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Remove handled via AlertDialog in JSX
|
|
||||||
|
|
||||||
const isLoading = projectLoading || assignmentsLoading
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Skeleton className="h-10 w-10" />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Skeleton className="h-8 w-64" />
|
|
||||||
<Skeleton className="h-4 w-32" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Card>
|
|
||||||
<CardContent className="space-y-4 pt-6">
|
|
||||||
<Skeleton className="h-10 w-full" />
|
|
||||||
<Skeleton className="h-10 w-full" />
|
|
||||||
<Skeleton className="h-10 w-full" />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link href={`/admin/projects/${id}`}>
|
|
||||||
<Button variant="ghost" size="icon">
|
|
||||||
<ArrowLeft className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold">Jury Assignments</h1>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
{project?.title}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button asChild>
|
|
||||||
<Link href={`/admin/rounds/${project?.roundProjects?.[0]?.round?.id}/assignments`}>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
Manage in Round
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Assigned Jury Members</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{assignments.length} jury member{assignments.length !== 1 ? 's' : ''} assigned to evaluate this project
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{assignments.length === 0 ? (
|
|
||||||
<div className="py-8 text-center text-muted-foreground">
|
|
||||||
No jury members assigned yet.
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Jury Member</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{assignments.map((assignment) => (
|
|
||||||
<TableRow key={assignment.id}>
|
|
||||||
<TableCell>
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">{assignment.user.name}</p>
|
|
||||||
<p className="text-sm text-muted-foreground">{assignment.user.email}</p>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge variant={assignment.evaluation?.status === 'SUBMITTED' ? 'success' : 'secondary'}>
|
|
||||||
{assignment.evaluation?.status || 'Pending'}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-right">
|
|
||||||
<AlertDialog>
|
|
||||||
<AlertDialogTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
disabled={removeAssignment.isPending}
|
|
||||||
>
|
|
||||||
<UserMinus className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</AlertDialogTrigger>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>Remove Assignment</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
Remove this jury member from the project? Their evaluation data will also be deleted.
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
||||||
<AlertDialogAction onClick={() => removeAssignment.mutate({ id: assignment.id })}>
|
|
||||||
Remove
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -602,46 +602,57 @@ function RoundDetailContent({ roundId }: { roundId: string }) {
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg">Quick Actions</CardTitle>
|
<CardTitle className="text-lg">Quick Actions</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="space-y-4">
|
||||||
<div className="flex flex-wrap gap-3">
|
{/* Project Management */}
|
||||||
<Button variant="outline" asChild>
|
<div>
|
||||||
<Link href={`/admin/projects/import?round=${round.id}`}>
|
<p className="text-sm font-medium text-muted-foreground mb-2">Project Management</p>
|
||||||
<Upload className="mr-2 h-4 w-4" />
|
<div className="flex flex-wrap gap-2">
|
||||||
Import Projects
|
<Button variant="outline" size="sm" asChild>
|
||||||
</Link>
|
<Link href={`/admin/projects/import?round=${round.id}`}>
|
||||||
</Button>
|
<Upload className="mr-2 h-4 w-4" />
|
||||||
{!isFilteringRound && (
|
Import
|
||||||
<Button variant="outline" asChild>
|
|
||||||
<Link href={`/admin/rounds/${round.id}/filtering`}>
|
|
||||||
<Filter className="mr-2 h-4 w-4" />
|
|
||||||
Manage Filtering
|
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
<Button variant="outline" size="sm" onClick={() => setAssignOpen(true)}>
|
||||||
<Button variant="outline" asChild>
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
<Link href={`/admin/rounds/${round.id}/assignments`}>
|
Assign
|
||||||
<Users className="mr-2 h-4 w-4" />
|
</Button>
|
||||||
Manage Assignments
|
<Button variant="outline" size="sm" onClick={() => setAdvanceOpen(true)}>
|
||||||
</Link>
|
<ArrowRightCircle className="mr-2 h-4 w-4" />
|
||||||
</Button>
|
Advance
|
||||||
<Button variant="outline" asChild>
|
</Button>
|
||||||
<Link href={`/admin/projects?round=${round.id}`}>
|
<Button variant="outline" size="sm" onClick={() => setRemoveOpen(true)}>
|
||||||
<FileText className="mr-2 h-4 w-4" />
|
<Minus className="mr-2 h-4 w-4" />
|
||||||
View Projects
|
Remove
|
||||||
</Link>
|
</Button>
|
||||||
</Button>
|
<Button variant="outline" size="sm" asChild>
|
||||||
<Button variant="outline" onClick={() => setAssignOpen(true)}>
|
<Link href={`/admin/projects?round=${round.id}`}>
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<FileText className="mr-2 h-4 w-4" />
|
||||||
Assign Projects
|
View All
|
||||||
</Button>
|
</Link>
|
||||||
<Button variant="outline" onClick={() => setAdvanceOpen(true)}>
|
</Button>
|
||||||
<ArrowRightCircle className="mr-2 h-4 w-4" />
|
</div>
|
||||||
Advance Projects
|
</div>
|
||||||
</Button>
|
|
||||||
<Button variant="outline" onClick={() => setRemoveOpen(true)}>
|
{/* Round Management */}
|
||||||
<Minus className="mr-2 h-4 w-4" />
|
<div>
|
||||||
Remove Projects
|
<p className="text-sm font-medium text-muted-foreground mb-2">Round Management</p>
|
||||||
</Button>
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Button variant="outline" size="sm" asChild>
|
||||||
|
<Link href={`/admin/rounds/${round.id}/assignments`}>
|
||||||
|
<Users className="mr-2 h-4 w-4" />
|
||||||
|
Jury Assignments
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
{!isFilteringRound && (
|
||||||
|
<Button variant="outline" size="sm" asChild>
|
||||||
|
<Link href={`/admin/rounds/${round.id}/filtering`}>
|
||||||
|
<Filter className="mr-2 h-4 w-4" />
|
||||||
|
Filtering
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { redirect } from 'next/navigation'
|
|
||||||
|
|
||||||
export default async function UserEditPage({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ id: string }>
|
|
||||||
}) {
|
|
||||||
const { id } = await params
|
|
||||||
redirect(`/admin/members/${id}`)
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { redirect } from 'next/navigation'
|
|
||||||
|
|
||||||
export default function UserInvitePage() {
|
|
||||||
redirect('/admin/members/invite')
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { redirect } from 'next/navigation'
|
|
||||||
|
|
||||||
export default function UsersPage() {
|
|
||||||
redirect('/admin/members')
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue