Files
caddy-proxy-manager/src/components/ui/DataTable.tsx

155 lines
4.0 KiB
TypeScript

"use client";
import {
Box,
Card,
Pagination,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { ReactNode } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
export type Column<T> = {
id: string;
label: string;
align?: "left" | "right" | "center";
width?: string | number;
render?: (row: T) => ReactNode;
};
type DataTableProps<T> = {
columns: Column<T>[];
data: T[];
keyField: keyof T;
emptyMessage?: string;
loading?: boolean;
onRowClick?: (row: T) => void;
pagination?: {
total: number;
page: number;
perPage: number;
};
/** If provided, renders this instead of the table on xs/sm screens */
mobileCard?: (row: T) => ReactNode;
};
function PaginationBar({ page, perPage, total }: { page: number; perPage: number; total: number }) {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const pageCount = Math.ceil(total / perPage);
if (pageCount <= 1) return null;
function handlePageChange(_: React.ChangeEvent<unknown>, newPage: number) {
const params = new URLSearchParams(searchParams.toString());
params.set("page", String(newPage));
router.push(`${pathname}?${params.toString()}`);
}
return (
<Box sx={{ display: "flex", justifyContent: "center", mt: 2 }}>
<Pagination
count={pageCount}
page={page}
onChange={handlePageChange}
color="primary"
shape="rounded"
/>
</Box>
);
}
export function DataTable<T>({
columns,
data,
keyField,
emptyMessage = "No data available",
loading = false,
onRowClick,
pagination,
mobileCard,
}: DataTableProps<T>) {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const isEmpty = data.length === 0 && !loading;
if (isMobile && mobileCard) {
return (
<Box>
{isEmpty ? (
<Card variant="outlined" sx={{ py: 6, textAlign: "center" }}>
<Typography color="text.secondary">{emptyMessage}</Typography>
</Card>
) : (
<Stack spacing={1.5}>
{data.map((row) => (
<Box key={String(row[keyField])}>
{mobileCard(row)}
</Box>
))}
</Stack>
)}
{pagination && <PaginationBar {...pagination} />}
</Box>
);
}
return (
<Box>
<TableContainer component={Card} variant="outlined" sx={{ overflowX: "auto" }}>
<Table sx={{ minWidth: 600 }}>
<TableHead>
<TableRow>
{columns.map((col) => (
<TableCell
key={col.id}
align={col.align || "left"}
width={col.width}
>
{col.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{isEmpty ? (
<TableRow>
<TableCell colSpan={columns.length} align="center" sx={{ py: 8 }}>
<Typography color="text.secondary">{emptyMessage}</Typography>
</TableCell>
</TableRow>
) : (
data.map((row) => (
<TableRow
key={String(row[keyField])}
onClick={onRowClick ? () => onRowClick(row) : undefined}
sx={onRowClick ? { cursor: "pointer", "&:hover": { bgcolor: "action.hover" } } : undefined}
>
{columns.map((col) => (
<TableCell key={col.id} align={col.align || "left"}>
{col.render ? col.render(row) : (row as Record<string, unknown>)[col.id] as ReactNode}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
{pagination && <PaginationBar {...pagination} />}
</Box>
);
}