Workflow Streamliner
Define Workflow Stages
Manage Tasks & View Workflow
Define New Stage
Current Workflow Stages (Drag to reorder - visual cue only, use buttons)
Add New Task
Current Workflow View
${stage.name}
${tasksInStage.length} task(s)`; stageGroupDiv.appendChild(stageHeader); const ul = document.createElement('ul'); ul.className = 'item-list'; if (tasksInStage.length === 0) { ul.innerHTML = '${task.description}
Assignee: ${task.assignee || 'N/A'} Due: ${formatDate(task.dueDate)} Prio: ${task.priority.charAt(0).toUpperCase() + task.priority.slice(1)}
${moveStageSelect.outerHTML}
`;
// Re-attach listener because innerHTML overwrites
li.querySelector('.task-stage-mover').addEventListener('change', (e) => moveTaskToStage(task.id, e.target.value));
li.querySelector('.edit-workflow-task-btn').addEventListener('click', (e) => populateWorkflowTaskFormForEdit(e.target.dataset.id));
li.querySelector('.delete-workflow-task-btn').addEventListener('click', (e) => deleteWorkflowTask(e.target.dataset.id));
ul.appendChild(li);
});
}
stageGroupDiv.appendChild(ul);
workflowBoardDiv.appendChild(stageGroupDiv);
});
}
// --- PDF Download ---
downloadWorkflowPdfBtn.addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'a4');
const margin = 40; let yPos = margin;
const pageWidth = doc.internal.pageSize.getWidth();
doc.setFontSize(18);
doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b);
doc.text('Workflow Status Report', pageWidth / 2, yPos, { align: 'center' });
yPos += 25;
doc.setFontSize(10);
doc.setTextColor(100);
doc.text(`Report Date: ${new Date().toLocaleString()}`, pageWidth / 2, yPos, { align: 'center' });
yPos += 30;
if (workflowStages.length === 0) {
doc.text('No workflow stages defined.', margin, yPos);
doc.save('Workflow_Report_NoStages.pdf');
return;
}
workflowStages.sort((a,b) => a.order - b.order).forEach(stage => {
const tasksInStage = workflowTasks.filter(t => t.stageId === stage.id);
// Sort tasks for PDF like on screen
const priorityOrderVal = { high: 1, medium: 2, low: 3 };
tasksInStage.sort((a, b) => {
if (priorityOrderVal[a.priority] !== priorityOrderVal[b.priority]) return priorityOrderVal[a.priority] - priorityOrderVal[b.priority];
const dateA = a.dueDate ? new Date(a.dueDate) : null; const dateB = b.dueDate ? new Date(b.dueDate) : null;
if (dateA && dateB) { if (dateA - dateB !== 0) return dateA - dateB; } else if (dateA) return -1; else if (dateB) return 1; return 0;
});
if (yPos > doc.internal.pageSize.getHeight() - 60 && tasksInStage.length > 0) { // Check before new section
doc.addPage(); yPos = margin;
}
doc.setFontSize(14);
doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b);
doc.text(`Stage: ${stage.name} (${tasksInStage.length} task(s))`, margin, yPos);
yPos += 20;
if (tasksInStage.length > 0) {
const body = tasksInStage.map(task => [
task.description,
task.priority.charAt(0).toUpperCase() + task.priority.slice(1),
task.assignee || 'N/A',
formatDate(task.dueDate)
]);
doc.autoTable({
startY: yPos,
head: [['Description', 'Priority', 'Assignee', 'Due Date']],
body: body,
theme: 'grid',
headStyles: { fillColor: varToRGB('--primary-color', true), textColor: varToRGB('--light-text', true) },
styles: { fontSize: 9 }
});
yPos = doc.lastAutoTable.finalY + 15;
} else {
doc.setFontSize(10); doc.setTextColor(100);
doc.text('No tasks in this stage.', margin + 5, yPos);
yPos += 15;
}
yPos += 5; // Gap after section
});
if (workflowTasks.length === 0 && workflowStages.length > 0) {
doc.text('No tasks found in any stage.', margin, yPos);
}
doc.save('Workflow_Streamliner_Report.pdf');
});
function varToRGB(varName, asArray = false) {
let colorHex = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
if (!colorHex.startsWith('#')) { colorHex = '#000000'; }
const r = parseInt(colorHex.slice(1, 3), 16);
const g = parseInt(colorHex.slice(3, 5), 16);
const b = parseInt(colorHex.slice(5, 7), 16);
return asArray ? [r,g,b] : {r,g,b};
}
// --- Initializations ---
if (workflowStages.length === 0) { // First time use, add default stages
workflowStages = JSON.parse(JSON.stringify(DEFAULT_STAGES)); // Deep copy defaults
saveStages();
}
renderWorkflowStagesList();
populateStageSelect(workflowTaskStageSelect); // For new task form
renderWorkflowBoard(); // Initial render of tasks on the board
})();
