265 lines
7.9 KiB
TypeScript
Executable File
265 lines
7.9 KiB
TypeScript
Executable File
import { Column, ColumnDef, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, PaginationState, SortingState, useReactTable } from '@tanstack/react-table';
|
|
import { LoaderCircleIcon, SearchIcon, Settings2, SquarePen } from 'lucide-react';
|
|
import React from 'react';
|
|
import { useIntl } from 'react-intl';
|
|
import { useDebounceSearch } from '@crux-kit/hooks';
|
|
import { mapTableStateToListQueryParams } from '#infrastructure/reactQuery';
|
|
import { PermissionGate } from '#core/Permission';
|
|
import { Button } from '#shared/ui/Button';
|
|
import { Card, CardFooter, CardHeader, CardTable, CardTitle, CardToolbar } from '#shared/ui/Card';
|
|
import { Input, InputWrapper } from '#shared/ui/Input';
|
|
import { ScrollArea, ScrollBar } from '#shared/ui/ScrollArea';
|
|
import { Skeleton } from '#shared/ui/Skeleton';
|
|
import { DataGrid, DataGridColumnHeader, DataGridColumnVisibility, DataGridPagination, DataGridTable, useDataGrid } from '#shared/widgets/DataGrid';
|
|
import { useGetList } from '../../api/list';
|
|
import { MODULE_NAME_LABEL, MODULE_PERMISSION_CREATE, MODULE_PERMISSION_DELETE, MODULE_PERMISSION_UPDATE } from '../../config/meta';
|
|
import { Entity } from '../../entities/Entity';
|
|
import { DeleteTrigger } from './DeleteTrigger';
|
|
import { ManageTrigger } from './ManageTrigger';
|
|
|
|
interface IColumnFilterProps<TData, TValue> {
|
|
column: Column<TData, TValue>;
|
|
}
|
|
|
|
export const DataTable = () => {
|
|
const intl = useIntl();
|
|
|
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
pageIndex: 0,
|
|
pageSize: 10
|
|
});
|
|
|
|
const [search, setSearch] = React.useState<string>('');
|
|
|
|
const { handleChange } = useDebounceSearch({
|
|
minQueryNumber: 3,
|
|
delay: 400,
|
|
initialQuery: search,
|
|
onTimeOut: setSearch
|
|
});
|
|
|
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
|
|
const { list, total, isFetching, isLoading, pagesCount } = useGetList(
|
|
mapTableStateToListQueryParams(pagination, sorting, search)
|
|
);
|
|
|
|
const columns = React.useMemo<ColumnDef<Entity>[]>(
|
|
() => [
|
|
{
|
|
id: 'id',
|
|
accessorFn: (row) => row.id,
|
|
header: ({ column }) => <DataGridColumnHeader title="id" column={column} />,
|
|
enableSorting: true,
|
|
size: 80,
|
|
meta: {
|
|
cellClassName: '',
|
|
skeleton: <Skeleton className="h-4" />
|
|
}
|
|
},
|
|
{
|
|
id: 'name',
|
|
accessorFn: (row) => row.name,
|
|
header: ({ column }) => (
|
|
<DataGridColumnHeader
|
|
title="name"
|
|
filter={<ColumnInputFilter column={column} />}
|
|
column={column}
|
|
/>
|
|
),
|
|
cell: (info) => info.getValue(),
|
|
enableSorting: true,
|
|
meta: {
|
|
cellClassName: '',
|
|
skeleton: <Skeleton className="h-4" />
|
|
}
|
|
},
|
|
{
|
|
id: 'name2',
|
|
accessorFn: (row) => row.name2,
|
|
header: ({ column }) => (
|
|
<DataGridColumnHeader
|
|
title="name2"
|
|
filter={<ColumnInputFilter column={column} />}
|
|
column={column}
|
|
/>
|
|
),
|
|
cell: (info) => info.getValue(),
|
|
enableSorting: true,
|
|
meta: {
|
|
cellClassName: '',
|
|
skeleton: <Skeleton className="h-4" />
|
|
}
|
|
},
|
|
{
|
|
id: 'order',
|
|
accessorFn: (row) => row.order,
|
|
header: ({ column }) => <DataGridColumnHeader title="order" column={column} />,
|
|
cell: (info) => info.getValue(),
|
|
enableSorting: true,
|
|
meta: {
|
|
cellClassName: '',
|
|
skeleton: <Skeleton className="h-4" />
|
|
}
|
|
},
|
|
{
|
|
id: 'number',
|
|
accessorFn: (row) => row.number,
|
|
header: ({ column }) => <DataGridColumnHeader title="number" column={column} />,
|
|
cell: (info) => info.getValue(),
|
|
enableSorting: true,
|
|
meta: {
|
|
cellClassName: '',
|
|
skeleton: <Skeleton className="h-4" />
|
|
}
|
|
},
|
|
{
|
|
id: 'action',
|
|
header: () => intl.formatMessage({ id: 'ACTIONS' }),
|
|
cell: (info) => {
|
|
const data = info.row.original;
|
|
const id = data.id;
|
|
|
|
return (
|
|
<div className="flex items-center justify-end gap-2">
|
|
<PermissionGate requiredPermissions={MODULE_PERMISSION_UPDATE}>
|
|
<ManageTrigger title={`Edit ${MODULE_NAME_LABEL}`} id={id}>
|
|
<Button variant="secondary" size="sm" shape="default" mode="icon">
|
|
<SquarePen />
|
|
</Button>
|
|
</ManageTrigger>
|
|
</PermissionGate>
|
|
<PermissionGate requiredPermissions={MODULE_PERMISSION_DELETE}>
|
|
<DeleteTrigger id={id} deleteTarget={id} />
|
|
</PermissionGate>
|
|
</div>
|
|
);
|
|
},
|
|
enableSorting: false,
|
|
size: 96,
|
|
meta: {
|
|
headerClassName: '',
|
|
skeleton: <Skeleton className="h-4" />
|
|
}
|
|
}
|
|
],
|
|
[intl]
|
|
);
|
|
|
|
const table = useReactTable({
|
|
columns,
|
|
autoResetPageIndex: false,
|
|
enableMultiSort: false,
|
|
data: list,
|
|
manualPagination: true,
|
|
pageCount: pagesCount,
|
|
getRowId: (row) => String(row.id),
|
|
state: {
|
|
pagination,
|
|
sorting
|
|
},
|
|
onPaginationChange: setPagination,
|
|
onSortingChange: setSorting,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getFilteredRowModel: getFilteredRowModel(),
|
|
getPaginationRowModel: getPaginationRowModel(),
|
|
getSortedRowModel: getSortedRowModel()
|
|
});
|
|
|
|
return (
|
|
<DataGrid
|
|
table={table}
|
|
recordCount={total || 0}
|
|
emptyMessage={intl.formatMessage({
|
|
id: 'TABLE.COMMON.NO_DATA_AVAILABLE'
|
|
})}
|
|
loadingMode="skeleton"
|
|
isLoading={isLoading}
|
|
tableLayout={{
|
|
columnsDraggable: false,
|
|
columnsResizable: false,
|
|
width: 'auto',
|
|
columnsPinnable: true,
|
|
columnsMovable: true,
|
|
columnsVisibility: true,
|
|
cellBorder: true
|
|
}}
|
|
>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>
|
|
<InputWrapper>
|
|
{search.length > 0 && isFetching ? (
|
|
<LoaderCircleIcon className="size-4 animate-spin" />
|
|
) : (
|
|
<SearchIcon className="size-4" />
|
|
)}
|
|
<Input
|
|
type="text"
|
|
placeholder={intl.formatMessage({ id: 'ACTION.SEARCH' })}
|
|
onChange={(e) => handleChange(e.target.value)}
|
|
/>
|
|
</InputWrapper>
|
|
</CardTitle>
|
|
<Toolbar />
|
|
</CardHeader>
|
|
<CardTable>
|
|
<ScrollArea>
|
|
<DataGridTable />
|
|
<ScrollBar orientation="horizontal" />
|
|
</ScrollArea>
|
|
</CardTable>
|
|
<CardFooter>
|
|
<DataGridPagination />
|
|
</CardFooter>
|
|
</Card>
|
|
</DataGrid>
|
|
);
|
|
};
|
|
|
|
const Toolbar = () => {
|
|
const { table } = useDataGrid();
|
|
const intl = useIntl();
|
|
|
|
return (
|
|
<CardToolbar>
|
|
<div className="flex flex-wrap items-center gap-2.5">
|
|
<PermissionGate requiredPermissions={MODULE_PERMISSION_CREATE}>
|
|
<ManageTrigger
|
|
id={null}
|
|
title={intl.formatMessage(
|
|
{ id: 'MODULE.COMMON.CREATE_NEW_ENTITY' },
|
|
{ entity: MODULE_NAME_LABEL }
|
|
)}
|
|
>
|
|
<Button>{intl.formatMessage({ id: 'ACTION.CREATE' })}</Button>
|
|
</ManageTrigger>
|
|
</PermissionGate>
|
|
|
|
<DataGridColumnVisibility
|
|
table={table}
|
|
trigger={
|
|
<Button variant="outline">
|
|
<Settings2 />
|
|
{intl.formatMessage({ id: 'TABLE.COMMON.COLUMNS' })}
|
|
</Button>
|
|
}
|
|
/>
|
|
</div>
|
|
</CardToolbar>
|
|
);
|
|
};
|
|
|
|
const ColumnInputFilter = <TData, TValue>({ column }: IColumnFilterProps<TData, TValue>) => {
|
|
const intl = useIntl();
|
|
|
|
return (
|
|
<Input
|
|
placeholder={intl.formatMessage({ id: 'FILTER' })}
|
|
value={(column.getFilterValue() as string) ?? ''}
|
|
onChange={(event) => column.setFilterValue(event.target.value)}
|
|
className="h-9 w-full max-w-40"
|
|
/>
|
|
);
|
|
};
|