diff --git a/backend/geonature/core/gn_synthese/routes.py b/backend/geonature/core/gn_synthese/routes.py index 3df6e82d7c..105f358a9d 100644 --- a/backend/geonature/core/gn_synthese/routes.py +++ b/backend/geonature/core/gn_synthese/routes.py @@ -22,7 +22,7 @@ from werkzeug.exceptions import Forbidden, NotFound, BadRequest, Conflict from werkzeug.datastructures import MultiDict from sqlalchemy import distinct, func, desc, asc, select, case, or_ -from sqlalchemy.orm import joinedload, lazyload, selectinload, contains_eager, raiseload +from sqlalchemy.orm import joinedload, lazyload, selectinload, contains_eager, raiseload, Query from geojson import FeatureCollection, Feature import sqlalchemy as sa from sqlalchemy.orm import load_only, aliased, Load, with_expression @@ -44,6 +44,7 @@ from geonature.core.gn_synthese.models import ( BibReportsTypes, CorAreaSynthese, + CorObserverSynthese, DefaultsNomenclaturesValue, Synthese, TSources, @@ -52,7 +53,12 @@ TReport, SyntheseLogEntry, ) +from geonature.core.gn_commons.models import TMedias + +from pypnusershub.db import User + from geonature.core.gn_synthese.synthese_config import MANDATORY_COLUMNS +from geonature.core.gn_synthese.utils.taxon_sheet import TaxonSheetUtils, SortOrder from geonature.core.gn_synthese.utils.blurring import ( build_allowed_geom_cte, @@ -67,7 +73,6 @@ from geonature.core.gn_permissions.decorators import login_required, permissions_required from geonature.core.gn_permissions.tools import get_scopes_by_action, get_permissions from geonature.core.sensitivity.models import cor_sensitivity_area_type - from ref_geo.models import LAreas, BibAreasTypes from apptax.taxonomie.models import ( @@ -972,37 +977,21 @@ def general_stats(permissions): if app.config["SYNTHESE"]["ENABLE_TAXON_SHEETS"]: - @routes.route("/taxon_stats/", methods=["GET"]) + @routes.route("/taxon_stats/", methods=["GET"]) @permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE") @json_resp - def taxon_stats(scope, cd_nom): + def taxon_stats(scope, cd_ref): """Return stats for a specific taxon""" area_type = request.args.get("area_type") - if not area_type: raise BadRequest("Missing area_type parameter") - # Ensure area_type is valid - valid_area_types = ( - db.session.query(BibAreasTypes.type_code) - .distinct() - .filter(BibAreasTypes.type_code == area_type) - .scalar() - ) - if not valid_area_types: - raise BadRequest("Invalid area_type") - - # Subquery to fetch areas based on area_type - areas_subquery = ( - select(LAreas.id_area) - .where(LAreas.id_type == BibAreasTypes.id_type, BibAreasTypes.type_code == area_type) - .alias("areas") - ) - cd_ref = db.session.scalar(select(Taxref.cd_ref).where(Taxref.cd_nom == cd_nom)) - taxref_cd_nom_list = db.session.scalars( - select(Taxref.cd_nom).where(Taxref.cd_ref == cd_ref) - ) + if not TaxonSheetUtils.is_valid_area_type(area_type): + raise BadRequest("Invalid area_type parameter") + + areas_subquery = TaxonSheetUtils.get_area_subquery(area_type) + taxref_cd_nom_list = TaxonSheetUtils.get_cd_nom_list_from_cd_ref(cd_ref) # Main query to fetch stats query = ( @@ -1030,13 +1019,12 @@ def taxon_stats(scope, cd_nom): .where(Synthese.cd_nom.in_(taxref_cd_nom_list)) ) - synthese_query_obj = SyntheseQuery(Synthese, query, {}) - synthese_query_obj.filter_query_with_cruved(g.current_user, scope) - result = DB.session.execute(synthese_query_obj.query) + synthese_query = TaxonSheetUtils.get_synthese_query_with_scope(g.current_user, scope, query) + result = DB.session.execute(synthese_query) synthese_stats = result.fetchone() data = { - "cd_ref": cd_nom, + "cd_ref": cd_ref, "observation_count": synthese_stats["observation_count"], "observer_count": synthese_stats["observer_count"], "area_count": synthese_stats["area_count"], @@ -1049,6 +1037,53 @@ def taxon_stats(scope, cd_nom): return data +if app.config["SYNTHESE"]["TAXON_SHEET"]["ENABLE_TAB_OBSERVERS"]: + + @routes.route("/taxon_observers/", methods=["GET"]) + @permissions.check_cruved_scope("R", get_scope=True, module_code="SYNTHESE") + def taxon_observers(scope, cd_ref): + per_page = request.args.get("per_page", 10, int) + page = request.args.get("page", 1, int) + sort_by = request.args.get("sort_by", "observer") + sort_order = request.args.get("sort_order", SortOrder.ASC, SortOrder) + field_separator = request.args.get( + "field_separator", app.config["SYNTHESE"]["FIELD_OBSERVERS_SEPARATOR"] + ) + + # Handle sorting + if sort_by not in ["observer", "date_min", "date_max", "observation_count", "media_count"]: + raise BadRequest(f"The sort_by column {sort_by} is not defined") + + taxref_cd_nom_list = TaxonSheetUtils.get_cd_nom_list_from_cd_ref(cd_ref) + + query = ( + db.session.query( + func.trim( + func.unnest(func.string_to_array(Synthese.observers, field_separator)) + ).label("observer"), + func.min(Synthese.date_min).label("date_min"), + func.max(Synthese.date_max).label("date_max"), + func.count(Synthese.id_synthese).label("observation_count"), + func.count(TMedias.id_media).label("media_count"), + ) + .group_by("observer") + .outerjoin(Synthese.medias) + .where(Synthese.cd_nom.in_(taxref_cd_nom_list)) + ) + query = TaxonSheetUtils.get_synthese_query_with_scope(g.current_user, scope, query) + query = TaxonSheetUtils.update_query_with_sorting(query, sort_by, sort_order) + results = TaxonSheetUtils.paginate(query, page, per_page) + + return jsonify( + { + "items": results.items, + "total": results.total, + "per_page": per_page, + "page": page, + } + ) + + @routes.route("/taxons_tree", methods=["GET"]) @login_required @json_resp diff --git a/backend/geonature/core/gn_synthese/utils/taxon_sheet.py b/backend/geonature/core/gn_synthese/utils/taxon_sheet.py new file mode 100644 index 0000000000..373b4e4a8f --- /dev/null +++ b/backend/geonature/core/gn_synthese/utils/taxon_sheet.py @@ -0,0 +1,64 @@ +import typing +from geonature.utils.env import db +from ref_geo.models import LAreas, BibAreasTypes + +from geonature.core.gn_synthese.models import Synthese +from sqlalchemy import select, desc, asc +from apptax.taxonomie.models import Taxref +from geonature.core.gn_synthese.utils.query_select_sqla import SyntheseQuery +from sqlalchemy.orm import Query +from werkzeug.exceptions import BadRequest +from flask_sqlalchemy.pagination import Pagination +from enum import Enum + + +class SortOrder(Enum): + ASC = "asc" + DESC = "desc" + + +class TaxonSheetUtils: + + @staticmethod + def update_query_with_sorting(query: Query, sort_by: str, sort_order: SortOrder) -> Query: + if sort_order == SortOrder.ASC: + return query.order_by(asc(sort_by)) + + return query.order_by(desc(sort_by)) + + @staticmethod + def paginate(query: Query, page: int, per_page: int) -> Pagination: + return query.paginate(page=page, per_page=per_page, error_out=False) + + # + @staticmethod + def get_cd_nom_list_from_cd_ref(cd_ref: int) -> typing.List[int]: + return db.session.scalars(select(Taxref.cd_nom).where(Taxref.cd_ref == cd_ref)) + + @staticmethod + def get_synthese_query_with_scope(current_user, scope: int, query: Query) -> SyntheseQuery: + synthese_query_obj = SyntheseQuery(Synthese, query, {}) + synthese_query_obj.filter_query_with_cruved(current_user, scope) + return synthese_query_obj.query + + @staticmethod + def is_valid_area_type(area_type: str) -> bool: + # Ensure area_type is valid + valid_area_types = ( + db.session.query(BibAreasTypes.type_code) + .distinct() + .filter(BibAreasTypes.type_code == area_type) + .scalar() + ) + + return valid_area_types + + @staticmethod + def get_area_subquery(area_type: str) -> Query: + + # Subquery to fetch areas based on area_type + return ( + select(LAreas.id_area) + .where(LAreas.id_type == BibAreasTypes.id_type, BibAreasTypes.type_code == area_type) + .alias("areas") + ) diff --git a/backend/geonature/tests/fixtures.py b/backend/geonature/tests/fixtures.py index fba219ad84..91b41b593b 100644 --- a/backend/geonature/tests/fixtures.py +++ b/backend/geonature/tests/fixtures.py @@ -330,12 +330,12 @@ def create_user( db.session.add(organisme) users_to_create = [ - (("noright_user", organisme, 0), {}), - (("stranger_user", None, 2), {}), - (("associate_user", organisme, 2), {}), - (("self_user", organisme, 1), {}), + (("noright_user", organisme, 0), {"nom_role": "User", "prenom_role": "NoRight"}), + (("stranger_user", None, 2), {"nom_role": "User", "prenom_role": "Stranger"}), + (("associate_user", organisme, 2), {"nom_role": "User", "prenom_role": "Associate"}), + (("self_user", organisme, 1), {"nom_role": "User", "prenom_role": "Self"}), (("user", organisme, 2), {"nom_role": "Bob", "prenom_role": "Bobby"}), - (("admin_user", organisme, 3), {}), + (("admin_user", organisme, 3), {"nom_role": "Administrateur", "prenom_role": "Test"}), (("associate_user_2_exclude_sensitive", organisme, 2, True), {}), ( ( @@ -546,6 +546,7 @@ def create_synthese( source, uuid=func.uuid_generate_v4(), cor_observers=[], + observers=[], date_min="", date_max="", altitude_min=800, @@ -574,6 +575,7 @@ def create_synthese( altitude_min=altitude_min, altitude_max=altitude_max, cor_observers=cor_observers, + observers=observers, **kwargs, ) @@ -743,6 +745,7 @@ def synthese_data(app, users, datasets, source, sources_modules): source_m, unique_id_sinp, [users["admin_user"], users["user"]], + ["Administrative Test", "Bobby Bob"], date_min, date_max, altitude_min, diff --git a/backend/geonature/tests/test_synthese.py b/backend/geonature/tests/test_synthese.py index f78116a4bb..cb4fd3e7c6 100644 --- a/backend/geonature/tests/test_synthese.py +++ b/backend/geonature/tests/test_synthese.py @@ -1144,28 +1144,28 @@ def test_taxon_stats(self, synthese_data, users): # Missing area_type parameter response = self.client.get( - url_for("gn_synthese.taxon_stats", cd_nom=CD_REF_VALID), + url_for("gn_synthese.taxon_stats", cd_ref=CD_REF_VALID), ) assert response.status_code == 400 assert response.json["description"] == "Missing area_type parameter" # Invalid area_type parameter response = self.client.get( - url_for("gn_synthese.taxon_stats", cd_nom=CD_REF_VALID, area_type=AREA_TYPE_INVALID), + url_for("gn_synthese.taxon_stats", cd_ref=CD_REF_VALID, area_type=AREA_TYPE_INVALID), ) assert response.status_code == 400 - assert response.json["description"] == "Invalid area_type" + assert response.json["description"] == "Invalid area_type parameter" # Invalid cd_ref parameter response = self.client.get( - url_for("gn_synthese.taxon_stats", cd_nom=CD_REF_INVALID, area_type=AREA_TYPE_VALID), + url_for("gn_synthese.taxon_stats", cd_ref=CD_REF_INVALID, area_type=AREA_TYPE_VALID), ) assert response.status_code == 200 assert response.get_json() == CD_REF_INVALID_STATS # Invalid cd_ref parameter response = self.client.get( - url_for("gn_synthese.taxon_stats", cd_nom=CD_REF_VALID, area_type=AREA_TYPE_VALID), + url_for("gn_synthese.taxon_stats", cd_ref=CD_REF_VALID, area_type=AREA_TYPE_VALID), ) response_json = response.get_json() assert response.status_code == 200 @@ -1225,6 +1225,126 @@ def test_get_one_synthese_record(self, app, users, synthese_data): ) assert response.status_code == Forbidden.code + def test_taxon_observer(self, synthese_data, users): + set_logged_user(self.client, users["stranger_user"]) + + ## Test Data + + SORT_ORDER_UNDEFINED = "sort-order-undefined" + SORT_ORDER_ASC = "asc" + SORT_ORDER_DESC = "desc" + PER_PAGE = 2 + SORT_BY_UNDEFINED = "sort-by-undefined" + + CD_REF = 2497 + CD_REF_OBSERVERS_ASC = { + "items": [ + { + "date_max": "Thu, 03 Oct 2024 08:09:10 GMT", + "date_min": "Wed, 02 Oct 2024 11:22:33 GMT", + "media_count": 0, + "observation_count": 3, + "observer": "Administrateur Test", + }, + { + "date_max": "Thu, 03 Oct 2024 08:09:10 GMT", + "date_min": "Wed, 02 Oct 2024 11:22:33 GMT", + "media_count": 0, + "observation_count": 3, + "observer": "Bob Bobby", + }, + ], + "page": 1, + "per_page": 2, + "total": 2, + } + CD_REF_OBSERVERS_DESC = { + "items": [ + { + "date_max": "Thu, 03 Oct 2024 08:09:10 GMT", + "date_min": "Wed, 02 Oct 2024 11:22:33 GMT", + "media_count": 0, + "observation_count": 3, + "observer": "Bob Bobby", + }, + { + "date_max": "Thu, 03 Oct 2024 08:09:10 GMT", + "date_min": "Wed, 02 Oct 2024 11:22:33 GMT", + "media_count": 0, + "observation_count": 3, + "observer": "Administrateur Test", + }, + ], + "page": 1, + "per_page": 2, + "total": 2, + } + + ## sort_order + + # Unknow sort_order parameters: shoudl fallback in asc + response = self.client.get( + url_for( + "gn_synthese.taxon_observers", + cd_ref=CD_REF, + per_page=PER_PAGE, + sort_order=SORT_ORDER_UNDEFINED, + ), + ) + assert response.status_code == 200 + assert response.get_json() == CD_REF_OBSERVERS_ASC + + # sort order ASC + response = self.client.get( + url_for( + "gn_synthese.taxon_observers", + cd_ref=CD_REF, + per_page=PER_PAGE, + sort_order=SORT_ORDER_ASC, + ), + ) + assert response.status_code == 200 + assert response.get_json() == CD_REF_OBSERVERS_ASC + + # sort order DESC + response = self.client.get( + url_for( + "gn_synthese.taxon_observers", + cd_ref=CD_REF, + per_page=PER_PAGE, + sort_order=SORT_ORDER_DESC, + ), + ) + assert response.status_code == 200 + assert response.get_json() == CD_REF_OBSERVERS_DESC + + ## sort_by + response = self.client.get( + url_for( + "gn_synthese.taxon_observers", + cd_ref=CD_REF, + per_page=PER_PAGE, + sort_order=SORT_ORDER_ASC, + sort_by=SORT_BY_UNDEFINED, + ), + ) + assert response.status_code == BadRequest.code + assert ( + response.json["description"] == f"The sort_by column {SORT_BY_UNDEFINED} is not defined" + ) + + # Ok + response = self.client.get( + url_for( + "gn_synthese.taxon_observers", + cd_ref=CD_REF, + per_page=PER_PAGE, + ) + ) + + assert response.status_code == 200 + assert response.get_json() == CD_REF_OBSERVERS_ASC + def test_color_taxon(self, synthese_data, users): # Note: require grids 5×5! set_logged_user(self.client, users["self_user"]) diff --git a/backend/geonature/utils/config_schema.py b/backend/geonature/utils/config_schema.py index 926248e726..c4d01cb4ed 100644 --- a/backend/geonature/utils/config_schema.py +++ b/backend/geonature/utils/config_schema.py @@ -292,6 +292,7 @@ class ExportObservationSchema(Schema): class TaxonSheet(Schema): # -------------------------------------------------------------------- # SYNTHESE - TAXON_SHEET + ENABLE_TAB_OBSERVERS = fields.Boolean(load_default=True) ENABLE_TAB_PROFILE = fields.Boolean(load_default=True) ENABLE_TAB_TAXONOMY = fields.Boolean(load_default=True) @@ -455,6 +456,8 @@ class Synthese(Schema): ENABLE_TAXON_SHEETS = fields.Boolean(load_default=True) TAXON_SHEET = fields.Nested(TaxonSheet, load_default=TaxonSheet().load({})) + FIELD_OBSERVERS_SEPARATOR = fields.String(load_default=",") + @pre_load def warn_deprecated(self, data, **kwargs): deprecated = { diff --git a/config/default_config.toml.example b/config/default_config.toml.example index 2ff5db7676..41cb737827 100644 --- a/config/default_config.toml.example +++ b/config/default_config.toml.example @@ -449,6 +449,8 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" ENABLE_TAB_PROFILE = true # Permet d'activer ou non l'onglet "Taxonomie" ENABLE_TAB_TAXONOMY = true + # Permet d'activer ou non la section "Observers" + ENABLE_TAB_OBSERVERS = true # Gestion des demandes d'inscription [ACCOUNT_MANAGEMENT] diff --git a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data-pagination-item.ts b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data-pagination-item.ts new file mode 100644 index 0000000000..07726a73ae --- /dev/null +++ b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data-pagination-item.ts @@ -0,0 +1,11 @@ +export interface SyntheseDataPaginationItem { + totalItems: number; + currentPage: number; + perPage: number; +} + +export const DEFAULT_PAGINATION: SyntheseDataPaginationItem = { + totalItems: 0, + currentPage: 1, + perPage: 10, +}; diff --git a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data-sort-item.ts b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data-sort-item.ts new file mode 100644 index 0000000000..e11b73f9d6 --- /dev/null +++ b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data-sort-item.ts @@ -0,0 +1,14 @@ +export enum SORT_ORDER { + ASC = 'asc', + DESC = 'desc', +} + +export interface SyntheseDataSortItem { + sortBy: string; + sortOrder: string; +} + +export const DEFAULT_SORT: SyntheseDataSortItem = { + sortBy: '', + sortOrder: SORT_ORDER.ASC, +}; diff --git a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts index 78a2f064f5..c3d6d4f549 100644 --- a/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts +++ b/frontend/src/app/GN2CommonModule/form/synthese-form/synthese-data.service.ts @@ -11,6 +11,8 @@ import { BehaviorSubject } from 'rxjs'; import { CommonService } from '@geonature_common/service/common.service'; import { Observable } from 'rxjs'; import { ConfigService } from '@geonature/services/config.service'; +import { DEFAULT_PAGINATION, SyntheseDataPaginationItem } from './synthese-data-pagination-item'; +import { DEFAULT_SORT, SyntheseDataSortItem } from './synthese-data-sort-item'; export const FormatMapMime = new Map([ ['csv', 'text/csv'], @@ -61,6 +63,21 @@ export class SyntheseDataService { }); } + getSyntheseTaxonSheetObservers( + cd_ref: number, + pagination: SyntheseDataPaginationItem = DEFAULT_PAGINATION, + sort: SyntheseDataSortItem = DEFAULT_SORT + ) { + return this._api.get(`${this.config.API_ENDPOINT}/synthese/taxon_observers/${cd_ref}`, { + params: { + per_page: pagination.perPage, + page: pagination.currentPage, + sort_by: sort.sortBy, + sort_order: sort.sortOrder, + }, + }); + } + getTaxaCount(params = {}) { let queryString = new HttpParams(); for (let key in params) { diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.html b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.html index 284659b63b..06fa7e9211 100644 --- a/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.html +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.html @@ -10,7 +10,7 @@ [count]="pagination.totalItems" (page)="onChangePage($event)" [externalSorting]="true" - [sorts]="[{ prop: sort.sort, dir: sort.orderby }]" + [sorts]="[{ prop: sort.sortBy, dir: sort.sortOrder }]" (sort)="onColumnSort($event)" > (); @@ -78,8 +72,8 @@ export class HomeDiscussionsTableComponent implements OnInit, OnDestroy { onColumnSort(event: any) { this.sort = { - sort: event.newValue, - orderby: event.column.prop, + sortBy: event.newValue, + sortOrder: event.column.prop, }; this.pagination.currentPage = 1; this._fetchDiscussions(); @@ -106,8 +100,8 @@ export class HomeDiscussionsTableComponent implements OnInit, OnDestroy { private _buildQueryParams(): URLSearchParams { const params = new URLSearchParams(); params.set('type', 'discussion'); - params.set('sort', this.sort.sort); - params.set('orderby', this.sort.orderby); + params.set('sort', this.sort.sortOrder); + params.set('orderby', this.sort.sortBy); params.set('page', this.pagination.currentPage.toString()); params.set('per_page', this.pagination.perPage.toString()); params.set('my_reports', this._myReportsOnly.toString()); diff --git a/frontend/src/app/syntheseModule/taxon-sheet/loadable.ts b/frontend/src/app/syntheseModule/taxon-sheet/loadable.ts new file mode 100644 index 0000000000..4561510d9b --- /dev/null +++ b/frontend/src/app/syntheseModule/taxon-sheet/loadable.ts @@ -0,0 +1,19 @@ +export class Loadable { + _isLoading: boolean; + + constructor(isLoading: boolean = false) { + this._isLoading = isLoading; + } + + startLoading() { + this._isLoading = true; + } + + stopLoading() { + this._isLoading = false; + } + + get isLoading(): boolean { + return this._isLoading; + } +} diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.html b/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.html new file mode 100644 index 0000000000..5547e2e902 --- /dev/null +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.html @@ -0,0 +1,106 @@ +
+ + {{ renderDate(date) }} + + + + {{ value ? value : '-' }} + + + + {{ value }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.scss b/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.scss new file mode 100644 index 0000000000..2e38c350ec --- /dev/null +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.scss @@ -0,0 +1,5 @@ +.Observers { + &__table { + box-shadow: none; + } +} diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.ts new file mode 100644 index 0000000000..301f1f175a --- /dev/null +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-observers/tab-observers.component.ts @@ -0,0 +1,95 @@ +import { Component, OnInit } from '@angular/core'; +import { GN2CommonModule } from '@geonature_common/GN2Common.module'; +import { CommonModule } from '@angular/common'; +import { ConfigService } from '@geonature/services/config.service'; +import { Taxon } from '@geonature_common/form/taxonomy/taxonomy.component'; +import { TaxonSheetService } from '../taxon-sheet.service'; +import { SyntheseDataService } from '@geonature_common/form/synthese-form/synthese-data.service'; +import { + DEFAULT_PAGINATION, + SyntheseDataPaginationItem, +} from '@geonature_common/form/synthese-form/synthese-data-pagination-item'; +import { + DEFAULT_SORT, + SORT_ORDER, + SyntheseDataSortItem, +} from '@geonature_common/form/synthese-form/synthese-data-sort-item'; +import { Loadable } from '../loadable'; +import { finalize } from 'rxjs/operators'; +@Component({ + standalone: true, + selector: 'tab-observers', + templateUrl: 'tab-observers.component.html', + styleUrls: ['tab-observers.component.scss'], + imports: [GN2CommonModule, CommonModule], +}) +export class TabObserversComponent extends Loadable implements OnInit { + readonly PROP_OBSERVER = 'observer'; + readonly PROP_DATE_MIN = 'date_min'; + readonly PROP_DATE_MAX = 'date_max'; + readonly PROP_OBSERVATION_COUNT = 'observation_count'; + readonly PROP_MEDIA_COUNT = 'media_count'; + + readonly DEFAULT_SORT = { + ...DEFAULT_SORT, + sortBy: this.PROP_OBSERVER, + sortOrder: SORT_ORDER.ASC, + }; + items: any[] = []; + pagination: SyntheseDataPaginationItem = DEFAULT_PAGINATION; + sort: SyntheseDataSortItem = this.DEFAULT_SORT; + + constructor( + private _syntheseDataService: SyntheseDataService, + private _tss: TaxonSheetService + ) { + super(); + } + + ngOnInit() { + this._tss.taxon.subscribe((taxon: Taxon | null) => { + this.fetchObservers(); + }); + } + + renderDate(date: string): string { + return new Date(date).toLocaleDateString(); + } + + onChangePage(event) { + this.pagination.currentPage = event.offset + 1; + this.fetchObservers(); + } + + onSort(event) { + this.sort = { + sortBy: event.column.prop, + sortOrder: event.newValue, + }; + this.fetchObservers(); + } + + async fetchObservers() { + this.startLoading(); + const taxon = this._tss.taxon.getValue(); + this.items = []; + if (!taxon) { + this.pagination = DEFAULT_PAGINATION; + this.sort = this.DEFAULT_SORT; + this.stopLoading(); + return; + } + this._syntheseDataService + .getSyntheseTaxonSheetObservers(taxon.cd_ref, this.pagination, this.sort) + .pipe(finalize(() => this.stopLoading())) + .subscribe((data) => { + // Store result + this.items = data.items; + this.pagination = { + totalItems: data.total, + currentPage: data.page, + perPage: data.per_page, + }; + }); + } +} diff --git a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts index 358800f474..db9e84a7ac 100644 --- a/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts +++ b/frontend/src/app/syntheseModule/taxon-sheet/taxon-sheet.route.service.ts @@ -11,6 +11,7 @@ import { Observable } from 'rxjs'; import { TabGeographicOverviewComponent } from './tab-geographic-overview/tab-geographic-overview.component'; import { TabProfileComponent } from './tab-profile/tab-profile.component'; import { TabTaxonomyComponent } from './tab-taxonomy/tab-taxonomy.component'; +import { TabObserversComponent } from './tab-observers/tab-observers.component'; interface Tab { label: string; @@ -38,6 +39,12 @@ export const ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES: Array = [ configEnabledField: 'ENABLE_TAB_PROFILE', component: TabProfileComponent, }, + { + label: 'Observateurs', + path: 'observers', + configEnabledField: 'ENABLE_TAB_OBSERVERS', + component: TabObserversComponent, + }, ]; @Injectable({