Improve projects UX, settings layout, uppercase names, per-page selector, and fix round deletion
- Fix round deletion FK constraint: add onDelete Cascade on Evaluation.form and SetNull on ProjectFile.round - Add configurable per-page selector (10/20/50/100) to Pagination component, wired in projects page with URL sync - Add display_project_names_uppercase setting in admin defaults, applied to project titles across desktop/mobile views - Redesign admin settings page: vertical sidebar nav on desktop with grouped sections, horizontal scrollable tabs on mobile - Polish projects page: responsive header with total count, search clear button with result count, status stats bar, submission date column, country display, mobile card file count Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
|
||||
interface PaginationProps {
|
||||
@@ -9,6 +16,7 @@ interface PaginationProps {
|
||||
total: number
|
||||
perPage: number
|
||||
onPageChange: (page: number) => void
|
||||
onPerPageChange?: (perPage: number) => void
|
||||
}
|
||||
|
||||
export function Pagination({
|
||||
@@ -17,40 +25,62 @@ export function Pagination({
|
||||
total,
|
||||
perPage,
|
||||
onPageChange,
|
||||
onPerPageChange,
|
||||
}: PaginationProps) {
|
||||
if (totalPages <= 1) return null
|
||||
if (totalPages <= 1 && !onPerPageChange) return null
|
||||
|
||||
const from = (page - 1) * perPage + 1
|
||||
const to = Math.min(page * perPage, total)
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Showing {from} to {to} of {total} results
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
disabled={page === 1}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
Previous
|
||||
</Button>
|
||||
<span className="text-sm">
|
||||
Page {page} of {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
disabled={page >= totalPages}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Showing {from} to {to} of {total} results
|
||||
</p>
|
||||
{onPerPageChange && (
|
||||
<Select
|
||||
value={String(perPage)}
|
||||
onValueChange={(v) => onPerPageChange(Number(v))}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-[70px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{[10, 20, 50, 100].map((n) => (
|
||||
<SelectItem key={n} value={String(n)}>
|
||||
{n}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
disabled={page === 1}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
Previous
|
||||
</Button>
|
||||
<span className="text-sm">
|
||||
Page {page} of {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
disabled={page >= totalPages}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user