// Data const teamMembers = [ { id: 1, name: 'Alex Morgan', role: 'Product Owner', avatar: 'http://static.photos/people/200x200/42' }, { id: 2, name: 'Sarah Chen', role: 'UI Designer', avatar: 'http://static.photos/people/200x200/45' }, { id: 3, name: 'Mike Johnson', role: 'Frontend Dev', avatar: 'http://static.photos/people/200x200/48' }, { id: 4, name: 'Emma Wilson', role: 'UX Researcher', avatar: 'http://static.photos/people/200x200/52' } ]; // Sprint Planning Data let projectGoals = [ { id: 1, title: 'Complete Design System v2', description: 'Finalize all components and documentation', priority: 'high' }, { id: 2, title: 'Mobile App MVP', description: 'Launch iOS and Android MVP versions', priority: 'high' }, { id: 3, title: 'Performance Optimization', description: 'Improve load times by 50%', priority: 'medium' } ]; let backlogTasks = [ { id: 101, title: 'Design button component library', points: 5, goalId: 1, type: 'design', status: 'backlog' }, { id: 102, title: 'Create icon system', points: 3, goalId: 1, type: 'design', status: 'backlog' }, { id: 103, title: 'Setup React Native project', points: 8, goalId: 2, type: 'dev', status: 'backlog' }, { id: 104, title: 'Implement navigation', points: 5, goalId: 2, type: 'dev', status: 'backlog' }, { id: 105, title: 'User authentication flow', points: 8, goalId: 2, type: 'dev', status: 'backlog' }, { id: 106, title: 'Optimize image loading', points: 5, goalId: 3, type: 'dev', status: 'backlog' }, { id: 107, title: 'Database query optimization', points: 8, goalId: 3, type: 'dev', status: 'backlog' }, { id: 108, title: 'Research competitor apps', points: 3, goalId: 2, type: 'research', status: 'backlog' }, { id: 109, title: 'Write documentation', points: 3, goalId: 1, type: 'dev', status: 'backlog' }, { id: 110, title: 'Setup CI/CD pipeline', points: 5, goalId: 2, type: 'dev', status: 'backlog' } ]; let generatedSprintsData = []; let tasks = [ { id: 1, title: 'Design system documentation', type: 'design', priority: 'high', status: 'todo', assignee: 2, tags: ['Design', 'Docs'], comments: 3 }, { id: 2, title: 'Fix navigation responsiveness', type: 'bug', priority: 'high', status: 'progress', assignee: 3, tags: ['Bug', 'Mobile'], comments: 1 }, { id: 3, title: 'User interview synthesis', type: 'research', priority: 'medium', status: 'review', assignee: 4, tags: ['Research'], comments: 5 }, { id: 4, title: 'API integration dashboard', type: 'dev', priority: 'high', status: 'progress', assignee: 3, tags: ['Backend'], comments: 2 }, { id: 5, title: 'Color palette refinement', type: 'design', priority: 'low', status: 'done', assignee: 2, tags: ['Design'], comments: 0 }, { id: 6, title: 'Accessibility audit', type: 'research', priority: 'medium', status: 'todo', assignee: 4, tags: ['A11y'], comments: 4 }, { id: 7, title: 'Component library setup', type: 'dev', priority: 'high', status: 'done', assignee: 3, tags: ['Frontend'], comments: 8 }, { id: 8, title: 'Landing page animations', type: 'design', priority: 'medium', status: 'review', assignee: 2, tags: ['Animation'], comments: 2 }, { id: 9, title: 'Database schema design', type: 'dev', priority: 'high', status: 'todo', assignee: 3, tags: ['Backend'], comments: 1 }, { id: 10, title: 'Mobile app wireframes', type: 'design', priority: 'medium', status: 'done', assignee: 2, tags: ['Mobile'], comments: 3 }, { id: 11, title: 'Performance optimization', type: 'dev', priority: 'low', status: 'done', assignee: 3, tags: ['Performance'], comments: 0 } ]; const activities = [ { user: 2, action: 'moved', target: 'Design system', from: 'In Progress', to: 'Review', time: '2 min ago' }, { user: 3, action: 'completed', target: 'API Integration', time: '1 hour ago' }, { user: 4, action: 'commented on', target: 'User Research', time: '3 hours ago' }, { user: 1, action: 'created', target: 'Sprint 24 Planning', time: '5 hours ago' }, { user: 2, action: 'uploaded', target: '3 new mockups', time: 'Yesterday' } ]; let selectedAssignee = null; // Initialize document.addEventListener('DOMContentLoaded', () => { lucide.createIcons(); renderBoard(); renderActivity(); initChart(); renderAssignees(); initSprintPlanner(); // Mobile menu toggle document.getElementById('mobile-menu-btn').addEventListener('click', () => { const sidebar = document.getElementById('sidebar'); sidebar.classList.toggle('hidden'); }); // Form submission document.getElementById('newTaskForm').addEventListener('submit', handleNewTask); document.getElementById('goalForm').addEventListener('submit', handleNewGoal); document.getElementById('backlogForm').addEventListener('submit', handleNewBacklogTask); // Capacity calculation listeners document.getElementById('teamVelocity')?.addEventListener('input', updateCapacity); document.getElementById('bufferPercent')?.addEventListener('input', updateCapacity); }); // View Navigation function showSprintPlanner() { document.querySelector('main:not(#sprintPlannerView)').classList.add('hidden'); document.getElementById('sprintPlannerView').classList.remove('hidden'); document.getElementById('sprintPlannerView').classList.add('block'); renderGoals(); renderBacklog(); updateCapacity(); lucide.createIcons(); } function showBoard() { document.querySelector('main:not(#sprintPlannerView)').classList.remove('hidden'); document.getElementById('sprintPlannerView').classList.add('hidden'); document.getElementById('sprintPlannerView').classList.remove('block'); lucide.createIcons(); } // Sprint Planning Functions function initSprintPlanner() { updateGoalSelect(); } function updateCapacity() { const velocity = parseInt(document.getElementById('teamVelocity')?.value || 40); const buffer = parseInt(document.getElementById('bufferPercent')?.value || 20); const effective = Math.floor(velocity * (1 - buffer / 100)); document.getElementById('effectiveCapacity').textContent = effective + ' pts'; const totalPoints = backlogTasks.reduce((sum, task) => sum + task.points, 0); document.getElementById('totalBacklogPoints').textContent = totalPoints + ' pts'; } function renderGoals() { const container = document.getElementById('goalsList'); if (!container) return; container.innerHTML = projectGoals.map(goal => `

${goal.title}

${goal.description}

${backlogTasks.filter(t => t.goalId === goal.id).reduce((s, t) => s + t.points, 0)} pts ${backlogTasks.filter(t => t.goalId === goal.id).length} tasks
`).join(''); lucide.createIcons(); } function renderBacklog() { const container = document.getElementById('backlogTasks'); if (!container) return; container.innerHTML = backlogTasks.map(task => { const goal = projectGoals.find(g => g.id === task.goalId); return `

${task.title}

${task.points} pts ${goal ? `${goal.title}` : ''}
`}).join(''); lucide.createIcons(); updateCapacity(); updateGoalSelect(); } function getPriorityDot(priority) { const colors = { high: 'bg-red-500', medium: 'bg-orange-500', low: 'bg-green-500' }; return colors[priority] || 'bg-gray-500'; } function getTypeColor(type) { const colors = { design: 'bg-pink-100 text-pink-600', dev: 'bg-blue-100 text-blue-600', research: 'bg-purple-100 text-purple-600', testing: 'bg-green-100 text-green-600' }; return colors[type] || 'bg-gray-100 text-gray-600'; } function updateGoalSelect() { const select = document.getElementById('backlogGoal'); if (!select) return; select.innerHTML = '' + projectGoals.map(g => ``).join(''); } // Modals function addGoal() { document.getElementById('goalModal').classList.remove('hidden'); } function closeGoalModal() { document.getElementById('goalModal').classList.add('hidden'); document.getElementById('goalForm').reset(); } function addBacklogTask() { document.getElementById('backlogModal').classList.remove('hidden'); } function closeBacklogModal() { document.getElementById('backlogModal').classList.add('hidden'); document.getElementById('backlogForm').reset(); } function handleNewGoal(e) { e.preventDefault(); const newGoal = { id: Date.now(), title: document.getElementById('goalTitle').value, description: document.getElementById('goalDesc').value, priority: document.getElementById('goalPriority').value }; projectGoals.push(newGoal); renderGoals(); closeGoalModal(); updateGoalSelect(); } function handleNewBacklogTask(e) { e.preventDefault(); const newTask = { id: Date.now(), title: document.getElementById('backlogTitle').value, points: parseInt(document.getElementById('backlogPoints').value), goalId: parseInt(document.getElementById('backlogGoal').value) || null, type: document.getElementById('backlogType').value, status: 'backlog' }; backlogTasks.push(newTask); renderBacklog(); closeBacklogModal(); } function deleteGoal(id) { projectGoals = projectGoals.filter(g => g.id !== id); backlogTasks = backlogTasks.filter(t => t.goalId !== id); renderGoals(); renderBacklog(); } function deleteBacklogTask(id) { backlogTasks = backlogTasks.filter(t => t.id !== id); renderBacklog(); } // Sprint Generation Algorithm function generateSprints() { const velocity = parseInt(document.getElementById('teamVelocity').value); const buffer = parseInt(document.getElementById('bufferPercent').value); const effectiveCapacity = Math.floor(velocity * (1 - buffer / 100)); const duration = parseInt(document.getElementById('sprintDuration').value); // Sort tasks by priority (high priority goals first) and points const sortedTasks = [...backlogTasks].sort((a, b) => { const goalA = projectGoals.find(g => g.id === a.goalId); const goalB = projectGoals.find(g => g.id === b.goalId); const priorityWeight = { high: 3, medium: 2, low: 1 }; const priorityDiff = (priorityWeight[goalB?.priority] || 0) - (priorityWeight[goalA?.priority] || 0); if (priorityDiff !== 0) return priorityDiff; return b.points - a.points; // Larger tasks first for better packing }); const sprints = []; let currentSprint = { number: 1, tasks: [], totalPoints: 0, goals: new Set() }; let unassignedTasks = []; sortedTasks.forEach(task => { if (currentSprint.totalPoints + task.points <= effectiveCapacity) { currentSprint.tasks.push(task); currentSprint.totalPoints += task.points; if (task.goalId) currentSprint.goals.add(task.goalId); } else { // Check if any remaining tasks can fit const remainingSpace = effectiveCapacity - currentSprint.totalPoints; const fittingTask = sortedTasks.find(t => !currentSprint.tasks.includes(t) && !unassignedTasks.includes(t) && t.points <= remainingSpace && t.id !== task.id ); if (fittingTask && fittingTask.id !== task.id) { currentSprint.tasks.push(fittingTask); currentSprint.totalPoints += fittingTask.points; if (fittingTask.goalId) currentSprint.goals.add(fittingTask.goalId); unassignedTasks.push(task); } else { sprints.push(currentSprint); currentSprint = { number: sprints.length + 1, tasks: [task], totalPoints: task.points, goals: new Set(task.goalId ? [task.goalId] : []) }; } } }); if (currentSprint.tasks.length > 0) { sprints.push(currentSprint); } // Add unassigned tasks to new sprints if any unassignedTasks.forEach(task => { const lastSprint = sprints[sprints.length - 1]; if (lastSprint && lastSprint.totalPoints + task.points <= effectiveCapacity) { lastSprint.tasks.push(task); lastSprint.totalPoints += task.points; if (task.goalId) lastSprint.goals.add(task.goalId); } else { sprints.push({ number: sprints.length + 1, tasks: [task], totalPoints: task.points, goals: new Set(task.goalId ? [task.goalId] : []) }); } }); generatedSprintsData = sprints; renderGeneratedSprints(sprints, effectiveCapacity, duration); } function renderGeneratedSprints(sprints, capacity, duration) { const container = document.getElementById('sprintsPreview'); const wrapper = document.getElementById('generatedSprints'); if (sprints.length === 0) { container.innerHTML = '

No tasks to generate sprints from

'; } else { container.innerHTML = sprints.map(sprint => { const goalNames = Array.from(sprint.goals).map(gid => { const goal = projectGoals.find(g => g.id === gid); return goal ? goal.title : ''; }).filter(Boolean); const utilization = Math.round((sprint.totalPoints / capacity) * 100); const utilizationColor = utilization > 90 ? 'text-green-600 bg-green-50' : utilization > 70 ? 'text-blue-600 bg-blue-50' : 'text-orange-600 bg-orange-50'; return `

Sprint ${sprint.number}

${duration} weeks • ${sprint.tasks.length} tasks

${sprint.totalPoints}/${capacity} pts
${sprint.tasks.slice(0, 3).map(t => `
${t.title} ${t.points}p
`).join('')} ${sprint.tasks.length > 3 ? `

+${sprint.tasks.length - 3} more tasks

` : ''}
${goalNames.length > 0 ? `
${goalNames.map(g => `${g}`).join('')}
` : ''}
`; }).join(''); } wrapper.classList.remove('hidden'); lucide.createIcons(); } function applyGeneratedSprints() { if (generatedSprintsData.length === 0) return; // Transform generated sprints into actual sprints const startDate = new Date(); generatedSprintsData.forEach((sprint, index) => { const sprintName = `Sprint ${25 + index}`; const sprintTasks = sprint.tasks.map(t => ({ id: tasks.length + 1, title: t.title, type: t.type, priority: 'medium', status: index === 0 ? 'todo' : 'backlog', assignee: teamMembers[index % teamMembers.length].id, tags: [t.type.charAt(0).toUpperCase() + t.type.slice(1)], comments: 0, storyPoints: t.points, sprintName: sprintName })); tasks.push(...sprintTasks); }); // Clear backlog backlogTasks = []; renderBacklog(); // Show board showBoard(); renderBoard(); // Show success message alert(`Successfully created ${generatedSprintsData.length} sprints with ${tasks.length} tasks!`); } // Close modals on outside click document.getElementById('goalModal')?.addEventListener('click', (e) => { if (e.target === e.currentTarget) closeGoalModal(); }); document.getElementById('backlogModal')?.addEventListener('click', (e) => { if (e.target === e.currentTarget) closeBacklogModal(); }); function getPriorityColor(priority) { const colors = { high: 'bg-red-100 text-red-700 border-red-200', medium: 'bg-orange-100 text-orange-700 border-orange-200', low: 'bg-green-100 text-green-700 border-green-200' }; return colors[priority] || colors.medium; } function getTypeIcon(type) { const icons = { design: 'palette', dev: 'code', bug: 'bug', research: 'search' }; return icons[type] || 'circle'; } function createTaskCard(task) { const assignee = teamMembers.find(m => m.id === task.assignee); const priorityClass = getPriorityColor(task.priority); return `
${task.priority}

${task.title}

${task.type}
${assignee.name}
${task.tags.map(tag => `${tag}`).join('')}
${task.comments}
`; } function deleteTask(taskId) { if (confirm('Are you sure you want to delete this task?')) { const taskIndex = tasks.findIndex(t => t.id === taskId); if (taskIndex > -1) { const deletedTask = tasks[taskIndex]; tasks.splice(taskIndex, 1); renderBoard(); // Add to activity activities.unshift({ user: teamMembers[0].id, action: 'deleted', target: deletedTask.title, time: 'Just now' }); renderActivity(); } } } function renderBoard() { const columns = { todo: document.getElementById('todo-column'), progress: document.getElementById('progress-column'), review: document.getElementById('review-column'), done: document.getElementById('done-column') }; const counts = { todo: 0, progress: 0, review: 0, done: 0 }; // Clear columns Object.values(columns).forEach(col => col.innerHTML = ''); // Populate columns tasks.forEach(task => { if (columns[task.status]) { columns[task.status].innerHTML += createTaskCard(task); counts[task.status]++; } }); // Update counts document.getElementById('count-todo').textContent = counts.todo; document.getElementById('count-progress').textContent = counts.progress; document.getElementById('count-review').textContent = counts.review; document.getElementById('count-done').textContent = counts.done; lucide.createIcons(); } function renderActivity() { const container = document.getElementById('activity-feed'); container.innerHTML = activities.map(activity => { const user = teamMembers.find(m => m.id === activity.user); return `

${user.name} ${activity.action} ${activity.target}

${activity.time}

`; }).join(''); } function renderAssignees() { const container = document.getElementById('assigneeList'); container.innerHTML = teamMembers.map(member => `

${member.name.split(' ')[0]}

`).join(''); } function selectAssignee(id) { selectedAssignee = id; renderAssignees(); } function initChart() { const ctx = document.getElementById('burndownChart').getContext('2d'); const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri']; const ideal = [32, 29, 26, 23, 20, 17, 14, 11, 8, 5, 2]; const actual = [32, 30, 28, 25, 24, 20, 18, 15, 12, 8]; new Chart(ctx, { type: 'line', data: { labels: days, datasets: [ { label: 'Ideal', data: ideal, borderColor: '#e5e7eb', borderDash: [5, 5], tension: 0.4, pointRadius: 0, borderWidth: 2 }, { label: 'Actual', data: actual, borderColor: '#6366f1', backgroundColor: 'rgba(99, 102, 241, 0.1)', tension: 0.4, fill: true, pointBackgroundColor: '#6366f1', pointBorderColor: '#fff', pointBorderWidth: 2, pointRadius: 4, borderWidth: 3 } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top', align: 'end', labels: { usePointStyle: true, boxWidth: 8 } } }, scales: { y: { beginAtZero: true, grid: { color: '#f3f4f6' }, ticks: { color: '#9ca3af', font: { size: 11 } } }, x: { grid: { display: false }, ticks: { color: '#9ca3af', font: { size: 11 } } } } } }); } // Drag and Drop function allowDrop(ev) { ev.preventDefault(); } function drag(ev) { ev.dataTransfer.setData("taskId", ev.target.getAttribute('data-task-id')); ev.target.classList.add('dragging'); } function drop(ev) { ev.preventDefault(); const taskId = parseInt(ev.dataTransfer.getData("taskId")); const column = ev.target.closest('.kanban-column'); if (column) { const newStatus = column.getAttribute('data-status'); const task = tasks.find(t => t.id === taskId); if (task && task.status !== newStatus) { task.status = newStatus; renderBoard(); // Add to activity const user = teamMembers[0]; // Current user activities.unshift({ user: user.id, action: 'moved', target: task.title, from: task.status, to: newStatus, time: 'Just now' }); renderActivity(); } } document.querySelectorAll('.task-card').forEach(card => { card.classList.remove('dragging'); }); } // Modal Functions function openTaskModal() { document.getElementById('taskModal').classList.remove('hidden'); selectedAssignee = null; renderAssignees(); } function closeTaskModal() { document.getElementById('taskModal').classList.add('hidden'); document.getElementById('newTaskForm').reset(); } function handleNewTask(e) { e.preventDefault(); const title = document.getElementById('taskTitle').value; const type = document.getElementById('taskType').value; const priority = document.getElementById('taskPriority').value; if (!selectedAssignee) { alert('Please select an assignee'); return; } const newTask = { id: tasks.length + 1, title: title, type: type, priority: priority, status: 'todo', assignee: selectedAssignee, tags: [type.charAt(0).toUpperCase() + type.slice(1)], comments: 0 }; tasks.push(newTask); renderBoard(); closeTaskModal(); // Add activity activities.unshift({ user: selectedAssignee, action: 'created', target: title, time: 'Just now' }); renderActivity(); } // Close modal on outside click document.getElementById('taskModal').addEventListener('click', (e) => { if (e.target === e.currentTarget) { closeTaskModal(); } });