132 lines
3.3 KiB
TypeScript
132 lines
3.3 KiB
TypeScript
import { Table, Text, Card, Stack, Group, Box } from '@pikku/mantine/core'
|
|
import { useMediaQuery } from '@mantine/hooks'
|
|
import { type ReactNode } from 'react'
|
|
import { asI18n, type I18nNode } from '@pikku/react'
|
|
|
|
export interface Column<T> {
|
|
key: string
|
|
label: I18nNode
|
|
render: (row: T) => ReactNode
|
|
}
|
|
|
|
interface DataTableProps<T> {
|
|
data: T[]
|
|
columns: Column<T>[]
|
|
emptyMessage?: string
|
|
rowKey: (row: T) => string
|
|
}
|
|
|
|
export function DataTable<T>({
|
|
data,
|
|
columns,
|
|
emptyMessage = 'No data found.',
|
|
rowKey,
|
|
}: DataTableProps<T>) {
|
|
const isMobile = useMediaQuery('(max-width: 768px)')
|
|
|
|
if (data.length === 0) {
|
|
return (
|
|
<Card
|
|
padding="xl"
|
|
radius="md"
|
|
style={{
|
|
backgroundColor: '#ffffff',
|
|
boxShadow:
|
|
'0 2px 12px rgba(61, 43, 31, 0.08), 0 1px 4px rgba(61, 43, 31, 0.04)',
|
|
}}
|
|
>
|
|
<Text size="sm" c="dimmed" ta="center">
|
|
{asI18n(emptyMessage)}
|
|
</Text>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
if (isMobile) {
|
|
return (
|
|
<Stack gap="sm">
|
|
{data.map((row) => (
|
|
<Card
|
|
key={rowKey(row)}
|
|
padding="md"
|
|
radius="md"
|
|
style={{
|
|
backgroundColor: '#ffffff',
|
|
boxShadow:
|
|
'0 2px 12px rgba(61, 43, 31, 0.08), 0 1px 4px rgba(61, 43, 31, 0.04)',
|
|
}}
|
|
>
|
|
<Stack gap="xs">
|
|
{columns.map((col) => (
|
|
<Group key={col.key} justify="space-between" wrap="nowrap" gap="sm">
|
|
<Text size="xs" c="dimmed" fw={600} style={{ minWidth: 80 }}>
|
|
{col.label}
|
|
</Text>
|
|
<Box style={{ textAlign: 'right' }}>{col.render(row)}</Box>
|
|
</Group>
|
|
))}
|
|
</Stack>
|
|
</Card>
|
|
))}
|
|
</Stack>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Card
|
|
padding={0}
|
|
radius="md"
|
|
style={{
|
|
backgroundColor: '#ffffff',
|
|
boxShadow:
|
|
'0 2px 12px rgba(61, 43, 31, 0.08), 0 1px 4px rgba(61, 43, 31, 0.04)',
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
<Table
|
|
highlightOnHover
|
|
horizontalSpacing="lg"
|
|
verticalSpacing="sm"
|
|
styles={{
|
|
thead: {
|
|
backgroundColor: '#f8f6f2',
|
|
borderBottom: '2px solid rgba(61, 43, 31, 0.1)',
|
|
},
|
|
th: {
|
|
padding: '12px 16px',
|
|
fontSize: '11px',
|
|
fontWeight: 700,
|
|
letterSpacing: '0.05em',
|
|
textTransform: 'uppercase' as const,
|
|
color: '#8b7d6b',
|
|
},
|
|
td: {
|
|
padding: '14px 16px',
|
|
borderBottom: '1px solid rgba(61, 43, 31, 0.06)',
|
|
},
|
|
tr: {
|
|
transition: 'background-color 0.15s ease',
|
|
},
|
|
}}
|
|
>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
{columns.map((col) => (
|
|
<Table.Th key={col.key}>{col.label}</Table.Th>
|
|
))}
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>
|
|
{data.map((row) => (
|
|
<Table.Tr key={rowKey(row)}>
|
|
{columns.map((col) => (
|
|
<Table.Td key={col.key}>{col.render(row)}</Table.Td>
|
|
))}
|
|
</Table.Tr>
|
|
))}
|
|
</Table.Tbody>
|
|
</Table>
|
|
</Card>
|
|
)
|
|
}
|