<?= $plantation->getStatusBadge() ?>
</td>
<td style="text-align:center;white-space:nowrap;">
- <a href="#" class="btn-plantation-edit" data-id="<?= (int)$plantation->id ?>" title="Редактировать плантацию">
- <i class="fa fa-pencil text-secondary"></i>
- </a>
- <?php if ($plantation->is_active): ?>
+ <?php if ($plantation->is_active && $producer->is_active): ?>
+ <a href="#" class="btn-plantation-edit" data-id="<?= (int)$plantation->id ?>" title="Редактировать плантацию">
+ <i class="fa fa-pencil text-secondary"></i>
+ </a>
+ <?php else: ?>
+ <i class="fa fa-pencil text-secondary" style="opacity:0.3;cursor:default;" title="Недоступно для деактивированной записи"></i>
+ <?php endif; ?>
+ <?php if ($plantation->is_active && $producer->is_active): ?>
<a href="#" class="btn-plantation-delete ms-1"
data-id="<?= (int)$plantation->id ?>"
data-name="<?= Html::encode($plantation->name) ?>"
$js = <<<JS
(function() {
+ // Снимаем старые обработчики чтобы не накапливать их при каждом reloadProducerTab()
+ $(document).off('.producertab');
+
+ var _bsModal = null;
+ var shouldReload = false;
+ var editingProducerId = null;
+ var editingPlantationId = null;
+ var loaderHtml = '<div class="text-center p-4"><i class="fa fa-spinner fa-spin fa-2x text-secondary"></i></div>';
+
+ var modalEl = document.getElementById('producer-modal');
+
+ function getModal() {
+ if (!_bsModal) {
+ _bsModal = new bootstrap.Modal(modalEl);
+ }
+ return _bsModal;
+ }
+
+ if (modalEl) {
+ // Перезагружаем вкладку только после полного закрытия модала —
+ // иначе Bootstrap не успевает убрать backdrop до замены DOM.
+ modalEl.addEventListener('hidden.bs.modal', function() {
+ if (shouldReload) {
+ shouldReload = false;
+ reloadProducerTab();
+ }
+ });
+ }
+
function reloadProducerTab() {
$.get('{$reloadUrl}', function(html) {
- // Заменяем содержимое вкладки целиком свежей копией (вместе с pjax, модалкой и JS).
$('#tab-producers').html(html);
});
}
- var producerModal = new bootstrap.Modal(document.getElementById('producer-modal'));
- var editingProducerId = null;
- var editingPlantationId = null;
- var loaderHtml = '<div class="text-center p-4"><i class="fa fa-spinner fa-spin fa-2x text-secondary"></i></div>';
-
// ========== PRODUCER ==========
$('#btn-producer-create').on('click', function() {
if (\$btn.prop('disabled')) return;
\$btn.prop('disabled', true);
editingProducerId = null;
+ editingPlantationId = null;
$('#producer-modal-title').text('Добавить производителя');
$('#producer-modal-body').html(loaderHtml);
- producerModal.show();
+ getModal().show();
$.get('{$createProducerFormUrl}', function(html) {
$('#producer-modal-body').html(html);
}).always(function() {
});
});
- $(document).on('click', '.btn-producer-edit', function(e) {
+ $(document).on('click.producertab', '.btn-producer-edit', function(e) {
e.preventDefault();
var \$btn = $(this);
if (\$btn.prop('disabled')) return;
\$btn.prop('disabled', true);
editingProducerId = \$btn.data('id');
+ editingPlantationId = null;
$('#producer-modal-title').text('Редактировать производителя');
$('#producer-modal-body').html(loaderHtml);
- producerModal.show();
+ getModal().show();
$.get('{$updateProducerFormUrl}', {id: editingProducerId}, function(html) {
$('#producer-modal-body').html(html);
}).always(function() {
});
});
- $(document).on('click', '#btn-producer-save', function() {
+ $(document).on('click.producertab', '#btn-producer-save', function() {
+ var \$saveBtn = $(this);
+ if (\$saveBtn.prop('disabled')) return;
+ \$saveBtn.prop('disabled', true);
+
var \$form = $('#producer-form');
var url = editingProducerId ? '{$updateProducerUrl}?id=' + editingProducerId : '{$createProducerUrl}';
dataType: 'json',
success: function(resp) {
if (resp.success) {
- producerModal.hide();
- reloadProducerTab();
+ shouldReload = true;
+ getModal().hide();
} else if (resp.errors) {
+ \$saveBtn.prop('disabled', false);
$.each(resp.errors, function(field, messages) {
var \$input = \$form.find('[name="Producer[' + field + ']"]');
\$input.addClass('is-invalid');
\$input.after('<div class="invalid-feedback">' + messages[0] + '</div>');
});
+ } else {
+ \$saveBtn.prop('disabled', false);
+ alert(resp.message || 'Ошибка сохранения');
}
},
- error: function() { alert('Ошибка сервера'); }
+ error: function() {
+ \$saveBtn.prop('disabled', false);
+ alert('Ошибка сервера');
+ }
});
});
- $(document).on('click', '.btn-producer-delete', function(e) {
+ $(document).on('click.producertab', '.btn-producer-delete', function(e) {
e.preventDefault();
var id = $(this).data('id');
var name = $(this).data('name');
if (\$btn.prop('disabled')) return;
\$btn.prop('disabled', true);
editingPlantationId = null;
+ editingProducerId = null;
$('#producer-modal-title').text('Добавить плантацию');
$('#producer-modal-body').html(loaderHtml);
- producerModal.show();
+ getModal().show();
$.get('{$createPlantationFormUrl}', function(html) {
$('#producer-modal-body').html(html);
}).always(function() {
});
});
- $(document).on('click', '.btn-plantation-edit', function(e) {
+ $(document).on('click.producertab', '.btn-plantation-edit', function(e) {
e.preventDefault();
var \$btn = $(this);
if (\$btn.prop('disabled')) return;
\$btn.prop('disabled', true);
editingPlantationId = \$btn.data('id');
+ editingProducerId = null;
$('#producer-modal-title').text('Редактировать плантацию');
$('#producer-modal-body').html(loaderHtml);
- producerModal.show();
+ getModal().show();
$.get('{$updatePlantationFormUrl}', {id: editingPlantationId}, function(html) {
$('#producer-modal-body').html(html);
}).always(function() {
});
});
- $(document).on('click', '#btn-plantation-save', function() {
+ $(document).on('click.producertab', '#btn-plantation-save', function() {
+ var \$saveBtn = $(this);
+ if (\$saveBtn.prop('disabled')) return;
+ \$saveBtn.prop('disabled', true);
+
var \$form = $('#plantation-form');
var url = editingPlantationId ? '{$updatePlantationUrl}?id=' + editingPlantationId : '{$createPlantationUrl}';
dataType: 'json',
success: function(resp) {
if (resp.success) {
- producerModal.hide();
- reloadProducerTab();
+ shouldReload = true;
+ getModal().hide();
} else if (resp.errors) {
+ \$saveBtn.prop('disabled', false);
$.each(resp.errors, function(field, messages) {
var \$input = \$form.find('[name="Plantation[' + field + ']"]');
\$input.addClass('is-invalid');
\$input.after('<div class="invalid-feedback">' + messages[0] + '</div>');
});
+ } else {
+ \$saveBtn.prop('disabled', false);
+ alert(resp.message || 'Ошибка сохранения');
}
},
- error: function() { alert('Ошибка сервера'); }
+ error: function() {
+ \$saveBtn.prop('disabled', false);
+ alert('Ошибка сервера');
+ }
});
});
- $(document).on('click', '.btn-plantation-delete', function(e) {
+ $(document).on('click.producertab', '.btn-plantation-delete', function(e) {
e.preventDefault();
var id = $(this).data('id');
var name = $(this).data('name');
});
// Очистка ошибок при вводе
- $(document).on('input change', '#producer-form input, #producer-form select, #plantation-form input, #plantation-form select', function() {
+ $(document).on('input.producertab change.producertab', '#producer-form input, #producer-form select, #plantation-form input, #plantation-form select', function() {
$(this).removeClass('is-invalid');
$(this).next('.invalid-feedback').remove();
});
/**
* Budget module — JS logic.
* Прогресс-бары, AJAX CRUD, approval workflow.
+ *
+ * Конфигурация: window.budgetConfig = { urls: { getProgress, save, requestApproval, resolveApproval } }
+ * Публичный API: window.budgetApp.{ saveBudget, requestApproval, resolveApproval, openApprovalModal }
*/
+(function () {
+ var cfg = window.budgetConfig || {};
+ var urls = cfg.urls || {
+ getProgress: '/budget/get-progress',
+ save: '/budget/save',
+ requestApproval: '/budget/request-approval',
+ resolveApproval: '/budget/resolve-approval'
+ };
+
+ var _autoRefreshTimer = null;
+
+ /* ── Helpers ─────────────────────────────────── */
-// CSRF token для AJAX POST-запросов
-$.ajaxSetup({
- headers: {
- 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
+ function escapeHtml(text) {
+ var div = document.createElement('div');
+ div.appendChild(document.createTextNode(String(text)));
+ return div.innerHTML;
}
-});
-
-function loadProgress() {
- var category = document.getElementById('filter-category').value;
- var periodStart = document.getElementById('filter-period').value;
-
- var url = '/budget/get-progress?';
- if (category) url += 'category=' + encodeURIComponent(category) + '&';
- if (periodStart) url += 'period_start=' + encodeURIComponent(periodStart);
-
- $.ajax({
- url: url,
- type: 'GET',
- dataType: 'json',
- success: function (response) {
- if (response.success) {
- renderProgressBars(response.data);
- renderBudgetTable(response.data);
- }
- },
- error: function () {
- console.error('Ошибка загрузки прогресса бюджета');
- }
- });
-}
-
-function renderProgressBars(budgets) {
- var container = document.getElementById('progress-container');
- container.innerHTML = '';
-
- budgets.forEach(function (b) {
- var pct = b.usage_pct;
- var color = '#28a745';
- if (b.is_exceeded) color = '#dc3545';
- else if (b.is_alert) color = '#ffc107';
-
- var html = '<div class="col-md-6 mb-3">' +
- '<div class="card"><div class="card-body">' +
- '<h6 class="card-title">' + escapeHtml(b.category) + ' ' + b.period_start + ' — ' + b.period_end + '</h6>' +
- '<div class="progress" style="height:30px;">' +
- '<div class="progress-bar" style="width:' + Math.min(pct, 100) + '%;background-color:' + color + ';">' +
- pct + '%</div></div>' +
- '<div class="mt-1 text-muted">' +
- formatNumber(b.used_amount) + ' ₽ / ' + formatNumber(b.limit_amount) + ' ₽ | Остаток: ' + formatNumber(b.remaining) + ' ₽</div>' +
- '</div></div></div>';
-
- container.innerHTML += html;
- });
-}
-
-function renderBudgetTable(budgets) {
- var tbody = document.querySelector('#budget-table tbody');
- tbody.innerHTML = '';
-
- budgets.forEach(function (b) {
- var statusBadge;
- if (b.is_exceeded) {
- statusBadge = '<span class="badge bg-danger">Превышен</span>';
- } else if (b.is_alert) {
- statusBadge = '<span class="badge bg-warning text-dark">Предупреждение</span>';
- } else {
- statusBadge = '<span class="badge bg-success">Норма</span>';
- }
- var row = '<tr>' +
- '<td>' + b.period_start + ' — ' + b.period_end + '</td>' +
- '<td>' + escapeHtml(b.category) + '</td>' +
- '<td>' + formatNumber(b.limit_amount) + '</td>' +
- '<td>' + formatNumber(b.used_amount) + '</td>' +
- '<td>' + b.usage_pct + '%</td>' +
- '<td>' + b.alert_threshold_pct + '%</td>' +
- '<td>' + statusBadge + '</td>' +
- '</tr>';
-
- tbody.innerHTML += row;
- });
-}
-
-function saveBudget() {
- var data = {
- category: document.getElementById('form-category').value,
- period_start: document.getElementById('form-period-start').value,
- limit_amount: document.getElementById('form-limit').value,
- alert_threshold_pct: document.getElementById('form-threshold').value
- };
+ function formatNumber(n) {
+ return new Intl.NumberFormat('ru-RU', {minimumFractionDigits: 0, maximumFractionDigits: 0}).format(n);
+ }
+
+ function csrfHeader() {
+ var token = document.querySelector('meta[name="csrf-token"]');
+ return token ? {'X-CSRF-Token': token.getAttribute('content')} : {};
+ }
- if (!data.category || !data.period_start || !data.limit_amount) {
- alert('Заполните все обязательные поля');
- return;
+ function getModalInstance(id) {
+ var el = document.getElementById(id);
+ if (!el) return null;
+ return bootstrap.Modal.getOrCreateInstance(el);
}
- // Проверка: понедельник
- var d = new Date(data.period_start);
- if (d.getDay() !== 1) {
- alert('Период должен начинаться с понедельника');
- return;
+ /* ── Rendering ───────────────────────────────── */
+
+ function renderProgressBars(budgets) {
+ var container = document.getElementById('progress-container');
+ if (!container) return;
+
+ var parts = [];
+ budgets.forEach(function (b) {
+ var pct = b.usage_pct;
+ var color = b.is_exceeded ? '#dc3545' : b.is_alert ? '#ffc107' : '#28a745';
+ parts.push(
+ '<div class="col-md-6 mb-3">' +
+ '<div class="card"><div class="card-body">' +
+ '<h6 class="card-title">' + escapeHtml(b.category) + ' ' + escapeHtml(b.period_start) + ' — ' + escapeHtml(b.period_end) + '</h6>' +
+ '<div class="progress" style="height:30px;">' +
+ '<div class="progress-bar" style="width:' + Math.min(pct, 100) + '%;background-color:' + color + ';">' +
+ escapeHtml(pct) + '%</div></div>' +
+ '<div class="mt-1 text-muted">' +
+ formatNumber(b.used_amount) + ' ₽ / ' + formatNumber(b.limit_amount) + ' ₽ | Остаток: ' + formatNumber(b.remaining) + ' ₽</div>' +
+ '</div></div></div>'
+ );
+ });
+ container.innerHTML = parts.join('');
}
- $.ajax({
- url: '/budget/save',
- type: 'POST',
- data: data,
- dataType: 'json',
- success: function (response) {
- if (response.success) {
- var modal = bootstrap.Modal.getInstance(document.getElementById('budgetFormModal'));
- if (modal) modal.hide();
- loadProgress();
- } else {
- alert(response.message || 'Ошибка сохранения');
+ function renderBudgetTable(budgets) {
+ var tbody = document.querySelector('#budget-table tbody');
+ if (!tbody) return;
+
+ var rows = [];
+ budgets.forEach(function (b) {
+ var statusBadge = b.is_exceeded
+ ? '<span class="badge bg-danger">Превышен</span>'
+ : b.is_alert
+ ? '<span class="badge bg-warning text-dark">Предупреждение</span>'
+ : '<span class="badge bg-success">Норма</span>';
+
+ rows.push(
+ '<tr>' +
+ '<td>' + escapeHtml(b.period_start) + ' — ' + escapeHtml(b.period_end) + '</td>' +
+ '<td>' + escapeHtml(b.category) + '</td>' +
+ '<td>' + formatNumber(b.limit_amount) + '</td>' +
+ '<td>' + formatNumber(b.used_amount) + '</td>' +
+ '<td>' + escapeHtml(b.usage_pct) + '%</td>' +
+ '<td>' + escapeHtml(b.alert_threshold_pct) + '%</td>' +
+ '<td>' + statusBadge + '</td>' +
+ '</tr>'
+ );
+ });
+ tbody.innerHTML = rows.join('');
+ }
+
+ /* ── Core ────────────────────────────────────── */
+
+ function loadProgress() {
+ var categoryEl = document.getElementById('filter-category');
+ var periodEl = document.getElementById('filter-period');
+ if (!categoryEl || !periodEl) return;
+
+ var params = {};
+ if (categoryEl.value) params.category = categoryEl.value;
+ if (periodEl.value) params.period_start = periodEl.value;
+
+ $.ajax({
+ url: urls.getProgress,
+ type: 'GET',
+ data: params,
+ dataType: 'json',
+ success: function (response) {
+ if (response.success) {
+ renderProgressBars(response.data);
+ renderBudgetTable(response.data);
+ }
+ },
+ error: function () {
+ console.error('Ошибка загрузки прогресса бюджета');
}
- },
- error: function () {
- alert('Ошибка сервера');
+ });
+ }
+
+ function saveBudget() {
+ var data = {
+ category: document.getElementById('form-category').value,
+ period_start: document.getElementById('form-period-start').value,
+ limit_amount: document.getElementById('form-limit').value,
+ alert_threshold_pct: document.getElementById('form-threshold').value
+ };
+
+ if (!data.category || !data.period_start || !data.limit_amount) {
+ alert('Заполните все обязательные поля');
+ return;
}
- });
-}
-function requestApproval() {
- var budgetId = document.getElementById('approval-budget-id').value;
- var amount = document.getElementById('approval-amount').value;
- var reason = document.getElementById('approval-reason').value;
+ var d = new Date(data.period_start);
+ if (d.getDay() !== 1) {
+ alert('Период должен начинаться с понедельника');
+ return;
+ }
- if (!amount || !reason) {
- alert('Заполните все поля');
- return;
+ $.ajax({
+ url: urls.save,
+ type: 'POST',
+ headers: csrfHeader(),
+ data: data,
+ dataType: 'json',
+ success: function (response) {
+ if (response.success) {
+ var modal = getModalInstance('budgetFormModal');
+ if (modal) modal.hide();
+ loadProgress();
+ } else {
+ alert(response.message || 'Ошибка сохранения');
+ }
+ },
+ error: function () { alert('Ошибка сервера'); }
+ });
}
- $.ajax({
- url: '/budget/request-approval',
- type: 'POST',
- data: {budget_id: budgetId, amount: amount, reason: reason},
- dataType: 'json',
- success: function (response) {
- if (response.success) {
- var modal = bootstrap.Modal.getInstance(document.getElementById('approvalRequestModal'));
- if (modal) modal.hide();
- location.reload();
- } else {
- alert(response.message || 'Ошибка');
- }
- },
- error: function () {
- alert('Ошибка сервера');
+ function requestApproval() {
+ var budgetId = document.getElementById('approval-budget-id').value;
+ var amount = document.getElementById('approval-amount').value;
+ var reason = document.getElementById('approval-reason').value;
+
+ if (!amount || !reason) {
+ alert('Заполните все поля');
+ return;
}
- });
-}
-function resolveApproval(approvalId, status) {
- var action = status === 'approved' ? 'одобрить' : 'отклонить';
- if (!confirm('Вы уверены, что хотите ' + action + ' запрос #' + approvalId + '?')) {
- return;
+ $.ajax({
+ url: urls.requestApproval,
+ type: 'POST',
+ headers: csrfHeader(),
+ data: {budget_id: budgetId, amount: amount, reason: reason},
+ dataType: 'json',
+ success: function (response) {
+ if (response.success) {
+ var modal = getModalInstance('approvalRequestModal');
+ if (modal) modal.hide();
+ location.reload();
+ } else {
+ alert(response.message || 'Ошибка');
+ }
+ },
+ error: function () { alert('Ошибка сервера'); }
+ });
}
- $.ajax({
- url: '/budget/resolve-approval',
- type: 'POST',
- data: {approval_id: approvalId, status: status},
- dataType: 'json',
- success: function (response) {
- if (response.success) {
- location.reload();
- } else {
- alert(response.message || 'Ошибка');
- }
- },
- error: function () {
- alert('Ошибка сервера');
+ function resolveApproval(approvalId, status) {
+ var action = status === 'approved' ? 'одобрить' : 'отклонить';
+ if (!confirm('Вы уверены, что хотите ' + action + ' запрос #' + approvalId + '?')) return;
+
+ $.ajax({
+ url: urls.resolveApproval,
+ type: 'POST',
+ headers: csrfHeader(),
+ data: {approval_id: approvalId, status: status},
+ dataType: 'json',
+ success: function (response) {
+ if (response.success) {
+ location.reload();
+ } else {
+ alert(response.message || 'Ошибка');
+ }
+ },
+ error: function () { alert('Ошибка сервера'); }
+ });
+ }
+
+ function openApprovalModal(budgetId) {
+ document.getElementById('approval-budget-id').value = budgetId;
+ document.getElementById('approval-amount').value = '';
+ document.getElementById('approval-reason').value = '';
+ var modal = getModalInstance('approvalRequestModal');
+ if (modal) modal.show();
+ }
+
+ /* ── Auto-refresh ────────────────────────────── */
+
+ function startAutoRefresh() {
+ if (_autoRefreshTimer) return;
+ _autoRefreshTimer = setInterval(loadProgress, 60000);
+ }
+
+ /* ── Init ────────────────────────────────────── */
+
+ $(function () {
+ // Запускаем авто-обновление только если DOM бюджета присутствует
+ if (document.getElementById('progress-container')) {
+ loadProgress();
+ startAutoRefresh();
}
});
-}
-
-function openApprovalModal(budgetId) {
- document.getElementById('approval-budget-id').value = budgetId;
- document.getElementById('approval-amount').value = '';
- document.getElementById('approval-reason').value = '';
- var modal = new bootstrap.Modal(document.getElementById('approvalRequestModal'));
- modal.show();
-}
-
-// Helpers
-function formatNumber(n) {
- return new Intl.NumberFormat('ru-RU', {minimumFractionDigits: 0, maximumFractionDigits: 0}).format(n);
-}
-
-function escapeHtml(text) {
- var div = document.createElement('div');
- div.appendChild(document.createTextNode(text));
- return div.innerHTML;
-}
-
-// Auto-refresh every 60 seconds
-setInterval(loadProgress, 60000);
+
+ /* ── Public API ──────────────────────────────── */
+
+ window.budgetApp = {
+ saveBudget: saveBudget,
+ requestApproval: requestApproval,
+ resolveApproval: resolveApproval,
+ openApprovalModal: openApprovalModal,
+ loadProgress: loadProgress
+ };
+}());
(function () {
// Единое состояние модуля — живёт пока жива страница
var state = {
- cfg: null, // URL-ы эндпоинтов
- modal: null, // Bootstrap Modal instance
- editingId: null,
- cascadeXhr: null, // in-flight cascade AJAX
- activeXhr: null, // in-flight reloadList AJAX
- searchTimer: null
+ cfg: null, // URL-ы эндпоинтов
+ modal: null, // Bootstrap Modal instance
+ editingId: null,
+ shouldReload: false, // флаг: перезагрузить список после hidden.bs.modal
+ cascadeXhr: null, // in-flight cascade AJAX
+ activeXhr: null, // in-flight reloadList AJAX
+ searchTimer: null
};
/* ───────────────────────────────────────────────
});
}
- function getModal() {
- // Всегда берём актуальный экземпляр Bootstrap Modal
- return state.modal;
- }
-
/* ───────────────────────────────────────────────
pmSetup — вызывается ОДИН РАЗ при загрузке страницы
─────────────────────────────────────────────── */
$btn.prop('disabled', true);
state.editingId = null;
- var modal = getModal();
+ var modal = state.modal;
if (!modal) { $btn.prop('disabled', false); return; }
$('#pm-modal-title').text('Добавить поставщика');
$btn.prop('disabled', true);
state.editingId = $btn.data('id');
- var modal = getModal();
+ var modal = state.modal;
if (!modal) { $btn.prop('disabled', false); return; }
$('#pm-modal-title').text('Редактировать маппинг');
$(document).on('click' + NS, '#btn-mapping-save', function () {
if (!state.cfg) return;
+ var $saveBtn = $(this);
+ if ($saveBtn.prop('disabled')) return;
+ $saveBtn.prop('disabled', true);
+
var $form = $('#mapping-form');
var url = state.editingId
? state.cfg.urls.update + '?id=' + state.editingId
dataType: 'json',
success: function (resp) {
if (resp.success) {
- var modal = getModal();
- if (modal) modal.hide();
- reloadList();
+ state.shouldReload = true;
+ if (state.modal) state.modal.hide();
} else if (resp.errors) {
+ $saveBtn.prop('disabled', false);
$.each(resp.errors, function (field, messages) {
var $input = $form.find('[name="ProductMapping[' + field + ']"]');
if (!$input.length) $input = $form.find('[name="ProductMapping[' + field + '][]"]');
});
}
},
- error: function () { alert('Ошибка сервера'); }
+ error: function () {
+ $saveBtn.prop('disabled', false);
+ alert('Ошибка сервера');
+ }
});
});
window.pmReinit = function (cfg) {
state.cfg = cfg;
+ state.shouldReload = false;
// Корректно уничтожаем старый Modal, чтобы Bootstrap не плодил глобальные слушатели
if (state.modal) {
var el = document.getElementById('pm-modal');
if (el) {
state.modal = new bootstrap.Modal(el);
+ // Перезагружаем список только после полного закрытия модала,
+ // иначе Bootstrap не успевает убрать backdrop до замены DOM.
+ el.addEventListener('hidden.bs.modal', function () {
+ if (state.shouldReload) {
+ state.shouldReload = false;
+ reloadList();
+ }
+ });
}
};
}());