diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a4276f..c7beee4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## [2026-02-27] - Modernization, Backend Tools & Dashboard Enhancements +## [2026-02-27] - Modernization, Backend Tools, GIS Refactoring & Dashboard Enhancements ### Added +- **GIS Module Refactoring:** + - Renamed legacy `map` module to `gis` for clarity and standardization. + - Performed system-wide namespace refactoring to `backend\modules\gis`. + - Redesigned GIS main dashboard with a modern Interactive Card UI and Font Awesome 5.11 icons. +- **RBAC for GIS:** + - Created a new RBAC role `gis` (GIS Data Manager) via database migration. + - Added `/gis/*` permission covering all GIS module actions. + - Configured `authManager` in console application to utilize shared RBAC files at `@backend/rbac/`. - **Premium Backend Dashboard:** - Redesigned the main dashboard with categorized module shortcuts for better organization. - Categorized modules into: Administration & Strategy, Assets & Resources, and Public Services & Revenue. diff --git a/backend/config/main.php b/backend/config/main.php index 8b12c8e7..806c04c0 100755 --- a/backend/config/main.php +++ b/backend/config/main.php @@ -32,6 +32,9 @@ return [ 'forum' => [ 'class' => 'backend\modules\forum\Module', ], + 'gis' => [ + 'class' => 'backend\modules\gis\Module', + ], 'gridview' => [ 'class' => '\kartik\grid\Module' ], diff --git a/backend/modules/gis/Module.php b/backend/modules/gis/Module.php new file mode 100755 index 00000000..1135dfd3 --- /dev/null +++ b/backend/modules/gis/Module.php @@ -0,0 +1,24 @@ +render('index'); + } +} diff --git a/backend/modules/gis/controllers/GeoFeaturesController.php b/backend/modules/gis/controllers/GeoFeaturesController.php new file mode 100755 index 00000000..aa35b093 --- /dev/null +++ b/backend/modules/gis/controllers/GeoFeaturesController.php @@ -0,0 +1,151 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ] + ); + } + + /** + * Lists all GisGeoFeatures models. + * + * @return string + */ + public function actionIndex() + { + $searchModel = new GisGeoFeaturesSearch(); + $dataProvider = $searchModel->search($this->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single GisGeoFeatures model. + * @param int $id ID + * @return string + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new GisGeoFeatures model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return string|\yii\web\Response + */ + public function actionCreate() + { + $model = new GisGeoFeatures(); + + if ($this->request->isPost && $model->load(Yii::$app->request->post())) { + $geo = json_decode(Yii::$app->request->post('GisGeoFeatures')['geometry'], true); + + if ($geo && isset($geo['type'], $geo['coordinates'])) { + $model->geometry = new \yii\db\Expression("ST_GeomFromGeoJSON('" . json_encode($geo) . "')"); + $model->type = $geo['type']; // ✅ ดึง type จาก GeoJSON โดยตรง + } + + if ($model->save(false)) { + return $this->redirect(['view', 'id' => $model->id]); + } + } else { + $model->loadDefaultValues(); + } + + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing GisGeoFeatures model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param int $id ID + * @return string|\yii\web\Response + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post())) { + $geo = json_decode(Yii::$app->request->post('GisGeoFeatures')['geometry'], true); + + if ($geo && isset($geo['type'], $geo['coordinates'])) { + $model->geometry = new \yii\db\Expression("ST_GeomFromGeoJSON('" . json_encode($geo) . "')"); + $model->type = $geo['type']; // ✅ ตั้งค่า type เช่นกัน + } + + if ($model->save(false)) { + return $this->redirect(['view', 'id' => $model->id]); + } + } + + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing GisGeoFeatures model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param int $id ID + * @return \yii\web\Response + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the GisGeoFeatures model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param int $id ID + * @return GisGeoFeatures the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = GisGeoFeatures::findOne(['id' => $id])) !== null) { + return $model; + } + + throw new NotFoundHttpException('The requested page does not exist.'); + } +} diff --git a/backend/modules/gis/controllers/GisBaseController.php b/backend/modules/gis/controllers/GisBaseController.php new file mode 100755 index 00000000..cb5265b6 --- /dev/null +++ b/backend/modules/gis/controllers/GisBaseController.php @@ -0,0 +1,134 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ] + ); + } + + /** + * Lists all GisBase models. + * + * @return string + */ + public function actionIndex() + { + $searchModel = new GisBaseSearch(); + $dataProvider = $searchModel->search($this->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single GisBase model. + * @param int $id ID + * @return string + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new GisBase model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return string|\yii\web\Response + */ + public function actionCreate() + { + $model = new GisBase(); + + if ($this->request->isPost) { + if ($model->load($this->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } + } else { + $model->loadDefaultValues(); + } + + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing GisBase model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param int $id ID + * @return string|\yii\web\Response + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($this->request->isPost && $model->load($this->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } + + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing GisBase model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param int $id ID + * @return \yii\web\Response + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the GisBase model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param int $id ID + * @return GisBase the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = GisBase::findOne(['id' => $id])) !== null) { + return $model; + } + + throw new NotFoundHttpException('The requested page does not exist.'); + } +} diff --git a/backend/modules/gis/controllers/MapController.php b/backend/modules/gis/controllers/MapController.php new file mode 100755 index 00000000..c70921c6 --- /dev/null +++ b/backend/modules/gis/controllers/MapController.php @@ -0,0 +1,115 @@ +render('index'); + } + + public function actionMap() + { + return $this->render('map'); + } + + public function actionGeojson() + { + Yii::$app->response->format = Response::FORMAT_JSON; + + $gis_base_id = Yii::$app->request->get('GisGeoFeatures')['gis_base_id'] ?? null; + + $features = []; + + // ✅ โหลดข้อมูลจากตาราง gis_tambon เสมอ + $tambonRows = (new Query()) + ->select(['id', 'tb_name_t', 'tb_code', 'ST_AsGeoJSON(geom) AS geojson']) + ->from('gis_tambon') + ->where(['id' => 7662]) + ->all(); + + foreach ($tambonRows as $row) { + $features[] = [ + 'type' => 'Feature', + 'geometry' => json_decode($row['geojson']), + 'properties' => [ + 'id' => $row['id'], + 'name' => $row['tb_name_t'], + 'interactive' => false + ] + ]; + } + + // ✅ โหลดข้อมูลจาก gis_geo_features ตาม gis_base_id (ถ้ามี) + $geoQuery = (new Query()) + ->select([ + 'f.id', + 'f.name', + 'ST_AsGeoJSON(f.geometry) AS geojson', + 'b.color' // 👈 ดึงสีจากตาราง gis_base + ]) + ->from(['f' => 'gis_geo_features']) + ->leftJoin(['b' => 'gis_base'], 'f.gis_base_id = b.id'); // 👈 join + + if ($gis_base_id) { + $geoQuery->where(['f.gis_base_id' => $gis_base_id]); + } + + $geoRows = $geoQuery->all(); + + foreach ($geoRows as $row) { + $features[] = [ + 'type' => 'Feature', + 'geometry' => json_decode($row['geojson']), + 'properties' => [ + 'id' => $row['id'], + 'name' => $row['name'], + 'color' => $row['color'] ?? '#3388ff' // ✅ กำหนดสี default ถ้าไม่มี + ] + ]; + } + + return [ + 'type' => 'FeatureCollection', + 'features' => $features + ]; + } + /*public function actionGeojson() + { + Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; + + $gis_base_id = Yii::$app->request->get('GisGeoFeatures')['gis_base_id'] ?? null; + + $query = GisGeoFeatures::find(); + + if ($gis_base_id) { + $query->andWhere(['gis_base_id' => $gis_base_id]); + } + + $features = $query->all(); + + $geojson = [ + "type" => "FeatureCollection", + "features" => [] + ]; + + foreach ($features as $f) { + $geojson['features'][] = [ + "type" => "Feature", + "geometry" => json_decode($f->geometry), + "properties" => [ + "name" => $f->name + ] + ]; + } + + return $geojson; + }*/ + +} diff --git a/backend/modules/gis/models/GisBaseSearch.php b/backend/modules/gis/models/GisBaseSearch.php new file mode 100755 index 00000000..315e9efb --- /dev/null +++ b/backend/modules/gis/models/GisBaseSearch.php @@ -0,0 +1,74 @@ +load()` method. + * + * @return ActiveDataProvider + */ + public function search($params, $formName = null) + { + $query = GisBase::find(); + + // add conditions that should always apply here + + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + ]); + + $this->load($params, $formName); + + if (!$this->validate()) { + // uncomment the following line if you do not want to return any records when validation fails + // $query->where('0=1'); + return $dataProvider; + } + + // grid filtering conditions + $query->andFilterWhere([ + 'id' => $this->id, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + 'created_by' => $this->created_by, + 'updated_by' => $this->updated_by, + ]); + + $query->andFilterWhere(['like', 'name', $this->name]) + ->andFilterWhere(['like', 'description', $this->description]); + + return $dataProvider; + } +} diff --git a/backend/modules/gis/models/GisGeoFeaturesSearch.php b/backend/modules/gis/models/GisGeoFeaturesSearch.php new file mode 100755 index 00000000..f9e64c36 --- /dev/null +++ b/backend/modules/gis/models/GisGeoFeaturesSearch.php @@ -0,0 +1,76 @@ +load()` method. + * + * @return ActiveDataProvider + */ + public function search($params, $formName = null) + { + $query = GisGeoFeatures::find(); + + // add conditions that should always apply here + + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + ]); + + $this->load($params, $formName); + + if (!$this->validate()) { + // uncomment the following line if you do not want to return any records when validation fails + // $query->where('0=1'); + return $dataProvider; + } + + // grid filtering conditions + $query->andFilterWhere([ + 'id' => $this->id, + 'gis_base_id' => $this->gis_base_id, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + 'created_by' => $this->created_by, + 'updated_by' => $this->updated_by, + ]); + + $query->andFilterWhere(['like', 'name', $this->name]) + ->andFilterWhere(['like', 'geometry', $this->geometry]) + ->andFilterWhere(['like', 'type', $this->type]); + + return $dataProvider; + } +} diff --git a/backend/modules/gis/views/default/index.php b/backend/modules/gis/views/default/index.php new file mode 100755 index 00000000..1661636e --- /dev/null +++ b/backend/modules/gis/views/default/index.php @@ -0,0 +1,109 @@ +title = 'ระบบภูมิสารสนเทศ (GIS)'; +$this->params['breadcrumbs'][] = $this->title; +?> + + + +
จัดการข้อมูลตำแหน่ง พิกัด และแผนที่ยุทธศาสตร์เพื่อการพัฒนาท้องถิ่น
++ = Html::a('เพิ่มข้อมูล', ['create'], ['class' => 'btn btn-success']) ?> +
+ + render('_search', ['model' => $searchModel]); ?> + + = GridView::widget([ + 'dataProvider' => $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + + //'id', + [ + 'attribute' => 'gis_base_id', + 'value' => function($model) { + return $model->gisBase->name; + } + ], + 'name', + //'geometry', + //'type', + //'created_at', + //'updated_at', + //'created_by', + //'updated_by', + [ + 'class' => ActionColumn::className(), + 'urlCreator' => function ($action, GisGeoFeatures $model, $key, $index, $column) { + return Url::toRoute([$action, 'id' => $model->id]); + } + ], + ], + ]); ?> + + ++ = Html::a('Update', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?> + = Html::a('Delete', ['delete', 'id' => $model->id], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => 'Are you sure you want to delete this item?', + 'method' => 'post', + ], + ]) ?> +
+ + = DetailView::widget([ + 'model' => $model, + 'attributes' => [ + 'id', + 'gis_base_id', + 'name', + 'geometry', + 'type', + 'created_at', + 'updated_at', + 'created_by', + 'updated_by', + ], + ]) ?> + ++ = Html::a('เพิ่มข้อมูล', ['create'], ['class' => 'btn btn-success']) ?> +
+ + render('_search', ['model' => $searchModel]); ?> + + = GridView::widget([ + 'dataProvider' => $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + + //'id', + 'name', + 'description:ntext', + //'created_at', + //'updated_at', + //'created_by', + //'updated_by', + [ + 'class' => ActionColumn::className(), + 'urlCreator' => function ($action, GisBase $model, $key, $index, $column) { + return Url::toRoute([$action, 'id' => $model->id]); + } + ], + ], + ]); ?> + + ++ = Html::a('Update', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?> + = Html::a('Delete', ['delete', 'id' => $model->id], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => 'Are you sure you want to delete this item?', + 'method' => 'post', + ], + ]) ?> +
+ + = DetailView::widget([ + 'model' => $model, + 'attributes' => [ + 'id', + 'name', + 'description:ntext', + 'created_at', + 'updated_at', + 'created_by', + 'updated_by', + ], + ]) ?> + +
+ You may change the content of this page by modifying
+ the file = __FILE__; ?>.
+
เวอร์ชัน 2.0.0 (Yii2 Core)
พัฒนาโดย HANUMANIT Co., Ltd.
เวอร์ชัน 2.0.0
พัฒนาโดย HANUMANIT Co., Ltd.