Comprehensive platform audit: security, UX, performance, and visual polish

Phase 1: Security - status transition validation, Zod tightening, DB indexes, transactions

Phase 2: Admin UX - search/filter for awards, learning, partners pages

Phase 3: Dashboard - Recent Activity feed, Pending Actions card, quick actions

Phase 4: Jury - assignments progress/urgency, autosave indicator, divergence highlighting

Phase 5: Portals - observer charts, mentor search, login/onboarding polish

Phase 6: Messages preview dialog, CsvExportDialog with column selection

Phase 7: Performance - query optimizations, loading skeletons, useDebounce hook

Phase 8: Visual - AnimatedCard, hover effects, StatusBadge, empty state CTAs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 22:05:01 +01:00
parent e0e4cb2a32
commit e73a676412
33 changed files with 3193 additions and 977 deletions

View File

@@ -1,6 +1,6 @@
'use client'
import { useState, useMemo } from 'react'
import { useState, useMemo, useCallback } from 'react'
import { trpc } from '@/lib/trpc/client'
import { Button } from '@/components/ui/button'
import {
@@ -53,6 +53,7 @@ import {
ArrowLeftRight,
} from 'lucide-react'
import { Switch } from '@/components/ui/switch'
import { CsvExportDialog } from '@/components/shared/csv-export-dialog'
import { formatDate } from '@/lib/utils'
import { cn } from '@/lib/utils'
@@ -163,7 +164,7 @@ export default function AuditLogPage() {
retry: false,
})
// Export mutation
// Export query
const exportLogs = trpc.export.auditLogs.useQuery(
{
userId: filters.userId || undefined,
@@ -176,41 +177,18 @@ export default function AuditLogPage() {
},
{ enabled: false }
)
const [showExportDialog, setShowExportDialog] = useState(false)
// Handle export
const handleExport = async () => {
const result = await exportLogs.refetch()
if (result.data) {
const { data: rows, columns } = result.data
// Build CSV
const csvContent = [
columns.join(','),
...rows.map((row) =>
columns
.map((col) => {
const value = row[col as keyof typeof row]
// Escape quotes and wrap in quotes if contains comma
if (typeof value === 'string' && (value.includes(',') || value.includes('"'))) {
return `"${value.replace(/"/g, '""')}"`
}
return value ?? ''
})
.join(',')
),
].join('\n')
// Download
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `audit-logs-${new Date().toISOString().split('T')[0]}.csv`
link.click()
URL.revokeObjectURL(url)
}
const handleExport = () => {
setShowExportDialog(true)
}
const handleRequestExportData = useCallback(async () => {
const result = await exportLogs.refetch()
return result.data ?? undefined
}, [exportLogs])
// Reset filters
const resetFilters = () => {
setFilters({
@@ -701,6 +679,16 @@ export default function AuditLogPage() {
</CardContent>
</Card>
)}
{/* CSV Export Dialog with Column Selection */}
<CsvExportDialog
open={showExportDialog}
onOpenChange={setShowExportDialog}
exportData={exportLogs.data ?? undefined}
isLoading={exportLogs.isFetching}
filename="audit-logs"
onRequestData={handleRequestExportData}
/>
</div>
)
}