// Partial early repayment screen — Komek Lombard v2 // Mirrors the "Условия пролонгации" screen pattern, but with an editable amount. const PR_TEAL = '#1B7F70'; const PR_TEAL_DEEP = '#176A5D'; const PR_BORDER = '#E2E2E5'; const PR_LABEL = '#0B0B0C'; const PR_BODY = '#3A3A3D'; const PR_HINT = '#9A9AA0'; // ─── icons ───────────────────────────────────────────────────────── const PRIcon = { arrowLeft: ( ), chevron: ( ), wallet: ( ), tenge: ( ), check: ( ), }; // ─── top bar ─────────────────────────────────────────────────────── function TopBar({ title, onBack }) { return (
{title}
); } // ─── field ───────────────────────────────────────────────────────── function FieldLabel({ children }) { return (
{children}
); } function MethodSelector({ value, onClick }) { return ( ); } function AmountInput({ value, onChange, max }) { const display = value === '' ? '' : Number(value).toLocaleString('ru-RU'); return (
{PRIcon.tenge} { const digits = e.target.value.replace(/\D/g, ''); if (digits === '') { onChange(''); return; } const n = parseInt(digits, 10); onChange(Math.min(n, max)); }} placeholder="0" style={{ flex: 1, border: 'none', outline: 'none', background: 'transparent', fontSize: 17, fontWeight: 700, color: PR_LABEL, fontFamily: 'inherit', minWidth: 0, }} />
); } function HelperRow({ left, right }) { return (
{left} {right}
); } function Checkbox({ checked, onChange, children }) { return ( ); } function PayButton({ enabled, children, onClick }) { return ( ); } // ─── screen ──────────────────────────────────────────────────────── // Default pledged items for a multi-item ticket. minRemainder is the BP-2 min. // balance for each item; a single repayment must keep at least that much in debt. const DEFAULT_ITEMS = [ { id: 'i1', name: 'Часы Rolex Datejust', balance: 18000, minRemainder: 5000 }, { id: 'i2', name: 'Кольцо золотое 585°', balance: 14000, minRemainder: 5000 }, { id: 'i3', name: 'Серьги серебряные', balance: 8000, minRemainder: 5000 }, ]; function itemMaxRepayable(it) { return Math.max(0, it.balance - it.minRemainder); } function distributeAllocations({ items, modes, manualValues, total }) { // total = budget that goes specifically to main debt (already excludes interest) const result = {}; let manualSum = 0; items.forEach(it => { if (modes[it.id] === 'manual') { const v = Math.max(0, Math.min(manualValues[it.id] || 0, itemMaxRepayable(it))); result[it.id] = v; manualSum += v; } }); const autoItems = items.filter(it => modes[it.id] === 'auto'); const autoBudget = Math.max(0, total - manualSum); const autoBase = autoItems.reduce((s, it) => s + itemMaxRepayable(it), 0); let assigned = 0; autoItems.forEach((it, idx) => { const cap = itemMaxRepayable(it); if (autoBase === 0 || autoBudget === 0) { result[it.id] = 0; } else if (idx === autoItems.length - 1) { result[it.id] = Math.max(0, Math.min(cap, autoBudget - assigned)); } else { const v = Math.min(cap, Math.round((cap / autoBase) * autoBudget)); result[it.id] = v; assigned += v; } }); return { result, manualSum }; } function ToggleSwitch({ checked, onChange }) { return ( ); } function ItemAllocationRow({ item, mode, value, onMode, onChange, isFirst, isLast }) { const cap = itemMaxRepayable(item); const editing = mode === 'manual'; const atCap = editing && (value || 0) >= cap && cap > 0; const valueColor = editing ? PR_LABEL : '#5C5C60'; return (
{/* header: title + toggle */}
{item.name}
Изменить onMode(c ? 'manual' : 'auto')}/>
{/* Amount field — same shape as main AmountInput; disabled when auto */}
{PRIcon.tenge} { if (!editing) return; const digits = e.target.value.replace(/\D/g, ''); onChange(digits === '' ? 0 : parseInt(digits, 10)); }} style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', background: 'transparent', fontFamily: 'inherit', fontSize: 17, fontWeight: 700, color: valueColor, cursor: editing ? 'text' : 'default', }} />
{/* Info below the field */}
{atCap ? `Достигнут максимум: ${cap.toLocaleString('ru-RU')} ₸` : `Долг ${item.balance.toLocaleString('ru-RU')} ₸ · мин. остаток ${item.minRemainder.toLocaleString('ru-RU')} ₸` }
); } // ─── screen ───────────────────────────────────────────────────── function PartialRepaymentScreen({ onBack, loanBalance = 40000, accruedInterest = 1234, minAmount = 5000, maxAmount = 35000, items = DEFAULT_ITEMS, initialAmount = 0, initialMethod = '', initialAgreed = false, initialAllocations }) { const [method, setMethod] = React.useState(initialMethod); const [amount, setAmount] = React.useState(initialAmount); const [agreed, setAgreed] = React.useState(initialAgreed); // per-item mode + manual values const [modes, setModes] = React.useState(() => Object.fromEntries( items.map(it => [it.id, (initialAllocations && initialAllocations[it.id] && initialAllocations[it.id].mode) || 'auto']) )); const [manualValues, setManualValues] = React.useState(() => Object.fromEntries( items.map(it => [it.id, (initialAllocations && initialAllocations[it.id] && initialAllocations[it.id].value) || 0]) )); const debtBudget = Math.max(0, Number(amount || 0) - accruedInterest); const { result: alloc, manualSum } = distributeAllocations({ items, modes, manualValues, total: debtBudget, }); const manualOver = manualSum > debtBudget; const max = maxAmount; const valid = method !== '' && Number(amount) >= minAmount && Number(amount) <= max && agreed && !manualOver; const setItemMode = (id, m) => { setModes(prev => ({ ...prev, [id]: m })); if (m === 'manual') { // when switching to manual, seed the field with the current auto allocation setManualValues(prev => ({ ...prev, [id]: alloc[id] || 0 })); } }; const setItemManual = (id, v) => { const item = items.find(it => it.id === id); const cap = itemMaxRepayable(item); setManualValues(prev => ({ ...prev, [id]: Math.min(cap, Math.max(0, v)) })); }; return (
{/* scrollable middle */}
{/* description */}

Укажите сумму частичного погашения — она спишется в счёт начисленных процентов, а остаток уменьшит основной долг.
Проценты пересчитаются автоматически.

{/* method */} Метод оплаты setMethod('Mastercard •••• 4783')}/> {/* amount */}
Частично погашаю сейчас
Максимальная сумма к погашению {max.toLocaleString('ru-RU')} ₸
{/* secondary info — less important than the payment summary */}
Сумма кредита до частичного погашения {loanBalance.toLocaleString('ru-RU')} ₸
Сумма кредита после частичного погашения {Math.max(0, loanBalance - Math.max(0, Number(amount || 0) - accruedInterest)).toLocaleString('ru-RU')} ₸
{/* summary preview */}
Сумма процентов {Math.min(Number(amount || 0), accruedInterest).toLocaleString('ru-RU')} ₸
Сумма погашения основного долга {Math.max(0, Number(amount || 0) - accruedInterest).toLocaleString('ru-RU')} ₸
Будет выполнен платёж {Number(amount || 0).toLocaleString('ru-RU')} ₸
{/* Items — per-item allocation (placed right before the footer) */}
Распределение по предметам {items.length} предмета
{items.map((it, idx) => ( setItemMode(it.id, m)} onChange={(v) => setItemManual(it.id, v)} /> ))}
{manualOver && (
Сумма ручных распределений ({manualSum.toLocaleString('ru-RU')} ₸) превышает сумму к распределению по основному долгу ({debtBudget.toLocaleString('ru-RU')} ₸). Уменьшите значения или увеличьте общую сумму платежа.
)}
{/* fixed footer */}
Согласен(-на) с условиями частичного погашения Оплатить
); } Object.assign(window, { PartialRepaymentScreen });