UNPKG

claritykit-svelte

Version:

A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility

262 lines (261 loc) 10.4 kB
/** * Gantt Utilities - ADHD-Optimized Project Management * Provides therapeutic calculations and algorithms for cognitive-friendly project management */ /** * Calculate task position based on timeline configuration */ export function calculateTaskPosition(task, timelineConfig) { const startDate = timelineConfig.startDate.getTime(); const taskStartMs = task.startDate.getTime(); const taskEndMs = task.endDate.getTime(); const msPerDay = 24 * 60 * 60 * 1000; const daysFromStart = (taskStartMs - startDate) / msPerDay; const taskDurationDays = (taskEndMs - taskStartMs) / msPerDay; return { left: daysFromStart * timelineConfig.scale, width: Math.max(taskDurationDays * timelineConfig.scale, 20) // Minimum width for visibility }; } /** * Critical Path Method (CPM) Algorithm * ADHD-optimized to identify the most important tasks clearly */ export function calculateCriticalPath(tasks, dependencies) { // Create task map for quick lookup const taskMap = new Map(tasks.map(task => [task.id, task])); // Calculate Early Start (ES) and Early Finish (EF) const earlyTimes = new Map(); // Forward pass const visited = new Set(); function calculateEarlyTimes(taskId) { if (visited.has(taskId)) return; const task = taskMap.get(taskId); if (!task) return; visited.add(taskId); // Find dependencies that end at this task const predecessors = dependencies.filter(dep => dep.toTaskId === taskId); let maxEF = 0; for (const pred of predecessors) { calculateEarlyTimes(pred.fromTaskId); const predEarly = earlyTimes.get(pred.fromTaskId); if (predEarly) { maxEF = Math.max(maxEF, predEarly.ef + pred.lag); } } const es = maxEF; const ef = es + task.duration; earlyTimes.set(taskId, { es, ef }); } // Calculate early times for all tasks tasks.forEach(task => calculateEarlyTimes(task.id)); // Find project completion time const projectFinish = Math.max(...Array.from(earlyTimes.values()).map(t => t.ef)); // Calculate Late Start (LS) and Late Finish (LF) - Backward pass const lateTimes = new Map(); const visitedBackward = new Set(); function calculateLateTimes(taskId) { if (visitedBackward.has(taskId)) return; const task = taskMap.get(taskId); if (!task) return; visitedBackward.add(taskId); // Find dependencies that start from this task const successors = dependencies.filter(dep => dep.fromTaskId === taskId); let minLS = projectFinish; if (successors.length === 0) { // End task minLS = projectFinish - task.duration; } else { for (const succ of successors) { calculateLateTimes(succ.toTaskId); const succLate = lateTimes.get(succ.toTaskId); if (succLate) { minLS = Math.min(minLS, succLate.ls - succ.lag); } } minLS = minLS - task.duration; } const ls = minLS; const lf = ls + task.duration; lateTimes.set(taskId, { ls, lf }); } // Calculate late times for all tasks tasks.forEach(task => calculateLateTimes(task.id)); // Identify critical tasks (where ES = LS and EF = LF) const criticalTasks = []; const criticalDependencies = []; for (const task of tasks) { const early = earlyTimes.get(task.id); const late = lateTimes.get(task.id); if (early && late && early.es === late.ls && early.ef === late.lf) { criticalTasks.push(task.id); } } // Find critical dependencies (connecting critical tasks) for (const dep of dependencies) { if (criticalTasks.includes(dep.fromTaskId) && criticalTasks.includes(dep.toTaskId)) { criticalDependencies.push(dep.id); } } return { criticalTasks, criticalDependencies, totalDuration: projectFinish, latestFinishDate: new Date(Date.now() + projectFinish * 24 * 60 * 60 * 1000) }; } /** * ADHD-Optimized Cognitive Load Assessment */ export function assessCognitiveLoad(tasks) { const totalTasks = tasks.length; const activeTasks = tasks.filter(t => t.status === 'in-progress').length; const overdueTasks = tasks.filter(t => t.status !== 'completed' && t.endDate < new Date()).length; // ADHD-friendly scoring system let cognitiveScore = 0; cognitiveScore += activeTasks * 15; // Each active task adds cognitive load cognitiveScore += overdueTasks * 25; // Overdue tasks add stress cognitiveScore += Math.max(0, totalTasks - 10) * 5; // Too many total tasks let recommendation; if (cognitiveScore <= 30) recommendation = 'optimal'; else if (cognitiveScore <= 60) recommendation = 'manageable'; else if (cognitiveScore <= 90) recommendation = 'warning'; else recommendation = 'overloaded'; return { totalTasks, activeTasks, overdueTasks, cognitiveScore: Math.min(100, cognitiveScore), recommendation }; } /** * Bulk Operations for ADHD-Friendly Project Management */ export function performBulkOperation(tasks, dependencies, operation) { const affectedTasks = []; const warnings = []; const recommendations = []; switch (operation.type) { case 'reschedule-all': { const offsetDays = operation.parameters?.offsetDays || 0; const newSchedule = tasks.map(task => ({ ...task, startDate: new Date(task.startDate.getTime() + offsetDays * 24 * 60 * 60 * 1000), endDate: new Date(task.endDate.getTime() + offsetDays * 24 * 60 * 60 * 1000) })); return { success: true, affectedTasks: tasks.map(t => t.id), newSchedule, recommendations: ['All tasks have been rescheduled. Review dependencies for conflicts.'] }; } case 'optimize-timeline': { // Remove gaps between dependent tasks const optimizedTasks = [...tasks]; const cognitiveLoad = assessCognitiveLoad(tasks); if (cognitiveLoad.recommendation === 'overloaded') { warnings.push('High cognitive load detected. Consider reducing parallel tasks.'); } recommendations.push('Consider batching similar tasks to reduce context switching.'); recommendations.push('Schedule high-energy tasks during peak focus periods.'); return { success: true, affectedTasks: tasks.map(t => t.id), warnings, recommendations }; } case 'auto-balance': { // Distribute workload evenly across timeline const highEnergyTasks = tasks.filter(t => t.energy >= 4); const lowEnergyTasks = tasks.filter(t => t.energy <= 2); recommendations.push(`Schedule ${highEnergyTasks.length} high-energy tasks during morning hours.`); recommendations.push(`Schedule ${lowEnergyTasks.length} low-energy tasks for afternoon periods.`); if (highEnergyTasks.length > 3) { warnings.push('Too many high-energy tasks scheduled. Consider spreading them across multiple days.'); } return { success: true, affectedTasks: [...highEnergyTasks, ...lowEnergyTasks].map(t => t.id), warnings, recommendations }; } case 'compress-timeline': { // Minimize project duration by parallelizing non-dependent tasks const criticalPath = calculateCriticalPath(tasks, dependencies); recommendations.push(`Focus on critical path tasks: ${criticalPath.criticalTasks.length} tasks identified.`); recommendations.push('Non-critical tasks can be delayed or parallelized.'); return { success: true, affectedTasks: criticalPath.criticalTasks, recommendations }; } default: return { success: false, affectedTasks: [], warnings: ['Unknown operation type'] }; } } /** * Get task pair for dependency visualization */ export function getTaskPair(dependency, tasks) { const fromTask = tasks.find(t => t.id === dependency.fromTaskId); const toTask = tasks.find(t => t.id === dependency.toTaskId); return { fromTask, toTask }; } /** * ADHD-Friendly Break Scheduling */ export function calculateBreakReminders(tasks) { const breakReminders = []; const activeTasks = tasks.filter(t => t.status === 'in-progress'); for (const task of activeTasks) { // Schedule breaks every 2 hours for high-energy tasks if (task.energy >= 4) { const taskStart = task.startDate.getTime(); const taskEnd = task.endDate.getTime(); const duration = taskEnd - taskStart; const breakInterval = 2 * 60 * 60 * 1000; // 2 hours for (let time = taskStart + breakInterval; time < taskEnd; time += breakInterval) { breakReminders.push(new Date(time)); } } } return breakReminders.sort((a, b) => a.getTime() - b.getTime()); } /** * Check for scheduling conflicts */ export function detectConflicts(tasks, dependencies) { const conflicts = []; // Check for dependency violations for (const dep of dependencies) { const fromTask = tasks.find(t => t.id === dep.fromTaskId); const toTask = tasks.find(t => t.id === dep.toTaskId); if (!fromTask || !toTask) continue; const fromEnd = fromTask.endDate.getTime(); const toStart = toTask.startDate.getTime(); const lagMs = dep.lag * 24 * 60 * 60 * 1000; if (dep.type === 'finish-to-start' && fromEnd + lagMs > toStart) { conflicts.push(`Task "${toTask.name}" starts before "${fromTask.name}" finishes (dependency violation)`); } } return conflicts; }