Update
This commit is contained in:
@ -1,111 +1,205 @@
|
||||
import { Input, Table } from '@mantine/core';
|
||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Badge, Button, Flex, Input, Modal, ScrollAreaAutosize, Select, Stack, Table, TextInput } from '@mantine/core';
|
||||
import { Cell, ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import styles from './CustomTable.module.scss'
|
||||
import { useRoles } from '../hooks/swrHooks';
|
||||
import { IRole } from '../interfaces/role';
|
||||
import { IconPlus } from '@tabler/icons-react';
|
||||
import { CreateField } from '../interfaces/create';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import FormFields from './FormFields';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
|
||||
// Sample data
|
||||
|
||||
type DataType = {
|
||||
id: number,
|
||||
name: string,
|
||||
age: number
|
||||
type CustomTableProps<T> = {
|
||||
data: T[];
|
||||
columns: ColumnDef<T>[];
|
||||
createFields?: CreateField[];
|
||||
submitHandler?: (data: T) => Promise<AxiosResponse>
|
||||
}
|
||||
|
||||
// Define columns
|
||||
const columns: ColumnDef<DataType>[] = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
cell: (info) => info.getValue(),
|
||||
maxSize: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
{
|
||||
accessorKey: 'age',
|
||||
header: 'Age',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
];
|
||||
|
||||
const CustomTable = () => {
|
||||
const [data, setData] = useState<DataType[]>([
|
||||
{ id: 1, name: 'John Doe', age: 25 },
|
||||
{ id: 2, name: 'Jane Smith', age: 30 },
|
||||
{ id: 3, name: 'Sam Green', age: 22 },
|
||||
]);
|
||||
const CustomTable = <T extends object>({
|
||||
data: initialData,
|
||||
columns,
|
||||
createFields,
|
||||
submitHandler
|
||||
}: CustomTableProps<T>) => {
|
||||
const [data, setData] = useState<T[]>(initialData);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [editingCell, setEditingCell] = useState<{ rowIndex: string | number | null, columnId: string | number | null }>({ rowIndex: null, columnId: null });
|
||||
|
||||
const tableColumns = useMemo<ColumnDef<typeof data[0]>[]>(() => columns, []);
|
||||
const tableColumns = useMemo(() => columns, [columns]);
|
||||
|
||||
// Function to handle cell edit
|
||||
const handleEditCell = (
|
||||
rowIndex: number,
|
||||
columnId: keyof T,
|
||||
value: T[keyof T]
|
||||
) => {
|
||||
const updatedData = [...data];
|
||||
updatedData[rowIndex][columnId] = value;
|
||||
setData(updatedData);
|
||||
//setEditingCell({ rowIndex: null, columnId: null });
|
||||
};
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!searchText) return data;
|
||||
|
||||
return data.filter((row) =>
|
||||
Object.values(row).some((value) =>
|
||||
value?.toString().toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
);
|
||||
}, [data, searchText])
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
data: filteredData,
|
||||
columns: tableColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
columnResizeMode: "onChange",
|
||||
});
|
||||
|
||||
// Function to handle cell edit
|
||||
const handleEditCell = (
|
||||
rowIndex: number,
|
||||
columnId: keyof DataType,
|
||||
value: DataType[keyof DataType]
|
||||
) => {
|
||||
const updatedData = [...data];
|
||||
(updatedData[rowIndex][columnId] as DataType[keyof DataType]) = value;
|
||||
setData(updatedData);
|
||||
//setEditingCell({ rowIndex: null, columnId: null });
|
||||
};
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
return (
|
||||
<Table striped withColumnBorders highlightOnHover className={styles.table}>
|
||||
<Table.Thead className={styles.thead}>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
<Table.Tr key={headerGroup.id} className={styles.tr}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<Table.Th key={header.id} className={styles.th} w={header.getSize()}>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
<div
|
||||
className={styles.resize_handler}
|
||||
onMouseDown={header.getResizeHandler()} //for desktop
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
>
|
||||
</div>
|
||||
</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Thead>
|
||||
<Table.Tbody className={styles.tbody}>
|
||||
{table.getRowModel().rows.map((row, rowIndex) => (
|
||||
<Table.Tr key={row.id} className={styles.tr}>
|
||||
{row.getVisibleCells().map(cell => {
|
||||
const isEditing = editingCell.rowIndex === rowIndex && editingCell.columnId === cell.column.id;
|
||||
<Stack h='100%'>
|
||||
{createFields && submitHandler &&
|
||||
<Modal opened={opened} onClose={close} title="Добавление объекта" centered>
|
||||
<FormFields
|
||||
fields={createFields}
|
||||
submitHandler={submitHandler}
|
||||
/>
|
||||
</Modal>
|
||||
}
|
||||
|
||||
return (
|
||||
<Table.Td
|
||||
key={cell.id}
|
||||
onDoubleClick={() => setEditingCell({ rowIndex, columnId: cell.column.id })}
|
||||
style={{ width: cell.column.getSize() }}
|
||||
className={styles.td}
|
||||
>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
type='text'
|
||||
value={data[rowIndex][cell.column.id as keyof DataType]}
|
||||
onChange={(e) => handleEditCell(rowIndex, (cell.column.id as keyof DataType), e.target.value)}
|
||||
onBlur={() => setEditingCell({ rowIndex: null, columnId: null })}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
flexRender(cell.column.columnDef.cell, cell.getContext())
|
||||
)}
|
||||
</Table.Td>
|
||||
);
|
||||
})}
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
<Flex w='100%' gap='sm'>
|
||||
<TextInput
|
||||
placeholder="Поиск"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
w='100%'
|
||||
/>
|
||||
{createFields && submitHandler &&
|
||||
<Button
|
||||
leftSection={<IconPlus />}
|
||||
onClick={open}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
}
|
||||
</Flex>
|
||||
|
||||
<ScrollAreaAutosize offsetScrollbars style={{ borderRadius: '4px' }}>
|
||||
<Table stickyHeader striped withColumnBorders highlightOnHover className={styles.table}>
|
||||
<Table.Thead className={styles.thead}>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
<Table.Tr key={headerGroup.id} className={styles.tr}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<Table.Th key={header.id} className={styles.th} w={header.getSize()}>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
<div
|
||||
className={styles.resize_handler}
|
||||
onMouseDown={header.getResizeHandler()} //for desktop
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
>
|
||||
</div>
|
||||
</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Thead>
|
||||
<Table.Tbody className={styles.tbody}>
|
||||
{table.getRowModel().rows.map((row, rowIndex) => (
|
||||
<Table.Tr key={row.id} className={styles.tr}>
|
||||
{row.getVisibleCells().map(cell => {
|
||||
const isEditing = editingCell.rowIndex === rowIndex && editingCell.columnId === cell.column.id;
|
||||
|
||||
return (
|
||||
<Table.Td
|
||||
key={cell.id}
|
||||
onDoubleClick={() => setEditingCell({ rowIndex, columnId: cell.column.id })}
|
||||
style={{ width: cell.column.getSize() }}
|
||||
className={styles.td}
|
||||
>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
type='text'
|
||||
value={(data[rowIndex][cell.column.id as keyof T] as string)}
|
||||
onChange={(e) => handleEditCell(rowIndex, (cell.column.id as keyof T), e.target.value as T[keyof T])}
|
||||
onBlur={() => setEditingCell({ rowIndex: null, columnId: null })}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<CellDisplay cell={cell} />
|
||||
)}
|
||||
</Table.Td>
|
||||
);
|
||||
})}
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</ScrollAreaAutosize>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
type CellDisplayProps<T> = {
|
||||
cell: Cell<T, unknown>;
|
||||
}
|
||||
|
||||
const CellDisplay = <T extends object>({
|
||||
cell
|
||||
}: CellDisplayProps<T>) => {
|
||||
const { roles } = useRoles()
|
||||
|
||||
const [roleOptions, setRoleOptions] = useState<{ label: string, value: string }[]>()
|
||||
|
||||
useEffect(() => {
|
||||
if (Array.isArray(roles)) {
|
||||
setRoleOptions(roles.map((role: IRole) => ({ label: role.name, value: role.id.toString() })))
|
||||
}
|
||||
}, [roles])
|
||||
|
||||
switch (cell.column.id) {
|
||||
case 'activity':
|
||||
return (
|
||||
cell.getValue() ? (
|
||||
<Badge fullWidth variant="light">
|
||||
Активен
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge color="gray" fullWidth variant="light">
|
||||
Отключен
|
||||
</Badge>
|
||||
)
|
||||
)
|
||||
case 'is_active':
|
||||
return (
|
||||
cell.getValue() ? (
|
||||
<Badge fullWidth variant="light">
|
||||
Активен
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge color="gray" fullWidth variant="light">
|
||||
Отключен
|
||||
</Badge>
|
||||
)
|
||||
)
|
||||
case 'role_id':
|
||||
return (
|
||||
<Select
|
||||
data={roleOptions}
|
||||
value={Number(cell.getValue()).toString()}
|
||||
variant="unstyled"
|
||||
allowDeselect={false}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
flexRender(cell.column.columnDef.cell, cell.getContext())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomTable;
|
||||
|
Reference in New Issue
Block a user