`;
}
function renderTaskList() {
taskListContainer.innerHTML = tasks.map(task => `
`).join('');
}
function renderAnalytics() {
// KPIs
document.getElementById('total-tasks').textContent = tasks.length;
document.getElementById('tasks-in-progress').textContent = tasks.filter(t => t.status === 'In Progress').length;
const completedTasks = tasks.filter(t => t.status === 'Done');
document.getElementById('tasks-completed').textContent = completedTasks.length;
// Avg Completion Time
const completableTasks = completedTasks.filter(t => t.createdAt && t.completedAt);
if (completableTasks.length > 0) {
const totalTime = completableTasks.reduce((sum, task) => {
const start = new Date(task.createdAt);
const end = new Date(task.completedAt);
return sum + (end - start);
}, 0);
const avgTime = totalTime / completableTasks.length / (1000 * 60 * 60 * 24); // in days
document.getElementById('avg-completion-time').textContent = `${avgTime.toFixed(1)} days`;
} else {
document.getElementById('avg-completion-time').textContent = 'N/A';
}
// Charts
const statusCounts = statuses.map(s => tasks.filter(t => t.status === s).length);
const assignees = [...new Set(tasks.map(t => t.assignee))];
const assigneeCounts = assignees.map(a => tasks.filter(t => t.assignee === a).length);
if (statusChart) statusChart.destroy();
statusChart = new Chart(document.getElementById('status-chart'), {
type: 'doughnut',
data: { labels: statuses, datasets: [{ data: statusCounts, backgroundColor: ['#FBBF24', '#3B82F6', '#10B981'] }] },
options: { responsive: true }
});
if (assigneeChart) assigneeChart.destroy();
assigneeChart = new Chart(document.getElementById('assignee-chart'), {
type: 'bar',
data: { labels: assignees, datasets: [{ label: 'Tasks Assigned', data: assigneeCounts, backgroundColor: '#6366F1' }] },
options: { responsive: true, scales: { y: { beginAtZero: true } } }
});
}
// --- DRAG & DROP ---
function addDragAndDropListeners() {
const taskCards = document.querySelectorAll('.task-card');
const columns = document.querySelectorAll('.kanban-column');
let draggedItem = null;
taskCards.forEach(card => {
card.addEventListener('dragstart', () => {
draggedItem = card;
setTimeout(() => card.classList.add('dragging'), 0);
});
card.addEventListener('dragend', () => {
draggedItem.classList.remove('dragging');
draggedItem = null;
});
});
columns.forEach(column => {
column.addEventListener('dragover', e => {
e.preventDefault();
column.classList.add('drag-over');
});
column.addEventListener('dragleave', () => column.classList.remove('drag-over'));
column.addEventListener('drop', () => {
column.classList.remove('drag-over');
if (draggedItem) {
const taskId = parseInt(draggedItem.dataset.taskId);
const newStatus = column.dataset.status;
const task = tasks.find(t => t.id === taskId);
if (task) {
task.status = newStatus;
if (newStatus === 'Done' && !task.completedAt) {
task.completedAt = new Date().toISOString();
}
updateAll();
}
}
});
});
}
// --- EVENT HANDLERS ---
taskForm.addEventListener('submit', (e) => {
e.preventDefault();
const id = parseInt(editTaskIdInput.value);
const taskData = {
name: taskNameInput.value,
assignee: taskAssigneeInput.value,
priority: taskPriorityInput.value,
dueDate: taskDueDateInput.value,
};
if (id) { // Update
const index = tasks.findIndex(t => t.id === id);
if (index !== -1) tasks[index] = { ...tasks[index], ...taskData };
} else { // Add
taskData.id = Date.now();
taskData.status = 'To Do';
taskData.createdAt = new Date().toISOString();
tasks.push(taskData);
}
resetTaskForm();
updateAll();
showModal('Success', `Task has been ${id ? 'updated' : 'added'} successfully.`);
});
downloadPdfBtn.addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFontSize(18);
doc.text('Workflow Analytics Report', 14, 22);
doc.setFontSize(11);
doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 14, 30);
const kpis = [
['Total Tasks', document.getElementById('total-tasks').textContent],
['Tasks In Progress', document.getElementById('tasks-in-progress').textContent],
['Tasks Completed', document.getElementById('tasks-completed').textContent],
['Avg. Completion Time', document.getElementById('avg-completion-time').textContent]
];
doc.autoTable({ head: [['Metric', 'Value']], body: kpis, startY: 40 });
doc.addPage();
doc.text('Task Details', 14, 22);
const head = [['ID', 'Task Name', 'Assignee', 'Priority', 'Due Date', 'Status']];
const body = tasks.map(t => [t.id, t.name, t.assignee, t.priority, t.dueDate, t.status]);
doc.autoTable({ head, body, startY: 30 });
doc.save('workflow_report.pdf');
});
// --- HELPERS ---
function resetTaskForm() {
taskForm.reset();
editTaskIdInput.value = '';
taskFormTitle.textContent = 'Add New Task';
taskSubmitBtn.textContent = 'Add Task';
taskCancelEditBtn.classList.add('hidden');
}
function showModal(title, message) {
modalTitle.textContent = title;
modalMessage.textContent = message;
modal.classList.add('visible');
}
modalCloseBtn.addEventListener('click', () => modal.classList.remove('visible'));
// --- GLOBAL API ---
window.app = {
editTask: (id) => {
const task = tasks.find(t => t.id === id);
if (task) {
setActiveTab('config');
editTaskIdInput.value = task.id;
taskNameInput.value = task.name;
taskAssigneeInput.value = task.assignee;
taskPriorityInput.value = task.priority;
taskDueDateInput.value = task.dueDate;
taskFormTitle.textContent = 'Edit Task';
taskSubmitBtn.textContent = 'Update Task';
taskCancelEditBtn.classList.remove('hidden');
}
},
deleteTask: (id) => {
tasks = tasks.filter(t => t.id !== id);
updateAll();
}
};
// --- TAB NAVIGATION ---
function setActiveTab(tabId) {
currentTab = tabId;
tabButtons.forEach(btn => btn.classList.toggle('active', btn.dataset.tab === tabId));
tabPanes.forEach(pane => pane.style.display = pane.id === tabId ? 'block' : 'none');
const currentIndex = tabs.indexOf(currentTab);
prevTabBtn.disabled = currentIndex === 0;
nextTabBtn.disabled = currentIndex === tabs.length - 1;
}
tabButtons.forEach(button => button.addEventListener('click', () => setActiveTab(button.dataset.tab)));
prevTabBtn.addEventListener('click', () => {
const newIndex = tabs.indexOf(currentTab) - 1;
if (newIndex >= 0) setActiveTab(tabs[newIndex]);
});
nextTabBtn.addEventListener('click', () => {
const newIndex = tabs.indexOf(currentTab) + 1;
if (newIndex < tabs.length) setActiveTab(tabs[newIndex]);
});
taskCancelEditBtn.addEventListener('click', resetTaskForm);
// --- START ---
initialize();
});
${task.name} (${task.status})
Assigned to: ${task.assignee}
