feat(task-management): enhance task components with performance optimizations and new status field
- Rearranged the order of task fields in the dropdown for better usability. - Introduced a new CSS file for task row optimizations, improving rendering performance and responsiveness. - Added utility functions for date formatting and performance monitoring to enhance task management efficiency. - Updated TaskRow and TaskStatusDropdown components to improve rendering and user experience, including better handling of status display and dark mode support. - Integrated new status field into various task management components, ensuring consistent visibility and functionality across the application.
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
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: 50,
|
||||
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<string, string>();
|
||||
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<string, number[]> = 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<string, { average: number; count: number; latest: number }> {
|
||||
const result: Record<string, { average: number; count: number; latest: number }> = {};
|
||||
|
||||
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 = <T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): ((...args: Parameters<T>) => void) => {
|
||||
let timeoutId: NodeJS.Timeout | null = null;
|
||||
let lastExecTime = 0;
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
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 = <T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): ((...args: Parameters<T>) => void) => {
|
||||
let timeoutId: NodeJS.Timeout | null = null;
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
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';
|
||||
};
|
||||
Reference in New Issue
Block a user