diff --git a/backend/gn_module_monitoring/monitoring/models.py b/backend/gn_module_monitoring/monitoring/models.py index 1aa51c989..84bd8067d 100644 --- a/backend/gn_module_monitoring/monitoring/models.py +++ b/backend/gn_module_monitoring/monitoring/models.py @@ -2,7 +2,7 @@ Modèles SQLAlchemy pour les modules de suivi """ from sqlalchemy import select, func, and_ -from sqlalchemy.orm import column_property +from sqlalchemy.orm import column_property, ColumnProperty, RelationshipProperty, class_mapper from sqlalchemy.dialects.postgresql import JSONB, UUID from uuid import uuid4 @@ -10,6 +10,7 @@ from utils_flask_sqla_geo.serializers import geoserializable from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.ext.declarative import declared_attr from pypnnomenclature.models import TNomenclatures, BibNomenclaturesTypes from geonature.core.gn_commons.models import TMedias @@ -21,6 +22,42 @@ from geonature.core.gn_monitoring.models import corVisitObserver from gn_module_monitoring.monitoring.queries import Query as MonitoringQuery + +class GenericModel: + @declared_attr + def __tablename__(cls): + return cls.__name__.lower() + + @classmethod + def set_id(cls) -> None: + pk_string = class_mapper(cls).primary_key[0].name + if hasattr(cls,"id_g") ==False: + pk_value= getattr(cls,pk_string) + setattr(cls,"id_g",pk_value) + + @classmethod + def get_id(cls) -> None: + pk_string = class_mapper(cls).primary_key[0].name + # print('======= ==>', pk_string) + if hasattr(cls,"id_g") ==False: + pk_value= getattr(cls,pk_string) + setattr(cls,"id_g",pk_value) + return pk_string + + @classmethod + def find_by_id(cls, _id: int) -> "GenericModel": + cls.set_id() + return cls.query.get_or_404(_id) + + @classmethod + def attribute_names(cls): + return [ + prop.key + for prop in class_mapper(cls).iterate_properties + if isinstance(prop, ColumnProperty) + ] + + cor_module_type = DB.Table( "cor_module_type", DB.Column( @@ -53,7 +90,7 @@ @serializable -class BibTypeSite(DB.Model): +class BibTypeSite(DB.Model, GenericModel): __tablename__ = "bib_type_site" __table_args__ = {"schema": "gn_monitoring"} query_class = MonitoringQuery @@ -247,7 +284,7 @@ class TMonitoringSites(TBaseSites): ) @serializable -class TMonitoringSitesGroups(DB.Model): +class TMonitoringSitesGroups(DB.Model, GenericModel): __tablename__ = 't_sites_groups' __table_args__ = {'schema': 'gn_monitoring'} query_class = MonitoringQuery diff --git a/backend/gn_module_monitoring/monitoring/schemas.py b/backend/gn_module_monitoring/monitoring/schemas.py index f34066c55..1816d677e 100644 --- a/backend/gn_module_monitoring/monitoring/schemas.py +++ b/backend/gn_module_monitoring/monitoring/schemas.py @@ -1,9 +1,9 @@ import json import geojson -from marshmallow import Schema, fields -from marshmallow_sqlalchemy import SQLAlchemyAutoSchema -from pypnnomenclature.schemas import NomenclatureSchema +from geonature.utils.env import MA +from marshmallow import Schema, fields, validate +from geonature.core.gn_commons.schemas import MediaSchema from gn_module_monitoring.monitoring.models import ( BibTypeSite, @@ -22,19 +22,30 @@ class PaginationSchema(Schema): return PaginationSchema -class MonitoringSitesGroupsSchema(SQLAlchemyAutoSchema): +class MonitoringSitesGroupsSchema(MA.SQLAlchemyAutoSchema): + + sites_group_name = fields.String( + validate=validate.Length(min=3,error="Length must be greater than 3"),) + class Meta: model = TMonitoringSitesGroups exclude = ("geom_geojson",) + load_instance = True + medias = MA.Nested(MediaSchema) + pk = fields.Method("set_pk",dump_only=True) geometry = fields.Method("serialize_geojson", dump_only=True) + + def set_pk(self,obj): + return self.Meta.model.get_id() def serialize_geojson(self, obj): if obj.geom_geojson is not None: return json.loads(obj.geom_geojson) + -class MonitoringSitesSchema(SQLAlchemyAutoSchema): +class MonitoringSitesSchema(MA.SQLAlchemyAutoSchema): class Meta: model = TMonitoringSites exclude = ("geom_geojson", "geom") @@ -46,7 +57,7 @@ def serialize_geojson(self, obj): return geojson.dumps(obj.as_geofeature().get("geometry")) -class BibTypeSiteSchema(SQLAlchemyAutoSchema): +class BibTypeSiteSchema(MA.SQLAlchemyAutoSchema): label = fields.Method("get_label_from_type_site") # See if useful in the future: # type_site = fields.Nested(NomenclatureSchema(only=("label_fr",)), dump_only=True) diff --git a/backend/gn_module_monitoring/routes/site.py b/backend/gn_module_monitoring/routes/site.py index b4c8cc673..d4ee778aa 100644 --- a/backend/gn_module_monitoring/routes/site.py +++ b/backend/gn_module_monitoring/routes/site.py @@ -36,8 +36,7 @@ def get_types_site(): @blueprint.route("/sites/types/", methods=["GET"]) def get_type_site_by_id(id_type_site): - query = BibTypeSite.query.filter_by(id_nomenclature_type_site=id_type_site) - res = query.first() + res = BibTypeSite.find_by_id(id_type_site) schema = BibTypeSiteSchema() return schema.dump(res) diff --git a/backend/gn_module_monitoring/routes/sites_groups.py b/backend/gn_module_monitoring/routes/sites_groups.py index e04b27c65..8ebb8d3fd 100644 --- a/backend/gn_module_monitoring/routes/sites_groups.py +++ b/backend/gn_module_monitoring/routes/sites_groups.py @@ -1,5 +1,6 @@ from flask import jsonify, request from geonature.utils.env import db +from marshmallow import ValidationError from sqlalchemy import func from werkzeug.datastructures import MultiDict @@ -14,6 +15,7 @@ paginate, sort, ) +from gn_module_monitoring.utils.errors.errorHandler import InvalidUsage @blueprint.route("/sites_groups", methods=["GET"]) @@ -34,6 +36,13 @@ def get_sites_groups(): ) +@blueprint.route("/sites_groups/", methods=["GET"]) +def get_sites_group_by_id(id_sites_group: int): + schema = MonitoringSitesGroupsSchema() + result = TMonitoringSitesGroups.find_by_id(id_sites_group) + return jsonify(schema.dump(result)) + + @blueprint.route("/sites_groups/geometries", methods=["GET"]) def get_sites_group_geometries(): subquery = ( @@ -53,3 +62,45 @@ def get_sites_group_geometries(): result = geojson_query(subquery) return jsonify(result) + + +@blueprint.route("/sites_groups/", methods=["PATCH"]) +def patch(_id): + item_schema = MonitoringSitesGroupsSchema() + item_json = request.get_json() + item = TMonitoringSitesGroups.find_by_id(_id) + fields = TMonitoringSitesGroups.attribute_names() + for field in item_json: + if field in fields: + setattr(item, field, item_json[field]) + item_schema.load(item_json) + db.session.add(item) + + db.session.commit() + return item_schema.dump(item), 201 + + +@blueprint.route("/sites_groups/", methods=["DELETE"]) +def delete(_id): + item_schema = MonitoringSitesGroupsSchema() + item = TMonitoringSitesGroups.find_by_id(_id) + TMonitoringSitesGroups.query.filter_by(id_g=_id).delete() + db.session.commit() + return item_schema.dump(item), 201 + + +@blueprint.route("/sites_groups", methods=["POST"]) +def post(): + item_schema = MonitoringSitesGroupsSchema() + item_json = request.get_json() + item = item_schema.load(item_json) + db.session.add(item) + db.session.commit() + return item_schema.dump(item), 201 + + +@blueprint.errorhandler(ValidationError) +def handle_validation_error(error): + return InvalidUsage( + "Fields cannot be validated, message : {}".format(error.messages), status_code=422, payload=error.data + ).to_dict() diff --git a/backend/gn_module_monitoring/tests/test_routes/test_sites_groups.py b/backend/gn_module_monitoring/tests/test_routes/test_sites_groups.py index f459d22ce..0d5d4f2e3 100644 --- a/backend/gn_module_monitoring/tests/test_routes/test_sites_groups.py +++ b/backend/gn_module_monitoring/tests/test_routes/test_sites_groups.py @@ -7,6 +7,16 @@ @pytest.mark.usefixtures("client_class", "temporary_transaction") class TestSitesGroups: + def test_get_sites_group_by_id(self, sites_groups): + sites_group = list(sites_groups.values())[0] + id_sites_group = sites_group.id_sites_group + r = self.client.get( + url_for("monitorings.get_sites_group_by_id", id_sites_group=id_sites_group) + ) + + assert r.json["id_sites_group"] == id_sites_group + assert r.json["sites_group_name"] == sites_group.sites_group_name + def test_get_sites_groups(self, sites_groups): r = self.client.get(url_for("monitorings.get_sites_groups")) diff --git a/backend/gn_module_monitoring/utils/errors/__init__.py b/backend/gn_module_monitoring/utils/errors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/gn_module_monitoring/utils/errors/errorHandler.py b/backend/gn_module_monitoring/utils/errors/errorHandler.py new file mode 100644 index 000000000..a10b13987 --- /dev/null +++ b/backend/gn_module_monitoring/utils/errors/errorHandler.py @@ -0,0 +1,19 @@ +from geonature.utils.errors import GeonatureApiError + + +class InvalidUsage(GeonatureApiError): + status_code = 400 + + def __init__(self, message, status_code=None, payload=None): + GeonatureApiError.__init__(self, message, status_code) + self.message = message + if status_code is not None: + self.status_code = status_code + self.payload = payload + + def to_dict(self): + rv = {} + rv["payload"] = self.payload + rv["message"] = self.message + rv["status_code"] = self.status_code + return (rv, self.status_code) diff --git a/config/monitoring/generic/config.json b/config/monitoring/generic/config.json index f2dafb345..dc748a66e 100644 --- a/config/monitoring/generic/config.json +++ b/config/monitoring/generic/config.json @@ -1,20 +1,22 @@ { "tree": { "module": { - "site": { - "visit": { - "observation": null + "sites_group": { + "site": { + "visit": { + "observation": null + } } } } }, - "synthese" : "__MODULE.B_SYNTHESE", + "synthese": "__MODULE.B_SYNTHESE", "default_display_field_names": { "user": "nom_complet", "nomenclature": "label_fr", "dataset": "dataset_name", "observer_list": "nom_liste", - "taxonomy" : "__MODULE.TAXONOMY_DISPLAY_FIELD_NAME", + "taxonomy": "__MODULE.TAXONOMY_DISPLAY_FIELD_NAME", "taxonomy_list": "nom_liste", "sites_group": "sites_group_name", "habitat": "lb_hab_fr", @@ -22,4 +24,4 @@ "municipality": "nom_com_dept", "site": "base_site_name" } -} +} \ No newline at end of file diff --git a/config/monitoring/generic/sites_group.json b/config/monitoring/generic/sites_group.json index fc7c96d57..8512fdb0d 100644 --- a/config/monitoring/generic/sites_group.json +++ b/config/monitoring/generic/sites_group.json @@ -18,16 +18,6 @@ {"prop": "sites_group_code"} ], "generic": { - "id_sites_group": { - "type_widget": "text", - "attribut_label": "Id site", - "hidden": true - }, - "id_module": { - "type_widget": "text", - "attribut_label": "ID Module", - "hidden": true - }, "sites_group_name": { "type_widget": "text", "attribut_label": "Nom", @@ -54,11 +44,6 @@ }, "nb_visits": { "attribut_label": "Nombre de visites" - }, - "medias": { - "type_widget": "medias", - "attribut_label": "Médias", - "schema_dot_table": "gn_monitoring.t_sites_groups" } } } diff --git a/frontend/app/class/monitoring-geom-component.ts b/frontend/app/class/monitoring-geom-component.ts new file mode 100644 index 000000000..acad9b05b --- /dev/null +++ b/frontend/app/class/monitoring-geom-component.ts @@ -0,0 +1,30 @@ +import { PageInfo } from "../interfaces/page"; +import { JsonData } from "../types/jsondata"; + +const LIMIT = 10; + +type callbackFunction = (pageNumber: number, filters: JsonData) => void; + +export class MonitoringGeomComponent { + protected getAllItemsCallback: callbackFunction; + protected limit = LIMIT; + public filters = {}; + public baseFilters = {}; + + constructor() {} + + setPage(page: PageInfo) { + this.getAllItemsCallback(page.offset + 1, this.filters); + } + + setSort(filters: JsonData) { + this.filters = { ...this.baseFilters, ...filters }; + const pageNumber = 1; + this.getAllItemsCallback(pageNumber, this.filters); + } + + setFilter(filters) { + this.filters = { ...this.baseFilters, ...filters }; + this.getAllItemsCallback(1, this.filters); + } +} diff --git a/frontend/app/class/monitoring-site.ts b/frontend/app/class/monitoring-site.ts new file mode 100644 index 000000000..d33650386 --- /dev/null +++ b/frontend/app/class/monitoring-site.ts @@ -0,0 +1,13 @@ +export enum columnNameSite { + base_site_name = "Nom", + last_visit = "Dernière visite", + nb_visits = "Nb. visites", + base_site_code = "Code", + altitude_max = "Alt.max", + altitude_min = "Alt.min", +} + +export const extendedDetailsSite = { + ...columnNameSite, + base_site_description: "Description", +}; diff --git a/frontend/app/class/monitoring-sites-group.ts b/frontend/app/class/monitoring-sites-group.ts new file mode 100644 index 000000000..96fab297d --- /dev/null +++ b/frontend/app/class/monitoring-sites-group.ts @@ -0,0 +1,12 @@ +export enum columnNameSiteGroup { + sites_group_name = "Nom", + nb_sites = "Nb. sites", + nb_visits = "Nb. visites", + sites_group_code = "Code", +} + +export const extendedDetailsSiteGroup = { + ...columnNameSiteGroup, + comments: "Commentaires", + sites_group_description: "Description", +}; diff --git a/frontend/app/components/modules/modules.component.html b/frontend/app/components/modules/modules.component.html index 611c18166..3e63f83e2 100644 --- a/frontend/app/components/modules/modules.component.html +++ b/frontend/app/components/modules/modules.component.html @@ -14,7 +14,7 @@

{{titleModule}}

{{description}}
-
diff --git a/frontend/app/components/modules/modules.component.ts b/frontend/app/components/modules/modules.component.ts index 9a45f8f00..56957d49d 100644 --- a/frontend/app/components/modules/modules.component.ts +++ b/frontend/app/components/modules/modules.component.ts @@ -14,8 +14,6 @@ import { AuthService, User } from "@geonature/components/auth/auth.service"; styleUrls: ["./modules.component.css"], }) export class ModulesComponent implements OnInit { - - currentUser: User; description: string; @@ -34,7 +32,7 @@ export class ModulesComponent implements OnInit { private _auth: AuthService, private _dataMonitoringObjectService: DataMonitoringObjectService, private _configService: ConfigService - ) { } + ) {} ngOnInit() { this.bLoading = true; @@ -67,10 +65,4 @@ export class ModulesComponent implements OnInit { this.currentUser["cruved"] = {}; this.currentUser["cruved_objects"] = {}; } - - onAccessSitesClick(modules) { - console.log("accès aux sites avec droits ") - console.log(modules) - } - } diff --git a/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.css b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.css new file mode 100644 index 000000000..6017b44ab --- /dev/null +++ b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.css @@ -0,0 +1,48 @@ +.cell-link { + cursor: pointer; +} + +:host::ng-deep .datatable-body-row.active .datatable-row-group { + background-color: rgb(117, 227, 118) !important; +} + +.link:hover { + background-color: rgba(0, 0, 0, 0.2) !important; + transition: background-color 0.5; +} + +.link { + display: inline; + transition: background-color 0.5s; + border-radius: 5px; +} + +.header-filter-span > input { + width: 100%; +} + +.header-sort-span { + /* width: 100%; */ + cursor: pointer; + text-overflow: ellipsis; + overflow: hidden; + white-space:nowrap +} + + +.header-sort-span:hover { + background-color: rgb(245, 245, 245); +} + +.icon-sort { + font-size: 1.2em; + float: right; +} + +:host::ng-deep .sort-btn { + display: none !important; +} + +.custom-dt { + box-shadow: none !important; +} diff --git a/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.html b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.html new file mode 100644 index 000000000..17e0a03ed --- /dev/null +++ b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.html @@ -0,0 +1,121 @@ + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + help + + {{ column.name }} +
+
+ +
+
+
+ diff --git a/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.spec.ts b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.spec.ts new file mode 100644 index 000000000..deaea12fe --- /dev/null +++ b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MonitoringDatatableComponent } from './monitoring-datatable-g.component'; + +describe('MonitoringDatatableComponent', () => { + let component: MonitoringDatatableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MonitoringDatatableComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MonitoringDatatableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.ts b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.ts new file mode 100644 index 000000000..b152c85cd --- /dev/null +++ b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.ts @@ -0,0 +1,204 @@ +import { DatatableComponent } from "@swimlane/ngx-datatable"; +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + ViewChild, + SimpleChanges, + TemplateRef, +} from "@angular/core"; +import { Router, ActivatedRoute } from "@angular/router"; +import { Subject } from "rxjs"; +import { debounceTime } from "rxjs/operators"; +import { DataTableService } from "../../services/data-table.service"; +import { IColumn } from "../../interfaces/column"; +import { IPage } from "../../interfaces/page"; +import { ObjectService } from "../../services/object.service"; + +interface ItemObjectTable { + id: number | null; + selected: boolean; + visible: boolean; + current: boolean; +} +type ItemsObjectTable = { [key: string]: ItemObjectTable }; + +@Component({ + selector: "pnx-monitoring-datatable-g", + templateUrl: "./monitoring-datatable-g.component.html", + styleUrls: ["./monitoring-datatable-g.component.css"], +}) +export class MonitoringDatatableGComponent implements OnInit { + @Input() rows; + @Input() colsname: IColumn[]; + @Input() page: IPage = { count: 0, limit: 0, page: 0 }; + @Input() obj; + + @Input() rowStatus: Array; + @Output() rowStatusChange = new EventEmitter(); + + @Output() bEditChanged = new EventEmitter(); + + @Input() currentUser; + + @Output() onSort = new EventEmitter(); + @Output() onFilter = new EventEmitter(); + @Output() onSetPage = new EventEmitter(); + @Output() onDetailsRow = new EventEmitter(); + @Output() addEvent = new EventEmitter(); + + private filterSubject: Subject = new Subject(); + displayFilter: boolean = false; + objectsStatus: ItemsObjectTable; + + objectType: string = ""; + columns; + row_save; + selected = []; + filters = {}; + + @ViewChild(DatatableComponent) table: DatatableComponent; + @ViewChild("actionsTemplate") actionsTemplate: TemplateRef; + @ViewChild("hdrTpl") hdrTpl: TemplateRef; + + constructor( + private _dataTableService: DataTableService, + private _objService: ObjectService, + private router: Router, + private _Activatedroute: ActivatedRoute + ) {} + + ngOnInit() { + this.initDatatable(); + } + + initDatatable() { + // IF prefered observable compare to ngOnChanges uncomment this: + // this._dataTableService.currentCols.subscribe(newCols => { this.columns = newCols }) + this._objService.currentObjectType.subscribe((newObjType) => { + this.objectType = newObjType; + }); + + this.filters = {}; + this.filterSubject.pipe(debounceTime(500)).subscribe(() => { + this.filter(); + }); + } + + onSortEvent($event) { + this.filters = { + ...this.filters, + sort: $event.column.prop, + sort_dir: $event.newValue, + }; + this.onSort.emit(this.filters); + } + + setPage($event) { + this.onSetPage.emit($event); + } + + filterInput($event) { + this.filterSubject.next(); + } + + filter(bInitFilter = false) { + // filter all + const oldFilters = this.filters; + this.filters = Object.keys(oldFilters).reduce(function (r, e) { + if (![undefined, "", null].includes(oldFilters[e])) r[e] = oldFilters[e]; + return r; + }, {}); + this.onFilter.emit(this.filters); + } + + onSelectEvent({ selected }) { + const id = selected[0].id_group_site; + + if (!this.rowStatus) { + return; + } + + this.rowStatus.forEach((status) => { + const bCond = status.id === id; + status["selected"] = bCond && !status["selected"]; + }); + + this.setSelected(); + this.rowStatusChange.emit(this.rowStatus); + } + + setSelected() { + // this.table._internalRows permet d'avoir les ligne triées et d'avoir les bons index + + if (!this.rowStatus) { + return; + } + + const status_selected = this.rowStatus.find((status) => status.selected); + if (!status_selected) { + return; + } + + const index_row_selected = this.table._internalRows.findIndex( + (row) => row.id === status_selected.id + ); + if (index_row_selected === -1) { + return; + } + + this.selected = [this.table._internalRows[index_row_selected]]; + this.table.offset = Math.floor(index_row_selected / this.table._limit); + } + + ngOnDestroy() { + this.filterSubject.unsubscribe(); + } + + // tooltip(column) { + // return this.child0.template.fieldDefinitions[column.prop] + // ? column.name + " : " + this.child0.template.fieldDefinitions[column.prop] + // : column.name; + // } + + ngOnChanges(changes: SimpleChanges) { + // IF prefered ngOnChanges compare to observable uncomment this: + if (changes["rows"] && this.rows && this.rows.length > 0) { + this.columns = this._dataTableService.colsTable( + this.colsname, + this.rows[0] + ); + } + + if (changes["colsname"]) { + this.filters = {}; + } + + if (changes["obj"] && this.obj) { + this.objectsStatus, + (this.rowStatus = this._dataTableService.initObjectsStatus( + this.obj, + "sites_groups" + )); + } + for (const propName of Object.keys(changes)) { + switch (propName) { + case "rowStatus": + this.setSelected(); + break; + } + } + } + navigateToAddChildren(_, rowId) { + this.addEvent.emit(rowId); + this.router.navigate(["create"], { + relativeTo: this._Activatedroute, + }); + } + navigateToDetail(row) { + row["id"] = row.pk; + this.onDetailsRow.emit(row); + } +} diff --git a/frontend/app/components/monitoring-form-g/monitoring-form.component-g.css b/frontend/app/components/monitoring-form-g/monitoring-form.component-g.css new file mode 100644 index 000000000..a5c26b9ca --- /dev/null +++ b/frontend/app/components/monitoring-form-g/monitoring-form.component-g.css @@ -0,0 +1,31 @@ +:host ::ng-deep .obj-form { + margin: 0; + margin-bottom: 10px; + padding: 0; +} + +.hide-spinner { + display: none; +} + +.btn-height { + height: 39px; +} + +.float-right { +margin-left: 5px; +} + + +.float-left { + margin-right: 10px; + float: left; +} + +form:invalid { + outline: none; +} + +form.ng-invalid { + border: 0px !important; +} \ No newline at end of file diff --git a/frontend/app/components/monitoring-form-g/monitoring-form.component-g.html b/frontend/app/components/monitoring-form-g/monitoring-form.component-g.html new file mode 100644 index 000000000..389e9284b --- /dev/null +++ b/frontend/app/components/monitoring-form-g/monitoring-form.component-g.html @@ -0,0 +1,124 @@ + +

Attention

+

+ + Vous êtes sur le point de supprimer le groupe de site + Description du groupe de site +

+ + +
+ +
+
+ + + + + + + +
+ + + + + + + + +
+ + + + + + +
+
+
+
+
diff --git a/frontend/app/components/monitoring-form-g/monitoring-form.component-g.spec.ts b/frontend/app/components/monitoring-form-g/monitoring-form.component-g.spec.ts new file mode 100644 index 000000000..1cfc81be0 --- /dev/null +++ b/frontend/app/components/monitoring-form-g/monitoring-form.component-g.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MonitoringFormComponent } from './monitoring-form.component'; + +describe('MonitoringFormComponent', () => { + let component: MonitoringFormComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MonitoringFormComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MonitoringFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/app/components/monitoring-form-g/monitoring-form.component-g.ts b/frontend/app/components/monitoring-form-g/monitoring-form.component-g.ts new file mode 100644 index 000000000..c993c5026 --- /dev/null +++ b/frontend/app/components/monitoring-form-g/monitoring-form.component-g.ts @@ -0,0 +1,396 @@ +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + SimpleChanges, +} from "@angular/core"; +import { FormGroup, FormBuilder } from "@angular/forms"; +import { MonitoringObject } from "../../class/monitoring-object"; +import { ConfigService } from "../../services/config.service"; +import { CommonService } from "@geonature_common/service/common.service"; +import { DynamicFormService } from "@geonature_common/form/dynamic-form-generator/dynamic-form.service"; +import { ActivatedRoute } from "@angular/router"; +import { EditObjectService } from "../../services/edit-object.service"; +import { Router } from "@angular/router"; +import { IDataForm } from "../../interfaces/form"; +import { ApiGeomService } from "../../services/api-geom.service"; +@Component({ + selector: "pnx-monitoring-form-g", + templateUrl: "./monitoring-form.component-g.html", + styleUrls: ["./monitoring-form.component-g.css"], +}) +export class MonitoringFormComponentG implements OnInit { + @Input() currentUser; + + @Input() objForm: FormGroup; + + // @Input() obj: any; + @Output() objChanged = new EventEmitter(); + + @Input() objectsStatus; + @Output() objectsStatusChange = new EventEmitter(); + + @Input() bEdit: boolean; + @Output() bEditChange = new EventEmitter(); + + @Input() sites: {}; + dataForm: IDataForm; + searchSite = ""; + + obj: any; + objFormsDefinition; + + meta: {}; + + public bSaveSpinner = false; + public bSaveAndAddChildrenSpinner = false; + public bDeleteSpinner = false; + public bDeleteModal = false; + public bChainInput = false; + public bAddChildren = false; + public chainShow = []; + + public queryParams = {}; + + constructor( + private _formBuilder: FormBuilder, + private _route: ActivatedRoute, + private _configService: ConfigService, + private _commonService: CommonService, + private _dynformService: DynamicFormService, + private _editService: EditObjectService, + private _apiGeomService: ApiGeomService, + private _router: Router + ) {} + + ngOnInit() { + this._editService.currentData.subscribe((dataToEdit) => { + this.obj = dataToEdit; + this.obj.bIsInitialized = true; + this._configService + .init(this.obj.moduleCode) + .pipe() + .subscribe(() => { + // return this._route.queryParamMap; + // }) + // .subscribe((queryParams) => { + this.queryParams = this._route.snapshot.queryParams || {}; + this.bChainInput = + this._configService.frontendParams()["bChainInput"]; + + const schema = this._configService.schema( + this.obj.moduleCode, + this.obj.objectType + ); + this.obj[this.obj.moduleCode] = schema; + // const schema = this.obj.schema(); + + // init objFormsDefinition + + // meta pour les parametres dynamiques + // ici pour avoir acces aux nomenclatures + this.meta = { + // nomenclatures: this._dataUtilsService.getDataUtil('nomenclature'), + // dataset: this._dataUtilsService.getDataUtil('dataset'), + // id_role: this.currentUser.id_role, + bChainInput: this.bChainInput, + parents: this.obj.parents, + }; + + this.objFormsDefinition = this._dynformService + .formDefinitionsdictToArray(schema, this.meta) + .filter((formDef) => formDef.type_widget) + .sort((a, b) => { + // medias à la fin + return a.attribut_name === "medias" + ? +1 + : b.attribut_name === "medias" + ? -1 + : 0; + }); + + // display_form pour customiser l'ordre dans le formulaire + // les éléments de display form sont placé en haut dans l'ordre du tableau + // tous les éléments non cachés restent affichés + + let displayProperties = [ + ...(this._configService.configModuleObjectParam( + this.obj.moduleCode, + this.obj.objectType, + "display_properties" + ) || []), + ]; + if (displayProperties && displayProperties.length) { + displayProperties.reverse(); + this.objFormsDefinition.sort((a, b) => { + let indexA = displayProperties.findIndex( + (e) => e == a.attribut_name + ); + let indexB = displayProperties.findIndex( + (e) => e == b.attribut_name + ); + return indexB - indexA; + }); + } + + // champs patch pour simuler un changement de valeur et déclencher le recalcul des propriété + // par exemple quand bChainInput change + this.objForm.addControl("patch_update", this._formBuilder.control(0)); + + // this._configService.configModuleObject(this.obj.moduleCode, this.obj.objectType); + // set geometry + // if (this.obj.config["geometry_type"]) { + // this.objForm.addControl( + // "geometry", + // this._formBuilder.control("", Validators.required) + // ); + // } + + // pour donner la valeur de idParent + + this.initForm(); + }); + }); + } + + /** pour réutiliser des paramètres déjà saisis */ + keepDefinitions() { + return this.objFormsDefinition.filter((def) => + this.obj.configParam("keep").includes(def.attribut_name) + ); + } + + setQueryParams() { + // par le biais des parametre query de route on donne l'id du ou des parents + // permet d'avoir un 'tree' ou un objet est sur plusieurs branches + // on attend des ids d'où test avec parseInt + for (const key of Object.keys(this.queryParams)) { + const strToInt = parseInt(this.queryParams[key]); + if (strToInt != NaN) { + this.obj.properties[key] = strToInt; + } + } + } + + /** initialise le formulaire quand le formulaire est prêt ou l'object est prêt */ + initForm() { + if (!(this.objForm && this.obj.bIsInitialized)) { + return; + } + + this.setQueryParams(); + // pour donner la valeur de l'objet au formulaire + this._editService.formValues(this.obj).subscribe((formValue) => { + this.objForm.patchValue(formValue); + this.setDefaultFormValue(); + this.dataForm = formValue; + // reset geom ? + }); + } + + keepNames() { + return this.obj.configParam("keep") || []; + } + + resetObjForm() { + // quand on enchaine les relevés + const chainShow = this.obj.configParam("chain_show"); + if (chainShow) { + this.chainShow.push( + chainShow.map((key) => this.obj.resolvedProperties[key]) + ); + this.chainShow.push(this.obj.resolvedProperties); + } + + // les valeur que l'on garde d'une saisie à l'autre + const keep = {}; + for (const key of this.keepNames()) { + keep[key] = this.obj.properties[key]; + } + + // nouvel object + this.obj = new MonitoringObject( + this.obj.moduleCode, + this.obj.objectType, + null, + this.obj.monitoringObjectService() + ); + this.obj.init({}); + + this.obj.properties[this.obj.configParam("id_field_Name")] = null; + + // pq get ????? + // this.obj.get(0).subscribe(() => { + this.obj.bIsInitialized = true; + for (const key of this.keepNames()) { + this.obj.properties[key] = keep[key]; + } + + this.objChanged.emit(this.obj); + this.objForm.patchValue({ geometry: null }); + this.initForm(); + // }); + } + + /** Pour donner des valeurs par defaut si la valeur n'est pas définie + * id_digitiser => current_user.id_role + * id_inventor => current_user.id_role + * date => today + */ + setDefaultFormValue() { + const value = this.objForm.value; + const date = new Date(); + const defaultValue = { + // id_digitiser: value["id_digitiser"] || this.currentUser.id_role, + // id_inventor: value["id_inventor"] || this.currentUser.id_role, + first_use_date: value["first_use_date"] || { + year: date.getUTCFullYear(), + month: date.getUTCMonth() + 1, + day: date.getUTCDate(), + }, + }; + this.objForm.patchValue(defaultValue); + } + + /** + * TODO faire des fonctions dans monitoring objet (ou moniotring objet service pour naviguer + */ + + /** + * Valider et renseigner les enfants + */ + navigateToAddChildren() { + this.bEditChange.emit(false); + this.obj.navigateToAddChildren(); + } + + /** + * Valider et aller à la page de l'objet + */ + navigateToDetail(id, objectType, queryParams) { + // patch bug navigation + this._router.navigate( + ["monitorings", objectType, id].filter((s) => !!s), + { + queryParams, + } + ); + this.bEditChange.emit(false); + } + + /** + * Valider et aller à la page de l'objet + */ + navigateToParent() { + this.bEditChange.emit(false); // patch bug navigation + this._router.navigateByUrl("/monitorings/sites_group"); + + // this.obj.navigateToParent(); + } + + msgToaster(action) { + // return `${action} ${this.obj.labelDu()} ${this.obj.description()} effectuée`.trim(); + return `${action} effectuée`.trim(); + } + + /** TODO améliorer site etc.. */ + onSubmit() { + const { patch_update, ...sendValue } = this.dataForm; + const action = this.obj.id + ? // ? this.obj.patch(this.objForm.value) + // : this.obj.post(this.objForm.value); + this._apiGeomService.patch(this.obj.id, sendValue) + : this._apiGeomService.create(sendValue); + const actionLabel = this.obj.id ? "Modification" : "Création"; + action.subscribe((objData) => { + this._commonService.regularToaster( + "success", + this.msgToaster(actionLabel) + ); + this.bSaveSpinner = this.bSaveAndAddChildrenSpinner = false; + this.objChanged.emit(this.obj); + + /** si c'est un module : reset de la config */ + if (this.obj.objectType === "module") { + this._configService.loadConfig(this.obj.moduleCode).subscribe(); + } + + if (this.bChainInput) { + this.resetObjForm(); + } else if (this.bAddChildren) { + this.navigateToAddChildren(); + } else { + if ( + this._configService.configModuleObjectParam( + this.obj.moduleCode, + this.obj.objectType, + "redirect_to_parent" + ) + ) { + this.navigateToParent(); + } else { + this.navigateToDetail( + this.obj.id, + this.obj.objectType, + this.queryParams + ); + } + } + }); + } + + onCancelEdit() { + if (this.obj.id) { + this.bEditChange.emit(false); + } else { + this.navigateToParent(); + } + } + + onDelete() { + this.bDeleteSpinner = true; + this._commonService.regularToaster("info", this.msgToaster("Suppression")); + // : this.obj.post(this.objForm.value); + this._apiGeomService.delete(this.obj.id).subscribe((del) => { + this.bDeleteSpinner = this.bDeleteModal = false; + this.objChanged.emit(this.obj); + setTimeout(() => { + this.navigateToParent(); + }, 100); + }); + } + + onObjFormValueChange(event) { + // let {id_module,medias, ...rest} = this.objForm.value; + // this.dataForm = rest + this.dataForm = this.objForm.value; + const change = this._configService.change( + this.obj.moduleCode, + this.obj.objectType + ); + if (!change) { + return; + } + setTimeout(() => { + change({ objForm: this.objForm, meta: this.meta }); + }, 100); + } + + procesPatchUpdateForm() { + this.objForm.patchValue({ + patch_update: this.objForm.value.patch_update + 1, + }); + } + + /** bChainInput gardé dans config service */ + bChainInputChanged() { + for (const formDef of this.objFormsDefinition) { + formDef.meta.bChainInput = this.bChainInput; + } + this._configService.setFrontendParams("bChainInput", this.bChainInput); + // patch pour recalculers + this.procesPatchUpdateForm(); + } +} diff --git a/frontend/app/components/monitoring-form/monitoring-form.component.ts b/frontend/app/components/monitoring-form/monitoring-form.component.ts index 3e0ae5634..5e3ca239a 100644 --- a/frontend/app/components/monitoring-form/monitoring-form.component.ts +++ b/frontend/app/components/monitoring-form/monitoring-form.component.ts @@ -281,13 +281,11 @@ export class MonitoringFormComponent implements OnInit { onDelete() { this.bDeleteSpinner = true; - this._commonService.regularToaster('info', this.msgToaster('Suppression')); - this.obj.delete().subscribe((objData) => { this.bDeleteSpinner = this.bDeleteModal = false; this.obj.deleted = true; this.objChanged.emit(this.obj); - + this._commonService.regularToaster('info', this.msgToaster('Suppression')); setTimeout(() => { this.navigateToParent(); }, 100); diff --git a/frontend/app/components/monitoring-map-list/monitoring-map-list.component.css b/frontend/app/components/monitoring-map-list/monitoring-map-list.component.css new file mode 100644 index 000000000..4c1e5a93e --- /dev/null +++ b/frontend/app/components/monitoring-map-list/monitoring-map-list.component.css @@ -0,0 +1,78 @@ +.flex-container { + background-color: rgb(240, 240, 240); + display: flex; + } + + .flex-container > div { + width: 50%; + padding: 10px; + margin: 10px; + } + + .flex-container > div:first-child { + margin-right: 0; + } + + .scroll { + overflow-y: scroll; + } + + :host ::ng-deep .cadre { + background-color: white; + /* border: 1px solid grey;*/ + border-radius: 5px; + padding: 5px; + margin: 5px; + /* display: inline-block; */ + } + + /* TABLE */ + + .cell-link { + cursor: pointer; + } + + :host::ng-deep .datatable-body-row.active .datatable-row-group { + background-color: rgb(117, 227, 118) !important; + } + + .link:hover { + background-color: rgba(0, 0, 0, 0.2) !important; + transition: background-color 0.5; + } + + .link { + display: inline; + transition: background-color 0.5s; + border-radius: 5px; + } + + .header-filter-span > input { + width: 100%; + } + + .header-sort-span { + /* width: 100%; */ + cursor: pointer; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .header-sort-span:hover { + background-color: rgb(245, 245, 245); + } + + .icon-sort { + font-size: 1.2em; + float: right; + } + + :host::ng-deep .sort-btn { + display: none !important; + } + + .custom-dt { + box-shadow: none !important; + } + \ No newline at end of file diff --git a/frontend/app/components/monitoring-map-list/monitoring-map-list.component.html b/frontend/app/components/monitoring-map-list/monitoring-map-list.component.html new file mode 100644 index 000000000..ee93b5280 --- /dev/null +++ b/frontend/app/components/monitoring-map-list/monitoring-map-list.component.html @@ -0,0 +1,13 @@ +
+
+
+ +
+
+
+
+ + +
+
+
\ No newline at end of file diff --git a/frontend/app/components/monitoring-map-list/monitoring-map-list.component.ts b/frontend/app/components/monitoring-map-list/monitoring-map-list.component.ts new file mode 100644 index 000000000..65bf3f50d --- /dev/null +++ b/frontend/app/components/monitoring-map-list/monitoring-map-list.component.ts @@ -0,0 +1,11 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "monitoring-map-list.component", + templateUrl: "./monitoring-map-list.component.html", + styleUrls: ["./monitoring-map-list.component.css"], +}) +export class MonitoringMapListComponent { + displayMap: boolean = true; + constructor() {} +} diff --git a/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.css b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.css new file mode 100644 index 000000000..c2fc53c60 --- /dev/null +++ b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.css @@ -0,0 +1,33 @@ +table { + font-size: small; +} + +th { + text-align: right; +} + +.key { + font-weight: bold; +} + +td { + padding-left: 20px; +} + +.small-icon { + font-size: 18px; +} + +.medias-tab { + margin: 10px; +} + +.hide-spinner { + display: none; +} + + +::ng-deep .cdk-global-overlay-wrapper, +::ng-deep .cdk-overlay-container { + z-index: 99999 !important; +} diff --git a/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.html b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.html new file mode 100644 index 000000000..ebab4aca9 --- /dev/null +++ b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.html @@ -0,0 +1,114 @@ +
+
+ + +
+
diff --git a/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.spec.ts b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.spec.ts new file mode 100644 index 000000000..dd9bf0fc3 --- /dev/null +++ b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MonitoringPropertiesComponent } from './monitoring-properties-g.component'; + +describe('MonitoringPropertiesGComponent', () => { + let component: MonitoringPropertiesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MonitoringPropertiesComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MonitoringPropertiesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.ts b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.ts new file mode 100644 index 000000000..d47a3ef60 --- /dev/null +++ b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.ts @@ -0,0 +1,49 @@ +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + SimpleChanges, +} from "@angular/core"; +import { FormControl } from "@angular/forms"; +import { extendedDetailsSiteGroup } from "../../class/monitoring-sites-group"; +import { ISitesGroup } from "../../interfaces/geom"; +import { EditObjectService } from "../../services/edit-object.service"; +import { ObjectService } from "../../services/object.service"; + +@Component({ + selector: "pnx-monitoring-properties-g", + templateUrl: "./monitoring-properties-g.component.html", + styleUrls: ["./monitoring-properties-g.component.css"], +}) +export class MonitoringPropertiesGComponent implements OnInit { + @Input() selectedObj: ISitesGroup; + @Input() bEdit: boolean; + @Output() bEditChange = new EventEmitter(); + @Input() objectType: string; + + infosColsSiteGroups: typeof extendedDetailsSiteGroup = + extendedDetailsSiteGroup; + color: string = "white"; + dataDetails: ISitesGroup; + + datasetForm = new FormControl(); + + constructor( + private _editService: EditObjectService, + private _objService: ObjectService + ) {} + + ngOnInit() { + this._objService.currentObjectTypeParent.subscribe((newObjType) => { + this.objectType = newObjType; + }); + } + + onEditClick() { + this.bEditChange.emit(true); + this.selectedObj["id"] = this.selectedObj[this.selectedObj.pk]; + this._editService.changeDataSub(this.selectedObj); + } +} diff --git a/frontend/app/components/monitoring-sites/monitoring-sites.component.css b/frontend/app/components/monitoring-sites/monitoring-sites.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/monitoring-sites/monitoring-sites.component.html b/frontend/app/components/monitoring-sites/monitoring-sites.component.html new file mode 100644 index 000000000..4c9a60e1c --- /dev/null +++ b/frontend/app/components/monitoring-sites/monitoring-sites.component.html @@ -0,0 +1,25 @@ + + + + diff --git a/frontend/app/components/monitoring-sites/monitoring-sites.component.ts b/frontend/app/components/monitoring-sites/monitoring-sites.component.ts new file mode 100644 index 000000000..00b9cd752 --- /dev/null +++ b/frontend/app/components/monitoring-sites/monitoring-sites.component.ts @@ -0,0 +1,138 @@ +import { Component, OnInit, Input } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { forkJoin } from "rxjs"; +import { tap, map, mergeMap } from "rxjs/operators"; +import * as L from "leaflet"; +import { ISite, ISitesGroup } from "../../interfaces/geom"; +import { IPage, IPaginated } from "../../interfaces/page"; +import { columnNameSite } from "../../class/monitoring-site"; +import { MonitoringGeomComponent } from "../../class/monitoring-geom-component"; +import { setPopup } from "../../functions/popup"; +import { GeoJSONService } from "../../services/geojson.service"; +import { FormGroup, FormBuilder } from "@angular/forms"; +import { + SitesService, + SitesGroupService, +} from "../../services/api-geom.service"; +import { ObjectService } from "../../services/object.service"; + +const LIMIT = 10; + +@Component({ + selector: "monitoring-sites", + templateUrl: "./monitoring-sites.component.html", + styleUrls: ["./monitoring-sites.component.css"], +}) +export class MonitoringSitesComponent + extends MonitoringGeomComponent + implements OnInit +{ + siteGroupId: number; + sites: ISite[]; + sitesGroup: ISitesGroup; + colsName: typeof columnNameSite = columnNameSite; + page: IPage; + filters = {}; + siteGroupLayer: L.FeatureGroup; + @Input() bEdit: boolean; + objForm: FormGroup; + objectType: string; + + constructor( + private _sitesGroupService: SitesGroupService, + private _siteService: SitesService, + private _objService: ObjectService, + private router: Router, + private _Activatedroute: ActivatedRoute, + private _geojsonService: GeoJSONService, + private _formBuilder: FormBuilder + ) { + super(); + this.getAllItemsCallback = this.getSitesFromSiteGroupId; + } + + ngOnInit() { + this.objForm = this._formBuilder.group({}); + this._objService.changeObjectType(this._siteService.addObjectType()); + this.initSite(); + } + + initSite() { + this._Activatedroute.params + .pipe( + map((params) => params["id"] as number), + tap((id: number) => { + this._geojsonService.getSitesGroupsChildGeometries( + this.onEachFeatureSite(), + { id_sites_group: id } + ); + }), + mergeMap((id: number) => + forkJoin({ + sitesGroup: this._sitesGroupService.getById(id), + sites: this._sitesGroupService.getSitesChild(1, this.limit, { + id_sites_group: id, + }), + }) + ) + ) + .subscribe( + (data: { sitesGroup: ISitesGroup; sites: IPaginated }) => { + this.sitesGroup = data.sitesGroup; + this.sites = data.sites.items; + this.page = { + count: data.sites.count, + page: data.sites.page, + limit: data.sites.limit, + }; + this.siteGroupLayer = this._geojsonService.setMapData( + data.sitesGroup.geometry, + () => {} + ); + this.baseFilters = { id_sites_group: this.sitesGroup.id_sites_group }; + } + ); + } + ngOnDestroy() { + this._geojsonService.removeFeatureGroup( + this._geojsonService.sitesFeatureGroup + ); + this._geojsonService.removeFeatureGroup(this.siteGroupLayer); + } + + onEachFeatureSite() { + const baseUrl = this.router.url; + return (feature, layer) => { + const popup = setPopup( + baseUrl, + feature.properties.id_base_site, + "Site :" + feature.properties.base_site_name + ); + layer.bindPopup(popup); + }; + } + + getSitesFromSiteGroupId(page, params) { + this._sitesGroupService + .getSitesChild(page, LIMIT, params) + .subscribe((data: IPaginated) => { + this.sites = data.items; + this.page = { + count: data.count, + limit: data.limit, + page: data.page - 1, + }; + }); + } + + seeDetails($event) { + this._objService.changeObjectTypeParent(this._siteService.editObjectType()); + this.router.navigate([`sites/${$event.id_base_site}`], { + relativeTo: this._Activatedroute, + }); + } + + onObjChanged($event) { + this.initSite(); + } +} diff --git a/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.css b/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.html b/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.html new file mode 100644 index 000000000..69379ea77 --- /dev/null +++ b/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.html @@ -0,0 +1,4 @@ + +
+ +
\ No newline at end of file diff --git a/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.ts b/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.ts new file mode 100644 index 000000000..4138a321b --- /dev/null +++ b/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from "@angular/core"; +import { EditObjectService } from "../../services/edit-object.service"; +import { FormGroup, FormBuilder } from "@angular/forms"; +import { ISitesGroup } from "../../interfaces/geom"; + +@Component({ + selector: "monitoring-sitesgroups-create", + templateUrl: "./monitoring-sitesgroups-create.component.html", + styleUrls: ["./monitoring-sitesgroups-create.component.css"], +}) +export class MonitoringSitesGroupsCreateComponent implements OnInit { + siteGroup: ISitesGroup; + form: FormGroup; + constructor( + private _editService: EditObjectService, + private _formBuilder: FormBuilder + ) {} + + ngOnInit() { + this._editService.changeDataSub({}); + this.form = this._formBuilder.group({}); + } +} diff --git a/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.css b/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.html b/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.html new file mode 100644 index 000000000..2ddf3aae0 --- /dev/null +++ b/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.html @@ -0,0 +1,6 @@ + +
+ +
\ No newline at end of file diff --git a/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.ts b/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.ts new file mode 100644 index 000000000..88473a04f --- /dev/null +++ b/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.ts @@ -0,0 +1,129 @@ +import { Component, OnInit, Input } from "@angular/core"; +import { SitesGroupService } from "../../services/api-geom.service"; +import { columnNameSiteGroup } from "../../class/monitoring-sites-group"; +import { IPaginated, IPage } from "../../interfaces/page"; +import { Router, ActivatedRoute } from "@angular/router"; +import { columnNameSite } from "../../class/monitoring-site"; +import { ISite, ISitesGroup } from "../../interfaces/geom"; +import { GeoJSONService } from "../../services/geojson.service"; +import { MonitoringGeomComponent } from "../../class/monitoring-geom-component"; +import { setPopup } from "../../functions/popup"; +import { ObjectService } from "../../services/object.service"; +import { FormGroup, FormBuilder } from "@angular/forms"; + +const LIMIT = 10; + +@Component({ + selector: "monitoring-sitesgroups", + templateUrl: "./monitoring-sitesgroups.component.html", + styleUrls: ["./monitoring-sitesgroups.component.css"], +}) +export class MonitoringSitesGroupsComponent + extends MonitoringGeomComponent + implements OnInit +{ + @Input() page: IPage; + @Input() sitesGroups: ISitesGroup[]; + @Input() sitesChild: ISite[]; + @Input() columnNameSiteGroup: typeof columnNameSiteGroup = + columnNameSiteGroup; + @Input() columnNameSite: typeof columnNameSite = columnNameSite; + @Input() sitesGroupsSelected: ISitesGroup; + + // @Input() rows; + @Input() colsname; + @Input() obj; + objectType: string; + objForm: FormGroup; + objInitForm: Object = {}; + // siteGroupEmpty={ + // "comments" :'', + // sites_group_code: string; + // sites_group_description: string; + // sites_group_name: string; + // uuid_sites_group: string; //FIXME: see if OK + // } + + constructor( + private _sites_group_service: SitesGroupService, + public geojsonService: GeoJSONService, + private router: Router, + private _objService: ObjectService, + private _formBuilder: FormBuilder, + private _Activatedroute: ActivatedRoute // private _routingService: RoutingService + ) { + super(); + this.getAllItemsCallback = this.getSitesGroups; + } + + ngOnInit() { + this.initSiteGroup(); + } + + initSiteGroup() { + this._objService.changeObjectTypeParent( + this._sites_group_service.editObjectType() + ); + this._objService.changeObjectType( + this._sites_group_service.addObjectType() + ); + this.getSitesGroups(1); + this.geojsonService.getSitesGroupsGeometries( + this.onEachFeatureSiteGroups() + ); + } + + ngOnDestroy() { + this.geojsonService.removeFeatureGroup( + this.geojsonService.sitesGroupFeatureGroup + ); + } + + onEachFeatureSiteGroups(): Function { + const baseUrl = this.router.url; + return (feature, layer) => { + const popup = setPopup( + baseUrl, + feature.properties.id_sites_group, + "Groupe de site :" + feature.properties.sites_group_name + ); + layer.bindPopup(popup); + }; + } + + getSitesGroups(page = 1, params = {}) { + this._sites_group_service + .get(page, LIMIT, params) + .subscribe((data: IPaginated) => { + this.page = { + count: data.count, + limit: data.limit, + page: data.page - 1, + }; + this.sitesGroups = data.items; + this.colsname = this.columnNameSiteGroup; + // IF prefered observable compare to ngOnChanges uncomment this: + // this._dataTableService.changeColsTable(this.colsname,this.sitesGroups[0]) + }); + } + + seeDetails($event) { + // TODO: routerLink + this._objService.changeObjectTypeParent( + this._sites_group_service.editObjectType() + ); + this.router.navigate([$event.id_sites_group], { + relativeTo: this._Activatedroute, + }); + } + + addSiteGp($event) { + this.router.navigate(["/create"], { + relativeTo: this._Activatedroute, + }); + } + onSelect($event) { + this.geojsonService.selectSitesGroupLayer($event); + } + onObjChanged($event) {} +} diff --git a/frontend/app/functions/popup.ts b/frontend/app/functions/popup.ts new file mode 100644 index 000000000..e6e22b90c --- /dev/null +++ b/frontend/app/functions/popup.ts @@ -0,0 +1,14 @@ +export function setPopup(baseUrl: string, id: number, name: string) { + const url = `#/${baseUrl}/${id}/`; + + const popup = ` +
+

${name}

+ + + +
+ `; + + return popup; +} diff --git a/frontend/app/gnModule.module.ts b/frontend/app/gnModule.module.ts index 64fecadc7..5cf2828de 100644 --- a/frontend/app/gnModule.module.ts +++ b/frontend/app/gnModule.module.ts @@ -25,15 +25,29 @@ import { MonitoringFormComponent } from "./components/monitoring-form/monitoring import { MonitoringListComponent } from "./components/monitoring-lists/monitoring-lists.component"; import { MonitoringPropertiesComponent } from "./components/monitoring-properties/monitoring-properties.component"; import { MonitoringDatatableComponent } from "./components/monitoring-datatable/monitoring-datatable.component"; +import { MonitoringDatatableGComponent } from "./components/monitoring-datatable-g/monitoring-datatable-g.component"; import { MatSlideToggleModule } from "@angular/material/slide-toggle"; -import { MatFormFieldModule} from "@angular/material/form-field"; -import { MatAutocompleteModule} from "@angular/material/autocomplete"; -import { MatSelectModule} from "@angular/material/select"; -import { MatInputModule} from "@angular/material/input"; - - - +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatAutocompleteModule } from "@angular/material/autocomplete"; +import { MatSelectModule } from "@angular/material/select"; +import { MatInputModule } from "@angular/material/input"; +import { MonitoringSitesGroupsComponent } from "./components/monitoring-sitesgroups/monitoring-sitesgroups.component"; +import { DataTableService } from "./services/data-table.service"; +import { MonitoringPropertiesGComponent } from "./components/monitoring-properties-g/monitoring-properties-g.component"; +import { GeoJSONService } from "./services/geojson.service"; +import { MonitoringSitesComponent } from "./components/monitoring-sites/monitoring-sites.component"; +import { MonitoringMapListComponent } from "./components/monitoring-map-list/monitoring-map-list.component"; +import { MonitoringFormComponentG } from "./components/monitoring-form-g/monitoring-form.component-g"; +import { EditObjectService } from "./services/edit-object.service"; +import { ObjectService } from "./services/object.service"; +import { + SitesGroupService, + SitesService, + ApiGeomService, +} from "./services/api-geom.service"; +import { MonitoringVisitsComponent } from "./components/monitoring-visits/monitoring-visits.component"; +import { MonitoringSitesGroupsCreateComponent } from "./components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component"; // my module routing const routes: Routes = [ @@ -41,9 +55,9 @@ const routes: Routes = [ { path: "", component: ModulesComponent }, /** module */ - { path: 'module/:moduleCode', component: MonitoringObjectComponent }, + { path: "module/:moduleCode", component: MonitoringObjectComponent }, /** create module */ - { path: 'module', component: MonitoringObjectComponent }, + { path: "module", component: MonitoringObjectComponent }, /** object */ { @@ -55,6 +69,27 @@ const routes: Routes = [ path: "create_object/:moduleCode/:objectType", component: MonitoringObjectComponent, }, + { + path: "sites_group", + component: MonitoringMapListComponent, + children: [ + { + path: "", + component: MonitoringSitesGroupsComponent, + }, + { path: "create", component: MonitoringSitesGroupsCreateComponent }, + { + path: ":id", + // Add new component here + children: [ + { + path: "", + component: MonitoringSitesComponent, + }, + ], + }, + ], + }, ]; @NgModule({ @@ -69,6 +104,13 @@ const routes: Routes = [ MonitoringListComponent, MonitoringPropertiesComponent, MonitoringDatatableComponent, + MonitoringMapListComponent, + MonitoringSitesGroupsComponent, + MonitoringSitesComponent, + MonitoringDatatableGComponent, + MonitoringPropertiesGComponent, + MonitoringFormComponentG, + MonitoringSitesGroupsCreateComponent, ], imports: [ GN2CommonModule, @@ -92,6 +134,13 @@ const routes: Routes = [ DataUtilsService, ConfigService, MonitoringObjectService, + DataTableService, + SitesGroupService, + SitesService, + GeoJSONService, + EditObjectService, + ObjectService, + ApiGeomService, ], bootstrap: [ModulesComponent], schemas: [ diff --git a/frontend/app/interfaces/column.ts b/frontend/app/interfaces/column.ts new file mode 100644 index 000000000..82f803037 --- /dev/null +++ b/frontend/app/interfaces/column.ts @@ -0,0 +1,5 @@ +export interface IColumn { + name: string; + prop: string; + description?: string; +} diff --git a/frontend/app/interfaces/form.ts b/frontend/app/interfaces/form.ts new file mode 100644 index 000000000..e57edf803 --- /dev/null +++ b/frontend/app/interfaces/form.ts @@ -0,0 +1,7 @@ +import { JsonData } from "../types/jsondata"; +import { ISite, ISitesGroup } from "./geom"; + +export interface IDataForm extends JsonData, ISitesGroup, ISite { + patch_update:number + } + \ No newline at end of file diff --git a/frontend/app/interfaces/geom.ts b/frontend/app/interfaces/geom.ts new file mode 100644 index 000000000..9d43b5cdf --- /dev/null +++ b/frontend/app/interfaces/geom.ts @@ -0,0 +1,50 @@ +import { GeoJSON } from "geojson"; +import { Observable } from "rxjs"; +import { JsonData } from "../types/jsondata"; +import { Resp } from "../types/response"; +import { IPaginated } from "./page"; + +export interface IGeomObject { + data: JsonData; + geometry: GeoJSON.Geometry; +} + +export interface ISitesGroup extends IGeomObject { + pk: number; + comments?: string; + id_sites_group: number; + nb_sites: number; + nb_visits: number; + sites_group_code: string; + sites_group_description: string; + sites_group_name: string; + uuid_sites_group: string; //FIXME: see if OK +} + +export interface ISite extends IGeomObject { + altitude_max: number; + altitude_min: number; + base_site_code: string; + base_site_description?: string; + base_site_name: string; + first_use_date: string; + id_base_site: number; + id_nomenclature_type_site?: number; + last_visit?: Date; + meta_create_date: Date; + meta_update_date: Date; + nb_visits: number; + uuid_base_site: string; +} + +export interface IGeomService { + get( + limit: number, + page: number, + params: JsonData + ): Observable>; + get_geometries(params: JsonData): Observable; + create(postdata: IGeomObject): Observable; + patch(id: number, updatedData: IGeomObject): Observable; + // delete(obj: IGeomObject) +} diff --git a/frontend/app/interfaces/page.ts b/frontend/app/interfaces/page.ts new file mode 100644 index 000000000..cca644c5f --- /dev/null +++ b/frontend/app/interfaces/page.ts @@ -0,0 +1,17 @@ +export interface IPage { + count: number; + limit: number; + page: number; +} + +export interface IPaginated extends IPage { + items: T[]; +} + +// PageInfo = object given by ngx-datatable +export interface PageInfo { + offset: number; + pageSize: number; + limit: number; + count: number; +} diff --git a/frontend/app/interfaces/response.ts b/frontend/app/interfaces/response.ts new file mode 100644 index 000000000..317f5c94d --- /dev/null +++ b/frontend/app/interfaces/response.ts @@ -0,0 +1,6 @@ +import { JsonData } from "../types/jsondata"; +export interface ResponseUpdated { + message: string; + payload: JsonData; + status_code: number; +} diff --git a/frontend/app/services/api-geom.service.ts b/frontend/app/services/api-geom.service.ts new file mode 100644 index 000000000..296913ebf --- /dev/null +++ b/frontend/app/services/api-geom.service.ts @@ -0,0 +1,121 @@ +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { GeoJSON } from "geojson"; + +import { CacheService } from "./cache.service"; +import { IGeomService, ISitesGroup, ISite } from "../interfaces/geom"; +import { IPaginated } from "../interfaces/page"; +import { JsonData } from "../types/jsondata"; +import { Resp } from "../types/response"; + +export enum endPoints { + sites_groups = "sites_groups", + sites = "sites", +} + +@Injectable() +export class ApiGeomService implements IGeomService { + public objectType: endPoints = endPoints.sites_groups; + + constructor(protected _cacheService: CacheService) { + this.init(); + } + + init() { + this.objectType = endPoints.sites_groups; + } + get( + page: number = 1, + limit: number = 10, + params: JsonData = {} + ): Observable> { + return this._cacheService.request< + Observable> + >("get", this.objectType, { + queryParams: { page, limit, ...params }, + }); + } + + getById(id: number): Observable { + return this._cacheService.request>( + "get", + `${this.objectType}/${id}` + ); + } + + get_geometries(params: JsonData = {}): Observable { + return this._cacheService.request>( + "get", + `${this.objectType}/geometries`, + { + queryParams: { ...params }, + } + ); + } + + patch(id: number, updatedData: ISitesGroup | ISite): Observable { + return this._cacheService.request("patch", `${this.objectType}/${id}`, { + postData: updatedData, + }); + } + + create( postData: ISitesGroup | ISite): Observable { + return this._cacheService.request("post", `${this.objectType}`, { + postData: postData, + }); + } + + delete(id: number): Observable { + return this._cacheService.request("delete", `${this.objectType}/${id}`); + } + +} + +@Injectable() +export class SitesGroupService extends ApiGeomService { + constructor(_cacheService: CacheService) { + super(_cacheService); + } + init(): void { + this.objectType = endPoints.sites_groups; + } + + getSitesChild( + page: number = 1, + limit: number = 10, + params: JsonData = {} + ): Observable> { + return this._cacheService.request>>( + "get", + `sites`, + { + queryParams: { page, limit, ...params }, + } + ); + } + + addObjectType(): string { + return "un nouveau groupe de site"; + } + + editObjectType(): string { + return "le groupe de site"; + } +} + +@Injectable() +export class SitesService extends ApiGeomService { + constructor(_cacheService: CacheService) { + super(_cacheService); + } + init(): void { + this.objectType = endPoints.sites; + } + addObjectType(): string { + return " un nouveau site"; + } + + editObjectType(): string { + return "le site"; + } +} diff --git a/frontend/app/services/cache.service.ts b/frontend/app/services/cache.service.ts index 876ac07f0..9fcbbc0e7 100644 --- a/frontend/app/services/cache.service.ts +++ b/frontend/app/services/cache.service.ts @@ -3,7 +3,7 @@ import { Injectable } from "@angular/core"; import { HttpClient, HttpHeaders } from "@angular/common/http"; import { Observable, of, Subject } from "rxjs"; -import { mergeMap, concatMap } from "rxjs/operators"; +import { mergeMap} from "rxjs/operators"; import { ConfigService } from "./config.service"; @@ -27,14 +27,14 @@ export class CacheService { * @param urlRelative url relative de la route * @param data post data (optionnel) */ - request( + request>( requestType: string, urlRelative: string, { postData = {}, queryParams = {} } = {} - ) { + ): Return { // verification de requestType if (!["get", "post", "patch", "delete"].includes(requestType)) { - return of(null); + throw console.error("Request must be get, post, patch or delete"); } const url_params = Object.keys(queryParams).length @@ -52,7 +52,7 @@ export class CacheService { this._config.backendModuleUrl() + "/" + urlRelative + url_params; // requete - return this._http[requestType](url, postData); + return this._http[requestType](url, postData); } /** Cache diff --git a/frontend/app/services/config.service.ts b/frontend/app/services/config.service.ts index d37243b66..831edeca0 100644 --- a/frontend/app/services/config.service.ts +++ b/frontend/app/services/config.service.ts @@ -129,7 +129,6 @@ export class ConfigService { moduleCode = moduleCode || 'generic'; const configObject = this._config[moduleCode][objectType]; - // gerer quand les paramètres ont un fonction comme valeur for (const typeSchema of ['generic', 'specific']) { diff --git a/frontend/app/services/data-table.service.ts b/frontend/app/services/data-table.service.ts new file mode 100644 index 000000000..8a76099ee --- /dev/null +++ b/frontend/app/services/data-table.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from "@angular/core"; +import { IColumn } from "../interfaces/column"; +import { BehaviorSubject} from "rxjs"; +interface ItemObjectTable { + id: number | null; + selected: boolean; + visible: boolean; + current: boolean; +} + +type ItemsObjectTable = { [key: string]: ItemObjectTable }; + +@Injectable() +export class DataTableService { + obj: ItemsObjectTable; + objectsStatus: ItemsObjectTable; + rowStatus: ItemObjectTable; + idObj: number; + + + // IF prefered observable compare to ngOnChanges uncomment this: + // dataCol:IColumn[] =[{prop:"",name:"",description:""}] + // private dataCols = new BehaviorSubject(this.dataCol); + // currentCols = this.dataCols.asObservable(); + + + constructor() {} + + // IF prefered observable compare to ngOnChanges uncomment this: + // changeColsTable(newCols:IColumn[],newRows){ + // const arr = Object.keys(newCols); + // const allColumn: IColumn[] = arr + // .filter((item) => Object.keys(newRows).includes(item)) + // .map((elm) => ({ + // name: newCols[elm], + // prop: elm, + // description: elm, + // })); + // this.dataCols.next(allColumn) + // } + + colsTable(colName:IColumn[], dataTable): IColumn[] { + const arr = Object.keys(colName); + const allColumn: IColumn[] = arr + .filter((item) => Object.keys(dataTable).includes(item)) + .map((elm) => ({ + name: colName[elm], + prop: elm, + description: elm, + })); + return allColumn; + } + + initObjectsStatus(obj, key) { + const objectsStatus = {}; + // for (const childrenType of Object.keys(this.obj.children)) { + objectsStatus[key] = obj.map((groupSite) => { + return { + id: groupSite.id_sites_group, + selected: false, + visible: true, + current: false, + }; + }); + // } + + // init site status + if (this.idObj) { + objectsStatus[key] = []; + obj.features.forEach((f) => { + // determination du site courrant + let cur = false; + if (f.properties.id_sites_group == this.idObj) { + cur = true; + } + + objectsStatus[key].push({ + id: f.properties.id_sites_group, + selected: false, + visible: true, + current: cur, + }); + }); + } + + this.objectsStatus = objectsStatus; + this.rowStatus = this.objectsStatus[key]; + return [this.objectsStatus, this.rowStatus]; + } +} diff --git a/frontend/app/services/edit-object.service.ts b/frontend/app/services/edit-object.service.ts new file mode 100644 index 000000000..fc106678c --- /dev/null +++ b/frontend/app/services/edit-object.service.ts @@ -0,0 +1,58 @@ +import { Injectable } from "@angular/core"; +import { BehaviorSubject, Observable, of, forkJoin } from "rxjs"; +import { concatMap } from "rxjs/operators"; + +import { JsonData } from "../types/jsondata"; + +import { Utils } from "../utils/utils"; +import { MonitoringObjectService } from "./monitoring-object.service"; + +@Injectable() +export class EditObjectService { + data: JsonData = {}; + private dataSub = new BehaviorSubject(this.data); + currentData = this.dataSub.asObservable(); + properties: JsonData; + moduleCode:string; + objecType:string; + + constructor( + private _objService:MonitoringObjectService + ) {} + + changeDataSub(newDat: JsonData) { + this.properties = newDat; + newDat.moduleCode = "generic"; + newDat.objectType = "sites_group"; + this.moduleCode= "generic"; + this.objecType= "sites_group" + this.dataSub.next(newDat) + + } + + + + formValues(obj): Observable { + const properties = Utils.copy(this.properties); + const observables = {}; + const schema = obj[this.moduleCode]; + for (const attribut_name of Object.keys(schema)) { + const elem = schema[attribut_name]; + if (!elem.type_widget) { + continue; + } + observables[attribut_name] = this._objService.toForm(elem, properties[attribut_name]); + } + + return forkJoin(observables).pipe( + concatMap((formValues_in) => { + const formValues = Utils.copy(formValues_in); + // geometry + // if (this.config["geometry_type"]) { + // formValues["geometry"] = this.geometry; // copy??? + // } + return of(formValues); + }) + ); + } +} diff --git a/frontend/app/services/geojson.service.ts b/frontend/app/services/geojson.service.ts new file mode 100644 index 000000000..22a26e980 --- /dev/null +++ b/frontend/app/services/geojson.service.ts @@ -0,0 +1,112 @@ +import { Injectable } from "@angular/core"; +import * as L from "leaflet"; +import { Observable } from "rxjs"; +import { map } from "rxjs/operators"; +import { GeoJSON } from "geojson"; +import { MapService } from "@geonature_common/map/map.service"; + +import { SitesService,SitesGroupService } from "./api-geom.service"; + + +// This service will be used for sites and sites groups + +const siteGroupStyle = { + fillColor: "#800080", + fillOpacity: 0.5, + color: "#800080", + opacity: 0.8, + weight: 2, + fill: true, +}; + +@Injectable() +export class GeoJSONService { + geojsonSitesGroups: GeoJSON.FeatureCollection; + geojsonSites: GeoJSON.FeatureCollection; + sitesGroupFeatureGroup: L.FeatureGroup; + sitesFeatureGroup: L.FeatureGroup; + + constructor( + private _sites_group_service: SitesGroupService, + private _sites_service: SitesService, + private _mapService: MapService + ) {} + + getSitesGroupsGeometries(onEachFeature: Function, params = {}) { + this._sites_group_service + .get_geometries(params) + .subscribe((data: GeoJSON.FeatureCollection) => { + this.geojsonSitesGroups = data; + this.sitesGroupFeatureGroup = this.setMapData( + data, + onEachFeature, + siteGroupStyle + ); + }); + } + + getSitesGroupsChildGeometries(onEachFeature: Function, params = {}) { + this._sites_service + .get_geometries(params) + .subscribe((data: GeoJSON.FeatureCollection) => { + //this.removeFeatureGroup(this.sitesFeatureGroup); + this.sitesFeatureGroup = this.setMapData(data, onEachFeature); + }); + } + + setMapData( + geojson: GeoJSON.Geometry | GeoJSON.FeatureCollection, + onEachFeature: Function, + style? + ) { + const map = this._mapService.getMap(); + const layer: L.Layer = this._mapService.createGeojson( + geojson, + false, + onEachFeature, + style + ); + const featureGroup = new L.FeatureGroup(); + this._mapService.map.addLayer(featureGroup); + featureGroup.addLayer(layer); + map.fitBounds(featureGroup.getBounds()); + return featureGroup; + } + + removeFeatureGroup(feature: L.FeatureGroup) { + if (feature) { + this._mapService.map.removeLayer(feature); + } + } + + onEachFeature() {} + + filterSitesGroups(siteGroupId: number) { + if (this.geojsonSitesGroups !== undefined) { + const features = this.geojsonSitesGroups.features.filter( + (feature) => feature.properties.id_sites_group == siteGroupId + ); + this.geojsonSitesGroups.features = features; + this.removeFeatureGroup(this.sitesGroupFeatureGroup); + this.setMapData( + this.geojsonSitesGroups, + this.onEachFeature, + siteGroupStyle + ); + } + } + + selectSitesGroupLayer(id: number) { + this.sitesGroupFeatureGroup.eachLayer((layer) => { + if (layer instanceof L.GeoJSON) { + layer.eachLayer((sublayer: L.GeoJSON) => { + const feature = sublayer.feature as GeoJSON.Feature; + if (feature.properties["id_sites_group"] == id) { + sublayer.openPopup(); + return; + } + }); + } + }); + } +} diff --git a/frontend/app/services/object.service.ts b/frontend/app/services/object.service.ts new file mode 100644 index 000000000..c570f506c --- /dev/null +++ b/frontend/app/services/object.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +@Injectable() +export class ObjectService { + objectType: string = ""; + private dataObjType = new BehaviorSubject(this.objectType); + currentObjectType = this.dataObjType.asObservable(); + + objectTypeParent: string = ""; + private dataObjTypeParent = new BehaviorSubject(this.objectTypeParent); + currentObjectTypeParent = this.dataObjTypeParent.asObservable(); + + constructor() {} + + changeObjectType(newObjType: string) { + this.dataObjType.next(newObjType); + } + + changeObjectTypeParent(newObjType: string) { + this.dataObjTypeParent.next(newObjType); + } +} diff --git a/frontend/app/types/jsondata.ts b/frontend/app/types/jsondata.ts new file mode 100644 index 000000000..fa13d6798 --- /dev/null +++ b/frontend/app/types/jsondata.ts @@ -0,0 +1 @@ +export type JsonData = { [key: string]: any }; diff --git a/frontend/app/types/response.ts b/frontend/app/types/response.ts new file mode 100644 index 000000000..dd26b3360 --- /dev/null +++ b/frontend/app/types/response.ts @@ -0,0 +1,4 @@ +import { ISite, ISitesGroup } from "../interfaces/geom"; +import { ResponseUpdated } from "../interfaces/response"; + +export type Resp = ResponseUpdated | ISite | ISitesGroup;