feat: implement full admin panel with dashboard, user management, and platform settings
Adds complete super_admin panel with 9 pages and account owner categories page. Backend includes 5 new DB tables, ~25 API endpoints, settings manager with in-memory cache, and 29 integration tests. Frontend includes reusable admin components (DataTable, Pagination, ActionMenu, etc.) with code-split lazy loading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
126
frontend/src/components/admin/DataTable.tsx
Normal file
126
frontend/src/components/admin/DataTable.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface Column<T> {
|
||||
key: string
|
||||
header: string
|
||||
render: (item: T) => ReactNode
|
||||
sortable?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
interface DataTableProps<T> {
|
||||
columns: Column<T>[]
|
||||
data: T[]
|
||||
keyExtractor: (item: T) => string
|
||||
isLoading?: boolean
|
||||
skeletonRows?: number
|
||||
onSort?: (key: string, direction: 'asc' | 'desc') => void
|
||||
sortKey?: string
|
||||
sortDirection?: 'asc' | 'desc'
|
||||
emptyState?: ReactNode
|
||||
}
|
||||
|
||||
export function DataTable<T>({
|
||||
columns,
|
||||
data,
|
||||
keyExtractor,
|
||||
isLoading = false,
|
||||
skeletonRows = 5,
|
||||
onSort,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
emptyState,
|
||||
}: DataTableProps<T>) {
|
||||
const [localSortKey, setLocalSortKey] = useState<string | null>(null)
|
||||
const [localSortDir, setLocalSortDir] = useState<'asc' | 'desc'>('asc')
|
||||
|
||||
const activeSortKey = sortKey ?? localSortKey
|
||||
const activeSortDir = sortDirection ?? localSortDir
|
||||
|
||||
const handleSort = (key: string) => {
|
||||
const newDir = activeSortKey === key && activeSortDir === 'asc' ? 'desc' : 'asc'
|
||||
if (onSort) {
|
||||
onSort(key, newDir)
|
||||
} else {
|
||||
setLocalSortKey(key)
|
||||
setLocalSortDir(newDir)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto rounded-lg border border-border">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border bg-muted/50">
|
||||
{columns.map((col) => (
|
||||
<th
|
||||
key={col.key}
|
||||
className={cn(
|
||||
'px-4 py-3 text-left font-medium text-muted-foreground',
|
||||
col.sortable && 'cursor-pointer select-none hover:text-foreground',
|
||||
col.className
|
||||
)}
|
||||
onClick={col.sortable ? () => handleSort(col.key) : undefined}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{col.header}
|
||||
{col.sortable && (
|
||||
<span className="inline-flex">
|
||||
{activeSortKey === col.key ? (
|
||||
activeSortDir === 'asc' ? (
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
)
|
||||
) : (
|
||||
<ChevronsUpDown className="h-3.5 w-3.5 opacity-40" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isLoading ? (
|
||||
Array.from({ length: skeletonRows }).map((_, i) => (
|
||||
<tr key={i} className="border-b border-border last:border-0">
|
||||
{columns.map((col) => (
|
||||
<td key={col.key} className="px-4 py-3">
|
||||
<div className="h-4 w-3/4 animate-pulse rounded bg-muted" />
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
) : data.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={columns.length} className="px-4 py-12 text-center">
|
||||
{emptyState || (
|
||||
<span className="text-muted-foreground">No data found</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
data.map((item) => (
|
||||
<tr
|
||||
key={keyExtractor(item)}
|
||||
className="border-b border-border last:border-0 hover:bg-muted/30 transition-colors"
|
||||
>
|
||||
{columns.map((col) => (
|
||||
<td key={col.key} className={cn('px-4 py-3', col.className)}>
|
||||
{col.render(item)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DataTable
|
||||
Reference in New Issue
Block a user