This commit is contained in:
cracklesparkle
2025-01-30 12:36:39 +09:00
parent e6b3dc05d3
commit 0788a401ca
43 changed files with 3710 additions and 1724 deletions

View File

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