Smart Workload Distribution Assistant

Smart Workload Distribution Assistant

Assign tasks intelligently based on team capacity and skills.

Team Workload Overview

Workload Distribution (Hours)

No team members configured. Please add team members in the configuration tab.

'; } workloadData.forEach(member => { const percentage = member.capacity > 0 ? (member.currentLoad / member.capacity) * 100 : 0; let barColor = 'bg-green-500'; if (percentage > 85) barColor = 'bg-red-500'; else if (percentage > 60) barColor = 'bg-yellow-500'; const taskItems = member.assignedTasks.map(t => `
  • ${t.desc} (${t.hours}h)
  • `).join(''); list.innerHTML += `
    ${member.name} ${member.currentLoad} / ${member.capacity} hrs
      ${taskItems || '
    • No tasks assigned.
    • '}
    `; }); renderChart(workloadData); }; const renderTeamConfig = () => { const list = document.getElementById('teamMemberList'); list.innerHTML = ''; team.forEach(member => { list.innerHTML += `

    ${member.name}

    Capacity: ${member.capacity} hrs/week

    Skills: ${member.skills.join(', ')}

    `; }); }; const renderChart = (workloadData) => { if (workloadChart) workloadChart.destroy(); const ctx = document.getElementById('workloadChart').getContext('2d'); workloadChart = new Chart(ctx, { type: 'bar', data: { labels: workloadData.map(m => m.name), datasets: [{ label: 'Current Workload (Hours)', data: workloadData.map(m => m.currentLoad), backgroundColor: '#3B82F6' }, { label: 'Capacity (Hours)', data: workloadData.map(m => m.capacity), backgroundColor: '#E5E7EB' }] }, options: { scales: { x: { stacked: false }, y: { stacked: false, beginAtZero: true } } } }); }; // --- MODAL & FORM LOGIC --- app.showTaskModal = () => { const select = document.getElementById('taskAssigneeSelect'); select.innerHTML = ''; team.forEach(m => select.innerHTML += ``); document.getElementById('taskModal').classList.remove('hidden'); }; app.showMemberModal = (id = null) => { const modal = document.getElementById('memberModal'); const title = document.getElementById('memberModalTitle'); const idInput = document.getElementById('memberIdInput'); const nameInput = document.getElementById('memberNameInput'); const capacityInput = document.getElementById('memberCapacityInput'); const skillsInput = document.getElementById('memberSkillsInput'); if (id) { const member = team.find(m => m.id === id); title.textContent = 'Edit Team Member'; idInput.value = member.id; nameInput.value = member.name; capacityInput.value = member.capacity; skillsInput.value = member.skills.join(', '); } else { title.textContent = 'Add Team Member'; idInput.value = ''; nameInput.value = ''; capacityInput.value = 40; skillsInput.value = ''; } modal.classList.remove('hidden'); }; app.hideModal = (modalId) => document.getElementById(modalId).classList.add('hidden'); // --- CORE LOGIC --- app.saveTask = () => { const desc = document.getElementById('taskDescInput').value; const hours = parseFloat(document.getElementById('taskHoursInput').value); const skill = document.getElementById('taskSkillInput').value.trim(); let assigneeId = parseInt(document.getElementById('taskAssigneeSelect').value); if (!desc || !hours) { alert('Description and hours are required.'); return; } if (!assigneeId) { // Auto-assign logic const availableMembers = team .map(member => ({ ...member, currentLoad: tasks.filter(t => t.assigneeId === member.id).reduce((sum, t) => sum + t.hours, 0) })) .filter(member => (member.currentLoad + hours) <= member.capacity && (!skill || member.skills.map(s => s.toLowerCase()).includes(skill.toLowerCase()))) .sort((a, b) => (a.currentLoad / a.capacity) - (b.currentLoad / b.capacity)); // Assign to least busy if (availableMembers.length > 0) { assigneeId = availableMembers[0].id; } else { alert('No suitable team member found with available capacity or required skill. Please assign manually or adjust task.'); return; } } tasks.push({ id: Date.now(), desc, hours, skill, assigneeId }); saveData(); renderWorkloadDashboard(); app.hideModal('taskModal'); }; app.saveMember = () => { const id = document.getElementById('memberIdInput').value; const name = document.getElementById('memberNameInput').value; if (!name.trim()) { alert('Name is required.'); return; } const memberData = { name, capacity: parseInt(document.getElementById('memberCapacityInput').value) || 40, skills: document.getElementById('memberSkillsInput').value.split(',').map(s => s.trim()).filter(Boolean) }; if (id) { const member = team.find(m => m.id == id); Object.assign(member, memberData); } else { team.push({ id: Date.now(), ...memberData }); } saveData(); renderTeamConfig(); app.hideModal('memberModal'); }; app.deleteMember = (id) => { if (confirm('Are you sure? This will unassign all their tasks.')) { team = team.filter(m => m.id !== id); tasks.forEach(task => { if (task.assigneeId === id) task.assigneeId = null; }); saveData(); renderTeamConfig(); renderWorkloadDashboard(); } }; // --- UTILS & HELPERS --- app.changeTab = (tabIndex) => { document.querySelectorAll('[id^="tabContent-"]').forEach(el => el.classList.add('hidden')); document.querySelectorAll('[id^="tab-"]').forEach(el => el.classList.replace('tab-active', 'tab-inactive')); document.getElementById(`tabContent-${tabIndex}`).classList.remove('hidden'); document.getElementById(`tab-${tabIndex}`).classList.replace('tab-inactive', 'tab-active'); }; app.downloadReportPDF = () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); doc.setFontSize(20); doc.text('Team Workload Report', 105, 15, { align: 'center' }); const head = [['Team Member', 'Skills', 'Capacity (h)', 'Current Load (h)', 'Utilization']]; const body = team.map(member => { const currentLoad = tasks.filter(t => t.assigneeId === member.id).reduce((sum, t) => sum + t.hours, 0); const utilization = member.capacity > 0 ? `${((currentLoad / member.capacity) * 100).toFixed(1)}%` : 'N/A'; return [member.name, member.skills.join(', '), member.capacity, currentLoad, utilization]; }); doc.autoTable({ head, body, startY: 25 }); doc.save('workload_report.pdf'); }; // --- INITIALIZATION --- loadData(); renderWorkloadDashboard(); renderTeamConfig(); });
    Scroll to Top