@moontra/moonui-pro 2.17.4 → 2.17.5
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.mjs +1168 -274
- package/package.json +1 -1
- package/src/components/calendar-pro/index.tsx +129 -24
- package/src/components/memory-efficient-data/index.tsx +730 -66
- package/src/components/virtual-list/index.tsx +436 -37
- package/dist/index.d.ts +0 -2798
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import React, { useMemo, useCallback, useState, useEffect, useRef } from 'react'
|
|
4
|
+
import { motion, AnimatePresence } from 'framer-motion'
|
|
4
5
|
import { cn } from '../../lib/utils'
|
|
6
|
+
import { Search, Filter, Download, SortAsc, SortDesc, BarChart3, Cpu, Clock, Zap } from 'lucide-react'
|
|
5
7
|
|
|
6
|
-
// Memory konfigürasyonu için tip tanımları
|
|
8
|
+
// Memory konfigürasyonu için tip tanımları
|
|
7
9
|
interface MemoryConfig {
|
|
8
10
|
chunkSize?: number
|
|
9
11
|
maxMemoryItems?: number
|
|
@@ -14,6 +16,45 @@ interface MemoryConfig {
|
|
|
14
16
|
streamingMode?: boolean
|
|
15
17
|
enableAnalytics?: boolean
|
|
16
18
|
analyticsCallback?: (stats: MemoryStats) => void
|
|
19
|
+
// Yeni özellikler
|
|
20
|
+
variableHeight?: boolean
|
|
21
|
+
preloadStrategy?: 'none' | 'aggressive' | 'smart'
|
|
22
|
+
performanceTracking?: boolean
|
|
23
|
+
enableSearch?: boolean
|
|
24
|
+
enableSort?: boolean
|
|
25
|
+
enableFilter?: boolean
|
|
26
|
+
enableExport?: boolean
|
|
27
|
+
animations?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Performance metrikleri için tip tanımı
|
|
31
|
+
interface MemoryPerformanceMetrics {
|
|
32
|
+
fps: number
|
|
33
|
+
renderTime: number
|
|
34
|
+
memoryLeaks: number
|
|
35
|
+
scrollPerformance: number
|
|
36
|
+
lastUpdate: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Search/Sort/Filter için tip tanımları
|
|
40
|
+
interface TableFeatures<T> {
|
|
41
|
+
searchTerm?: string
|
|
42
|
+
sortConfig?: {
|
|
43
|
+
key: keyof T
|
|
44
|
+
direction: 'asc' | 'desc'
|
|
45
|
+
}
|
|
46
|
+
filters?: Array<{
|
|
47
|
+
key: keyof T
|
|
48
|
+
value: any
|
|
49
|
+
operator: 'equals' | 'contains' | 'greaterThan' | 'lessThan'
|
|
50
|
+
}>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Height measurement için tip tanımı
|
|
54
|
+
interface ItemHeightInfo {
|
|
55
|
+
index: number
|
|
56
|
+
height: number
|
|
57
|
+
offset: number
|
|
17
58
|
}
|
|
18
59
|
|
|
19
60
|
// Memory istatistikleri için tip tanımı
|
|
@@ -23,6 +64,7 @@ interface MemoryStats {
|
|
|
23
64
|
cacheHitRate: number
|
|
24
65
|
compressionRatio?: number
|
|
25
66
|
lastCleanup?: number
|
|
67
|
+
performance?: MemoryPerformanceMetrics
|
|
26
68
|
}
|
|
27
69
|
|
|
28
70
|
// Ana component props
|
|
@@ -33,6 +75,16 @@ export interface MemoryEfficientDataProps<T = any> {
|
|
|
33
75
|
height?: number
|
|
34
76
|
onMemoryUpdate?: (stats: MemoryStats) => void
|
|
35
77
|
className?: string
|
|
78
|
+
// Yeni özellikler
|
|
79
|
+
itemHeight?: number | ((item: T, index: number) => number)
|
|
80
|
+
onSearch?: (searchTerm: string) => T[]
|
|
81
|
+
onSort?: (data: T[], key: keyof T, direction: 'asc' | 'desc') => T[]
|
|
82
|
+
onFilter?: (data: T[], filters: TableFeatures<T>['filters']) => T[]
|
|
83
|
+
onExport?: (data: T[], format: 'csv' | 'json' | 'xlsx') => void
|
|
84
|
+
searchPlaceholder?: string
|
|
85
|
+
emptyStateMessage?: string
|
|
86
|
+
loadingComponent?: React.ReactNode
|
|
87
|
+
errorComponent?: React.ReactNode
|
|
36
88
|
}
|
|
37
89
|
|
|
38
90
|
// Cache node yapısı
|
|
@@ -67,16 +119,6 @@ class MemoryCache<T> {
|
|
|
67
119
|
})
|
|
68
120
|
}
|
|
69
121
|
|
|
70
|
-
get(key: string): T[] | null {
|
|
71
|
-
const node = this.cache.get(key)
|
|
72
|
-
if (!node) return null
|
|
73
|
-
|
|
74
|
-
// Access count'ı artır (LFU için)
|
|
75
|
-
node.accessCount++
|
|
76
|
-
node.timestamp = Date.now() // LRU için timestamp güncelle
|
|
77
|
-
|
|
78
|
-
return node.data
|
|
79
|
-
}
|
|
80
122
|
|
|
81
123
|
private evict(): void {
|
|
82
124
|
if (this.cache.size === 0) return
|
|
@@ -121,12 +163,181 @@ class MemoryCache<T> {
|
|
|
121
163
|
this.cache.clear()
|
|
122
164
|
}
|
|
123
165
|
|
|
124
|
-
getStats(): { size: number; hitRate: number } {
|
|
166
|
+
getStats(): { size: number; hitRate: number; memoryUsage: number } {
|
|
167
|
+
let totalMemory = 0
|
|
168
|
+
for (const [, node] of this.cache) {
|
|
169
|
+
totalMemory += JSON.stringify(node.data).length * 2 // Rough memory calculation
|
|
170
|
+
}
|
|
171
|
+
|
|
125
172
|
return {
|
|
126
173
|
size: this.cache.size,
|
|
127
|
-
hitRate:
|
|
174
|
+
hitRate: this.calculateHitRate(),
|
|
175
|
+
memoryUsage: totalMemory
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private hitCount = 0
|
|
180
|
+
private missCount = 0
|
|
181
|
+
|
|
182
|
+
private calculateHitRate(): number {
|
|
183
|
+
const total = this.hitCount + this.missCount
|
|
184
|
+
return total === 0 ? 0 : this.hitCount / total
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Hit/miss tracking için güncelle
|
|
188
|
+
get(key: string): T[] | null {
|
|
189
|
+
const node = this.cache.get(key)
|
|
190
|
+
if (!node) {
|
|
191
|
+
this.missCount++
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.hitCount++
|
|
196
|
+
node.accessCount++
|
|
197
|
+
node.timestamp = Date.now()
|
|
198
|
+
return node.data
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Performance Tracker sınıfı
|
|
203
|
+
class PerformanceTracker {
|
|
204
|
+
private frames: number[] = []
|
|
205
|
+
private renderTimes: number[] = []
|
|
206
|
+
private startTime = 0
|
|
207
|
+
private lastFrameTime = 0
|
|
208
|
+
|
|
209
|
+
startRender(): void {
|
|
210
|
+
this.startTime = performance.now()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
endRender(): void {
|
|
214
|
+
const renderTime = performance.now() - this.startTime
|
|
215
|
+
this.renderTimes.push(renderTime)
|
|
216
|
+
|
|
217
|
+
// Son 60 render time'ı tut
|
|
218
|
+
if (this.renderTimes.length > 60) {
|
|
219
|
+
this.renderTimes.shift()
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
recordFrame(): void {
|
|
224
|
+
const now = performance.now()
|
|
225
|
+
if (this.lastFrameTime > 0) {
|
|
226
|
+
const fps = 1000 / (now - this.lastFrameTime)
|
|
227
|
+
this.frames.push(fps)
|
|
228
|
+
|
|
229
|
+
// Son 60 frame'i tut
|
|
230
|
+
if (this.frames.length > 60) {
|
|
231
|
+
this.frames.shift()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
this.lastFrameTime = now
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
getMetrics(): MemoryPerformanceMetrics {
|
|
238
|
+
const avgFPS = this.frames.length > 0
|
|
239
|
+
? this.frames.reduce((a, b) => a + b, 0) / this.frames.length
|
|
240
|
+
: 0
|
|
241
|
+
|
|
242
|
+
const avgRenderTime = this.renderTimes.length > 0
|
|
243
|
+
? this.renderTimes.reduce((a, b) => a + b, 0) / this.renderTimes.length
|
|
244
|
+
: 0
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
fps: Math.round(avgFPS),
|
|
248
|
+
renderTime: Math.round(avgRenderTime * 100) / 100,
|
|
249
|
+
memoryLeaks: this.detectMemoryLeaks(),
|
|
250
|
+
scrollPerformance: this.calculateScrollPerformance(),
|
|
251
|
+
lastUpdate: Date.now()
|
|
128
252
|
}
|
|
129
253
|
}
|
|
254
|
+
|
|
255
|
+
private detectMemoryLeaks(): number {
|
|
256
|
+
// Basit memory leak detection
|
|
257
|
+
return this.renderTimes.filter(time => time > 16).length // 16ms = 60fps threshold
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private calculateScrollPerformance(): number {
|
|
261
|
+
// Scroll performance hesaplama (0-100 score)
|
|
262
|
+
const avgFPS = this.frames.length > 0
|
|
263
|
+
? this.frames.reduce((a, b) => a + b, 0) / this.frames.length
|
|
264
|
+
: 60
|
|
265
|
+
|
|
266
|
+
return Math.min(100, Math.max(0, (avgFPS / 60) * 100))
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Variable Height Manager sınıfı
|
|
271
|
+
class VariableHeightManager {
|
|
272
|
+
private heights = new Map<number, number>()
|
|
273
|
+
private defaultHeight: number
|
|
274
|
+
private totalHeight = 0
|
|
275
|
+
private measuredCount = 0
|
|
276
|
+
|
|
277
|
+
constructor(defaultHeight = 60) {
|
|
278
|
+
this.defaultHeight = defaultHeight
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
setItemHeight(index: number, height: number): void {
|
|
282
|
+
const oldHeight = this.heights.get(index) || this.defaultHeight
|
|
283
|
+
this.heights.set(index, height)
|
|
284
|
+
|
|
285
|
+
if (!this.heights.has(index)) {
|
|
286
|
+
this.measuredCount++
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.totalHeight += height - oldHeight
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
getItemHeight(index: number): number {
|
|
293
|
+
return this.heights.get(index) || this.defaultHeight
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
getOffsetForIndex(index: number): number {
|
|
297
|
+
let offset = 0
|
|
298
|
+
for (let i = 0; i < index; i++) {
|
|
299
|
+
offset += this.getItemHeight(i)
|
|
300
|
+
}
|
|
301
|
+
return offset
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
getTotalHeight(itemCount: number): number {
|
|
305
|
+
if (this.measuredCount === 0) {
|
|
306
|
+
return itemCount * this.defaultHeight
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const measuredHeight = Array.from(this.heights.values())
|
|
310
|
+
.reduce((sum, height) => sum + height, 0)
|
|
311
|
+
const averageHeight = measuredHeight / this.measuredCount
|
|
312
|
+
const unmeasuredCount = itemCount - this.measuredCount
|
|
313
|
+
|
|
314
|
+
return measuredHeight + (unmeasuredCount * averageHeight)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
getVisibleRange(scrollTop: number, containerHeight: number, itemCount: number): { start: number; end: number } {
|
|
318
|
+
let start = 0
|
|
319
|
+
let currentOffset = 0
|
|
320
|
+
|
|
321
|
+
// Scroll position'a kadar olan itemları say
|
|
322
|
+
while (start < itemCount && currentOffset < scrollTop) {
|
|
323
|
+
currentOffset += this.getItemHeight(start)
|
|
324
|
+
start++
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
start = Math.max(0, start - 1)
|
|
328
|
+
|
|
329
|
+
// Görünür alan boyunca devam et
|
|
330
|
+
let end = start
|
|
331
|
+
const targetOffset = scrollTop + containerHeight
|
|
332
|
+
currentOffset = this.getOffsetForIndex(start)
|
|
333
|
+
|
|
334
|
+
while (end < itemCount && currentOffset < targetOffset) {
|
|
335
|
+
currentOffset += this.getItemHeight(end)
|
|
336
|
+
end++
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return { start, end: Math.min(itemCount, end + 1) }
|
|
340
|
+
}
|
|
130
341
|
}
|
|
131
342
|
|
|
132
343
|
// Ana Memory Efficient Data Component
|
|
@@ -136,7 +347,17 @@ export function MemoryEfficientData<T = any>({
|
|
|
136
347
|
renderItem,
|
|
137
348
|
height = 400,
|
|
138
349
|
onMemoryUpdate,
|
|
139
|
-
className
|
|
350
|
+
className,
|
|
351
|
+
// Yeni props
|
|
352
|
+
itemHeight = 60,
|
|
353
|
+
onSearch,
|
|
354
|
+
onSort,
|
|
355
|
+
onFilter,
|
|
356
|
+
onExport,
|
|
357
|
+
searchPlaceholder = "Ara...",
|
|
358
|
+
emptyStateMessage = "Veri bulunamadı",
|
|
359
|
+
loadingComponent,
|
|
360
|
+
errorComponent
|
|
140
361
|
}: MemoryEfficientDataProps<T>) {
|
|
141
362
|
const {
|
|
142
363
|
chunkSize = 1000,
|
|
@@ -147,7 +368,16 @@ export function MemoryEfficientData<T = any>({
|
|
|
147
368
|
cleanupThreshold = 0.8,
|
|
148
369
|
streamingMode = false,
|
|
149
370
|
enableAnalytics = false,
|
|
150
|
-
analyticsCallback
|
|
371
|
+
analyticsCallback,
|
|
372
|
+
// Yeni konfigürasyonlar
|
|
373
|
+
variableHeight = false,
|
|
374
|
+
preloadStrategy = 'smart',
|
|
375
|
+
performanceTracking = false,
|
|
376
|
+
enableSearch = false,
|
|
377
|
+
enableSort = false,
|
|
378
|
+
enableFilter = false,
|
|
379
|
+
enableExport = false,
|
|
380
|
+
animations = true
|
|
151
381
|
} = config
|
|
152
382
|
|
|
153
383
|
// State yönetimi
|
|
@@ -157,48 +387,150 @@ export function MemoryEfficientData<T = any>({
|
|
|
157
387
|
itemCount: 0,
|
|
158
388
|
cacheHitRate: 0.95
|
|
159
389
|
})
|
|
390
|
+
const [tableFeatures, setTableFeatures] = useState<TableFeatures<T>>({
|
|
391
|
+
searchTerm: '',
|
|
392
|
+
sortConfig: undefined,
|
|
393
|
+
filters: []
|
|
394
|
+
})
|
|
395
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
396
|
+
const [error, setError] = useState<string | null>(null)
|
|
160
397
|
|
|
161
398
|
// Refs
|
|
162
399
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
163
400
|
const cacheRef = useRef(new MemoryCache<T>(Math.floor(maxMemoryItems / chunkSize), cacheStrategy))
|
|
164
401
|
const scrollTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
|
|
402
|
+
const performanceTrackerRef = useRef(new PerformanceTracker())
|
|
403
|
+
const heightManagerRef = useRef(new VariableHeightManager(typeof itemHeight === 'number' ? itemHeight : 60))
|
|
404
|
+
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map())
|
|
405
|
+
|
|
406
|
+
// İşlenmiş data'yı hesapla (search, sort, filter)
|
|
407
|
+
const processedData = useMemo(() => {
|
|
408
|
+
let result = [...data]
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
setIsLoading(true)
|
|
412
|
+
|
|
413
|
+
// Search functionality
|
|
414
|
+
if (enableSearch && tableFeatures.searchTerm && onSearch) {
|
|
415
|
+
result = onSearch(tableFeatures.searchTerm)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Filter functionality
|
|
419
|
+
if (enableFilter && tableFeatures.filters && tableFeatures.filters.length > 0 && onFilter) {
|
|
420
|
+
result = onFilter(result, tableFeatures.filters)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Sort functionality
|
|
424
|
+
if (enableSort && tableFeatures.sortConfig && onSort) {
|
|
425
|
+
result = onSort(result, tableFeatures.sortConfig.key, tableFeatures.sortConfig.direction)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
setError(null)
|
|
429
|
+
return result
|
|
430
|
+
} catch (err) {
|
|
431
|
+
setError(err instanceof Error ? err.message : 'Veri işlenirken hata oluştu')
|
|
432
|
+
return data
|
|
433
|
+
} finally {
|
|
434
|
+
setIsLoading(false)
|
|
435
|
+
}
|
|
436
|
+
}, [data, tableFeatures, enableSearch, enableFilter, enableSort, onSearch, onFilter, onSort])
|
|
165
437
|
|
|
166
438
|
// Visible data'yı memoize et
|
|
167
439
|
const visibleData = useMemo(() => {
|
|
440
|
+
if (performanceTracking) {
|
|
441
|
+
performanceTrackerRef.current.startRender()
|
|
442
|
+
}
|
|
443
|
+
|
|
168
444
|
const start = visibleRange.start
|
|
169
|
-
const end = Math.min(visibleRange.end,
|
|
445
|
+
const end = Math.min(visibleRange.end, processedData.length)
|
|
170
446
|
|
|
171
447
|
// Cache'den kontrol et
|
|
172
|
-
const cacheKey = `${start}-${end}`
|
|
448
|
+
const cacheKey = `${start}-${end}-${JSON.stringify(tableFeatures)}`
|
|
173
449
|
const cachedData = cacheRef.current.get(cacheKey)
|
|
174
450
|
|
|
175
451
|
if (cachedData && cachedData.length === (end - start)) {
|
|
452
|
+
if (performanceTracking) {
|
|
453
|
+
performanceTrackerRef.current.endRender()
|
|
454
|
+
}
|
|
176
455
|
return cachedData
|
|
177
456
|
}
|
|
178
457
|
|
|
179
458
|
// Yeni data slice'ını oluştur
|
|
180
|
-
const newData =
|
|
459
|
+
const newData = processedData.slice(start, end)
|
|
181
460
|
|
|
182
461
|
// Cache'e kaydet
|
|
183
462
|
cacheRef.current.set(cacheKey, newData, compression)
|
|
184
463
|
|
|
464
|
+
if (performanceTracking) {
|
|
465
|
+
performanceTrackerRef.current.endRender()
|
|
466
|
+
}
|
|
467
|
+
|
|
185
468
|
return newData
|
|
186
|
-
}, [
|
|
469
|
+
}, [processedData, visibleRange, compression, tableFeatures, performanceTracking])
|
|
470
|
+
|
|
471
|
+
// Item height measurement
|
|
472
|
+
const measureItemHeight = useCallback((index: number, element: HTMLDivElement) => {
|
|
473
|
+
if (!variableHeight) return
|
|
474
|
+
|
|
475
|
+
const height = element.getBoundingClientRect().height
|
|
476
|
+
heightManagerRef.current.setItemHeight(index, height)
|
|
477
|
+
|
|
478
|
+
// Force re-render if height changed significantly
|
|
479
|
+
const oldHeight = heightManagerRef.current.getItemHeight(index)
|
|
480
|
+
if (Math.abs(height - oldHeight) > 5) {
|
|
481
|
+
// Recalculate visible range
|
|
482
|
+
const container = containerRef.current
|
|
483
|
+
if (container) {
|
|
484
|
+
handleScroll({ currentTarget: container } as React.UIEvent<HTMLDivElement>)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}, [variableHeight])
|
|
187
488
|
|
|
188
489
|
// Scroll handler
|
|
189
490
|
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
|
491
|
+
if (performanceTracking) {
|
|
492
|
+
performanceTrackerRef.current.recordFrame()
|
|
493
|
+
}
|
|
494
|
+
|
|
190
495
|
const scrollTop = e.currentTarget.scrollTop
|
|
191
496
|
const containerHeight = e.currentTarget.clientHeight
|
|
192
497
|
|
|
193
|
-
|
|
194
|
-
const estimatedItemHeight = 60
|
|
195
|
-
const startIndex = Math.floor(scrollTop / estimatedItemHeight)
|
|
196
|
-
const visibleCount = Math.ceil(containerHeight / estimatedItemHeight)
|
|
498
|
+
let newStart: number, newEnd: number
|
|
197
499
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
500
|
+
if (variableHeight) {
|
|
501
|
+
// Variable height kullan
|
|
502
|
+
const range = heightManagerRef.current.getVisibleRange(scrollTop, containerHeight, processedData.length)
|
|
503
|
+
newStart = range.start
|
|
504
|
+
newEnd = range.end
|
|
505
|
+
} else {
|
|
506
|
+
// Fixed height kullan
|
|
507
|
+
const itemHeightValue = typeof itemHeight === 'number' ? itemHeight : 60
|
|
508
|
+
const startIndex = Math.floor(scrollTop / itemHeightValue)
|
|
509
|
+
const visibleCount = Math.ceil(containerHeight / itemHeightValue)
|
|
510
|
+
|
|
511
|
+
// Buffer ekle
|
|
512
|
+
const bufferSize = Math.floor(visibleCount * 0.5)
|
|
513
|
+
newStart = Math.max(0, startIndex - bufferSize)
|
|
514
|
+
newEnd = Math.min(processedData.length, startIndex + visibleCount + bufferSize)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Preloading strategy
|
|
518
|
+
if (preloadStrategy === 'aggressive') {
|
|
519
|
+
const extraBuffer = Math.floor((newEnd - newStart) * 0.5)
|
|
520
|
+
newStart = Math.max(0, newStart - extraBuffer)
|
|
521
|
+
newEnd = Math.min(processedData.length, newEnd + extraBuffer)
|
|
522
|
+
} else if (preloadStrategy === 'smart') {
|
|
523
|
+
// Scroll direction'a göre preload
|
|
524
|
+
const currentScroll = scrollTop
|
|
525
|
+
const lastScroll = scrollTimeoutRef.current ? parseInt(scrollTimeoutRef.current as any) : 0
|
|
526
|
+
const isScrollingDown = currentScroll > lastScroll
|
|
527
|
+
|
|
528
|
+
if (isScrollingDown) {
|
|
529
|
+
newEnd = Math.min(processedData.length, newEnd + Math.floor((newEnd - newStart) * 0.2))
|
|
530
|
+
} else {
|
|
531
|
+
newStart = Math.max(0, newStart - Math.floor((newEnd - newStart) * 0.2))
|
|
532
|
+
}
|
|
533
|
+
}
|
|
202
534
|
|
|
203
535
|
// Throttle scroll updates
|
|
204
536
|
if (scrollTimeoutRef.current) {
|
|
@@ -208,17 +540,48 @@ export function MemoryEfficientData<T = any>({
|
|
|
208
540
|
scrollTimeoutRef.current = setTimeout(() => {
|
|
209
541
|
setVisibleRange({ start: newStart, end: newEnd })
|
|
210
542
|
}, 16) // ~60fps
|
|
211
|
-
}, [
|
|
543
|
+
}, [processedData.length, variableHeight, itemHeight, preloadStrategy, performanceTracking])
|
|
544
|
+
|
|
545
|
+
// Search handler
|
|
546
|
+
const handleSearch = useCallback((searchTerm: string) => {
|
|
547
|
+
setTableFeatures(prev => ({ ...prev, searchTerm }))
|
|
548
|
+
}, [])
|
|
549
|
+
|
|
550
|
+
// Sort handler
|
|
551
|
+
const handleSort = useCallback((key: keyof T) => {
|
|
552
|
+
setTableFeatures(prev => {
|
|
553
|
+
const currentDirection = prev.sortConfig?.key === key && prev.sortConfig?.direction === 'asc' ? 'desc' : 'asc'
|
|
554
|
+
return {
|
|
555
|
+
...prev,
|
|
556
|
+
sortConfig: { key, direction: currentDirection }
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
}, [])
|
|
560
|
+
|
|
561
|
+
// Filter handler
|
|
562
|
+
const handleFilter = useCallback((filters: TableFeatures<T>['filters']) => {
|
|
563
|
+
setTableFeatures(prev => ({ ...prev, filters }))
|
|
564
|
+
}, [])
|
|
565
|
+
|
|
566
|
+
// Export handler
|
|
567
|
+
const handleExport = useCallback((format: 'csv' | 'json' | 'xlsx') => {
|
|
568
|
+
if (onExport) {
|
|
569
|
+
onExport(processedData, format)
|
|
570
|
+
}
|
|
571
|
+
}, [processedData, onExport])
|
|
212
572
|
|
|
213
573
|
// Memory statistics'i güncelle
|
|
214
574
|
useEffect(() => {
|
|
215
575
|
const cacheStats = cacheRef.current.getStats()
|
|
576
|
+
const performanceMetrics = performanceTracking ? performanceTrackerRef.current.getMetrics() : undefined
|
|
577
|
+
|
|
216
578
|
const newStats: MemoryStats = {
|
|
217
|
-
memoryUsage:
|
|
579
|
+
memoryUsage: cacheStats.memoryUsage,
|
|
218
580
|
itemCount: visibleData.length,
|
|
219
581
|
cacheHitRate: cacheStats.hitRate,
|
|
220
582
|
compressionRatio: compression ? 0.7 : 1.0,
|
|
221
|
-
lastCleanup: Date.now()
|
|
583
|
+
lastCleanup: Date.now(),
|
|
584
|
+
performance: performanceMetrics
|
|
222
585
|
}
|
|
223
586
|
|
|
224
587
|
setMemoryStats(newStats)
|
|
@@ -230,7 +593,7 @@ export function MemoryEfficientData<T = any>({
|
|
|
230
593
|
if (onMemoryUpdate) {
|
|
231
594
|
onMemoryUpdate(newStats)
|
|
232
595
|
}
|
|
233
|
-
}, [visibleData, enableAnalytics, analyticsCallback, onMemoryUpdate, compression])
|
|
596
|
+
}, [visibleData, enableAnalytics, analyticsCallback, onMemoryUpdate, compression, performanceTracking])
|
|
234
597
|
|
|
235
598
|
// Auto cleanup effect
|
|
236
599
|
useEffect(() => {
|
|
@@ -247,36 +610,330 @@ export function MemoryEfficientData<T = any>({
|
|
|
247
610
|
return () => clearInterval(cleanupInterval)
|
|
248
611
|
}, [autoCleanup, cleanupThreshold, maxMemoryItems, visibleData.length])
|
|
249
612
|
|
|
613
|
+
// Loading ve Error states için render
|
|
614
|
+
if (error && errorComponent) {
|
|
615
|
+
return errorComponent
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (isLoading && loadingComponent) {
|
|
619
|
+
return loadingComponent
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Total height hesapla
|
|
623
|
+
const totalHeight = variableHeight
|
|
624
|
+
? heightManagerRef.current.getTotalHeight(processedData.length)
|
|
625
|
+
: processedData.length * (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
626
|
+
|
|
627
|
+
// Offset hesapla
|
|
628
|
+
const offsetY = variableHeight
|
|
629
|
+
? heightManagerRef.current.getOffsetForIndex(visibleRange.start)
|
|
630
|
+
: visibleRange.start * (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
631
|
+
|
|
250
632
|
return (
|
|
251
|
-
<div
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
633
|
+
<div className={cn("flex flex-col", className)}>
|
|
634
|
+
{/* Control Bar */}
|
|
635
|
+
{(enableSearch || enableSort || enableFilter || enableExport) && (
|
|
636
|
+
<motion.div
|
|
637
|
+
className="flex items-center gap-2 p-3 border-b bg-muted/5"
|
|
638
|
+
initial={animations ? { opacity: 0, y: -10 } : {}}
|
|
639
|
+
animate={animations ? { opacity: 1, y: 0 } : {}}
|
|
640
|
+
transition={{ duration: 0.2 }}
|
|
641
|
+
>
|
|
642
|
+
{enableSearch && (
|
|
643
|
+
<div className="relative flex-1 max-w-sm">
|
|
644
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
645
|
+
<input
|
|
646
|
+
type="text"
|
|
647
|
+
placeholder={searchPlaceholder}
|
|
648
|
+
value={tableFeatures.searchTerm}
|
|
649
|
+
onChange={(e) => handleSearch(e.target.value)}
|
|
650
|
+
className="w-full pl-10 pr-4 py-2 border rounded-md bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
|
651
|
+
/>
|
|
652
|
+
</div>
|
|
653
|
+
)}
|
|
654
|
+
|
|
655
|
+
<div className="flex items-center gap-1">
|
|
656
|
+
{enableFilter && (
|
|
657
|
+
<button
|
|
658
|
+
onClick={() => {/* Filter modal açılacak */}}
|
|
659
|
+
className="p-2 hover:bg-muted rounded-md transition-colors"
|
|
660
|
+
title="Filtrele"
|
|
661
|
+
>
|
|
662
|
+
<Filter className="h-4 w-4" />
|
|
663
|
+
</button>
|
|
664
|
+
)}
|
|
665
|
+
|
|
666
|
+
{enableExport && (
|
|
667
|
+
<div className="relative">
|
|
668
|
+
<button
|
|
669
|
+
onClick={() => handleExport('csv')}
|
|
670
|
+
className="p-2 hover:bg-muted rounded-md transition-colors"
|
|
671
|
+
title="Dışa Aktar"
|
|
672
|
+
>
|
|
673
|
+
<Download className="h-4 w-4" />
|
|
674
|
+
</button>
|
|
675
|
+
</div>
|
|
676
|
+
)}
|
|
677
|
+
</div>
|
|
678
|
+
</motion.div>
|
|
679
|
+
)}
|
|
680
|
+
|
|
681
|
+
{/* Data Container */}
|
|
682
|
+
<div
|
|
683
|
+
ref={containerRef}
|
|
684
|
+
className="overflow-auto border rounded-lg bg-background"
|
|
685
|
+
style={{ height: typeof height === 'number' ? height - (enableSearch || enableSort || enableFilter || enableExport ? 60 : 0) : height }}
|
|
686
|
+
onScroll={handleScroll}
|
|
687
|
+
>
|
|
688
|
+
{processedData.length === 0 ? (
|
|
689
|
+
<motion.div
|
|
690
|
+
className="flex items-center justify-center h-full text-muted-foreground"
|
|
691
|
+
initial={animations ? { opacity: 0 } : {}}
|
|
692
|
+
animate={animations ? { opacity: 1 } : {}}
|
|
693
|
+
>
|
|
694
|
+
{emptyStateMessage}
|
|
695
|
+
</motion.div>
|
|
696
|
+
) : (
|
|
697
|
+
<div style={{ height: totalHeight, position: 'relative' }}>
|
|
698
|
+
<motion.div
|
|
699
|
+
style={{
|
|
700
|
+
transform: `translateY(${offsetY}px)`,
|
|
701
|
+
position: 'absolute',
|
|
702
|
+
width: '100%'
|
|
703
|
+
}}
|
|
704
|
+
animate={animations ? { y: offsetY } : {}}
|
|
705
|
+
transition={animations ? { duration: 0.1, ease: "linear" } : {}}
|
|
706
|
+
>
|
|
707
|
+
<AnimatePresence mode="popLayout">
|
|
708
|
+
{visibleData.map((item, index) => {
|
|
709
|
+
const globalIndex = visibleRange.start + index
|
|
710
|
+
const itemHeightValue = typeof itemHeight === 'function'
|
|
711
|
+
? itemHeight(item, globalIndex)
|
|
712
|
+
: (typeof itemHeight === 'number' ? itemHeight : 60)
|
|
713
|
+
|
|
714
|
+
return (
|
|
715
|
+
<motion.div
|
|
716
|
+
key={globalIndex}
|
|
717
|
+
ref={(el) => {
|
|
718
|
+
if (el && variableHeight) {
|
|
719
|
+
itemRefs.current.set(globalIndex, el)
|
|
720
|
+
measureItemHeight(globalIndex, el)
|
|
721
|
+
}
|
|
722
|
+
}}
|
|
723
|
+
style={variableHeight ? {} : { height: itemHeightValue }}
|
|
724
|
+
initial={animations ? { opacity: 0, y: 20 } : {}}
|
|
725
|
+
animate={animations ? { opacity: 1, y: 0 } : {}}
|
|
726
|
+
exit={animations ? { opacity: 0, y: -20 } : {}}
|
|
727
|
+
transition={animations ? { duration: 0.2 } : {}}
|
|
728
|
+
layout={animations}
|
|
729
|
+
>
|
|
730
|
+
{renderItem(item, globalIndex)}
|
|
731
|
+
</motion.div>
|
|
732
|
+
)
|
|
733
|
+
})}
|
|
734
|
+
</AnimatePresence>
|
|
735
|
+
</motion.div>
|
|
736
|
+
</div>
|
|
737
|
+
)}
|
|
738
|
+
</div>
|
|
739
|
+
|
|
740
|
+
{/* Performance Monitor */}
|
|
741
|
+
{performanceTracking && memoryStats.performance && (
|
|
742
|
+
<div className="p-2 border-t bg-muted/5 text-xs text-muted-foreground flex items-center gap-4">
|
|
743
|
+
<div className="flex items-center gap-1">
|
|
744
|
+
<Zap className="h-3 w-3" />
|
|
745
|
+
<span>FPS: {memoryStats.performance.fps}</span>
|
|
746
|
+
</div>
|
|
747
|
+
<div className="flex items-center gap-1">
|
|
748
|
+
<Clock className="h-3 w-3" />
|
|
749
|
+
<span>Render: {memoryStats.performance.renderTime}ms</span>
|
|
750
|
+
</div>
|
|
751
|
+
<div className="flex items-center gap-1">
|
|
752
|
+
<Cpu className="h-3 w-3" />
|
|
753
|
+
<span>Scroll: {memoryStats.performance.scrollPerformance}%</span>
|
|
754
|
+
</div>
|
|
755
|
+
<div className="flex items-center gap-1">
|
|
756
|
+
<BarChart3 className="h-3 w-3" />
|
|
757
|
+
<span>Cache: {Math.round(memoryStats.cacheHitRate * 100)}%</span>
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
256
760
|
)}
|
|
257
|
-
|
|
258
|
-
|
|
761
|
+
</div>
|
|
762
|
+
)
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Real-time Performance Monitor Component
|
|
766
|
+
interface RealTimePerformanceMonitorProps {
|
|
767
|
+
memoryStats?: MemoryStats
|
|
768
|
+
className?: string
|
|
769
|
+
showChart?: boolean
|
|
770
|
+
updateInterval?: number
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export function RealTimePerformanceMonitor({
|
|
774
|
+
memoryStats,
|
|
775
|
+
className,
|
|
776
|
+
showChart = false,
|
|
777
|
+
updateInterval = 1000
|
|
778
|
+
}: RealTimePerformanceMonitorProps) {
|
|
779
|
+
const [history, setHistory] = useState<MemoryStats[]>([])
|
|
780
|
+
const [isActive, setIsActive] = useState(true)
|
|
781
|
+
|
|
782
|
+
useEffect(() => {
|
|
783
|
+
if (!memoryStats || !isActive) return
|
|
784
|
+
|
|
785
|
+
setHistory(prev => {
|
|
786
|
+
const newHistory = [...prev, memoryStats]
|
|
787
|
+
// Son 60 entry'yi tut (1 dakika)
|
|
788
|
+
return newHistory.length > 60 ? newHistory.slice(-60) : newHistory
|
|
789
|
+
})
|
|
790
|
+
}, [memoryStats, isActive])
|
|
791
|
+
|
|
792
|
+
const formatBytes = (bytes: number) => {
|
|
793
|
+
if (bytes === 0) return '0 Bytes'
|
|
794
|
+
const k = 1024
|
|
795
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
796
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
797
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const getPerformanceColor = (value: number, threshold: { good: number; warning: number }) => {
|
|
801
|
+
if (value >= threshold.good) return 'text-green-500'
|
|
802
|
+
if (value >= threshold.warning) return 'text-yellow-500'
|
|
803
|
+
return 'text-red-500'
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (!memoryStats) {
|
|
807
|
+
return (
|
|
808
|
+
<div className={cn("p-4 border rounded-lg bg-muted/5", className)}>
|
|
809
|
+
<div className="text-center text-muted-foreground">
|
|
810
|
+
Performance monitoring devre dışı
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return (
|
|
817
|
+
<motion.div
|
|
818
|
+
className={cn("p-4 border rounded-lg bg-background space-y-4", className)}
|
|
819
|
+
initial={{ opacity: 0, y: 20 }}
|
|
820
|
+
animate={{ opacity: 1, y: 0 }}
|
|
821
|
+
transition={{ duration: 0.3 }}
|
|
259
822
|
>
|
|
260
|
-
<div
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
823
|
+
<div className="flex items-center justify-between">
|
|
824
|
+
<h3 className="font-semibold flex items-center gap-2">
|
|
825
|
+
<BarChart3 className="h-4 w-4" />
|
|
826
|
+
Gerçek Zamanlı Performans
|
|
827
|
+
</h3>
|
|
828
|
+
<button
|
|
829
|
+
onClick={() => setIsActive(!isActive)}
|
|
830
|
+
className={cn(
|
|
831
|
+
"px-3 py-1 text-xs rounded-md transition-colors",
|
|
832
|
+
isActive
|
|
833
|
+
? "bg-green-500/10 text-green-500 hover:bg-green-500/20"
|
|
834
|
+
: "bg-gray-500/10 text-gray-500 hover:bg-gray-500/20"
|
|
835
|
+
)}
|
|
267
836
|
>
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
837
|
+
{isActive ? 'Aktif' : 'Durduruldu'}
|
|
838
|
+
</button>
|
|
839
|
+
</div>
|
|
840
|
+
|
|
841
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
842
|
+
{/* FPS */}
|
|
843
|
+
<div className="space-y-1">
|
|
844
|
+
<div className="text-xs text-muted-foreground">FPS</div>
|
|
845
|
+
<div className={cn(
|
|
846
|
+
"text-lg font-mono font-bold",
|
|
847
|
+
memoryStats.performance ? getPerformanceColor(memoryStats.performance.fps, { good: 55, warning: 30 }) : ''
|
|
848
|
+
)}>
|
|
849
|
+
{memoryStats.performance?.fps || 0}
|
|
850
|
+
</div>
|
|
851
|
+
</div>
|
|
852
|
+
|
|
853
|
+
{/* Render Time */}
|
|
854
|
+
<div className="space-y-1">
|
|
855
|
+
<div className="text-xs text-muted-foreground">Render Süresi</div>
|
|
856
|
+
<div className={cn(
|
|
857
|
+
"text-lg font-mono font-bold",
|
|
858
|
+
memoryStats.performance ? getPerformanceColor(100 - memoryStats.performance.renderTime, { good: 84, warning: 70 }) : ''
|
|
859
|
+
)}>
|
|
860
|
+
{memoryStats.performance?.renderTime.toFixed(2) || 0}ms
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
|
|
864
|
+
{/* Memory Usage */}
|
|
865
|
+
<div className="space-y-1">
|
|
866
|
+
<div className="text-xs text-muted-foreground">Bellek Kullanımı</div>
|
|
867
|
+
<div className="text-lg font-mono font-bold">
|
|
868
|
+
{formatBytes(memoryStats.memoryUsage)}
|
|
869
|
+
</div>
|
|
870
|
+
</div>
|
|
871
|
+
|
|
872
|
+
{/* Cache Hit Rate */}
|
|
873
|
+
<div className="space-y-1">
|
|
874
|
+
<div className="text-xs text-muted-foreground">Cache Hit Rate</div>
|
|
875
|
+
<div className={cn(
|
|
876
|
+
"text-lg font-mono font-bold",
|
|
877
|
+
getPerformanceColor(memoryStats.cacheHitRate * 100, { good: 85, warning: 70 })
|
|
878
|
+
)}>
|
|
879
|
+
{Math.round(memoryStats.cacheHitRate * 100)}%
|
|
880
|
+
</div>
|
|
273
881
|
</div>
|
|
274
882
|
</div>
|
|
275
|
-
|
|
883
|
+
|
|
884
|
+
{/* Memory Leaks & Scroll Performance */}
|
|
885
|
+
{memoryStats.performance && (
|
|
886
|
+
<div className="grid grid-cols-2 gap-4 pt-2 border-t">
|
|
887
|
+
<div className="space-y-1">
|
|
888
|
+
<div className="text-xs text-muted-foreground">Memory Leaks</div>
|
|
889
|
+
<div className={cn(
|
|
890
|
+
"text-sm font-mono",
|
|
891
|
+
memoryStats.performance.memoryLeaks > 5 ? 'text-red-500' : 'text-green-500'
|
|
892
|
+
)}>
|
|
893
|
+
{memoryStats.performance.memoryLeaks} detection
|
|
894
|
+
</div>
|
|
895
|
+
</div>
|
|
896
|
+
<div className="space-y-1">
|
|
897
|
+
<div className="text-xs text-muted-foreground">Scroll Performance</div>
|
|
898
|
+
<div className={cn(
|
|
899
|
+
"text-sm font-mono",
|
|
900
|
+
getPerformanceColor(memoryStats.performance.scrollPerformance, { good: 85, warning: 70 })
|
|
901
|
+
)}>
|
|
902
|
+
{memoryStats.performance.scrollPerformance}%
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
</div>
|
|
906
|
+
)}
|
|
907
|
+
|
|
908
|
+
{/* Mini Chart */}
|
|
909
|
+
{showChart && history.length > 1 && (
|
|
910
|
+
<div className="pt-2 border-t">
|
|
911
|
+
<div className="text-xs text-muted-foreground mb-2">FPS Geçmişi (Son 60 güncelleme)</div>
|
|
912
|
+
<div className="h-16 flex items-end gap-1">
|
|
913
|
+
{history.slice(-30).map((stat, index) => {
|
|
914
|
+
const fps = stat.performance?.fps || 0
|
|
915
|
+
const height = Math.max(4, (fps / 60) * 100)
|
|
916
|
+
const color = fps >= 55 ? 'bg-green-500' : fps >= 30 ? 'bg-yellow-500' : 'bg-red-500'
|
|
917
|
+
|
|
918
|
+
return (
|
|
919
|
+
<motion.div
|
|
920
|
+
key={index}
|
|
921
|
+
className={cn("w-1 rounded-t-sm", color)}
|
|
922
|
+
style={{ height: `${height}%` }}
|
|
923
|
+
initial={{ height: 0 }}
|
|
924
|
+
animate={{ height: `${height}%` }}
|
|
925
|
+
transition={{ duration: 0.2 }}
|
|
926
|
+
/>
|
|
927
|
+
)
|
|
928
|
+
})}
|
|
929
|
+
</div>
|
|
930
|
+
</div>
|
|
931
|
+
)}
|
|
932
|
+
</motion.div>
|
|
276
933
|
)
|
|
277
934
|
}
|
|
278
935
|
|
|
279
|
-
// Memory Analytics Component
|
|
936
|
+
// Memory Analytics Component (Backward compatibility)
|
|
280
937
|
interface MemoryAnalyticsProps {
|
|
281
938
|
onStatsUpdate?: (stats: MemoryStats) => void
|
|
282
939
|
showRealTime?: boolean
|
|
@@ -294,16 +951,23 @@ export function MemoryAnalytics({
|
|
|
294
951
|
cacheHitRate: 0.95
|
|
295
952
|
})
|
|
296
953
|
|
|
297
|
-
// Mock real-time updates
|
|
954
|
+
// Mock real-time updates for backward compatibility
|
|
298
955
|
useEffect(() => {
|
|
299
956
|
if (!showRealTime) return
|
|
300
957
|
|
|
301
958
|
const interval = setInterval(() => {
|
|
302
959
|
const newStats: MemoryStats = {
|
|
303
|
-
memoryUsage: Math.random() * 50 * 1024 * 1024,
|
|
960
|
+
memoryUsage: Math.random() * 50 * 1024 * 1024,
|
|
304
961
|
itemCount: Math.floor(Math.random() * 50000),
|
|
305
|
-
cacheHitRate: 0.9 + Math.random() * 0.1,
|
|
306
|
-
lastCleanup: Date.now()
|
|
962
|
+
cacheHitRate: 0.9 + Math.random() * 0.1,
|
|
963
|
+
lastCleanup: Date.now(),
|
|
964
|
+
performance: {
|
|
965
|
+
fps: Math.floor(30 + Math.random() * 30),
|
|
966
|
+
renderTime: Math.random() * 16,
|
|
967
|
+
memoryLeaks: Math.floor(Math.random() * 3),
|
|
968
|
+
scrollPerformance: 70 + Math.random() * 30,
|
|
969
|
+
lastUpdate: Date.now()
|
|
970
|
+
} as MemoryPerformanceMetrics
|
|
307
971
|
}
|
|
308
972
|
|
|
309
973
|
setStats(newStats)
|
|
@@ -315,15 +979,7 @@ export function MemoryAnalytics({
|
|
|
315
979
|
return () => clearInterval(interval)
|
|
316
980
|
}, [showRealTime, onStatsUpdate])
|
|
317
981
|
|
|
318
|
-
|
|
319
|
-
return null
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return (
|
|
323
|
-
<div className={cn("text-xs text-muted-foreground", className)}>
|
|
324
|
-
Memory Analytics Active - Updates every second
|
|
325
|
-
</div>
|
|
326
|
-
)
|
|
982
|
+
return <RealTimePerformanceMonitor memoryStats={stats} className={className} showChart={showRealTime} />
|
|
327
983
|
}
|
|
328
984
|
|
|
329
985
|
// Utility hook for streaming data
|
|
@@ -349,4 +1005,12 @@ export function useStreamingData<T>(
|
|
|
349
1005
|
}
|
|
350
1006
|
|
|
351
1007
|
// Export types
|
|
352
|
-
export type {
|
|
1008
|
+
export type {
|
|
1009
|
+
MemoryConfig,
|
|
1010
|
+
MemoryStats,
|
|
1011
|
+
MemoryAnalyticsProps,
|
|
1012
|
+
MemoryPerformanceMetrics,
|
|
1013
|
+
TableFeatures,
|
|
1014
|
+
ItemHeightInfo,
|
|
1015
|
+
RealTimePerformanceMonitorProps
|
|
1016
|
+
}
|