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:
2026-02-10 20:13:47 +01:00
parent 829acf8d4e
commit 5c4200158f
5 changed files with 323 additions and 97 deletions

View File

@@ -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>
)
}