import { html, nothing, type TemplateResult } from 'lit';
import type { NuiType } from '../../types/nui-type.js';
import {
filterRows,
getFilterOptionsForColumn,
isColumnFilterable,
} from './data-table-filters.js';
import {
getCellValue,
getCurrentPage,
getPageCount,
paginateRows,
resolveColumns,
sortRows,
} from './data-table-state.js';
import type {
DataTableColumn,
DataTableFilters,
DataTableRow,
DataTableSize,
DataTableSortOrder,
} from './types.js';
export interface NuiDataTableViewState {
value: DataTableRow[];
columns: DataTableColumn[];
dataKey: string;
rows: number;
first: number;
paginator: boolean;
filter: boolean;
filters: DataTableFilters;
showHeaders: boolean;
stripedRows: boolean;
rowHover: boolean;
loading: boolean;
sortField: string;
sortOrder: DataTableSortOrder;
emptyMessage: string;
size: DataTableSize;
nuiType: NuiType;
dataTableClass: string;
}
export interface DataTableRenderHandlers {
onSort: (field: string) => void;
onRowClick: (row: DataTableRow, index: number) => void;
onPageChange: (first: number) => void;
onFilterChange: (field: string, value: string) => void;
onFilterSelectOpen: (event: Event) => void;
}
export function getDataTableClass(dataTableClass: string): string {
return ['nui-data-table', dataTableClass].filter(Boolean).join(' ');
}
function renderSortIcon(
field: string,
sortField: string,
sortOrder: DataTableSortOrder,
): TemplateResult | typeof nothing {
if (field !== sortField || sortOrder === 0) {
return nothing;
}
return html`
${sortOrder === 1 ? '▲' : '▼'}
`;
}
function renderFilterControl(
state: NuiDataTableViewState,
column: DataTableColumn,
handlers: DataTableRenderHandlers,
): TemplateResult | typeof nothing {
if (!isColumnFilterable(column, state.filter)) {
return nothing;
}
const value = state.filters[column.field] ?? '';
if (column.filterType === 'select') {
const options = getFilterOptionsForColumn(state.value, column);
return html`
({ label: option, value: option })),
]}
placeholder="All"
fluid
@click=${(event: Event) => event.stopPropagation()}
@nui-select-open=${handlers.onFilterSelectOpen}
@nui-change=${(event: Event) => {
handlers.onFilterChange(
column.field,
(event as CustomEvent<{ value: string }>).detail.value,
);
}}
>
`;
}
return html`
event.stopPropagation()}
@input=${(event: Event) => {
handlers.onFilterChange(
column.field,
(event as CustomEvent<{ value: string }>).detail.value,
);
}}
>
`;
}
function renderFilterRow(
state: NuiDataTableViewState,
columns: DataTableColumn[],
handlers: DataTableRenderHandlers,
): TemplateResult | typeof nothing {
if (!state.filter) {
return nothing;
}
const hasFilterableColumn = columns.some((column) =>
isColumnFilterable(column, state.filter),
);
if (!hasFilterableColumn) {
return nothing;
}
return html`
${columns.map(
(column) => html`
|
${renderFilterControl(state, column, handlers)}
|
`,
)}
`;
}
function renderHeader(
state: NuiDataTableViewState,
columns: DataTableColumn[],
handlers: DataTableRenderHandlers,
): TemplateResult {
return html`
${columns.map(
(column) => html`
| {
if (column.sortable !== false) {
handlers.onSort(column.field);
}
}}
>
|
`,
)}
${renderFilterRow(state, columns, handlers)}
`;
}
function renderBody(
state: NuiDataTableViewState,
columns: DataTableColumn[],
rows: DataTableRow[],
handlers: DataTableRenderHandlers,
): TemplateResult {
if (rows.length === 0) {
return html`
|
${state.emptyMessage}
|
`;
}
return html`
${rows.map((row, index) => {
const key = getCellValue(row, state.dataKey) || String(index);
return html`
handlers.onRowClick(row, index)}
>
${columns.map(
(column) => html`
|
${getCellValue(row, column.field)}
|
`,
)}
`;
})}
`;
}
function renderPaginator(
state: NuiDataTableViewState,
totalRecords: number,
handlers: DataTableRenderHandlers,
): TemplateResult {
const pageCount = getPageCount(totalRecords, state.rows);
const currentPage = getCurrentPage(state.first, state.rows);
const atStart = state.first <= 0;
const atEnd = state.first + state.rows >= totalRecords;
return html`
${currentPage} / ${pageCount}
`;
}
export function renderDataTable(
state: NuiDataTableViewState,
handlers: DataTableRenderHandlers,
): TemplateResult {
if (state.loading) {
return html`
`;
}
if (!state.value || state.value.length === 0) {
return html`
`;
}
const columns = resolveColumns(state.value, state.columns);
const filteredRows = filterRows(
state.value,
state.filters,
columns,
state.filter,
);
const sortedRows = sortRows(filteredRows, state.sortField, state.sortOrder);
const visibleRows = state.paginator
? paginateRows(sortedRows, state.first, state.rows)
: sortedRows;
return html`
${state.showHeaders ? renderHeader(state, columns, handlers) : nothing}
${renderBody(state, columns, visibleRows, handlers)}
${
state.paginator
? renderPaginator(state, sortedRows.length, handlers)
: nothing
}
`;
}