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:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user