`;
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();
});
