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