mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
advanced table fixes
This commit is contained in:
@@ -9,8 +9,10 @@ import {
|
||||
Pagination,
|
||||
Loader,
|
||||
Center,
|
||||
Paper,
|
||||
Badge,
|
||||
} from '@mantine/core';
|
||||
import { IconRefresh, IconEye } from '@tabler/icons-react';
|
||||
import { IconRefresh, IconEye, IconInbox } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface AdvancedColumn<T> {
|
||||
@@ -64,84 +66,106 @@ export function AdvancedTable<T extends { id?: string | number }>({
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState<boolean[]>(columns.map((c) => c.enabled ?? true));
|
||||
const toggleColumn = (i: number) =>
|
||||
setVisible((prev) => prev.map((v, idx) => (idx === i ? !v : v)));
|
||||
setVisible((prev) => {
|
||||
if (prev[i] && prev.filter(Boolean).length === 1) return prev; // keep at least one column visible
|
||||
return prev.map((v, idx) => (idx === i ? !v : v));
|
||||
});
|
||||
const shownColumns = columns.filter((_, i) => visible[i] ?? true);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text fw={600}>{tableName}</Text>
|
||||
<Paper withBorder radius="md" p="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Group gap="xs">
|
||||
<Menu closeOnItemClick={false} shadow="md" position="bottom-end">
|
||||
<Text fw={600}>{tableName}</Text>
|
||||
<Badge variant="light" size="sm" radius="sm">
|
||||
{itemCount}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<Menu closeOnItemClick={false} shadow="md" position="bottom-end" width={220}>
|
||||
<Menu.Target>
|
||||
<Button variant="subtle" size="sm" leftSection={<IconEye size={16} />}>
|
||||
<Button variant="default" size="sm" leftSection={<IconEye size={16} />}>
|
||||
{t('common.view', 'View')}
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>{t('common.toggleColumns', 'Toggle columns')}</Menu.Label>
|
||||
{columns.map((col, i) => (
|
||||
<Menu.Item key={i} onClick={() => toggleColumn(i)}>
|
||||
<Checkbox
|
||||
label={col.header}
|
||||
checked={visible[i] ?? true}
|
||||
onChange={() => toggleColumn(i)}
|
||||
disabled={visible.filter(Boolean).length === 1 && (visible[i] ?? true)}
|
||||
readOnly
|
||||
tabIndex={-1}
|
||||
styles={{ input: { cursor: 'pointer' }, label: { cursor: 'pointer' } }}
|
||||
/>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
{refresh && (
|
||||
<Button variant="subtle" size="sm" rightSection={<IconRefresh size={16} />} onClick={refresh}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
leftSection={<IconRefresh size={16} />}
|
||||
onClick={refresh}
|
||||
loading={isLoading}
|
||||
>
|
||||
{t('common.refresh', 'Refresh')}
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
{shownColumns.map((col, i) => (
|
||||
<Table.Th key={i} style={{ width: col.size, textAlign: col.align }}>
|
||||
{col.header}
|
||||
</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{isLoading ? (
|
||||
<Table.ScrollContainer minWidth={480}>
|
||||
<Table striped highlightOnHover withTableBorder withColumnBorders verticalSpacing="sm">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={shownColumns.length}>
|
||||
<Center py="xl">
|
||||
<Loader size="sm" />
|
||||
</Center>
|
||||
</Table.Td>
|
||||
{shownColumns.map((col, i) => (
|
||||
<Table.Th key={i} style={{ width: col.size, textAlign: col.align }}>
|
||||
{col.header}
|
||||
</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
) : data.length === 0 ? (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={shownColumns.length}>
|
||||
<Text c="dimmed" ta="center" py="xl">
|
||||
{emptyText ?? t('common.noResult', 'No results')}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
) : (
|
||||
data.map((row, rowIndex) => (
|
||||
<Table.Tr key={row.id ?? rowIndex}>
|
||||
{shownColumns.map((col, i) => {
|
||||
const value = getByPath(row, col.accessorKey);
|
||||
return (
|
||||
<Table.Td key={i} style={{ textAlign: col.align }}>
|
||||
{col.cell ? col.cell({ row: { original: row }, value }) : (value as ReactNode) ?? '-'}
|
||||
</Table.Td>
|
||||
);
|
||||
})}
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{isLoading ? (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={shownColumns.length}>
|
||||
<Center py="xl">
|
||||
<Loader size="sm" />
|
||||
</Center>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
) : data.length === 0 ? (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={shownColumns.length}>
|
||||
<Center py="xl">
|
||||
<Group gap="xs" c="dimmed">
|
||||
<IconInbox size={18} />
|
||||
<Text c="dimmed">{emptyText ?? t('common.noResult', 'No results')}</Text>
|
||||
</Group>
|
||||
</Center>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
) : (
|
||||
data.map((row, rowIndex) => (
|
||||
<Table.Tr key={row.id ?? rowIndex}>
|
||||
{shownColumns.map((col, i) => {
|
||||
const value = getByPath(row, col.accessorKey);
|
||||
return (
|
||||
<Table.Td key={i} style={{ textAlign: col.align }}>
|
||||
{col.cell ? col.cell({ row: { original: row }, value }) : (value as ReactNode) ?? '-'}
|
||||
</Table.Td>
|
||||
);
|
||||
})}
|
||||
</Table.Tr>
|
||||
))
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Table.ScrollContainer>
|
||||
|
||||
{itemCount > pageSize && (
|
||||
<Group justify="flex-end" mt="md">
|
||||
@@ -153,6 +177,6 @@ export function AdvancedTable<T extends { id?: string | number }>({
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user