import { Task } from '@/types/task-management.types'; import { dayjs } from './antd-imports'; // Performance constants export const PERFORMANCE_CONSTANTS = { CACHE_CLEAR_INTERVAL: 300000, // 5 minutes VIRTUALIZATION_THRESHOLD: 25, // Updated to match main virtualization threshold DRAG_THROTTLE_MS: 50, RENDER_TIMEOUT_MS: 16, // 60fps target MAX_CACHE_SIZE: 1000, } as const; // Priority and status color constants export const PRIORITY_COLORS = { critical: '#ff4d4f', high: '#ff7a45', medium: '#faad14', low: '#52c41a', } as const; export const STATUS_COLORS = { todo: '#f0f0f0', doing: '#1890ff', done: '#52c41a', } as const; // Cache management for date formatting class DateFormatCache { private cache = new Map(); private maxSize: number; constructor(maxSize: number = PERFORMANCE_CONSTANTS.MAX_CACHE_SIZE) { this.maxSize = maxSize; } get(key: string): string | undefined { return this.cache.get(key); } set(key: string, value: string): void { // LRU eviction when cache is full if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value as string; if (firstKey) { this.cache.delete(firstKey); } } this.cache.set(key, value); } has(key: string): boolean { return this.cache.has(key); } clear(): void { this.cache.clear(); } size(): number { return this.cache.size; } } // Global cache instances export const formatDateCache = new DateFormatCache(); export const formatDateTimeCache = new DateFormatCache(); // Optimized date formatters with caching export const formatDate = (dateString?: string): string => { if (!dateString) return ''; if (formatDateCache.has(dateString)) { return formatDateCache.get(dateString)!; } const formatted = dayjs(dateString).format('MMM DD, YYYY'); formatDateCache.set(dateString, formatted); return formatted; }; export const formatDateTime = (dateString?: string): string => { if (!dateString) return ''; if (formatDateTimeCache.has(dateString)) { return formatDateTimeCache.get(dateString)!; } const formatted = dayjs(dateString).format('MMM DD, YYYY HH:mm'); formatDateTimeCache.set(dateString, formatted); return formatted; }; // Performance monitoring utilities export class PerformanceMonitor { private static instance: PerformanceMonitor; private metrics: Map = new Map(); static getInstance(): PerformanceMonitor { if (!PerformanceMonitor.instance) { PerformanceMonitor.instance = new PerformanceMonitor(); } return PerformanceMonitor.instance; } startTiming(operation: string): () => void { const startTime = performance.now(); return () => { const endTime = performance.now(); const duration = endTime - startTime; if (!this.metrics.has(operation)) { this.metrics.set(operation, []); } const operationMetrics = this.metrics.get(operation)!; operationMetrics.push(duration); // Keep only last 100 measurements if (operationMetrics.length > 100) { operationMetrics.shift(); } }; } getAverageTime(operation: string): number { const times = this.metrics.get(operation); if (!times || times.length === 0) return 0; const sum = times.reduce((acc, time) => acc + time, 0); return sum / times.length; } getMetrics(): Record { const result: Record = {}; this.metrics.forEach((times, operation) => { if (times.length > 0) { result[operation] = { average: this.getAverageTime(operation), count: times.length, latest: times[times.length - 1], }; } }); return result; } logMetrics(): void { const metrics = this.getMetrics(); console.table(metrics); } clearMetrics(): void { this.metrics.clear(); } } // Task comparison utilities for React.memo export const taskPropsEqual = (prevTask: Task, nextTask: Task): boolean => { // Quick identity check if (prevTask === nextTask) return true; if (prevTask.id !== nextTask.id) return false; // Check commonly changing properties const criticalProps: (keyof Task)[] = [ 'title', 'progress', 'status', 'priority', 'description', 'startDate', 'dueDate', 'updatedAt' ]; for (const prop of criticalProps) { if (prevTask[prop] !== nextTask[prop]) { return false; } } // Check array lengths (fast path) if (prevTask.labels?.length !== nextTask.labels?.length) return false; if (prevTask.assignee_names?.length !== nextTask.assignee_names?.length) return false; return true; }; // Throttle utility for drag operations export const throttle = any>( func: T, delay: number ): ((...args: Parameters) => void) => { let timeoutId: NodeJS.Timeout | null = null; let lastExecTime = 0; return (...args: Parameters) => { const currentTime = Date.now(); const execute = () => { lastExecTime = currentTime; func(...args); }; if (currentTime - lastExecTime > delay) { execute(); } else { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(execute, delay - (currentTime - lastExecTime)); } }; }; // Debounce utility for input operations export const debounce = any>( func: T, delay: number ): ((...args: Parameters) => void) => { let timeoutId: NodeJS.Timeout | null = null; return (...args: Parameters) => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => func(...args), delay); }; }; // Virtualization utilities export const shouldVirtualize = (itemCount: number): boolean => { return itemCount > PERFORMANCE_CONSTANTS.VIRTUALIZATION_THRESHOLD; }; export const calculateVirtualizedHeight = ( itemCount: number, itemHeight: number, maxHeight: number = 600 ): number => { const totalHeight = itemCount * itemHeight; return Math.min(totalHeight, maxHeight); }; // CSS class utilities for performance export const getOptimizedClasses = ( isDragging: boolean, isVirtualized: boolean, isSelected: boolean ): string => { const classes = ['task-row-optimized']; if (isDragging) { classes.push('task-row-dragging'); } if (isVirtualized) { classes.push('task-row-virtualized'); } if (isSelected) { classes.push('task-row-selected'); } return classes.join(' '); }; // Memory management export const clearAllCaches = (): void => { formatDateCache.clear(); formatDateTimeCache.clear(); PerformanceMonitor.getInstance().clearMetrics(); }; // Performance debugging helpers export const logPerformanceWarning = (operation: string, duration: number): void => { if (duration > PERFORMANCE_CONSTANTS.RENDER_TIMEOUT_MS) { console.warn( `Performance warning: ${operation} took ${duration.toFixed(2)}ms (target: ${PERFORMANCE_CONSTANTS.RENDER_TIMEOUT_MS}ms)` ); } }; // Auto-cleanup setup let cleanupInterval: NodeJS.Timeout | null = null; export const setupAutoCleanup = (): void => { if (cleanupInterval) { clearInterval(cleanupInterval); } cleanupInterval = setInterval(() => { clearAllCaches(); }, PERFORMANCE_CONSTANTS.CACHE_CLEAR_INTERVAL); }; export const teardownAutoCleanup = (): void => { if (cleanupInterval) { clearInterval(cleanupInterval); cleanupInterval = null; } }; // Initialize auto-cleanup if (typeof window !== 'undefined') { setupAutoCleanup(); // Cleanup on page unload window.addEventListener('beforeunload', teardownAutoCleanup); } // Export performance monitor instance export const performanceMonitor = PerformanceMonitor.getInstance(); // Task adapter utilities export const createLabelsAdapter = (task: Task) => ({ id: task.id, name: task.title, parent_task_id: undefined, manual_progress: false, all_labels: task.labels?.map(label => ({ id: label.id, name: label.name, color_code: label.color })) || [], labels: task.labels?.map(label => ({ id: label.id, name: label.name, color_code: label.color })) || [], } as any); export const createAssigneeAdapter = (task: Task) => ({ id: task.id, name: task.title, parent_task_id: undefined, manual_progress: false, assignees: task.assignee_names?.map(member => ({ team_member_id: member.team_member_id || '', id: member.team_member_id || '', project_member_id: member.team_member_id || '', name: member.name, })) || [], } as any); // Color utilities export const getPriorityColor = (priority: string): string => { return PRIORITY_COLORS[priority as keyof typeof PRIORITY_COLORS] || '#d9d9d9'; }; export const getStatusColor = (status: string): string => { return STATUS_COLORS[status as keyof typeof STATUS_COLORS] || '#d9d9d9'; };