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.
- package/dist/index.cjs +3244 -2215
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +2127 -1063
- package/dist/index.css.map +1 -1
- package/dist/index.modern.js +3244 -2215
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +3244 -2215
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/html/ExampleLayout.css +401 -0
- package/src/html/ExampleLayout.js +192 -0
- package/src/html/README-sidebar-navigation.md +195 -0
- package/src/html/accordion.example.js +123 -4
- package/src/html/accordion.example.js.backup +390 -0
- package/src/html/button.example.js +50 -3
- package/src/html/button.example.js.backup +374 -0
- package/src/html/button.example.new.js +416 -0
- package/src/html/checkbox.example.js +93 -4
- package/src/html/checkbox.example.js.backup +316 -0
- package/src/html/chip.example.js +108 -4
- package/src/html/chip.example.js.backup +355 -0
- package/src/html/color.example.js +108 -4
- package/src/html/color.example.js.backup +527 -0
- package/src/html/components.example.js +123 -4
- package/src/html/components.example.js.backup +492 -0
- package/src/html/convert-examples.js +183 -0
- package/src/html/demo-sidebar.html +410 -0
- package/src/html/form.example.js +93 -4
- package/src/html/form.example.js.backup +385 -0
- package/src/html/header2.example.js +108 -4
- package/src/html/header2.example.js.backup +411 -0
- package/src/html/icon.example.js +77 -3
- package/src/html/icon.example.js.backup +268 -0
- package/src/html/list.example.js +93 -4
- package/src/html/list.example.js.backup +404 -0
- package/src/html/progress.example.js +74 -4
- package/src/html/progress.example.js.backup +424 -0
- package/src/html/property.example.js +123 -4
- package/src/html/property.example.js.backup +553 -0
- package/src/html/radio.example.js +108 -4
- package/src/html/radio.example.js.backup +389 -0
- package/src/html/section.example.js +42 -3
- package/src/html/section.example.js.backup +99 -0
- package/src/html/switch.example.js +108 -4
- package/src/html/switch.example.js.backup +461 -0
- package/src/html/tab.example.js +93 -4
- package/src/html/tab.example.js.backup +446 -0
- package/src/html/table-export-utils.js +483 -0
- package/src/html/table-summary-functions.js +363 -0
- package/src/html/table2.css +1496 -432
- package/src/html/table2.example.js +2937 -512
- package/src/html/table2.example.js.broken +1226 -0
- package/src/html/table2.js +1426 -1000
- package/src/html/test-resize.html +279 -0
- package/src/html/test-selection.html +387 -0
- package/src/html/textfield2.example.js +108 -4
- package/src/html/textfield2.example.js.backup +1370 -0
- package/src/html/tokenfield.example.js +108 -4
- package/src/html/tokenfield.example.js.backup +503 -0
- package/src/html/tree.css +2 -4
- package/src/html/tree.example.js +93 -4
- package/src/html/tree.example.js.backup +475 -0
- package/src/html/tree.js +19 -3
package/src/html/table2.js
CHANGED
@@ -1,516 +1,1136 @@
|
|
1
|
-
import 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 {
|
9
|
-
import {
|
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
|
-
*
|
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
|
-
|
23
|
-
columns = [],
|
24
|
-
rows = [],
|
25
|
-
|
26
|
-
onSort,
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
//
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
//
|
83
|
-
const [
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
99
|
-
|
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
|
-
|
102
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
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
|
-
//
|
126
|
-
const
|
127
|
-
const
|
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 (
|
130
|
-
|
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
|
-
//
|
165
|
-
const
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
181
|
-
|
182
|
-
|
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
|
-
}, [
|
185
|
-
|
186
|
-
//
|
187
|
-
const
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
const
|
193
|
-
|
194
|
-
|
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
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
}
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
269
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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=
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
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
|
-
{/*
|
320
|
-
|
321
|
-
<div className="
|
322
|
-
<
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
>
|
327
|
-
|
328
|
-
|
329
|
-
|
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
|
-
{/*
|
334
|
-
<
|
335
|
-
<
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
-
{/*
|
377
|
-
{
|
378
|
-
<div className="
|
379
|
-
|
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
|
-
{/*
|
384
|
-
|
385
|
-
<
|
386
|
-
<
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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
|
-
*
|
931
|
+
* DataTable Header Component
|
409
932
|
*/
|
410
|
-
const
|
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
|
-
|
421
|
-
|
422
|
-
|
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
|
-
{
|
434
|
-
<th className="
|
435
|
-
<
|
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
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
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
|
-
<
|
452
|
-
key={id}
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
}
|
459
|
-
|
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
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
-
*
|
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
|
1123
|
+
const DataTableBody = ({
|
504
1124
|
columns,
|
505
1125
|
rows,
|
506
1126
|
onSelect,
|
507
|
-
|
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
|
-
|
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
|
-
{
|
530
|
-
|
531
|
-
|
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
|
-
|
1166
|
+
onRowSelection={onRowSelection}
|
540
1167
|
editable={editable}
|
541
|
-
|
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
|
-
|
1180
|
+
isExpanded={expandedRows.has(row.id)}
|
1181
|
+
onRowExpand={onRowExpand}
|
552
1182
|
/>
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
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
|
-
*
|
1213
|
+
* DataTable Row Component
|
573
1214
|
*/
|
574
|
-
const
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
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
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
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
|
635
|
-
|
636
|
-
|
1251
|
+
const handleRowDoubleClick = useCallback(() => {
|
1252
|
+
if (onRowDoubleClick) onRowDoubleClick(row.id, row)
|
1253
|
+
}, [onRowDoubleClick, row])
|
637
1254
|
|
638
|
-
|
639
|
-
|
1255
|
+
const handleRowContextMenu = useCallback((e) => {
|
1256
|
+
if (onRowContextMenu) onRowContextMenu(e, row.id, row)
|
1257
|
+
}, [onRowContextMenu, row])
|
640
1258
|
|
641
|
-
const
|
642
|
-
|
643
|
-
|
644
|
-
|
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
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
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
|
-
|
659
|
-
|
660
|
-
|
1282
|
+
{showRowNumbers && (
|
1283
|
+
<td className="datatable2__row-number">{index + 1}</td>
|
1284
|
+
)}
|
661
1285
|
|
662
|
-
|
663
|
-
|
664
|
-
|
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
|
-
|
667
|
-
|
668
|
-
|
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
|
-
|
687
|
-
|
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
|
-
|
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
|
-
|
758
|
-
|
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
|
-
*
|
1341
|
+
* DataTable Cell Component
|
763
1342
|
*/
|
764
|
-
const
|
765
|
-
index,
|
766
|
-
row,
|
1343
|
+
const DataTableCell = ({
|
767
1344
|
column,
|
768
|
-
|
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(
|
778
|
-
|
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(
|
782
|
-
}, [onCellDoubleClick,
|
783
|
-
|
784
|
-
const
|
785
|
-
|
786
|
-
|
787
|
-
|
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
|
822
|
-
|
823
|
-
|
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
|
827
|
-
|
828
|
-
|
829
|
-
|
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
|
833
|
-
<EntityCellViewer2 id={column.id} item={column.item} value={cell} />
|
834
|
-
) : (
|
1424
|
+
return (
|
835
1425
|
<td
|
836
|
-
|
837
|
-
className={finalCellClassName}
|
1426
|
+
className={cellClasses}
|
838
1427
|
onClick={handleCellClick}
|
839
1428
|
onDoubleClick={handleCellDoubleClick}
|
840
|
-
{...cellProps}
|
841
1429
|
>
|
842
|
-
{
|
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
|
-
*
|
1436
|
+
* DataTable Filters Row Component
|
852
1437
|
*/
|
853
|
-
const
|
854
|
-
|
855
|
-
if (onChange) onChange(id, file, message)
|
856
|
-
}
|
1438
|
+
const DataTableFiltersRow = ({ columns, onClear, showRowNumbers, hasActionsColumn, shouldShowCheckboxes }) => {
|
1439
|
+
const [filters, setFilters] = useState({})
|
857
1440
|
|
858
|
-
|
859
|
-
|
860
|
-
|
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
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
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
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
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
|
-
|
964
|
-
|
965
|
-
|
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
|
-
|
972
|
-
|
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
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
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
|
-
*
|
1535
|
+
* DataTable Empty State Component
|
1012
1536
|
*/
|
1013
|
-
const
|
1014
|
-
|
1015
|
-
|
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
|
-
<
|
1020
|
-
|
1021
|
-
<
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
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
|
1565
|
+
* DataTable Skeleton Component
|
1030
1566
|
*/
|
1031
|
-
const
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
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
|
-
{
|
1044
|
-
<tr key={
|
1045
|
-
{
|
1046
|
-
<td
|
1047
|
-
<div className="
|
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
|
-
|
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
|
1614
|
+
// PropTypes
|
1057
1615
|
DataTable2.propTypes = {
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
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
|
-
|
1621
|
+
onSelect: PropTypes.func,
|
1622
|
+
onRowSelection: PropTypes.func, // Compatibilidad con table.js
|
1095
1623
|
onCheckAll: PropTypes.func,
|
1096
|
-
|
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
|
-
|
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
|
-
|
1129
|
-
|
1130
|
-
|
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
|
-
|
1143
|
-
|
1144
|
-
|
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
|
-
|
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
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
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
|