Project Timeline Planner Generator

Project Timeline Planner Generator

Define the major phases of your project. The order here will define the project timeline flow.

Current Project Phases (Editable)

Phase Order Phase Name Duration (Days) Actions

Add detailed tasks under each phase. Task durations are summed up to contribute to the phase's duration.

Tasks Overview (Editable)

Phase Task Name Duration (Days) Actions

Review the final calculated project timeline plan.

Your calculated plan preview will appear here.

Project Manager: ${escapeHTML(projectData.manager)}

Start Date: ${escapeHTML(projectData.startDate)}

Total Estimated Duration: ${totalDays} working days (approx. ${totalWeeks} weeks)

Phase Breakdown

${timeline.map(p => ` `).join('')}
# Phase Name Start Day End Day Duration (Days)
${p.order} ${escapeHTML(p.name)} Day ${p.startDay} Day ${p.endDay} ${p.durationDays}

Detailed Task List

    ${timeline.map(p => `
  • Phase ${p.order}: ${escapeHTML(p.name)} (${p.durationDays} Days)

      ${p.tasks.length > 0 ? p.tasks.map(t => `
    • ${escapeHTML(t.name)} (${t.duration} days)
    • `).join('') : '
    • No specific tasks defined. Using default phase duration.
    • '}
  • `).join('')}
`; }; const getTableDataForPDF = (timeline) => { const head = [['#', 'Phase Name', 'Start Day', 'End Day', 'Duration (Days)', 'Tasks']]; const body = timeline.map(p => [ p.order, p.name, `Day ${p.startDay}`, `Day ${p.endDay}`, p.durationDays, p.tasks.map(t => `${t.name} (${t.duration} days)`).join('\n') || 'N/A' ]); return { head, body }; }; const downloadPDF = () => { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { showMessage(messageBoxTask, 'Error: jsPDF library not loaded.', 'error'); return; } const { timeline, totalDays } = calculateTimeline(); const { head, body } = getTableDataForPDF(timeline); const doc = new jsPDF('l', 'mm', 'a4'); // Landscape for better table fit const pageWidth = doc.internal.pageSize.getWidth(); const totalWeeks = Math.ceil(totalDays / 5); // 1. Title doc.setFontSize(22); doc.setFont(undefined, 'bold'); doc.setTextColor(44, 62, 80); doc.text(`Project Timeline: ${projectData.name || 'Untitled Project'}`, pageWidth / 2, 15, { align: 'center' }); // 2. Overview doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(52, 73, 94); let yPos = 25; doc.text(`Manager: ${projectData.manager || 'N/A'}`, 15, yPos); doc.text(`Start Date: ${projectData.startDate || 'N/A'}`, 15, yPos + 5); doc.text(`Total Duration: ${totalDays} days (approx. ${totalWeeks} weeks)`, 15, yPos + 10); yPos += 20; // 3. Table doc.autoTable({ head: head, body: body, startY: yPos, theme: 'grid', headStyles: { fillColor: [0, 123, 255], textColor: [255, 255, 255], fontSize: 10, fontStyle: 'bold' }, styles: { fontSize: 9, cellPadding: 2, valign: 'top', lineColor: [200, 200, 200], lineWidth: 0.1 }, columnStyles: { 0: { cellWidth: 10 }, 1: { cellWidth: 40, fontStyle: 'bold' }, 2: { cellWidth: 20 }, 3: { cellWidth: 20 }, 4: { cellWidth: 25 }, 5: { cellWidth: 'auto' } } }); doc.save('project_timeline.pdf'); }; const downloadTxt = () => { const { timeline, totalDays } = calculateTimeline(); const totalWeeks = Math.ceil(totalDays / 5); let content = `PROJECT TIMELINE PLAN\n`; content += `========================================\n\n`; content += `Project: ${projectData.name || 'Untitled Project'}\n`; content += `Manager: ${projectData.manager || 'N/A'}\n`; content += `Start Date: ${projectData.startDate || 'N/A'}\n`; content += `Total Duration: ${totalDays} working days (approx. ${totalWeeks} weeks)\n\n`; content += `PHASE BREAKDOWN\n`; content += `----------------------------------------\n`; timeline.forEach(p => { content += `\nPHASE ${p.order}: ${p.name.toUpperCase()} (Days ${p.startDay} - ${p.endDay} | ${p.durationDays} Days)\n`; if (p.tasks.length > 0) { p.tasks.forEach(t => { content += ` - ${t.name} (${t.duration} days)\n`; }); } else { content += ` - No specific tasks defined.\n`; } }); const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `timeline_${projectData.name.replace(/ /g, '_') || 'plan'}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }; // --- Event Listeners --- // Tab Buttons tabButtons.forEach((btn, index) => { btn.addEventListener('click', () => showTab(index + 1)); }); // Next/Prev Navigation nextBtn.addEventListener('click', () => showTab(currentTab + 1)); prevBtn.addEventListener('click', () => showTab(currentTab - 1)); // Tab 2 Actions (Phases) addPhaseBtn.addEventListener('click', () => addPhase()); // Call with no arguments for manual input phaseTbody.addEventListener('click', (e) => { if (e.target.dataset.removePhaseId) { removePhase(parseInt(e.target.dataset.removePhaseId)); } }); phaseTbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; updatePhase(id, field, e.target.textContent); } }, true); // Tab 3 Actions (Tasks) addTaskBtn.addEventListener('click', addTask); taskTbody.addEventListener('click', (e) => { if (e.target.dataset.removeTaskId) { const phaseId = parseInt(e.target.dataset.removePhaseId); const taskId = parseInt(e.target.dataset.removeTaskId); removeTask(phaseId, taskId); } }); taskTbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const phaseId = parseInt(e.target.dataset.phaseId); const taskId = parseInt(e.target.dataset.taskId); const field = e.target.dataset.field; updateTask(phaseId, taskId, field, e.target.textContent); } }, true); // Tab 4 Actions downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- // Pre-populate with sample data projectData.name = "Q4 E-commerce Redesign"; projectData.manager = "Jane Doe"; projectData.startDate = "2025-11-01"; // Use the addPhase function but pass the initialization data as an argument object addPhase({ name: "Discovery & UX", duration: 15, tasks: [] }); addPhase({ name: "Development & Integration", duration: 30, tasks: [] }); addPhase({ name: "UAT & Launch Prep", duration: 10, tasks: [] }); // The phase IDs will be 0, 1, 2 due to phaseIdCounter incrementing inside addPhase // Update sample tasks (Note: this is an imperfect way to pass nested data, but it preserves functionality) // Correcting the initial assignment of task data based on the original logic projectData.phases[0].tasks = [ { id: taskIdCounter++, name: "Stakeholder Interviews", duration: 3 }, { id: taskIdCounter++, name: "Finalize Wireframes", duration: 7 } ]; projectData.phases[1].tasks = [ { id: taskIdCounter++, name: "Front-end coding", duration: 15 }, { id: taskIdCounter++, name: "Backend API implementation", duration: 10 }, { id: taskIdCounter++, name: "Payment gateway integration", duration: 5 } ]; projectNameInput.value = projectData.name; projectManagerInput.value = projectData.manager; projectStartInput.value = projectData.startDate; // Initial render calls renderPhases(); renderTasks(); showTab(1); // Set initial state });
Scroll to Top