@ackplus/react-tanstack-data-table
Version:
A powerful React data table component built with MUI and TanStack Table
138 lines (137 loc) • 12.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ColumnCustomFilterControl = ColumnCustomFilterControl;
const jsx_runtime_1 = require("react/jsx-runtime");
const icons_material_1 = require("@mui/icons-material");
const material_1 = require("@mui/material");
const react_1 = require("react");
const menu_dropdown_1 = require("../droupdown/menu-dropdown");
const data_table_context_1 = require("../../contexts/data-table-context");
const icons_1 = require("../../icons");
const column_helpers_1 = require("../../utils/column-helpers");
const slot_helpers_1 = require("../../utils/slot-helpers");
const filters_1 = require("../filters");
const filter_value_input_1 = require("../filters/filter-value-input");
function ColumnCustomFilterControl() {
var _a, _b, _c;
const { table, slots, slotProps } = (0, data_table_context_1.useDataTableContext)();
const FilterIconSlot = (0, slot_helpers_1.getSlotComponent)(slots, 'filterIcon', icons_material_1.FilterList);
const customFilterState = ((_a = table.getCustomColumnFilterState) === null || _a === void 0 ? void 0 : _a.call(table)) || {
filters: [],
logic: 'AND',
pendingFilters: [],
pendingLogic: 'AND'
};
const filters = customFilterState.pendingFilters;
const filterLogic = customFilterState.pendingLogic;
const activeFiltersCount = ((_c = (_b = table.getActiveColumnFilters) === null || _b === void 0 ? void 0 : _b.call(table)) === null || _c === void 0 ? void 0 : _c.length) || 0;
const filterableColumns = (0, react_1.useMemo)(() => {
return table.getAllLeafColumns()
.filter(column => (0, column_helpers_1.isColumnFilterable)(column));
}, [table]);
const addFilter = (0, react_1.useCallback)((columnId, operator) => {
var _a, _b;
let defaultOperator = operator || '';
if (columnId && !operator) {
const column = filterableColumns.find(col => col.id === columnId);
const columnType = (0, column_helpers_1.getColumnType)(column);
const operators = filters_1.FILTER_OPERATORS[columnType] || filters_1.FILTER_OPERATORS.text;
defaultOperator = ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || 'contains';
}
(_b = table.addPendingColumnFilter) === null || _b === void 0 ? void 0 : _b.call(table, columnId || '', defaultOperator, '');
}, [table, filterableColumns]);
const handleAddFilter = (0, react_1.useCallback)(() => {
addFilter();
}, [addFilter]);
const updateFilter = (0, react_1.useCallback)((filterId, updates) => {
var _a;
(_a = table.updatePendingColumnFilter) === null || _a === void 0 ? void 0 : _a.call(table, filterId, updates);
}, [table]);
const removeFilter = (0, react_1.useCallback)((filterId) => {
var _a;
(_a = table.removePendingColumnFilter) === null || _a === void 0 ? void 0 : _a.call(table, filterId);
}, [table]);
const clearAllFilters = (0, react_1.useCallback)((closeDialog) => {
var _a;
(_a = table.clearAllPendingColumnFilters) === null || _a === void 0 ? void 0 : _a.call(table);
setTimeout(() => {
var _a;
(_a = table.applyPendingColumnFilters) === null || _a === void 0 ? void 0 : _a.call(table);
if (closeDialog) {
closeDialog();
}
}, 0);
}, [table]);
const handleLogicChange = (0, react_1.useCallback)((newLogic) => {
var _a;
(_a = table.setPendingFilterLogic) === null || _a === void 0 ? void 0 : _a.call(table, newLogic);
}, [table]);
const applyFilters = (0, react_1.useCallback)(() => {
var _a;
(_a = table.applyPendingColumnFilters) === null || _a === void 0 ? void 0 : _a.call(table);
}, [table]);
const handleApplyFilters = (0, react_1.useCallback)((closeDialog) => {
applyFilters();
closeDialog();
}, [applyFilters]);
const getOperatorsForColumn = (0, react_1.useCallback)((columnId) => {
const column = filterableColumns.find(col => col.id === columnId);
const type = (0, column_helpers_1.getColumnType)(column);
return filters_1.FILTER_OPERATORS[type] || filters_1.FILTER_OPERATORS.text;
}, [filterableColumns]);
const handleColumnChange = (0, react_1.useCallback)((filterId, newColumnId, currentFilter) => {
var _a;
const newColumn = filterableColumns.find(col => col.id === newColumnId);
const columnType = (0, column_helpers_1.getColumnType)(newColumn);
const operators = filters_1.FILTER_OPERATORS[columnType] || filters_1.FILTER_OPERATORS.text;
const currentOperatorValid = operators.some(op => op.value === currentFilter.operator);
const newOperator = currentOperatorValid ? currentFilter.operator : ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || '';
updateFilter(filterId, {
columnId: newColumnId,
operator: newOperator,
value: ['isEmpty', 'isNotEmpty'].includes(newOperator) ? '' : currentFilter.value,
});
}, [filterableColumns, updateFilter]);
const handleOperatorChange = (0, react_1.useCallback)((filterId, newOperator, currentFilter) => {
updateFilter(filterId, {
operator: newOperator,
value: ['isEmpty', 'isNotEmpty'].includes(newOperator) ? '' : currentFilter.value,
});
}, [updateFilter]);
const handleFilterValueChange = (0, react_1.useCallback)((filterId, value) => {
updateFilter(filterId, { value });
}, [updateFilter]);
const handleRemoveFilter = (0, react_1.useCallback)((filterId) => {
removeFilter(filterId);
}, [removeFilter]);
const pendingFiltersCount = filters.filter(f => {
if (!f.columnId || !f.operator)
return false;
if (['isEmpty', 'isNotEmpty'].includes(f.operator))
return true;
return f.value && f.value.toString().trim() !== '';
}).length;
const hasAppliedFilters = activeFiltersCount > 0;
const hasPendingChanges = pendingFiltersCount > 0 || (filters.length === 0 && hasAppliedFilters);
(0, react_1.useEffect)(() => {
var _a;
if (filters.length === 0 && filterableColumns.length > 0 && activeFiltersCount === 0) {
const firstColumn = filterableColumns[0];
const columnType = (0, column_helpers_1.getColumnType)(firstColumn);
const operators = filters_1.FILTER_OPERATORS[columnType] || filters_1.FILTER_OPERATORS.text;
const defaultOperator = ((_a = operators[0]) === null || _a === void 0 ? void 0 : _a.value) || 'contains';
addFilter(firstColumn.id, defaultOperator);
}
}, [filters.length, filterableColumns, addFilter, activeFiltersCount]);
return ((0, jsx_runtime_1.jsx)(menu_dropdown_1.MenuDropdown, { anchor: ((0, jsx_runtime_1.jsx)(material_1.Badge, { variant: "dot", color: "primary", invisible: activeFiltersCount === 0, children: (0, jsx_runtime_1.jsx)(material_1.IconButton, { size: "small", color: activeFiltersCount > 0 ? 'primary' : 'default', sx: {
flexShrink: 0,
}, children: (0, jsx_runtime_1.jsx)(FilterIconSlot, Object.assign({}, slotProps === null || slotProps === void 0 ? void 0 : slotProps.filterIcon)) }) })), children: ({ handleClose }) => ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: {
p: 2,
minWidth: 500,
maxWidth: 650,
}, children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { mb: 2 }, children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", alignItems: "center", spacing: 1, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "subtitle1", sx: { fontWeight: 'bold' }, children: "Column Filters" }), activeFiltersCount > 0 && ((0, jsx_runtime_1.jsx)(material_1.Chip, { size: "small", label: `${activeFiltersCount} active`, color: "primary", variant: "outlined" }))] }), (0, jsx_runtime_1.jsx)(material_1.Stack, { direction: "row", spacing: 1, children: (0, jsx_runtime_1.jsx)(material_1.Button, { size: "small", variant: "outlined", startIcon: (0, jsx_runtime_1.jsx)(icons_1.AddIcon, {}), onClick: handleAddFilter, children: "Add Filter" }) })] }), filters.length > 1 && ((0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { mb: 2 }, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: "Filter Logic:" }), (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", spacing: 1, children: [(0, jsx_runtime_1.jsx)(material_1.Button, { size: "small", variant: filterLogic === 'AND' ? 'contained' : 'outlined', onClick: () => handleLogicChange('AND'), children: "AND (All conditions)" }), (0, jsx_runtime_1.jsx)(material_1.Button, { size: "small", variant: filterLogic === 'OR' ? 'contained' : 'outlined', onClick: () => handleLogicChange('OR'), children: "OR (Any condition)" })] })] })), (0, jsx_runtime_1.jsx)(material_1.Divider, { sx: { mb: 2 } }), filters.length === 0 ? ((0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", sx: {
textAlign: 'center',
py: 2,
}, children: "No filters applied. Click \"Add Filter\" to start." })) : ((0, jsx_runtime_1.jsx)(material_1.Stack, { spacing: 2, children: filters.map((filter) => ((0, jsx_runtime_1.jsx)(material_1.Box, { children: (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", spacing: 1, alignItems: "center", children: [(0, jsx_runtime_1.jsxs)(material_1.FormControl, { size: "small", sx: { minWidth: 140 }, children: [(0, jsx_runtime_1.jsx)(material_1.InputLabel, { children: "Column" }), (0, jsx_runtime_1.jsx)(material_1.Select, { value: filter.columnId, label: "Column", onChange: (e) => handleColumnChange(filter.id, e.target.value, filter), children: filterableColumns.map(column => ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: column.id, children: typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id }, column.id))) })] }), (0, jsx_runtime_1.jsxs)(material_1.FormControl, { size: "small", sx: { minWidth: 160 }, disabled: !filter.columnId, children: [(0, jsx_runtime_1.jsx)(material_1.InputLabel, { children: "Operator" }), (0, jsx_runtime_1.jsx)(material_1.Select, { value: filter.operator, label: "Operator", onChange: (e) => handleOperatorChange(filter.id, e.target.value, filter), children: getOperatorsForColumn(filter.columnId).map(op => ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: op.value, children: op.label }, op.value))) })] }), !['isEmpty', 'isNotEmpty'].includes(filter.operator) ? ((0, jsx_runtime_1.jsx)(filter_value_input_1.FilterValueInput, { filter: filter, column: filterableColumns.find(col => col.id === filter.columnId), onValueChange: (value) => handleFilterValueChange(filter.id, value) })) : ((0, jsx_runtime_1.jsx)(material_1.Box, { sx: { flex: 1 } })), (0, jsx_runtime_1.jsx)(material_1.IconButton, { size: "small", onClick: () => handleRemoveFilter(filter.id), color: "error", children: (0, jsx_runtime_1.jsx)(icons_1.DeleteIcon, {}) })] }) }, filter.id))) })), (0, jsx_runtime_1.jsx)(material_1.Divider, { sx: { my: 2 } }), (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", spacing: 1, children: [(hasAppliedFilters || filters.length > 0) && ((0, jsx_runtime_1.jsx)(material_1.Button, { size: "small", variant: "text", color: "warning", onClick: () => clearAllFilters(handleClose), startIcon: (0, jsx_runtime_1.jsx)(icons_1.DeleteIcon, {}), children: "Reset" })), !(hasAppliedFilters || filters.length > 0) && (0, jsx_runtime_1.jsx)(material_1.Box, {}), (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", spacing: 1, children: [(0, jsx_runtime_1.jsx)(material_1.Button, { variant: "outlined", onClick: handleClose, children: "Close" }), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", onClick: () => handleApplyFilters(handleClose), disabled: !hasPendingChanges, children: pendingFiltersCount === 0 && hasAppliedFilters ? 'Clear All Filters' :
`Apply ${pendingFiltersCount} Filter${pendingFiltersCount !== 1 ? 's' : ''}${pendingFiltersCount > 1 ? ` (${filterLogic})` : ''}` })] })] })] })) }));
}