</div>
<?php
-$createFormUrl = Url::to(['/product-mapping/create-form']);
-$updateFormUrl = Url::to(['/product-mapping/update-form']);
-$createUrl = Url::to(['/product-mapping/create']);
-$updateUrl = Url::to(['/product-mapping/update']);
-$deleteUrl = Url::to(['/product-mapping/delete']);
-$indexUrl = Url::to(['/product-mapping/index']);
-$analyticsUrl = Url::to(['/product-mapping/analytics']);
-$cascadeUrl = Url::to(['/product-mapping/cascade-filters']);
-$exportUrl = Url::to(['/product-mapping/export']);
-
-$js = <<<JS
-(function() {
- var pmModal = new bootstrap.Modal(document.getElementById('pm-modal'));
- var editingId = null;
- var searchTimer = null;
- var loaderHtml = '<div class="text-center p-4"><i class="fa fa-spinner fa-spin fa-2x text-secondary"></i></div>';
-
- function collectFilters() {
- var params = {};
- $('.pm-filter').each(function() {
- var \$el = $(this);
- var name = \$el.attr('name');
- if (!name) return;
- if (\$el.is(':checkbox')) {
- if (\$el.is(':checked')) {
- params[name] = '1';
- }
- } else {
- var v = \$el.val();
- if (v !== '' && v !== null) {
- params[name] = v;
- }
- }
- });
- params['per-page'] = $('#pm-per-page').val();
- return params;
- }
-
- function reloadList(extraParams) {
- var params = collectFilters();
- if (extraParams) {
- \$.extend(params, extraParams);
- }
- var \$tab = $('#tab-mappings');
- \$tab.html('<div class="text-muted p-4 text-center"><i class="fa fa-spinner fa-spin me-2"></i>Загрузка...</div>');
- \$.get('{$indexUrl}', params, function(html) {
- \$tab.html(html);
- });
- }
-
- function reloadAnalytics() {
- var params = collectFilters();
- delete params['per-page'];
- \$.get('{$analyticsUrl}', params, function(data) {
- \$.each(data, function(field, value) {
- $('[data-analytics-field="' + field + '"]').text(value);
- });
- }, 'json');
- }
-
- // ========= ФИЛЬТРЫ =========
-
- // Смена фильтров — reload списка
- $(document).on('change', '.pm-filter', function() {
- var name = $(this).attr('name');
-
- // Каскадное обновление категорий
- if (name === 'category') {
- var category = $(this).val();
- $('#pm-filter-subcategory').html('<option value="">Все</option>').prop('disabled', !category);
- $('#pm-filter-species').html('<option value="">Все</option>').prop('disabled', true);
- if (category) {
- \$.get('{$cascadeUrl}', {category: category}, function(data) {
- var html = '<option value="">Все</option>';
- \$.each(data.subcategories, function(_, sub) {
- html += '<option value="' + sub + '">' + sub + '</option>';
- });
- $('#pm-filter-subcategory').html(html).prop('disabled', false);
- }, 'json');
- }
- } else if (name === 'subcategory') {
- var cat = $('#pm-filter-category').val();
- var sub = $(this).val();
- $('#pm-filter-species').html('<option value="">Все</option>').prop('disabled', !sub);
- if (cat && sub) {
- \$.get('{$cascadeUrl}', {category: cat, subcategory: sub}, function(data) {
- var html = '<option value="">Все</option>';
- \$.each(data.species, function(_, sp) {
- html += '<option value="' + sp + '">' + sp + '</option>';
- });
- $('#pm-filter-species').html(html).prop('disabled', false);
- }, 'json');
- }
- }
-
- // Для поиска по названию — debounce
- if (name === 'search') return;
-
- reloadList({page: 1});
- });
-
- // Поиск — с debounce 400мс
- $(document).on('input', 'input[name="search"].pm-filter', function() {
- clearTimeout(searchTimer);
- searchTimer = setTimeout(function() {
- reloadList({page: 1});
- }, 400);
- });
-
- // Сброс фильтров
- $(document).on('click', '#pm-filter-reset', function() {
- $('.pm-filter').each(function() {
- var \$el = $(this);
- if (\$el.is(':checkbox')) {
- \$el.prop('checked', false);
- } else if (\$el.is('select')) {
- \$el.val('');
- } else {
- \$el.val('');
- }
- });
- reloadList({page: 1});
- });
-
- // Смена per-page
- $('#pm-per-page').on('change', function() {
- reloadList({page: 1});
- });
-
- // Экспорт в .xlsx — редирект с текущими фильтрами
- $(document).on('click', '#pm-export-btn', function() {
- var params = collectFilters();
- delete params['per-page'];
- var qs = $.param(params);
- window.location.href = '{$exportUrl}' + (qs ? '?' + qs : '');
- });
-
- // Пагинация — перехват ссылок
- $(document).on('click', '.pm-pager a', function(e) {
- e.preventDefault();
- var href = $(this).attr('href');
- var pageMatch = href.match(/[?&]page=(\d+)/);
- var page = pageMatch ? pageMatch[1] : 1;
- reloadList({page: page});
- });
-
- // ========= МОДАЛКА МАППИНГА =========
-
- $(document).on('click', '.btn-pm-add', function() {
- var \$btn = $(this);
- if (\$btn.prop('disabled')) return;
- \$btn.prop('disabled', true);
- editingId = null;
- var guid = \$btn.data('product-guid');
- $('#pm-modal-title').text('Добавить поставщика');
- $('#pm-modal-body').html(loaderHtml);
- pmModal.show();
- \$.get('{$createFormUrl}', {product_guid: guid}, function(html) {
- $('#pm-modal-body').html(html);
- }).always(function() {
- \$btn.prop('disabled', false);
- });
- });
-
- $(document).on('click', '.btn-pm-edit', function(e) {
- e.preventDefault();
- var \$btn = $(this);
- if (\$btn.prop('disabled')) return;
- \$btn.prop('disabled', true);
- editingId = \$btn.data('id');
- $('#pm-modal-title').text('Редактировать маппинг');
- $('#pm-modal-body').html(loaderHtml);
- pmModal.show();
- \$.get('{$updateFormUrl}', {id: editingId}, function(html) {
- $('#pm-modal-body').html(html);
- }).always(function() {
- \$btn.prop('disabled', false);
- });
- });
-
- $(document).on('click', '#btn-mapping-save', function() {
- var \$form = $('#mapping-form');
- var url = editingId ? '{$updateUrl}?id=' + editingId : '{$createUrl}';
-
- \$form.find('.is-invalid').removeClass('is-invalid');
- \$form.find('.invalid-feedback').remove();
-
- \$.ajax({
- url: url,
- type: 'POST',
- data: \$form.serialize(),
- dataType: 'json',
- success: function(resp) {
- if (resp.success) {
- pmModal.hide();
- reloadList();
- } else if (resp.errors) {
- \$.each(resp.errors, function(field, messages) {
- var \$input = \$form.find('[name="ProductMapping[' + field + ']"]');
- if (!\$input.length) {
- \$input = \$form.find('[name="ProductMapping[' + field + '][]"]');
- }
- \$input.addClass('is-invalid');
- \$input.after('<div class="invalid-feedback">' + messages[0] + '</div>');
- });
- }
- },
- error: function() { alert('Ошибка сервера'); }
- });
- });
-
- $(document).on('click', '.btn-pm-delete', function(e) {
- e.preventDefault();
- var id = $(this).data('id');
- var name = $(this).data('name');
-
- if (!confirm('Удалить маппинг на поставщика "' + name + '"?')) {
- return;
- }
-
- \$.ajax({
- url: '{$deleteUrl}?id=' + id,
- type: 'POST',
- data: {_csrf: yii.getCsrfToken()},
- dataType: 'json',
- success: function(resp) {
- if (resp.success) {
- reloadList();
- } else {
- alert(resp.message || 'Ошибка удаления');
- }
- },
- error: function() { alert('Ошибка сервера'); }
- });
- });
-
- // Очистка ошибок при вводе
- $(document).on('input change', '#mapping-form input, #mapping-form select', function() {
- $(this).removeClass('is-invalid');
- $(this).next('.invalid-feedback').remove();
- });
-})();
-JS;
-
-$this->registerJs($js);
+$this->registerJs('window.pmCfg = ' . \yii\helpers\Json::encode([
+ 'urls' => [
+ 'createForm' => Url::to(['/product-mapping/create-form']),
+ 'updateForm' => Url::to(['/product-mapping/update-form']),
+ 'create' => Url::to(['/product-mapping/create']),
+ 'update' => Url::to(['/product-mapping/update']),
+ 'delete' => Url::to(['/product-mapping/delete']),
+ 'index' => Url::to(['/product-mapping/index']),
+ 'analytics' => Url::to(['/product-mapping/analytics']),
+ 'cascade' => Url::to(['/product-mapping/cascade-filters']),
+ 'export' => Url::to(['/product-mapping/export']),
+ ],
+]) . ';');
+
+$this->registerJsFile('/js/product-mapping/index.js', ['position' => \yii\web\View::POS_END]);
?>
--- /dev/null
+/* global bootstrap, yii */
+(function () {
+ var cfg = window.pmCfg;
+ if (!cfg) return;
+
+ // Снимаем все предыдущие обработчики этого модуля с document,
+ // чтобы предотвратить накопление при каждом reloadList().
+ var NS = '.pmapping';
+ $(document).off(NS);
+
+ var pmModal = new bootstrap.Modal(document.getElementById('pm-modal'));
+ var editingId = null;
+ var searchTimer = null;
+ var loaderHtml = '<div class="text-center p-4"><i class="fa fa-spinner fa-spin fa-2x text-secondary"></i></div>';
+
+ function collectFilters() {
+ var params = {};
+ $('.pm-filter').each(function () {
+ var $el = $(this);
+ var name = $el.attr('name');
+ if (!name) return;
+ if ($el.is(':checkbox')) {
+ if ($el.is(':checked')) {
+ params[name] = '1';
+ }
+ } else {
+ var v = $el.val();
+ if (v !== '' && v !== null) {
+ params[name] = v;
+ }
+ }
+ });
+ params['per-page'] = $('#pm-per-page').val();
+ return params;
+ }
+
+ function reloadList(extraParams) {
+ var params = collectFilters();
+ if (extraParams) {
+ $.extend(params, extraParams);
+ }
+ var $tab = $('#tab-mappings');
+ $tab.html('<div class="text-muted p-4 text-center"><i class="fa fa-spinner fa-spin me-2"></i>Загрузка...</div>');
+ $.get(cfg.urls.index, params, function (html) {
+ $tab.html(html);
+ });
+ }
+
+ function reloadAnalytics() {
+ var params = collectFilters();
+ delete params['per-page'];
+ $.get(cfg.urls.analytics, params, function (data) {
+ $.each(data, function (field, value) {
+ $('[data-analytics-field="' + field + '"]').text(value);
+ });
+ }, 'json');
+ }
+
+ // ========= ФИЛЬТРЫ =========
+
+ $(document).on('change' + NS, '.pm-filter', function () {
+ var name = $(this).attr('name');
+
+ if (name === 'category') {
+ var category = $(this).val();
+ $('#pm-filter-subcategory').html('<option value="">Все</option>').prop('disabled', !category);
+ $('#pm-filter-species').html('<option value="">Все</option>').prop('disabled', true);
+ if (category) {
+ $.get(cfg.urls.cascade, {category: category}, function (data) {
+ var html = '<option value="">Все</option>';
+ $.each(data.subcategories, function (_, sub) {
+ html += '<option value="' + sub + '">' + sub + '</option>';
+ });
+ $('#pm-filter-subcategory').html(html).prop('disabled', false);
+ }, 'json');
+ }
+ reloadList({page: 1});
+ return;
+ }
+
+ if (name === 'subcategory') {
+ var cat = $('#pm-filter-category').val();
+ var sub = $(this).val();
+ $('#pm-filter-species').html('<option value="">Все</option>').prop('disabled', !sub);
+ if (cat && sub) {
+ $.get(cfg.urls.cascade, {category: cat, subcategory: sub}, function (data) {
+ var html = '<option value="">Все</option>';
+ $.each(data.species, function (_, sp) {
+ html += '<option value="' + sp + '">' + sp + '</option>';
+ });
+ $('#pm-filter-species').html(html).prop('disabled', false);
+ }, 'json');
+ }
+ reloadList({page: 1});
+ return;
+ }
+
+ if (name === 'search') return;
+
+ reloadList({page: 1});
+ });
+
+ // Поиск — с debounce 400мс
+ $(document).on('input' + NS, 'input[name="search"].pm-filter', function () {
+ clearTimeout(searchTimer);
+ searchTimer = setTimeout(function () {
+ reloadList({page: 1});
+ }, 400);
+ });
+
+ // Сброс фильтров
+ $(document).on('click' + NS, '#pm-filter-reset', function () {
+ $('.pm-filter').each(function () {
+ var $el = $(this);
+ if ($el.is(':checkbox')) {
+ $el.prop('checked', false);
+ } else if ($el.is('select')) {
+ $el.val('');
+ } else {
+ $el.val('');
+ }
+ });
+ reloadList({page: 1});
+ });
+
+ // Смена per-page
+ $('#pm-per-page').on('change', function () {
+ reloadList({page: 1});
+ });
+
+ // Экспорт в .xlsx — редирект с текущими фильтрами
+ $(document).on('click' + NS, '#pm-export-btn', function () {
+ var params = collectFilters();
+ delete params['per-page'];
+ var qs = $.param(params);
+ window.location.href = cfg.urls.export + (qs ? '?' + qs : '');
+ });
+
+ // Пагинация — перехват ссылок
+ $(document).on('click' + NS, '.pm-pager a', function (e) {
+ e.preventDefault();
+ var href = $(this).attr('href');
+ var pageMatch = href.match(/[?&]page=(\d+)/);
+ var page = pageMatch ? pageMatch[1] : 1;
+ reloadList({page: page});
+ });
+
+ // ========= МОДАЛКА МАППИНГА =========
+
+ $(document).on('click' + NS, '.btn-pm-add', function () {
+ var $btn = $(this);
+ if ($btn.prop('disabled')) return;
+ $btn.prop('disabled', true);
+ editingId = null;
+ var guid = $btn.data('product-guid');
+ $('#pm-modal-title').text('Добавить поставщика');
+ $('#pm-modal-body').html(loaderHtml);
+ pmModal.show();
+ $.get(cfg.urls.createForm, {product_guid: guid}, function (html) {
+ $('#pm-modal-body').html(html);
+ }).always(function () {
+ $btn.prop('disabled', false);
+ });
+ });
+
+ $(document).on('click' + NS, '.btn-pm-edit', function (e) {
+ e.preventDefault();
+ var $btn = $(this);
+ if ($btn.prop('disabled')) return;
+ $btn.prop('disabled', true);
+ editingId = $btn.data('id');
+ $('#pm-modal-title').text('Редактировать маппинг');
+ $('#pm-modal-body').html(loaderHtml);
+ pmModal.show();
+ $.get(cfg.urls.updateForm, {id: editingId}, function (html) {
+ $('#pm-modal-body').html(html);
+ }).always(function () {
+ $btn.prop('disabled', false);
+ });
+ });
+
+ $(document).on('click' + NS, '#btn-mapping-save', function () {
+ var $form = $('#mapping-form');
+ var url = editingId ? cfg.urls.update + '?id=' + editingId : cfg.urls.create;
+
+ $form.find('.is-invalid').removeClass('is-invalid');
+ $form.find('.invalid-feedback').remove();
+
+ $.ajax({
+ url: url,
+ type: 'POST',
+ data: $form.serialize(),
+ dataType: 'json',
+ success: function (resp) {
+ if (resp.success) {
+ pmModal.hide();
+ reloadList();
+ } else if (resp.errors) {
+ $.each(resp.errors, function (field, messages) {
+ var $input = $form.find('[name="ProductMapping[' + field + ']"]');
+ if (!$input.length) {
+ $input = $form.find('[name="ProductMapping[' + field + '][]"]');
+ }
+ $input.addClass('is-invalid');
+ $input.after('<div class="invalid-feedback">' + messages[0] + '</div>');
+ });
+ }
+ },
+ error: function () { alert('Ошибка сервера'); }
+ });
+ });
+
+ $(document).on('click' + NS, '.btn-pm-delete', function (e) {
+ e.preventDefault();
+ var id = $(this).data('id');
+ var name = $(this).data('name');
+
+ if (!confirm('Удалить маппинг на поставщика "' + name + '"?')) {
+ return;
+ }
+
+ $.ajax({
+ url: cfg.urls.delete + '?id=' + id,
+ type: 'POST',
+ data: {_csrf: yii.getCsrfToken()},
+ dataType: 'json',
+ success: function (resp) {
+ if (resp.success) {
+ reloadList();
+ } else {
+ alert(resp.message || 'Ошибка удаления');
+ }
+ },
+ error: function () { alert('Ошибка сервера'); }
+ });
+ });
+
+ // Очистка ошибок при вводе
+ $(document).on('input change' + NS, '#mapping-form input, #mapping-form select', function () {
+ $(this).removeClass('is-invalid');
+ $(this).next('.invalid-feedback').remove();
+ });
+}());