No budget categories added yet. Click "+ Add Category" to start.
';
} else {
budgetData.forEach((item, index) => {
const row = document.createElement('div');
row.className = 'grid grid-cols-1 md:grid-cols-7 gap-4 items-center';
row.innerHTML = `
`;
configRowsContainer.appendChild(row);
});
}
addConfigEventListeners();
};
/**
* Renders the doughnut chart on the dashboard.
*/
const renderChart = () => {
if (myChart) {
myChart.destroy();
}
const ctx = chartCanvas.getContext('2d');
const labels = budgetData.map(item => item.category);
const data = budgetData.map(item => item.amount);
const backgroundColors = [
'#4f46e5', '#7c3aed', '#db2777', '#f59e0b', '#10b981',
'#3b82f6', '#ef4444', '#8b5cf6', '#ec4899', '#f97316'
];
myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
label: 'Budget Allocation',
data: data,
backgroundColor: backgroundColors.slice(0, data.length),
borderColor: '#ffffff',
borderWidth: 2,
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
font: { size: 12, family: 'Inter' },
color: '#4b5563',
padding: 20
}
},
tooltip: {
callbacks: {
label: function(context) {
let label = context.label || '';
if (label) {
label += ': ';
}
if (context.parsed !== null) {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = total > 0 ? ((context.parsed / total) * 100).toFixed(2) : 0;
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed) + ` (${percentage}%)`;
}
return label;
}
}
}
}
}
});
};
/**
* Updates the summary card on the dashboard.
*/
const updateSummary = () => {
const total = budgetData.reduce((sum, item) => sum + (parseFloat(item.amount) || 0), 0);
document.getElementById('summaryTotal').textContent = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(total);
document.getElementById('summaryCategories').textContent = budgetData.length;
const amounts = budgetData.map(item => parseFloat(item.amount) || 0);
const highest = amounts.length > 0 ? Math.max(...amounts) : 0;
const lowest = amounts.length > 0 ? Math.min(...amounts) : 0;
document.getElementById('summaryHighest').textContent = amounts.length > 0 ? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(highest) : 'N/A';
document.getElementById('summaryLowest').textContent = amounts.length > 0 ? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(lowest) : 'N/A';
const tableBody = document.getElementById('summaryTableBody');
tableBody.innerHTML = '';
const sortedData = [...budgetData].sort((a, b) => (parseFloat(b.amount) || 0) - (parseFloat(a.amount) || 0));
sortedData.forEach(item => {
const row = document.createElement('tr');
row.innerHTML = `
${item.category} |
${new Intl.NumberFormat('en-US').format(item.amount)} |
`;
tableBody.appendChild(row);
});
};
/**
* Adds a new empty category row to the configuration.
*/
window.addCategoryRow = () => {
budgetData.push({ category: '', amount: '' });
updateAll();
};
/**
* Removes a category row.
* @param {number} index - The index of the row to remove.
*/
window.removeCategoryRow = (index) => {
budgetData.splice(index, 1);
updateAll();
};
/**
* Adds event listeners to the input fields in the config tab.
*/
const addConfigEventListeners = () => {
document.querySelectorAll('.category-input').forEach(input => {
input.addEventListener('change', (e) => {
const index = e.target.dataset.index;
budgetData[index].category = e.target.value;
updateAll();
});
});
document.querySelectorAll('.amount-input').forEach(input => {
input.addEventListener('change', (e) => {
const index = e.target.dataset.index;
budgetData[index].amount = parseFloat(e.target.value) || 0;
updateAll();
});
});
};
// --- UI Interaction ---
/**
* Switches the visible tab.
* @param {string} tabId - The ID of the tab to switch to.
*/
window.switchTab = (tabId) => {
currentTab = tabId;
tabDashboard.classList.toggle('tab-active', tabId === 'dashboard');
tabConfig.classList.toggle('tab-active', tabId === 'config');
contentDashboard.classList.toggle('hidden', tabId !== 'dashboard');
contentConfig.classList.toggle('hidden', tabId !== 'config');
updateNavButtons();
};
/**
* Updates the state of the Next/Previous navigation buttons.
*/
const updateNavButtons = () => {
const currentIndex = tabs.indexOf(currentTab);
prevBtn.disabled = currentIndex === 0;
nextBtn.disabled = currentIndex === tabs.length - 1;
};
/**
* Navigates between tabs using Next/Previous buttons.
* @param {string} direction - 'next' or 'prev'.
*/
window.navigateTabs = (direction) => {
const currentIndex = tabs.indexOf(currentTab);
let newIndex;
if (direction === 'next' && currentIndex < tabs.length - 1) {
newIndex = currentIndex + 1;
} else if (direction === 'prev' && currentIndex > 0) {
newIndex = currentIndex - 1;
}
if (newIndex !== undefined) {
switchTab(tabs[newIndex]);
}
};
// --- PDF Generation ---
/**
* Generates and downloads a PDF of the budget dashboard.
*/
window.downloadPDF = async () => {
const { jsPDF } = window.jspdf;
const pdfContent = document.getElementById('pdf-content');
// Temporarily make it visible if it's not, for rendering
const wasHidden = pdfContent.offsetParent === null;
if (wasHidden) {
pdfContent.style.display = 'block';
}
const canvas = await html2canvas(pdfContent, {
scale: 2, // Higher scale for better quality
backgroundColor: '#ffffff',
useCORS: true
});
if (wasHidden) {
pdfContent.style.display = ''; // Revert style
}
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'px',
format: [canvas.width, canvas.height]
});
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
pdf.save('Legal_Budget_Plan.pdf');
};
// --- Initial Load ---
init();
updateNavButtons();
});