advanced table fixes

This commit is contained in:
estifanos
2026-07-27 08:52:13 +00:00
parent af85fca02a
commit b5d09d2aea
2 changed files with 75 additions and 50 deletions

View File

@@ -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"),

View File

@@ -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>
); );
} }