ywana-core8 0.1.74 → 0.1.76
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/ACCORDION_EVALUATION.md +583 -0
- package/CHECKBOX_EVALUATION.md +273 -0
- package/CHIP_EVALUATION.md +542 -0
- package/COLOR_EVALUATION.md +524 -0
- package/COMPONENTS_EVALUATION.md +477 -0
- package/FORM_EVALUATION.md +459 -0
- package/HEADER_EVALUATION.md +436 -0
- package/ICON_EVALUATION.md +254 -0
- package/LIST_EVALUATION.md +574 -0
- package/PROGRESS_EVALUATION.md +450 -0
- package/RADIO_EVALUATION.md +439 -0
- package/RADIO_VISUAL_FIX.md +183 -0
- package/SECTION_IMPROVEMENTS.md +153 -0
- package/SWITCH_EVALUATION.md +335 -0
- package/SWITCH_VISUAL_FIX.md +232 -0
- package/TAB_EVALUATION.md +626 -0
- package/TEXTFIELD_EVALUATION.md +747 -0
- package/TOOLTIP_FIX.md +157 -0
- package/TREE_EVALUATION.md +708 -0
- package/dist/index.cjs +7900 -1615
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +6094 -1122
- package/dist/index.css.map +1 -1
- package/dist/index.modern.js +7929 -1645
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +7900 -1615
- package/dist/index.umd.js.map +1 -1
- package/jest.config.js +24 -0
- package/package.json +10 -1
- package/src/html/accordion.css +208 -4
- package/src/html/accordion.example.js +390 -0
- package/src/html/accordion.js +284 -28
- package/src/html/accordion.unit.test.js +334 -0
- package/src/html/button.css +157 -16
- package/src/html/button.example.js +374 -0
- package/src/html/button.js +240 -60
- package/src/html/button.test.js +422 -0
- package/src/html/checkbox.css +74 -2
- package/src/html/checkbox.example.js +316 -0
- package/src/html/checkbox.js +113 -26
- package/src/html/checkbox.test.js +285 -0
- package/src/html/chip.css +230 -19
- package/src/html/chip.example.js +355 -0
- package/src/html/chip.js +321 -25
- package/src/html/chip.test.js +425 -0
- package/src/html/color.css +435 -6
- package/src/html/color.example.js +527 -0
- package/src/html/color.js +458 -9
- package/src/html/color.test.js +362 -4
- package/src/html/components.example.js +492 -0
- package/src/html/components_enhanced.test.js +581 -0
- package/src/html/form.css +70 -3
- package/src/html/form.example.js +385 -0
- package/src/html/form.js +232 -34
- package/src/html/form.test.js +369 -0
- package/src/html/header2.css +264 -0
- package/src/html/header2.example.js +411 -0
- package/src/html/header2.js +203 -0
- package/src/html/header2.test.js +377 -0
- package/src/html/icon.css +20 -2
- package/src/html/icon.example.js +268 -0
- package/src/html/icon.js +86 -16
- package/src/html/icon.test.js +231 -0
- package/src/html/index.js +1 -1
- package/src/html/list.css +393 -1
- package/src/html/list.example.js +404 -0
- package/src/html/list.js +583 -40
- package/src/html/list.test.js +383 -0
- package/src/html/progress.css +707 -17
- package/src/html/progress.example.js +424 -0
- package/src/html/progress.js +906 -9
- package/src/html/progress.test.js +313 -0
- package/src/html/property.css +399 -0
- package/src/html/property.example.js +553 -0
- package/src/html/property.js +393 -15
- package/src/html/property.test.js +351 -2
- package/src/html/radio-visual-test.js +289 -0
- package/src/html/radio.css +137 -11
- package/src/html/radio.example.js +389 -0
- package/src/html/radio.js +234 -10
- package/src/html/radio.test.js +318 -0
- package/src/html/section.example.js +99 -0
- package/src/html/section.js +40 -3
- package/src/html/section.test.js +131 -0
- package/src/html/selector.css +329 -3
- package/src/html/selector.js +369 -23
- package/src/html/switch-debug.js +197 -0
- package/src/html/switch-test-visual.js +294 -0
- package/src/html/switch.css +200 -0
- package/src/html/switch.example.js +461 -0
- package/src/html/switch.js +283 -23
- package/src/html/switch.test.js +355 -0
- package/src/html/tab.css +288 -0
- package/src/html/tab.example.js +446 -0
- package/src/html/tab.js +387 -22
- package/src/html/tab_enhanced.js +378 -0
- package/src/html/tab_enhanced.test.js +504 -0
- package/src/html/table2.css +576 -0
- package/src/html/table2.example.js +703 -0
- package/src/html/table2.js +1252 -0
- package/src/html/table2.migration.md +328 -0
- package/src/html/table2.test.js +582 -0
- package/src/html/text.css +375 -0
- package/src/html/text.js +311 -20
- package/src/html/textfield.js +1 -1
- package/src/html/textfield2.css +842 -0
- package/src/html/textfield2.example.js +499 -0
- package/src/html/textfield2.js +1130 -0
- package/src/html/textfield2.test.js +950 -0
- package/src/html/thumbnail.css +289 -2
- package/src/html/thumbnail.js +214 -9
- package/src/html/tokenfield.css +449 -1
- package/src/html/tokenfield.example.js +503 -0
- package/src/html/tokenfield.js +561 -56
- package/src/html/tokenfield.test.js +423 -0
- package/src/html/tooltip-positioning-demo.js +187 -0
- package/src/html/tooltip.css +25 -2
- package/src/html/tree.css +228 -0
- package/src/html/tree.example.js +475 -0
- package/src/html/tree.js +712 -28
- package/src/html/tree_enhanced.test.js +495 -0
- package/table2.test.js +454 -0
- package/src/html/button.tsx +0 -38
@@ -0,0 +1,1252 @@
|
|
1
|
+
import React, { Fragment, useState, useCallback, useRef, useEffect, useMemo } from 'react'
|
2
|
+
import PropTypes from 'prop-types'
|
3
|
+
import { FORMATS, TYPES } from '../domain/ContentType'
|
4
|
+
import { DropDown, TextField } from './textfield'
|
5
|
+
import { CheckBox } from './checkbox'
|
6
|
+
import { Icon } from './icon'
|
7
|
+
import { Text } from './text'
|
8
|
+
import { EmptyMessage } from '../widgets/empty/EmptyMessage'
|
9
|
+
import { Uploader } from '../widgets/upload/Uploader'
|
10
|
+
import './table2.css'
|
11
|
+
|
12
|
+
const isFunction = value => value && (Object.prototype.toString.call(value) === "[object Function]" || "function" === typeof value || value instanceof Function);
|
13
|
+
|
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.
|
19
|
+
*/
|
20
|
+
export const DataTable2 = (props) => {
|
21
|
+
const {
|
22
|
+
// Original props (100% compatible)
|
23
|
+
columns = [],
|
24
|
+
rows = [],
|
25
|
+
onRowSelection,
|
26
|
+
onSort,
|
27
|
+
onCheckAll,
|
28
|
+
editable,
|
29
|
+
outlined,
|
30
|
+
expanded = false,
|
31
|
+
className,
|
32
|
+
emptyMessage = "No Results Found",
|
33
|
+
emptyIcon = "search_off",
|
34
|
+
multisort = false,
|
35
|
+
filterable = false,
|
36
|
+
onClearFilters,
|
37
|
+
// New enhanced props (all optional for compatibility)
|
38
|
+
loading = false,
|
39
|
+
skeleton = false,
|
40
|
+
striped = false,
|
41
|
+
hover = true,
|
42
|
+
compact = false,
|
43
|
+
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
|
+
exportable = false,
|
53
|
+
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,
|
70
|
+
rowClassName,
|
71
|
+
cellClassName,
|
72
|
+
headerClassName,
|
73
|
+
footerContent,
|
74
|
+
showRowNumbers = false,
|
75
|
+
showSelectAll = true,
|
76
|
+
persistState = false,
|
77
|
+
stateKey,
|
78
|
+
onStateChange,
|
79
|
+
...restProps
|
80
|
+
} = props
|
81
|
+
|
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')
|
97
|
+
}
|
98
|
+
if (!Array.isArray(rows)) {
|
99
|
+
console.warn('DataTable2: rows prop must be an array')
|
100
|
+
}
|
101
|
+
if (virtualScrolling && !pageSize) {
|
102
|
+
console.warn('DataTable2: pageSize is required when virtualScrolling is enabled')
|
103
|
+
}
|
104
|
+
}, [columns, rows, virtualScrolling, pageSize])
|
105
|
+
|
106
|
+
// Sync loading state
|
107
|
+
useEffect(() => {
|
108
|
+
setIsLoading(loading)
|
109
|
+
}, [loading])
|
110
|
+
|
111
|
+
// Enhanced sort function (maintaining original behavior)
|
112
|
+
const changeSort = useCallback((id) => {
|
113
|
+
if (sortMode === 'none') return
|
114
|
+
|
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 })
|
122
|
+
}
|
123
|
+
}, [sortDir, sortMode, multisort])
|
124
|
+
|
125
|
+
// Enhanced multiSort function (maintaining original logic)
|
126
|
+
const multiSort = useCallback((array, sortObject = {}) => {
|
127
|
+
const sortKeys = Object.keys(sortObject);
|
128
|
+
|
129
|
+
if (!sortKeys.length) {
|
130
|
+
return array;
|
131
|
+
}
|
132
|
+
|
133
|
+
const keySort = (a, b, direction) => {
|
134
|
+
direction = direction !== null ? direction : 1;
|
135
|
+
|
136
|
+
// check if a and b are numbers and compare as numbers
|
137
|
+
if (!isNaN(a) && !isNaN(b)) {
|
138
|
+
a = Number(a);
|
139
|
+
b = Number(b);
|
140
|
+
}
|
141
|
+
|
142
|
+
// If b > a, multiply by -1 to get the reverse direction.
|
143
|
+
return a > b ? direction : -1 * direction;
|
144
|
+
};
|
145
|
+
|
146
|
+
return array.sort((a, b) => {
|
147
|
+
let sorted = 0;
|
148
|
+
let index = 0;
|
149
|
+
|
150
|
+
// Loop until sorted (-1 or 1) or until the sort keys have been processed.
|
151
|
+
while (sorted === 0 && index < sortKeys.length) {
|
152
|
+
const key = sortKeys[index];
|
153
|
+
if (key) {
|
154
|
+
const direction = sortObject[key];
|
155
|
+
sorted = keySort(a[key], b[key], direction);
|
156
|
+
index++;
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
return sorted;
|
161
|
+
});
|
162
|
+
}, [])
|
163
|
+
|
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
|
+
}
|
179
|
+
|
180
|
+
// Call original callback
|
181
|
+
if (onRowSelection) onRowSelection(row, event)
|
182
|
+
if (onRowClick) onRowClick(row, event)
|
183
|
+
}
|
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
|
+
}
|
202
|
+
}
|
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())
|
216
|
+
})
|
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])
|
241
|
+
|
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)
|
265
|
+
}
|
266
|
+
}, [processedRows, columns, onExport])
|
267
|
+
|
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(' ')
|
285
|
+
|
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
|
+
)
|
303
|
+
}
|
304
|
+
|
305
|
+
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
|
+
/>
|
316
|
+
</div>
|
317
|
+
)}
|
318
|
+
|
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>
|
330
|
+
</div>
|
331
|
+
)}
|
332
|
+
|
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>
|
375
|
+
|
376
|
+
{/* Footer */}
|
377
|
+
{footerContent && (
|
378
|
+
<div className="datatable2__footer">
|
379
|
+
{footerContent}
|
380
|
+
</div>
|
381
|
+
)}
|
382
|
+
|
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>
|
402
|
+
)}
|
403
|
+
</div>
|
404
|
+
)
|
405
|
+
}
|
406
|
+
|
407
|
+
/**
|
408
|
+
* Enhanced DataTable Header - maintains original structure
|
409
|
+
*/
|
410
|
+
const DataTable2Header = ({
|
411
|
+
columns,
|
412
|
+
sortDir,
|
413
|
+
onSort,
|
414
|
+
allChecked,
|
415
|
+
onCheckAll,
|
416
|
+
showSelectAll,
|
417
|
+
showRowNumbers,
|
418
|
+
resizable,
|
419
|
+
onColumnResize,
|
420
|
+
accessibility,
|
421
|
+
headerClassName,
|
422
|
+
stickyHeader
|
423
|
+
}) => {
|
424
|
+
const headerClasses = [
|
425
|
+
'datatable2__header',
|
426
|
+
stickyHeader && 'datatable2__header--sticky',
|
427
|
+
headerClassName
|
428
|
+
].filter(Boolean).join(' ')
|
429
|
+
|
430
|
+
return (
|
431
|
+
<thead className={headerClasses}>
|
432
|
+
<tr>
|
433
|
+
{showRowNumbers && (
|
434
|
+
<th className="datatable2__row-number-header">
|
435
|
+
<div>#</div>
|
436
|
+
</th>
|
437
|
+
)}
|
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
|
+
} : {}
|
449
|
+
|
450
|
+
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>
|
478
|
+
)
|
479
|
+
})}
|
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
|
+
}
|
495
|
+
</tr>
|
496
|
+
</thead>
|
497
|
+
)
|
498
|
+
}
|
499
|
+
|
500
|
+
/**
|
501
|
+
* Enhanced DataTable Body - maintains original structure
|
502
|
+
*/
|
503
|
+
const DataTable2Body = ({
|
504
|
+
columns,
|
505
|
+
rows,
|
506
|
+
onSelect,
|
507
|
+
onDrop,
|
508
|
+
editable,
|
509
|
+
expanded,
|
510
|
+
filterable,
|
511
|
+
onClearFilters,
|
512
|
+
selectedRows,
|
513
|
+
showRowNumbers,
|
514
|
+
onRowDoubleClick,
|
515
|
+
onRowContextMenu,
|
516
|
+
onCellClick,
|
517
|
+
onCellDoubleClick,
|
518
|
+
onCellEdit,
|
519
|
+
rowClassName,
|
520
|
+
cellClassName,
|
521
|
+
accessibility,
|
522
|
+
emptyMessage,
|
523
|
+
emptyIcon,
|
524
|
+
customEmptyState,
|
525
|
+
isLoading
|
526
|
+
}) => {
|
527
|
+
return (
|
528
|
+
<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}
|
536
|
+
row={row}
|
537
|
+
columns={columns}
|
538
|
+
onSelect={onSelect}
|
539
|
+
onDrop={onDrop}
|
540
|
+
editable={editable}
|
541
|
+
expanded={expanded}
|
542
|
+
selected={selectedRows.has(row.id)}
|
543
|
+
showRowNumbers={showRowNumbers}
|
544
|
+
onRowDoubleClick={onRowDoubleClick}
|
545
|
+
onRowContextMenu={onRowContextMenu}
|
546
|
+
onCellClick={onCellClick}
|
547
|
+
onCellDoubleClick={onCellDoubleClick}
|
548
|
+
onCellEdit={onCellEdit}
|
549
|
+
rowClassName={rowClassName}
|
550
|
+
cellClassName={cellClassName}
|
551
|
+
accessibility={accessibility}
|
552
|
+
/>
|
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
|
+
)}
|
567
|
+
</tbody>
|
568
|
+
)
|
569
|
+
}
|
570
|
+
|
571
|
+
/**
|
572
|
+
* Enhanced DataTableFiltersRow - maintains original structure
|
573
|
+
*/
|
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
|
+
}
|
610
|
+
|
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
|
633
|
+
|
634
|
+
const { className: rowClass } = row
|
635
|
+
const [isInfoOpen, toggleInfo] = useState(expanded)
|
636
|
+
const infoIcon = isInfoOpen ? 'expand_less' : 'expand_more'
|
637
|
+
|
638
|
+
let style = isInfoOpen ? "expanded" : ""
|
639
|
+
if (selected) style += " selected"
|
640
|
+
|
641
|
+
const finalRowClassName = [
|
642
|
+
style,
|
643
|
+
rowClass,
|
644
|
+
typeof rowClassName === 'function' ? rowClassName(row, index) : rowClassName
|
645
|
+
].filter(Boolean).join(' ')
|
646
|
+
|
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])
|
657
|
+
|
658
|
+
const handleRowDoubleClick = useCallback((ev) => {
|
659
|
+
if (onRowDoubleClick) onRowDoubleClick(row, ev)
|
660
|
+
}, [onRowDoubleClick, row])
|
661
|
+
|
662
|
+
const handleRowContextMenu = useCallback((ev) => {
|
663
|
+
if (onRowContextMenu) onRowContextMenu(row, ev)
|
664
|
+
}, [onRowContextMenu, row])
|
665
|
+
|
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}
|
685
|
+
column={column}
|
686
|
+
cell={row[column.id]}
|
687
|
+
editable={editable || column.editable}
|
688
|
+
onCellClick={onCellClick}
|
689
|
+
onCellDoubleClick={onCellDoubleClick}
|
690
|
+
onCellEdit={onCellEdit}
|
691
|
+
cellClassName={cellClassName}
|
692
|
+
accessibility={accessibility}
|
693
|
+
/>
|
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'
|
756
|
+
|
757
|
+
document.addEventListener('mousemove', handleMouseMove)
|
758
|
+
document.addEventListener('mouseup', handleMouseUp)
|
759
|
+
}
|
760
|
+
|
761
|
+
/**
|
762
|
+
* Enhanced DataTable Cell - maintains original structure and logic
|
763
|
+
*/
|
764
|
+
const DataTableCell2 = ({
|
765
|
+
index,
|
766
|
+
row,
|
767
|
+
column,
|
768
|
+
cell,
|
769
|
+
editable,
|
770
|
+
onCellClick,
|
771
|
+
onCellDoubleClick,
|
772
|
+
onCellEdit,
|
773
|
+
cellClassName,
|
774
|
+
accessibility
|
775
|
+
}) => {
|
776
|
+
const handleCellClick = useCallback((e) => {
|
777
|
+
if (onCellClick) onCellClick(row, column, cell, e)
|
778
|
+
}, [onCellClick, row, column, cell])
|
779
|
+
|
780
|
+
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
|
+
}
|
818
|
+
}
|
819
|
+
}
|
820
|
+
|
821
|
+
const finalCellClassName = [
|
822
|
+
column.id,
|
823
|
+
typeof cellClassName === 'function' ? cellClassName(row, column, cell) : cellClassName
|
824
|
+
].filter(Boolean).join(' ')
|
825
|
+
|
826
|
+
const cellProps = accessibility ? {
|
827
|
+
role: 'cell',
|
828
|
+
'aria-describedby': column.label ? `${column.id}-header` : undefined,
|
829
|
+
tabIndex: editable ? 0 : -1
|
830
|
+
} : {}
|
831
|
+
|
832
|
+
return column.type === TYPES.ENTITY ? (
|
833
|
+
<EntityCellViewer2 id={column.id} item={column.item} value={cell} />
|
834
|
+
) : (
|
835
|
+
<td
|
836
|
+
key={column.id}
|
837
|
+
className={finalCellClassName}
|
838
|
+
onClick={handleCellClick}
|
839
|
+
onDoubleClick={handleCellDoubleClick}
|
840
|
+
{...cellProps}
|
841
|
+
>
|
842
|
+
{render(column.type)}
|
843
|
+
</td>
|
844
|
+
)
|
845
|
+
}
|
846
|
+
|
847
|
+
// Import and re-export original cell viewers with enhanced versions
|
848
|
+
// These maintain 100% compatibility while adding new features
|
849
|
+
|
850
|
+
/**
|
851
|
+
* Enhanced Image Cell Viewer
|
852
|
+
*/
|
853
|
+
const ImageCellViewer2 = ({ id, value, uploadURL, onChange }) => {
|
854
|
+
function success(file, message) {
|
855
|
+
if (onChange) onChange(id, file, message)
|
856
|
+
}
|
857
|
+
|
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
|
+
}
|
874
|
+
|
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;
|
909
|
+
}
|
910
|
+
}
|
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
|
+
}
|
939
|
+
|
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
|
+
}
|
961
|
+
|
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
|
+
}
|
970
|
+
|
971
|
+
function onClick() {
|
972
|
+
if (action) action(id, value, format, options)
|
973
|
+
}
|
974
|
+
|
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>)
|
1008
|
+
}
|
1009
|
+
|
1010
|
+
/**
|
1011
|
+
* Enhanced String Cell Editor
|
1012
|
+
*/
|
1013
|
+
const StringCellEditor2 = ({ id, value = '', options, onChange }) => {
|
1014
|
+
function change(id, value) {
|
1015
|
+
if (onChange) onChange(id, value)
|
1016
|
+
}
|
1017
|
+
|
1018
|
+
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>
|
1025
|
+
)
|
1026
|
+
}
|
1027
|
+
|
1028
|
+
/**
|
1029
|
+
* Skeleton loading table
|
1030
|
+
*/
|
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>
|
1042
|
+
<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>
|
1048
|
+
</td>
|
1049
|
+
))}
|
1050
|
+
</tr>
|
1051
|
+
))}
|
1052
|
+
</tbody>
|
1053
|
+
</table>
|
1054
|
+
)
|
1055
|
+
|
1056
|
+
// PropTypes for DataTable2
|
1057
|
+
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 */
|
1093
|
+
onSort: PropTypes.func,
|
1094
|
+
/** Check all callback */
|
1095
|
+
onCheckAll: PropTypes.func,
|
1096
|
+
/** Enable editing */
|
1097
|
+
editable: PropTypes.bool,
|
1098
|
+
/** Outlined style */
|
1099
|
+
outlined: PropTypes.bool,
|
1100
|
+
/** Expand rows by default */
|
1101
|
+
expanded: PropTypes.bool,
|
1102
|
+
/** Additional CSS classes */
|
1103
|
+
className: PropTypes.string,
|
1104
|
+
/** Empty state message */
|
1105
|
+
emptyMessage: PropTypes.string,
|
1106
|
+
/** Empty state icon */
|
1107
|
+
emptyIcon: PropTypes.string,
|
1108
|
+
/** Enable multi-sort */
|
1109
|
+
multisort: PropTypes.bool,
|
1110
|
+
/** Enable filtering */
|
1111
|
+
filterable: PropTypes.bool,
|
1112
|
+
/** Clear filters callback */
|
1113
|
+
onClearFilters: PropTypes.func,
|
1114
|
+
|
1115
|
+
// New enhanced props (all optional for compatibility)
|
1116
|
+
/** Loading state */
|
1117
|
+
loading: PropTypes.bool,
|
1118
|
+
/** Skeleton placeholder */
|
1119
|
+
skeleton: PropTypes.bool,
|
1120
|
+
/** Striped rows */
|
1121
|
+
striped: PropTypes.bool,
|
1122
|
+
/** Hover effects */
|
1123
|
+
hover: PropTypes.bool,
|
1124
|
+
/** Compact layout */
|
1125
|
+
compact: PropTypes.bool,
|
1126
|
+
/** Bordered table */
|
1127
|
+
bordered: PropTypes.bool,
|
1128
|
+
/** Responsive design */
|
1129
|
+
responsive: PropTypes.bool,
|
1130
|
+
/** Sticky header */
|
1131
|
+
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
|
+
resizable: PropTypes.bool,
|
1142
|
+
/** Reorderable columns */
|
1143
|
+
reorderable: PropTypes.bool,
|
1144
|
+
/** Export functionality */
|
1145
|
+
exportable: PropTypes.bool,
|
1146
|
+
/** Search functionality */
|
1147
|
+
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 */
|
1159
|
+
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
|
+
rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
1182
|
+
/** Cell class name function */
|
1183
|
+
cellClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
1184
|
+
/** Header class name */
|
1185
|
+
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
|
1249
|
+
}
|
1250
|
+
|
1251
|
+
// Default export for easy migration
|
1252
|
+
export default DataTable2
|