diff --git a/backend/geonature/core/gn_synthese/routes.py b/backend/geonature/core/gn_synthese/routes.py index c42c65711f..6ae19f95dd 100644 --- a/backend/geonature/core/gn_synthese/routes.py +++ b/backend/geonature/core/gn_synthese/routes.py @@ -15,7 +15,7 @@ jsonify, g, ) -from geonature.core.gn_synthese.schemas import SyntheseSchema +from geonature.core.gn_synthese.schemas import ReportSchema, SyntheseSchema from geonature.core.gn_synthese.synthese_config import MANDATORY_COLUMNS from pypnusershub.db.models import User from pypnnomenclature.models import BibNomenclaturesTypes, TNomenclatures @@ -1490,53 +1490,146 @@ def update_content_report(id_report): @routes.route("/reports", methods=["GET"]) @permissions_required("R", module_code="SYNTHESE") -def list_reports(permissions): +def list_all_reports(permissions): + # Parameters type_name = request.args.get("type") - id_synthese = request.args.get("idSynthese") + orderby = request.args.get("orderby", "creation_date") sort = request.args.get("sort") - # VERIFY ID SYNTHESE + page = int(request.args.get("page", 1)) + per_page = int(request.args.get("per_page", 10)) + my_reports = request.args.get("my_reports", "false").lower() == "true" + + # Start query + query = ( + sa.select(TReport, User.nom_complet) + .join(User, TReport.id_role == User.id_role) + .options( + joinedload(TReport.report_type).load_only( + BibReportsTypes.type, BibReportsTypes.id_type + ), + joinedload(TReport.synthese).load_only( + Synthese.cd_nom, + Synthese.nom_cite, + Synthese.observers, + Synthese.date_min, + Synthese.date_max, + ), + joinedload(TReport.user).load_only(User.nom_role, User.prenom_role), + ) + ) + # Verify and filter by type + if type_name: + type_exists = db.session.scalar( + sa.exists(BibReportsTypes).where(BibReportsTypes.type == type_name).select() + ) + if not type_exists: + raise BadRequest("This report type does not exist") + query = query.where(TReport.report_type.has(BibReportsTypes.type == type_name)) + + # Filter by id_role for 'pin' type only or if my_reports is true + if type_name == "pin" or my_reports: + query = query.where(TReport.id_role == g.current_user.id_role) + + # On vérifie les permissions en lecture sur la synthese + synthese_query = select(Synthese.id_synthese).select_from(Synthese) + synthese_query_obj = SyntheseQuery(Synthese, synthese_query, {}) + synthese_query_obj.filter_query_with_cruved(g.current_user, permissions) + ids_synthese = db.session.scalars(synthese_query_obj.query).all() + query = query.where(TReport.id_synthese.in_(ids_synthese)) + + SORT_COLUMNS = { + "user.nom_complet": User.nom_complet, + "content": TReport.content, + "creation_date": TReport.creation_date, + } + + # Determine the sorting + if orderby in SORT_COLUMNS: + sort_column = SORT_COLUMNS[orderby] + if sort == "desc": + query = query.order_by(desc(sort_column)) + else: + query = query.order_by(asc(sort_column)) + else: + raise BadRequest("Bad orderby") + + # Pagination + total = db.session.scalar( + select(func.count("*")) + .select_from(TReport) + .where(TReport.report_type.has(BibReportsTypes.type == type_name)) + ) + paginated_results = db.paginate(query, page=page, per_page=per_page) + + result = [] + + for report in paginated_results.items: + report_dict = { + "id_report": report.id_report, + "id_synthese": report.id_synthese, + "id_role": report.id_role, + "report_type": { + "type": report.report_type.type, + "id_type": report.report_type.id_type, + }, + "content": report.content, + "deleted": report.deleted, + "creation_date": report.creation_date, + "user": {"nom_complet": report.user.nom_complet}, + "synthese": { + "cd_nom": report.synthese.cd_nom, + "nom_cite": report.synthese.nom_cite, + "observers": report.synthese.observers, + "date_min": report.synthese.date_min, + "date_max": report.synthese.date_max, + }, + } + result.append(report_dict) + + response = { + "total_filtered": paginated_results.total, + "total": total, + "pages": paginated_results.pages, + "current_page": page, + "per_page": per_page, + "items": result, + } + return jsonify(response) + + +@routes.route("/reports/", methods=["GET"]) +@permissions_required("R", module_code="SYNTHESE") +def list_reports(permissions, id_synthese): + type_name = request.args.get("type") + synthese = db.get_or_404(Synthese, id_synthese) if not synthese.has_instance_permission(permissions): raise Forbidden - # START REQUEST - req = TReport.query.where(TReport.id_synthese == id_synthese) - # SORT - if sort == "asc": - req = req.order_by(asc(TReport.creation_date)) - if sort == "desc": - req = req.order_by(desc(TReport.creation_date)) - # VERIFY AND SET TYPE - type_exists = BibReportsTypes.query.filter_by(type=type_name).one_or_none() - # type param is not required to get all - if type_name and not type_exists: - raise BadRequest("This report type does not exist") + + query = sa.select(TReport).where(TReport.id_synthese == id_synthese) + + # Verify and filter by type if type_name: - req = req.where(TReport.report_type.has(BibReportsTypes.type == type_name)) - # filter by id_role for pin type only - if type_name and type_name == "pin": - req = req.where(TReport.id_role == g.current_user.id_role) - req = req.options( + type_exists = db.session.scalar( + sa.exists(BibReportsTypes).where(BibReportsTypes.type == type_name).select() + ) + if not type_exists: + raise BadRequest("This report type does not exist") + query = query.where(TReport.report_type.has(BibReportsTypes.type == type_name)) + + # Filter by id_role for 'pin' type only + if type_name == "pin": + query = query.where(TReport.id_role == g.current_user.id_role) + + # Join the User table + query = query.options( joinedload(TReport.user).load_only(User.nom_role, User.prenom_role), joinedload(TReport.report_type), ) - result = [ - report.as_dict( - fields=[ - "id_report", - "id_synthese", - "id_role", - "report_type.type", - "report_type.id_type", - "content", - "deleted", - "creation_date", - "user.nom_role", - "user.prenom_role", - ] - ) - for report in req.all() - ] - return jsonify(result) + + return ReportSchema(many=True, only=["+user.nom_role", "+user.prenom_role"]).dump( + db.session.scalars(query).all() + ) @routes.route("/reports/", methods=["DELETE"]) diff --git a/backend/geonature/tests/fixtures.py b/backend/geonature/tests/fixtures.py index 428a93a143..53524c5a2c 100644 --- a/backend/geonature/tests/fixtures.py +++ b/backend/geonature/tests/fixtures.py @@ -845,6 +845,8 @@ def create_report(id_synthese, id_role, content, id_type, deleted): reports = [ (ids[0], users["admin_user"].id_role, "comment1", discussionId, False), (ids[1], users["admin_user"].id_role, "comment1", alertId, False), + (ids[2], users["user"].id_role, "a_comment1", discussionId, True), + (ids[3], users["user"].id_role, "b_comment1", discussionId, True), ] for id_synthese, *args in reports: data.append(create_report(id_synthese, *args)) diff --git a/backend/geonature/tests/test_reports.py b/backend/geonature/tests/test_reports.py index e236ad8745..408e8560e3 100644 --- a/backend/geonature/tests/test_reports.py +++ b/backend/geonature/tests/test_reports.py @@ -1,5 +1,6 @@ import json +from datetime import datetime import pytest from flask import url_for from sqlalchemy import func, select, exists @@ -144,29 +145,125 @@ def test_delete_report(self, reports_data, users): def test_list_reports(self, reports_data, synthese_data, users): url = "gn_synthese.list_reports" - # TEST GET WITHOUT REQUIRED ID SYNTHESE - set_logged_user(self.client, users["admin_user"]) - response = self.client.get(url_for(url)) - assert response.status_code == NotFound.code ids = [s.id_synthese for s in synthese_data.values()] + + # User: noright_user + set_logged_user(self.client, users["noright_user"]) + response = self.client.get( + url_for( + url, id_synthese=ids[0], idRole=users["noright_user"].id_role, type="discussion" + ) + ) + assert response.status_code == Forbidden.code + + # User: admin_user + set_logged_user(self.client, users["admin_user"]) + # TEST GET BY ID SYNTHESE response = self.client.get( - url_for(url, idSynthese=ids[0], idRole=users["admin_user"].id_role, type="discussion") + url_for(url, id_synthese=ids[0], idRole=users["admin_user"].id_role, type="discussion") ) assert response.status_code == 200 assert len(response.json) == 1 + + # TEST INVALID - TYPE DOES NOT EXISTS + response = self.client.get( + url_for( + url, + id_synthese=ids[0], + idRole=users["admin_user"].id_role, + type="UNKNOW-REPORT-TYPE", + ) + ) + assert response.status_code == 400 + assert response.json["description"] == "This report type does not exist" + + # TEST VALID - ADD PIN + response = self.client.get( + url_for(url, id_synthese=ids[0], idRole=users["admin_user"].id_role, type="pin") + ) + assert response.status_code == 200 + assert len(response.json) == 0 # TEST NO RESULT if len(ids) > 1: # not exists because ids[1] is an alert - response = self.client.get(url_for(url, idSynthese=ids[1], type="discussion")) + response = self.client.get(url_for(url, id_synthese=ids[1], type="discussion")) assert response.status_code == 200 assert len(response.json) == 0 # TEST TYPE NOT EXISTS - response = self.client.get(url_for(url, idSynthese=ids[1], type="foo")) + response = self.client.get(url_for(url, id_synthese=ids[1], type="foo")) assert response.status_code == BadRequest.code # NO TYPE - TYPE IS NOT REQUIRED - response = self.client.get(url_for(url, idSynthese=ids[1])) + response = self.client.get(url_for(url, id_synthese=ids[1])) + assert response.status_code == 200 + + @pytest.mark.parametrize( + "sort,orderby,expected_error", + [ + ("asc", "creation_date", False), + ("desc", "creation_date", False), + ("asc", "user.nom_complet", False), + ("asc", "content", False), + ("asc", "nom_cite", True), + ], + ) + def test_list_all_reports( + self, sort, orderby, expected_error, reports_data, synthese_data, users + ): + url = "gn_synthese.list_all_reports" + set_logged_user(self.client, users["admin_user"]) + # TEST GET WITHOUT REQUIRED ID SYNTHESE + response = self.client.get(url_for(url, type="discussion")) + assert response.status_code == 200 + assert "items" in response.json + assert isinstance(response.json["items"], list) + assert len(response.json["items"]) >= 0 + + ids = [s.id_synthese for s in synthese_data.values()] + # TEST WITH MY_REPORTS TRUE + set_logged_user(self.client, users["user"]) + response = self.client.get(url_for(url, type="discussion", my_reports="true")) + assert response.status_code == 200 + items = response.json["items"] + # Check that all items belong to the current user + id_role = users["user"].id_role + nom_complet = users["user"].nom_complet + assert all( + item["id_role"] == id_role and item["user"]["nom_complet"] == nom_complet + for item in items + ) + + # Test undefined type + response = self.client.get(url_for(url, type="UNKNOW-REPORT-TYPE", my_reports="true")) + assert response.status_code == 400 + assert response.json["description"] == "This report type does not exist" + + # TEST SORT AND PAGINATION + if expected_error: + # Test with invalid orderby + response = self.client.get(url_for(url, orderby=orderby, sort=sort)) + assert response.status_code == BadRequest.code + else: + response = self.client.get(url_for(url, orderby=orderby, sort=sort, page=1, per_page=5)) assert response.status_code == 200 + assert "items" in response.json + assert len(response.json["items"]) <= 5 + + # Verify sorting + items = response.json["items"] + reverse_sort = sort == "desc" + if orderby == "creation_date": + dates = [ + datetime.strptime(item["creation_date"], "%a, %d %b %Y %H:%M:%S %Z") + for item in items + ] + assert dates == sorted(dates, reverse=reverse_sort) + elif orderby == "content": + contents = [item["content"] for item in items] + assert contents == sorted(contents, reverse=reverse_sort, key=str.casefold) + elif orderby == "user.nom_complet": + names = [item["user"]["nom_complet"] for item in items] + assert names == sorted(names, reverse=reverse_sort) @pytest.mark.usefixtures("client_class", "notifications_enabled", "temporary_transaction") diff --git a/backend/geonature/utils/config_schema.py b/backend/geonature/utils/config_schema.py index fd420ffb08..36d4e1ed8f 100644 --- a/backend/geonature/utils/config_schema.py +++ b/backend/geonature/utils/config_schema.py @@ -129,6 +129,7 @@ class HomeConfig(Schema): load_default="Texte d'introduction, configurable pour le modifier régulièrement ou le masquer" ) FOOTER = fields.String(load_default="") + DISPLAY_LATEST_DISCUSSIONS = fields.Boolean(load_default=True) class MetadataConfig(Schema): diff --git a/config/default_config.toml.example b/config/default_config.toml.example index 05fa5a6a83..61727d3f11 100644 --- a/config/default_config.toml.example +++ b/config/default_config.toml.example @@ -595,6 +595,7 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *" TITLE = "Bienvenue dans GeoNature" INTRODUCTION = "Texte d'introduction, configurable pour le modifier régulièrement ou le masquer" FOOTER = "" + DISPLAY_LATEST_DISCUSSIONS = true [AUTHENTICATION] DEFAULT_RECONCILIATION_GROUP_ID = 2 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 85ec1862c1..24f71d119e 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 @@ -229,8 +229,10 @@ export class SyntheseDataService { document.body.removeChild(link); } - getReports(params) { - return this._api.get(`${this.config.API_ENDPOINT}/synthese/reports?${params}`); + getReports(params, idSynthese = null) { + const baseUrl = `${this.config.API_ENDPOINT}/synthese/reports`; + const url = idSynthese ? `${baseUrl}/${idSynthese}` : baseUrl; + return this._api.get(`${url}?${params}`); } createReport(params) { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 99acbf60cd..09c9f3798f 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -17,6 +17,10 @@ import { GN2CommonModule } from '@geonature_common/GN2Common.module'; import { AppComponent } from './app.component'; import { routing } from './routing/app-routing.module'; // RoutingModule import { HomeContentComponent } from './components/home-content/home-content.component'; +import { HomeDiscussionsTableComponent } from './components/home-content/home-discussions/home-discussions-table/home-discussions-table.component'; +import { HomeDiscussionsComponent } from './components/home-content/home-discussions/home-discussions.component'; +import { HomeDiscussionsToggleComponent } from './components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component'; + import { SidenavItemsComponent } from './components/sidenav-items/sidenav-items.component'; import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; import { NavHomeComponent } from './components/nav-home/nav-home.component'; @@ -97,6 +101,7 @@ export function initApp(injector) { }, }), LoginModule, + HomeDiscussionsComponent, ], declarations: [ AppComponent, diff --git a/frontend/src/app/components/home-content/home-content.component.html b/frontend/src/app/components/home-content/home-content.component.html index 5edfdd29ad..df02967b13 100644 --- a/frontend/src/app/components/home-content/home-content.component.html +++ b/frontend/src/app/components/home-content/home-content.component.html @@ -38,7 +38,20 @@ - +
+ + + + + + +
= new Subject(); public cluserOrSimpleFeatureGroup = null; + @ViewChild('table') + table: DatatableComponent; + discussions = []; + columns = []; + currentPage = 1; + perPage = 2; + totalPages = 1; + totalRows: Number; + myReportsOnly = false; + sort = 'desc'; + orderby = 'date'; + params: URLSearchParams = new URLSearchParams(); constructor( private _SideNavService: SideNavService, private _syntheseApi: SyntheseDataService, private _mapService: MapService, private _moduleService: ModuleService, private translateService: TranslateService, - public config: ConfigService + public config: ConfigService, + private datePipe: DatePipe ) { // this work here thanks to APP_INITIALIZER on ModuleService let synthese_module = this._moduleService.getModule('SYNTHESE'); @@ -74,6 +89,10 @@ export class HomeContentComponent implements OnInit, AfterViewInit { this.destroy$.unsubscribe(); } + get isExistBlockToDisplay(): boolean { + return this.config.HOME.DISPLAY_LATEST_DISCUSSIONS; // NOTES [projet ARB]: ajouter les autres config à afficher ici || this.config.HOME.DISPLAY_LATEST_VALIDATIONS ..; + } + private computeMapBloc() { this.cluserOrSimpleFeatureGroup.addTo(this._mapService.map); this._syntheseApi 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 new file mode 100644 index 0000000000..cfb2340e53 --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.html @@ -0,0 +1,133 @@ + diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.scss b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.ts b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.ts new file mode 100644 index 0000000000..11cb0c39a9 --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-table/home-discussions-table.component.ts @@ -0,0 +1,73 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; +import { GN2CommonModule } from '@geonature_common/GN2Common.module'; +import { DatatableComponent } from '@swimlane/ngx-datatable'; + +@Component({ + standalone: true, + selector: 'pnx-home-discussions-table', + templateUrl: './home-discussions-table.component.html', + styleUrls: ['./home-discussions-table.component.scss'], + imports: [GN2CommonModule, CommonModule], +}) +export class HomeDiscussionsTableComponent { + @Input() discussions = []; + @Input() currentPage = 1; + @Input() perPage = 2; + @Input() totalPages = 1; + @Input() totalRows = 0; + @Input() totalFilteredRows = 0; + headerHeight: number = 50; + footerHeight: number = 50; + rowHeight: string | number = 'auto'; + limit: number = 10; + count: number = 0; + offset: number = 0; + columnMode: string = 'force'; + rowDetailHeight: number = 150; + columns = []; + sort = 'desc'; + orderby = 'creation_date'; + + @Output() sortChange = new EventEmitter(); + @Output() orderbyChange = new EventEmitter(); + @Output() currentPageChange = new EventEmitter(); + + @ViewChild('table', { static: false }) table: DatatableComponent | undefined; + + constructor(private _router: Router) {} + + ngOnInit() { + this.columns = this.getColumnsConfig(); + } + + handleExpandRow(row: any) { + this.table.rowDetail.toggleExpandRow(row); + } + + handlePageChange(event: any) { + this.currentPage = event.page; + this.currentPageChange.emit(this.currentPage); + } + + onColumnSort(event: any) { + this.sort = event.sorts[0].dir; + this.orderby = event.sorts[0].prop; + this.sortChange.emit({ sort: this.sort, orderby: this.orderby }); + } + + onRowClick(row: any) { + // TODO: ajouter au chemin 'discussions' une fois que la PR https://github.com/PnX-SI/GeoNature/pull/3169 a été reviewed et mergé + this._router.navigate(['/synthese', 'occurrence', row.id_synthese]); + } + + getColumnsConfig() { + return [ + { prop: 'creation_date', name: 'Date commentaire', sortable: true }, + { prop: 'user.nom_complet', name: 'Auteur', sortable: true }, + { prop: 'content', name: 'Contenu', sortable: true }, + { prop: 'observation', name: 'Observation', sortable: false, maxWidth: 500 }, + ]; + } +} diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.html b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.html new file mode 100644 index 0000000000..f02b2c2902 --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.html @@ -0,0 +1,6 @@ + + Mes discussions uniquement + diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.scss b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.ts b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.ts new file mode 100644 index 0000000000..8ea32fb8fc --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions-toggle/home-discussions-toggle.component.ts @@ -0,0 +1,18 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { GN2CommonModule } from '@geonature_common/GN2Common.module'; + +@Component({ + standalone: true, + selector: 'pnx-home-discussions-toggle', + templateUrl: './home-discussions-toggle.component.html', + styleUrls: ['./home-discussions-toggle.component.scss'], + imports: [GN2CommonModule], +}) +export class HomeDiscussionsToggleComponent { + @Input() isChecked: boolean = false; + @Output() toggle = new EventEmitter(); + + onToggle(event: any): void { + this.toggle.emit(event.checked); + } +} diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions.component.html b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.html new file mode 100644 index 0000000000..b98d5b77c1 --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.html @@ -0,0 +1,16 @@ +
+ +
+ diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions.component.scss b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/components/home-content/home-discussions/home-discussions.component.ts b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.ts new file mode 100644 index 0000000000..637bb33d0c --- /dev/null +++ b/frontend/src/app/components/home-content/home-discussions/home-discussions.component.ts @@ -0,0 +1,124 @@ +import { Component, OnInit, ViewChild, OnDestroy, Input } from '@angular/core'; +import { SyntheseDataService } from '@geonature_common/form/synthese-form/synthese-data.service'; +import { ConfigService } from '@geonature/services/config.service'; +import { DatatableComponent } from '@swimlane/ngx-datatable'; +import { DatePipe } from '@angular/common'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { HomeDiscussionsTableComponent } from './home-discussions-table/home-discussions-table.component'; +import { HomeDiscussionsToggleComponent } from './home-discussions-toggle/home-discussions-toggle.component'; + +@Component({ + standalone: true, + selector: 'pnx-home-discussions', + templateUrl: './home-discussions.component.html', + styleUrls: ['./home-discussions.component.scss'], + providers: [DatePipe], + imports: [HomeDiscussionsTableComponent, HomeDiscussionsToggleComponent], +}) +export class HomeDiscussionsComponent implements OnInit, OnDestroy { + @ViewChild('table') table: DatatableComponent; + + discussions = []; + currentPage = 1; + perPage = 2; + totalPages = 1; + totalRows = 0; + totalFilteredRows = 0; + myReportsOnly = false; + sort = 'desc'; + orderby = 'creation_date'; + private destroy$ = new Subject(); + + constructor( + private syntheseApi: SyntheseDataService, + public config: ConfigService, + private datePipe: DatePipe + ) {} + + async ngOnInit() { + this.getDiscussions(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + getDiscussions() { + const params = this.buildQueryParams(); + this.syntheseApi + .getReports(params.toString()) + .pipe(takeUntil(this.destroy$)) + .subscribe((response) => { + this.setDiscussions(response); + }); + } + + buildQueryParams(): URLSearchParams { + const params = new URLSearchParams(); + params.set('type', 'discussion'); + params.set('sort', this.sort); + params.set('orderby', this.orderby); + params.set('page', this.currentPage.toString()); + params.set('per_page', this.perPage.toString()); + params.set('my_reports', this.myReportsOnly.toString()); + return params; + } + + setDiscussions(data: any) { + this.discussions = this.transformDiscussions(data.items); + this.totalRows = data.total; + this.totalPages = data.pages; + this.totalFilteredRows = data.total_filtered; + } + + transformDiscussions(items: any[]): any[] { + return items.map((item) => ({ + ...item, + observation: this.formatObservation(item.synthese), + })); + } + + formatObservation(synthese: any): string { + return ` + Nom Cité: ${synthese.nom_cite || 'N/A'}
+ Observateurs: ${synthese.observers || 'N/A'}
+ Date Observation: ${ + this.formatDateRange(synthese.date_min, synthese.date_max) || 'N/A' + } + `; + } + + toggleMyReports(isMyReports: boolean) { + this.myReportsOnly = isMyReports; + this.currentPage = 1; + this.getDiscussions(); + } + + formatDateRange(dateMin: string, dateMax: string): string { + if (!dateMin) return 'N/A'; + + const formattedDateMin = this.datePipe.transform(dateMin, 'dd-MM-yyyy'); + const formattedDateMax = this.datePipe.transform(dateMax, 'dd-MM-yyyy'); + + if (!dateMax || formattedDateMin === formattedDateMax) { + return formattedDateMin || 'N/A'; + } + + return `${formattedDateMin} - ${formattedDateMax}`; + } + + // Event handlers for updates from the child component + // NOTES: utilisation de service à la place ? + onSortChange(sortAndOrberby: { sort: string; orderby: string }) { + this.sort = sortAndOrberby.sort; + this.orderby = sortAndOrberby.orderby; + this.getDiscussions(); + } + + onCurrentPageChange(newPage: number) { + this.currentPage = newPage; + this.getDiscussions(); + } +} diff --git a/frontend/src/app/shared/discussionCardModule/discussion-card.component.ts b/frontend/src/app/shared/discussionCardModule/discussion-card.component.ts index a04ac732b8..c50eda5448 100644 --- a/frontend/src/app/shared/discussionCardModule/discussion-card.component.ts +++ b/frontend/src/app/shared/discussionCardModule/discussion-card.component.ts @@ -122,8 +122,8 @@ export class DiscussionCardComponent implements OnInit, OnChanges { * get all discussion by module and type */ getDiscussions() { - const params = `idSynthese=${this.idSynthese}&type=discussion&sort=${this.sort}`; - this._syntheseDataService.getReports(params).subscribe((response) => { + const params = `type=discussion&sort=${this.sort}`; + this._syntheseDataService.getReports(params, this.idSynthese).subscribe((response) => { this.setDiscussions(response); }); } diff --git a/frontend/src/app/syntheseModule/synthese.component.ts b/frontend/src/app/syntheseModule/synthese.component.ts index e1b4108059..0d4b52bae2 100644 --- a/frontend/src/app/syntheseModule/synthese.component.ts +++ b/frontend/src/app/syntheseModule/synthese.component.ts @@ -110,6 +110,7 @@ export class SyntheseComponent implements OnInit { } // Store geojson + // TODO: [IMPROVE][PAGINATE] this._mapListService.geojsonData = this.simplifyGeoJson(cloneDeep(data)); this.formatDataForTable(data); @@ -132,6 +133,7 @@ export class SyntheseComponent implements OnInit { /** table data expect an array obs observation * the geojson get from API is a list of features whith an observation list */ + // TODO: [IMPROVE][PAGINATE] data in datable is formated here formatDataForTable(geojson) { this._mapListService.tableData = []; const idSynthese = new Set();