This commit is contained in:
Mateusz Gruszczyński
2026-06-03 13:18:08 +02:00
parent 7cb2eddafe
commit 3a4e1b90e2
6 changed files with 107 additions and 19 deletions
+57 -7
View File
@@ -13,6 +13,38 @@ function todayIso() {
return d.toISOString().slice(0, 10);
}
function updateTermLabel() {
const months = Math.max(1, Number($('termMonths').value || 1));
const years = Math.max(1, Math.round(months / 12));
$('yearsSlider').value = Math.min(50, years);
$('yearsSliderLabel').textContent = `${months} mies. ≈ ${(months / 12).toFixed(1)} lat`;
}
function setMonthsFromYearsSlider() {
const years = Number($('yearsSlider').value || 1);
$('termMonths').value = years * 12;
$('yearsSliderLabel').textContent = `${years} lat = ${years * 12} mies.`;
recalc();
}
function defaultProtection() {
return {
commission: Number($('protectionCommission')?.value || 0),
until: Number($('protectionMonths')?.value || 0) || ''
};
}
function applyProtectionToOverpayments() {
const defaults = defaultProtection();
document.querySelectorAll('#overpayments .dynamic-row').forEach(row => {
const commissionInput = row.querySelector('[data-field="commission"]');
const untilInput = row.querySelector('[data-field="commissionUntil"]');
if (commissionInput) commissionInput.value = defaults.commission || 0;
if (untilInput) untilInput.value = defaults.until || '';
});
recalc();
}
function setTheme(theme) {
document.body.dataset.theme = theme;
localStorage.setItem('mortgage-theme', theme);
@@ -49,7 +81,8 @@ function buildRequest() {
amount: Number(row.querySelector('[data-field="amount"]').value || 0),
repeat: row.querySelector('[data-field="repeat"]').value,
until_month: Number(row.querySelector('[data-field="until"]').value || 0) || null,
commission_percent: Number(row.querySelector('[data-field="commission"]').value || 0)
commission_percent: Number(row.querySelector('[data-field="commission"]').value || 0),
commission_until_month: Number(row.querySelector('[data-field="commissionUntil"]').value || 0) || null
})).filter(x => x.month > 0 && x.amount > 0);
const historicalMonths = [...document.querySelectorAll('#historicalMonths .dynamic-row')].map(row => {
@@ -64,9 +97,12 @@ function buildRequest() {
};
}).filter(x => x.month > 0);
const termMonths = Math.max(1, Math.round(num('termMonths') || 1));
return {
principal: num('principal'),
years: num('years'),
years: Math.max(1, Math.ceil(termMonths / 12)),
term_months: termMonths,
margin: num('margin'),
base_rate: num('baseRate'),
installment_type: $('installmentType').value,
@@ -74,6 +110,8 @@ function buildRequest() {
loan_start_date: $('loanStartDate').value || todayIso(),
due_day: num('dueDay') || 5,
move_due_date_to_business_day: $('moveDueDate').checked,
overpayment_protection_months: Number($('protectionMonths').value || 0) || null,
overpayment_protection_commission_percent: Number($('protectionCommission').value || 0),
rate_changes: rateChanges,
overpayments: overpayments,
historical_months: historicalMonths
@@ -101,15 +139,19 @@ function addRateRow(month = 13, rate = 7.0) {
recalc();
}
function addOverpaymentRow(month = 12, amount = 10000, repeat = 'once', until = '', commission = 0) {
function addOverpaymentRow(month = 12, amount = 10000, repeat = 'once', until = '', commission = null, commissionUntil = null) {
const defaults = defaultProtection();
if (commission === null || commission === undefined || commission === '') commission = defaults.commission;
if (commissionUntil === null || commissionUntil === undefined || commissionUntil === '') commissionUntil = defaults.until;
const div = document.createElement('div');
div.className = 'dynamic-row overpay';
div.innerHTML = `
<div><label class="form-label">Miesiąc</label><input data-field="month" type="number" min="1" class="form-control form-control-sm" value="${month}"></div>
<div><label class="form-label">Kwota</label><input data-field="amount" type="number" min="1" class="form-control form-control-sm" value="${amount}"></div>
<div><label class="form-label">Prowizja %</label><input data-field="commission" type="number" min="0" step="0.01" class="form-control form-control-sm" value="${commission}"></div>
<div><label class="form-label">Prowizja do mies.</label><input data-field="commissionUntil" type="number" min="1" class="form-control form-control-sm" value="${commissionUntil || ''}" placeholder="bez limitu"></div>
<div><label class="form-label">Powtarzaj</label><select data-field="repeat" class="form-select form-select-sm"><option value="once">raz</option><option value="monthly">co miesiąc</option><option value="yearly">co rok</option></select></div>
<div><label class="form-label">Do mies.</label><input data-field="until" type="number" min="1" class="form-control form-control-sm" value="${until || ''}"></div>
<div><label class="form-label">Nadpłaty do mies.</label><input data-field="until" type="number" min="1" class="form-control form-control-sm" value="${until || ''}"></div>
<button class="btn btn-sm btn-outline-danger" type="button">Usuń</button>`;
div.querySelector('[data-field="repeat"]').value = repeat;
div.querySelector('button').onclick = () => { div.remove(); recalc(); };
@@ -237,7 +279,9 @@ function clearRows() {
function applyRequest(data) {
$('principal').value = data.principal ?? 600000;
$('years').value = data.years ?? 25;
const loadedMonths = data.term_months ?? ((data.years ?? 25) * 12);
$('termMonths').value = loadedMonths;
updateTermLabel();
$('margin').value = data.margin ?? 2;
$('baseRate').value = data.base_rate ?? 5.75;
$('installmentType').value = data.installment_type ?? 'equal';
@@ -245,9 +289,11 @@ function applyRequest(data) {
$('loanStartDate').value = data.loan_start_date ?? todayIso();
$('dueDay').value = data.due_day ?? 5;
$('moveDueDate').checked = data.move_due_date_to_business_day ?? true;
$('protectionMonths').value = data.overpayment_protection_months ?? '';
$('protectionCommission').value = data.overpayment_protection_commission_percent ?? 0;
clearRows();
(data.rate_changes || []).forEach(x => addRateRow(x.month, x.annual_rate));
(data.overpayments || []).forEach(x => addOverpaymentRow(x.month, x.amount, x.repeat, x.until_month || '', x.commission_percent || 0));
(data.overpayments || []).forEach(x => addOverpaymentRow(x.month, x.amount, x.repeat, x.until_month || '', x.commission_percent || 0, x.commission_until_month || ''));
(data.historical_months || []).forEach(x => addHistoricalRow(x.month, x.annual_rate ?? '', x.grace_type || 'none', x.overpayment || 0, x.overpayment_commission_percent || 0, x.note || ''));
recalc();
}
@@ -282,9 +328,12 @@ async function loadNbp() {
}
}
['principal','years','baseRate','margin','installmentType','overpaymentEffect','loanStartDate','dueDay','moveDueDate'].forEach(id => $(id).addEventListener('input', recalc));
['principal','baseRate','margin','installmentType','overpaymentEffect','loanStartDate','dueDay','moveDueDate','protectionMonths','protectionCommission'].forEach(id => $(id).addEventListener('input', recalc));
$('termMonths').addEventListener('input', () => { updateTermLabel(); recalc(); });
$('yearsSlider').addEventListener('input', setMonthsFromYearsSlider);
$('addRate').onclick = () => addRateRow();
$('addOverpayment').onclick = () => addOverpaymentRow();
$('applyProtection').onclick = applyProtectionToOverpayments;
$('addHistorical').onclick = () => addHistoricalRow();
$('exportCsv').onclick = () => download('/api/export/csv', 'symulacja-kredytu.csv');
$('exportPdf').onclick = () => download('/api/export/pdf', 'symulacja-kredytu.pdf');
@@ -293,6 +342,7 @@ $('importJson').onclick = () => $('jsonFile').click();
$('jsonFile').onchange = (e) => e.target.files?.[0] && importJsonFile(e.target.files[0]);
$('loadNbp').onclick = loadNbp;
$('loanStartDate').value = todayIso();
updateTermLabel();
initTheme();
addOverpaymentRow(24, 20000, 'once', '', 0);