Research Timeline Generator

Research Timeline Generator

1. Project Setup
2. Phase Management
IDPhase NameDescriptionAction
3. Task Management
TaskPhaseDurationMilestoneAction
4. Define Task Dependencies

Select a **preceding** task that must be completed before the **dependent** task can start. All tasks without dependencies will start on the Project Start Date.

Dependent TaskPreceding TaskAction

Click "Calculate Timeline" to generate the final schedule.

Total Days from Start: ${data.totalDaysFromStart || 'N/A'}

`; reviewArea.innerHTML = html; pdfDownloadBtn.disabled = false; } const getFormDataReview = (scheduledTasks) => { const projectTitle = document.getElementById('project-name').value; const projectStart = projectStartDateInput.value; let timelineData = []; let projectEnd = parseDate(projectStart); let maxEndDate = projectEnd; // Sort tasks by phase order, then by calculated start date scheduledTasks.sort((a, b) => { const phaseA = phases.findIndex(p => p.id === a.phaseId); const phaseB = phases.findIndex(p => p.id === b.phaseId); if (phaseA !== phaseB) return phaseA - phaseB; return parseDate(a.startDate) - parseDate(b.startDate); }); let currentPhaseId = null; scheduledTasks.forEach(task => { if (task.phaseId !== currentPhaseId) { const phase = phases.find(p => p.id === task.phaseId); if (phase) { timelineData.push({ type: 'phase', name: phase.name }); currentPhaseId = phase.id; } } timelineData.push({ type: 'task', id: task.id, name: task.name, duration: task.duration, startDate: formatDate(task.startDate), endDate: formatDate(task.endDate), isMilestone: task.isMilestone }); // Calculate overall project end date if (task.endDate) { const taskEnd = parseDate(task.endDate); if (taskEnd > maxEndDate) { maxEndDate = taskEnd; } } }); let totalDays = null; if (maxEndDate && projectStart) { const start = parseDate(projectStart); totalDays = Math.ceil((maxEndDate - start) / (1000 * 60 * 60 * 24)) + 1; } return { projectName: projectTitle, startDate: projectStart, phases: phases, totalTasks: scheduledTasks.length, timeline: timelineData, calculatedEndDate: formatDate(maxEndDate), totalDaysFromStart: totalDays }; }; /** * PDF Generation Function */ function downloadPDF() { const scheduledTasks = tasks.map(task => ({ ...task, startDate: task.startDate, endDate: task.endDate, duration: parseInt(task.duration, 10) })); const data = getFormDataReview(scheduledTasks); const { jsPDF } = window.jspdf; const doc = new jsPDF('l', 'pt', 'a4'); // Landscape for timeline let currentY = 40; const margin = 40; const pageWidth = doc.internal.pageSize.width; const maxWidth = pageWidth - (margin * 2); const checkPageBreak = (spaceNeeded) => { if (currentY + spaceNeeded > doc.internal.pageSize.height - margin) { doc.addPage(); currentY = margin; } }; // --- PDF Content --- // Title Block doc.setFontSize(22); doc.setFont('Helvetica', 'bold'); doc.setTextColor(0, 123, 255); // Primary color doc.text("Research Timeline Schedule", pageWidth / 2, currentY, { align: 'center' }); currentY += 15; doc.setFontSize(14); doc.setFont('Helvetica', 'normal'); doc.setTextColor(100); doc.text(`Project: ${data.projectName}`, pageWidth / 2, currentY, { align: 'center' }); currentY += 25; doc.setTextColor(0); // Summary Table const summaryHead = [["Project Start", "Calculated End", "Total Duration", "Total Tasks"]]; const summaryBody = [[ data.startDate, data.calculatedEndDate, `${data.totalDaysFromStart} days`, data.totalTasks ]]; doc.autoTable({ startY: currentY, head: summaryHead, body: summaryBody, theme: 'grid', headStyles: { fillColor: [230, 230, 230], textColor: [0] }, styles: { fontSize: 10, cellPadding: 5, font: 'Helvetica' } }); currentY = doc.autoTable.previous.finalY + 20; // Detailed Timeline Table addText = (text, size = 12, style = 'normal') => { doc.setFontSize(size); doc.setFont('Helvetica', style); doc.text(text, margin, currentY); currentY += size * 1.5; }; const timelineTableHead = [["Task ID", "Task Name", "Phase", "Duration (Days)", "Start Date", "End Date", "Milestone"]]; const timelineTableBody = []; data.timeline.forEach(item => { if (item.type === 'phase') { timelineTableBody.push([{ content: item.name, colSpan: 7, styles: { fontStyle: 'bold', fillColor: [227, 242, 253] } }]); // light blue } else if (item.type === 'task') { const phase = phases.find(p => p.id === item.id.substring(0, 2)); timelineTableBody.push([ item.id, item.name, phase ? phase.name : 'N/A', item.duration, item.startDate, item.endDate, item.isMilestone === 'true' ? 'Yes' : 'No' ]); } }); doc.autoTable({ startY: currentY, head: timelineTableHead, body: timelineTableBody, theme: 'grid', headStyles: { fillColor: [180, 180, 180], textColor: [0] }, styles: { fontSize: 9, cellPadding: 4, font: 'Helvetica' } }); currentY = doc.autoTable.previous.finalY + 10; doc.save(`${data.projectName.replace(/\s/g, '_') || 'Research'}_Timeline.pdf`); } // --- Event Listeners --- calculateBtn.addEventListener('click', () => { calculateTimeline(); switchTab(2); // Switch to review tab (index 2) }); pdfDownloadBtn.addEventListener('click', downloadPDF); // --- Tab Navigation --- function switchTab(tabIndex) { tabs.forEach((tab, index) => { tab.classList.toggle('active', index === tabIndex); contents[index].classList.toggle('active', index === tabIndex); }); currentTab = tabIndex; updateNavButtons(); } function updateNavButtons() { prevBtn.disabled = currentTab === 0; nextBtn.disabled = currentTab === tabs.length - 1; } tabs.forEach((tab, index) => { tab.addEventListener('click', () => switchTab(index)); }); nextBtn.addEventListener('click', () => { if (currentTab < tabs.length - 1) switchTab(currentTab + 1); }); prevBtn.addEventListener('click', () => { if (currentTab > 0) switchTab(currentTab - 1); }); // --- Initial Data Load --- function loadInitialData() { // Sample Phases phases = [ { id: 'P1', name: 'Phase 1: Planning', description: 'Define scope, secure funding, and initial literature review.' }, { id: 'P2', name: 'Phase 2: Execution', description: 'Conduct experiments and collect data.' }, { id: 'P3', name: 'Phase 3: Analysis & Write-up', description: 'Process data, write thesis, and submit.' } ]; // Sample Tasks tasks = [ { id: 'T1', name: 'Define Research Questions', phaseId: 'P1', duration: '5', isMilestone: 'false' }, { id: 'T2', name: 'Literature Review Complete', phaseId: 'P1', duration: '10', isMilestone: 'true' }, { id: 'T3', name: 'Secure Lab Access', phaseId: 'P1', duration: '2', isMilestone: 'false' }, { id: 'T4', name: 'Run Experiment Set A', phaseId: 'P2', duration: '15', isMilestone: 'false' }, { id: 'T5', name: 'Data Collection Complete', phaseId: 'P2', duration: '1', isMilestone: 'true' }, { id: 'T6', name: 'Draft Thesis Chapters 1-3', phaseId: 'P3', duration: '20', isMilestone: 'false' } ]; // Sample Dependencies dependencies = [ { precedingId: 'T1', dependentId: 'T2' }, // Qs must be defined before literature review can complete { precedingId: 'T3', dependentId: 'T4' }, // Lab must be secured before experiments run { precedingId: 'T5', dependentId: 'T6' } // Data collected before writing starts ]; } loadInitialData(); renderPhases(); renderTasks(); renderDependencies(); updateNavButtons(); });
Scroll to Top