From 7b9bd543c4b896376eaeb01f645fb3ea2d5891eb Mon Sep 17 00:00:00 2001 From: Maxime Vergez <85738261+mvergez@users.noreply.github.com> Date: Tue, 18 Apr 2023 15:44:53 +0200 Subject: [PATCH] Refactor to prepare for visits (#44) * Fix/db migrations (#31) * feat(db): upgrade down_revision following rebase Since rebase with develop: changed the down_revision number * fix(db): fix bind params enabling downgrade Beforehand the downgrade was not possible... * refactor(db): removed cor_site_type_category * refactor(db): changed category into type in cor * refactor(db): create cor_type_site * fix(db): renamed column * refactor(api): update models to fit migrations * fix(db):change bib_categorie_site to bib_type_site Adding : cor_site_module cor_site_type revision alembic to create function and trigger in order to add bib_type_site but only with nomenclature 'TYPE_SITE' upgrade and downgrade works [Refs ticket]: #3 Reviewed-by: andriac * fix(api): updated models from migrations * fix(api): wip: fix admin following migrations * fix(api): update routes and tests To match migration changes * feat: flask admin bib_type_site Change bib_categories to bib_type_site into flask admin Adding filtering in list label_fr of type_site to secure the unique constraint Reviewed-by: andriac [Refs ticket]: #3 * fix(api): updated schema to match models * fix(api): module edition * style(api): uniformize type_site * style(api): change relationship name for type_site * feat(api): validator admin * fix(api): make unique BibTypeSite in admin * test(api): fix test when existing nomenclatures In database Co-authored-by: Andria Capai * refactor: object.service with observable obj Change the objectType string to objectType obj in order to subscribe to multilple properties link to this object Reviewed-by: andriacap [Refs_ticket]: #40 * feat: adapt service to get config json Get fieldsName and fieldsLabel for display properties (properties component) Add logic to setItem in LocalStorage to keepLast Value of observable on reload page TODO: - [ ] see if Localstorage is really necessary with the configService used inside api-geom.service ) - [ ] adapt monitoring-form-g.component with the new service Reviewed-by: andriacap [Refs ticket]: #4 * refactor(front): svc: better use of types * feat(front): add Visits component and service * feat(back): add get site by id route & test * feat(api): add visits routes and schema * fix(api): join modules to have the modulecode For the frontend to be able to redirect to the correct route * feat(api): add sites/id/module route To retrieve all the modules compatibles with this site This lead to add a relationship. Set to noload so that it is not loaded by other not "raiseloaded" queries * test(api): add test to test the /sites/id/module Route. Also changed some fixture to be able to write these tests * fix(front): remove double def of IGeomService * refactor(front): remove Resp interface Since it is not usefull anymore * fix(api): exclude sites relationship * fix(config): change KeyValue Since id changed * feat(front): make datatable accepts other add btn To be able to customize the "add object" button * feat(front): add btn select protocole for visit Add new component: select-btn Add call to route to get modules from a base site id Call the new component in the visit component Add interfaces Add type in config service * feat(front): parameter for label and placeholder For the btn component. Style menu and form-field to make them larger * refactor(front): api service with generic types * fix(front): div removed following rebase * refactor(front): add service as input for formComp So that formComp is more type generic * fix(config): remove "s" from sites_group * refactor(front): put initConfig in ApiService * fix(front): remove "s" from visit * fix(front): put back condition on css class To make ng-content conditionnal * fix: following rebase Fix imports, double declarations... * fix(api): redirect to url * refactor(front): rename select-btn to option-list * fix: remove contrib --------- Co-authored-by: Andria Capai --- .../config/generic/module.json | 2 +- .../gn_module_monitoring/monitoring/admin.py | 1 + .../gn_module_monitoring/monitoring/models.py | 7 +- .../monitoring/schemas.py | 2 +- backend/gn_module_monitoring/routes/site.py | 19 +- backend/gn_module_monitoring/routes/visit.py | 2 +- .../tests/fixtures/module.py | 16 + .../tests/fixtures/site.py | 16 + .../tests/fixtures/visit.py | 1 - .../tests/test_routes/test_site.py | 45 +++ .../monitoring-datatable-g.component.css | 6 + .../monitoring-datatable-g.component.html | 17 +- .../monitoring-form.component-g.ts | 12 +- .../monitoring-properties-g.component.ts | 2 +- .../monitoring-sites-create.component.html | 1 + .../monitoring-sites-create.component.ts | 2 +- .../monitoring-sites.component.html | 1 + .../monitoring-sites.component.ts | 2 +- ...nitoring-sitesgroups-create.component.html | 2 +- ...monitoring-sitesgroups-create.component.ts | 3 +- .../monitoring-visits.component.html | 6 +- .../monitoring-visits.component.ts | 27 +- .../option-list-btn.component.css | 11 + .../option-list-btn.component.html | 22 ++ .../option-list-btn.component.ts | 59 ++++ frontend/app/constants/api.ts | 1 + frontend/app/gnModule.module.ts | 4 +- frontend/app/interfaces/geom.ts | 16 +- frontend/app/interfaces/module.ts | 9 + frontend/app/interfaces/objObs.ts | 8 +- frontend/app/interfaces/object.ts | 19 +- frontend/app/interfaces/response.ts | 6 - frontend/app/interfaces/visit.ts | 4 +- frontend/app/services/api-geom.service.ts | 275 ++++++------------ frontend/app/services/config.service.ts | 2 +- frontend/app/types/response.ts | 4 - 36 files changed, 380 insertions(+), 252 deletions(-) create mode 100644 frontend/app/components/option-list-btn/option-list-btn.component.css create mode 100644 frontend/app/components/option-list-btn/option-list-btn.component.html create mode 100644 frontend/app/components/option-list-btn/option-list-btn.component.ts create mode 100644 frontend/app/constants/api.ts create mode 100644 frontend/app/interfaces/module.ts delete mode 100644 frontend/app/interfaces/response.ts delete mode 100644 frontend/app/types/response.ts diff --git a/backend/gn_module_monitoring/config/generic/module.json b/backend/gn_module_monitoring/config/generic/module.json index ef3c5ddae..5aac3e66c 100644 --- a/backend/gn_module_monitoring/config/generic/module.json +++ b/backend/gn_module_monitoring/config/generic/module.json @@ -126,7 +126,7 @@ "type_widget": "datalist", "attribut_label": "Types de sites", "type_util": "types_site", - "keyValue": "id_nomenclature", + "keyValue": "id_nomenclature_type_site", "keyLabel": "label", "multiple": true, "api" : "__MONITORINGS_PATH/sites/types", diff --git a/backend/gn_module_monitoring/monitoring/admin.py b/backend/gn_module_monitoring/monitoring/admin.py index 5e77d0a0a..aec375e21 100644 --- a/backend/gn_module_monitoring/monitoring/admin.py +++ b/backend/gn_module_monitoring/monitoring/admin.py @@ -73,3 +73,4 @@ def list_label_nomenclature_formatter(view, _context, model, _name): column_list = ("nomenclature", "config") column_formatters = dict(nomenclature=list_label_nomenclature_formatter) + form_excluded_columns = "sites" diff --git a/backend/gn_module_monitoring/monitoring/models.py b/backend/gn_module_monitoring/monitoring/models.py index a4f3493c1..0c6e2cc4c 100644 --- a/backend/gn_module_monitoring/monitoring/models.py +++ b/backend/gn_module_monitoring/monitoring/models.py @@ -104,6 +104,12 @@ class BibTypeSite(DB.Model, GenericModel): uselist=False, backref=DB.backref('bib_type_site', uselist=False) ) + + sites = DB.relationship( + "TMonitoringSites", + secondary=cor_type_site, + lazy="noload" + ) @serializable class TMonitoringObservationDetails(DB.Model): @@ -209,7 +215,6 @@ class TMonitoringVisits(TBaseVisits, GenericModel): ) ) - module = DB.relationship( TModules, lazy="select", diff --git a/backend/gn_module_monitoring/monitoring/schemas.py b/backend/gn_module_monitoring/monitoring/schemas.py index 7dde989c3..0eed83424 100644 --- a/backend/gn_module_monitoring/monitoring/schemas.py +++ b/backend/gn_module_monitoring/monitoring/schemas.py @@ -9,7 +9,7 @@ BibTypeSite, TMonitoringSites, TMonitoringSitesGroups, - TMonitoringVisits + TMonitoringVisits, ) diff --git a/backend/gn_module_monitoring/routes/site.py b/backend/gn_module_monitoring/routes/site.py index 0e2444b37..77f123146 100644 --- a/backend/gn_module_monitoring/routes/site.py +++ b/backend/gn_module_monitoring/routes/site.py @@ -1,10 +1,13 @@ from flask import request from flask.json import jsonify +from sqlalchemy.orm import Load, joinedload from werkzeug.datastructures import MultiDict +from geonature.core.gn_commons.schemas import ModuleSchema + from gn_module_monitoring.blueprint import blueprint from gn_module_monitoring.config.repositories import get_config -from gn_module_monitoring.monitoring.models import BibTypeSite, TMonitoringSites, TNomenclatures +from gn_module_monitoring.monitoring.models import BibTypeSite, TMonitoringSites, TNomenclatures, TMonitoringModules from gn_module_monitoring.monitoring.schemas import BibTypeSiteSchema, MonitoringSitesSchema from gn_module_monitoring.routes.sites_groups import create_or_update_object_api from gn_module_monitoring.utils.routes import ( @@ -111,6 +114,20 @@ def get_all_site_geometries(): return jsonify(result) +@blueprint.route("/sites//modules", methods=["GET"]) +def get_module_by_id_base_site(id_base_site: int): + query = TMonitoringModules.query.options( + Load(TMonitoringModules).raiseload("*"), + joinedload(TMonitoringModules.types_site).options(joinedload(BibTypeSite.sites)), + ).filter(TMonitoringModules.types_site.any(BibTypeSite.sites.any(id_base_site=id_base_site))) + + result = query.all() + schema = ModuleSchema() + # TODO: Is it usefull to put a limit here? Will there be more than 200 modules? + # If limit here, implement paginated/infinite scroll on frontend side + return [schema.dump(res) for res in result] + + @blueprint.route("/sites/module/", methods=["GET"]) def get_module_sites(module_code: str): # TODO: load with site_categories.json API diff --git a/backend/gn_module_monitoring/routes/visit.py b/backend/gn_module_monitoring/routes/visit.py index 4303ba51e..c8c2e9c8c 100644 --- a/backend/gn_module_monitoring/routes/visit.py +++ b/backend/gn_module_monitoring/routes/visit.py @@ -24,7 +24,7 @@ def get_visits(): params=params, default_sort="id_base_visit", default_direction="desc" ) query = TMonitoringVisits.query.options(joinedload(TMonitoringVisits.module)) - query = filter_params(query=TMonitoringVisits.query, params=params) + query = filter_params(query=query, params=params) query = sort(query=query, sort=sort_label, sort_dir=sort_dir) return paginate( diff --git a/backend/gn_module_monitoring/tests/fixtures/module.py b/backend/gn_module_monitoring/tests/fixtures/module.py index 29f678f25..3a925cbcc 100644 --- a/backend/gn_module_monitoring/tests/fixtures/module.py +++ b/backend/gn_module_monitoring/tests/fixtures/module.py @@ -21,3 +21,19 @@ def monitoring_module(types_site): db.session.add(t_monitoring_module) return t_monitoring_module + + +@pytest.fixture +def monitoring_module_wo_types_site(): + t_monitoring_module = TMonitoringModules( + module_code=uuid4(), + module_label="NoType", + active_frontend=True, + active_backend=False, + module_path="NoType", + ) + + with db.session.begin_nested(): + db.session.add(t_monitoring_module) + + return t_monitoring_module diff --git a/backend/gn_module_monitoring/tests/fixtures/site.py b/backend/gn_module_monitoring/tests/fixtures/site.py index 4125b3e16..4e8f289cf 100644 --- a/backend/gn_module_monitoring/tests/fixtures/site.py +++ b/backend/gn_module_monitoring/tests/fixtures/site.py @@ -23,6 +23,22 @@ def sites(users, types_site, site_group_with_sites): types_site=[types_site[key]], id_sites_group=site_group_with_sites.id_sites_group, ) + + # Add a special site that has no type + sites["no-type"] = TMonitoringSites( + id_inventor=user.id_role, + id_digitiser=user.id_role, + base_site_name=f"no-type", + base_site_description=f"Description-no-type", + base_site_code=f"Code-no-type", + geom=geom_4326, + # Random id_nomenclature_type_site + # FIXME: when id_nomenclature_type_site disapears => remove this line + id_nomenclature_type_site=list(types_site.values())[0].id_nomenclature_type_site, + types_site=[], + id_sites_group=site_group_with_sites.id_sites_group, + ) + with db.session.begin_nested(): db.session.add_all(sites.values()) return sites diff --git a/backend/gn_module_monitoring/tests/fixtures/visit.py b/backend/gn_module_monitoring/tests/fixtures/visit.py index cd7dc197d..5404952b7 100644 --- a/backend/gn_module_monitoring/tests/fixtures/visit.py +++ b/backend/gn_module_monitoring/tests/fixtures/visit.py @@ -1,7 +1,6 @@ import datetime import pytest -from geonature.tests.fixtures import datasets from geonature.utils.env import db from gn_module_monitoring.monitoring.models import TMonitoringVisits diff --git a/backend/gn_module_monitoring/tests/test_routes/test_site.py b/backend/gn_module_monitoring/tests/test_routes/test_site.py index bb00e5006..77612d0a6 100644 --- a/backend/gn_module_monitoring/tests/test_routes/test_site.py +++ b/backend/gn_module_monitoring/tests/test_routes/test_site.py @@ -57,6 +57,14 @@ def test_get_sites_id_base_site(self, sites): assert len(r.json["items"]) == 1 assert r.json["items"][0]["id_base_site"] == id_base_site + def test_get_sites_by_id(self, sites): + site = list(sites.values())[0] + id_base_site = site.id_base_site + + r = self.client.get(url_for("monitorings.get_site_by_id", id_base_site=id_base_site)) + + assert r.json["id_base_site"] == id_base_site + def test_get_all_site_geometries(self, sites): r = self.client.get(url_for("monitorings.get_all_site_geometries")) @@ -85,6 +93,43 @@ def test_get_all_site_geometries_filter_site_group(self, sites, site_group_witho features = json_resp.get("features") assert features is None + def test_get_module_by_id_base_site(self, sites, monitoring_module): + site = list(sites.values())[0] + id_base_site = site.id_base_site + + r = self.client.get( + url_for("monitorings.get_module_by_id_base_site", id_base_site=id_base_site) + ) + + expected_modules = {monitoring_module.id_module} + current_modules = {module["id_module"] for module in r.json} + assert expected_modules.issubset(current_modules) + + def test_get_module_by_id_base_site_no_type_module( + self, sites, monitoring_module_wo_types_site + ): + site = list(sites.values())[0] + id_base_site = site.id_base_site + + r = self.client.get( + url_for("monitorings.get_module_by_id_base_site", id_base_site=id_base_site) + ) + + expected_absent_modules = {monitoring_module_wo_types_site.id_module} + current_modules = {module["id_module"] for module in r.json} + assert expected_absent_modules.isdisjoint(current_modules) + + def test_get_module_by_id_base_site_no_type_site(self, sites, monitoring_module): + id_base_site = sites["no-type"].id_base_site + + r = self.client.get( + url_for("monitorings.get_module_by_id_base_site", id_base_site=id_base_site) + ) + + expected_modules = {monitoring_module.id_module} + current_modules = {module["id_module"] for module in r.json} + assert expected_modules.isdisjoint(current_modules) + def test_get_module_sites(self): module_code = "TEST" r = self.client.get(url_for("monitorings.get_module_sites", module_code=module_code)) 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 index 11176aee6..fa047afa2 100644 --- a/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.css +++ b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.css @@ -45,3 +45,9 @@ .custom-dt { box-shadow: none !important; } + +/* Work around to make ng-content conditional +(not supported, maybe in Angular16) */ +.wrapper-button:not(:empty) + .default-button { + display: none; +} \ No newline at end of file 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 index 095b562a1..0a65158a5 100644 --- a/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.html +++ b/frontend/app/components/monitoring-datatable-g/monitoring-datatable-g.component.html @@ -7,7 +7,7 @@ (keyup)="updateFilter($event)" /> --> -
+
- - - +
+ +
+
+ + + +
(); @Input() sites: {}; + @Input() apiService: ApiGeomService; dataForm: IDataForm; searchSite = ''; @@ -58,7 +59,6 @@ export class MonitoringFormComponentG implements OnInit { private _commonService: CommonService, private _dynformService: DynamicFormService, private _formService: FormService, - private _apiGeomService: ApiGeomService, private _router: Router, ) {} @@ -69,7 +69,7 @@ export class MonitoringFormComponentG implements OnInit { tap((data) => { this.obj = data; this.obj.bIsInitialized = true; - this._apiGeomService.init(this.obj.endPoint, this.obj.objSelected); + this.apiService.init(this.obj.endPoint, this.obj.objSelected); }), mergeMap((data: any) => this._configService.init(data.moduleCode)) ) @@ -272,7 +272,7 @@ export class MonitoringFormComponentG implements OnInit { const urlPathDetail = [this.obj.urlRelative].concat(urlSegment).join('/'); this.objChanged.emit(this.obj); this.bEditChange.emit(false); - this._router.navigateByUrl(urlPathDetail); + this.obj.urlRelative ? this._router.navigateByUrl(urlPathDetail): null; } /** @@ -296,8 +296,8 @@ export class MonitoringFormComponentG implements OnInit { const objToUpdateOrCreate = this._formService.postData(sendValue, this.obj); console.log(objToUpdateOrCreate); const action = this.obj.id - ? this._apiGeomService.patch(this.obj.id, objToUpdateOrCreate) - : this._apiGeomService.create(objToUpdateOrCreate); + ? this.apiService.patch(this.obj.id, objToUpdateOrCreate) + : this.apiService.create(objToUpdateOrCreate); const actionLabel = this.obj.id ? 'Modification' : 'Création'; action.subscribe((objData) => { this._commonService.regularToaster('success', this.msgToaster(actionLabel)); @@ -344,7 +344,7 @@ export class MonitoringFormComponentG implements OnInit { 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.apiService.delete(this.obj.id).subscribe((del) => { this.bDeleteSpinner = this.bDeleteModal = false; this.objChanged.emit(this.obj); setTimeout(() => { 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 index 3c411bf06..ec93302e3 100644 --- a/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.ts +++ b/frontend/app/components/monitoring-properties-g/monitoring-properties-g.component.ts @@ -24,7 +24,7 @@ export class MonitoringPropertiesGComponent implements OnInit { color: string = 'white'; dataDetails: ISitesGroup; fields: JsonData; - fieldDefinitions: JsonData; + fieldDefinitions: JsonData = {}; fieldsNames: string[]; endPoint: string; datasetForm = new FormControl(); diff --git a/frontend/app/components/monitoring-sites-create/monitoring-sites-create.component.html b/frontend/app/components/monitoring-sites-create/monitoring-sites-create.component.html index cc95c530c..cd9a5bcd3 100644 --- a/frontend/app/components/monitoring-sites-create/monitoring-sites-create.component.html +++ b/frontend/app/components/monitoring-sites-create/monitoring-sites-create.component.html @@ -7,6 +7,7 @@ >
diff --git a/frontend/app/components/monitoring-sites-create/monitoring-sites-create.component.ts b/frontend/app/components/monitoring-sites-create/monitoring-sites-create.component.ts index 33741d789..ea83a7eb4 100644 --- a/frontend/app/components/monitoring-sites-create/monitoring-sites-create.component.ts +++ b/frontend/app/components/monitoring-sites-create/monitoring-sites-create.component.ts @@ -34,7 +34,7 @@ export class MonitoringSitesCreateComponent implements OnInit { constructor( private _formService: FormService, private _formBuilder: FormBuilder, - private siteService: SitesService, + public siteService: SitesService, private route: ActivatedRoute, private _objService: ObjectService ) {} diff --git a/frontend/app/components/monitoring-sites/monitoring-sites.component.html b/frontend/app/components/monitoring-sites/monitoring-sites.component.html index 7c7b3aa9f..943d69b13 100644 --- a/frontend/app/components/monitoring-sites/monitoring-sites.component.html +++ b/frontend/app/components/monitoring-sites/monitoring-sites.component.html @@ -6,6 +6,7 @@ >
- +
\ 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 index 3e6b774cc..7ff9a6791 100644 --- a/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.ts +++ b/frontend/app/components/monitoring-sitesgroups-create/monitoring-sitesgroups-create.component.ts @@ -5,6 +5,7 @@ import { ActivatedRoute } from '@angular/router'; import { endPoints } from '../../enum/endpoints'; import { ISitesGroup } from '../../interfaces/geom'; import { FormService } from '../../services/form.service'; +import { SitesGroupService } from '../../services/api-geom.service'; @Component({ selector: 'monitoring-sitesgroups-create', @@ -18,7 +19,7 @@ export class MonitoringSitesGroupsCreateComponent implements OnInit { constructor( private _formService: FormService, private _formBuilder: FormBuilder, - private route: ActivatedRoute + public sitesGroupService: SitesGroupService ) {} ngOnInit() { diff --git a/frontend/app/components/monitoring-visits/monitoring-visits.component.html b/frontend/app/components/monitoring-visits/monitoring-visits.component.html index dc350d016..a451e2484 100644 --- a/frontend/app/components/monitoring-visits/monitoring-visits.component.html +++ b/frontend/app/components/monitoring-visits/monitoring-visits.component.html @@ -3,6 +3,8 @@ + (onFilter)="setFilter($event)" (onSetPage)="setPage($event)" [obj]="visits" (onDetailsRow)="seeDetails($event)"> + +
\ No newline at end of file diff --git a/frontend/app/components/monitoring-visits/monitoring-visits.component.ts b/frontend/app/components/monitoring-visits/monitoring-visits.component.ts index 1f4f2f27b..b9a98545e 100644 --- a/frontend/app/components/monitoring-visits/monitoring-visits.component.ts +++ b/frontend/app/components/monitoring-visits/monitoring-visits.component.ts @@ -12,6 +12,9 @@ import { SitesService, VisitsService } from '../../services/api-geom.service'; import { GeoJSONService } from '../../services/geojson.service'; import { ObjectService } from '../../services/object.service'; import { JsonData } from '../../types/jsondata'; +import { SelectObject } from '../../interfaces/object'; +import { Module } from '../../interfaces/module'; +import { ConfigService } from '../../services/config.service'; @Component({ selector: 'monitoring-visits', @@ -28,6 +31,7 @@ export class MonitoringVisitsComponent extends MonitoringGeomComponent implement objForm: FormGroup; @Input() colsname; objParent: any; + modules: SelectObject[]; constructor( private _sites_service: SitesService, @@ -36,7 +40,8 @@ export class MonitoringVisitsComponent extends MonitoringGeomComponent implement public geojsonService: GeoJSONService, private router: Router, private _Activatedroute: ActivatedRoute, - private _formBuilder: FormBuilder + private _formBuilder: FormBuilder, + private _configService: ConfigService ) { super(); this.getAllItemsCallback = this.getVisits; @@ -89,4 +94,24 @@ export class MonitoringVisitsComponent extends MonitoringGeomComponent implement `monitorings/object/${$event.module.module_code}/visit/${$event.id_base_visit}`, ]); } + + getModules() { + this._sites_service.getSiteModules(this.site.id_base_site).subscribe( + (data: Module[]) => + (this.modules = data.map((item) => { + return { id: item.module_code, label: item.module_label }; + })) + ); + } + + addNewVisit($event: SelectObject) { + const moduleCode = $event.id; + //create_object/cheveches_sites_group/visit?id_base_site=47 + this._configService.init(moduleCode).subscribe(() => { + this.router.navigate([ + `monitorings/create_object/${moduleCode}/visit`, + { queryParams: { id_base_site: this.site.id_base_site } }, + ]); + }); + } } diff --git a/frontend/app/components/option-list-btn/option-list-btn.component.css b/frontend/app/components/option-list-btn/option-list-btn.component.css new file mode 100644 index 000000000..af550605f --- /dev/null +++ b/frontend/app/components/option-list-btn/option-list-btn.component.css @@ -0,0 +1,11 @@ +.dropdown { + padding: 5px; +} + +::ng-deep.mat-menu-panel.btn-menu { + width: 400px; +} + +::ng-deep.mat-form-field.btn-menu { + display: block; +} diff --git a/frontend/app/components/option-list-btn/option-list-btn.component.html b/frontend/app/components/option-list-btn/option-list-btn.component.html new file mode 100644 index 000000000..a82dd2548 --- /dev/null +++ b/frontend/app/components/option-list-btn/option-list-btn.component.html @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/frontend/app/components/option-list-btn/option-list-btn.component.ts b/frontend/app/components/option-list-btn/option-list-btn.component.ts new file mode 100644 index 000000000..5abee42ae --- /dev/null +++ b/frontend/app/components/option-list-btn/option-list-btn.component.ts @@ -0,0 +1,59 @@ +import { + Component, + ViewChild, + Input, + Output, + EventEmitter, +} from "@angular/core"; +import { FormControl } from "@angular/forms"; +import { MatMenuTrigger } from "@angular/material/menu"; +import { SelectObject } from "../../interfaces/object"; + +@Component({ + selector: "option-list-btn", + templateUrl: "./option-list-btn.component.html", + styleUrls: ["./option-list-btn.component.css"], +}) +export class OptionListButtonComponent { + @ViewChild(MatMenuTrigger) ddTrigger: MatMenuTrigger; + + form = new FormControl(); + private _optionList: SelectObject[]; + @Input() set optionList(value: SelectObject[]) { + this._optionList = value; + } + + get optionList(): SelectObject[] { + // other logic + return this._optionList; + } + @Input() label: string = ""; + @Input() placeholder: string = ""; + @Output() onSaved = new EventEmitter(); + @Output() onDeployed = new EventEmitter(); + + constructor() {} + + cancelClick(ev: MouseEvent) { + ev.stopPropagation(); + } + + onCancel() { + this.ddTrigger.closeMenu(); + } + + onSave() { + this.ddTrigger.closeMenu(); + this.onSaved.emit(this.form.value); + } + + onDeploy() { + this.onDeployed.emit(); + } + + displayFn(value: SelectObject) { + if (value) { + return value.label; + } + } +} diff --git a/frontend/app/constants/api.ts b/frontend/app/constants/api.ts new file mode 100644 index 000000000..4a3dc15e2 --- /dev/null +++ b/frontend/app/constants/api.ts @@ -0,0 +1 @@ +export const LIMIT = 10; diff --git a/frontend/app/gnModule.module.ts b/frontend/app/gnModule.module.ts index a3da05a3e..6ef0e9801 100644 --- a/frontend/app/gnModule.module.ts +++ b/frontend/app/gnModule.module.ts @@ -54,6 +54,7 @@ import { MonitoringSitesCreateComponent } from "./components/monitoring-sites-cr import { BtnSelectComponent } from "./components/btn-select/btn-select.component"; import { MonitoringSitesEditComponent } from "./components/monitoring-sites-edit/monitoring-sites-edit.component"; import { MonitoringVisitsComponent } from "./components/monitoring-visits/monitoring-visits.component"; +import { OptionListButtonComponent } from "./components/option-list-btn/option-list-btn.component"; // my module routing const routes: Routes = [ @@ -134,7 +135,8 @@ const routes: Routes = [ MonitoringSitesCreateComponent, MonitoringSitesEditComponent, BtnSelectComponent, - MonitoringVisitsComponent + MonitoringVisitsComponent, + OptionListButtonComponent, ], imports: [ GN2CommonModule, diff --git a/frontend/app/interfaces/geom.ts b/frontend/app/interfaces/geom.ts index eea5242ce..de69a3b40 100644 --- a/frontend/app/interfaces/geom.ts +++ b/frontend/app/interfaces/geom.ts @@ -1,11 +1,9 @@ import { GeoJSON } from 'geojson'; import { Observable } from 'rxjs'; import { JsonData } from '../types/jsondata'; -import { Resp } from '../types/response'; -import { IPaginated } from './page'; +import { IObject, IService } from './object'; -export interface IGeomObject { - data: JsonData; +export interface IGeomObject extends IObject { geometry: GeoJSON.Geometry; } @@ -38,16 +36,8 @@ export interface ISite extends IGeomObject { uuid_base_site: string; } -interface IGeomObjectProperties { - properties: IGeomObject; -} - -export interface IGeomService { - get(limit: number, page: number, params: JsonData): Observable>; +export interface IGeomService extends IService { get_geometries(params: JsonData): Observable; - create(postdata: IGeomObjectProperties): Observable; - patch(id: number, updatedData: IGeomObjectProperties): Observable; - delete(id: number); } export interface ISiteType { diff --git a/frontend/app/interfaces/module.ts b/frontend/app/interfaces/module.ts new file mode 100644 index 000000000..5fe510d71 --- /dev/null +++ b/frontend/app/interfaces/module.ts @@ -0,0 +1,9 @@ +export type Module = { + id_module: number; + meta_create_date: Date; + meta_update_date: Date; + module_code: string; + module_label: string; + ng_module: string | null; + type: string; +}; diff --git a/frontend/app/interfaces/objObs.ts b/frontend/app/interfaces/objObs.ts index 01f59d3e2..311ed4e4c 100644 --- a/frontend/app/interfaces/objObs.ts +++ b/frontend/app/interfaces/objObs.ts @@ -2,12 +2,12 @@ import { endPoints } from "../enum/endpoints"; import { JsonData } from "../types/jsondata"; import { ISite, ISitesGroup } from "./geom"; import { IVisit } from "./visit"; -export type ObjDataType = ISite | ISitesGroup | IVisit | JsonData ; -export interface IobjObs { - properties: ObjDataType; +export type ObjDataType = ISite | ISitesGroup | IVisit ; +export interface IobjObs { + properties: T | {}; endPoint: endPoints; - objectType: "site" | "sites_group" | "visits"; + objectType: "site" | "sites_group" | "visit"; label: string; addObjLabel: string; editObjLabel: string; diff --git a/frontend/app/interfaces/object.ts b/frontend/app/interfaces/object.ts index 3e52f3935..cec2af00d 100644 --- a/frontend/app/interfaces/object.ts +++ b/frontend/app/interfaces/object.ts @@ -2,19 +2,24 @@ import { JsonData } from "../types/jsondata"; import { IPaginated } from "./page"; import { GeoJSON } from "geojson"; import { Observable } from "rxjs"; -import { Resp } from "../types/response"; export interface IObject { data: JsonData; } +export interface IObjectProperties { + properties: T; +} + export interface IService { get(limit: number, page: number, params: JsonData): Observable>; - create(postdata: T): Observable; - patch(id: number, updatedData: T): Observable; - // delete(obj: IGeomObject) + create(postdata: IObjectProperties): Observable; + patch(id: number, updatedData: IObjectProperties): Observable; + delete(id: number): Observable; } -export interface IGeomService extends IService { - get_geometries(params: JsonData): Observable; -} +export type SelectObject = { + id: string; + label: string; +}; + diff --git a/frontend/app/interfaces/response.ts b/frontend/app/interfaces/response.ts deleted file mode 100644 index 317f5c94d..000000000 --- a/frontend/app/interfaces/response.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { JsonData } from "../types/jsondata"; -export interface ResponseUpdated { - message: string; - payload: JsonData; - status_code: number; -} diff --git a/frontend/app/interfaces/visit.ts b/frontend/app/interfaces/visit.ts index 0404ee6e8..c285fcd13 100644 --- a/frontend/app/interfaces/visit.ts +++ b/frontend/app/interfaces/visit.ts @@ -1,7 +1,7 @@ import { JsonData } from "../types/jsondata"; -import { IGeomObject } from "./geom"; +import { IObject } from "./object"; -export interface IVisit extends IGeomObject { +export interface IVisit extends IObject { pk:number; comments: string; data: JsonData; diff --git a/frontend/app/services/api-geom.service.ts b/frontend/app/services/api-geom.service.ts index 92a39cf73..f9e942718 100644 --- a/frontend/app/services/api-geom.service.ts +++ b/frontend/app/services/api-geom.service.ts @@ -2,70 +2,94 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { endPoints } from '../enum/endpoints'; -import { IGeomService, ISite, ISiteType, ISitesGroup } from '../interfaces/geom'; -import { IobjObs, ObjDataType } from '../interfaces/objObs'; +import { IGeomObject, IGeomService, ISite, ISiteType, ISitesGroup } from '../interfaces/geom'; +import { IobjObs } from '../interfaces/objObs'; import { IPaginated } from '../interfaces/page'; import { JsonData } from '../types/jsondata'; -import { Resp } from '../types/response'; import { Utils } from '../utils/utils'; import { CacheService } from './cache.service'; import { ConfigJsonService } from './config-json.service'; import { IVisit } from '../interfaces/visit'; +import { IObject, IObjectProperties, IService } from '../interfaces/object'; +import { LIMIT } from '../constants/api'; +import { Module } from '../interfaces/module'; @Injectable() -export class ApiGeomService implements IGeomService { +export class ApiService implements IService { + public objectObs: IobjObs; public endPoint: endPoints; - public objectObs: IobjObs; - constructor( protected _cacheService: CacheService, protected _configJsonService: ConfigJsonService - ) { - this.init(this.endPoint, this.objectObs); - } + ) {} - init(endPoint:endPoints, objectObjs: IobjObs) { + init(endPoint: endPoints, objectObjs: IobjObs) { this.endPoint = endPoint; this.objectObs = objectObjs; - // this.endPoint = endPoints.sites_groups; - // this.objectObs = { - // properties: {}, - // endPoint: endPoints.sites_groups, - // objectType: 'sites_group', - // label: 'groupe de site', - // addObjLabel: 'Ajouter', - // editObjLabel: 'Editer', - // id: null, - // moduleCode: 'generic', - // schema: {}, - // template: { - // fieldNames: [], - // fieldLabels: {}, - // fieldNamesList: [], - // fieldDefinitions: {}, - // }, - // dataTable: { colNameObj: {} }, - // }; + this.initConfig(); } - get( - page: number = 1, - limit: number = 10, - params: JsonData = {} - ): Observable> { - return this._cacheService.request>>( - 'get', - this.endPoint, - { - queryParams: { page, limit, ...params }, - } - ); + + private initConfig(): void { + this._configJsonService + .init(this.objectObs.moduleCode) + .pipe() + .subscribe(() => { + const fieldNames = this._configJsonService.configModuleObjectParam( + this.objectObs.moduleCode, + this.objectObs.objectType, + 'display_properties' + ); + //FIXME: same as site group: to refact + const fieldNamesList = this._configJsonService.configModuleObjectParam( + this.objectObs.moduleCode, + this.objectObs.objectType, + 'display_list' + ); + const schema = this._configJsonService.schema( + this.objectObs.moduleCode, + this.objectObs.objectType + ); + const fieldLabels = this._configJsonService.fieldLabels(schema); + const fieldDefinitions = this._configJsonService.fieldDefinitions(schema); + this.objectObs.template.fieldNames = fieldNames; + this.objectObs.template.fieldNamesList = fieldNamesList; + this.objectObs.schema = schema; + this.objectObs.template.fieldLabels = fieldLabels; + this.objectObs.template.fieldDefinitions = fieldDefinitions; + this.objectObs.template.fieldNamesList = fieldNamesList; + this.objectObs.dataTable.colNameObj = Utils.toObject(fieldNamesList, fieldLabels); + }); + } + get(page: number = 1, limit: number = LIMIT, params: JsonData = {}): Observable> { + return this._cacheService.request>>('get', this.objectObs.endPoint, { + queryParams: { page, limit, ...params }, + }); } - getById(id: number): Observable { - return this._cacheService.request>( - 'get', - `${this.endPoint}/${id}` - ); + getById(id: number): Observable { + return this._cacheService.request>('get', `${this.objectObs.endPoint}/${id}`); + } + patch(id: number, updatedData: IObjectProperties): Observable { + return this._cacheService.request('patch', `${this.objectObs.endPoint}/${id}`, { + postData: updatedData as {}, + }); + } + + create(postData: IObjectProperties): Observable { + return this._cacheService.request('post', `${this.objectObs.endPoint}`, { + postData: postData as {}, + }); + } + + delete(id: number): Observable { + return this._cacheService.request('delete', `${this.objectObs.endPoint}/${id}`); + } +} +@Injectable() +export class ApiGeomService extends ApiService implements IGeomService { + constructor(protected _cacheService: CacheService, protected _configJsonService: ConfigJsonService) { + super(_cacheService, _configJsonService); + this.init(this.endPoint, this.objectObs); } get_geometries(params: JsonData = {}): Observable { @@ -77,32 +101,16 @@ export class ApiGeomService implements IGeomService { } ); } - - patch(id: number, updatedData: { properties: ISitesGroup | ISite | IVisit }): Observable { - return this._cacheService.request('patch', `${this.endPoint}/${id}`, { - postData: updatedData, - }); - } - - create(postData: { properties: ISitesGroup | ISite }): Observable { - return this._cacheService.request('post', `${this.endPoint}`, { - postData: postData, - }); - } - - delete(id: number): Observable { - return this._cacheService.request('delete', `${this.endPoint}/${id}`); - } } @Injectable() -export class SitesGroupService extends ApiGeomService { +export class SitesGroupService extends ApiGeomService { constructor(_cacheService: CacheService, _configJsonService: ConfigJsonService) { super(_cacheService, _configJsonService); } init(): void { - this.endPoint = endPoints.sites_groups; - this.objectObs = { + const endPoint = endPoints.sites_groups; + const objectObs: IobjObs = { properties: {}, endPoint: endPoints.sites_groups, objectType: 'sites_group', @@ -121,34 +129,7 @@ export class SitesGroupService extends ApiGeomService { }, dataTable: { colNameObj: {} }, }; - this._configJsonService - .init(this.objectObs.moduleCode) - .pipe() - .subscribe(() => { - const fieldNames = this._configJsonService.configModuleObjectParam( - this.objectObs.moduleCode, - this.objectObs.objectType, - 'display_properties' - ); - const fieldNamesList = this._configJsonService.configModuleObjectParam( - this.objectObs.moduleCode, - this.objectObs.objectType, - 'display_list' - ); - const schema = this._configJsonService.schema( - this.objectObs.moduleCode, - this.objectObs.objectType - ); - const fieldLabels = this._configJsonService.fieldLabels(schema); - const fieldDefinitions = this._configJsonService.fieldDefinitions(schema); - this.objectObs.template.fieldNames = fieldNames; - this.objectObs.template.fieldNamesList = fieldNamesList; - this.objectObs.schema = schema; - this.objectObs.template.fieldLabels = fieldLabels; - this.objectObs.template.fieldDefinitions = fieldDefinitions; - this.objectObs.template.fieldNamesList = fieldNamesList; - this.objectObs.dataTable.colNameObj = Utils.toObject(fieldNamesList, fieldLabels); - }); + super.init(endPoint, objectObs); } getSitesChild( @@ -160,26 +141,17 @@ export class SitesGroupService extends ApiGeomService { 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 { +export class SitesService extends ApiGeomService { constructor(_cacheService: CacheService, _configJsonService: ConfigJsonService) { super(_cacheService, _configJsonService); } - opts = []; init(): void { - this.endPoint = endPoints.sites; - this.objectObs = { + const endPoint = endPoints.sites; + const objectObs: IobjObs = { properties: {}, endPoint: endPoints.sites, objectType: 'site', @@ -198,45 +170,9 @@ export class SitesService extends ApiGeomService { }, dataTable: { colNameObj: {} }, }; - this._configJsonService - .init(this.objectObs.moduleCode) - .pipe() - .subscribe(() => { - const fieldNames = this._configJsonService.configModuleObjectParam( - this.objectObs.moduleCode, - this.objectObs.objectType, - 'display_properties' - ); - //FIXME: same as site group: to refact - const fieldNamesList = this._configJsonService.configModuleObjectParam( - this.objectObs.moduleCode, - this.objectObs.objectType, - 'display_list' - ); - const schema = this._configJsonService.schema( - this.objectObs.moduleCode, - this.objectObs.objectType - ); - const fieldLabels = this._configJsonService.fieldLabels(schema); - const fieldDefinitions = this._configJsonService.fieldDefinitions(schema); - this.objectObs.template.fieldNames = fieldNames; - this.objectObs.template.fieldNamesList = fieldNamesList; - this.objectObs.schema = schema; - this.objectObs.template.fieldLabels = fieldLabels; - this.objectObs.template.fieldDefinitions = fieldDefinitions; - this.objectObs.template.fieldNamesList = fieldNamesList; - this.objectObs.dataTable.colNameObj = Utils.toObject(fieldNamesList, fieldLabels); - }); + super.init(endPoint, objectObs); } - // getTypeSites( - // ): Observable> { - // return this._cacheService.request>>( - // "get", - // "sites/types" - // ); - // } - getTypeSites( page: number = 1, limit: number = 10, @@ -251,26 +187,23 @@ export class SitesService extends ApiGeomService { ); } - addObjectType(): string { - return ' un nouveau site'; - } - - editObjectType(): string { - return 'le site'; + getSiteModules(idSite: number): Observable { + return this._cacheService.request('get', `sites/${idSite}/modules`); } } @Injectable() -export class VisitsService extends ApiGeomService { +export class VisitsService extends ApiService { constructor(_cacheService: CacheService, _configJsonService: ConfigJsonService) { super(_cacheService, _configJsonService); + this.init(); } init(): void { - this.endPoint = endPoints.visits; - this.objectObs = { + const endPoint = endPoints.visits; + const objectObs: IobjObs = { properties: {}, endPoint: endPoints.visits, - objectType: 'visits', + objectType: 'visit', label: 'visite', addObjLabel: 'Ajouter une nouvelle visite', editObjLabel: 'Editer la visite', @@ -286,40 +219,6 @@ export class VisitsService extends ApiGeomService { }, dataTable: { colNameObj: {} }, }; - this._configJsonService - .init(this.objectObs.moduleCode) - .pipe() - .subscribe(() => { - const fieldNames = this._configJsonService.configModuleObjectParam( - this.objectObs.moduleCode, - this.objectObs.objectType, - 'display_properties' - ); - const fieldNamesList = this._configJsonService.configModuleObjectParam( - this.objectObs.moduleCode, - this.objectObs.objectType, - 'display_list' - ); - const schema = this._configJsonService.schema( - this.objectObs.moduleCode, - this.objectObs.objectType - ); - const fieldLabels = this._configJsonService.fieldLabels(schema); - const fieldDefinitions = this._configJsonService.fieldDefinitions(schema); - this.objectObs.template.fieldNames = fieldNames; - this.objectObs.template.fieldNamesList = fieldNamesList; - this.objectObs.schema = schema; - this.objectObs.template.fieldLabels = fieldLabels; - this.objectObs.template.fieldDefinitions = fieldDefinitions; - this.objectObs.template.fieldNamesList = fieldNamesList; - this.objectObs.dataTable.colNameObj = Utils.toObject(fieldNamesList, fieldLabels); - }); - } - addObjectType(): string { - return " une nouvelle visite"; + super.init(endPoint, objectObs); } - - editObjectType(): string { - return "la visite"; - } -} \ No newline at end of file +} diff --git a/frontend/app/services/config.service.ts b/frontend/app/services/config.service.ts index 5c141356a..526ec5563 100644 --- a/frontend/app/services/config.service.ts +++ b/frontend/app/services/config.service.ts @@ -13,7 +13,7 @@ export class ConfigService { /** Configuration */ - init(moduleCode: null | string = null) { + init(moduleCode: string | null = null) { // a definir ailleurs moduleCode = moduleCode || 'generic'; diff --git a/frontend/app/types/response.ts b/frontend/app/types/response.ts deleted file mode 100644 index dd26b3360..000000000 --- a/frontend/app/types/response.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ISite, ISitesGroup } from "../interfaces/geom"; -import { ResponseUpdated } from "../interfaces/response"; - -export type Resp = ResponseUpdated | ISite | ISitesGroup;