${formatCurrency(expense.amount * conversionRate, state.baseCurrency)}
`;
expenseList.appendChild(li);
});
}
// --- EVENT HANDLERS & ACTIONS ---
window.changeTab = (tabIndex) => {
state.currentTab = tabIndex;
renderTabs();
};
window.navigateTabs = (direction) => {
if (direction === 'next' && state.currentTab < tabs.length - 1) {
state.currentTab++;
} else if (direction === 'prev' && state.currentTab > 0) {
state.currentTab--;
}
renderTabs();
};
function addBudgetCategory() {
state.categories.push({ name: 'New Category', budget: 0 });
renderBudgetSetup();
}
window.removeBudgetCategory = (index) => {
state.categories.splice(index, 1);
renderBudgetSetup();
updateUI();
};
window.updateCategoryName = (index, newName) => {
state.categories[index].name = newName;
updateUI();
};
window.updateCategoryBudget = (index, newBudget) => {
state.categories[index].budget = parseFloat(newBudget) || 0;
updateDashboard();
};
function addExpense() {
const date = document.getElementById('expense-date').value;
const description = document.getElementById('expense-description').value;
const category = document.getElementById('expense-category').value;
const amount = parseFloat(document.getElementById('expense-amount').value);
if (!date || !description || !category || isNaN(amount)) {
console.error("Invalid expense input");
return;
}
state.expenses.push({ date, description, category, amount });
document.getElementById('expense-description').value = '';
document.getElementById('expense-amount').value = '';
updateUI();
}
window.removeExpense = (index) => {
state.expenses.splice(index, 1);
updateUI();
};
async function generatePdf() {
const { jsPDF } = window.jspdf;
const dashboardEl = document.getElementById('dashboard-tab');
const downloadButton = document.getElementById('pdf-download-button');
if (!dashboardEl || !downloadButton) return;
downloadButton.style.display = 'none';
const canvas = await html2canvas(dashboardEl, { scale: 2, useCORS: true, windowWidth: dashboardEl.scrollWidth, windowHeight: dashboardEl.scrollHeight });
downloadButton.style.display = 'block';
const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const margin = 10;
const contentWidth = pdfWidth - (margin * 2);
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
pdf.setFontSize(18);
pdf.text(`${state.tripName || 'Budget'} - Summary`, pdfWidth / 2, margin + 5, { align: 'center' });
let yCanvas = 0;
let page = 1;
while (yCanvas < canvasHeight) {
if (page > 1) pdf.addPage();
let yPdf = margin;
let pageContentHeight = pdfHeight - (margin * 2);
if (page === 1) {
const titleSpace = 20;
yPdf += titleSpace;
pageContentHeight -= titleSpace;
}
let sliceHeightInCanvasPixels = canvasWidth * pageContentHeight / contentWidth;
sliceHeightInCanvasPixels = Math.min(sliceHeightInCanvasPixels, canvasHeight - yCanvas);
const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvasWidth;
pageCanvas.height = sliceHeightInCanvasPixels;
const pageCtx = pageCanvas.getContext('2d');
pageCtx.drawImage(canvas, 0, yCanvas, canvasWidth, sliceHeightInCanvasPixels, 0, 0, canvasWidth, sliceHeightInCanvasPixels);
const pageImgData = pageCanvas.toDataURL('image/png');
const sliceHeightInPdfMm = contentWidth * sliceHeightInCanvasPixels / canvasWidth;
pdf.addImage(pageImgData, 'PNG', margin, yPdf, contentWidth, sliceHeightInPdfMm);
yCanvas += sliceHeightInCanvasPixels;
page++;
}
pdf.save(`${(state.tripName || 'budget').replace(/\s+/g, '_')}_Summary.pdf`);
}
function calculateStormRisk() {
const startDate = new Date(document.getElementById('travel-start-date').value);
const endDate = new Date(document.getElementById('travel-end-date').value);
const basinKey = document.getElementById('ocean-basin').value;
const destination = document.getElementById('travel-destination').value || "your destination";
if (isNaN(startDate) || isNaN(endDate) || !basinKey) { return; }
const basin = stormSeasons[basinKey];
let riskScore = 0;
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
const month = d.getMonth() + 1;
let monthInSeason = (basin.season[0] <= basin.season[1]) ? (month >= basin.season[0] && month <= basin.season[1]) : (month >= basin.season[0] || month <= basin.season[1]);
if (monthInSeason) {
riskScore += 1;
let monthInPeak = (basin.peak[0] <= basin.peak[1]) ? (month >= basin.peak[0] && month <= basin.peak[1]) : (month >= basin.peak[0] || month <= basin.peak[1]);
if (monthInPeak) riskScore += 2;
}
}
const duration = Math.ceil(Math.abs(endDate - startDate) / (1000 * 60 * 60 * 24)) + 1;
const avgRisk = duration > 0 ? riskScore / duration : 0;
displayRiskResult(avgRisk, basin.name, destination);
}
function displayRiskResult(avgRisk, basinName, destination) {
const resultDiv = document.getElementById('risk-result');
let level, cssClass, summary, recommendations;
if (avgRisk > 1.5) {
level = 'High'; cssClass = 'risk-high';
summary = `Your travel to ${destination} occurs during the **peak** of the ${basinName} tropical storm season. The probability of storm activity is significantly elevated.`;
recommendations = `Risk Level: ${level}
${summary}
Recommendations:
- ${recommendations}
${index + 1}
${country.name}
Cost: ${['High', 'Med', 'Low'][country.cost-1]}
Internet: ${['Good', 'Great', 'Excel.'][country.internet-1]}
Quality: ${['Good', 'Great', 'Excel.'][country.quality-1]}
Safety: ${['High', 'Very High'][country.safety-1]}
${country.score}
Match Score
