Files
admin/apps/backend/app/templates/frontend/module/view/widgets/DataTable.tsx
T

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"
/>
);
};