changes2
This commit is contained in:
+6
-5
@@ -345,12 +345,13 @@ def export_pdf(req: SimulationRequest):
|
|||||||
story.append(Spacer(1, 0.25 * cm))
|
story.append(Spacer(1, 0.25 * cm))
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
["Kwota kredytu", _money(req.principal), "Okres", f"{req.years} lat"],
|
["Kwota kredytu", _money(req.principal), "Okres", f"{req.term_months or req.years * 12} mies. ({round((req.term_months or req.years * 12) / 12, 1)} lat)"],
|
||||||
["Stopa bazowa", _pct(req.base_rate), "Marza", _pct(req.margin)],
|
["Stopa bazowa", _pct(req.base_rate), "Marza", _pct(req.margin)],
|
||||||
["Oprocentowanie startowe", _pct(req.base_rate + req.margin), "Typ rat", _installment_label(req.installment_type.value)],
|
["Oprocentowanie startowe", _pct(req.base_rate + req.margin), "Typ rat", _installment_label(req.installment_type.value)],
|
||||||
["Efekt nadplat", _effect_label(req.overpayment_effect.value), "Liczba rat po symulacji", str(result.summary.months)],
|
["Efekt nadplat", _effect_label(req.overpayment_effect.value), "Liczba rat po symulacji", str(result.summary.months)],
|
||||||
["Data startu", req.loan_start_date.isoformat(), "Dzien splaty", str(req.due_day)],
|
["Data startu", req.loan_start_date.isoformat(), "Dzien splaty", str(req.due_day)],
|
||||||
["Przesuwaj dni wolne", "tak" if req.move_due_date_to_business_day else "nie", "Data konca", result.summary.payoff_date or "-"],
|
["Przesuwaj dni wolne", "tak" if req.move_due_date_to_business_day else "nie", "Data konca", result.summary.payoff_date or "-"],
|
||||||
|
["Okres ochronny nadplat", str(req.overpayment_protection_months or "brak"), "Domyslna prowizja", _pct(req.overpayment_protection_commission_percent)],
|
||||||
]
|
]
|
||||||
story.append(Paragraph("Parametry wejściowe", styles["Heading2"]))
|
story.append(Paragraph("Parametry wejściowe", styles["Heading2"]))
|
||||||
story.append(Table(params, colWidths=[4.9 * cm, 3.4 * cm, 4.4 * cm, 3.9 * cm], style=TableStyle([
|
story.append(Table(params, colWidths=[4.9 * cm, 3.4 * cm, 4.4 * cm, 3.9 * cm], style=TableStyle([
|
||||||
@@ -418,13 +419,13 @@ def export_pdf(req: SimulationRequest):
|
|||||||
story.append(Spacer(1, 0.35 * cm))
|
story.append(Spacer(1, 0.35 * cm))
|
||||||
|
|
||||||
story.append(Paragraph("Nadplaty", styles["Heading2"]))
|
story.append(Paragraph("Nadplaty", styles["Heading2"]))
|
||||||
over_rows = [["Miesiac", "Kwota", "Prowizja", "Powtarzanie", "Do miesiaca"]]
|
over_rows = [["Miesiac", "Kwota", "Prowizja", "Prow. do mies.", "Powtarzanie", "Nadplaty do mies."]]
|
||||||
if req.overpayments:
|
if req.overpayments:
|
||||||
for op in sorted(req.overpayments, key=lambda x: x.month):
|
for op in sorted(req.overpayments, key=lambda x: x.month):
|
||||||
over_rows.append([str(op.month), _money(op.amount), _pct(op.commission_percent), _repeat_label(op.repeat), str(op.until_month or "-")])
|
over_rows.append([str(op.month), _money(op.amount), _pct(op.commission_percent), str(op.commission_until_month or "bez limitu"), _repeat_label(op.repeat), str(op.until_month or "-")])
|
||||||
else:
|
else:
|
||||||
over_rows.append(["-", "-", "-", "-", "-"])
|
over_rows.append(["-", "-", "-", "-", "-", "-"])
|
||||||
story.append(Table(over_rows, repeatRows=1, colWidths=[2.4 * cm, 3.4 * cm, 2.4 * cm, 3.4 * cm, 3.4 * cm], style=TableStyle([
|
story.append(Table(over_rows, repeatRows=1, colWidths=[1.8 * cm, 2.8 * cm, 2.1 * cm, 2.7 * cm, 2.8 * cm, 2.8 * cm], style=TableStyle([
|
||||||
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e5e7eb")),
|
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e5e7eb")),
|
||||||
("GRID", (0, 0), (-1, -1), 0.25, colors.HexColor("#cbd5e1")),
|
("GRID", (0, 0), (-1, -1), 0.25, colors.HexColor("#cbd5e1")),
|
||||||
("FONTNAME", (0, 0), (-1, -1), APP_FONT),
|
("FONTNAME", (0, 0), (-1, -1), APP_FONT),
|
||||||
|
|||||||
+5
-1
@@ -33,6 +33,7 @@ class Overpayment(BaseModel):
|
|||||||
repeat: Literal["once", "monthly", "yearly"] = "once"
|
repeat: Literal["once", "monthly", "yearly"] = "once"
|
||||||
until_month: int | None = Field(default=None, ge=1)
|
until_month: int | None = Field(default=None, ge=1)
|
||||||
commission_percent: float = Field(default=0, ge=0, le=20, description="Prowizja od nadplaty w procentach")
|
commission_percent: float = Field(default=0, ge=0, le=20, description="Prowizja od nadplaty w procentach")
|
||||||
|
commission_until_month: int | None = Field(default=None, ge=1, description="Ostatni miesiac naliczania prowizji od nadplaty")
|
||||||
|
|
||||||
|
|
||||||
class HistoricalMonth(BaseModel):
|
class HistoricalMonth(BaseModel):
|
||||||
@@ -46,7 +47,8 @@ class HistoricalMonth(BaseModel):
|
|||||||
|
|
||||||
class SimulationRequest(BaseModel):
|
class SimulationRequest(BaseModel):
|
||||||
principal: float = Field(gt=0)
|
principal: float = Field(gt=0)
|
||||||
years: int = Field(ge=1, le=50)
|
years: int = Field(default=25, ge=1, le=50)
|
||||||
|
term_months: int | None = Field(default=None, ge=1, le=600, description="Okres kredytu w miesiacach")
|
||||||
margin: float = Field(ge=0, le=20, default=2.0)
|
margin: float = Field(ge=0, le=20, default=2.0)
|
||||||
base_rate: float = Field(ge=0, le=30, default=5.75)
|
base_rate: float = Field(ge=0, le=30, default=5.75)
|
||||||
installment_type: InstallmentType = InstallmentType.equal
|
installment_type: InstallmentType = InstallmentType.equal
|
||||||
@@ -54,6 +56,8 @@ class SimulationRequest(BaseModel):
|
|||||||
loan_start_date: date = Field(default_factory=date.today)
|
loan_start_date: date = Field(default_factory=date.today)
|
||||||
due_day: int = Field(default=5, ge=1, le=28, description="Dzien splaty raty")
|
due_day: int = Field(default=5, ge=1, le=28, description="Dzien splaty raty")
|
||||||
move_due_date_to_business_day: bool = True
|
move_due_date_to_business_day: bool = True
|
||||||
|
overpayment_protection_months: int | None = Field(default=None, ge=1, description="Okres ochronny prowizji od nadplat w miesiacach")
|
||||||
|
overpayment_protection_commission_percent: float = Field(default=0, ge=0, le=20, description="Domyslna prowizja od nadplat w okresie ochronnym")
|
||||||
rate_changes: list[RateChange] = Field(default_factory=list)
|
rate_changes: list[RateChange] = Field(default_factory=list)
|
||||||
overpayments: list[Overpayment] = Field(default_factory=list)
|
overpayments: list[Overpayment] = Field(default_factory=list)
|
||||||
historical_months: list[HistoricalMonth] = Field(default_factory=list)
|
historical_months: list[HistoricalMonth] = Field(default_factory=list)
|
||||||
|
|||||||
+7
-2
@@ -127,7 +127,8 @@ def _scheduled_overpayment_for_month(req: SimulationRequest, month: int) -> tupl
|
|||||||
active = True
|
active = True
|
||||||
if active:
|
if active:
|
||||||
total += op.amount
|
total += op.amount
|
||||||
fee += op.amount * op.commission_percent / 100
|
if op.commission_until_month is None or month <= op.commission_until_month:
|
||||||
|
fee += op.amount * op.commission_percent / 100
|
||||||
return total, fee
|
return total, fee
|
||||||
|
|
||||||
|
|
||||||
@@ -144,9 +145,13 @@ def _overpayment_for_month(req: SimulationRequest, month: int, historical: dict[
|
|||||||
return scheduled_amount + hist_amount, scheduled_fee + hist_fee
|
return scheduled_amount + hist_amount, scheduled_fee + hist_fee
|
||||||
|
|
||||||
|
|
||||||
|
def _term_months(req: SimulationRequest) -> int:
|
||||||
|
return int(req.term_months or req.years * 12)
|
||||||
|
|
||||||
|
|
||||||
def _simulate_raw(req: SimulationRequest, include_overpayments: bool = True) -> list[ScheduleRow]:
|
def _simulate_raw(req: SimulationRequest, include_overpayments: bool = True) -> list[ScheduleRow]:
|
||||||
balance = float(req.principal)
|
balance = float(req.principal)
|
||||||
total_months = int(req.years * 12)
|
total_months = _term_months(req)
|
||||||
rows: list[ScheduleRow] = []
|
rows: list[ScheduleRow] = []
|
||||||
fixed_payment: float | None = None
|
fixed_payment: float | None = None
|
||||||
month = 1
|
month = 1
|
||||||
|
|||||||
+57
-7
@@ -13,6 +13,38 @@ function todayIso() {
|
|||||||
return d.toISOString().slice(0, 10);
|
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) {
|
function setTheme(theme) {
|
||||||
document.body.dataset.theme = theme;
|
document.body.dataset.theme = theme;
|
||||||
localStorage.setItem('mortgage-theme', theme);
|
localStorage.setItem('mortgage-theme', theme);
|
||||||
@@ -49,7 +81,8 @@ function buildRequest() {
|
|||||||
amount: Number(row.querySelector('[data-field="amount"]').value || 0),
|
amount: Number(row.querySelector('[data-field="amount"]').value || 0),
|
||||||
repeat: row.querySelector('[data-field="repeat"]').value,
|
repeat: row.querySelector('[data-field="repeat"]').value,
|
||||||
until_month: Number(row.querySelector('[data-field="until"]').value || 0) || null,
|
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);
|
})).filter(x => x.month > 0 && x.amount > 0);
|
||||||
|
|
||||||
const historicalMonths = [...document.querySelectorAll('#historicalMonths .dynamic-row')].map(row => {
|
const historicalMonths = [...document.querySelectorAll('#historicalMonths .dynamic-row')].map(row => {
|
||||||
@@ -64,9 +97,12 @@ function buildRequest() {
|
|||||||
};
|
};
|
||||||
}).filter(x => x.month > 0);
|
}).filter(x => x.month > 0);
|
||||||
|
|
||||||
|
const termMonths = Math.max(1, Math.round(num('termMonths') || 1));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
principal: num('principal'),
|
principal: num('principal'),
|
||||||
years: num('years'),
|
years: Math.max(1, Math.ceil(termMonths / 12)),
|
||||||
|
term_months: termMonths,
|
||||||
margin: num('margin'),
|
margin: num('margin'),
|
||||||
base_rate: num('baseRate'),
|
base_rate: num('baseRate'),
|
||||||
installment_type: $('installmentType').value,
|
installment_type: $('installmentType').value,
|
||||||
@@ -74,6 +110,8 @@ function buildRequest() {
|
|||||||
loan_start_date: $('loanStartDate').value || todayIso(),
|
loan_start_date: $('loanStartDate').value || todayIso(),
|
||||||
due_day: num('dueDay') || 5,
|
due_day: num('dueDay') || 5,
|
||||||
move_due_date_to_business_day: $('moveDueDate').checked,
|
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,
|
rate_changes: rateChanges,
|
||||||
overpayments: overpayments,
|
overpayments: overpayments,
|
||||||
historical_months: historicalMonths
|
historical_months: historicalMonths
|
||||||
@@ -101,15 +139,19 @@ function addRateRow(month = 13, rate = 7.0) {
|
|||||||
recalc();
|
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');
|
const div = document.createElement('div');
|
||||||
div.className = 'dynamic-row overpay';
|
div.className = 'dynamic-row overpay';
|
||||||
div.innerHTML = `
|
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">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">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 %</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">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>`;
|
<button class="btn btn-sm btn-outline-danger" type="button">Usuń</button>`;
|
||||||
div.querySelector('[data-field="repeat"]').value = repeat;
|
div.querySelector('[data-field="repeat"]').value = repeat;
|
||||||
div.querySelector('button').onclick = () => { div.remove(); recalc(); };
|
div.querySelector('button').onclick = () => { div.remove(); recalc(); };
|
||||||
@@ -237,7 +279,9 @@ function clearRows() {
|
|||||||
|
|
||||||
function applyRequest(data) {
|
function applyRequest(data) {
|
||||||
$('principal').value = data.principal ?? 600000;
|
$('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;
|
$('margin').value = data.margin ?? 2;
|
||||||
$('baseRate').value = data.base_rate ?? 5.75;
|
$('baseRate').value = data.base_rate ?? 5.75;
|
||||||
$('installmentType').value = data.installment_type ?? 'equal';
|
$('installmentType').value = data.installment_type ?? 'equal';
|
||||||
@@ -245,9 +289,11 @@ function applyRequest(data) {
|
|||||||
$('loanStartDate').value = data.loan_start_date ?? todayIso();
|
$('loanStartDate').value = data.loan_start_date ?? todayIso();
|
||||||
$('dueDay').value = data.due_day ?? 5;
|
$('dueDay').value = data.due_day ?? 5;
|
||||||
$('moveDueDate').checked = data.move_due_date_to_business_day ?? true;
|
$('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();
|
clearRows();
|
||||||
(data.rate_changes || []).forEach(x => addRateRow(x.month, x.annual_rate));
|
(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 || ''));
|
(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();
|
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();
|
$('addRate').onclick = () => addRateRow();
|
||||||
$('addOverpayment').onclick = () => addOverpaymentRow();
|
$('addOverpayment').onclick = () => addOverpaymentRow();
|
||||||
|
$('applyProtection').onclick = applyProtectionToOverpayments;
|
||||||
$('addHistorical').onclick = () => addHistoricalRow();
|
$('addHistorical').onclick = () => addHistoricalRow();
|
||||||
$('exportCsv').onclick = () => download('/api/export/csv', 'symulacja-kredytu.csv');
|
$('exportCsv').onclick = () => download('/api/export/csv', 'symulacja-kredytu.csv');
|
||||||
$('exportPdf').onclick = () => download('/api/export/pdf', 'symulacja-kredytu.pdf');
|
$('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]);
|
$('jsonFile').onchange = (e) => e.target.files?.[0] && importJsonFile(e.target.files[0]);
|
||||||
$('loadNbp').onclick = loadNbp;
|
$('loadNbp').onclick = loadNbp;
|
||||||
$('loanStartDate').value = todayIso();
|
$('loanStartDate').value = todayIso();
|
||||||
|
updateTermLabel();
|
||||||
initTheme();
|
initTheme();
|
||||||
|
|
||||||
addOverpaymentRow(24, 20000, 'once', '', 0);
|
addOverpaymentRow(24, 20000, 'once', '', 0);
|
||||||
|
|||||||
+29
-3
@@ -13,7 +13,7 @@
|
|||||||
<div class="card-body d-flex flex-wrap align-items-center justify-content-between gap-3">
|
<div class="card-body d-flex flex-wrap align-items-center justify-content-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1">Symulator kredytu hipotecznego</h1>
|
<h1 class="h3 mb-1">Symulator kredytu hipotecznego</h1>
|
||||||
<p class="text-muted mb-0">@linuiarz.pl</p>
|
<p class="text-muted mb-0">@linuxiarz.pl</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2 flex-wrap justify-content-end">
|
<div class="d-flex gap-2 flex-wrap justify-content-end">
|
||||||
<button id="themeToggle" class="btn btn-outline-secondary btn-sm" type="button" aria-label="Przełącz motyw">☀️ Jasny</button>
|
<button id="themeToggle" class="btn btn-outline-secondary btn-sm" type="button" aria-label="Przełącz motyw">☀️ Jasny</button>
|
||||||
@@ -38,8 +38,15 @@
|
|||||||
<input id="principal" type="number" class="form-control form-control-sm" value="600000" min="1">
|
<input id="principal" type="number" class="form-control form-control-sm" value="600000" min="1">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">Okres lat</label>
|
<label class="form-label">Okres w miesiącach</label>
|
||||||
<input id="years" type="number" class="form-control form-control-sm" value="25" min="1" max="50">
|
<input id="termMonths" type="number" class="form-control form-control-sm" value="300" min="1" max="600">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 term-slider-wrap">
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||||
|
<label class="form-label mb-0" for="yearsSlider">Szybki wybór lat</label>
|
||||||
|
<span id="yearsSliderLabel" class="small text-muted">25 lat = 300 mies.</span>
|
||||||
|
</div>
|
||||||
|
<input id="yearsSlider" type="range" class="form-range" min="1" max="50" step="1" value="25">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="form-label">Stopa bazowa %</label>
|
<label class="form-label">Stopa bazowa %</label>
|
||||||
@@ -86,6 +93,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="rateChanges" class="stack"></div>
|
<div id="rateChanges" class="stack"></div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-2 mb-2">
|
||||||
|
<div>
|
||||||
|
<h3 class="h6 mb-0">Okres ochronny nadpłat</h3>
|
||||||
|
<div class="text-muted small">Domyślna prowizja automatycznie wpisywana do nowych nadpłat</div>
|
||||||
|
</div>
|
||||||
|
<button id="applyProtection" class="btn btn-sm btn-outline-secondary" type="button">Uzupełnij</button>
|
||||||
|
</div>
|
||||||
|
<div class="row g-2 align-items-end">
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label">Prowizja %</label>
|
||||||
|
<input id="protectionCommission" type="number" min="0" max="20" step="0.01" class="form-control form-control-sm" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label">Do miesiąca</label>
|
||||||
|
<input id="protectionMonths" type="number" min="1" class="form-control form-control-sm" placeholder="np. 36">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<h3 class="h6 mb-0">Nadpłaty</h3>
|
<h3 class="h6 mb-0">Nadpłaty</h3>
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ body {
|
|||||||
.chart-card canvas { max-height: 330px; }
|
.chart-card canvas { max-height: 330px; }
|
||||||
.chart-card-sm canvas { max-height: 230px; }
|
.chart-card-sm canvas { max-height: 230px; }
|
||||||
.form-label { font-size: .78rem; margin-bottom: .2rem; }
|
.form-label { font-size: .78rem; margin-bottom: .2rem; }
|
||||||
|
.term-slider-wrap { padding: .25rem .1rem .15rem; }
|
||||||
|
.form-range { margin-bottom: 0; }
|
||||||
.form-control, .form-select { border-radius: 10px; }
|
.form-control, .form-select { border-radius: 10px; }
|
||||||
.stack { display: grid; gap: .45rem; }
|
.stack { display: grid; gap: .45rem; }
|
||||||
.dynamic-row {
|
.dynamic-row {
|
||||||
@@ -100,7 +102,7 @@ body {
|
|||||||
padding: .55rem;
|
padding: .55rem;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
.dynamic-row.overpay { grid-template-columns: .75fr 1fr .75fr .9fr .75fr auto; }
|
.dynamic-row.overpay { grid-template-columns: .65fr .9fr .7fr .9fr .9fr .9fr auto; }
|
||||||
.dynamic-row.historical { grid-template-columns: .65fr .8fr 1fr .9fr .7fr 1.2fr auto; }
|
.dynamic-row.historical { grid-template-columns: .65fr .8fr 1fr .9fr .7fr 1.2fr auto; }
|
||||||
.stat-card { border-radius: 16px; }
|
.stat-card { border-radius: 16px; }
|
||||||
.stat-value { font-size: 1.15rem; font-weight: 700; }
|
.stat-value { font-size: 1.15rem; font-weight: 700; }
|
||||||
|
|||||||
Reference in New Issue
Block a user