Workflow Streamliner

Workflow Streamliner

Define New Stage

Current Workflow Stages (Drag to reorder - visual cue only, use buttons)

Add New Task

Current Workflow View

'; return; } workflowStages.sort((a,b) => a.order - b.order).forEach(stage => { const stageGroupDiv = document.createElement('div'); stageGroupDiv.className = 'workflow-stage-group'; const tasksInStage = workflowTasks.filter(t => t.stageId === stage.id); // Sort tasks within stage: Priority (High->Low), then DueDate (Earliest->Latest, nulls last) 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) return dateA - dateB; if (dateA) return -1; // dateA exists, dateB doesn't if (dateB) return 1; // dateB exists, dateA doesn't return 0; // Both null or equal after prio }); const stageHeader = document.createElement('div'); stageHeader.className = 'workflow-stage-header'; stageHeader.innerHTML = `

${stage.name}

${tasksInStage.length} task(s)`; stageGroupDiv.appendChild(stageHeader); const ul = document.createElement('ul'); ul.className = 'item-list'; if (tasksInStage.length === 0) { ul.innerHTML = '
  • No tasks in this stage.
  • '; } else { tasksInStage.forEach(task => { const li = document.createElement('li'); li.className = `workflow-task-item priority-${task.priority}`; const moveStageSelect = document.createElement('select'); moveStageSelect.className = 'task-stage-mover'; populateStageSelect(moveStageSelect); // Use the same populator moveStageSelect.value = task.stageId; moveStageSelect.addEventListener('change', (e) => moveTaskToStage(task.id, e.target.value)); li.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 })();
    Scroll to Top