$(function () { var accountModalEl = document.getElementById('account-modal'); var creditModalEl = document.getElementById('credit-modal'); var $form = $('#account-form'); var $error = $('#account-form-error'); var $rate = $('#account-rate-field'); var $goalFields = $('#account-goal-fields'); var $colorHidden = $('#acc-color-hidden'); var hasColorOverride = false; if (!accountModalEl) { return; } var accountModal = new bootstrap.Modal(accountModalEl); var creditModal = creditModalEl ? new bootstrap.Modal(creditModalEl) : null; var archiveEl = document.getElementById('archive-modal'); var archiveModal = archiveEl ? new bootstrap.Modal(archiveEl) : null; // Плитка «Кредит» в модалке счёта переключает на модалку кредита (см. design-хендофф) — // Bootstrap не любит два открытых .modal одновременно (общий backdrop/scroll-lock), поэтому // ждём полного закрытия account-modal (событие hidden.bs.modal) и только потом открываем credit-modal. var openCreditAfterHide = false; $(accountModalEl).on('hidden.bs.modal', function () { $('#acc-color-picker').hide(); if (openCreditAfterHide) { openCreditAfterHide = false; creditModal.show(); } }); function applyPreview(color, icon) { $('#acc-color-swatch').css('background', color); $('#acc-icon-preview').css('background', 'color-mix(in srgb,' + color + ' 20%, transparent)').css('color', color); $('#acc-icon-preview-use').attr('href', '#' + icon); } function setType(type, isEdit) { var $tile = $('#acc-type-tiles .pm-tile[data-type="' + type + '"]'); $('#acc-type-tiles .pm-tile').removeClass('selected'); $tile.addClass('selected'); $('#acc-type-hidden').val(type); applyPreview(hasColorOverride ? $colorHidden.val() : $tile.data('color'), $tile.data('icon')); var isSavings = type === 'savings'; $rate.toggle(isSavings && !isEdit); $rate.find('[name=rate_percent]').prop('required', isSavings && !isEdit); $goalFields.toggle(isSavings); if (!isEdit) { var includeVal = isSavings ? '0' : '1'; $form.find('[name=include_in_total][value="' + includeVal + '"]').prop('checked', true); } } function resetForm() { $form[0].reset(); $form.find('[name=id]').val(''); $error.hide().text(''); hasColorOverride = false; $colorHidden.val(''); setType('bank', false); $form.find('[name=include_in_total][value="1"]').prop('checked', true); $('#acc-type-tiles').removeClass('locked'); $('#acc-type-lock-hint').hide(); } $(document).on('click', '.js-account-add', function () { resetForm(); $form.data('mode', 'create').attr('action', '/accounts/create'); $('#account-modal-title').text('Новый счёт'); accountModal.show(); }); $(document).on('click', '.js-account-archive-open', function () { if (archiveModal) { archiveModal.show(); } }); $(document).on('click', '.js-account-restore', function () { var $row = $(this).closest('.archive-row'); var csrf = $form.find('[name=csrf_token]').val(); $.post('/accounts/restore/' + $row.data('id'), {csrf_token: csrf}) .done(function () { window.location.reload(); }) .fail(function () { window.alert('Не удалось восстановить счёт'); }); }); // Общая логика заполнения модалки счёта на редактирование — используется и для room-card // (обычный счёт), и для goal-card (та же карточка счёта, только с иным набором data-* // не нужна — goal-card несёт ровно те же атрибуты, см. index.html), $accountId берётся // отдельно, т.к. имя атрибута с id разное (data-id / data-account-id). function populateEditForm($card, accountId) { resetForm(); $form.data('mode', 'edit').attr('action', '/accounts/edit/' + accountId); $form.find('[name=title]').val($card.data('title')); $form.find('[name=summa]').val($card.data('summa')); $form.find('[name=currency]').val($card.data('currency')); $form.find('[name=description]').val($card.data('description')); $form.find('[name=goal_target_summa]').val($card.data('goal-target')); $form.find('[name=goal_deadline]').val($card.data('goal-deadline')); var color = $card.data('color'); if (color) { hasColorOverride = true; $colorHidden.val(color); } var includeVal = $card.data('include') ? '1' : '0'; $form.find('[name=include_in_total][value="' + includeVal + '"]').prop('checked', true); setType($card.data('type'), true); $('#acc-type-tiles').addClass('locked'); $('#acc-type-lock-hint').show(); $('#account-modal-title').text('Редактировать счёт'); accountModal.show(); } $(document).on('click', '.js-account-edit', function (e) { e.preventDefault(); var $card = $(this).closest('.room-card'); populateEditForm($card, $card.data('id')); }); $(document).on('click', '.js-account-delete', function (e) { e.preventDefault(); var $card = $(this).closest('.room-card'); var title = $card.data('title'); var csrf = $form.find('[name=csrf_token]').val(); if (!window.confirm('Архивировать счёт «' + title + '»?')) { return; } $.post('/accounts/delete/' + $card.data('id'), {csrf_token: csrf}) .done(function () { window.location.reload(); }) .fail(function () { window.alert('Не удалось архивировать счёт'); }); }); // «Редактировать» на карточке копилки — просто .js-account-edit (goal-card несёт класс // .room-card и тот же набор data-*, см. index.html), отдельного обработчика не нужно. // «Удалить» на карточке копилки с целью — убирает только цель, счёт остаётся (для копилки // без цели там же используется обычный .js-account-delete, архивирующий сам счёт). $(document).on('click', '.js-goal-delete', function (e) { e.preventDefault(); var $card = $(this).closest('.goal-card'); var csrf = $form.find('[name=csrf_token]').val(); if (!window.confirm('Удалить цель? Сам счёт останется.')) { return; } $.post('/goals/delete/' + $card.data('goal-id'), {csrf_token: csrf}) .done(function () { window.location.reload(); }) .fail(function () { window.alert('Не удалось удалить цель'); }); }); $(document).on('click', '#acc-type-tiles .pm-tile', function () { if ($form.data('mode') === 'edit') { return; } var type = $(this).data('type'); if (type === 'credit') { openCreditAfterHide = true; accountModal.hide(); return; } setType(type, $form.data('mode') === 'edit'); }); $(document).on('click', '#acc-color-swatch', function (e) { e.stopPropagation(); $('#acc-color-picker').toggle(); }); $(document).on('click', '.acc-color-swatch-opt', function () { var color = $(this).data('color'); hasColorOverride = true; $colorHidden.val(color); applyPreview(color, $('#acc-type-tiles .pm-tile.selected').data('icon')); $('#acc-color-picker').hide(); }); $(document).on('click', function (e) { if (!$(e.target).closest('#acc-color-swatch, #acc-color-picker').length) { $('#acc-color-picker').hide(); } }); // ---------- модалка «Новый кредит» (вёрстка-заглушка, см. App/design_handoff_personal_finance) ---------- $(document).on('click', '#credit-mode-tiles .pm-tile', function () { $('#credit-mode-tiles .pm-tile').removeClass('selected'); $(this).addClass('selected'); var isExisting = $(this).data('mode') === 'existing'; $('#credit-existing-hint').toggle(isExisting); $('#credit-existing-fields').css('display', isExisting ? 'grid' : 'none'); }); $(document).on('click', '#credit-type-tiles .pm-tile', function () { $('#credit-type-tiles .pm-tile').removeClass('selected'); $(this).addClass('selected'); var isCard = $(this).data('credit-type') === 'card'; $('#credit-card-fields').toggle(isCard); $('#credit-noncard-fields').toggle(!isCard); $('#credit-min-hint').html( 'Минимум нужно: ' + (isCard ? 'название, лимит, ставка' : 'название, сумма, ставка и срок или платёж') + '' ); }); $(document).on('submit', '#account-form', function (e) { e.preventDefault(); $error.hide().text(''); $.post($form.attr('action'), $form.serialize()) .done(function () { window.location.reload(); }) .fail(function (xhr) { var errors = xhr.responseJSON && xhr.responseJSON.errors; var message = errors ? Object.values(errors).join(' ') : 'Не удалось сохранить счёт'; $error.text(message).show(); }); }); $(document).on('click', '.room-menu-btn', function () { $(this).closest('.room-menu').toggleClass('open'); }); $(document).on('click', function (e) { if (!$(e.target).closest('.room-menu').length) { $('.room-menu.open').removeClass('open'); } }); // ---------- ручная сортировка счетов (drag & drop) ---------- // Одна и та же кнопка включает режим (карточки становятся draggable, ⋮-меню временно // отключено — см. CSS .reorder-mode .room-menu) и выключает его же, сохраняя итоговый // порядок одним запросом на /accounts/reorder. var $accountsGrid = $('.grid-cards').first(); var reorderMode = false; var $dragging = null; function saveAccountOrder() { var ids = $accountsGrid.find('.room-card').map(function () { return $(this).data('id'); }).get(); var csrf = $form.find('[name=csrf_token]').val(); $.post('/accounts/reorder', {csrf_token: csrf, ids: ids}); } $(document).on('click', '.js-account-reorder-toggle', function () { reorderMode = !reorderMode; $accountsGrid.toggleClass('reorder-mode', reorderMode); $accountsGrid.find('.room-card').attr('draggable', reorderMode); $(this).toggleClass('active', reorderMode).attr( 'title', reorderMode ? 'Готово' : 'Изменить порядок счетов' ); if (!reorderMode) { saveAccountOrder(); } }); $(document).on('dragstart', '.room-card', function (e) { if (!reorderMode) { return; } $dragging = $(this).addClass('dragging'); e.originalEvent.dataTransfer.effectAllowed = 'move'; e.originalEvent.dataTransfer.setData('text/plain', String($(this).data('id'))); }); $(document).on('dragover', '.room-card', function (e) { if (!reorderMode || !$dragging) { return; } e.preventDefault(); var $target = $(this); if ($target.is($dragging)) { return; } if ($dragging.index() < $target.index()) { $target.after($dragging); } else { $target.before($dragging); } }); $(document).on('dragend', '.room-card', function () { $(this).removeClass('dragging'); $dragging = null; }); });