import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; import { cn } from '../../utils/cn'; import { BaseTableData, TableColumn, FilterConfig, GlobalSearchConfig, ExportFormat, TableThemeConfig, TableSize } from '../../types'; import { Filter, MoreVertical, Refresh } from '../../icons'; import ColumnVisibilityToggle from './ColumnVisibilityToggle'; import AdvancedFilterPanel, { FilterPreset } from './AdvancedFilterPanel'; import { useSafeTableSettings } from '../../hooks/useSafeTableSettings'; import { TableSettings as TableSettingsType } from '../../hooks/useTableSettings'; import TableSettings from './TableSettings'; export interface TableToolbarProps { className?: string; title?: string; description?: string; // Global search searchValue?: string; onSearch?: (value: string) => void; searchPlaceholder?: string; isSearchActive?: boolean; onClearSearch?: () => void; // Filters filters?: FilterConfig[]; activeFilters?: Record; filterValues?: Record; onFilterChange?: (field: string, value: any) => void; onClearFilters?: () => void; hasActiveFilters?: boolean; // Advanced filtering options advancedFiltering?: { enabled?: boolean; allowPresets?: boolean; allowComplexFilters?: boolean; initialPresets?: FilterPreset[]; onPresetsChange?: (presets: FilterPreset[]) => void; persistKey?: string; }; // Selection selectedCount?: number; isAllSelected?: boolean; isSomeSelected?: boolean; onSelectAll?: () => void; onSelectNone?: () => void; bulkActions?: { label: string; value: string; icon?: React.ReactNode }[]; onBulkAction?: (action: string) => void; // Export export?: { enabled: boolean } | undefined; exportFormats?: ExportFormat[]; onExport?: (format: ExportFormat) => void; onExportCurrentView?: () => void; onExportSelected?: () => void; // Refresh onRefresh?: () => void; // Column visibility columns?: TableColumn[]; visibleColumns?: TableColumn[]; onToggleColumn?: (columnId: string) => void; onHideAllColumns?: () => void; onShowAllColumns?: () => void; onResetColumns?: () => void; hasCreated?: boolean; createButton?: React.ReactNode; // Column visibility object columnVisibility?: Record; // Size variants size?: 'sm' | 'md' | 'lg'; // Visual variants variant?: 'default' | 'bg-gray' | 'borderless' | 'shadow'; // Table settings onUpdateTableSettings?: (key: K, value: TableSettingsType[K]) => void; onTableSettingsChange?: (settings: TableSettingsType) => void; tableTheme?: TableThemeConfig; tableSize?: TableSize; tableStriped?: boolean; tableHover?: boolean; tableBordered?: boolean; tableStickyHeader?: boolean; // Table Identifier /** * Current table identifier used for saving settings */ currentTableIdentifier?: string; /** * Callback when a new table identifier is saved */ onSaveTableIdentifier?: (identifier: string) => void; /** * Selected rows data */ selectedRows?: T[]; } export function TableToolbar({ className, title, description, // Global search searchValue = '', onSearch, searchPlaceholder = 'Search...', isSearchActive, onClearSearch, // Filters filters = [], activeFilters = {}, filterValues = {}, onFilterChange, onClearFilters, hasActiveFilters, // Advanced filtering advancedFiltering = { enabled: false }, // Selection selectedCount = 0, isAllSelected, isSomeSelected, onSelectAll, onSelectNone, bulkActions = [], onBulkAction, // Export export: exportConfig, exportFormats = ['csv', 'excel', 'pdf'], onExport, onExportCurrentView, onExportSelected, // Refresh onRefresh, // Column visibility columns = [], visibleColumns = [], onToggleColumn, onHideAllColumns, onShowAllColumns, onResetColumns, hasCreated = false, createButton, // Column visibility object columnVisibility, // Size variants size = 'md', // Visual variants variant = 'default', // Table settings onUpdateTableSettings, onTableSettingsChange, tableTheme, tableSize, tableStriped, tableHover, tableBordered, tableStickyHeader, // Table identifier currentTableIdentifier, onSaveTableIdentifier, // Selected rows and batch actions selectedRows = [], }: TableToolbarProps): React.ReactElement { const [showFilters, setShowFilters] = useState(false); const [showExportOptions, setShowExportOptions] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [showBatchActions, setShowBatchActions] = useState(false); const [batchActionLoading, setBatchActionLoading] = useState(false); // State for advanced filter panel const [isAdvancedFilterExpanded, setIsAdvancedFilterExpanded] = useState(false); // Initialize table settings hook with useSafeTableSettings to prevent SSR issues const { settings, updateSetting, resetSettings, getThemeConfig, getCurrentStorageKey, saveSettingsWithIdentifier, isClient } = useSafeTableSettings({ initialSettings: { size: tableSize, theme: tableTheme?.theme, variant: tableTheme?.variant, striped: tableStriped, hover: tableHover, bordered: tableBordered, sticky: tableStickyHeader, colorScheme: tableTheme?.colorScheme, borderRadius: tableTheme?.borderRadius }, tableIdentifier: currentTableIdentifier }); // Create fallback implementations for missing methods const updateSettings = useCallback((newSettings: Partial) => { if (newSettings) { // Update each setting individually since updateSettings is not available Object.entries(newSettings).forEach(([key, value]) => { updateSetting(key as keyof TableSettingsType, value); }); } }, [updateSetting]); // Find all saved configurations for this table const savedConfigurations = useMemo(() => { // Only run on client-side if (!isClient) return []; // Fallback implementation for getSavedConfigurations try { const configs: { id: string; name?: string }[] = []; const baseKeyPrefix = 'rpt-settings'; // Skip if we're not in a browser environment if (typeof window === 'undefined') return []; // Get the pathname part of our current key const currentKey = getCurrentStorageKey(); if (!currentKey) return []; const keyParts = currentKey.split('-'); // We want everything except the last part (which is the identifier) const basePath = keyParts.length > 1 ? keyParts.slice(0, -1).join('-') : currentKey; // Find all keys that match our base path for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (!key || !key.startsWith(baseKeyPrefix)) continue; // Check if this key is for the same table (same path) if (key.startsWith(`${basePath}-`) || key === basePath) { // Extract the identifier (last part of key) const keyParts = key.split('-'); const identifier = keyParts[keyParts.length - 1]; configs.push({ id: identifier, name: identifier // We could add custom names in the future }); } } return configs; } catch (error) { console.warn('Error fetching saved configurations:', error); return []; } }, [getCurrentStorageKey, isClient]); const hasSelectedRows = selectedCount > 0; const isExportEnabled = exportConfig?.enabled !== false; const isAdvancedFilterEnabled = advancedFiltering?.enabled === true && filters.length > 0; // Determine if we're using standard or advanced filtering const useStandardFiltering = filters.length > 0 && !isAdvancedFilterEnabled; const useAdvancedFiltering = filters.length > 0 && isAdvancedFilterEnabled; // Refs for dropdown menus const filterDropdownRef = useRef(null); const exportDropdownRef = useRef(null); // Close dropdowns when clicking outside useEffect(() => { function handleClickOutside(event: MouseEvent) { // Close filter dropdown if ( showFilters && filterDropdownRef.current && !filterDropdownRef.current.contains(event.target as Node) ) { setShowFilters(false); } // Close export dropdown if ( showExportOptions && exportDropdownRef.current && !exportDropdownRef.current.contains(event.target as Node) ) { setShowExportOptions(false); } } document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [showFilters, showExportOptions]); // Show batch actions when rows are selected useEffect(() => { setShowBatchActions(selectedRows.length > 0); }, [selectedRows]); // Handle refresh with animation const handleRefresh = () => { if (onRefresh && !isRefreshing) { setIsRefreshing(true); onRefresh(); // Reset refreshing state after animation setTimeout(() => { setIsRefreshing(false); }, 1000); } }; // Handle table settings update const handleUpdateSetting = useCallback((key: K, value: TableSettingsType[K]) => { // Update local settings updateSetting?.(key, value); // Call parent handler if provided if (onUpdateTableSettings) { onUpdateTableSettings(key, value); } // Notify about all settings change if (onTableSettingsChange && settings) { // Get updated settings and pass to callback const updatedSettings = { ...settings, [key]: value }; onTableSettingsChange(updatedSettings); } }, [updateSetting, onUpdateTableSettings, onTableSettingsChange, settings]); // Handle saving table identifier const handleSaveTableIdentifier = useCallback((identifier: string) => { if (onSaveTableIdentifier) { onSaveTableIdentifier(identifier); } }, [onSaveTableIdentifier]); // Handle saving all table settings with tableIdentifier const handleSaveAllSettings = useCallback((identifier: string) => { if (!identifier.trim() || !isClient) return; try { // Save settings with new identifier using the hook function const success = saveSettingsWithIdentifier?.(identifier); if (success) { // Notify parent component about identifier change if (onSaveTableIdentifier) { onSaveTableIdentifier(identifier); } // Show success message to the user alert('Settings have been saved successfully! The page will reload to apply changes.'); // Reload the page after a short delay to apply settings setTimeout(() => { window.location.reload(); }, 300); } else { alert('Failed to save settings. Please try again.'); } } catch (error) { console.error('Error saving table settings:', error); alert('An error occurred while saving settings.'); } }, [saveSettingsWithIdentifier, onSaveTableIdentifier, isClient]); // Calculate active filter count const activeFilterCount = useMemo(() => { if (!filterValues) return 0; return Object.values(filterValues).filter(v => v !== undefined && v !== null && v !== '').length; }, [filterValues]); // Extract table identifier from the storage key if not provided explicitly const effectiveTableIdentifier = useMemo(() => { // Only extract table identifier on client-side if (!isClient) return currentTableIdentifier || ''; // Directly use currentTableIdentifier if provided if (currentTableIdentifier) return currentTableIdentifier; // Try to extract it from storage key if available try { const key = getCurrentStorageKey(); if (key) { const parts = key.split('-'); // Last part is typically the identifier if (parts.length > 1) { return parts[parts.length - 1]; } } } catch (e) { console.warn("Error extracting table identifier:", e); } return ''; // Fallback to empty string }, [currentTableIdentifier, isClient, getCurrentStorageKey]); return ( <>
{/* Main toolbar content */} {/* Left section: Title and Description */}
{title &&

{title}

} {description &&

{description}

}
{/* Right section: Search, Filters, and Actions */}
{/* Standard Filter button */} {useStandardFiltering && onFilterChange && (
{showFilters && (

Filters

{activeFilterCount > 0 && onClearFilters && (
)}
{filters.map((filter) => (
{filter.label}
{filter.type === 'select' && ( )} {filter.type === 'text' && ( onFilterChange(filter.key, e.target.value)} placeholder={filter.placeholder || `Filter by ${filter.label}`} /> )} {filter.type === 'number' && ( onFilterChange(filter.key, e.target.value === '' ? '' : Number(e.target.value))} placeholder={filter.placeholder || `Filter by ${filter.label}`} /> )} {filter.type === 'date' && ( onFilterChange(filter.key, e.target.value)} /> )} {filter.type === 'boolean' && ( )} {(filterValues[filter.key] !== undefined && filterValues[filter.key] !== '' && filterValues[filter.key] !== null) && ( )}
))}
)}
)} {/* Global search */}
{onSearch && ( <> onSearch(e.target.value)} placeholder={searchPlaceholder} />
{searchValue && ( )} )}
{/* Table Settings - Only render when client-side */} {isClient && ( )} {/* Toolbar section with buttons */}
{/* Create button */} {createButton && (
{createButton}
)} {/* Refresh button */} {onRefresh && ( )} {/* Export */} {isExportEnabled && onExport && (
{showExportOptions && (

Export data

{onExportCurrentView && ( )} {exportFormats.map((format) => ( ))}
)}
)} {/* Column visibility */} {columns.length > 0 && onToggleColumn && (
col.enableHiding !== false)} visibleColumns={visibleColumns} onToggleColumn={onToggleColumn} onShowAllColumns={onShowAllColumns} onHideAllColumns={onHideAllColumns} onResetColumns={onResetColumns} columnVisibility={columnVisibility} />
)}
{/* Advanced Filter Panel - shown below the toolbar */} {useAdvancedFiltering && onFilterChange && ( )}
); } export default TableToolbar;