mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Update data displayed in table
This commit is contained in:
@@ -236,22 +236,23 @@
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f3f4f6;
|
||||
padding: 0.5rem;
|
||||
padding: 0.5rem 0.375rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.375rem 0.5rem;
|
||||
padding: 0.375rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
@@ -262,6 +263,140 @@
|
||||
background: #d1fae5 !important;
|
||||
}
|
||||
|
||||
.net-worth-cell {
|
||||
font-weight: 600;
|
||||
color: #2563eb;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-decoration-color: #93c5fd;
|
||||
}
|
||||
|
||||
.net-worth-cell:hover {
|
||||
color: #1d4ed8;
|
||||
background-color: #dbeafe;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
animation: modalSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes modalSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s ease;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.calculation-step {
|
||||
background: #f9fafb;
|
||||
border-left: 4px solid #3b82f6;
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.calculation-step.highlight {
|
||||
background: #dbeafe;
|
||||
border-left-color: #1d4ed8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.calculation-label {
|
||||
color: #6b7280;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.calculation-value {
|
||||
color: #1f2937;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.calculation-formula {
|
||||
background: #fef3c7;
|
||||
border: 1px solid #fbbf24;
|
||||
padding: 0.75rem;
|
||||
border-radius: 6px;
|
||||
margin: 1rem 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -290,7 +425,7 @@
|
||||
|
||||
<div class="input-group">
|
||||
<label class="input-label" for="currentNetWorth">Current Net Worth (excl. home)</label>
|
||||
<input type="number" id="currentNetWorth" class="input-field" value="300000" step="1000">
|
||||
<input type="number" id="currentNetWorth" class="input-field" value="200000" step="1000">
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
@@ -300,7 +435,7 @@
|
||||
|
||||
<div class="input-group">
|
||||
<label class="input-label" for="annualExpenses">Annual Expenses (AUD)</label>
|
||||
<input type="number" id="annualExpenses" class="input-field" value="150000" step="1000">
|
||||
<input type="number" id="annualExpenses" class="input-field" value="130000" step="1000">
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
@@ -514,7 +649,8 @@
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Detailed breakdown of your journey to FIRE.
|
||||
Detailed year-by-year breakdown showing your income, savings, investment returns, and net worth growth.
|
||||
<strong class="text-blue-600">Click on any Net Worth value</strong> to see the detailed calculation with actual numbers.
|
||||
<strong class="text-green-600">Green rows</strong> indicate years where you've achieved FIRE (≥100% progress).
|
||||
</p>
|
||||
<div class="overflow-x-auto">
|
||||
@@ -523,10 +659,12 @@
|
||||
<tr>
|
||||
<th>Age</th>
|
||||
<th>Year</th>
|
||||
<th>Net Worth</th>
|
||||
<th>Annual Income</th>
|
||||
<th>Annual Savings</th>
|
||||
<th>Investment Return</th>
|
||||
<th>Net Worth (End)</th>
|
||||
<th>FIRE Target</th>
|
||||
<th>Progress</th>
|
||||
<th>Annual Savings</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="projectionTableBody">
|
||||
@@ -537,6 +675,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Net Worth Calculation Modal -->
|
||||
<div id="netWorthModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">
|
||||
<i class="fas fa-calculator mr-2"></i>
|
||||
Net Worth Calculation Details
|
||||
</h3>
|
||||
<button class="modal-close" onclick="closeNetWorthModal()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" id="modalCalculationContent">
|
||||
<!-- Calculation details will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Educational Content (Moved to Bottom) -->
|
||||
<div class="info-box mt-4">
|
||||
<div class="flex items-start">
|
||||
@@ -661,26 +817,39 @@
|
||||
const currentYear = new Date().getFullYear() + year;
|
||||
const fireTarget = inputs.fireTarget * Math.pow(1 + inputs.inflationRate, year);
|
||||
const annualSavings = currentIncome * inputs.savingsRate;
|
||||
const progress = (netWorth / fireTarget) * 100;
|
||||
|
||||
// Store starting net worth for the year
|
||||
const startingNetWorth = netWorth;
|
||||
|
||||
// Calculate investment return for the year (return on starting net worth + savings)
|
||||
const investmentReturn = (netWorth + annualSavings) * inputs.returnRate;
|
||||
|
||||
// Calculate ending net worth for the year
|
||||
const endingNetWorth = (netWorth + annualSavings) * (1 + inputs.returnRate);
|
||||
|
||||
// Calculate progress based on ending net worth
|
||||
const progress = (endingNetWorth / fireTarget) * 100;
|
||||
|
||||
projections.push({
|
||||
age: age,
|
||||
year: currentYear,
|
||||
netWorth: netWorth,
|
||||
netWorth: endingNetWorth, // Store ENDING net worth
|
||||
startingNetWorth: startingNetWorth, // Keep starting for modal calculation
|
||||
fireTarget: fireTarget,
|
||||
progress: progress,
|
||||
annualSavings: annualSavings
|
||||
annualSavings: annualSavings,
|
||||
investmentReturn: investmentReturn,
|
||||
annualIncome: currentIncome,
|
||||
returnRate: inputs.returnRate
|
||||
});
|
||||
|
||||
// Project next year's net worth and income
|
||||
netWorth = (netWorth + annualSavings) * (1 + inputs.returnRate);
|
||||
netWorth = endingNetWorth; // Next year starts with this year's ending
|
||||
currentIncome = currentIncome * (1 + inputs.incomeGrowthRate);
|
||||
}
|
||||
|
||||
return projections;
|
||||
}
|
||||
|
||||
// Find the year FIRE is achieved
|
||||
} // Find the year FIRE is achieved
|
||||
function findFIREYear(projections) {
|
||||
return projections.find(p => p.progress >= 100);
|
||||
}
|
||||
@@ -749,21 +918,27 @@
|
||||
function updateTable(projections) {
|
||||
let tableHTML = '';
|
||||
|
||||
projections.forEach(proj => {
|
||||
projections.forEach((proj, index) => {
|
||||
const rowClass = proj.progress >= 100 ? 'fire-achieved' : '';
|
||||
|
||||
tableHTML += `
|
||||
<tr class="${rowClass}">
|
||||
<td>${proj.age}</td>
|
||||
<td>${proj.year}</td>
|
||||
<td>${formatCurrency(proj.netWorth)}</td>
|
||||
<td>${formatCurrency(proj.annualIncome)}</td>
|
||||
<td>${formatCurrency(proj.annualSavings)}</td>
|
||||
<td>${formatCurrency(proj.investmentReturn)}</td>
|
||||
<td class="net-worth-cell" onclick="showNetWorthCalculation(${index})">${formatCurrency(proj.netWorth)}</td>
|
||||
<td>${formatCurrency(proj.fireTarget)}</td>
|
||||
<td>${formatPercentage(proj.progress)}</td>
|
||||
<td>${formatCurrency(proj.annualSavings)}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById('projectionTableBody').innerHTML = tableHTML;
|
||||
|
||||
// Store projections globally for modal access
|
||||
window.currentProjections = projections;
|
||||
}
|
||||
|
||||
// Adjust value with quick buttons
|
||||
@@ -1132,13 +1307,100 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Show Net Worth Calculation Modal
|
||||
function showNetWorthCalculation(index) {
|
||||
const proj = window.currentProjections[index];
|
||||
const modal = document.getElementById('netWorthModal');
|
||||
const content = document.getElementById('modalCalculationContent');
|
||||
|
||||
let calculationHTML = '';
|
||||
|
||||
// All years use the same calculation logic
|
||||
const startNetWorth = proj.startingNetWorth;
|
||||
const withSavings = startNetWorth + proj.annualSavings;
|
||||
const endNetWorth = withSavings * (1 + proj.returnRate);
|
||||
const returnPercent = (proj.returnRate * 100).toFixed(1);
|
||||
const returnMultiplier = (1 + proj.returnRate).toFixed(2);
|
||||
|
||||
calculationHTML = `
|
||||
<div style="text-align: center; margin-bottom: 1.5rem;">
|
||||
<div class="text-sm text-gray-600 font-semibold">Age ${proj.age} - Year ${proj.year}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">Showing net worth growth during this year</div>
|
||||
</div>
|
||||
|
||||
<div class="calculation-step">
|
||||
<div class="calculation-label">Step 1: Starting Net Worth</div>
|
||||
<div class="calculation-value">${formatCurrency(startNetWorth)}</div>
|
||||
${index === 0 ? '<div class="text-xs text-gray-600 mt-1">(Your initial net worth)</div>' :
|
||||
`<div class="text-xs text-gray-600 mt-1">(This is what you ended age ${proj.age - 1} with)</div>`}
|
||||
</div>
|
||||
|
||||
<div class="calculation-step">
|
||||
<div class="calculation-label">Step 2: Add Annual Savings</div>
|
||||
<div class="calculation-value">+ ${formatCurrency(proj.annualSavings)}</div>
|
||||
<div class="text-xs text-gray-600 mt-1">
|
||||
From income of ${formatCurrency(proj.annualIncome)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calculation-step">
|
||||
<div class="calculation-label">Step 3: Subtotal Before Returns</div>
|
||||
<div class="calculation-value">= ${formatCurrency(withSavings)}</div>
|
||||
</div>
|
||||
|
||||
<div class="calculation-formula">
|
||||
<div class="font-semibold mb-1">Apply Investment Return:</div>
|
||||
${formatCurrency(withSavings)} × ${returnMultiplier} (${returnPercent}% return)
|
||||
</div>
|
||||
|
||||
<div class="calculation-step highlight">
|
||||
<div class="calculation-label">Final: Ending Net Worth</div>
|
||||
<div class="calculation-value" style="font-size: 1.5rem; color: #2563eb;">
|
||||
${formatCurrency(endNetWorth)}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-1">
|
||||
(This becomes your starting net worth for age ${proj.age + 1})
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<div class="text-xs font-semibold text-blue-900 mb-1">💡 Understanding the Formula</div>
|
||||
<div class="text-xs text-blue-800">
|
||||
Net Worth (End) = (Net Worth (Start) + Annual Savings) × (1 + Return Rate)
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
content.innerHTML = calculationHTML;
|
||||
modal.style.display = 'flex';
|
||||
|
||||
// Close modal when clicking outside
|
||||
modal.onclick = function(event) {
|
||||
if (event.target === modal) {
|
||||
closeNetWorthModal();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Close Net Worth Calculation Modal
|
||||
function closeNetWorthModal() {
|
||||
document.getElementById('netWorthModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// Close modal with Escape key
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape') {
|
||||
closeNetWorthModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Reset form
|
||||
function resetForm() {
|
||||
document.getElementById('currentAge').value = 35;
|
||||
document.getElementById('targetAge').value = 50;
|
||||
document.getElementById('currentNetWorth').value = 300000;
|
||||
document.getElementById('currentNetWorth').value = 200000;
|
||||
document.getElementById('totalNetWorth').value = 900000;
|
||||
document.getElementById('annualExpenses').value = 150000;
|
||||
document.getElementById('annualExpenses').value = 130000;
|
||||
document.getElementById('annualIncome').value = 300000;
|
||||
document.getElementById('savingsRate').value = 40;
|
||||
document.getElementById('returnRate').value = 8;
|
||||
|
||||
Reference in New Issue
Block a user