]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Создаем дерево
authorVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Fri, 5 Sep 2025 13:45:14 +0000 (16:45 +0300)
committerVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Fri, 5 Sep 2025 13:45:14 +0000 (16:45 +0300)
erp24/controllers/MatrixTypeController.php
erp24/migrations/m250905_090045_add_parentid_active_fields_to_matrix_type_table.php [new file with mode: 0644]
erp24/records/MatrixType.php
erp24/views/matrix-type/index.php
erp24/web/js/matrix-type/index.js [new file with mode: 0644]

index 0c31759237c335c8702e134f65559eebaaf781fa..389ee1d29b3e71f94d81eb3c5df33a537fea5643 100644 (file)
@@ -92,6 +92,50 @@ class MatrixTypeController extends Controller
         ]);
     }
 
+    public function actionUpdateName($id)
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $model = $this->findModel($id);
+        if (!$model) {
+            return ['success'=>false,'message'=>'Не найдено'];
+        }
+        $model->load(Yii::$app->request->post(), '');
+        if ($model->save()) {
+            return ['success'=>true,'data'=>['name'=>$model->name]];
+        }
+        return ['success'=>false,'message'=>current($model->getFirstErrors()) ?: 'Ошибка сохранения'];
+    }
+
+    public function actionCreateChild($parent_id)
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $child = new MatrixType();
+        $child->load(Yii::$app->request->post(), '');
+        $child->parent_id = (int)$parent_id;
+
+        if ($child->save()) {
+            return ['success'=>true,'data'=>['id'=>$child->id, 'name'=>$child->name]];
+        }
+        return ['success'=>false,'message'=>current($child->getFirstErrors()) ?: 'Ошибка создания'];
+    }
+
+    public function actionToggleActive()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $id = Yii::$app->request->post('id');
+        $active = Yii::$app->request->post('active');
+
+        $model = $this->findModel($id);
+        if (!$model) {
+            return ['success' => false, 'message' => 'Не найдено'];
+        }
+
+        $model->active = (int)$active;
+        if ($model->save(false)) {
+            return ['success' => true];
+        }
+        return ['success' => false, 'message' => 'Не удалось сохранить'];
+    }
     /**
      * Deletes an existing MatrixType model.
      * If deletion is successful, the browser will be redirected to the 'index' page.
diff --git a/erp24/migrations/m250905_090045_add_parentid_active_fields_to_matrix_type_table.php b/erp24/migrations/m250905_090045_add_parentid_active_fields_to_matrix_type_table.php
new file mode 100644 (file)
index 0000000..e2b70c1
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+use yii\db\Migration;
+
+class m250905_090045_add_parentid_active_fields_to_matrix_type_table extends Migration
+{
+    const TABLE_NAME = 'erp24.matrix_type';
+    /**
+     * {@inheritdoc}
+     */
+    public function safeUp()
+    {
+        $table = $this->db->schema->getTableSchema(self::TABLE_NAME);
+        if ($table === null) {
+            return;
+        }
+
+        if (!$this->db->schema->getTableSchema(self::TABLE_NAME, true)->getColumn('parent_id')) {
+            $this->addColumn(
+                self::TABLE_NAME,
+                'parent_id',
+                $this->integer()->null()->comment('id записи родителя')
+            );
+        }
+        if (!$this->db->schema->getTableSchema(self::TABLE_NAME, true)->getColumn('active')) {
+            $this->addColumn(
+                self::TABLE_NAME,
+                'active',
+                $this->tinyInteger()->notNull()->defaultValue(1)->comment('активность')
+            );
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function safeDown()
+    {
+        if ($this->db->schema->getTableSchema(self::TABLE_NAME, true)->getColumn('parent_id')) {
+            $this->dropColumn(self::TABLE_NAME, 'parent_id');
+        }
+        if ($this->db->schema->getTableSchema(self::TABLE_NAME, true)->getColumn('active')) {
+            $this->dropColumn(self::TABLE_NAME, 'active');
+        }
+    }
+
+    /*
+    // Use up()/down() to run migration code without a transaction.
+    public function up()
+    {
+
+    }
+
+    public function down()
+    {
+        echo "m250905_090045_add_parentid_active_fields_to_matrix_type_table cannot be reverted.\n";
+
+        return false;
+    }
+    */
+}
index a61304bc3b3be5ba07393c95b40347dce1dc9a2a..0709aa18ad8688c9d6329ddd4d1cf9a2f539eb0a 100644 (file)
@@ -10,6 +10,8 @@ use yii\db\Expression;
  * This is the model class for table "erp24.matrix_type".
  *
  * @property int $id
+ * @property int|null $parent_id id родительской категории - null - корневая
+ * @property int $active активность (0,1) по умолчанию - 1 - активная запись
  * @property string $name
  * @property int $created_by
  * @property string $created_at
@@ -50,11 +52,26 @@ class MatrixType extends \yii\db\ActiveRecord
     {
         return [
             [['name'], 'filter', 'filter' => 'trim'],
-            [['name'], 'unique', 'targetAttribute' => ['name'], 'message' => 'Имя матрицы уже используется'],
             [['name'], 'required'],
-            [['created_by', 'updated_by'], 'integer'],
+            [['created_by', 'updated_by', 'active', 'parent_id'], 'integer'],
             [['created_at', 'updated_at'], 'safe'],
             [['name'], 'string', 'max' => 255],
+            // Уникальность имени в рамках одной группы
+            [
+                ['name', 'parent_id'],
+                'unique',
+                'targetAttribute' => ['name', 'parent_id'],
+                'message' => 'Подгруппа с таким названием уже существует в этой группе.',
+            ],
+
+            // Уникальность имени для групп (без parent_id)
+            [
+                'name',
+                'unique',
+                'targetAttribute' => 'name',
+                'filter' => ['parent_id' => null],
+                'message' => 'Группа с таким названием уже существует.',
+            ],
         ];
     }
 
@@ -66,6 +83,8 @@ class MatrixType extends \yii\db\ActiveRecord
         return [
             'id' => 'ID',
             'name' => 'Название типа матрицы',
+            'parent_id' => 'Родительская категория',
+            'active' => 'Активность',
             'created_by' => 'ИД создателя',
             'created_at' => 'Дата создания',
             'updated_by' => 'ИД редактировавшего',
index 1ca95c953b9f76d6981602fce75844bb29f8c4b2..31527777f5bc9e85ec0112eebc0922b52aced84b 100644 (file)
@@ -1,13 +1,16 @@
 <?php
 
+use leandrogehlen\treegrid\TreeGrid;
 use yii\helpers\Html;
 use yii\grid\GridView;
+use yii\helpers\Url;
 
 /* @var $this yii\web\View */
 /* @var $dataProvider yii\data\ActiveDataProvider */
 
 $this->title = 'Типы матриц';
 $this->params['breadcrumbs'][] = $this->title;
+$this->registerJsFile('/js/matrix-type/index.js', ['position' => \yii\web\View::POS_END]);
 ?>
 <div class="matrix-type-index p-3">
 
@@ -17,18 +20,159 @@ $this->params['breadcrumbs'][] = $this->title;
         <?= Html::a('Создать новый тип матрицы', ['create'], ['class' => 'btn btn-success']) ?>
     </p>
 
-    <?= GridView::widget([
+<!--    --><?php //= GridView::widget([
+//        'dataProvider' => $dataProvider,
+//        'filterModel' => null,
+//        'columns' => [
+//            'id',
+//            'name',
+//            'created_at',
+//            'updated_at',
+//            [
+//                'class' => 'yii\grid\ActionColumn',
+//                'template' => '{update} {delete}',
+//            ],
+//        ],
+//    ]); ?>
+
+    <?=
+    TreeGrid::widget([
         'dataProvider' => $dataProvider,
-        'filterModel' => null,
+        'keyColumnName' => 'id',
+        'showOnEmpty' => FALSE,
+        'parentColumnName' => 'parent_id',
         'columns' => [
-            'id',
-            'name',
-            'created_at',
-            'updated_at',
+
+            //'name',
+            [
+                'attribute' => 'name',
+                'format' => 'raw',
+                'value' => function ($model)  {
+                    $id = (int)$model->id;
+                    $editModalId = "editModal-$id";
+                    $createModalId = "createModal-$id";
+
+                    $editUrl   = Url::to(['matrix-type/update-name', 'id' => $id]);
+                    $createUrl = Url::to(['matrix-type/create-child', 'parent_id' => $id]);
+
+                    ob_start(); ?>
+
+                    <div class="d-inline-block">
+                        <div class="d-flex justify-content-start align-items-center">
+
+                            <button type="button"
+                                    class="btn btn-outline-primary"
+                                    data-bs-toggle="modal"
+                                    data-bs-target="#<?= $editModalId ?>">
+                                <span id="nameLabel-<?= $id ?>"><?= Html::encode($model->name) ?></span>
+                                <i class="fa fa-pencil ms-2 "></i>
+                            </button>
+
+                            <button type="button"
+                                    class="btn btn-sm"
+                                    data-bs-toggle="modal"
+                                    data-bs-target="#<?= $createModalId ?>"
+                                    aria-label="Добавить">
+                                <i class="fa fa-plus"></i>
+                            </button>
+                        </div>
+                    </div>
+
+
+                    <div class="modal fade" id="<?= $editModalId ?>" tabindex="-1" aria-hidden="true">
+                        <div class="modal-dialog">
+                            <div class="modal-content">
+                                <div class="modal-header">
+                                    <h5 class="modal-title">Редактировать название</h5>
+                                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"><i class="fa fa-times"></i></button>
+                                </div>
+                                <form class="ajax-form" data-action="<?= $editUrl ?>" data-success="rename" data-id="<?= $id ?>">
+                                    <div class="modal-body">
+                                        <div class="mb-3">
+                                            <label class="form-label">Название</label>
+                                            <input type="text" name="name" class="form-control" value="<?= Html::encode($model->name) ?>" required>
+                                        </div>
+                                        <div class="alert alert-danger d-none js-form-error"></div>
+                                    </div>
+
+                                    <div class="modal-footer">
+
+                                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отменить</button>
+                                        <button type="submit" class="btn btn-primary">Сохранить</button>
+                                    </div>
+                                </form>
+                            </div>
+                        </div>
+                    </div>
+
+
+                    <div class="modal fade" id="<?= $createModalId ?>" tabindex="-1" aria-hidden="true">
+                        <div class="modal-dialog">
+                            <div class="modal-content">
+                                <div class="modal-header">
+                                    <h5 class="modal-title">Новый пункт (дочерний)</h5>
+                                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"><i class="fa fa-times"></i></button>
+                                </div>
+                                <form class="ajax-form" data-action="<?= $createUrl ?>" data-success="created" data-parent-id="<?= $id ?>">
+                                    <div class="modal-body">
+                                        <input type="hidden" name="parent_id" value="<?= $id ?>">
+                                        <div class="mb-3">
+                                            <label class="form-label">Название</label>
+                                            <input type="text" name="name" class="form-control" required>
+                                        </div>
+                                        <div class="alert alert-danger d-none js-form-error"></div>
+                                    </div>
+
+                                    <div class="modal-footer">
+
+                                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отменить</button>
+                                        <button type="submit" class="btn btn-success">Создать</button>
+                                    </div>
+                                </form>
+                            </div>
+                        </div>
+                    </div>
+
+
+                    <?php
+                    return ob_get_clean();
+                },
+            ],
             [
-                'class' => 'yii\grid\ActionColumn',
-                'template' => '{update} {delete}',
+                'attribute' => 'active',
+                'format' => 'raw',
+                'value' => function ($model) {
+                    return \yii\helpers\Html::checkbox(
+                        'active[' . $model->id . ']',
+                        $model->active == 1,
+                        [
+                            'class' => 'js-toggle-active',
+                            'data-id' => $model->id,
+                        ]
+                    );
+                },
+                'contentOptions' => ['class' => 'text-center', 'style' => 'width:80px;  min-width:50px;  '],
             ],
-        ],
-    ]); ?>
+
+            ['class' => 'yii\grid\ActionColumn',
+                'template' => '{delete}',
+                'contentOptions' => ['class' => 'text-center', 'style' => 'width:80px;  min-width:50px;  '],
+                'buttons' => [
+                    'delete' => function ($url, $model, $key)
+                    {
+                        return Html::a('<span class="fa fa-times"></span>',
+                            $url,
+                        [
+                                'title' => "Удалить",
+                                'data' => [
+                                        'method' => 'post',
+                                        'confirm' => 'Вы уверены, что хотите удалить этот элемент?',
+                                ]
+                        ]);
+                    },
+                ]
+            ]
+        ]
+    ]);
+    ?>
 </div>
diff --git a/erp24/web/js/matrix-type/index.js b/erp24/web/js/matrix-type/index.js
new file mode 100644 (file)
index 0000000..62036bd
--- /dev/null
@@ -0,0 +1,103 @@
+(function(){
+    function setError(form, msg){
+        var $box = $(form).find('.js-form-error');
+        if(!$box.length) return;
+        if(!msg){ $box.addClass('d-none').text(''); return; }
+        $box.removeClass('d-none').text(msg);
+    }
+
+    $(document).on('submit', '.ajax-form', function(e){
+        e.preventDefault();
+        var $form = $(this);
+        setError($form[0], null);
+
+        var action   = $form.data('action');
+        var mode     = $form.data('success');
+        var id       = $form.data('id');
+        var parentId = $form.data('parent-id');
+
+        var dataArr = $form.serializeArray();
+
+
+        dataArr.push({
+            name: yii.getCsrfParam(),
+            value: yii.getCsrfToken()
+        });
+
+        if (id && !$form.find('[name="id"]').length) {
+            dataArr.push({name: 'id', value: id});
+        }
+        if (parentId && !$form.find('[name="parent_id"]').length) {
+            dataArr.push({name: 'parent_id', value: parentId});
+        }
+
+        var $submitBtn = $form.find('button[type="submit"]').prop('disabled', true);
+
+        $.ajax({
+            url: action,
+            type: 'POST',
+            data: $.param(dataArr),
+            dataType: 'json',
+            headers: { 'X-Requested-With':'XMLHttpRequest' },
+            success: function(json){
+                if (!json || json.success !== true) {
+                    setError($form[0], (json && json.message) ? json.message : 'Ошибка сохранения');
+                    return;
+                }
+
+                if (mode === 'rename') {
+                    var label = document.getElementById('nameLabel-' + id);
+                    var newName = (json.data && json.data.name) ? json.data.name : $form.find('[name="name"]').val();
+                    if (label && newName) { label.textContent = newName; }
+                } else if (mode === 'created') {
+                    if ($.pjax) {
+                        $.pjax.reload({container:'#pjaxtable', async:false});
+                    } else {
+                        location.reload();
+                    }
+                }
+
+                // закрыть модалку
+                var modalEl = $form.closest('.modal')[0];
+                if (modalEl) {
+                    var modal = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl);
+                    modal.hide();
+                    $form[0].reset();
+                    setError($form[0], null);
+                }
+            },
+            error: function(){
+                setError($form[0], 'Сеть/сервер недоступен');
+            },
+            complete: function(){
+                $submitBtn.prop('disabled', false);
+            }
+        });
+    });
+
+    $(document).on('change', '.js-toggle-active', function () {
+        var checkbox = $(this);
+        var id = checkbox.data('id');
+        var active = checkbox.is(':checked') ? 1 : 0;
+
+        $.ajax({
+            url: '/matrix-type/toggle-active',
+            type: 'POST',
+            data: {
+                id: id,
+                active: active,
+                _csrf: yii.getCsrfToken()
+            },
+            success: function (res) {
+                if (!res.success) {
+                    alert('Ошибка: ' + res.message);
+                    checkbox.prop('checked', !active);
+                }
+            },
+            error: function () {
+                alert('Ошибка сервера');
+                checkbox.prop('checked', !active);
+            }
+        });
+    });
+})();