ywana-core8 0.1.78 → 0.1.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/index.cjs +3244 -2215
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +2127 -1063
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.modern.js +3244 -2215
  6. package/dist/index.modern.js.map +1 -1
  7. package/dist/index.umd.js +3244 -2215
  8. package/dist/index.umd.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/html/ExampleLayout.css +401 -0
  11. package/src/html/ExampleLayout.js +192 -0
  12. package/src/html/README-sidebar-navigation.md +195 -0
  13. package/src/html/accordion.example.js +123 -4
  14. package/src/html/accordion.example.js.backup +390 -0
  15. package/src/html/button.example.js +50 -3
  16. package/src/html/button.example.js.backup +374 -0
  17. package/src/html/button.example.new.js +416 -0
  18. package/src/html/checkbox.example.js +93 -4
  19. package/src/html/checkbox.example.js.backup +316 -0
  20. package/src/html/chip.example.js +108 -4
  21. package/src/html/chip.example.js.backup +355 -0
  22. package/src/html/color.example.js +108 -4
  23. package/src/html/color.example.js.backup +527 -0
  24. package/src/html/components.example.js +123 -4
  25. package/src/html/components.example.js.backup +492 -0
  26. package/src/html/convert-examples.js +183 -0
  27. package/src/html/demo-sidebar.html +410 -0
  28. package/src/html/form.example.js +93 -4
  29. package/src/html/form.example.js.backup +385 -0
  30. package/src/html/header2.example.js +108 -4
  31. package/src/html/header2.example.js.backup +411 -0
  32. package/src/html/icon.example.js +77 -3
  33. package/src/html/icon.example.js.backup +268 -0
  34. package/src/html/list.example.js +93 -4
  35. package/src/html/list.example.js.backup +404 -0
  36. package/src/html/progress.example.js +74 -4
  37. package/src/html/progress.example.js.backup +424 -0
  38. package/src/html/property.example.js +123 -4
  39. package/src/html/property.example.js.backup +553 -0
  40. package/src/html/radio.example.js +108 -4
  41. package/src/html/radio.example.js.backup +389 -0
  42. package/src/html/section.example.js +42 -3
  43. package/src/html/section.example.js.backup +99 -0
  44. package/src/html/switch.example.js +108 -4
  45. package/src/html/switch.example.js.backup +461 -0
  46. package/src/html/tab.example.js +93 -4
  47. package/src/html/tab.example.js.backup +446 -0
  48. package/src/html/table-export-utils.js +483 -0
  49. package/src/html/table-summary-functions.js +363 -0
  50. package/src/html/table2.css +1496 -432
  51. package/src/html/table2.example.js +2937 -512
  52. package/src/html/table2.example.js.broken +1226 -0
  53. package/src/html/table2.js +1426 -1000
  54. package/src/html/test-resize.html +279 -0
  55. package/src/html/test-selection.html +387 -0
  56. package/src/html/textfield2.example.js +108 -4
  57. package/src/html/textfield2.example.js.backup +1370 -0
  58. package/src/html/tokenfield.example.js +108 -4
  59. package/src/html/tokenfield.example.js.backup +503 -0
  60. package/src/html/tree.css +2 -4
  61. package/src/html/tree.example.js +93 -4
  62. package/src/html/tree.example.js.backup +475 -0
  63. package/src/html/tree.js +19 -3
@@ -1,516 +1,1136 @@
1
- import React, { Fragment, useState, useCallback, useRef, useEffect, useMemo } from 'react'
1
+ import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { FORMATS, TYPES } from '../domain/ContentType'
4
- import { DropDown2 as DropDown, TextField2 as TextField } from './textfield2'
5
- import { CheckBox } from './checkbox'
6
3
  import { Icon } from './icon'
7
4
  import { Text } from './text'
8
- import { EmptyMessage } from '../widgets/empty/EmptyMessage'
9
- import { Uploader } from '../widgets/upload/Uploader'
5
+ import { CheckBox } from './checkbox'
6
+ import { TextField, DropDown } from './textfield'
7
+ import { calculateColumnSummary } from './table-summary-functions'
8
+ import { exportToCSV, exportToJSON, exportToExcel, exportToXML, exportSelected, getExportStats, validateExportData, exportFormats } from './table-export-utils'
10
9
  import './table2.css'
11
10
 
12
11
  const isFunction = value => value && (Object.prototype.toString.call(value) === "[object Function]" || "function" === typeof value || value instanceof Function);
13
12
 
13
+
14
14
  /**
15
- * Enhanced DataTable v2 - 100% Compatible with original DataTable
16
- *
17
- * This is a completely rewritten version of DataTable that maintains 100% API compatibility
18
- * while adding significant performance improvements, accessibility, and new features.
15
+ * DataTable2 - Reimplemented from scratch with enhanced features
16
+ * Compatible with original DataTable while adding modern functionality
19
17
  */
20
18
  export const DataTable2 = (props) => {
21
19
  const {
22
- // Original props (100% compatible)
23
- columns = [],
24
- rows = [],
25
- onRowSelection,
26
- onSort,
27
- onCheckAll,
28
- editable,
29
- outlined,
30
- expanded = false,
20
+ id,
21
+ columns = [],
22
+ rows = [],
23
+ sortDir: externalSortDir,
24
+ onSort,
25
+ onSelect,
26
+ onRowSelection, // Compatibilidad con table.js
27
+ onCheckAll,
28
+ allChecked = false,
29
+ showSelectAll = false,
30
+ showRowNumbers = false,
31
+ editable = false,
32
+ expanded = false,
33
+ selectedRows = [],
34
+ onRowDoubleClick,
35
+ onRowContextMenu,
36
+ onCellClick,
37
+ onCellDoubleClick,
38
+ onCellEdit,
39
+ onDrop,
31
40
  className,
32
41
  emptyMessage = "No Results Found",
33
42
  emptyIcon = "search_off",
34
43
  multisort = false,
35
44
  filterable = false,
36
45
  onClearFilters,
37
- // New enhanced props (all optional for compatibility)
46
+ // Enhanced props
38
47
  loading = false,
39
48
  skeleton = false,
40
49
  striped = false,
41
50
  hover = true,
42
- compact = false,
43
51
  bordered = false,
44
- responsive = true,
45
- stickyHeader = true,
46
- virtualScrolling = false,
47
- pageSize = 50,
48
- selectionMode = 'single', // 'single', 'multiple', 'none'
49
- sortMode = 'single', // 'single', 'multiple', 'none'
50
- resizable = false,
51
- reorderable = false,
52
+ size = 'medium',
53
+ density = 'normal',
54
+ theme = 'default',
55
+ readability = null, // 'large', 'contrast', 'dyslexia', 'print', 'compact', 'focus', 'dark'
56
+ stickyHeader = false,
57
+ resizable = true,
58
+ showSidebar = false,
59
+ sidebarId = null, // ID personalizable para localStorage
60
+ sidebarTools = [], // Array de herramientas para la toolbar
52
61
  exportable = false,
53
62
  searchable = false,
54
- searchPlaceholder = "Search...",
55
- onSearch,
56
- onExport,
57
- onColumnResize,
58
- onColumnReorder,
59
- customEmptyState,
60
- rowHeight = 'medium', // 'small', 'medium', 'large'
61
- density = 'normal', // 'compact', 'normal', 'comfortable'
62
- theme = 'default', // 'default', 'dark', 'minimal'
63
- accessibility = true,
64
- onRowClick,
65
- onRowDoubleClick,
66
- onRowContextMenu,
67
- onCellClick,
68
- onCellDoubleClick,
69
- onCellEdit,
63
+ pagination = null,
64
+ customEmptyState = null,
70
65
  rowClassName,
71
66
  cellClassName,
72
67
  headerClassName,
73
- footerContent,
74
- showRowNumbers = false,
75
- showSelectAll = true,
76
- persistState = false,
77
- stateKey,
78
- onStateChange,
68
+ accessibility = true,
69
+ height = null, // Altura específica (ej: "400px", "100%")
70
+ maxHeight = null, // Altura máxima (ej: "500px")
71
+ minHeight = null, // Altura mínima (ej: "200px")
72
+ onColumnResize,
73
+ onExport,
74
+ onSearch,
79
75
  ...restProps
80
76
  } = props
81
77
 
82
- // State management
83
- const [sortDir, setSortDir] = useState({})
84
- const [allChecked, setAllChecked] = useState(false)
85
- const [searchTerm, setSearchTerm] = useState('')
86
- const [selectedRows, setSelectedRows] = useState(new Set())
87
- const [isLoading, setIsLoading] = useState(loading)
88
- const [currentPage, setCurrentPage] = useState(0)
89
-
90
- // Refs
91
- const tableRef = useRef(null)
92
-
93
- // Validate props
94
- useEffect(() => {
95
- if (!Array.isArray(columns)) {
96
- console.warn('DataTable2: columns prop must be an array')
78
+ // Column widths state with localStorage persistence
79
+ const [columnWidths, setColumnWidths] = useState(() => {
80
+ const storageKey = `datatable2-widths-${id || 'default'}`
81
+ const saved = localStorage.getItem(storageKey)
82
+ return saved ? JSON.parse(saved) : {}
83
+ })
84
+
85
+ // Expanded rows state
86
+ const [expandedRows, setExpandedRows] = useState(() => {
87
+ if (expanded) {
88
+ return new Set(rows.filter(row => row.info).map(row => row.id))
97
89
  }
98
- if (!Array.isArray(rows)) {
99
- console.warn('DataTable2: rows prop must be an array')
90
+ return new Set()
91
+ })
92
+
93
+ // Sorting state - usar externo si se proporciona, sino interno
94
+ const [internalSortDir, setInternalSortDir] = useState({})
95
+ const sortDir = externalSortDir || internalSortDir
96
+
97
+ // Sidebar state - ahora maneja herramientas múltiples
98
+ const [activeTool, setActiveTool] = useState(null) // null = cerrado, string = herramienta activa
99
+ const sidebarOpen = activeTool !== null
100
+
101
+ // Column visibility state with localStorage persistence
102
+ const storageKey = `datatable2-columns-${sidebarId || id || 'default'}`
103
+ const [visibleColumns, setVisibleColumns] = useState(() => {
104
+ if (!showSidebar) return columns.map(col => col.id)
105
+
106
+ const saved = localStorage.getItem(storageKey)
107
+ if (saved) {
108
+ try {
109
+ const parsed = JSON.parse(saved)
110
+ // Verificar que las columnas guardadas existen en las columnas actuales
111
+ const currentColumnIds = columns.map(col => col.id)
112
+ return parsed.filter(id => currentColumnIds.includes(id))
113
+ } catch (e) {
114
+ console.warn('Error parsing saved column visibility:', e)
115
+ }
100
116
  }
101
- if (virtualScrolling && !pageSize) {
102
- console.warn('DataTable2: pageSize is required when virtualScrolling is enabled')
117
+ // Por defecto, todas las columnas visibles
118
+ return columns.map(col => col.id)
119
+ })
120
+
121
+ // Handle sorting
122
+ const handleSort = useCallback((columnId) => {
123
+ if (!externalSortDir) {
124
+ // Solo manejar sorting interno si no hay sortDir externo
125
+ setInternalSortDir(prev => {
126
+ const currentDir = prev[columnId] || 0
127
+ const nextDir = currentDir === 0 ? 1 : currentDir === 1 ? -1 : 0
128
+ const newSortDir = nextDir === 0 ? {} : { [columnId]: nextDir }
129
+ return newSortDir
130
+ })
103
131
  }
104
- }, [columns, rows, virtualScrolling, pageSize])
105
132
 
106
- // Sync loading state
107
- useEffect(() => {
108
- setIsLoading(loading)
109
- }, [loading])
133
+ // Llamar callback externo si existe
134
+ if (onSort) {
135
+ const currentDir = sortDir[columnId] || 0
136
+ const nextDir = currentDir === 0 ? 1 : currentDir === 1 ? -1 : 0
137
+ onSort(columnId, nextDir)
138
+ }
139
+ }, [externalSortDir, onSort, sortDir])
140
+
141
+ // Handle column visibility
142
+ const handleColumnVisibilityChange = useCallback((columnId, visible) => {
143
+ setVisibleColumns(prev => {
144
+ const newVisible = visible
145
+ ? [...prev, columnId]
146
+ : prev.filter(id => id !== columnId)
147
+
148
+ // Guardar en localStorage
149
+ if (showSidebar) {
150
+ localStorage.setItem(storageKey, JSON.stringify(newVisible))
151
+ }
110
152
 
111
- // Enhanced sort function (maintaining original behavior)
112
- const changeSort = useCallback((id) => {
113
- if (sortMode === 'none') return
153
+ return newVisible
154
+ })
155
+ }, [showSidebar, storageKey])
156
+
157
+ // Handle tool selection
158
+ const handleToolSelect = useCallback((toolId) => {
159
+ console.log('handleToolSelect called with toolId:', toolId)
160
+ setActiveTool(prev => {
161
+ const newTool = prev === toolId ? null : toolId
162
+ console.log('Tool changed from', prev, 'to', newTool)
163
+ return newTool
164
+ })
165
+ }, [])
114
166
 
115
- if (sortMode === 'multiple' || multisort) {
116
- const nextDir = sortDir[id] ? sortDir[id] * -1 : 1
117
- const next = Object.assign({}, sortDir, { [id]: nextDir })
118
- setSortDir(next)
119
- } else {
120
- const nextDir = sortDir[id] ? sortDir[id] * -1 : 1
121
- setSortDir({ [id]: nextDir })
167
+ // Close sidebar
168
+ const closeSidebar = useCallback(() => {
169
+ setActiveTool(null)
170
+ }, [])
171
+
172
+ // Default tools - herramientas por defecto
173
+ const defaultTools = useMemo(() => [
174
+ {
175
+ id: 'columns',
176
+ icon: 'person',
177
+ title: 'Configurar columnas',
178
+ component: ColumnsPanel
179
+ },
180
+ {
181
+ id: 'export',
182
+ icon: 'person',
183
+ title: 'Exportar datos',
184
+ component: ExportPanel
185
+ },
186
+ ], [])
187
+
188
+ // Merge custom tools with default ones
189
+ const allTools = useMemo(() => {
190
+ const tools = [...defaultTools]
191
+ if (sidebarTools && sidebarTools.length > 0) {
192
+ tools.push(...sidebarTools)
122
193
  }
123
- }, [sortDir, sortMode, multisort])
194
+ return tools
195
+ }, [defaultTools, sidebarTools])
196
+
197
+ // Handle row expansion
198
+ const handleRowExpand = useCallback((rowId) => {
199
+ setExpandedRows(prev => {
200
+ const newSet = new Set(prev)
201
+ if (newSet.has(rowId)) {
202
+ newSet.delete(rowId)
203
+ } else {
204
+ newSet.add(rowId)
205
+ }
206
+ return newSet
207
+ })
208
+ }, [])
124
209
 
125
- // Enhanced multiSort function (maintaining original logic)
126
- const multiSort = useCallback((array, sortObject = {}) => {
127
- const sortKeys = Object.keys(sortObject);
210
+ // Save column widths to localStorage
211
+ const saveColumnWidths = useCallback((widths) => {
212
+ const storageKey = `datatable2-widths-${id || 'default'}`
213
+ localStorage.setItem(storageKey, JSON.stringify(widths))
214
+ setColumnWidths(widths)
215
+ }, [id])
216
+
217
+ // Handle column resize
218
+ const handleColumnResize = useCallback((columnId, width) => {
219
+ const newWidths = { ...columnWidths, [columnId]: width }
220
+ saveColumnWidths(newWidths)
221
+ if (onColumnResize) onColumnResize(columnId, width)
222
+ }, [columnWidths, saveColumnWidths, onColumnResize])
223
+
224
+ // Calculate dynamic height styles
225
+ const containerStyles = useMemo(() => {
226
+ const styles = {}
227
+
228
+ // Solo aplicar altura al contenedor principal si es necesario
229
+ if (height) {
230
+ styles.height = height
231
+ }
128
232
 
129
- if (!sortKeys.length) {
130
- return array;
233
+ if (maxHeight) {
234
+ styles.maxHeight = maxHeight
131
235
  }
132
236
 
237
+ if (minHeight) {
238
+ styles.minHeight = minHeight
239
+ }
240
+
241
+ return styles
242
+ }, [height, maxHeight, minHeight])
243
+
244
+ const tableContainerStyles = useMemo(() => {
245
+ const styles = {}
246
+
247
+ // Si hay altura específica, aplicar scroll a la tabla
248
+ if (height) {
249
+ styles.height = height
250
+ styles.overflow = 'auto'
251
+ } else if (maxHeight) {
252
+ styles.maxHeight = maxHeight
253
+ styles.overflow = 'auto'
254
+ }
255
+
256
+ if (minHeight) {
257
+ styles.minHeight = minHeight
258
+ }
259
+
260
+ return styles
261
+ }, [height, maxHeight, minHeight])
262
+
263
+ // Generate CSS classes
264
+ const tableClasses = useMemo(() => [
265
+ 'datatable2',
266
+ `datatable2--${size}`,
267
+ `datatable2--${density}`,
268
+ `datatable2--${theme}`,
269
+ readability && 'datatable2--readable',
270
+ readability && `datatable2--readable-${readability}`,
271
+ striped && 'datatable2--striped',
272
+ bordered && 'datatable2--bordered',
273
+ !hover && 'datatable2--no-hover',
274
+ loading && 'datatable2--loading',
275
+ skeleton && 'datatable2--skeleton',
276
+ className
277
+ ].filter(Boolean).join(' '), [size, density, theme, readability, striped, bordered, hover, loading, skeleton, className])
278
+
279
+ // Check if any row has actions column
280
+ const hasActionsColumn = useMemo(() =>
281
+ rows.some(row => row.info) || filterable, [rows, filterable])
282
+
283
+ // Función de ordenamiento (similar a table.js)
284
+ const multiSort = useCallback((array, sortObject = {}) => {
285
+ const sortKeys = Object.keys(sortObject)
286
+ if (sortKeys.length === 0) return array
287
+
133
288
  const keySort = (a, b, direction) => {
134
- direction = direction !== null ? direction : 1;
289
+ direction = direction !== null ? direction : 1
135
290
 
136
291
  // check if a and b are numbers and compare as numbers
137
292
  if (!isNaN(a) && !isNaN(b)) {
138
- a = Number(a);
139
- b = Number(b);
293
+ a = Number(a)
294
+ b = Number(b)
140
295
  }
141
296
 
297
+ // If a > b, multiply by 1 to get the correct direction.
298
+ if (a > b) return direction
299
+
142
300
  // If b > a, multiply by -1 to get the reverse direction.
143
- return a > b ? direction : -1 * direction;
144
- };
301
+ return a > b ? direction : -1 * direction
302
+ }
145
303
 
146
304
  return array.sort((a, b) => {
147
- let sorted = 0;
148
- let index = 0;
305
+ let sorted = 0
306
+ let index = 0
149
307
 
150
308
  // Loop until sorted (-1 or 1) or until the sort keys have been processed.
151
309
  while (sorted === 0 && index < sortKeys.length) {
152
- const key = sortKeys[index];
310
+ const key = sortKeys[index]
153
311
  if (key) {
154
- const direction = sortObject[key];
155
- sorted = keySort(a[key], b[key], direction);
156
- index++;
312
+ const direction = sortObject[key]
313
+ sorted = keySort(a[key], b[key], direction)
314
+ index++
157
315
  }
158
316
  }
159
317
 
160
- return sorted;
161
- });
318
+ return sorted
319
+ })
162
320
  }, [])
163
321
 
164
- // Enhanced row selection (maintaining original behavior)
165
- const select = useCallback((row, event) => {
166
- if (event.target.id !== "checked") {
167
- // Enhanced selection logic
168
- if (selectionMode === 'multiple') {
169
- const newSelected = new Set(selectedRows)
170
- if (newSelected.has(row.id)) {
171
- newSelected.delete(row.id)
172
- } else {
173
- newSelected.add(row.id)
174
- }
175
- setSelectedRows(newSelected)
176
- } else if (selectionMode === 'single') {
177
- setSelectedRows(new Set([row.id]))
178
- }
322
+ // Compatibilidad con table.js: detectar columna "checked"
323
+ const hasCheckedColumn = useMemo(() =>
324
+ columns.some(col => col.id === 'checked'), [columns])
325
+
326
+ // Mostrar checkboxes automáticos si showSelectAll=true O si hay datos con checked pero NO hay columna "checked"
327
+ const shouldShowCheckboxes = showSelectAll || (rows.some(row => row.checked !== undefined) && !hasCheckedColumn)
328
+
329
+ // Filtrar columnas visibles
330
+ const filteredColumns = useMemo(() => {
331
+ if (!showSidebar) return columns
332
+ return columns.filter(col => visibleColumns.includes(col.id))
333
+ }, [columns, visibleColumns, showSidebar])
179
334
 
180
- // Call original callback
181
- if (onRowSelection) onRowSelection(row, event)
182
- if (onRowClick) onRowClick(row, event)
335
+ // Aplicar ordenamiento a las filas
336
+ const sortedRows = useMemo(() => {
337
+ return multiSort([...rows], sortDir)
338
+ }, [rows, sortDir, multiSort])
339
+
340
+ return (
341
+ <div className={tableClasses} style={containerStyles} {...restProps}>
342
+ {searchable && (
343
+ <DataTableSearch onSearch={onSearch} />
344
+ )}
345
+
346
+ <div className="datatable2__container">
347
+ <div className={`datatable2__main ${sidebarOpen ? 'datatable2__main--with-panel' : ''}`}>
348
+ <div className="datatable2__table-container" style={tableContainerStyles}>
349
+ <table>
350
+ <DataTableHeader
351
+ columns={filteredColumns}
352
+ rows={sortedRows}
353
+ sortDir={sortDir}
354
+ onSort={handleSort}
355
+ allChecked={allChecked}
356
+ onCheckAll={onCheckAll}
357
+ showSelectAll={showSelectAll}
358
+ shouldShowCheckboxes={shouldShowCheckboxes}
359
+ hasCheckedColumn={hasCheckedColumn}
360
+ showRowNumbers={showRowNumbers}
361
+ resizable={resizable}
362
+ columnWidths={columnWidths}
363
+ onColumnResize={handleColumnResize}
364
+ hasActionsColumn={hasActionsColumn}
365
+ stickyHeader={stickyHeader}
366
+ filterable={filterable}
367
+ onClearFilters={onClearFilters}
368
+ />
369
+ <DataTableBody
370
+ columns={filteredColumns}
371
+ rows={sortedRows}
372
+ onSelect={onSelect}
373
+ onRowSelection={onRowSelection}
374
+ editable={editable}
375
+ selectedRows={selectedRows}
376
+ showRowNumbers={showRowNumbers}
377
+ hasActionsColumn={hasActionsColumn}
378
+ hasCheckedColumn={hasCheckedColumn}
379
+ shouldShowCheckboxes={shouldShowCheckboxes}
380
+ onRowDoubleClick={onRowDoubleClick}
381
+ onRowContextMenu={onRowContextMenu}
382
+ onCellClick={onCellClick}
383
+ onCellDoubleClick={onCellDoubleClick}
384
+ onCellEdit={onCellEdit}
385
+ rowClassName={rowClassName}
386
+ cellClassName={cellClassName}
387
+ emptyMessage={emptyMessage}
388
+ emptyIcon={emptyIcon}
389
+ customEmptyState={customEmptyState}
390
+ loading={loading}
391
+ skeleton={skeleton}
392
+ expandedRows={expandedRows}
393
+ onRowExpand={handleRowExpand}
394
+ />
395
+
396
+ {/* Pie de tabla con sumarios */}
397
+ <tfoot>
398
+ <DataTableSummaryRow
399
+ columns={filteredColumns}
400
+ rows={sortedRows}
401
+ showRowNumbers={showRowNumbers}
402
+ hasActionsColumn={hasActionsColumn}
403
+ shouldShowCheckboxes={shouldShowCheckboxes}
404
+ />
405
+ </tfoot>
406
+ </table>
407
+ </div>
408
+
409
+ {exportable && (
410
+ <DataTableFooter onExport={onExport} />
411
+ )}
412
+ </div>
413
+
414
+ {showSidebar && sidebarOpen && (
415
+ <DataTablePanel
416
+ isOpen={sidebarOpen}
417
+ activeTool={activeTool}
418
+ onClose={closeSidebar}
419
+ columns={columns}
420
+ rows={sortedRows}
421
+ selectedRows={selectedRows}
422
+ visibleColumns={visibleColumns}
423
+ onColumnVisibilityChange={handleColumnVisibilityChange}
424
+ tools={allTools}
425
+ />
426
+ )}
427
+
428
+ {showSidebar && (
429
+ <DataTableToolbar
430
+ tools={allTools}
431
+ activeTool={activeTool}
432
+ onToolSelect={handleToolSelect}
433
+ />
434
+ )}
435
+ </div>
436
+ </div>
437
+ )
438
+ }
439
+
440
+ /**
441
+ * DataTable Search Component
442
+ */
443
+ const DataTableSearch = ({ onSearch }) => {
444
+ const [searchTerm, setSearchTerm] = useState('')
445
+
446
+ const handleSearch = useCallback((value) => {
447
+ setSearchTerm(value)
448
+ if (onSearch) onSearch(value)
449
+ }, [onSearch])
450
+
451
+ return (
452
+ <div className="datatable2__search">
453
+ <TextField
454
+ id="datatable-search"
455
+ placeholder="Search..."
456
+ value={searchTerm}
457
+ onChange={(id, value) => handleSearch(value)}
458
+ icon="search"
459
+ />
460
+ </div>
461
+ )
462
+ }
463
+
464
+ /**
465
+ * Columns Panel Component
466
+ */
467
+ const ColumnsPanel = ({ columns, visibleColumns, onColumnVisibilityChange }) => {
468
+ const handleColumnToggle = useCallback((columnId) => {
469
+ const isVisible = visibleColumns.includes(columnId)
470
+ onColumnVisibilityChange(columnId, !isVisible)
471
+ }, [visibleColumns, onColumnVisibilityChange])
472
+
473
+ const visibleCount = visibleColumns.length
474
+ const totalCount = columns.length
475
+
476
+ return (
477
+ <div className="datatable2__panel-section">
478
+ <h4 className="datatable2__panel-section-title">
479
+ Columnas Visibles ({visibleCount}/{totalCount})
480
+ </h4>
481
+
482
+ <ul className="datatable2__column-list">
483
+ {columns.map(column => {
484
+ const isVisible = visibleColumns.includes(column.id)
485
+ return (
486
+ <li key={column.id} className="datatable2__column-item">
487
+ <input
488
+ type="checkbox"
489
+ id={`column-${column.id}`}
490
+ className="datatable2__column-checkbox"
491
+ checked={isVisible}
492
+ onChange={() => handleColumnToggle(column.id)}
493
+ />
494
+ <label
495
+ htmlFor={`column-${column.id}`}
496
+ className="datatable2__column-label"
497
+ >
498
+ {column.label || column.id}
499
+ </label>
500
+ {column.type && (
501
+ <span className="datatable2__column-count">
502
+ {column.type}
503
+ </span>
504
+ )}
505
+ </li>
506
+ )
507
+ })}
508
+ </ul>
509
+ </div>
510
+ )
511
+ }
512
+
513
+ /**
514
+ * Export Panel Component
515
+ */
516
+ const ExportPanel = ({ columns = [], rows = [], selectedRows = [] }) => {
517
+ const [exportStatus, setExportStatus] = useState(null)
518
+ const [isExporting, setIsExporting] = useState(false)
519
+
520
+ // Detectar entorno
521
+ const environment = useMemo(() => {
522
+ const isInIframe = window !== window.top
523
+ const isPreviewJs = window.location.href.includes('previewjs') ||
524
+ window.location.href.includes('localhost:3140')
525
+ const isLocalhost = window.location.hostname === 'localhost' ||
526
+ window.location.hostname === '127.0.0.1'
527
+
528
+ return {
529
+ isInIframe,
530
+ isPreviewJs,
531
+ isLocalhost,
532
+ isRestricted: isInIframe || isPreviewJs
183
533
  }
184
- }, [selectedRows, selectionMode, onRowSelection, onRowClick])
185
-
186
- // Enhanced move row function (maintaining original behavior)
187
- const moveRow = useCallback((dragged, dropped) => {
188
- if (onSort) onSort(dragged, dropped)
189
- }, [onSort])
190
-
191
- // Enhanced check all function (maintaining original behavior)
192
- const checkAll = useCallback((_, value) => {
193
- const ids = rows.map(row => row.id)
194
- setAllChecked(value)
195
-
196
- if (selectionMode === 'multiple') {
197
- if (value) {
198
- setSelectedRows(new Set(ids))
199
- } else {
200
- setSelectedRows(new Set())
201
- }
534
+ }, [])
535
+
536
+ // Estadísticas de exportación
537
+ const stats = useMemo(() => getExportStats(rows, columns), [rows, columns])
538
+
539
+ // Validar datos
540
+ const validation = useMemo(() => validateExportData(rows, columns), [rows, columns])
541
+
542
+ const handleExport = async (format) => {
543
+ if (!validation.isValid) {
544
+ setExportStatus({ type: 'error', message: validation.errors.join(', ') })
545
+ return
202
546
  }
203
-
204
- if (onCheckAll) onCheckAll(ids, value)
205
- }, [rows, selectionMode, onCheckAll])
206
-
207
- // Search functionality
208
- const filteredRows = useMemo(() => {
209
- if (!searchTerm || !searchable) return rows
210
-
211
- return rows.filter(row => {
212
- return columns.some(column => {
213
- const value = row[column.id]
214
- if (value == null) return false
215
- return String(value).toLowerCase().includes(searchTerm.toLowerCase())
547
+
548
+ setIsExporting(true)
549
+ setExportStatus(null)
550
+
551
+ try {
552
+ let result
553
+ switch (format) {
554
+ case 'csv':
555
+ result = exportToCSV(rows, columns)
556
+ break
557
+ case 'excel':
558
+ result = exportToExcel(rows, columns)
559
+ break
560
+ case 'json':
561
+ result = exportToJSON(rows, columns)
562
+ break
563
+ case 'xml':
564
+ result = exportToXML(rows, columns)
565
+ break
566
+ default:
567
+ result = { success: false, message: 'Formato no soportado' }
568
+ }
569
+
570
+ setExportStatus({
571
+ type: result.success ? 'success' : 'error',
572
+ message: result.message
216
573
  })
217
- })
218
- }, [rows, searchTerm, searchable, columns])
219
-
220
- // Pagination for virtual scrolling
221
- const paginatedRows = useMemo(() => {
222
- if (!virtualScrolling) return filteredRows
223
-
224
- const start = currentPage * pageSize
225
- const end = start + pageSize
226
- return filteredRows.slice(start, end)
227
- }, [filteredRows, virtualScrolling, currentPage, pageSize])
228
-
229
- // Final processed rows
230
- const processedRows = useMemo(() => {
231
- const rowsToProcess = virtualScrolling ? paginatedRows : filteredRows
232
- return multiSort(rowsToProcess, sortDir)
233
- }, [virtualScrolling, paginatedRows, filteredRows, multiSort, sortDir])
234
-
235
- // Handle search
236
- const handleSearch = useCallback((term) => {
237
- setSearchTerm(term)
238
- setCurrentPage(0) // Reset to first page
239
- if (onSearch) onSearch(term)
240
- }, [onSearch])
574
+ } catch (error) {
575
+ setExportStatus({
576
+ type: 'error',
577
+ message: 'Error inesperado durante la exportación'
578
+ })
579
+ } finally {
580
+ setIsExporting(false)
581
+ // Limpiar mensaje después de 3 segundos
582
+ setTimeout(() => setExportStatus(null), 3000)
583
+ }
584
+ }
241
585
 
242
- // Handle export
243
- const handleExport = useCallback(() => {
244
- if (onExport) {
245
- onExport(processedRows, columns)
246
- } else {
247
- // Default CSV export
248
- const csvContent = [
249
- columns.map(col => col.label || col.id).join(','),
250
- ...processedRows.map(row =>
251
- columns.map(col => {
252
- const value = row[col.id]
253
- return typeof value === 'string' ? `"${value}"` : value
254
- }).join(',')
255
- )
256
- ].join('\n')
257
-
258
- const blob = new Blob([csvContent], { type: 'text/csv' })
259
- const url = URL.createObjectURL(blob)
260
- const a = document.createElement('a')
261
- a.href = url
262
- a.download = 'table-export.csv'
263
- a.click()
264
- URL.revokeObjectURL(url)
586
+ const handleExportSelected = async (format) => {
587
+ if (selectedRows.length === 0) {
588
+ setExportStatus({ type: 'error', message: 'No hay filas seleccionadas' })
589
+ return
265
590
  }
266
- }, [processedRows, columns, onExport])
267
591
 
268
- // Generate CSS classes
269
- const cssClasses = [
270
- 'datatable8', // Maintain original class for compatibility
271
- 'datatable2', // New class for v2 features
272
- outlined && 'outlined',
273
- striped && 'datatable2--striped',
274
- compact && 'datatable2--compact',
275
- bordered && 'datatable2--bordered',
276
- !hover && 'datatable2--no-hover',
277
- `datatable2--${rowHeight}`,
278
- `datatable2--${density}`,
279
- `datatable2--${theme}`,
280
- responsive && 'datatable2--responsive',
281
- isLoading && 'datatable2--loading',
282
- skeleton && 'datatable2--skeleton',
283
- className
284
- ].filter(Boolean).join(' ')
592
+ setIsExporting(true)
593
+ setExportStatus(null)
285
594
 
286
- // Accessibility attributes
287
- const ariaAttributes = accessibility ? {
288
- role: 'table',
289
- 'aria-label': 'Data table',
290
- 'aria-rowcount': processedRows.length,
291
- 'aria-colcount': columns.length,
292
- 'aria-multiselectable': selectionMode === 'multiple',
293
- 'aria-busy': isLoading
294
- } : {}
295
-
296
- // Render loading/skeleton state
297
- if (isLoading && skeleton) {
298
- return (
299
- <div className={cssClasses} {...ariaAttributes} {...restProps}>
300
- <SkeletonTable columns={columns} rows={5} />
301
- </div>
302
- )
595
+ try {
596
+ const result = exportSelected(rows, columns, selectedRows, format)
597
+ setExportStatus({
598
+ type: result.success ? 'success' : 'error',
599
+ message: result.message
600
+ })
601
+ } catch (error) {
602
+ setExportStatus({
603
+ type: 'error',
604
+ message: 'Error al exportar filas seleccionadas'
605
+ })
606
+ } finally {
607
+ setIsExporting(false)
608
+ setTimeout(() => setExportStatus(null), 3000)
609
+ }
303
610
  }
304
611
 
305
612
  return (
306
- <div className={cssClasses} ref={tableRef} {...ariaAttributes} {...restProps}>
307
- {/* Search bar */}
308
- {searchable && (
309
- <div className="datatable2__search">
310
- <TextField
311
- placeholder={searchPlaceholder}
312
- value={searchTerm}
313
- onChange={(_, value) => handleSearch(value)}
314
- icon="search"
315
- />
613
+ <div className="datatable2__panel-section">
614
+ <h4 className="datatable2__panel-section-title">Exportar Datos</h4>
615
+
616
+ {/* Notificación de entorno restringido */}
617
+ {environment.isRestricted && (
618
+ <div className="datatable2__export-environment-notice">
619
+ <Icon icon="info" size="small" />
620
+ <div>
621
+ <strong>Entorno de desarrollo detectado</strong>
622
+ <p>Los archivos se abrirán en nueva ventana. Usa Ctrl+S para guardar.</p>
623
+ </div>
316
624
  </div>
317
625
  )}
318
626
 
319
- {/* Export button */}
320
- {exportable && (
321
- <div className="datatable2__toolbar">
322
- <button
323
- className="datatable2__export-btn"
324
- onClick={handleExport}
325
- disabled={isLoading}
326
- >
327
- <Icon icon="download" size="small" />
328
- Export
329
- </button>
627
+ {/* Estadísticas */}
628
+ <div className="datatable2__export-stats">
629
+ <div className="datatable2__stat">
630
+ <span className="datatable2__stat-label">Filas:</span>
631
+ <span className="datatable2__stat-value">{stats.totalRows}</span>
632
+ </div>
633
+ <div className="datatable2__stat">
634
+ <span className="datatable2__stat-label">Columnas:</span>
635
+ <span className="datatable2__stat-value">{stats.visibleColumns}</span>
636
+ </div>
637
+ {selectedRows.length > 0 && (
638
+ <div className="datatable2__stat">
639
+ <span className="datatable2__stat-label">Seleccionadas:</span>
640
+ <span className="datatable2__stat-value">{selectedRows.length}</span>
641
+ </div>
642
+ )}
643
+ </div>
644
+
645
+ {/* Estado de exportación */}
646
+ {exportStatus && (
647
+ <div className={`datatable2__export-status datatable2__export-status--${exportStatus.type}`}>
648
+ <Icon
649
+ icon={exportStatus.type === 'success' ? 'check_circle' : 'error'}
650
+ size="small"
651
+ />
652
+ {exportStatus.message}
330
653
  </div>
331
654
  )}
332
655
 
333
- {/* Table */}
334
- <table>
335
- <DataTable2Header
336
- columns={columns}
337
- sortDir={sortDir}
338
- onSort={changeSort}
339
- allChecked={allChecked}
340
- onCheckAll={checkAll}
341
- showSelectAll={showSelectAll}
342
- showRowNumbers={showRowNumbers}
343
- resizable={resizable}
344
- onColumnResize={onColumnResize}
345
- accessibility={accessibility}
346
- headerClassName={headerClassName}
347
- stickyHeader={stickyHeader}
348
- />
349
-
350
- <DataTable2Body
351
- columns={columns}
352
- rows={processedRows}
353
- onSelect={select}
354
- onDrop={moveRow}
355
- editable={editable}
356
- expanded={expanded}
357
- filterable={filterable}
358
- onClearFilters={onClearFilters}
359
- selectedRows={selectedRows}
360
- showRowNumbers={showRowNumbers}
361
- onRowDoubleClick={onRowDoubleClick}
362
- onRowContextMenu={onRowContextMenu}
363
- onCellClick={onCellClick}
364
- onCellDoubleClick={onCellDoubleClick}
365
- onCellEdit={onCellEdit}
366
- rowClassName={rowClassName}
367
- cellClassName={cellClassName}
368
- accessibility={accessibility}
369
- emptyMessage={emptyMessage}
370
- emptyIcon={emptyIcon}
371
- customEmptyState={customEmptyState}
372
- isLoading={isLoading}
373
- />
374
- </table>
656
+ {/* Botones de exportación - Todos los datos */}
657
+ <div className="datatable2__export-section">
658
+ <h5 className="datatable2__export-section-title">Exportar todos los datos</h5>
659
+ <div className="datatable2__export-options">
660
+ {Object.values(exportFormats).map(format => (
661
+ <button
662
+ key={format.id}
663
+ className="datatable2__export-button"
664
+ onClick={() => handleExport(format.id)}
665
+ disabled={isExporting || !validation.isValid}
666
+ >
667
+ <Icon icon={format.icon} size="small" />
668
+ {format.label}
669
+ <span className="datatable2__export-description">
670
+ {format.description}
671
+ </span>
672
+ </button>
673
+ ))}
674
+ </div>
675
+ </div>
375
676
 
376
- {/* Footer */}
377
- {footerContent && (
378
- <div className="datatable2__footer">
379
- {footerContent}
677
+ {/* Botones de exportación - Solo seleccionadas */}
678
+ {selectedRows.length > 0 && (
679
+ <div className="datatable2__export-section">
680
+ <h5 className="datatable2__export-section-title">
681
+ Exportar filas seleccionadas ({selectedRows.length})
682
+ </h5>
683
+ <div className="datatable2__export-options">
684
+ {Object.values(exportFormats).map(format => (
685
+ <button
686
+ key={`selected-${format.id}`}
687
+ className="datatable2__export-button datatable2__export-button--selected"
688
+ onClick={() => handleExportSelected(format.id)}
689
+ disabled={isExporting}
690
+ >
691
+ <Icon icon={format.icon} size="small" />
692
+ {format.label}
693
+ </button>
694
+ ))}
695
+ </div>
380
696
  </div>
381
697
  )}
382
698
 
383
- {/* Virtual scrolling pagination */}
384
- {virtualScrolling && filteredRows.length > pageSize && (
385
- <div className="datatable2__pagination">
386
- <button
387
- onClick={() => setCurrentPage(Math.max(0, currentPage - 1))}
388
- disabled={currentPage === 0}
389
- >
390
- Previous
391
- </button>
392
- <span>
393
- Page {currentPage + 1} of {Math.ceil(filteredRows.length / pageSize)}
394
- </span>
395
- <button
396
- onClick={() => setCurrentPage(Math.min(Math.ceil(filteredRows.length / pageSize) - 1, currentPage + 1))}
397
- disabled={currentPage >= Math.ceil(filteredRows.length / pageSize) - 1}
398
- >
399
- Next
400
- </button>
401
- </div>
699
+ {/* Información adicional */}
700
+ <div className="datatable2__export-info">
701
+ <p className="datatable2__export-note">
702
+ <Icon icon="info" size="small" />
703
+ Los archivos se descargarán automáticamente en tu carpeta de descargas.
704
+ </p>
705
+ </div>
706
+ </div>
707
+ )
708
+ }
709
+
710
+ /**
711
+ * Filters Panel Component
712
+ */
713
+ const FiltersPanel = () => {
714
+ return (
715
+ <div className="datatable2__panel-section">
716
+ <h4 className="datatable2__panel-section-title">Filtros Avanzados</h4>
717
+ <div className="datatable2__filter-options">
718
+ <p style={{ color: '#6b7280', fontSize: '0.875rem' }}>
719
+ Funcionalidad de filtros avanzados próximamente...
720
+ </p>
721
+ </div>
722
+ </div>
723
+ )
724
+ }
725
+
726
+ /**
727
+ * DataTable Summary Row Component
728
+ */
729
+ const DataTableSummaryRow = ({ columns, rows, showRowNumbers, hasActionsColumn, shouldShowCheckboxes }) => {
730
+ // Calcular sumarios para cada columna
731
+ const summaries = useMemo(() => {
732
+ return columns.map(column => {
733
+ if (column.summary) {
734
+ return calculateColumnSummary(column, rows)
735
+ }
736
+ return null
737
+ })
738
+ }, [columns, rows])
739
+
740
+ // Verificar si hay algún sumario para mostrar
741
+ const hasSummaries = summaries.some(summary => summary !== null)
742
+
743
+ if (!hasSummaries) {
744
+ return null
745
+ }
746
+
747
+ return (
748
+ <tr className="datatable2__summary-row">
749
+ {/* Celda para checkboxes si están habilitados */}
750
+ {shouldShowCheckboxes && (
751
+ <td className="datatable2__summary-cell datatable2__summary-cell--checkbox">
752
+ <strong>Total</strong>
753
+ </td>
754
+ )}
755
+
756
+ {/* Celda para números de fila si están habilitados */}
757
+ {showRowNumbers && (
758
+ <td className="datatable2__summary-cell datatable2__summary-cell--row-number">
759
+ {!shouldShowCheckboxes && <strong>Total</strong>}
760
+ </td>
761
+ )}
762
+
763
+ {/* Celdas de sumario para cada columna */}
764
+ {columns.map((column, index) => {
765
+ const summary = summaries[index]
766
+ return (
767
+ <td key={column.id} className="datatable2__summary-cell">
768
+ {summary ? (
769
+ <span
770
+ className="datatable2__summary-value"
771
+ title={`${summary.function.label}: ${summary.function.description}`}
772
+ >
773
+ {summary.formatted}
774
+ </span>
775
+ ) : (
776
+ // Mostrar "Total" en la primera columna si no hay checkboxes ni números
777
+ index === 0 && !shouldShowCheckboxes && !showRowNumbers ? (
778
+ <strong>Total</strong>
779
+ ) : null
780
+ )}
781
+ </td>
782
+ )
783
+ })}
784
+
785
+ {/* Celda para acciones */}
786
+ {hasActionsColumn && (
787
+ <td className="datatable2__summary-cell datatable2__summary-cell--actions"></td>
402
788
  )}
789
+ </tr>
790
+ )
791
+ }
792
+
793
+ /**
794
+ * DataTable Panel Component
795
+ */
796
+ const DataTablePanel = ({ isOpen, activeTool, onClose, columns, rows, selectedRows, visibleColumns, onColumnVisibilityChange, tools }) => {
797
+ console.log('DataTablePanel render - isOpen:', isOpen, 'activeTool:', activeTool)
798
+
799
+ // Find the active tool configuration
800
+ const activeToolConfig = tools.find(tool => tool.id === activeTool)
801
+
802
+ // Render the component dynamically
803
+ const renderPanelContent = () => {
804
+ console.log('renderPanelContent called with activeTool:', activeTool)
805
+
806
+ if (!activeToolConfig) {
807
+ return (
808
+ <div className="datatable2__panel-section">
809
+ <h4 className="datatable2__panel-section-title">Herramienta Desconocida</h4>
810
+ <p>No se encontró contenido para la herramienta: {activeTool}</p>
811
+ </div>
812
+ )
813
+ }
814
+
815
+ const { component: Component } = activeToolConfig
816
+
817
+ // If component is a React component, render it
818
+ if (typeof Component === 'function') {
819
+ console.log('Rendering custom component with props:', {
820
+ columns: columns?.length,
821
+ visibleColumns: visibleColumns?.length
822
+ })
823
+ return (
824
+ <Component
825
+ columns={columns || []}
826
+ rows={rows || []}
827
+ selectedRows={selectedRows || []}
828
+ visibleColumns={visibleColumns || []}
829
+ onColumnVisibilityChange={onColumnVisibilityChange}
830
+ />
831
+ )
832
+ }
833
+
834
+ // If component is a string (legacy), handle it
835
+ if (typeof Component === 'string') {
836
+ switch (Component) {
837
+ case 'columns':
838
+ return (
839
+ <ColumnsPanel
840
+ columns={columns}
841
+ visibleColumns={visibleColumns}
842
+ onColumnVisibilityChange={onColumnVisibilityChange}
843
+ />
844
+ )
845
+ case 'export':
846
+ return (
847
+ <ExportPanel
848
+ columns={columns}
849
+ rows={rows}
850
+ selectedRows={selectedRows}
851
+ />
852
+ )
853
+ case 'filters':
854
+ return <FiltersPanel />
855
+ default:
856
+ return (
857
+ <div className="datatable2__panel-section">
858
+ <h4 className="datatable2__panel-section-title">Componente No Encontrado</h4>
859
+ <p>Componente string no reconocido: {Component}</p>
860
+ </div>
861
+ )
862
+ }
863
+ }
864
+
865
+ // If component is neither function nor string
866
+ return (
867
+ <div className="datatable2__panel-section">
868
+ <h4 className="datatable2__panel-section-title">Tipo de Componente Inválido</h4>
869
+ <p>El componente debe ser una función React o un string.</p>
870
+ </div>
871
+ )
872
+ }
873
+
874
+ // Get tool title
875
+ const getToolTitle = () => {
876
+ if (activeToolConfig) {
877
+ return activeToolConfig.title
878
+ }
879
+ return 'Herramientas'
880
+ }
881
+
882
+ return (
883
+ <div className={`datatable2__panel ${isOpen ? 'datatable2__panel--open' : ''}`}>
884
+ <div className="datatable2__panel-header">
885
+ <h3 className="datatable2__panel-title">{getToolTitle()}</h3>
886
+ <button
887
+ className="datatable2__panel-close"
888
+ onClick={onClose}
889
+ title="Cerrar"
890
+ >
891
+ <Icon icon="close" size="small" />
892
+ </button>
893
+ </div>
894
+
895
+ <div className="datatable2__panel-content">
896
+ {renderPanelContent()}
897
+ </div>
898
+ </div>
899
+ )
900
+ }
901
+
902
+ /**
903
+ * DataTable Toolbar Component
904
+ */
905
+ const DataTableToolbar = ({ tools, activeTool, onToolSelect }) => {
906
+ console.log('DataTableToolbar render - tools:', tools, 'activeTool:', activeTool)
907
+
908
+ const handleButtonClick = (toolId) => {
909
+ console.log('Button clicked for tool:', toolId)
910
+ onToolSelect(toolId)
911
+ }
912
+
913
+ return (
914
+ <div className="datatable2__toolbar">
915
+ {tools.map(tool => (
916
+ <Icon
917
+ key={tool.id}
918
+ icon={tool.icon}
919
+ size="small"
920
+ clickable
921
+ action={() => handleButtonClick(tool.id)}
922
+ ariaLabel={tool.title}
923
+ className={activeTool === tool.id ? 'datatable2__toolbar-icon--active' : ''}
924
+ />
925
+ ))}
403
926
  </div>
404
927
  )
405
928
  }
406
929
 
407
930
  /**
408
- * Enhanced DataTable Header - maintains original structure
931
+ * DataTable Header Component
409
932
  */
410
- const DataTable2Header = ({
933
+ const DataTableHeader = ({
411
934
  columns,
935
+ rows,
412
936
  sortDir,
413
937
  onSort,
414
938
  allChecked,
415
939
  onCheckAll,
416
940
  showSelectAll,
941
+ shouldShowCheckboxes,
942
+ hasCheckedColumn,
417
943
  showRowNumbers,
418
944
  resizable,
945
+ columnWidths,
419
946
  onColumnResize,
420
- accessibility,
421
- headerClassName,
422
- stickyHeader
947
+ hasActionsColumn,
948
+ stickyHeader,
949
+ filterable,
950
+ onClearFilters
423
951
  }) => {
424
952
  const headerClasses = [
425
953
  'datatable2__header',
426
- stickyHeader && 'datatable2__header--sticky',
427
- headerClassName
954
+ stickyHeader && 'datatable2__header--sticky'
428
955
  ].filter(Boolean).join(' ')
429
956
 
430
957
  return (
431
958
  <thead className={headerClasses}>
432
959
  <tr>
433
- {showRowNumbers && (
434
- <th className="datatable2__row-number-header">
435
- <div>#</div>
960
+ {shouldShowCheckboxes && (
961
+ <th className="datatable2__select-header">
962
+ <CheckBox
963
+ id="select-all"
964
+ value={allChecked}
965
+ onChange={() => onCheckAll && onCheckAll(!allChecked)}
966
+ />
436
967
  </th>
437
968
  )}
438
- {columns.map(({ id, label, type, item, sortable, resizable: colResizable = false }) => {
439
- const isResizable = resizable || colResizable
440
- const resizableStyle = isResizable ? "resizable-column" : ""
441
- const sort = sortDir[id] ? sortDir[id] : null
442
- const [rowspan, colspan] = type === TYPES.ENTITY ? [1, Object.values(item).filter(v => v.column === true).length] : [2, 1]
443
-
444
- const thProps = accessibility ? {
445
- role: 'columnheader',
446
- 'aria-sort': sort > 0 ? 'ascending' : sort < 0 ? 'descending' : 'none',
447
- tabIndex: sortable ? 0 : -1
448
- } : {}
969
+
970
+ {showRowNumbers && (
971
+ <th className="datatable2__row-number-header">#</th>
972
+ )}
973
+
974
+ {columns.map((column) => {
975
+ // Compatibilidad con table.js: manejar columna "checked"
976
+ if (column.id === 'checked') {
977
+ return (
978
+ <th key={column.id} className="datatable2__select-header">
979
+ {onCheckAll ? (
980
+ <CheckBox
981
+ id="select-all"
982
+ value={allChecked}
983
+ onChange={() => {
984
+ if (onCheckAll) {
985
+ // Compatibilidad con table.js: pasar IDs y estado
986
+ const allIds = rows.map(row => row.id)
987
+ onCheckAll(allIds, !allChecked)
988
+ }
989
+ }}
990
+ />
991
+ ) : (
992
+ <Text>{column.label}</Text>
993
+ )}
994
+ </th>
995
+ )
996
+ }
449
997
 
450
998
  return (
451
- <th
452
- key={id}
453
- className={resizableStyle}
454
- rowSpan={rowspan}
455
- colSpan={colspan}
456
- style={{
457
- minWidth: '80px'
458
- }}
459
- {...thProps}
460
- >
461
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
462
- <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
463
- {id === "checked" && onCheckAll && showSelectAll ?
464
- <CheckBox onChange={onCheckAll} value={allChecked} /> :
465
- <Text key={`th_${id}`}>{label}</Text>
466
- }
467
- {sortable ? <SortIcon2 sortDir={sortDir[id]} onChange={() => onSort(id)} /> : null}
468
- </div>
469
- </div>
470
- {isResizable && (
471
- <div
472
- className="column-resizer"
473
- onMouseDown={(e) => handleColumnResize(e, id, onColumnResize)}
474
- title="Drag to resize column"
475
- />
476
- )}
477
- </th>
999
+ <DataTableHeaderCell
1000
+ key={column.id}
1001
+ column={column}
1002
+ sortDir={sortDir[column.id]}
1003
+ onSort={onSort}
1004
+ resizable={resizable}
1005
+ width={columnWidths[column.id]}
1006
+ onResize={onColumnResize}
1007
+ />
478
1008
  )
479
1009
  })}
480
- <th rowSpan={2} colSpan={1}></th>
481
- </tr>
482
- <tr>
483
- {showRowNumbers && <th></th>}
484
- {
485
- columns
486
- .filter(({ type }) => type === TYPES.ENTITY)
487
- .map(column => {
488
- const { item } = column
489
- const fields = item ? Object.values(item) : []
490
- return fields
491
- .filter(field => field.column === true)
492
- .map(field => <th key={field.id}>{field.label}</th>)
493
- })
494
- }
1010
+
1011
+ {hasActionsColumn && (
1012
+ <th className="datatable2__actions-header">Actions</th>
1013
+ )}
495
1014
  </tr>
1015
+
1016
+ {/* Fila de filtros siempre visible en el header */}
1017
+ {filterable && (
1018
+ <DataTableFiltersRow
1019
+ columns={columns}
1020
+ onClear={onClearFilters}
1021
+ showRowNumbers={showRowNumbers}
1022
+ hasActionsColumn={hasActionsColumn}
1023
+ shouldShowCheckboxes={shouldShowCheckboxes}
1024
+ />
1025
+ )}
496
1026
  </thead>
497
1027
  )
498
1028
  }
499
1029
 
500
1030
  /**
501
- * Enhanced DataTable Body - maintains original structure
1031
+ * DataTable Header Cell with Resizing
1032
+ */
1033
+ const DataTableHeaderCell = ({ column, sortDir, onSort, resizable, width, onResize }) => {
1034
+ const [isResizing, setIsResizing] = useState(false)
1035
+ const cellRef = useRef(null)
1036
+ const resizeDataRef = useRef({ startX: 0, startWidth: 0 })
1037
+
1038
+ const handleSort = useCallback(() => {
1039
+ if (column.sortable && onSort) {
1040
+ onSort(column.id)
1041
+ }
1042
+ }, [column.id, column.sortable, onSort])
1043
+
1044
+ // Create stable references for event handlers
1045
+ const handleMouseMove = useCallback((e) => {
1046
+ const { startX, startWidth } = resizeDataRef.current
1047
+ const diff = e.clientX - startX
1048
+ const newWidth = Math.max(50, startWidth + diff)
1049
+
1050
+ console.log('Resizing column:', column.id, 'newWidth:', newWidth, 'diff:', diff)
1051
+
1052
+ if (onResize) {
1053
+ onResize(column.id, newWidth)
1054
+ }
1055
+ }, [column.id, onResize])
1056
+
1057
+ const handleMouseUp = useCallback(() => {
1058
+ setIsResizing(false)
1059
+ }, [])
1060
+
1061
+ // Effect to manage event listeners and body class
1062
+ useEffect(() => {
1063
+ if (isResizing) {
1064
+ document.body.classList.add('datatable2-resizing')
1065
+ document.addEventListener('mousemove', handleMouseMove)
1066
+ document.addEventListener('mouseup', handleMouseUp)
1067
+
1068
+ return () => {
1069
+ document.body.classList.remove('datatable2-resizing')
1070
+ document.removeEventListener('mousemove', handleMouseMove)
1071
+ document.removeEventListener('mouseup', handleMouseUp)
1072
+ }
1073
+ }
1074
+ }, [isResizing, handleMouseMove, handleMouseUp])
1075
+
1076
+ const handleMouseDown = useCallback((e) => {
1077
+ if (!resizable) return
1078
+
1079
+ e.preventDefault()
1080
+ e.stopPropagation()
1081
+
1082
+ const startX = e.clientX
1083
+ const startWidth = cellRef.current?.offsetWidth || width || 150
1084
+
1085
+ console.log('Starting resize for column:', column.id, 'startWidth:', startWidth, 'startX:', startX)
1086
+
1087
+ resizeDataRef.current = { startX, startWidth }
1088
+ setIsResizing(true)
1089
+ }, [resizable, width, column.id])
1090
+
1091
+ const cellStyle = width ? { width: `${width}px`, minWidth: `${width}px` } : {}
1092
+
1093
+ return (
1094
+ <th
1095
+ ref={cellRef}
1096
+ className={`datatable2__header-cell ${column.sortable ? 'sortable' : ''}`}
1097
+ style={cellStyle}
1098
+ onClick={handleSort}
1099
+ >
1100
+ <div className="datatable2__header-content">
1101
+ <Text>{column.label}</Text>
1102
+ {column.sortable && (
1103
+ <Icon
1104
+ icon={sortDir > 0 ? 'arrow_upward' : sortDir < 0 ? 'arrow_downward' : 'swap_vert'}
1105
+ size="small"
1106
+ />
1107
+ )}
1108
+ </div>
1109
+ {resizable && (
1110
+ <div
1111
+ className="datatable2__resize-handle"
1112
+ onMouseDown={handleMouseDown}
1113
+ style={{ cursor: 'col-resize' }}
1114
+ />
1115
+ )}
1116
+ </th>
1117
+ )
1118
+ }
1119
+
1120
+ /**
1121
+ * DataTable Body Component
502
1122
  */
503
- const DataTable2Body = ({
1123
+ const DataTableBody = ({
504
1124
  columns,
505
1125
  rows,
506
1126
  onSelect,
507
- onDrop,
1127
+ onRowSelection,
508
1128
  editable,
509
- expanded,
510
- filterable,
511
- onClearFilters,
512
1129
  selectedRows,
513
1130
  showRowNumbers,
1131
+ hasActionsColumn,
1132
+ hasCheckedColumn,
1133
+ shouldShowCheckboxes,
514
1134
  onRowDoubleClick,
515
1135
  onRowContextMenu,
516
1136
  onCellClick,
@@ -518,29 +1138,38 @@ const DataTable2Body = ({
518
1138
  onCellEdit,
519
1139
  rowClassName,
520
1140
  cellClassName,
521
- accessibility,
522
1141
  emptyMessage,
523
1142
  emptyIcon,
524
1143
  customEmptyState,
525
- isLoading
1144
+ loading,
1145
+ skeleton,
1146
+ expandedRows,
1147
+ onRowExpand
526
1148
  }) => {
1149
+ if (loading || skeleton) {
1150
+ return <DataTableSkeleton columns={columns} showRowNumbers={showRowNumbers} hasActionsColumn={hasActionsColumn} />
1151
+ }
1152
+
1153
+ if (rows.length === 0) {
1154
+ return <DataTableEmpty message={emptyMessage} icon={emptyIcon} customState={customEmptyState} />
1155
+ }
1156
+
527
1157
  return (
528
1158
  <tbody>
529
- {filterable ? <DataTableFiltersRow2 columns={columns} onClear={onClearFilters} showRowNumbers={showRowNumbers} /> : null}
530
-
531
- {rows.length > 0 ? (
532
- rows.map((row, index) => (
533
- <DataTableRow2
534
- key={row.id}
535
- index={index}
1159
+ {rows.map((row, index) => (
1160
+ <React.Fragment key={row.id || index}>
1161
+ <DataTableRow
536
1162
  row={row}
1163
+ index={index}
537
1164
  columns={columns}
538
1165
  onSelect={onSelect}
539
- onDrop={onDrop}
1166
+ onRowSelection={onRowSelection}
540
1167
  editable={editable}
541
- expanded={expanded}
542
- selected={selectedRows.has(row.id)}
1168
+ isSelected={selectedRows.includes(row.id)}
543
1169
  showRowNumbers={showRowNumbers}
1170
+ hasActionsColumn={hasActionsColumn}
1171
+ hasCheckedColumn={hasCheckedColumn}
1172
+ shouldShowCheckboxes={shouldShowCheckboxes}
544
1173
  onRowDoubleClick={onRowDoubleClick}
545
1174
  onRowContextMenu={onRowContextMenu}
546
1175
  onCellClick={onCellClick}
@@ -548,705 +1177,502 @@ const DataTable2Body = ({
548
1177
  onCellEdit={onCellEdit}
549
1178
  rowClassName={rowClassName}
550
1179
  cellClassName={cellClassName}
551
- accessibility={accessibility}
1180
+ isExpanded={expandedRows.has(row.id)}
1181
+ onRowExpand={onRowExpand}
552
1182
  />
553
- ))
554
- ) : (
555
- <tr>
556
- <td colSpan={columns.length + (showRowNumbers ? 1 : 0) + 1}>
557
- {customEmptyState || (
558
- <EmptyMessage
559
- icon={emptyIcon ? emptyIcon : "search_off"}
560
- text={emptyMessage}
561
- className="empty-message"
562
- />
563
- )}
564
- </td>
565
- </tr>
566
- )}
1183
+ {row.info && expandedRows.has(row.id) && (
1184
+ <tr className="datatable2__expanded-row">
1185
+ <td
1186
+ colSpan={
1187
+ columns.length +
1188
+ (showRowNumbers ? 1 : 0) +
1189
+ (hasActionsColumn ? 1 : 0) +
1190
+ (row.checked !== undefined ? 1 : 0)
1191
+ }
1192
+ className="datatable2__expanded-cell"
1193
+ >
1194
+ <div className="datatable2__expanded-content">
1195
+ {typeof row.info === 'string' ? (
1196
+ <Text>{row.info}</Text>
1197
+ ) : row.info.content ? (
1198
+ row.info.content
1199
+ ) : (
1200
+ <Text>{row.info}</Text>
1201
+ )}
1202
+ </div>
1203
+ </td>
1204
+ </tr>
1205
+ )}
1206
+ </React.Fragment>
1207
+ ))}
567
1208
  </tbody>
568
1209
  )
569
1210
  }
570
1211
 
571
1212
  /**
572
- * Enhanced DataTableFiltersRow - maintains original structure
1213
+ * DataTable Row Component
573
1214
  */
574
- const DataTableFiltersRow2 = ({ columns, onClear, showRowNumbers }) => {
575
- const [form, setForm] = useState({})
576
-
577
- function changeFilter(id, value, onFilter) {
578
- setForm({ ...form, [id]: value })
579
- if (onFilter) onFilter(id, value)
580
- }
581
-
582
- function clear() {
583
- setForm({})
584
- if (onClear) onClear()
585
- }
586
-
587
- const dirty = Object.keys(form).length > 0
588
-
589
- return (
590
- <tr className="filters-row">
591
- {showRowNumbers && <td className="filter-cell"></td>}
592
- {columns.map(({ id, filterable, onFilter, predictive = false, options }) => {
593
- const value = form[id] ? form[id] : ''
594
- const field = options ?
595
- <DropDown id={id} value={value} options={options} predictive={predictive} onChange={(id, value) => changeFilter(id, value, onFilter)} outlined />
596
- :
597
- <TextField id={id} value={value} onChange={(id, value) => changeFilter(id, value, onFilter)} outlined />
598
- return (
599
- <td key={id} className='filter-cell'>
600
- {filterable ? field : null}
601
- </td>
602
- )
603
- })}
604
- <td>
605
- <Icon icon="close" size="small" clickable action={clear} disabled={!dirty} />
606
- </td>
607
- </tr>
608
- )
609
- }
1215
+ const DataTableRow = ({
1216
+ row,
1217
+ index,
1218
+ columns,
1219
+ onSelect,
1220
+ onRowSelection,
1221
+ editable,
1222
+ isSelected,
1223
+ showRowNumbers,
1224
+ hasActionsColumn,
1225
+ hasCheckedColumn,
1226
+ shouldShowCheckboxes,
1227
+ onRowDoubleClick,
1228
+ onRowContextMenu,
1229
+ onCellClick,
1230
+ onCellDoubleClick,
1231
+ onCellEdit,
1232
+ rowClassName,
1233
+ cellClassName,
1234
+ isExpanded,
1235
+ onRowExpand
1236
+ }) => {
1237
+ const handleRowClick = useCallback((event) => {
1238
+ // Compatibilidad con table.js: evitar selección si se hace click en checkbox
1239
+ if (event && event.target && event.target.id === "checked") {
1240
+ return
1241
+ }
610
1242
 
611
- /**
612
- * Enhanced DataTable Row - maintains original structure
613
- */
614
- const DataTableRow2 = (props) => {
615
- const {
616
- index,
617
- row,
618
- columns = [],
619
- onSelect,
620
- editable,
621
- expanded = false,
622
- selected = false,
623
- showRowNumbers,
624
- onRowDoubleClick,
625
- onRowContextMenu,
626
- onCellClick,
627
- onCellDoubleClick,
628
- onCellEdit,
629
- rowClassName,
630
- cellClassName,
631
- accessibility
632
- } = props
1243
+ // Usar onRowSelection si está disponible (compatibilidad con table.js)
1244
+ if (onRowSelection) {
1245
+ onRowSelection(row, event)
1246
+ } else if (onSelect) {
1247
+ onSelect(row.id, row)
1248
+ }
1249
+ }, [onSelect, onRowSelection, row])
633
1250
 
634
- const { className: rowClass } = row
635
- const [isInfoOpen, toggleInfo] = useState(expanded)
636
- const infoIcon = isInfoOpen ? 'expand_less' : 'expand_more'
1251
+ const handleRowDoubleClick = useCallback(() => {
1252
+ if (onRowDoubleClick) onRowDoubleClick(row.id, row)
1253
+ }, [onRowDoubleClick, row])
637
1254
 
638
- let style = isInfoOpen ? "expanded" : ""
639
- if (selected) style += " selected"
1255
+ const handleRowContextMenu = useCallback((e) => {
1256
+ if (onRowContextMenu) onRowContextMenu(e, row.id, row)
1257
+ }, [onRowContextMenu, row])
640
1258
 
641
- const finalRowClassName = [
642
- style,
643
- rowClass,
644
- typeof rowClassName === 'function' ? rowClassName(row, index) : rowClassName
1259
+ const rowClasses = [
1260
+ 'datatable2__row',
1261
+ isSelected && 'datatable2__row--selected',
1262
+ rowClassName && isFunction(rowClassName) ? rowClassName(row, index) : rowClassName
645
1263
  ].filter(Boolean).join(' ')
646
1264
 
647
- const rowProps = accessibility ? {
648
- role: 'row',
649
- 'aria-selected': selected,
650
- 'aria-rowindex': index + 1,
651
- tabIndex: 0
652
- } : {}
653
-
654
- const handleRowClick = useCallback((ev) => {
655
- onSelect(row, ev)
656
- }, [onSelect, row])
1265
+ return (
1266
+ <tr
1267
+ className={rowClasses}
1268
+ onClick={handleRowClick}
1269
+ onDoubleClick={handleRowDoubleClick}
1270
+ onContextMenu={handleRowContextMenu}
1271
+ >
1272
+ {shouldShowCheckboxes && row.checked !== undefined && (
1273
+ <td className="datatable2__select-cell">
1274
+ <CheckBox
1275
+ id={`select-${row.id}`}
1276
+ value={row.checked}
1277
+ onChange={() => onSelect && onSelect(row.id, row)}
1278
+ />
1279
+ </td>
1280
+ )}
657
1281
 
658
- const handleRowDoubleClick = useCallback((ev) => {
659
- if (onRowDoubleClick) onRowDoubleClick(row, ev)
660
- }, [onRowDoubleClick, row])
1282
+ {showRowNumbers && (
1283
+ <td className="datatable2__row-number">{index + 1}</td>
1284
+ )}
661
1285
 
662
- const handleRowContextMenu = useCallback((ev) => {
663
- if (onRowContextMenu) onRowContextMenu(row, ev)
664
- }, [onRowContextMenu, row])
1286
+ {columns.map((column) => {
1287
+ // Compatibilidad con table.js: manejar columna "checked"
1288
+ if (column.id === 'checked') {
1289
+ return (
1290
+ <td key={column.id} className="datatable2__select-cell">
1291
+ {!row.checkDisabled && (
1292
+ <CheckBox
1293
+ id="checked"
1294
+ value={row[column.id]}
1295
+ onChange={(id, value) => column.onChange && column.onChange(row.id, id, value)}
1296
+ />
1297
+ )}
1298
+ </td>
1299
+ )
1300
+ }
665
1301
 
666
- return (
667
- <Fragment>
668
- <tr
669
- className={finalRowClassName}
670
- onClick={handleRowClick}
671
- onDoubleClick={handleRowDoubleClick}
672
- onContextMenu={handleRowContextMenu}
673
- {...rowProps}
674
- >
675
- {showRowNumbers && (
676
- <td className="datatable2__row-number">
677
- {index + 1}
678
- </td>
679
- )}
680
- {columns.map((column, cindex) => (
681
- <DataTableCell2
682
- key={`${column.id}_${cindex}`}
683
- index={index}
684
- row={row}
1302
+ return (
1303
+ <DataTableCell
1304
+ key={column.id}
685
1305
  column={column}
686
- cell={row[column.id]}
687
- editable={editable || column.editable}
1306
+ row={row}
1307
+ value={row[column.id]}
1308
+ editable={editable && column.editable}
688
1309
  onCellClick={onCellClick}
689
1310
  onCellDoubleClick={onCellDoubleClick}
690
1311
  onCellEdit={onCellEdit}
691
1312
  cellClassName={cellClassName}
692
- accessibility={accessibility}
693
1313
  />
694
- ))}
695
- {row.info ? (
696
- <td>
697
- <Icon icon={infoIcon} clickable action={() => toggleInfo(!isInfoOpen)} />
698
- </td>
699
- ) : (
700
- <td></td>
701
- )}
702
- </tr>
703
- {row.info && isInfoOpen ? (
704
- <tr className="table-row-info">
705
- <td colSpan={columns.length + (showRowNumbers ? 1 : 0) + 1}>
706
- {isFunction(row.info) ? row.info() : row.info}
707
- </td>
708
- </tr>
709
- ) : null}
710
- </Fragment>
711
- )
712
- }
713
-
714
- /**
715
- * Enhanced Sort Icon - maintains original behavior
716
- */
717
- const SortIcon2 = (props) => {
718
- const { sortDir, onChange } = props
719
- const icon = sortDir ? sortDir > 0 ? "arrow_upward" : "arrow_downward" : "swap_vert"
720
- return <Icon icon={icon} size="small" clickable action={onChange} />
721
- }
722
-
723
- /**
724
- * Enhanced column resize handler
725
- */
726
- const handleColumnResize = (e, columnId, onColumnResize) => {
727
- e.preventDefault()
728
- e.stopPropagation()
729
-
730
- const startX = e.clientX
731
- const th = e.target.parentElement
732
- const startWidth = th.offsetWidth
733
-
734
- const handleMouseMove = (e) => {
735
- const newWidth = Math.max(80, startWidth + (e.clientX - startX))
736
-
737
- // Apply width directly to the element for immediate feedback
738
- th.style.width = `${newWidth}px`
739
-
740
- // Call external callback if provided
741
- if (onColumnResize) {
742
- onColumnResize(columnId, newWidth)
743
- }
744
- }
745
-
746
- const handleMouseUp = () => {
747
- document.removeEventListener('mousemove', handleMouseMove)
748
- document.removeEventListener('mouseup', handleMouseUp)
749
- document.body.style.cursor = 'default'
750
- document.body.style.userSelect = 'auto'
751
- }
752
-
753
- // Prevent text selection during resize
754
- document.body.style.cursor = 'col-resize'
755
- document.body.style.userSelect = 'none'
1314
+ )
1315
+ })}
756
1316
 
757
- document.addEventListener('mousemove', handleMouseMove)
758
- document.addEventListener('mouseup', handleMouseUp)
1317
+ {hasActionsColumn && (
1318
+ <td className="datatable2__actions-cell">
1319
+ {row.info && (
1320
+ <Icon
1321
+ icon={isExpanded ? "expand_less" : "expand_more"}
1322
+ size="small"
1323
+ clickable
1324
+ action={() => {
1325
+ if (row.info && typeof row.info === 'object' && row.info.action) {
1326
+ row.info.action(row)
1327
+ }
1328
+ if (onRowExpand) {
1329
+ onRowExpand(row.id)
1330
+ }
1331
+ }}
1332
+ />
1333
+ )}
1334
+ </td>
1335
+ )}
1336
+ </tr>
1337
+ )
759
1338
  }
760
1339
 
761
1340
  /**
762
- * Enhanced DataTable Cell - maintains original structure and logic
1341
+ * DataTable Cell Component
763
1342
  */
764
- const DataTableCell2 = ({
765
- index,
766
- row,
1343
+ const DataTableCell = ({
767
1344
  column,
768
- cell,
1345
+ row,
1346
+ value,
769
1347
  editable,
770
1348
  onCellClick,
771
1349
  onCellDoubleClick,
772
1350
  onCellEdit,
773
- cellClassName,
774
- accessibility
1351
+ cellClassName
775
1352
  }) => {
1353
+ const [isEditing, setIsEditing] = useState(false)
1354
+ const [editValue, setEditValue] = useState(value)
1355
+
776
1356
  const handleCellClick = useCallback((e) => {
777
- if (onCellClick) onCellClick(row, column, cell, e)
778
- }, [onCellClick, row, column, cell])
1357
+ if (onCellClick) onCellClick(e, column.id, row.id, value)
1358
+ if (editable) setIsEditing(true)
1359
+ }, [onCellClick, column.id, row.id, value, editable])
779
1360
 
780
1361
  const handleCellDoubleClick = useCallback((e) => {
781
- if (onCellDoubleClick) onCellDoubleClick(row, column, cell, e)
782
- }, [onCellDoubleClick, row, column, cell])
783
-
784
- const render = (type) => {
785
- const { id, disabled = false, min, max, onChange, format, options, item, action, maxDecimals } = column
786
-
787
- if (id === "checked") {
788
- return row.checkDisabled ? null : <CheckBox id={id} value={cell} onChange={(id, value) => onChange(row.id, id, value)} />
789
- } else if (editable && onChange) {
790
- switch (type) {
791
- case "ICON": return <Icon icon={cell} />
792
- case "BOOLEAN": return <CheckBox id={id} value={cell} onChange={(id, value) => onChange(row.id, id, value)} disabled={disabled} />
793
- case "Boolean": return <CheckBox id={id} value={cell} onChange={(id, value) => onChange(row.id, id, value)} disabled={disabled} />
794
- case "SELECTION": return <DropDown id={id} value={cell} placeholder="--Select--" options={options} onChange={(id, value) => onChange(row.id, id, value)} />
795
- case "CHECK": return <CheckBox id={id} value={cell} onChange={(id, value) => onChange(row.id, id, value)} disabled={row.disabled} />
796
- case "CHECKABLE": return cell && cell.value ? <CheckBox id={id} value={cell.checked || false} label={cell.value} onChange={(id, checked) => onChange(row.id, id, cell.value, checked, cell)} /> : ''
797
- case "TEXTFIELD": return <TextField id={id} value={cell} onChange={(id, value) => onChange(row.id, id, value)} />
798
- case "String": return <StringCellEditor2 id={id} value={cell} options={options} onChange={(id, value) => onChange(row.id, id, value)} />
799
- case "Number": return <TextField id={id} type="number" value={cell} min={min} max={max} maxDecimals={column.maxDecimals} onChange={(id, value) => onChange(row.id, id, value)} />
800
- case "Image": return <ImageCellViewer2 id={row.id} value={cell} uploadURL={column.uploadURL} onChange={column.onChange} />
801
- default: return cell
802
- }
803
- } else {
804
- switch (type) {
805
- case "INDEX": return <span>{index}</span>
806
- case "ICON": return <Icon icon={cell} />
807
- case "Boolean": return <BooleanCellViewer2 id={id} value={cell} />
808
- case "String": return <StringCellViewer2 id={id} value={cell} format={format} options={options} action={action} />
809
- case "Number": return <NumberCellViewer2 id={id} value={cell} format={format} maxDecimals={maxDecimals} />
810
- case "Image": return <img src={cell} alt="" />
811
- case "DATETIME":
812
- const locale = window.navigator.userLanguage || window.navigator.language;
813
- let date = new Date(cell)
814
- date.setMinutes(date.getMinutes() + date.getTimezoneOffset() + 1)
815
- return <span>{date.toLocaleString(locale, { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' })}</span>
816
- default: return cell
817
- }
1362
+ if (onCellDoubleClick) onCellDoubleClick(e, column.id, row.id, value)
1363
+ }, [onCellDoubleClick, column.id, row.id, value])
1364
+
1365
+ const handleEditComplete = useCallback(() => {
1366
+ setIsEditing(false)
1367
+ if (onCellEdit && editValue !== value) {
1368
+ onCellEdit(column.id, row.id, editValue)
818
1369
  }
819
- }
1370
+ }, [onCellEdit, column.id, row.id, editValue, value])
1371
+
1372
+ const handleKeyDown = useCallback((e) => {
1373
+ if (e.key === 'Enter') {
1374
+ handleEditComplete()
1375
+ } else if (e.key === 'Escape') {
1376
+ setEditValue(value)
1377
+ setIsEditing(false)
1378
+ }
1379
+ }, [handleEditComplete, value])
820
1380
 
821
- const finalCellClassName = [
822
- column.id,
823
- typeof cellClassName === 'function' ? cellClassName(row, column, cell) : cellClassName
1381
+ const cellClasses = [
1382
+ 'datatable2__cell',
1383
+ editable && 'datatable2__cell--editable',
1384
+ isEditing && 'datatable2__cell--editing',
1385
+ cellClassName && isFunction(cellClassName) ? cellClassName(column, row, value) : cellClassName
824
1386
  ].filter(Boolean).join(' ')
825
1387
 
826
- const cellProps = accessibility ? {
827
- role: 'cell',
828
- 'aria-describedby': column.label ? `${column.id}-header` : undefined,
829
- tabIndex: editable ? 0 : -1
830
- } : {}
1388
+ const renderCellContent = () => {
1389
+ if (isEditing && editable) {
1390
+ if (column.options) {
1391
+ return (
1392
+ <DropDown
1393
+ id={`edit-${column.id}-${row.id}`}
1394
+ value={editValue}
1395
+ options={column.options}
1396
+ onChange={(_, newValue) => setEditValue(newValue)}
1397
+ onBlur={handleEditComplete}
1398
+ autoFocus
1399
+ className="datatable2__edit-field"
1400
+ />
1401
+ )
1402
+ } else {
1403
+ return (
1404
+ <TextField
1405
+ id={`edit-${column.id}-${row.id}`}
1406
+ value={editValue}
1407
+ onChange={(_, newValue) => setEditValue(newValue)}
1408
+ onBlur={handleEditComplete}
1409
+ onKeyDown={handleKeyDown}
1410
+ autoFocus
1411
+ className="datatable2__edit-field"
1412
+ />
1413
+ )
1414
+ }
1415
+ }
1416
+
1417
+ if (column.render && isFunction(column.render)) {
1418
+ return column.render(value, row, column)
1419
+ }
1420
+
1421
+ return <Text>{value}</Text>
1422
+ }
831
1423
 
832
- return column.type === TYPES.ENTITY ? (
833
- <EntityCellViewer2 id={column.id} item={column.item} value={cell} />
834
- ) : (
1424
+ return (
835
1425
  <td
836
- key={column.id}
837
- className={finalCellClassName}
1426
+ className={cellClasses}
838
1427
  onClick={handleCellClick}
839
1428
  onDoubleClick={handleCellDoubleClick}
840
- {...cellProps}
841
1429
  >
842
- {render(column.type)}
1430
+ {renderCellContent()}
843
1431
  </td>
844
1432
  )
845
1433
  }
846
1434
 
847
- // Import and re-export original cell viewers with enhanced versions
848
- // These maintain 100% compatibility while adding new features
849
-
850
1435
  /**
851
- * Enhanced Image Cell Viewer
1436
+ * DataTable Filters Row Component
852
1437
  */
853
- const ImageCellViewer2 = ({ id, value, uploadURL, onChange }) => {
854
- function success(file, message) {
855
- if (onChange) onChange(id, file, message)
856
- }
1438
+ const DataTableFiltersRow = ({ columns, onClear, showRowNumbers, hasActionsColumn, shouldShowCheckboxes }) => {
1439
+ const [filters, setFilters] = useState({})
857
1440
 
858
- function error(file, message) {
859
- if (onChange) onChange(id, file, message)
860
- console.error(message)
861
- }
862
-
863
- return uploadURL ? (
864
- <div className="image-cell">
865
- {value ? <img src={value} alt="" /> : <Icon icon="person" />}
866
- <Uploader view="icon" icon="cloud_upload" target={uploadURL} onSuccess={success} onError={error} />
867
- </div>
868
- ) : (
869
- <div className="image-cell">
870
- {value ? <img src={value} alt="" /> : <Icon icon="person" />}
871
- </div>
872
- )
873
- }
1441
+ const handleFilterChange = useCallback((columnId, value) => {
1442
+ const newFilters = { ...filters, [columnId]: value }
1443
+ setFilters(newFilters)
874
1444
 
875
- /**
876
- * Enhanced Entity Cell Viewer
877
- */
878
- const EntityCellViewer2 = ({ id, item, value }) => {
879
- const fields = Object.values(item).filter(field => field.column === true)
880
- const locale = window.navigator.userLanguage || window.navigator.language;
881
-
882
- return fields.map(field => {
883
- let text = value[field.id]
884
-
885
- if (field.format) {
886
- switch (field.format) {
887
- case FORMATS.COLOR:
888
- text = <input type="color" value={text} disabled />
889
- break;
890
- case FORMATS.URL:
891
- text = <a href={text} target="download" download>{text}</a>
892
- break;
893
- case FORMATS.IMG:
894
- text = <img src={text} alt="" />
895
- break;
896
- case FORMATS.DATE:
897
- let fecha = new Date(text)
898
- fecha.setMinutes(fecha.getMinutes() + fecha.getTimezoneOffset() + 1)
899
- text = fecha.toLocaleString(locale, { day: 'numeric', month: 'numeric', year: 'numeric' });
900
- break;
901
- case FORMATS.TIME:
902
- text = new Date(text).toLocaleString(locale, { hour: 'numeric', minute: 'numeric', second: 'numeric' });
903
- break;
904
- case FORMATS.DATETIME:
905
- text = new Date(text).toLocaleString(locale, { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' });
906
- break;
907
- default:
908
- break;
1445
+ // Llamar al callback de filtro de la columna específica
1446
+ const column = columns.find(col => col.id === columnId)
1447
+ if (column && column.onFilter) {
1448
+ column.onFilter(columnId, value)
1449
+ }
1450
+ }, [filters, columns])
1451
+
1452
+ const handleClearFilters = useCallback(() => {
1453
+ setFilters({})
1454
+ // Limpiar todos los filtros de columnas
1455
+ columns.forEach(column => {
1456
+ if (column.onFilter) {
1457
+ column.onFilter(column.id, '')
909
1458
  }
1459
+ })
1460
+ if (onClear) onClear()
1461
+ }, [onClear, columns])
1462
+
1463
+ const hasFilters = Object.keys(filters).some(key => filters[key])
1464
+
1465
+ const renderFilterField = (column) => {
1466
+ const value = filters[column.id] || ''
1467
+
1468
+ if (column.options) {
1469
+ // Dropdown para columnas con opciones
1470
+ return (
1471
+ <DropDown
1472
+ id={`filter-${column.id}`}
1473
+ value={value}
1474
+ options={column.options}
1475
+ onChange={(_, value) => handleFilterChange(column.id, value)}
1476
+ placeholder={`Filtrar ${column.label || column.id}...`}
1477
+ className="datatable2__filter-field"
1478
+ outlined
1479
+ predictive={column.predictive}
1480
+ />
1481
+ )
1482
+ } else {
1483
+ // TextField para filtros de texto
1484
+ return (
1485
+ <TextField
1486
+ id={`filter-${column.id}`}
1487
+ value={value}
1488
+ onChange={(_, value) => handleFilterChange(column.id, value)}
1489
+ placeholder={`Filtrar ${column.label || column.id}...`}
1490
+ className="datatable2__filter-field"
1491
+ outlined
1492
+ />
1493
+ )
910
1494
  }
911
-
912
- return (<td key={field.id} className={`entity-cell ${field.id}`}>{text}</td>)
913
- })
914
- }
915
-
916
- /**
917
- * Enhanced Boolean Cell Viewer
918
- */
919
- const BooleanCellViewer2 = ({ id, value = false }) => {
920
- const icon = value === true ? "check_box" : "check_box_outline_blank"
921
- return <Icon icon={icon} />
922
- }
923
-
924
- /**
925
- * Enhanced Number Cell Viewer
926
- */
927
- const NumberCellViewer2 = ({ id, value, format, maxDecimals }) => {
928
- function formatNumber(number, maxDecimals = 0) {
929
- if (number === null) return "null"
930
- let result = number.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
931
- var parts = result.toString().split(",");
932
- const numberPart = parts[0];
933
- const decimalPart = parts[1];
934
- const thousands = /\B(?=(\d{3})+(?!\d))/g;
935
-
936
- const decimal = decimalPart ? decimalPart.substring(0, maxDecimals) : null;
937
- return numberPart.replace(thousands, ".") + (decimal ? "," + decimal : "");
938
1495
  }
939
1496
 
940
- if (format) {
941
- switch (format) {
942
- case FORMATS.CURRENCY:
943
- return <span>{value.toLocaleString('es-ES', { style: 'currency', currency: 'EUR' })}</span>
944
- case FORMATS.PERCENT:
945
- return <span>{value.toLocaleString('es-ES', { style: 'percent', minimumFractionDigits: 2 })}</span>
946
- case "ES_es":
947
- let number = Number(value)
948
- if (isNaN(number)) return value
949
- return <span>{formatNumber(number, maxDecimals)}</span>
950
- default:
951
- return <span>{value}</span>
952
- }
953
- } else {
954
- const decimals = maxDecimals ? maxDecimals : 2
955
- if (typeof value === "string") {
956
- value = parseFloat(value)
957
- }
958
- return (value && !isNaN(value)) ? <span>{value.toFixed(decimals)}</span> : ""
959
- }
960
- }
1497
+ return (
1498
+ <tr className="datatable2__filters-row">
1499
+ {/* Celda para checkboxes si están habilitados */}
1500
+ {shouldShowCheckboxes && (
1501
+ <td className="datatable2__filter-cell datatable2__filter-cell--checkbox"></td>
1502
+ )}
961
1503
 
962
- /**
963
- * Enhanced String Cell Viewer
964
- */
965
- const StringCellViewer2 = ({ id, value, format, options, action }) => {
966
- function buildOptions() {
967
- const opts = typeof options === 'function' ? options() : options
968
- return opts
969
- }
1504
+ {/* Celda para números de fila si están habilitados */}
1505
+ {showRowNumbers && (
1506
+ <td className="datatable2__filter-cell datatable2__filter-cell--row-number"></td>
1507
+ )}
970
1508
 
971
- function onClick() {
972
- if (action) action(id, value, format, options)
973
- }
1509
+ {/* Celdas de filtro para cada columna */}
1510
+ {columns.map((column) => (
1511
+ <td key={column.id} className="datatable2__filter-cell">
1512
+ {column.filterable ? renderFilterField(column) : null}
1513
+ </td>
1514
+ ))}
974
1515
 
975
- const option = options ? buildOptions().find(o => o.value === value) : null
976
- let text = option ? option.label : value
977
- const className = option ? option.className : ''
978
- const locale = window.navigator.userLanguage || window.navigator.language;
979
-
980
- switch (format) {
981
- case FORMATS.TOKENS:
982
- text = value && Array.isArray(value) ? value.join(", ") : value
983
- break;
984
- case FORMATS.COLOR:
985
- text = <input type="color" value={text} disabled />
986
- break;
987
- case FORMATS.URL:
988
- text = <a href={text} target="download" download>{text}</a>
989
- break;
990
- case FORMATS.IMG:
991
- text = <img src={text} onClick={onClick} alt="" />
992
- break;
993
- case FORMATS.DATE:
994
- let fecha = new Date(text)
995
- fecha.setMinutes(fecha.getMinutes() + fecha.getTimezoneOffset() + 1)
996
- text = fecha.toLocaleString(locale, { day: 'numeric', month: 'numeric', year: 'numeric' });
997
- break;
998
- case FORMATS.TIME:
999
- text = new Date(text).toLocaleString(locale, { hour: 'numeric', minute: 'numeric', second: 'numeric' });
1000
- break;
1001
- case FORMATS.DATETIME:
1002
- text = new Date(text).toLocaleString(locale, { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' });
1003
- break;
1004
- default:
1005
- break;
1006
- }
1007
- return (<div className={`field-editor string-viewer ${className}`} title={text}>{text}</div>)
1516
+ {/* Celda para acciones con botón de limpiar */}
1517
+ {hasActionsColumn && (
1518
+ <td className="datatable2__filter-cell datatable2__filter-cell--actions">
1519
+ <Icon
1520
+ icon="close"
1521
+ size="small"
1522
+ clickable
1523
+ action={handleClearFilters}
1524
+ disabled={!hasFilters}
1525
+ ariaLabel="Limpiar filtros"
1526
+ className={`datatable2__clear-filters ${hasFilters ? 'datatable2__clear-filters--active' : ''}`}
1527
+ />
1528
+ </td>
1529
+ )}
1530
+ </tr>
1531
+ )
1008
1532
  }
1009
1533
 
1010
1534
  /**
1011
- * Enhanced String Cell Editor
1535
+ * DataTable Empty State Component
1012
1536
  */
1013
- const StringCellEditor2 = ({ id, value = '', options, onChange }) => {
1014
- function change(id, value) {
1015
- if (onChange) onChange(id, value)
1537
+ const DataTableEmpty = ({ message, icon, customState }) => {
1538
+ if (customState) {
1539
+ return (
1540
+ <tbody>
1541
+ <tr>
1542
+ <td colSpan="100%" className="datatable2__empty-cell">
1543
+ {customState}
1544
+ </td>
1545
+ </tr>
1546
+ </tbody>
1547
+ )
1016
1548
  }
1017
1549
 
1018
1550
  return (
1019
- <div className='field-editor string-editor'>
1020
- {options ?
1021
- <DropDown outlined id={id} value={value} onChange={change} options={options} />
1022
- : <TextField outlined id={id} value={value} onChange={change} />
1023
- }
1024
- </div>
1551
+ <tbody>
1552
+ <tr>
1553
+ <td colSpan="100%" className="datatable2__empty-cell">
1554
+ <div className="datatable2__empty-state">
1555
+ <Icon icon={icon} size="large" />
1556
+ <Text>{message}</Text>
1557
+ </div>
1558
+ </td>
1559
+ </tr>
1560
+ </tbody>
1025
1561
  )
1026
1562
  }
1027
1563
 
1028
1564
  /**
1029
- * Skeleton loading table
1565
+ * DataTable Skeleton Component
1030
1566
  */
1031
- const SkeletonTable = ({ columns, rows }) => (
1032
- <table>
1033
- <thead>
1034
- <tr>
1035
- {columns.map(col => (
1036
- <th key={col.id}>
1037
- <div className="skeleton-text skeleton-text--header"></div>
1038
- </th>
1039
- ))}
1040
- </tr>
1041
- </thead>
1567
+ const DataTableSkeleton = ({ columns, showRowNumbers, hasActionsColumn }) => {
1568
+ const skeletonRows = Array.from({ length: 5 }, (_, i) => i)
1569
+
1570
+ return (
1042
1571
  <tbody>
1043
- {Array.from({ length: rows }, (_, i) => (
1044
- <tr key={i}>
1045
- {columns.map(col => (
1046
- <td key={col.id}>
1047
- <div className="skeleton-text"></div>
1572
+ {skeletonRows.map((_, index) => (
1573
+ <tr key={index} className="datatable2__skeleton-row">
1574
+ {showRowNumbers && (
1575
+ <td className="datatable2__skeleton-cell">
1576
+ <div className="datatable2__skeleton-text datatable2__skeleton-text--short" />
1577
+ </td>
1578
+ )}
1579
+
1580
+ {columns.map((column) => (
1581
+ <td key={column.id} className="datatable2__skeleton-cell">
1582
+ <div className="datatable2__skeleton-text" />
1048
1583
  </td>
1049
1584
  ))}
1585
+
1586
+ {hasActionsColumn && (
1587
+ <td className="datatable2__skeleton-cell">
1588
+ <div className="datatable2__skeleton-text datatable2__skeleton-text--short" />
1589
+ </td>
1590
+ )}
1050
1591
  </tr>
1051
1592
  ))}
1052
1593
  </tbody>
1053
- </table>
1054
- )
1594
+ )
1595
+ }
1596
+
1597
+ /**
1598
+ * DataTable Footer Component
1599
+ */
1600
+ const DataTableFooter = ({ onExport }) => {
1601
+ return (
1602
+ <div className="datatable2__footer">
1603
+ <button
1604
+ className="datatable2__export-btn"
1605
+ onClick={onExport}
1606
+ >
1607
+ <Icon icon="download" size="small" />
1608
+ Export
1609
+ </button>
1610
+ </div>
1611
+ )
1612
+ }
1055
1613
 
1056
- // PropTypes for DataTable2
1614
+ // PropTypes
1057
1615
  DataTable2.propTypes = {
1058
- // Original props (100% compatible)
1059
- /** Array of column definitions */
1060
- columns: PropTypes.arrayOf(PropTypes.shape({
1061
- id: PropTypes.string.isRequired,
1062
- label: PropTypes.string,
1063
- type: PropTypes.string,
1064
- sortable: PropTypes.bool,
1065
- filterable: PropTypes.bool,
1066
- resizable: PropTypes.bool,
1067
- editable: PropTypes.bool,
1068
- format: PropTypes.string,
1069
- options: PropTypes.array,
1070
- onChange: PropTypes.func,
1071
- onFilter: PropTypes.func,
1072
- action: PropTypes.func,
1073
- disabled: PropTypes.bool,
1074
- min: PropTypes.number,
1075
- max: PropTypes.number,
1076
- maxDecimals: PropTypes.number,
1077
- predictive: PropTypes.bool,
1078
- item: PropTypes.object,
1079
- uploadURL: PropTypes.string
1080
- })),
1081
- /** Array of row data */
1082
- rows: PropTypes.arrayOf(PropTypes.shape({
1083
- id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
1084
- selected: PropTypes.bool,
1085
- className: PropTypes.string,
1086
- checkDisabled: PropTypes.bool,
1087
- disabled: PropTypes.bool,
1088
- info: PropTypes.oneOfType([PropTypes.node, PropTypes.func])
1089
- })),
1090
- /** Row selection callback */
1091
- onRowSelection: PropTypes.func,
1092
- /** Sort callback */
1616
+ id: PropTypes.string,
1617
+ columns: PropTypes.array.isRequired,
1618
+ rows: PropTypes.array.isRequired,
1619
+ sortDir: PropTypes.object,
1093
1620
  onSort: PropTypes.func,
1094
- /** Check all callback */
1621
+ onSelect: PropTypes.func,
1622
+ onRowSelection: PropTypes.func, // Compatibilidad con table.js
1095
1623
  onCheckAll: PropTypes.func,
1096
- /** Enable editing */
1624
+ allChecked: PropTypes.bool,
1625
+ showSelectAll: PropTypes.bool,
1626
+ showRowNumbers: PropTypes.bool,
1097
1627
  editable: PropTypes.bool,
1098
- /** Outlined style */
1099
- outlined: PropTypes.bool,
1100
- /** Expand rows by default */
1101
1628
  expanded: PropTypes.bool,
1102
- /** Additional CSS classes */
1629
+ selectedRows: PropTypes.array,
1630
+ onRowDoubleClick: PropTypes.func,
1631
+ onRowContextMenu: PropTypes.func,
1632
+ onCellClick: PropTypes.func,
1633
+ onCellDoubleClick: PropTypes.func,
1634
+ onCellEdit: PropTypes.func,
1635
+ onDrop: PropTypes.func,
1103
1636
  className: PropTypes.string,
1104
- /** Empty state message */
1105
1637
  emptyMessage: PropTypes.string,
1106
- /** Empty state icon */
1107
1638
  emptyIcon: PropTypes.string,
1108
- /** Enable multi-sort */
1109
1639
  multisort: PropTypes.bool,
1110
- /** Enable filtering */
1111
1640
  filterable: PropTypes.bool,
1112
- /** Clear filters callback */
1113
1641
  onClearFilters: PropTypes.func,
1114
-
1115
- // New enhanced props (all optional for compatibility)
1116
- /** Loading state */
1117
1642
  loading: PropTypes.bool,
1118
- /** Skeleton placeholder */
1119
1643
  skeleton: PropTypes.bool,
1120
- /** Striped rows */
1121
1644
  striped: PropTypes.bool,
1122
- /** Hover effects */
1123
1645
  hover: PropTypes.bool,
1124
- /** Compact layout */
1125
- compact: PropTypes.bool,
1126
- /** Bordered table */
1127
1646
  bordered: PropTypes.bool,
1128
- /** Responsive design */
1129
- responsive: PropTypes.bool,
1130
- /** Sticky header */
1647
+ size: PropTypes.oneOf(['small', 'medium', 'large']),
1648
+ density: PropTypes.oneOf(['compact', 'normal', 'comfortable']),
1649
+ theme: PropTypes.oneOf(['default', 'dark', 'minimal']),
1650
+ readability: PropTypes.oneOf([null, 'large', 'contrast', 'dyslexia', 'print', 'compact', 'focus', 'dark']),
1131
1651
  stickyHeader: PropTypes.bool,
1132
- /** Virtual scrolling */
1133
- virtualScrolling: PropTypes.bool,
1134
- /** Page size for virtual scrolling */
1135
- pageSize: PropTypes.number,
1136
- /** Selection mode */
1137
- selectionMode: PropTypes.oneOf(['single', 'multiple', 'none']),
1138
- /** Sort mode */
1139
- sortMode: PropTypes.oneOf(['single', 'multiple', 'none']),
1140
- /** Resizable columns */
1141
1652
  resizable: PropTypes.bool,
1142
- /** Reorderable columns */
1143
- reorderable: PropTypes.bool,
1144
- /** Export functionality */
1653
+ showSidebar: PropTypes.bool,
1654
+ sidebarId: PropTypes.string,
1655
+ sidebarTools: PropTypes.arrayOf(PropTypes.shape({
1656
+ id: PropTypes.string.isRequired,
1657
+ icon: PropTypes.string.isRequired,
1658
+ title: PropTypes.string.isRequired,
1659
+ component: PropTypes.oneOfType([
1660
+ PropTypes.func, // React component
1661
+ PropTypes.string // Legacy string identifier
1662
+ ]).isRequired
1663
+ })),
1145
1664
  exportable: PropTypes.bool,
1146
- /** Search functionality */
1147
1665
  searchable: PropTypes.bool,
1148
- /** Search placeholder */
1149
- searchPlaceholder: PropTypes.string,
1150
- /** Search callback */
1151
- onSearch: PropTypes.func,
1152
- /** Export callback */
1153
- onExport: PropTypes.func,
1154
- /** Column resize callback */
1155
- onColumnResize: PropTypes.func,
1156
- /** Column reorder callback */
1157
- onColumnReorder: PropTypes.func,
1158
- /** Custom empty state */
1666
+ pagination: PropTypes.object,
1159
1667
  customEmptyState: PropTypes.node,
1160
- /** Row height */
1161
- rowHeight: PropTypes.oneOf(['small', 'medium', 'large']),
1162
- /** Table density */
1163
- density: PropTypes.oneOf(['compact', 'normal', 'comfortable']),
1164
- /** Theme */
1165
- theme: PropTypes.oneOf(['default', 'dark', 'minimal']),
1166
- /** Accessibility features */
1167
- accessibility: PropTypes.bool,
1168
- /** Row click callback */
1169
- onRowClick: PropTypes.func,
1170
- /** Row double click callback */
1171
- onRowDoubleClick: PropTypes.func,
1172
- /** Row context menu callback */
1173
- onRowContextMenu: PropTypes.func,
1174
- /** Cell click callback */
1175
- onCellClick: PropTypes.func,
1176
- /** Cell double click callback */
1177
- onCellDoubleClick: PropTypes.func,
1178
- /** Cell edit callback */
1179
- onCellEdit: PropTypes.func,
1180
- /** Row class name function */
1181
1668
  rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
1182
- /** Cell class name function */
1183
1669
  cellClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
1184
- /** Header class name */
1185
1670
  headerClassName: PropTypes.string,
1186
- /** Footer content */
1187
- footerContent: PropTypes.node,
1188
- /** Show row numbers */
1189
- showRowNumbers: PropTypes.bool,
1190
- /** Show select all checkbox */
1191
- showSelectAll: PropTypes.bool,
1192
- /** Persist table state */
1193
- persistState: PropTypes.bool,
1194
- /** State key for persistence */
1195
- stateKey: PropTypes.string,
1196
- /** State change callback */
1197
- onStateChange: PropTypes.func
1198
- }
1199
-
1200
- DataTable2.defaultProps = {
1201
- columns: [],
1202
- rows: [],
1203
- expanded: false,
1204
- emptyMessage: "No Results Found",
1205
- emptyIcon: "search_off",
1206
- multisort: false,
1207
- filterable: false,
1208
- loading: false,
1209
- skeleton: false,
1210
- striped: false,
1211
- hover: true,
1212
- compact: false,
1213
- bordered: false,
1214
- responsive: true,
1215
- stickyHeader: true,
1216
- virtualScrolling: false,
1217
- pageSize: 50,
1218
- selectionMode: 'single',
1219
- sortMode: 'single',
1220
- resizable: false,
1221
- reorderable: false,
1222
- exportable: false,
1223
- searchable: false,
1224
- searchPlaceholder: "Search...",
1225
- rowHeight: 'medium',
1226
- density: 'normal',
1227
- theme: 'default',
1228
- accessibility: true,
1229
- showRowNumbers: false,
1230
- showSelectAll: true,
1231
- persistState: false
1232
- }
1233
-
1234
- // Export original components for compatibility
1235
- export { StringCellEditor2 as StringCellEditor }
1236
-
1237
- // Export new enhanced components
1238
- export {
1239
- DataTable2Header,
1240
- DataTable2Body,
1241
- DataTableRow2,
1242
- DataTableCell2,
1243
- StringCellEditor2,
1244
- ImageCellViewer2,
1245
- EntityCellViewer2,
1246
- BooleanCellViewer2,
1247
- NumberCellViewer2,
1248
- StringCellViewer2
1671
+ accessibility: PropTypes.bool,
1672
+ height: PropTypes.string,
1673
+ maxHeight: PropTypes.string,
1674
+ minHeight: PropTypes.string,
1675
+ onColumnResize: PropTypes.func,
1676
+ onExport: PropTypes.func,
1677
+ onSearch: PropTypes.func
1249
1678
  }
1250
-
1251
- // Default export for easy migration
1252
- export default DataTable2