@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.
@@ -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: 0.95 // Basit bir mock değer
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, data.length)
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 = data.slice(start, end)
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
- }, [data, visibleRange, compression])
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
- // Tahmini item yüksekliği (temel implementasyon)
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
- // Buffer ekle
199
- const bufferSize = Math.floor(visibleCount * 0.5)
200
- const newStart = Math.max(0, startIndex - bufferSize)
201
- const newEnd = Math.min(data.length, startIndex + visibleCount + bufferSize)
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
- }, [data.length])
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: visibleData.length * 1024, // Rough estimation
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
- ref={containerRef}
253
- className={cn(
254
- "overflow-auto border rounded-lg bg-background",
255
- className
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
- style={{ height }}
258
- onScroll={handleScroll}
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 style={{ height: data.length * 60, position: 'relative' }}>
261
- <div
262
- style={{
263
- transform: `translateY(${visibleRange.start * 60}px)`,
264
- position: 'absolute',
265
- width: '100%'
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
- {visibleData.map((item, index) => (
269
- <React.Fragment key={visibleRange.start + index}>
270
- {renderItem(item, visibleRange.start + index)}
271
- </React.Fragment>
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
- </div>
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, // 0-50MB
960
+ memoryUsage: Math.random() * 50 * 1024 * 1024,
304
961
  itemCount: Math.floor(Math.random() * 50000),
305
- cacheHitRate: 0.9 + Math.random() * 0.1, // 90-100%
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
- if (!showRealTime) {
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 { MemoryConfig, MemoryStats, MemoryAnalyticsProps }
1008
+ export type {
1009
+ MemoryConfig,
1010
+ MemoryStats,
1011
+ MemoryAnalyticsProps,
1012
+ MemoryPerformanceMetrics,
1013
+ TableFeatures,
1014
+ ItemHeightInfo,
1015
+ RealTimePerformanceMonitorProps
1016
+ }