Skip to content

Commit

Permalink
feat: rework datatables
Browse files Browse the repository at this point in the history
DATATR-1763

Signed-off-by: Jonathan Perchoc <[email protected]>
  • Loading branch information
jperchoc committed Dec 26, 2024
1 parent da641d0 commit 431b997
Show file tree
Hide file tree
Showing 57 changed files with 1,567 additions and 472 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"status-PENDING": "Ausstehend",
"status-UPDATING": "Update",
"status-ERROR": "Fehler",
"status-ERROR_INCONSISTENT_SPEC": "Fehler",
"status-ERROR_INCONSISTENT_SPEC": "SPEC-Fehler",
"status-READY": "Verfügbar"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"status-PENDING": "Queued",
"status-UPDATING": "Updating",
"status-ERROR": "Error",
"status-ERROR_INCONSISTENT_SPEC": "Error",
"status-ERROR_INCONSISTENT_SPEC": "spec error",
"status-READY": "Available"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"status-PENDING": "Pendiente",
"status-UPDATING": "Actualizada",
"status-ERROR": "En error",
"status-ERROR_INCONSISTENT_SPEC": "En error",
"status-ERROR_INCONSISTENT_SPEC": "Error spec",
"status-READY": "Disponible"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"status-PENDING": "En attente",
"status-UPDATING": "Mise à jour",
"status-ERROR": "En erreur",
"status-ERROR_INCONSISTENT_SPEC": "En erreur",
"status-ERROR_INCONSISTENT_SPEC": "Erreur spec",
"status-READY": "Disponible"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"status-PENDING": "En attente",
"status-UPDATING": "Mise à jour",
"status-ERROR": "En erreur",
"status-ERROR_INCONSISTENT_SPEC": "En erreur",
"status-ERROR_INCONSISTENT_SPEC": "Erreur spec",
"status-READY": "Disponible"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"status-PENDING": "In attesa",
"status-UPDATING": "Aggiornamento",
"status-ERROR": "In errore",
"status-ERROR_INCONSISTENT_SPEC": "In errore",
"status-ERROR_INCONSISTENT_SPEC": "Errore specifico",
"status-READY": "Disponibile"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"status-PENDING": "Oczekujące",
"status-UPDATING": "Aktualizacja",
"status-ERROR": "Błąd",
"status-ERROR_INCONSISTENT_SPEC": "Błąd",
"status-ERROR_INCONSISTENT_SPEC": "Błąd spec",
"status-READY": "Dostępny"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"status-PENDING": "Em espera",
"status-UPDATING": "Atualização",
"status-ERROR": "Erro",
"status-ERROR_INCONSISTENT_SPEC": "Erro",
"status-ERROR_INCONSISTENT_SPEC": "Erro spec",
"status-READY": "Disponível"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ import { PointerEvent } from './helpers/pointerEvent';
// it is requiered for DropdownMenus
// source: https://github.com/radix-ui/primitives/issues/856#issuecomment-928704064
window.PointerEvent = PointerEvent as any;

const originalConsoleError = console.error;

console.error = (...args) => {
if (typeof args[0] === 'string' && args[0].includes('connect ECONNREFUSED')) {
return;
}
originalConsoleError(...args);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { ReactElement, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { flexRender } from '@tanstack/react-table';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useDataTableContext } from './DataTableContext';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Button } from '../ui/button';

export const MENU_COLUMN_ID = 'actions';

interface DatatableProps<TData> {
renderRowExpansion?: (row: TData) => ReactElement | null;
}

export function DataTable<TData>({
renderRowExpansion,
}: DatatableProps<TData>) {
const { table, rows } = useDataTableContext();
const { t } = useTranslation('pci-databases-analytics/components/data-table');
const [expandedRows, setExpandedRows] = useState<Record<string, boolean>>({});

const toggleRowExpansion = (rowId: string) => {
setExpandedRows((prev) => ({
...prev,
[rowId]: !prev[rowId],
}));
};

const headerGroups = table.getHeaderGroups();
return (
<Table>
<TableHeader className="border bg-gray-50">
{headerGroups.map((headerGroup) => (
<TableRow key={headerGroup.id}>
{renderRowExpansion && (
<TableHead className="border-r-0 w-6"></TableHead>
)}
{headerGroup.headers.map((header, index) => {
const isEmptyHeader = header.id === MENU_COLUMN_ID;
// Get a reference to the previous header
const isEmptyNextHeader =
headerGroup.headers[index + 1]?.id === MENU_COLUMN_ID;
return (
<TableHead
key={header.id}
className={`border font-semibold text-primary-800 ${
isEmptyHeader || renderRowExpansion
? 'border-l-0' // Remove left border for empty headers and row extend column
: ''
} ${
isEmptyNextHeader
? 'border-r-0' // Remove right border from current column if next header is empty
: ''
}`}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody className="border">
{rows?.length ? (
rows.map((row) => (
<React.Fragment key={row.id}>
<TableRow data-state={row.getIsSelected() && 'selected'}>
{renderRowExpansion && (
<TableCell>
<Button
variant="ghost"
onClick={() => toggleRowExpansion(row.id)}
data-testid="table-row-expand-button"
>
{expandedRows[row.id] ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</TableCell>
)}
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
{expandedRows[row.id] && renderRowExpansion && (
<TableRow>
<TableCell colSpan={headerGroups[0].headers.length + 1}>
{renderRowExpansion(row.original as TData)}
</TableCell>
</TableRow>
)}
</React.Fragment>
))
) : (
<TableRow>
<TableCell
colSpan={headerGroups[0].headers.length}
className="h-24 text-center"
>
{t('noResult')}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
ColumnDef,
Row,
SortingState,
Table,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { ReactNode, createContext, useContext, useMemo, useState } from 'react';
import { useColumnFilters } from './useColumnFilters.hook';
import { applyFilters } from '@/lib/filters';
import { ColumnFilter } from './DatatableDefaultFilterButton';
import { DataTable } from './DataTable';
import { DataTablePagination } from './DatatablePagination';

interface DataTableProviderProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
pageSize?: number;
itemNumber?: number;
filtersDefinition?: ColumnFilter[];
children?: ReactNode;
}

interface DataTableContextValue<TData> {
table: Table<TData>;
filtersDefinition?: ColumnFilter[];
columnFilters: ReturnType<typeof useColumnFilters>;
globalFilter: string;
data: TData[];
filteredData: TData[];
sorting: SortingState;
rows: Row<TData>[];
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const DataTableContext = createContext<DataTableContextValue<any> | null>(null);

export function DataTableProvider<TData, TValue>({
columns,
data,
pageSize,
filtersDefinition,
children,
}: DataTableProviderProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([
{
id: columns[0]?.id as string,
desc: false,
},
]);
const [globalFilter, setGlobalFilter] = useState<string>('');
const columnFilters = useColumnFilters();

const filteredData = useMemo(
() => applyFilters(data || [], columnFilters.filters) as TData[],
[columnFilters.filters, data],
);
const table = useReactTable({
data: filteredData,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
globalFilter,
},
initialState: {
pagination: { pageSize: pageSize ?? 5 },
},
onGlobalFilterChange: (e) => {
setGlobalFilter(e);
},
globalFilterFn: 'auto',
});

const rows = useMemo(() => table.getRowModel()?.rows, [
table,
globalFilter,
columnFilters.filters,
data,
sorting,
]);

const contextValue: DataTableContextValue<TData> = {
table,
filtersDefinition,
columnFilters,
globalFilter,
data,
filteredData,
sorting,
rows,
};

return (
<DataTableContext.Provider value={contextValue}>
{children || (
<>
<DataTable />
<DataTablePagination />
</>
)}
</DataTableContext.Provider>
);
}

export function useDataTableContext<TData>() {
const context = useContext<DataTableContextValue<TData>>(DataTableContext);
if (!context) {
throw new Error(
'useDataTableContext must be used within a DataTableProvider',
);
}
return context as DataTableContextValue<TData>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ReactNode } from 'react';

const DatatableAction = ({ children }: { children: ReactNode }) => {
return <>{children || <></>}</>;
};

export default DatatableAction;
Loading

0 comments on commit 431b997

Please sign in to comment.