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

@@ -116,6 +116,7 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
'default_timezone',
'default_page_size',
'autosave_interval_seconds',
'display_project_names_uppercase',
])
const digestSettings = getSettingsByKeys([
@@ -152,58 +153,142 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
return (
<>
<Tabs defaultValue="ai" className="space-y-6">
<TabsList className="flex flex-wrap h-auto gap-1">
<TabsTrigger value="ai" className="gap-2">
<Bot className="h-4 w-4" />
<span className="hidden sm:inline">AI</span>
</TabsTrigger>
<TabsTrigger value="tags" className="gap-2">
<Tags className="h-4 w-4" />
<span className="hidden sm:inline">Tags</span>
</TabsTrigger>
<TabsTrigger value="branding" className="gap-2">
<Palette className="h-4 w-4" />
<span className="hidden sm:inline">Branding</span>
</TabsTrigger>
<TabsTrigger value="email" className="gap-2">
<Mail className="h-4 w-4" />
<span className="hidden sm:inline">Email</span>
</TabsTrigger>
<TabsTrigger value="notifications" className="gap-2">
<Bell className="h-4 w-4" />
<span className="hidden sm:inline">Notifications</span>
</TabsTrigger>
<TabsTrigger value="storage" className="gap-2">
<HardDrive className="h-4 w-4" />
<span className="hidden sm:inline">Storage</span>
</TabsTrigger>
<TabsTrigger value="security" className="gap-2">
<Shield className="h-4 w-4" />
<span className="hidden sm:inline">Security</span>
</TabsTrigger>
<TabsTrigger value="defaults" className="gap-2">
<Tabs defaultValue="defaults" className="space-y-6">
{/* Mobile: horizontal scrollable tabs */}
<TabsList className="flex h-auto gap-1 overflow-x-auto whitespace-nowrap lg:hidden">
<TabsTrigger value="defaults" className="gap-2 shrink-0">
<SettingsIcon className="h-4 w-4" />
<span className="hidden sm:inline">Defaults</span>
Defaults
</TabsTrigger>
<TabsTrigger value="digest" className="gap-2">
<Newspaper className="h-4 w-4" />
<span className="hidden sm:inline">Digest</span>
<TabsTrigger value="branding" className="gap-2 shrink-0">
<Palette className="h-4 w-4" />
Branding
</TabsTrigger>
<TabsTrigger value="analytics" className="gap-2">
<BarChart3 className="h-4 w-4" />
<span className="hidden sm:inline">Analytics</span>
</TabsTrigger>
<TabsTrigger value="audit" className="gap-2">
<ShieldAlert className="h-4 w-4" />
<span className="hidden sm:inline">Audit</span>
</TabsTrigger>
<TabsTrigger value="localization" className="gap-2">
<TabsTrigger value="localization" className="gap-2 shrink-0">
<Globe className="h-4 w-4" />
<span className="hidden sm:inline">Locale</span>
Locale
</TabsTrigger>
<TabsTrigger value="email" className="gap-2 shrink-0">
<Mail className="h-4 w-4" />
Email
</TabsTrigger>
<TabsTrigger value="notifications" className="gap-2 shrink-0">
<Bell className="h-4 w-4" />
Notif.
</TabsTrigger>
<TabsTrigger value="digest" className="gap-2 shrink-0">
<Newspaper className="h-4 w-4" />
Digest
</TabsTrigger>
<TabsTrigger value="security" className="gap-2 shrink-0">
<Shield className="h-4 w-4" />
Security
</TabsTrigger>
<TabsTrigger value="audit" className="gap-2 shrink-0">
<ShieldAlert className="h-4 w-4" />
Audit
</TabsTrigger>
<TabsTrigger value="ai" className="gap-2 shrink-0">
<Bot className="h-4 w-4" />
AI
</TabsTrigger>
<TabsTrigger value="tags" className="gap-2 shrink-0">
<Tags className="h-4 w-4" />
Tags
</TabsTrigger>
<TabsTrigger value="analytics" className="gap-2 shrink-0">
<BarChart3 className="h-4 w-4" />
Analytics
</TabsTrigger>
<TabsTrigger value="storage" className="gap-2 shrink-0">
<HardDrive className="h-4 w-4" />
Storage
</TabsTrigger>
</TabsList>
<div className="lg:flex lg:gap-8">
{/* Desktop: sidebar navigation */}
<div className="hidden lg:block lg:w-56 lg:shrink-0">
<nav className="space-y-6">
<div>
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">General</p>
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
<TabsTrigger value="defaults" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<SettingsIcon className="h-4 w-4" />
Defaults
</TabsTrigger>
<TabsTrigger value="branding" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Palette className="h-4 w-4" />
Branding
</TabsTrigger>
<TabsTrigger value="localization" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Globe className="h-4 w-4" />
Locale
</TabsTrigger>
</TabsList>
</div>
<div>
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Communication</p>
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
<TabsTrigger value="email" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Mail className="h-4 w-4" />
Email
</TabsTrigger>
<TabsTrigger value="notifications" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Bell className="h-4 w-4" />
Notifications
</TabsTrigger>
<TabsTrigger value="digest" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Newspaper className="h-4 w-4" />
Digest
</TabsTrigger>
</TabsList>
</div>
<div>
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Security</p>
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
<TabsTrigger value="security" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Shield className="h-4 w-4" />
Security
</TabsTrigger>
<TabsTrigger value="audit" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<ShieldAlert className="h-4 w-4" />
Audit
</TabsTrigger>
</TabsList>
</div>
<div>
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Features</p>
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
<TabsTrigger value="ai" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Bot className="h-4 w-4" />
AI
</TabsTrigger>
<TabsTrigger value="tags" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Tags className="h-4 w-4" />
Tags
</TabsTrigger>
<TabsTrigger value="analytics" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<BarChart3 className="h-4 w-4" />
Analytics
</TabsTrigger>
</TabsList>
</div>
<div>
<p className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Infrastructure</p>
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
<TabsTrigger value="storage" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<HardDrive className="h-4 w-4" />
Storage
</TabsTrigger>
</TabsList>
</div>
</nav>
</div>
{/* Content area */}
<div className="flex-1 min-w-0">
<TabsContent value="ai" className="space-y-6">
<Card>
<CardHeader>
@@ -390,6 +475,8 @@ export function SettingsContent({ initialSettings }: SettingsContentProps) {
</CardContent>
</Card>
</TabsContent>
</div>{/* end content area */}
</div>{/* end lg:flex */}
</Tabs>
{/* Quick Links to sub-pages */}