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