diff --git a/backend/geonature/core/gn_synthese/routes.py b/backend/geonature/core/gn_synthese/routes.py index 9f543633b1..1e341a4ea9 100644 --- a/backend/geonature/core/gn_synthese/routes.py +++ b/backend/geonature/core/gn_synthese/routes.py @@ -15,6 +15,7 @@ jsonify, g, ) +from geonature.core.gn_commons.schemas import TMedias 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 @@ -1466,6 +1467,28 @@ def notify_new_report_change(synthese, user, id_roles, content): ) +@routes.route("/taxon_medias/", methods=["GET"]) +@login_required +@permissions.check_cruved_scope("R", module_code="SYNTHESE") +@json_resp +def taxon_medias(cd_ref): + per_page = request.args.get("per_page", 10, int) + page = request.args.get("page", 1, int) + + query = select(TMedias).join(Synthese.medias).order_by(TMedias.meta_create_date.desc()) + + taxref_cd_nom_list = db.session.scalars(select(Taxref.cd_nom).where(Taxref.cd_ref == cd_ref)) + query = query.where(Synthese.cd_nom.in_(taxref_cd_nom_list)) + + pagination = DB.paginate(query, page=page, per_page=per_page) + return { + "total": pagination.total, + "page": pagination.page, + "per_page": pagination.per_page, + "items": [media.as_dict() for media in pagination.items], + } + + @routes.route("/reports/", methods=["PUT"]) @login_required @json_resp diff --git a/backend/geonature/tests/test_synthese.py b/backend/geonature/tests/test_synthese.py index 84f604d07b..057d6bf19f 100644 --- a/backend/geonature/tests/test_synthese.py +++ b/backend/geonature/tests/test_synthese.py @@ -1836,3 +1836,17 @@ def test_export_observations_sensitive_excluded( # No feature accessible because sensitive data excluded if # the user has no right to see it assert len(response.json["features"]) == 0 + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestMediaTaxon: + def test_taxon_medias(self, synthese_read_permissions, users): + set_logged_user(self.client, users["self_user"]) + synthese_read_permissions(users["self_user"], None, sensitivity_filter=True) + + cd_ref = db.session.scalar(select(Taxref.cd_ref)) + + response = self.client.get(url_for("gn_synthese.taxon_medias", cd_ref=cd_ref)) + + assert response.status_code == 200 + assert isinstance(response.json["items"], list) diff --git a/backend/geonature/utils/config_schema.py b/backend/geonature/utils/config_schema.py index 5ab938a84b..78d42bcd6d 100644 --- a/backend/geonature/utils/config_schema.py +++ b/backend/geonature/utils/config_schema.py @@ -276,6 +276,7 @@ class TaxonSheet(Schema): # -------------------------------------------------------------------- # SYNTHESE - TAXON_SHEET ENABLE_PROFILE = fields.Boolean(load_default=True) + ENABLE_MEDIA = fields.Boolean(load_default=True) class Synthese(Schema): diff --git a/frontend/src/app/GN2CommonModule/form/media/display-medias.component.html b/frontend/src/app/GN2CommonModule/form/media/display-medias.component.html index 2d70eefe8e..22ebfd7942 100644 --- a/frontend/src/app/GN2CommonModule/form/media/display-medias.component.html +++ b/frontend/src/app/GN2CommonModule/form/media/display-medias.component.html @@ -31,7 +31,7 @@
{ + return this._api.get(`${this.config.API_ENDPOINT}/synthese/taxon_medias/${cdRef}`, { + params, + }); + } + getTaxaCount(params = {}) { let queryString = new HttpParams(); for (let key in params) { diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.html b/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.html new file mode 100644 index 0000000000..b1e6b5a437 --- /dev/null +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.html @@ -0,0 +1,55 @@ +
+

No media

+
+
+
+
+ +
+ Miniature +
+ {{ ms.icon(media) }} +
+
+
+
+ + +
+ +
+ +
+
diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.scss b/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.scss new file mode 100644 index 0000000000..65d2f8de5b --- /dev/null +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.scss @@ -0,0 +1,62 @@ +.MediaContainer { + display: flex; + justify-content: space-between; + width: 100%; + + &--empty { + flex: 1; + justify-content: center; + align-items: center; + } + + &__view, + &__list { + width: 50%; + } + + &__view { + overflow: hidden; + align-content: center; + } + + &__list { + display: flex; + flex-direction: column; + justify-content: space-between; + // flex: 1; + min-width: 0; + + .List { + display: flex; + flex-flow: row wrap; + gap: 2rem; + margin: 1rem; + justify-content: start; + &__item { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + + .Media { + width: 5rem; + height: 5rem; + color: rgba(0, 0, 0, 0.87); + display: flex; + justify-content: center; + align-items: center; + border: 3px solid rgba(0, 0, 0, 0.125); + border-radius: 8px; + &--thumbnail { + height: 7rem; + width: auto; + border-color: rgba(0, 0, 0, 0); + } + &--selected { + border-color: var(--purple) !important; + } + } + } + } + } +} diff --git a/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.ts b/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.ts new file mode 100644 index 0000000000..753d0fc706 --- /dev/null +++ b/frontend/src/app/syntheseModule/taxon-sheet/tab-media/tab-media.component.ts @@ -0,0 +1,82 @@ +import { Component, OnInit } from '@angular/core'; +import { Taxon } from '@geonature_common/form/taxonomy/taxonomy.component'; +import { MediaService } from '@geonature_common/service/media.service'; +import { TaxonSheetService } from '../taxon-sheet.service'; +import { GN2CommonModule } from '@geonature_common/GN2Common.module'; +import { CommonModule } from '@angular/common'; +import { PageEvent } from '@angular/material/paginator'; +import { SyntheseDataService } from '@geonature_common/form/synthese-form/synthese-data.service'; + +export interface Pagination { + totalItems: number; + currentPage: number; + perPage: number; +} + +export const DEFAULT_PAGINATION: Pagination = { + totalItems: 0, + currentPage: 0, + perPage: 10, +}; + +@Component({ + standalone: true, + selector: 'pnx-tab-media', + templateUrl: './tab-media.component.html', + styleUrls: ['./tab-media.component.scss'], + imports: [GN2CommonModule, CommonModule], +}) +export class TabMediaComponent implements OnInit { + public medias: any[] = []; + public selectedMedia: any = {}; + taxon: Taxon | null = null; + pagination: Pagination = DEFAULT_PAGINATION; + + constructor( + public ms: MediaService, + private _tss: TaxonSheetService, + private _syntheseDataService: SyntheseDataService + ) {} + + ngOnInit() { + this._tss.taxon.subscribe((taxon) => { + this.taxon = taxon; + if (!this.taxon) { + this.medias = []; + this.selectedMedia = {}; + this.pagination = DEFAULT_PAGINATION; + return; + } + this.loadMedias(); + }); + } + + loadMedias() { + this._syntheseDataService + .getTaxonMedias(this.taxon.cd_ref, { + page: this.pagination.currentPage + 1, + per_page: this.pagination.perPage, + }) + .subscribe((response) => { + this.medias = response.items; + this.pagination = { + totalItems: response.total, + currentPage: response.page - 1, + perPage: response.per_page, + }; + if (!this.medias.some((media) => media.id_media == this.selectedMedia.id_media)) { + this.selectedMedia = this.medias[0]; + } + }); + } + + selectMedia(media: any) { + this.selectedMedia = media; + } + + onPageChange(event: PageEvent) { + this.pagination.currentPage = event.pageIndex; + this.pagination.perPage = event.pageSize; + this.loadMedias(); + } +} 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 66b098d819..976a3f65e0 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 @@ -9,6 +9,7 @@ import { ConfigService } from '@geonature/services/config.service'; import { Observable } from 'rxjs'; import { TabGeographicOverviewComponent } from './tab-geographic-overview/tab-geographic-overview.component'; import { TabProfileComponent } from './tab-profile/tab-profile.component'; +import { TabMediaComponent } from './tab-media/tab-media.component'; interface Tab { label: string; @@ -30,6 +31,12 @@ export const ALL_TAXON_SHEET_ADVANCED_INFOS_ROUTES: Array = [ configEnabledField: 'ENABLE_PROFILE', component: TabProfileComponent, }, + { + label: 'Médias', + path: 'media', + configEnabledField: 'ENABLE_MEDIA', + component: TabMediaComponent, + }, ]; @Injectable({