expand sub tasks

This commit is contained in:
chamiakJ
2025-07-03 01:31:05 +05:30
parent 3bef18901a
commit ecd4d29a38
435 changed files with 13150 additions and 11087 deletions

View File

@@ -1,6 +1,6 @@
import { formatDistanceToNow } from "date-fns";
import { enUS, es, pt } from "date-fns/locale";
import { getLanguageFromLocalStorage } from "./language-utils";
import { formatDistanceToNow } from 'date-fns';
import { enUS, es, pt } from 'date-fns/locale';
import { getLanguageFromLocalStorage } from './language-utils';
export function calculateTimeGap(timestamp: string | Date): string {
const localeString = getLanguageFromLocalStorage();

View File

@@ -1,13 +1,13 @@
import { tasksApiService } from "@/api/tasks/tasks.api.service";
import logger from "./errorLogger";
import { tasksApiService } from '@/api/tasks/tasks.api.service';
import logger from './errorLogger';
export const checkTaskDependencyStatus = async (taskId: string, statusId: string) => {
if (!taskId || !statusId) return false;
try {
const res = await tasksApiService.getTaskDependencyStatus(taskId, statusId);
return res.done ? res.body.can_continue : false;
} catch (error) {
logger.error('Error checking task dependency status:', error);
return false;
}
};
if (!taskId || !statusId) return false;
try {
const res = await tasksApiService.getTaskDependencyStatus(taskId, statusId);
return res.done ? res.body.can_continue : false;
} catch (error) {
logger.error('Error checking task dependency status:', error);
return false;
}
};

View File

@@ -1,3 +1,3 @@
export const tagBackground = (color: string): string => {
return `${color}1A`; // 1A is 10% opacity in hex
};
};

View File

@@ -7,7 +7,7 @@ dayjs.extend(relativeTime);
/**
* Formats a date to a relative time string (e.g., "2 hours ago", "a day ago")
* This mimics the Angular fromNow pipe functionality
*
*
* @param date - The date to format (string, Date, or dayjs object)
* @returns A string representing the relative time
*/
@@ -18,7 +18,7 @@ export const fromNow = (date: string | Date | dayjs.Dayjs): string => {
/**
* Formats a date to a specific format
*
*
* @param date - The date to format (string, Date, or dayjs object)
* @param format - The format string (default: 'YYYY-MM-DD')
* @returns A formatted date string
@@ -29,4 +29,4 @@ export const formatDate = (
): string => {
if (!date) return '';
return dayjs(date).format(format);
};
};

View File

@@ -4,7 +4,8 @@ export const debugPerformance = {
// Log component render times
logComponentRender: (componentName: string, startTime: number) => {
const renderTime = performance.now() - startTime;
if (renderTime > 16) { // Log slow renders (>16ms)
if (renderTime > 16) {
// Log slow renders (>16ms)
console.warn(`Slow render detected: ${componentName} took ${renderTime.toFixed(2)}ms`);
}
},
@@ -12,7 +13,8 @@ export const debugPerformance = {
// Log Redux selector performance
logSelectorPerformance: (selectorName: string, startTime: number) => {
const executionTime = performance.now() - startTime;
if (executionTime > 5) { // Log slow selectors (>5ms)
if (executionTime > 5) {
// Log slow selectors (>5ms)
console.warn(`Slow selector detected: ${selectorName} took ${executionTime.toFixed(2)}ms`);
}
},
@@ -24,9 +26,11 @@ export const debugPerformance = {
const usedMB = memory.usedJSHeapSize / 1024 / 1024;
const totalMB = memory.totalJSHeapSize / 1024 / 1024;
const limitMB = memory.jsHeapSizeLimit / 1024 / 1024;
console.log(`Memory Usage: ${usedMB.toFixed(1)}MB / ${totalMB.toFixed(1)}MB (${limitMB.toFixed(1)}MB limit)`);
console.log(
`Memory Usage: ${usedMB.toFixed(1)}MB / ${totalMB.toFixed(1)}MB (${limitMB.toFixed(1)}MB limit)`
);
if (usedMB > 100) {
console.warn(`High memory usage detected: ${usedMB.toFixed(1)}MB`);
}
@@ -37,7 +41,7 @@ export const debugPerformance = {
logDOMNodes: () => {
const nodeCount = document.querySelectorAll('*').length;
console.log(`Total DOM nodes: ${nodeCount}`);
if (nodeCount > 1000) {
console.warn(`High DOM node count detected: ${nodeCount} nodes`);
}
@@ -54,25 +58,28 @@ export const debugPerformance = {
logScrollPerformance: () => {
let lastScrollTime = 0;
let scrollCount = 0;
const handleScroll = () => {
const currentTime = performance.now();
const timeSinceLastScroll = currentTime - lastScrollTime;
if (timeSinceLastScroll < 16) { // Less than 60fps
if (timeSinceLastScroll < 16) {
// Less than 60fps
scrollCount++;
if (scrollCount > 5) {
console.warn(`Poor scroll performance detected: ${timeSinceLastScroll.toFixed(2)}ms between scrolls`);
console.warn(
`Poor scroll performance detected: ${timeSinceLastScroll.toFixed(2)}ms between scrolls`
);
}
} else {
scrollCount = 0;
}
lastScrollTime = currentTime;
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
window.removeEventListener('scroll', handleScroll);
};
@@ -81,21 +88,21 @@ export const debugPerformance = {
// Log long tasks
logLongTasks: () => {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'longtask') {
console.warn(`Long task detected: ${entry.duration.toFixed(2)}ms`, entry);
}
}
});
observer.observe({ entryTypes: ['longtask'] });
return () => {
observer.disconnect();
};
}
return () => {};
},
@@ -104,38 +111,39 @@ export const debugPerformance = {
let hoverStartTime = 0;
let hoverCount = 0;
let totalHoverTime = 0;
const handleMouseEnter = () => {
hoverStartTime = performance.now();
};
const handleMouseLeave = () => {
if (hoverStartTime > 0) {
const hoverDuration = performance.now() - hoverStartTime;
totalHoverTime += hoverDuration;
hoverCount++;
if (hoverDuration > 50) { // Log slow hover operations (>50ms)
if (hoverDuration > 50) {
// Log slow hover operations (>50ms)
console.warn(`Slow hover operation detected: ${hoverDuration.toFixed(2)}ms`);
}
// Log average hover time every 10 hovers
if (hoverCount % 10 === 0) {
const avgHoverTime = totalHoverTime / hoverCount;
console.log(`Average hover time: ${avgHoverTime.toFixed(2)}ms (${hoverCount} hovers)`);
}
hoverStartTime = 0;
}
};
// Monitor hover events on task rows specifically
const taskRows = document.querySelectorAll('.task-row-optimized, .task-row');
taskRows.forEach(row => {
row.addEventListener('mouseenter', handleMouseEnter, { passive: true });
row.addEventListener('mouseleave', handleMouseLeave, { passive: true });
});
return () => {
taskRows.forEach(row => {
row.removeEventListener('mouseenter', handleMouseEnter);
@@ -147,48 +155,49 @@ export const debugPerformance = {
// NEW: Monitor CSS transitions and animations
logCSSPerformance: () => {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'measure') {
const duration = entry.duration;
if (duration > 16) { // Log slow CSS operations (>16ms)
if (duration > 16) {
// Log slow CSS operations (>16ms)
console.warn(`Slow CSS operation detected: ${duration.toFixed(2)}ms - ${entry.name}`);
}
}
}
});
observer.observe({ entryTypes: ['measure'] });
return () => {
observer.disconnect();
};
}
return () => {};
},
// Comprehensive performance check
runPerformanceCheck: () => {
console.group('🔍 Performance Check');
// Memory usage
debugPerformance.logMemoryUsage();
// DOM nodes
debugPerformance.logDOMNodes();
// React components
debugPerformance.logReactComponents();
// Start monitoring
const stopScroll = debugPerformance.logScrollPerformance();
const stopLongTasks = debugPerformance.logLongTasks();
const stopHover = debugPerformance.logHoverPerformance();
const stopCSS = debugPerformance.logCSSPerformance();
console.groupEnd();
return () => {
stopScroll();
stopLongTasks();
@@ -200,7 +209,7 @@ export const debugPerformance = {
// Monitor specific component
monitorComponent: (componentName: string) => {
const startTime = performance.now();
return () => {
debugPerformance.logComponentRender(componentName, startTime);
};
@@ -217,41 +226,41 @@ export const debugPerformance = {
// NEW: Quick hover performance test
testHoverPerformance: () => {
console.group('🧪 Hover Performance Test');
const taskRows = document.querySelectorAll('.task-row-optimized, .task-row');
console.log(`Found ${taskRows.length} task rows to test`);
let totalHoverTime = 0;
let hoverCount = 0;
const testHover = (row: Element) => {
return new Promise<void>((resolve) => {
return new Promise<void>(resolve => {
const startTime = performance.now();
// Simulate hover
row.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
setTimeout(() => {
row.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }));
const hoverTime = performance.now() - startTime;
totalHoverTime += hoverTime;
hoverCount++;
if (hoverTime > 50) {
console.warn(`Slow hover on row ${hoverCount}: ${hoverTime.toFixed(2)}ms`);
}
resolve();
}, 100);
});
};
// Test first 5 rows
const testRows = Array.from(taskRows).slice(0, 5);
Promise.all(testRows.map(testHover)).then(() => {
const avgHoverTime = totalHoverTime / hoverCount;
console.log(`Average hover time: ${avgHoverTime.toFixed(2)}ms (${hoverCount} tests)`);
if (avgHoverTime > 30) {
console.error(`🚨 Poor hover performance detected: ${avgHoverTime.toFixed(2)}ms average`);
} else if (avgHoverTime > 16) {
@@ -259,10 +268,10 @@ export const debugPerformance = {
} else {
console.log(`✅ Good hover performance: ${avgHoverTime.toFixed(2)}ms average`);
}
console.groupEnd();
});
}
},
};
// Auto-run performance check in development
@@ -271,7 +280,7 @@ if (process.env.NODE_ENV === 'development') {
window.addEventListener('load', () => {
setTimeout(() => {
debugPerformance.runPerformanceCheck();
// Run hover performance test after 3 seconds
setTimeout(() => {
debugPerformance.testHoverPerformance();
@@ -281,4 +290,4 @@ if (process.env.NODE_ENV === 'development') {
}
// Export for manual use
export default debugPerformance;
export default debugPerformance;

View File

@@ -51,7 +51,7 @@ export class PerformanceMonitor {
// Get all metrics
getMetrics(): Record<string, { average: number; count: number; min: number; max: number }> {
const result: Record<string, { average: number; count: number; min: number; max: number }> = {};
this.metrics.forEach((values, name) => {
if (values.length > 0) {
result[name] = {
@@ -62,7 +62,7 @@ export class PerformanceMonitor {
};
}
});
return result;
}
@@ -70,9 +70,9 @@ export class PerformanceMonitor {
monitorComponentRender(componentName: string): () => void {
const startMark = `${componentName}-render-start`;
const endMark = `${componentName}-render-end`;
this.mark(startMark);
return () => {
this.mark(endMark);
this.measure(`${componentName}-render-time`, startMark, endMark);
@@ -84,7 +84,7 @@ export class PerformanceMonitor {
const startTime = performance.now();
const result = selectorFn();
const endTime = performance.now();
this.addMetric(`${selectorName}-execution-time`, endTime - startTime);
return result;
}
@@ -94,7 +94,7 @@ export class PerformanceMonitor {
const startTime = performance.now();
operation();
const endTime = performance.now();
this.addMetric(`${operationName}-dom-time`, endTime - startTime);
}
@@ -112,23 +112,24 @@ export class PerformanceMonitor {
startFrameRateMonitoring(): () => void {
let frameCount = 0;
let lastTime = performance.now();
const measureFrameRate = () => {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) { // Every second
if (currentTime - lastTime >= 1000) {
// Every second
const fps = frameCount / ((currentTime - lastTime) / 1000);
this.addMetric('fps', fps);
frameCount = 0;
lastTime = currentTime;
}
requestAnimationFrame(measureFrameRate);
};
const animationId = requestAnimationFrame(measureFrameRate);
return () => {
cancelAnimationFrame(animationId);
};
@@ -137,7 +138,7 @@ export class PerformanceMonitor {
// Monitor long tasks
startLongTaskMonitoring(): () => void {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'longtask') {
this.addMetric('long-task-duration', entry.duration);
@@ -145,16 +146,16 @@ export class PerformanceMonitor {
}
}
});
observer.observe({ entryTypes: ['longtask'] });
this.observers.set('longtask', observer);
return () => {
observer.disconnect();
this.observers.delete('longtask');
};
}
return () => {};
}
@@ -163,19 +164,20 @@ export class PerformanceMonitor {
let layoutCount = 0;
let lastLayoutTime = 0;
const monitor = this;
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
Element.prototype.getBoundingClientRect = function() {
Element.prototype.getBoundingClientRect = function () {
const currentTime = performance.now();
if (currentTime - lastLayoutTime < 16) { // Less than 16ms between calls
if (currentTime - lastLayoutTime < 16) {
// Less than 16ms between calls
layoutCount++;
monitor.addMetric('layout-thrashing-count', layoutCount);
}
lastLayoutTime = currentTime;
return originalGetBoundingClientRect.call(this);
};
return () => {
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
};
@@ -190,60 +192,62 @@ export class PerformanceMonitor {
summary: this.generateSummary(metrics),
recommendations: this.generateRecommendations(metrics),
};
return JSON.stringify(report, null, 2);
}
private generateSummary(metrics: Record<string, any>): Record<string, string> {
const summary: Record<string, string> = {};
// Component render times
const renderTimes = Object.keys(metrics).filter(key => key.includes('render-time'));
if (renderTimes.length > 0) {
const avgRenderTime = renderTimes.reduce((sum, key) => sum + metrics[key].average, 0) / renderTimes.length;
const avgRenderTime =
renderTimes.reduce((sum, key) => sum + metrics[key].average, 0) / renderTimes.length;
summary.renderPerformance = avgRenderTime > 16 ? 'Poor' : avgRenderTime > 8 ? 'Fair' : 'Good';
}
// FPS
if (metrics.fps) {
summary.frameRate = metrics.fps.average > 55 ? 'Good' : metrics.fps.average > 30 ? 'Fair' : 'Poor';
summary.frameRate =
metrics.fps.average > 55 ? 'Good' : metrics.fps.average > 30 ? 'Fair' : 'Poor';
}
// Memory usage
if (metrics['memory-used'] && metrics['memory-limit']) {
const memoryUsage = (metrics['memory-used'].average / metrics['memory-limit'].average) * 100;
summary.memoryUsage = memoryUsage > 80 ? 'High' : memoryUsage > 50 ? 'Moderate' : 'Low';
}
return summary;
}
private generateRecommendations(metrics: Record<string, any>): string[] {
const recommendations: string[] = [];
// Check for slow component renders
const slowRenders = Object.keys(metrics).filter(key =>
key.includes('render-time') && metrics[key].average > 16
const slowRenders = Object.keys(metrics).filter(
key => key.includes('render-time') && metrics[key].average > 16
);
if (slowRenders.length > 0) {
recommendations.push(`Optimize component renders: ${slowRenders.join(', ')}`);
}
// Check for layout thrashing
if (metrics['layout-thrashing-count'] && metrics['layout-thrashing-count'].count > 10) {
recommendations.push('Reduce layout thrashing by batching DOM reads and writes');
}
// Check for long tasks
if (metrics['long-task-duration'] && metrics['long-task-duration'].count > 0) {
recommendations.push('Break down long tasks into smaller chunks');
}
// Check for low FPS
if (metrics.fps && metrics.fps.average < 30) {
recommendations.push('Optimize rendering performance to maintain 60fps');
}
// Check for high memory usage
if (metrics['memory-used'] && metrics['memory-limit']) {
const memoryUsage = (metrics['memory-used'].average / metrics['memory-limit'].average) * 100;
@@ -251,7 +255,7 @@ export class PerformanceMonitor {
recommendations.push('Reduce memory usage to prevent performance degradation');
}
}
return recommendations;
}
@@ -265,7 +269,7 @@ export class PerformanceMonitor {
// Stop all monitoring
stop(): void {
this.observers.forEach((observer) => {
this.observers.forEach(observer => {
observer.disconnect();
});
this.observers.clear();
@@ -292,13 +296,13 @@ export const performanceMiddleware = (store: any) => (next: any) => (action: any
const startTime = performance.now();
const result = next(action);
const endTime = performance.now();
performanceMonitor.addMetric(`redux-action-${action.type}`, endTime - startTime);
return result;
};
// Export for global access
if (typeof window !== 'undefined') {
(window as any).performanceMonitor = performanceMonitor;
}
}

View File

@@ -29,18 +29,22 @@ class PerformanceOptimizer {
private initializeObservers() {
// Monitor long tasks
if ('PerformanceObserver' in window) {
this.longTaskObserver = new PerformanceObserver((list) => {
this.longTaskObserver = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'longtask') {
this.metrics.longTaskCount++;
this.metrics.averageLongTaskDuration =
(this.metrics.averageLongTaskDuration * (this.metrics.longTaskCount - 1) + entry.duration) / this.metrics.longTaskCount;
console.warn(`🚨 Long task detected: ${entry.duration.toFixed(2)}ms - Consider chunking this operation`);
this.metrics.averageLongTaskDuration =
(this.metrics.averageLongTaskDuration * (this.metrics.longTaskCount - 1) +
entry.duration) /
this.metrics.longTaskCount;
console.warn(
`🚨 Long task detected: ${entry.duration.toFixed(2)}ms - Consider chunking this operation`
);
}
}
});
this.longTaskObserver.observe({ entryTypes: ['longtask'] });
}
@@ -51,24 +55,24 @@ class PerformanceOptimizer {
private startFrameRateMonitoring() {
let frameCount = 0;
let lastTime = performance.now();
const measureFrameRate = () => {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
this.metrics.frameRate = frameCount;
frameCount = 0;
lastTime = currentTime;
if (this.metrics.frameRate < 30) {
console.warn(`⚠️ Low frame rate detected: ${this.metrics.frameRate}fps`);
}
}
requestAnimationFrame(measureFrameRate);
};
requestAnimationFrame(measureFrameRate);
}
@@ -79,26 +83,28 @@ class PerformanceOptimizer {
chunkSize: number = 10,
delay: number = 16
): Promise<void> {
return new Promise((resolve) => {
return new Promise(resolve => {
let index = 0;
const processChunk = () => {
const startTime = performance.now();
const endIndex = Math.min(index + chunkSize, items.length);
// Process items in this chunk
for (let i = index; i < endIndex; i++) {
operation(items[i], i);
}
index = endIndex;
// Check if we need to yield to prevent long tasks
const processingTime = performance.now() - startTime;
if (processingTime > 16) {
console.warn(`⚠️ Chunk processing took ${processingTime.toFixed(2)}ms - consider smaller chunks`);
console.warn(
`⚠️ Chunk processing took ${processingTime.toFixed(2)}ms - consider smaller chunks`
);
}
if (index < items.length) {
// Schedule next chunk with delay to prevent blocking
setTimeout(processChunk, delay);
@@ -106,7 +112,7 @@ class PerformanceOptimizer {
resolve();
}
};
processChunk();
});
}
@@ -117,19 +123,16 @@ class PerformanceOptimizer {
requestAnimationFrame(() => {
// Force layout read first
document.body.offsetHeight;
// Perform all write operations
operations.forEach(operation => operation());
});
}
// Debounce function for expensive operations
static debounce<T extends (...args: any[]) => void>(
func: T,
delay: number
): T {
static debounce<T extends (...args: any[]) => void>(func: T, delay: number): T {
let timeoutId: NodeJS.Timeout;
return ((...args: any[]) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
@@ -137,15 +140,12 @@ class PerformanceOptimizer {
}
// Throttle function for frequent operations
static throttle<T extends (...args: any[]) => void>(
func: T,
delay: number
): T {
static throttle<T extends (...args: any[]) => void>(func: T, delay: number): T {
let lastExecTime = 0;
return ((...args: any[]) => {
const currentTime = performance.now();
if (currentTime - lastExecTime > delay) {
func(...args);
lastExecTime = currentTime;
@@ -163,11 +163,7 @@ class PerformanceOptimizer {
overscanCount?: number;
} = {}
): React.ReactNode[] {
const {
chunkSize = 50,
virtualizationThreshold = 100,
overscanCount = 5
} = options;
const { chunkSize = 50, virtualizationThreshold = 100, overscanCount = 5 } = options;
// For small lists, render everything
if (items.length <= virtualizationThreshold) {
@@ -176,16 +172,18 @@ class PerformanceOptimizer {
// For large lists, use chunked rendering
const chunks: React.ReactNode[] = [];
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
chunks.push(
React.createElement('div', { key: `chunk-${i}`, className: 'virtualized-chunk' },
React.createElement(
'div',
{ key: `chunk-${i}`, className: 'virtualized-chunk' },
chunk.map((item, index) => renderItem(item, i + index))
)
);
}
return chunks;
}
@@ -196,7 +194,7 @@ class PerformanceOptimizer {
return {
used: memory.usedJSHeapSize / 1024 / 1024,
total: memory.totalJSHeapSize / 1024 / 1024,
limit: memory.jsHeapSizeLimit / 1024 / 1024
limit: memory.jsHeapSizeLimit / 1024 / 1024,
};
}
return { used: 0, total: 0, limit: 0 };
@@ -205,7 +203,7 @@ class PerformanceOptimizer {
// Optimize scroll performance
static optimizeScroll(container: HTMLElement, handler: (event: Event) => void): () => void {
let ticking = false;
const optimizedHandler = (event: Event) => {
if (!ticking) {
requestAnimationFrame(() => {
@@ -215,9 +213,9 @@ class PerformanceOptimizer {
ticking = true;
}
};
container.addEventListener('scroll', optimizedHandler, { passive: true });
return () => {
container.removeEventListener('scroll', optimizedHandler);
};
@@ -232,20 +230,20 @@ class PerformanceOptimizer {
): () => void {
let enterTimeout: NodeJS.Timeout;
let leaveTimeout: NodeJS.Timeout;
const handleMouseEnter = () => {
clearTimeout(leaveTimeout);
enterTimeout = setTimeout(onEnter, delay);
};
const handleMouseLeave = () => {
clearTimeout(enterTimeout);
leaveTimeout = setTimeout(onLeave, delay);
};
element.addEventListener('mouseenter', handleMouseEnter, { passive: true });
element.addEventListener('mouseleave', handleMouseLeave, { passive: true });
return () => {
clearTimeout(enterTimeout);
clearTimeout(leaveTimeout);
@@ -259,7 +257,7 @@ class PerformanceOptimizer {
const memory = PerformanceOptimizer.getMemoryUsage();
this.metrics.memoryUsage = memory.used;
this.metrics.layoutThrashingCount = this.layoutThrashingCount;
return { ...this.metrics };
}
@@ -294,4 +292,4 @@ window.addEventListener('beforeunload', () => {
});
export { PerformanceOptimizer, performanceOptimizer };
export default PerformanceOptimizer;
export default PerformanceOptimizer;

View File

@@ -22,7 +22,7 @@ class PerformanceMonitor {
*/
public startTimer(name: string): void {
if (isProduction) return;
this.timers.set(name, performance.now());
}
@@ -46,10 +46,7 @@ class PerformanceMonitor {
if (isDevelopment) {
const color = duration > 100 ? '#ff4d4f' : duration > 50 ? '#faad14' : '#52c41a';
console.log(
`%c⏱ ${name}: ${duration.toFixed(2)}ms`,
`color: ${color}; font-weight: bold;`
);
console.log(`%c⏱ ${name}: ${duration.toFixed(2)}ms`, `color: ${color}; font-weight: bold;`);
}
return duration;
@@ -100,7 +97,7 @@ class PerformanceMonitor {
if (isProduction || this.entries.length === 0) return;
console.group('📊 Performance Summary');
const sortedEntries = this.entries
.filter(entry => entry.duration !== undefined)
.sort((a, b) => (b.duration || 0) - (a.duration || 0));
@@ -109,13 +106,13 @@ class PerformanceMonitor {
sortedEntries.map(entry => ({
Name: entry.name,
Duration: `${(entry.duration || 0).toFixed(2)}ms`,
'Start Time': `${entry.startTime.toFixed(2)}ms`
'Start Time': `${entry.startTime.toFixed(2)}ms`,
}))
);
const totalTime = sortedEntries.reduce((sum, entry) => sum + (entry.duration || 0), 0);
console.log(`%cTotal measured time: ${totalTime.toFixed(2)}ms`, 'font-weight: bold;');
console.groupEnd();
}
}
@@ -133,7 +130,7 @@ export function withPerformanceMonitoring<P extends object>(
if (isProduction) return Component;
const name = componentName || Component.displayName || Component.name || 'Unknown';
const WrappedComponent = (props: P) => {
React.useEffect(() => {
performanceMonitor.startTimer(`${name} mount`);
@@ -179,4 +176,4 @@ export function useRenderPerformance(componentName: string): void {
startTime.current = performance.now();
}
export default performanceMonitor;
export default performanceMonitor;

View File

@@ -1,5 +1,5 @@
import { GroupedProject, ProjectGroupBy } from "@/types/project/project.types";
import { IProjectViewModel } from "@/types/project/projectViewModel.types";
import { GroupedProject, ProjectGroupBy } from '@/types/project/project.types';
import { IProjectViewModel } from '@/types/project/projectViewModel.types';
export const groupProjects = (
projects: IProjectViewModel[],
@@ -33,7 +33,7 @@ export const groupProjects = (
projects: [],
count: 0,
totalProgress: 0,
totalTasks: 0
totalTasks: 0,
};
}
@@ -44,4 +44,4 @@ export const groupProjects = (
});
return Object.values(grouped);
};
};

View File

@@ -18,7 +18,11 @@ class RoutePreloader {
/**
* Register a route for preloading
*/
public registerRoute(path: string, loader: () => Promise<any>, priority: 'high' | 'medium' | 'low' = 'medium'): void {
public registerRoute(
path: string,
loader: () => Promise<any>,
priority: 'high' | 'medium' | 'low' = 'medium'
): void {
if (this.preloadedRoutes.has(path)) return;
this.preloadQueue.push({ path, loader, priority });
@@ -83,7 +87,11 @@ class RoutePreloader {
/**
* Preload routes on user interaction (hover, focus)
*/
public preloadOnInteraction(element: HTMLElement, path: string, loader: () => Promise<any>): void {
public preloadOnInteraction(
element: HTMLElement,
path: string,
loader: () => Promise<any>
): void {
if (this.preloadedRoutes.has(path)) return;
let preloadTriggered = false;
@@ -93,7 +101,7 @@ class RoutePreloader {
preloadTriggered = true;
this.preloadRoute(path, loader);
// Clean up listeners
element.removeEventListener('mouseenter', handleInteraction);
element.removeEventListener('focus', handleInteraction);
@@ -110,9 +118,12 @@ class RoutePreloader {
*/
public preloadOnIdle(): void {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
this.startPreloading();
}, { timeout: 2000 });
requestIdleCallback(
() => {
this.startPreloading();
},
{ timeout: 2000 }
);
} else {
setTimeout(() => {
this.startPreloading();
@@ -147,7 +158,9 @@ export const routePreloader = new RoutePreloader();
/**
* React hook to preload routes on component mount
*/
export function useRoutePreloader(routes: Array<{ path: string; loader: () => Promise<any>; priority?: 'high' | 'medium' | 'low' }>): void {
export function useRoutePreloader(
routes: Array<{ path: string; loader: () => Promise<any>; priority?: 'high' | 'medium' | 'low' }>
): void {
React.useEffect(() => {
routes.forEach(route => {
routePreloader.registerRoute(route.path, route.loader, route.priority);
@@ -178,4 +191,4 @@ export function usePreloadOnHover(path: string, loader: () => Promise<any>) {
return elementRef;
}
export default routePreloader;
export default routePreloader;

View File

@@ -2,34 +2,34 @@ import DOMPurify from 'dompurify';
/**
* Sanitizes user input to prevent XSS attacks
*
*
* @param input - The user input string to sanitize
* @param options - Optional configuration for DOMPurify
* @returns Sanitized string
*/
export const sanitizeInput = (input: string, options?: DOMPurify.Config): string => {
if (!input) return '';
// Default options for plain text inputs (strip all HTML)
const defaultOptions: DOMPurify.Config = {
ALLOWED_TAGS: [],
ALLOWED_ATTR: [],
};
return DOMPurify.sanitize(input, options || defaultOptions);
};
/**
* Sanitizes a string for use in HTML contexts (allows some basic tags)
*
*
* @param input - The input containing HTML to sanitize
* @returns Sanitized HTML string
*/
export const sanitizeHtml = (input: string): string => {
if (!input) return '';
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'span'],
ALLOWED_ATTR: ['href', 'target', 'rel', 'class'],
});
};
};

View File

@@ -1,3 +1,3 @@
export const getDayName = (date: Date) => {
return date.toLocaleDateString('en-US', { weekday: 'long' }); // Returns `Monday`, `Tuesday`, etc.
};
return date.toLocaleDateString('en-US', { weekday: 'long' }); // Returns `Monday`, `Tuesday`, etc.
};

View File

@@ -6,7 +6,7 @@ export const sortByBooleanField = <T extends Record<string, any>>(
return [...data].sort((a, b) => {
const aValue = !!a[field];
const bValue = !!b[field];
if (aValue === bValue) return 0;
if (prioritizeTrue) {
return aValue ? -1 : 1;
@@ -16,19 +16,17 @@ export const sortByBooleanField = <T extends Record<string, any>>(
});
};
export const sortBySelection = (data: Array<{ selected?: boolean }>) =>
export const sortBySelection = (data: Array<{ selected?: boolean }>) =>
sortByBooleanField(data, 'selected');
export const sortByPending = (data: Array<{ pending_invitation?: boolean }>) =>
export const sortByPending = (data: Array<{ pending_invitation?: boolean }>) =>
sortByBooleanField(data, 'pending_invitation', false);
export const sortTeamMembers = (data: Array<{ selected?: boolean; pending_invitation?: boolean; is_pending?: boolean }>) => {
export const sortTeamMembers = (
data: Array<{ selected?: boolean; pending_invitation?: boolean; is_pending?: boolean }>
) => {
return sortByBooleanField(
sortByBooleanField(
sortByBooleanField(data, 'is_pending', false),
'pending_invitation',
false
),
sortByBooleanField(sortByBooleanField(data, 'is_pending', false), 'pending_invitation', false),
'selected'
);
};

View File

@@ -92,7 +92,8 @@ function getRandomTimeTracking() {
}
function getRandomDueDate(): string | undefined {
if (Math.random() < 0.7) { // 70% chance of having a due date
if (Math.random() < 0.7) {
// 70% chance of having a due date
const now = new Date();
const daysToAdd = Math.floor(Math.random() * 30) - 10; // -10 to +20 days from now
const dueDate = new Date(now.getTime() + daysToAdd * 24 * 60 * 60 * 1000);
@@ -104,7 +105,7 @@ function getRandomDueDate(): string | undefined {
export function generateMockTask(index: number): Task {
const now = new Date();
const createdAt = new Date(now.getTime() - Math.random() * 30 * 24 * 60 * 60 * 1000); // Up to 30 days ago
return {
id: nanoid(),
title: getRandomElement(taskTitles),
@@ -133,10 +134,10 @@ export function generateBalancedMockTasks(count: number = 100): Task[] {
const tasks: Task[] = [];
const statusDistribution = { todo: 0.4, doing: 0.4, done: 0.2 };
const priorityDistribution = { critical: 0.1, high: 0.3, medium: 0.4, low: 0.2 };
for (let i = 0; i < count; i++) {
const task = generateMockTask(i);
// Distribute statuses
const statusRand = Math.random();
if (statusRand < statusDistribution.todo) {
@@ -146,21 +147,24 @@ export function generateBalancedMockTasks(count: number = 100): Task[] {
} else {
task.status = 'done';
}
// Distribute priorities
const priorityRand = Math.random();
if (priorityRand < priorityDistribution.critical) {
task.priority = 'critical';
} else if (priorityRand < priorityDistribution.critical + priorityDistribution.high) {
task.priority = 'high';
} else if (priorityRand < priorityDistribution.critical + priorityDistribution.high + priorityDistribution.medium) {
} else if (
priorityRand <
priorityDistribution.critical + priorityDistribution.high + priorityDistribution.medium
) {
task.priority = 'medium';
} else {
task.priority = 'low';
}
tasks.push(task);
}
return tasks;
}
}

View File

@@ -10,4 +10,3 @@ export function buildTimeString(hours: number, minutes: number, seconds: number)
const s = `${seconds}s`;
return `${h} ${m} ${s}`.trim();
}

View File

@@ -1,11 +1,11 @@
export const validateEmail = (email: string): boolean => {
if (!email) return false;
// Check if the email has basic format with @ and domain part
if (!email.includes('@') || email.endsWith('@') || email.split('@').length !== 2) {
return false;
}
const EMAIL_REGEXP =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return EMAIL_REGEXP.test(email);