feat: add missing DataTable component for admin pages
Fixes build issue caused by missing data-table import in: - src/app/admin/exam-proctoring/page.tsx - src/app/admin/signage/page.tsx The component provides reusable table interface with search, sorting, and actions for all admin interfaces.
This commit is contained in:
1 parent
be1e61d94b
commit
e44174517f
1 file changed
+148
@@ -0,0 +1,148 @@
|
||||
"use client"
|
||||
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table"
|
||||
import { Button } from "./ui/button"
|
||||
import { Input } from "./ui/input"
|
||||
|
||||
interface Column {
|
||||
key: string
|
||||
label: string
|
||||
sortable?: boolean
|
||||
width?: string
|
||||
render?: (value: any, row: any) => React.ReactNode
|
||||
}
|
||||
|
||||
interface Action {
|
||||
label: string
|
||||
onClick: (row: any) => void
|
||||
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
|
||||
}
|
||||
|
||||
interface DataTableProps {
|
||||
columns: Column[]
|
||||
data: any[]
|
||||
isLoading?: boolean
|
||||
searchPlaceholder?: string
|
||||
searchableColumns?: string[]
|
||||
actions?: Action[]
|
||||
}
|
||||
|
||||
export function DataTable({
|
||||
columns,
|
||||
data,
|
||||
isLoading,
|
||||
searchPlaceholder = "Search...",
|
||||
searchableColumns = [],
|
||||
actions = []
|
||||
}: DataTableProps) {
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [sortConfig, setSortConfig] = useState<{ key: string; direction: "asc" | "desc" } | null>(null)
|
||||
|
||||
const filteredData = data.filter((row) => {
|
||||
if (!searchTerm) return true
|
||||
return searchableColumns.some((key) => {
|
||||
const value = row[key]
|
||||
return value?.toString().toLowerCase().includes(searchTerm.toLowerCase())
|
||||
})
|
||||
})
|
||||
|
||||
const sortedData = [...filteredData].sort((a, b) => {
|
||||
if (!sortConfig) return 0
|
||||
const aValue = a[sortConfig.key]
|
||||
const bValue = b[sortConfig.key]
|
||||
|
||||
if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1
|
||||
if (aValue > bValue) return sortConfig.direction === "asc" ? 1 : -1
|
||||
return 0
|
||||
})
|
||||
|
||||
const handleSort = (key: string) => {
|
||||
setSortConfig(current => {
|
||||
if (current?.key === key) {
|
||||
return { key, direction: current.direction === "asc" ? "desc" : "asc" }
|
||||
}
|
||||
return { key, direction: "asc" }
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{searchableColumns.length > 0 && (
|
||||
<Input
|
||||
placeholder={searchPlaceholder}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{columns.map((column) => (
|
||||
<TableHead
|
||||
key={column.key}
|
||||
className={column.width ? `w-[${column.width}]` : ""}
|
||||
onClick={column.sortable ? () => handleSort(column.key) : undefined}
|
||||
style={{ cursor: column.sortable ? "pointer" : "default" }}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{column.label}
|
||||
{column.sortable && (
|
||||
<span className="text-xs text-gray-400">
|
||||
{sortConfig?.key === column.key && sortConfig.direction === "asc" ? "↑" : "↓"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableHead>
|
||||
))}
|
||||
{actions.length > 0 && (
|
||||
<TableHead className="w-[100px]">Actions</TableHead>
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length + (actions.length > 0 ? 1 : 0)} className="text-center py-8">
|
||||
Loading...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : sortedData.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length + (actions.length > 0 ? 1 : 0)} className="text-center py-8 text-gray-500">
|
||||
No data found
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
sortedData.map((row, index) => (
|
||||
<TableRow key={index}>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column.key}>
|
||||
{column.render ? column.render(row[column.key], row) : row[column.key]}
|
||||
</TableCell>
|
||||
))}
|
||||
{actions.length > 0 && (
|
||||
<TableCell>
|
||||
<div className="flex gap-2">
|
||||
{actions.map((action, actionIndex) => (
|
||||
<Button
|
||||
key={actionIndex}
|
||||
size="sm"
|
||||
variant={action.variant || "outline"}
|
||||
onClick={() => action.onClick(row)}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user