diff --git a/cms/config/sync/admin-role.strapi-author.json b/cms/config/sync/admin-role.strapi-author.json index 870c66ce..b61836e0 100644 --- a/cms/config/sync/admin-role.strapi-author.json +++ b/cms/config/sync/admin-role.strapi-author.json @@ -861,8 +861,7 @@ "protection_coverage_stats", "totalTerrestrialArea", "name_es", - "name_fr", - "mpaa_fully_highly_protected_area" + "name_fr" ], "locales": [] }, @@ -901,8 +900,7 @@ "protection_coverage_stats", "totalTerrestrialArea", "name_es", - "name_fr", - "mpaa_fully_highly_protected_area" + "name_fr" ], "locales": [] }, @@ -926,8 +924,7 @@ "protection_coverage_stats", "totalTerrestrialArea", "name_es", - "name_fr", - "mpaa_fully_highly_protected_area" + "name_fr" ], "locales": [] }, diff --git a/cms/config/sync/admin-role.strapi-editor.json b/cms/config/sync/admin-role.strapi-editor.json index 34d7653c..43043c16 100644 --- a/cms/config/sync/admin-role.strapi-editor.json +++ b/cms/config/sync/admin-role.strapi-editor.json @@ -848,8 +848,7 @@ "protection_coverage_stats", "totalTerrestrialArea", "name_es", - "name_fr", - "mpaa_fully_highly_protected_area" + "name_fr" ], "locales": [] }, @@ -884,8 +883,7 @@ "protection_coverage_stats", "totalTerrestrialArea", "name_es", - "name_fr", - "mpaa_fully_highly_protected_area" + "name_fr" ], "locales": [] }, @@ -907,8 +905,7 @@ "protection_coverage_stats", "totalTerrestrialArea", "name_es", - "name_fr", - "mpaa_fully_highly_protected_area" + "name_fr" ], "locales": [] }, diff --git a/cms/config/sync/admin-role.strapi-super-admin.json b/cms/config/sync/admin-role.strapi-super-admin.json index 77cf8503..8f558c8e 100644 --- a/cms/config/sync/admin-role.strapi-super-admin.json +++ b/cms/config/sync/admin-role.strapi-super-admin.json @@ -1468,11 +1468,11 @@ "location", "wdpaid", "mpaa_protection_level", - "is_child", "iucn_category", "designation", "environment", - "coverage" + "coverage", + "parent" ] }, "conditions": [], @@ -1501,11 +1501,11 @@ "location", "wdpaid", "mpaa_protection_level", - "is_child", "iucn_category", "designation", "environment", - "coverage" + "coverage", + "parent" ] }, "conditions": [], @@ -1527,11 +1527,11 @@ "location", "wdpaid", "mpaa_protection_level", - "is_child", "iucn_category", "designation", "environment", - "coverage" + "coverage", + "parent" ] }, "conditions": [], @@ -2246,4 +2246,4 @@ "actionParameters": {} } ] -} \ No newline at end of file +} diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##location.location.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##location.location.json index d47d03ea..fec12c21 100644 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##location.location.json +++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##location.location.json @@ -316,16 +316,6 @@ { "name": "name", "size": 6 - }, - { - "name": "name_es", - "size": 6 - } - ], - [ - { - "name": "name_fr", - "size": 6 } ], [ @@ -358,12 +348,6 @@ "size": 6 } ], - [ - { - "name": "mpaa_fully_highly_protected_area", - "size": 4 - } - ], [ { "name": "marine_bounds", @@ -380,6 +364,20 @@ { "name": "mpaa_protection_level_stat", "size": 6 + }, + { + "name": "name_es", + "size": 6 + } + ], + [ + { + "name": "name_fr", + "size": 6 + }, + { + "name": "mpaa_fully_highly_protected_area", + "size": 4 } ] ] diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpa.mpa.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpa.mpa.json deleted file mode 100644 index 4883beab..00000000 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpa.mpa.json +++ /dev/null @@ -1,392 +0,0 @@ -{ - "key": "plugin_content_manager_configuration_content_types::api::mpa.mpa", - "value": { - "uid": "api::mpa.mpa", - "settings": { - "bulkable": true, - "filterable": true, - "searchable": true, - "pageSize": 10, - "mainField": "name", - "defaultSortBy": "name", - "defaultSortOrder": "ASC" - }, - "metadatas": { - "id": { - "edit": {}, - "list": { - "label": "id", - "searchable": true, - "sortable": true - } - }, - "name": { - "edit": { - "label": "name", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "name", - "searchable": true, - "sortable": true - } - }, - "area": { - "edit": { - "label": "area", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "area", - "searchable": true, - "sortable": true - } - }, - "year": { - "edit": { - "label": "year", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "year", - "searchable": true, - "sortable": true - } - }, - "protection_status": { - "edit": { - "label": "protection_status", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "slug" - }, - "list": { - "label": "protection_status", - "searchable": true, - "sortable": true - } - }, - "bbox": { - "edit": { - "label": "bbox", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "bbox", - "searchable": false, - "sortable": false - } - }, - "children": { - "edit": { - "label": "children", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "name" - }, - "list": { - "label": "children", - "searchable": false, - "sortable": false - } - }, - "data_source": { - "edit": { - "label": "data_source", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "slug" - }, - "list": { - "label": "data_source", - "searchable": true, - "sortable": true - } - }, - "mpaa_establishment_stage": { - "edit": { - "label": "mpaa_establishment_stage", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "slug" - }, - "list": { - "label": "mpaa_establishment_stage", - "searchable": true, - "sortable": true - } - }, - "location": { - "edit": { - "label": "location", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "code" - }, - "list": { - "label": "location", - "searchable": true, - "sortable": true - } - }, - "wdpaid": { - "edit": { - "label": "wdpaid", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "wdpaid", - "searchable": true, - "sortable": true - } - }, - "mpaa_protection_level": { - "edit": { - "label": "mpaa_protection_level", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "slug" - }, - "list": { - "label": "mpaa_protection_level", - "searchable": true, - "sortable": true - } - }, - "is_child": { - "edit": { - "label": "is_child", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "is_child", - "searchable": true, - "sortable": true - } - }, - "iucn_category": { - "edit": { - "label": "iucn_category", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "slug" - }, - "list": { - "label": "iucn_category", - "searchable": true, - "sortable": true - } - }, - "designation": { - "edit": { - "label": "designation", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "designation", - "searchable": true, - "sortable": true - } - }, - "environment": { - "edit": { - "label": "environment", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "name" - }, - "list": { - "label": "environment", - "searchable": true, - "sortable": true - } - }, - "createdAt": { - "edit": { - "label": "createdAt", - "description": "", - "placeholder": "", - "visible": false, - "editable": true - }, - "list": { - "label": "createdAt", - "searchable": true, - "sortable": true - } - }, - "updatedAt": { - "edit": { - "label": "updatedAt", - "description": "", - "placeholder": "", - "visible": false, - "editable": true - }, - "list": { - "label": "updatedAt", - "searchable": true, - "sortable": true - } - }, - "createdBy": { - "edit": { - "label": "createdBy", - "description": "", - "placeholder": "", - "visible": false, - "editable": true, - "mainField": "firstname" - }, - "list": { - "label": "createdBy", - "searchable": true, - "sortable": true - } - }, - "updatedBy": { - "edit": { - "label": "updatedBy", - "description": "", - "placeholder": "", - "visible": false, - "editable": true, - "mainField": "firstname" - }, - "list": { - "label": "updatedBy", - "searchable": true, - "sortable": true - } - } - }, - "layouts": { - "list": [ - "id", - "name", - "area", - "year" - ], - "edit": [ - [ - { - "name": "name", - "size": 6 - }, - { - "name": "area", - "size": 4 - } - ], - [ - { - "name": "year", - "size": 4 - }, - { - "name": "protection_status", - "size": 6 - } - ], - [ - { - "name": "wdpaid", - "size": 6 - } - ], - [ - { - "name": "bbox", - "size": 12 - } - ], - [ - { - "name": "children", - "size": 6 - }, - { - "name": "data_source", - "size": 6 - } - ], - [ - { - "name": "mpaa_establishment_stage", - "size": 6 - }, - { - "name": "location", - "size": 6 - } - ], - [ - { - "name": "mpaa_protection_level", - "size": 6 - }, - { - "name": "is_child", - "size": 4 - } - ], - [ - { - "name": "designation", - "size": 6 - }, - { - "name": "iucn_category", - "size": 6 - } - ], - [ - { - "name": "environment", - "size": 6 - } - ] - ] - } - }, - "type": "object", - "environment": null, - "tag": null -} \ No newline at end of file diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpaa-establishment-stage-stat.mpaa-establishment-stage-stat.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpaa-establishment-stage-stat.mpaa-establishment-stage-stat.json deleted file mode 100644 index baa0618d..00000000 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpaa-establishment-stage-stat.mpaa-establishment-stage-stat.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "key": "plugin_content_manager_configuration_content_types::api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat", - "value": { - "uid": "api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat", - "settings": { - "bulkable": true, - "filterable": true, - "searchable": true, - "pageSize": 10, - "mainField": "id", - "defaultSortBy": "id", - "defaultSortOrder": "ASC" - }, - "metadatas": { - "id": { - "edit": {}, - "list": { - "label": "id", - "searchable": true, - "sortable": true - } - }, - "location": { - "edit": { - "label": "location", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "code" - }, - "list": { - "label": "location", - "searchable": true, - "sortable": true - } - }, - "mpaa_establishment_stage": { - "edit": { - "label": "mpaa_establishment_stage", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "slug" - }, - "list": { - "label": "mpaa_establishment_stage", - "searchable": true, - "sortable": true - } - }, - "protection_status": { - "edit": { - "label": "protection_status", - "description": "", - "placeholder": "", - "visible": true, - "editable": true, - "mainField": "slug" - }, - "list": { - "label": "protection_status", - "searchable": true, - "sortable": true - } - }, - "year": { - "edit": { - "label": "year", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "year", - "searchable": true, - "sortable": true - } - }, - "area": { - "edit": { - "label": "area", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "area", - "searchable": true, - "sortable": true - } - }, - "createdAt": { - "edit": { - "label": "createdAt", - "description": "", - "placeholder": "", - "visible": false, - "editable": true - }, - "list": { - "label": "createdAt", - "searchable": true, - "sortable": true - } - }, - "updatedAt": { - "edit": { - "label": "updatedAt", - "description": "", - "placeholder": "", - "visible": false, - "editable": true - }, - "list": { - "label": "updatedAt", - "searchable": true, - "sortable": true - } - }, - "createdBy": { - "edit": { - "label": "createdBy", - "description": "", - "placeholder": "", - "visible": false, - "editable": true, - "mainField": "firstname" - }, - "list": { - "label": "createdBy", - "searchable": true, - "sortable": true - } - }, - "updatedBy": { - "edit": { - "label": "updatedBy", - "description": "", - "placeholder": "", - "visible": false, - "editable": true, - "mainField": "firstname" - }, - "list": { - "label": "updatedBy", - "searchable": true, - "sortable": true - } - } - }, - "layouts": { - "list": [ - "id", - "location", - "mpaa_establishment_stage", - "protection_status" - ], - "edit": [ - [ - { - "name": "location", - "size": 6 - }, - { - "name": "mpaa_establishment_stage", - "size": 6 - } - ], - [ - { - "name": "protection_status", - "size": 6 - }, - { - "name": "year", - "size": 4 - } - ], - [ - { - "name": "area", - "size": 4 - } - ] - ] - } - }, - "type": "object", - "environment": null, - "tag": null -} \ No newline at end of file diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpaa-protection-level-stat.mpaa-protection-level-stat.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpaa-protection-level-stat.mpaa-protection-level-stat.json index 2f9a1d56..494862d3 100644 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpaa-protection-level-stat.mpaa-protection-level-stat.json +++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##mpaa-protection-level-stat.mpaa-protection-level-stat.json @@ -138,12 +138,6 @@ } }, "layouts": { - "list": [ - "id", - "location", - "mpaa_protection_level", - "area" - ], "edit": [ [ { @@ -165,6 +159,12 @@ "size": 4 } ] + ], + "list": [ + "id", + "location", + "mpaa_protection_level", + "area" ] } }, diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##pa.pa.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##pa.pa.json index 4be3fbe5..0cb2a6c4 100644 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##pa.pa.json +++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##pa.pa.json @@ -180,20 +180,6 @@ "sortable": true } }, - "is_child": { - "edit": { - "label": "is_child", - "description": "", - "placeholder": "", - "visible": true, - "editable": true - }, - "list": { - "label": "is_child", - "searchable": true, - "sortable": true - } - }, "iucn_category": { "edit": { "label": "iucn_category", @@ -252,6 +238,21 @@ "sortable": true } }, + "parent": { + "edit": { + "label": "parent", + "description": "", + "placeholder": "", + "visible": true, + "editable": true, + "mainField": "name" + }, + "list": { + "label": "parent", + "searchable": true, + "sortable": true + } + }, "createdAt": { "edit": { "label": "createdAt", @@ -377,28 +378,28 @@ ], [ { - "name": "is_child", - "size": 4 + "name": "iucn_category", + "size": 6 }, { - "name": "iucn_category", + "name": "designation", "size": 6 } ], [ { - "name": "designation", + "name": "environment", "size": 6 }, { - "name": "environment", - "size": 6 + "name": "coverage", + "size": 4 } ], [ { - "name": "coverage", - "size": 4 + "name": "parent", + "size": 6 } ] ] diff --git a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##protection-coverage-stat.protection-coverage-stat.json b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##protection-coverage-stat.protection-coverage-stat.json index 617a8093..04982bbf 100644 --- a/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##protection-coverage-stat.protection-coverage-stat.json +++ b/cms/config/sync/core-store.plugin_content_manager_configuration_content_types##api##protection-coverage-stat.protection-coverage-stat.json @@ -243,10 +243,6 @@ { "name": "year", "size": 4 - }, - { - "name": "is_last_year", - "size": 4 } ], [ @@ -272,6 +268,12 @@ "name": "oecms", "size": 4 }, + { + "name": "is_last_year", + "size": 4 + } + ], + [ { "name": "global_contribution", "size": 4 diff --git a/cms/export_custom-db.json b/cms/export_custom-db.json index 221fa0b8..54d63656 100644 --- a/cms/export_custom-db.json +++ b/cms/export_custom-db.json @@ -1311,7 +1311,7 @@ "updatedBy": null } }, - "api::mpa.mpa": { + "api::pa.pa": { "1": { "id": 1, "wdpaid": "555635929", diff --git a/cms/src/api/environment/content-types/environment/schema.json b/cms/src/api/environment/content-types/environment/schema.json index a1746235..3d2ce82a 100644 --- a/cms/src/api/environment/content-types/environment/schema.json +++ b/cms/src/api/environment/content-types/environment/schema.json @@ -4,7 +4,8 @@ "info": { "singularName": "environment", "pluralName": "environments", - "displayName": "Environment" + "displayName": "Environment", + "description": "" }, "options": { "draftAndPublish": false @@ -23,7 +24,7 @@ }, "type": "string", "required": true, - "unique": true + "unique": false }, "slug": { "pluginOptions": { diff --git a/cms/src/api/location/content-types/location/schema.json b/cms/src/api/location/content-types/location/schema.json index e901a634..ef734426 100644 --- a/cms/src/api/location/content-types/location/schema.json +++ b/cms/src/api/location/content-types/location/schema.json @@ -10,31 +10,19 @@ "options": { "draftAndPublish": false }, - "pluginOptions": { - "i18n": { - "localized": true - } - }, + "pluginOptions": {}, "attributes": { "code": { "type": "string", "required": true, "unique": false, "description": "Unique textual identifier for the location, e.g. iso3 code for countries.", - "pluginOptions": { - "i18n": { - "localized": false - } - } + "pluginOptions": {} }, "name": { "type": "string", "required": true, - "pluginOptions": { - "i18n": { - "localized": true - } - } + "pluginOptions": {} }, "totalMarineArea": { "type": "decimal", @@ -49,20 +37,12 @@ 2 ] }, - "pluginOptions": { - "i18n": { - "localized": false - } - } + "pluginOptions": {} }, "type": { "type": "string", "required": true, - "pluginOptions": { - "i18n": { - "localized": false - } - } + "pluginOptions": {} }, "groups": { "type": "relation", @@ -82,11 +62,11 @@ "target": "api::fishing-protection-level-stat.fishing-protection-level-stat", "mappedBy": "location" }, - "mpaa_protection_level_stats": { + "mpaa_protection_level_stat": { "type": "relation", - "relation": "oneToMany", + "relation": "oneToOne", "target": "api::mpaa-protection-level-stat.mpaa-protection-level-stat", - "mappedBy": "location" + "inversedBy": "location" }, "protection_coverage_stats": { "type": "relation", @@ -96,29 +76,30 @@ }, "marine_bounds": { "type": "json", - "pluginOptions": { - "i18n": { - "localized": false - } - } + "pluginOptions": {} }, "totalTerrestrialArea": { - "pluginOptions": { - "i18n": { - "localized": false - } - }, + "pluginOptions": {}, "type": "decimal", "required": true, "min": 0 }, "terrestrial_bounds": { - "pluginOptions": { - "i18n": { - "localized": false - } - }, + "pluginOptions": {}, "type": "json" + }, + "name_es": { + "type": "string", + "required": true + }, + "name_fr": { + "type": "string", + "required": true + }, + "mpaa_fully_highly_protected_area": { + "type": "decimal", + "required": false, + "min": 0 } } } diff --git a/cms/src/api/location/documentation/1.0.0/location.json b/cms/src/api/location/documentation/1.0.0/location.json index f85c9f1b..d0cd577f 100644 --- a/cms/src/api/location/documentation/1.0.0/location.json +++ b/cms/src/api/location/documentation/1.0.0/location.json @@ -253,97 +253,5 @@ ], "operationId": "get/locations/{id}" } - }, - "/locations/{id}/localizations": { - "post": { - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LocationLocalizationResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "tags": [ - "Location" - ], - "parameters": [ - { - "name": "id", - "in": "path", - "description": "", - "deprecated": false, - "required": true, - "schema": { - "type": "number" - } - } - ], - "operationId": "post/locations/{id}/localizations", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LocationLocalizationRequest" - } - } - } - } - } } } diff --git a/cms/src/api/mpa/controllers/mpa.ts b/cms/src/api/mpa/controllers/mpa.ts deleted file mode 100644 index 728a90b8..00000000 --- a/cms/src/api/mpa/controllers/mpa.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * mpa controller - */ - -import { factories } from '@strapi/strapi' - -export default factories.createCoreController('api::mpa.mpa'); diff --git a/cms/src/api/mpa/routes/mpa.ts b/cms/src/api/mpa/routes/mpa.ts deleted file mode 100644 index 759b9b3b..00000000 --- a/cms/src/api/mpa/routes/mpa.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * mpa router - */ - -import { factories } from '@strapi/strapi'; - -export default factories.createCoreRouter('api::mpa.mpa', { - only: ['find', 'findOne'] -}); - diff --git a/cms/src/api/mpa/services/mpa.ts b/cms/src/api/mpa/services/mpa.ts deleted file mode 100644 index 0a44ede0..00000000 --- a/cms/src/api/mpa/services/mpa.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * mpa service - */ - -import { factories } from '@strapi/strapi'; - -export default factories.createCoreService('api::mpa.mpa'); diff --git a/cms/src/api/mpaa-establishment-stage-stat/content-types/mpaa-establishment-stage-stat/schema.json b/cms/src/api/mpaa-establishment-stage-stat/content-types/mpaa-establishment-stage-stat/schema.json deleted file mode 100644 index 681e8be7..00000000 --- a/cms/src/api/mpaa-establishment-stage-stat/content-types/mpaa-establishment-stage-stat/schema.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "kind": "collectionType", - "collectionName": "mpaa_establishment_stage_stats", - "info": { - "singularName": "mpaa-establishment-stage-stat", - "pluralName": "mpaa-establishment-stage-stats", - "displayName": "MPAA Establishment Stage Stats" - }, - "options": { - "draftAndPublish": false - }, - "pluginOptions": {}, - "attributes": { - "location": { - "type": "relation", - "relation": "oneToOne", - "target": "api::location.location" - }, - "mpaa_establishment_stage": { - "type": "relation", - "relation": "oneToOne", - "target": "api::mpaa-establishment-stage.mpaa-establishment-stage" - }, - "protection_status": { - "type": "relation", - "relation": "oneToOne", - "target": "api::protection-status.protection-status" - }, - "year": { - "type": "integer", - "required": true, - "min": 0 - }, - "area": { - "type": "decimal", - "required": true, - "min": 0, - "column": { - "defaultTo": 0, - "type": "decimal", - "args": [12,2] - } - } - } -} diff --git a/cms/src/api/mpaa-establishment-stage-stat/controllers/mpaa-establishment-stage-stat.ts b/cms/src/api/mpaa-establishment-stage-stat/controllers/mpaa-establishment-stage-stat.ts deleted file mode 100644 index 7a0a29c1..00000000 --- a/cms/src/api/mpaa-establishment-stage-stat/controllers/mpaa-establishment-stage-stat.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * mpaa-establishment-stage-stat controller - */ - -import { factories } from '@strapi/strapi' - -export default factories.createCoreController('api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat'); diff --git a/cms/src/api/mpaa-establishment-stage-stat/documentation/1.0.0/mpaa-establishment-stage-stat.json b/cms/src/api/mpaa-establishment-stage-stat/documentation/1.0.0/mpaa-establishment-stage-stat.json deleted file mode 100644 index 4ad255e9..00000000 --- a/cms/src/api/mpaa-establishment-stage-stat/documentation/1.0.0/mpaa-establishment-stage-stat.json +++ /dev/null @@ -1,257 +0,0 @@ -{ - "/mpaa-establishment-stage-stats": { - "get": { - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MpaaEstablishmentStageStatListResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "tags": [ - "Mpaa-establishment-stage-stat" - ], - "parameters": [ - { - "name": "sort", - "in": "query", - "description": "Sort by attributes ascending (asc) or descending (desc)", - "deprecated": false, - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "pagination[withCount]", - "in": "query", - "description": "Return page/pageSize (default: true)", - "deprecated": false, - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "pagination[page]", - "in": "query", - "description": "Page number (default: 0)", - "deprecated": false, - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "pagination[pageSize]", - "in": "query", - "description": "Page size (default: 25)", - "deprecated": false, - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "pagination[start]", - "in": "query", - "description": "Offset value (default: 0)", - "deprecated": false, - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "pagination[limit]", - "in": "query", - "description": "Number of entities to return (default: 25)", - "deprecated": false, - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "fields", - "in": "query", - "description": "Fields to return (ex: title,author)", - "deprecated": false, - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "populate", - "in": "query", - "description": "Relations to return", - "deprecated": false, - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "filters", - "in": "query", - "description": "Filters to apply", - "deprecated": false, - "required": false, - "schema": { - "type": "object" - }, - "style": "deepObject" - }, - { - "name": "locale", - "in": "query", - "description": "Locale to apply", - "deprecated": false, - "required": false, - "schema": { - "type": "string" - } - } - ], - "operationId": "get/mpaa-establishment-stage-stats" - } - }, - "/mpaa-establishment-stage-stats/{id}": { - "get": { - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MpaaEstablishmentStageStatResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "tags": [ - "Mpaa-establishment-stage-stat" - ], - "parameters": [ - { - "name": "id", - "in": "path", - "description": "", - "deprecated": false, - "required": true, - "schema": { - "type": "number" - } - } - ], - "operationId": "get/mpaa-establishment-stage-stats/{id}" - } - } -} diff --git a/cms/src/api/mpaa-establishment-stage-stat/routes/mpaa-establishment-stage-stat.ts b/cms/src/api/mpaa-establishment-stage-stat/routes/mpaa-establishment-stage-stat.ts deleted file mode 100644 index 8467199f..00000000 --- a/cms/src/api/mpaa-establishment-stage-stat/routes/mpaa-establishment-stage-stat.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * mpaa-establishment-stage-stat router - */ - -import { factories } from '@strapi/strapi'; - -export default factories.createCoreRouter('api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat', { - only: ['find', 'findOne'] -}); diff --git a/cms/src/api/mpaa-establishment-stage-stat/services/mpaa-establishment-stage-stat.ts b/cms/src/api/mpaa-establishment-stage-stat/services/mpaa-establishment-stage-stat.ts deleted file mode 100644 index 7656bbf6..00000000 --- a/cms/src/api/mpaa-establishment-stage-stat/services/mpaa-establishment-stage-stat.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * mpaa-establishment-stage-stat service - */ - -import { factories } from '@strapi/strapi'; - -export default factories.createCoreService('api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat'); diff --git a/cms/src/api/mpaa-protection-level-stat/content-types/mpaa-protection-level-stat/schema.json b/cms/src/api/mpaa-protection-level-stat/content-types/mpaa-protection-level-stat/schema.json index f39cf8af..8be64859 100644 --- a/cms/src/api/mpaa-protection-level-stat/content-types/mpaa-protection-level-stat/schema.json +++ b/cms/src/api/mpaa-protection-level-stat/content-types/mpaa-protection-level-stat/schema.json @@ -12,12 +12,6 @@ }, "pluginOptions": {}, "attributes": { - "location": { - "type": "relation", - "relation": "manyToOne", - "target": "api::location.location", - "inversedBy": "mpaa_protection_level_stats" - }, "mpaa_protection_level": { "type": "relation", "relation": "oneToOne", @@ -35,6 +29,15 @@ 2 ] } + }, + "percentage": { + "type": "decimal" + }, + "location": { + "type": "relation", + "relation": "oneToOne", + "target": "api::location.location", + "mappedBy": "mpaa_protection_level_stat" } } } diff --git a/cms/src/api/mpa/content-types/mpa/schema.json b/cms/src/api/pa/content-types/pa/schema.json similarity index 84% rename from cms/src/api/mpa/content-types/mpa/schema.json rename to cms/src/api/pa/content-types/pa/schema.json index eb3f052b..cc071213 100644 --- a/cms/src/api/mpa/content-types/mpa/schema.json +++ b/cms/src/api/pa/content-types/pa/schema.json @@ -1,10 +1,10 @@ { "kind": "collectionType", - "collectionName": "mpas", + "collectionName": "pas", "info": { - "singularName": "mpa", - "pluralName": "mpas", - "displayName": "MPA", + "singularName": "pa", + "pluralName": "pas", + "displayName": "PA", "description": "" }, "options": { @@ -45,7 +45,7 @@ "children": { "type": "relation", "relation": "oneToMany", - "target": "api::mpa.mpa" + "target": "api::pa.pa" }, "data_source": { "type": "relation", @@ -70,11 +70,6 @@ "relation": "oneToOne", "target": "api::mpaa-protection-level.mpaa-protection-level" }, - "is_child": { - "type": "boolean", - "default": false, - "required": true - }, "iucn_category": { "type": "relation", "relation": "oneToOne", @@ -87,6 +82,16 @@ "type": "relation", "relation": "oneToOne", "target": "api::environment.environment" + }, + "coverage": { + "type": "decimal", + "required": true, + "min": 0 + }, + "parent": { + "type": "relation", + "relation": "oneToOne", + "target": "api::pa.pa" } } } diff --git a/cms/src/api/pa/controllers/pa.ts b/cms/src/api/pa/controllers/pa.ts new file mode 100644 index 00000000..a71a251f --- /dev/null +++ b/cms/src/api/pa/controllers/pa.ts @@ -0,0 +1,7 @@ +/** + * pa controller + */ + +import { factories } from '@strapi/strapi' + +export default factories.createCoreController('api::pa.pa'); diff --git a/cms/src/api/mpa/documentation/1.0.0/mpa.json b/cms/src/api/pa/documentation/1.0.0/pa.json similarity index 95% rename from cms/src/api/mpa/documentation/1.0.0/mpa.json rename to cms/src/api/pa/documentation/1.0.0/pa.json index 6821e251..f7919f60 100644 --- a/cms/src/api/mpa/documentation/1.0.0/mpa.json +++ b/cms/src/api/pa/documentation/1.0.0/pa.json @@ -1,5 +1,5 @@ { - "/mpas": { + "/pas": { "get": { "responses": { "200": { @@ -7,7 +7,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MpaListResponse" + "$ref": "#/components/schemas/PaListResponse" } } } @@ -64,7 +64,7 @@ } }, "tags": [ - "Mpa" + "Pa" ], "parameters": [ { @@ -169,10 +169,10 @@ } } ], - "operationId": "get/mpas" + "operationId": "get/pas" } }, - "/mpas/{id}": { + "/pas/{id}": { "get": { "responses": { "200": { @@ -180,7 +180,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MpaResponse" + "$ref": "#/components/schemas/PaResponse" } } } @@ -237,7 +237,7 @@ } }, "tags": [ - "Mpa" + "Pa" ], "parameters": [ { @@ -251,7 +251,7 @@ } } ], - "operationId": "get/mpas/{id}" + "operationId": "get/pas/{id}" } } } diff --git a/cms/src/api/pa/routes/pa.ts b/cms/src/api/pa/routes/pa.ts new file mode 100644 index 00000000..d3f0a9da --- /dev/null +++ b/cms/src/api/pa/routes/pa.ts @@ -0,0 +1,10 @@ +/** + * pa router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::pa.pa', { + only: ['find', 'findOne'] +}); + diff --git a/cms/src/api/pa/services/pa.ts b/cms/src/api/pa/services/pa.ts new file mode 100644 index 00000000..a100b050 --- /dev/null +++ b/cms/src/api/pa/services/pa.ts @@ -0,0 +1,7 @@ +/** + * pa service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::pa.pa'); diff --git a/cms/src/api/protection-coverage-stat/content-types/protection-coverage-stat/schema.json b/cms/src/api/protection-coverage-stat/content-types/protection-coverage-stat/schema.json index 687d1f7b..58dac49d 100644 --- a/cms/src/api/protection-coverage-stat/content-types/protection-coverage-stat/schema.json +++ b/cms/src/api/protection-coverage-stat/content-types/protection-coverage-stat/schema.json @@ -18,29 +18,11 @@ "target": "api::location.location", "inversedBy": "protection_coverage_stats" }, - "protection_status": { - "type": "relation", - "relation": "oneToOne", - "target": "api::protection-status.protection-status" - }, "year": { "type": "integer", "required": true, "min": 0 }, - "cumSumProtectedArea": { - "type": "decimal", - "required": true, - "min": 0, - "column": { - "defaultTo": 0, - "type": "decimal", - "args": [ - 12, - 2 - ] - } - }, "protectedArea": { "type": "decimal", "min": 0, @@ -61,6 +43,22 @@ "type": "relation", "relation": "oneToOne", "target": "api::environment.environment" + }, + "coverage": { + "type": "decimal" + }, + "pas": { + "type": "decimal" + }, + "oecms": { + "type": "decimal" + }, + "is_last_year": { + "type": "boolean", + "default": false + }, + "global_contribution": { + "type": "decimal" } } } diff --git a/cms/types/generated/contentTypes.d.ts b/cms/types/generated/contentTypes.d.ts index 23ff7924..14cbf62b 100644 --- a/cms/types/generated/contentTypes.d.ts +++ b/cms/types/generated/contentTypes.d.ts @@ -1232,6 +1232,7 @@ export interface ApiEnvironmentEnvironment extends Schema.CollectionType { singularName: 'environment'; pluralName: 'environments'; displayName: 'Environment'; + description: ''; }; options: { draftAndPublish: false; @@ -1244,7 +1245,6 @@ export interface ApiEnvironmentEnvironment extends Schema.CollectionType { attributes: { name: Attribute.String & Attribute.Required & - Attribute.Unique & Attribute.SetPluginOptions<{ i18n: { localized: true; @@ -1611,43 +1611,15 @@ export interface ApiLocationLocation extends Schema.CollectionType { options: { draftAndPublish: false; }; - pluginOptions: { - i18n: { - localized: true; - }; - }; attributes: { - code: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }>; - name: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; + code: Attribute.String & Attribute.Required; + name: Attribute.String & Attribute.Required; totalMarineArea: Attribute.Decimal & Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }> & Attribute.SetMinMax<{ min: 0; }>; - type: Attribute.String & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }>; + type: Attribute.String & Attribute.Required; groups: Attribute.Relation< 'api::location.location', 'manyToMany', @@ -1663,9 +1635,9 @@ export interface ApiLocationLocation extends Schema.CollectionType { 'oneToMany', 'api::fishing-protection-level-stat.fishing-protection-level-stat' >; - mpaa_protection_level_stats: Attribute.Relation< + mpaa_protection_level_stat: Attribute.Relation< 'api::location.location', - 'oneToMany', + 'oneToOne', 'api::mpaa-protection-level-stat.mpaa-protection-level-stat' >; protection_coverage_stats: Attribute.Relation< @@ -1673,27 +1645,18 @@ export interface ApiLocationLocation extends Schema.CollectionType { 'oneToMany', 'api::protection-coverage-stat.protection-coverage-stat' >; - marine_bounds: Attribute.JSON & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }>; + marine_bounds: Attribute.JSON; totalTerrestrialArea: Attribute.Decimal & Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; - }> & Attribute.SetMinMax<{ min: 0; }>; - terrestrial_bounds: Attribute.JSON & - Attribute.SetPluginOptions<{ - i18n: { - localized: false; - }; + terrestrial_bounds: Attribute.JSON; + name_es: Attribute.String & Attribute.Required; + name_fr: Attribute.String & Attribute.Required; + mpaa_fully_highly_protected_area: Attribute.Decimal & + Attribute.SetMinMax<{ + min: 0; }>; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; @@ -1709,85 +1672,6 @@ export interface ApiLocationLocation extends Schema.CollectionType { 'admin::user' > & Attribute.Private; - localizations: Attribute.Relation< - 'api::location.location', - 'oneToMany', - 'api::location.location' - >; - locale: Attribute.String; - }; -} - -export interface ApiMpaMpa extends Schema.CollectionType { - collectionName: 'mpas'; - info: { - singularName: 'mpa'; - pluralName: 'mpas'; - displayName: 'MPA'; - description: ''; - }; - options: { - draftAndPublish: false; - }; - attributes: { - name: Attribute.String & Attribute.Required; - area: Attribute.Decimal & - Attribute.Required & - Attribute.SetMinMax<{ - min: 0; - }>; - year: Attribute.Integer & - Attribute.SetMinMax<{ - min: 0; - }>; - protection_status: Attribute.Relation< - 'api::mpa.mpa', - 'oneToOne', - 'api::protection-status.protection-status' - >; - bbox: Attribute.JSON & Attribute.Required; - children: Attribute.Relation<'api::mpa.mpa', 'oneToMany', 'api::mpa.mpa'>; - data_source: Attribute.Relation< - 'api::mpa.mpa', - 'oneToOne', - 'api::data-source.data-source' - >; - mpaa_establishment_stage: Attribute.Relation< - 'api::mpa.mpa', - 'oneToOne', - 'api::mpaa-establishment-stage.mpaa-establishment-stage' - >; - location: Attribute.Relation< - 'api::mpa.mpa', - 'oneToOne', - 'api::location.location' - >; - wdpaid: Attribute.BigInteger; - mpaa_protection_level: Attribute.Relation< - 'api::mpa.mpa', - 'oneToOne', - 'api::mpaa-protection-level.mpaa-protection-level' - >; - is_child: Attribute.Boolean & - Attribute.Required & - Attribute.DefaultTo; - iucn_category: Attribute.Relation< - 'api::mpa.mpa', - 'oneToOne', - 'api::mpa-iucn-category.mpa-iucn-category' - >; - designation: Attribute.String; - environment: Attribute.Relation< - 'api::mpa.mpa', - 'oneToOne', - 'api::environment.environment' - >; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation<'api::mpa.mpa', 'oneToOne', 'admin::user'> & - Attribute.Private; - updatedBy: Attribute.Relation<'api::mpa.mpa', 'oneToOne', 'admin::user'> & - Attribute.Private; }; } @@ -1913,60 +1797,6 @@ export interface ApiMpaaEstablishmentStageMpaaEstablishmentStage }; } -export interface ApiMpaaEstablishmentStageStatMpaaEstablishmentStageStat - extends Schema.CollectionType { - collectionName: 'mpaa_establishment_stage_stats'; - info: { - singularName: 'mpaa-establishment-stage-stat'; - pluralName: 'mpaa-establishment-stage-stats'; - displayName: 'MPAA Establishment Stage Stats'; - }; - options: { - draftAndPublish: false; - }; - attributes: { - location: Attribute.Relation< - 'api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat', - 'oneToOne', - 'api::location.location' - >; - mpaa_establishment_stage: Attribute.Relation< - 'api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat', - 'oneToOne', - 'api::mpaa-establishment-stage.mpaa-establishment-stage' - >; - protection_status: Attribute.Relation< - 'api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat', - 'oneToOne', - 'api::protection-status.protection-status' - >; - year: Attribute.Integer & - Attribute.Required & - Attribute.SetMinMax<{ - min: 0; - }>; - area: Attribute.Decimal & - Attribute.Required & - Attribute.SetMinMax<{ - min: 0; - }>; - createdAt: Attribute.DateTime; - updatedAt: Attribute.DateTime; - createdBy: Attribute.Relation< - 'api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - updatedBy: Attribute.Relation< - 'api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat', - 'oneToOne', - 'admin::user' - > & - Attribute.Private; - }; -} - export interface ApiMpaaProtectionLevelMpaaProtectionLevel extends Schema.CollectionType { collectionName: 'mpaa_protection_levels'; @@ -2041,11 +1871,6 @@ export interface ApiMpaaProtectionLevelStatMpaaProtectionLevelStat draftAndPublish: false; }; attributes: { - location: Attribute.Relation< - 'api::mpaa-protection-level-stat.mpaa-protection-level-stat', - 'manyToOne', - 'api::location.location' - >; mpaa_protection_level: Attribute.Relation< 'api::mpaa-protection-level-stat.mpaa-protection-level-stat', 'oneToOne', @@ -2056,6 +1881,12 @@ export interface ApiMpaaProtectionLevelStatMpaaProtectionLevelStat Attribute.SetMinMax<{ min: 0; }>; + percentage: Attribute.Decimal; + location: Attribute.Relation< + 'api::mpaa-protection-level-stat.mpaa-protection-level-stat', + 'oneToOne', + 'api::location.location' + >; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; createdBy: Attribute.Relation< @@ -2073,6 +1904,82 @@ export interface ApiMpaaProtectionLevelStatMpaaProtectionLevelStat }; } +export interface ApiPaPa extends Schema.CollectionType { + collectionName: 'pas'; + info: { + singularName: 'pa'; + pluralName: 'pas'; + displayName: 'PA'; + description: ''; + }; + options: { + draftAndPublish: false; + }; + attributes: { + name: Attribute.String & Attribute.Required; + area: Attribute.Decimal & + Attribute.Required & + Attribute.SetMinMax<{ + min: 0; + }>; + year: Attribute.Integer & + Attribute.SetMinMax<{ + min: 0; + }>; + protection_status: Attribute.Relation< + 'api::pa.pa', + 'oneToOne', + 'api::protection-status.protection-status' + >; + bbox: Attribute.JSON & Attribute.Required; + children: Attribute.Relation<'api::pa.pa', 'oneToMany', 'api::pa.pa'>; + data_source: Attribute.Relation< + 'api::pa.pa', + 'oneToOne', + 'api::data-source.data-source' + >; + mpaa_establishment_stage: Attribute.Relation< + 'api::pa.pa', + 'oneToOne', + 'api::mpaa-establishment-stage.mpaa-establishment-stage' + >; + location: Attribute.Relation< + 'api::pa.pa', + 'oneToOne', + 'api::location.location' + >; + wdpaid: Attribute.BigInteger; + mpaa_protection_level: Attribute.Relation< + 'api::pa.pa', + 'oneToOne', + 'api::mpaa-protection-level.mpaa-protection-level' + >; + iucn_category: Attribute.Relation< + 'api::pa.pa', + 'oneToOne', + 'api::mpa-iucn-category.mpa-iucn-category' + >; + designation: Attribute.String; + environment: Attribute.Relation< + 'api::pa.pa', + 'oneToOne', + 'api::environment.environment' + >; + coverage: Attribute.Decimal & + Attribute.Required & + Attribute.SetMinMax<{ + min: 0; + }>; + parent: Attribute.Relation<'api::pa.pa', 'oneToOne', 'api::pa.pa'>; + createdAt: Attribute.DateTime; + updatedAt: Attribute.DateTime; + createdBy: Attribute.Relation<'api::pa.pa', 'oneToOne', 'admin::user'> & + Attribute.Private; + updatedBy: Attribute.Relation<'api::pa.pa', 'oneToOne', 'admin::user'> & + Attribute.Private; + }; +} + export interface ApiProtectionCoverageStatProtectionCoverageStat extends Schema.CollectionType { collectionName: 'protection_coverage_stats'; @@ -2091,21 +1998,11 @@ export interface ApiProtectionCoverageStatProtectionCoverageStat 'manyToOne', 'api::location.location' >; - protection_status: Attribute.Relation< - 'api::protection-coverage-stat.protection-coverage-stat', - 'oneToOne', - 'api::protection-status.protection-status' - >; year: Attribute.Integer & Attribute.Required & Attribute.SetMinMax<{ min: 0; }>; - cumSumProtectedArea: Attribute.Decimal & - Attribute.Required & - Attribute.SetMinMax<{ - min: 0; - }>; protectedArea: Attribute.Decimal & Attribute.SetMinMax<{ min: 0; @@ -2116,6 +2013,11 @@ export interface ApiProtectionCoverageStatProtectionCoverageStat 'oneToOne', 'api::environment.environment' >; + coverage: Attribute.Decimal; + pas: Attribute.Decimal; + oecms: Attribute.Decimal; + is_last_year: Attribute.Boolean & Attribute.DefaultTo; + global_contribution: Attribute.Decimal; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; createdBy: Attribute.Relation< @@ -2292,12 +2194,11 @@ declare module '@strapi/types' { 'api::habitat-stat.habitat-stat': ApiHabitatStatHabitatStat; 'api::layer.layer': ApiLayerLayer; 'api::location.location': ApiLocationLocation; - 'api::mpa.mpa': ApiMpaMpa; 'api::mpa-iucn-category.mpa-iucn-category': ApiMpaIucnCategoryMpaIucnCategory; 'api::mpaa-establishment-stage.mpaa-establishment-stage': ApiMpaaEstablishmentStageMpaaEstablishmentStage; - 'api::mpaa-establishment-stage-stat.mpaa-establishment-stage-stat': ApiMpaaEstablishmentStageStatMpaaEstablishmentStageStat; 'api::mpaa-protection-level.mpaa-protection-level': ApiMpaaProtectionLevelMpaaProtectionLevel; 'api::mpaa-protection-level-stat.mpaa-protection-level-stat': ApiMpaaProtectionLevelStatMpaaProtectionLevelStat; + 'api::pa.pa': ApiPaPa; 'api::protection-coverage-stat.protection-coverage-stat': ApiProtectionCoverageStatProtectionCoverageStat; 'api::protection-status.protection-status': ApiProtectionStatusProtectionStatus; 'api::static-indicator.static-indicator': ApiStaticIndicatorStaticIndicator; diff --git a/frontend/orval.config.ts b/frontend/orval.config.ts index 032eb93c..5942e8e3 100644 --- a/frontend/orval.config.ts +++ b/frontend/orval.config.ts @@ -36,7 +36,7 @@ module.exports = { 'Location', 'Habitat', 'Habitat-stat', - 'Mpa', + 'Pa', 'Mpaa-protection-level', 'Mpaa-protection-level-stat', 'Mpaa-establishment-stage', @@ -58,6 +58,7 @@ module.exports = { 'Data-source', 'Static-indicator', 'Contact-detail', + 'Environment', ], }, }, diff --git a/frontend/package.json b/frontend/package.json index e6217948..45116edb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,7 +45,7 @@ "@tailwindcss/line-clamp": "0.4.4", "@tailwindcss/typography": "0.5.9", "@tanstack/react-query": "4.26.1", - "@tanstack/react-table": "^8.9.7", + "@tanstack/react-table": "^8.20.5", "@turf/turf": "^6.5.0", "axios": "1.5.1", "class-variance-authority": "^0.7.0", diff --git a/frontend/src/components/positional-scroll/index.tsx b/frontend/src/components/positional-scroll/index.tsx index a18ba7f4..dce19425 100644 --- a/frontend/src/components/positional-scroll/index.tsx +++ b/frontend/src/components/positional-scroll/index.tsx @@ -1,8 +1,8 @@ -import { PropsWithChildren, useEffect, useState } from 'react'; +import { PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react'; import { cn } from '@/lib/classnames'; -export type ScrollPositions = 'start' | 'middle' | 'end'; +export type ScrollPositions = 'start' | 'middle' | 'end' | 'no-scroll'; export type PositionalScrollProps = PropsWithChildren<{ className?: string; @@ -16,9 +16,49 @@ const PositionalScroll: React.FC = ({ onYScrollPositionChange, children, }) => { + const ref = useRef(); + const [xPosition, setXPosition] = useState('start'); const [yPosition, setYPosition] = useState('start'); + const handleScroll = useCallback(() => { + const target = ref.current; + + const xAtStartPosition = target.scrollLeft === 0; + const xAtEndPosition = target.scrollLeft === target.scrollWidth - target.clientWidth; + + const yAtStartPosition = target.scrollTop === 0; + const yAtEndPosition = target.scrollTop === target.scrollHeight - target.clientHeight; + + let calculatedXPosition: ScrollPositions = 'middle'; + if (xAtStartPosition && xAtEndPosition) { + calculatedXPosition = 'no-scroll'; + } else if (xAtStartPosition) { + calculatedXPosition = 'start'; + } else if (xAtEndPosition) { + calculatedXPosition = 'end'; + } + + let calculatedYPosition: ScrollPositions = 'middle'; + if (yAtStartPosition && yAtEndPosition) { + calculatedYPosition = 'no-scroll'; + } else if (yAtStartPosition) { + calculatedYPosition = 'start'; + } else if (yAtEndPosition) { + calculatedYPosition = 'end'; + } + + setXPosition(calculatedXPosition); + setYPosition(calculatedYPosition); + }, []); + + // TODO: improve this + // Regularly recomputes the scroll position to make sure that if the size of the content has + // changed (independently from the scroll), we're still providing accurate information. + useEffect(() => { + setInterval(() => handleScroll(), 1000); + }, [handleScroll]); + useEffect(() => { if (!onXScrollPositionChange) return; onXScrollPositionChange(xPosition); @@ -29,24 +69,8 @@ const PositionalScroll: React.FC = ({ onYScrollPositionChange(yPosition); }, [onYScrollPositionChange, yPosition]); - const handleScroll = (event) => { - const target = event.target; - - const xAtStartPosition = target.scrollLeft === 0; - const xAtEndPosition = target.scrollLeft === target.scrollWidth - target.clientWidth; - - const yAtStartPosition = target.scrollTop === 0; - const yAtEndPosition = target.scrollTop === target.scrollHeight - target.clientHeight; - - const calculatedXPosition = xAtStartPosition ? 'start' : xAtEndPosition ? 'end' : 'middle'; - const calculatedYPosition = yAtStartPosition ? 'start' : yAtEndPosition ? 'end' : 'middle'; - - if (calculatedXPosition !== xPosition) setXPosition(calculatedXPosition); - if (calculatedYPosition !== yPosition) setYPosition(calculatedYPosition); - }; - return ( -
+
{children}
); diff --git a/frontend/src/containers/homepage/intro/index.tsx b/frontend/src/containers/homepage/intro/index.tsx index 88e0a273..89a1c57a 100644 --- a/frontend/src/containers/homepage/intro/index.tsx +++ b/frontend/src/containers/homepage/intro/index.tsx @@ -11,6 +11,7 @@ import ArrowRight from '@/styles/icons/arrow-right.svg'; import { FCWithMessages } from '@/types'; import { useGetProtectionCoverageStats } from '@/types/generated/protection-coverage-stat'; import { useGetStaticIndicators } from '@/types/generated/static-indicator'; +import { ProtectionCoverageStatListResponseDataItem } from '@/types/generated/strapi.schemas'; type IntroProps = { onScrollClick: () => void; @@ -20,29 +21,35 @@ const Intro: FCWithMessages = ({ onScrollClick }) => { const t = useTranslations('containers.homepage-intro'); const locale = useLocale(); - const { - data: { data: protectionStatsData }, - } = useGetProtectionCoverageStats( - { - locale, - filters: { - location: { - code: 'GLOB', + const { data: protectionStatsData } = + useGetProtectionCoverageStats( + { + locale, + filters: { + location: { + code: 'GLOB', + }, + is_last_year: { + $eq: true, + }, + environment: { + slug: { + $eq: 'marine', + }, + }, }, + populate: 'location,environment', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + 'sort[year]': 'desc', + 'pagination[limit]': 1, }, - populate: 'location', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - 'sort[year]': 'desc', - 'pagination[limit]': -1, - }, - { - query: { - select: ({ data }) => ({ data }), - placeholderData: { data: [] }, - }, - } - ); + { + query: { + select: ({ data }) => data?.[0], + }, + } + ); const { data: protectedTerrestrialInlandAreasData } = useGetStaticIndicators( { @@ -62,26 +69,9 @@ const Intro: FCWithMessages = ({ onScrollClick }) => { const formattedOceanProtectedAreaPercentage = useMemo(() => { if (!protectionStatsData) return null; - const lastProtectionDataYear = Math.max( - ...protectionStatsData.map(({ attributes }) => attributes.year) - ); - - const protectionStats = protectionStatsData.filter( - ({ attributes }) => attributes.year === lastProtectionDataYear - ); - - const totalMarineArea = - protectionStats[0]?.attributes?.location?.data?.attributes?.totalMarineArea; - - const protectedArea = protectionStats.reduce( - (acc, { attributes }) => acc + attributes?.cumSumProtectedArea, - 0 - ); - const coveragePercentage = (protectedArea * 100) / totalMarineArea; - - if (Number.isNaN(coveragePercentage)) return null; - - return formatPercentage(locale, coveragePercentage, { displayPercentageSign: false }); + return formatPercentage(locale, protectionStatsData.attributes.coverage, { + displayPercentageSign: false, + }); }, [locale, protectionStatsData]); return ( diff --git a/frontend/src/containers/map/content/details/helpers.ts b/frontend/src/containers/map/content/details/helpers.ts deleted file mode 100644 index 1a01c4d6..00000000 --- a/frontend/src/containers/map/content/details/helpers.ts +++ /dev/null @@ -1,46 +0,0 @@ -type Row = { [key: string]: unknown }; -type Rows = (Row & { subRows?: Row[] })[]; -type Filters = Record; - -export const applyFilters = (data: Rows, filters: Filters) => { - const filteredData = []; - - for (const row of data) { - let keep = true; // Whether the row is kept or filtered out - let filteredSubRows = row.subRows; - - for (const key in filters) { - // If the filter doesn't have any value, we look at the next filter - if (!filters[key].length) { - continue; - } - - // We apply the filters to the sub rows using recursion - if (filteredSubRows?.length) { - filteredSubRows = applyFilters(filteredSubRows, filters); - } - - // If the row's value doesn't match any of the filter's value and the number of matching sub - // rows (if any) is 0, the row is filtered out - if ( - !filters[key].includes(row[key] as string) && - (!filteredSubRows || filteredSubRows.length === 0) - ) { - keep = false; - break; - } - } - - if (keep) { - // We create a new row to define a new list of sub rows, if any - const newRow = { ...row }; - if (filteredSubRows) { - newRow.subRows = filteredSubRows; - } - - filteredData.push(newRow); - } - } - - return filteredData; -}; diff --git a/frontend/src/containers/map/content/details/index.tsx b/frontend/src/containers/map/content/details/index.tsx index 43a52bc6..d7c24a3f 100644 --- a/frontend/src/containers/map/content/details/index.tsx +++ b/frontend/src/containers/map/content/details/index.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { useRouter } from 'next/router'; @@ -19,7 +19,7 @@ const MapDetails: FCWithMessages = () => { const t = useTranslations('containers.map'); const locale = useLocale(); - const [, setSettings] = useSyncMapContentSettings(); + const [{ tab }, setSettings] = useSyncMapContentSettings(); const { query: { locationCode = 'GLOB' }, } = useRouter(); @@ -50,9 +50,9 @@ const MapDetails: FCWithMessages = () => { } ); - const handleOnCloseClick = () => { + const handleOnCloseClick = useCallback(() => { setSettings((prevSettings) => ({ ...prevSettings, showDetails: false })); - }; + }, [setSettings]); const tablesSettings = useMemo( () => ({ @@ -60,20 +60,48 @@ const MapDetails: FCWithMessages = () => { locationTypes: ['worldwide', 'region'], component: GlobalRegionalTable, title: { - worldwide: t('marine-conservation-national-regional-levels'), - region: t('marine-conservation-location'), - // Fallback to use in case the slug/code isn't defined, in order to prevent crashes - fallback: t('marine-conservation'), + summary: { + worldwide: t('environmental-conservation-national-regional-levels'), + region: t('environmental-conservation-location'), + // Fallback to use in case the slug/code isn't defined, in order to prevent crashes + fallback: t('environmental-conservation'), + }, + marine: { + worldwide: t('marine-conservation-national-regional-levels'), + region: t('marine-conservation-location'), + // Fallback to use in case the slug/code isn't defined, in order to prevent crashes + fallback: t('marine-conservation'), + }, + terrestrial: { + worldwide: t('terrestrial-conservation-national-regional-levels'), + region: t('terrestrial-conservation-location'), + // Fallback to use in case the slug/code isn't defined, in order to prevent crashes + fallback: t('terrestrial-conservation'), + }, }, }, countryHighseas: { locationTypes: ['country', 'highseas'], component: NationalHighSeasTable, title: { - country: t('marine-conservation-location'), - highseas: t('marin-conservation-high-seas'), - // Fallback to use in case the slug/code isn't defined, in order to prevent crashes - fallback: t('marine-conservation'), + summary: { + country: t('environmental-conservation-location'), + highseas: t('environmental-conservation-high-seas'), + // Fallback to use in case the slug/code isn't defined, in order to prevent crashes + fallback: t('environmental-conservation'), + }, + marine: { + country: t('marine-conservation-location'), + highseas: t('marine-conservation-high-seas'), + // Fallback to use in case the slug/code isn't defined, in order to prevent crashes + fallback: t('marine-conservation'), + }, + terrestrial: { + country: t('terrestrial-conservation-location'), + highseas: t('terrestrial-conservation-high-seas'), + // Fallback to use in case the slug/code isn't defined, in order to prevent crashes + fallback: t('terrestrial-conservation'), + }, }, }, }), @@ -88,22 +116,28 @@ const MapDetails: FCWithMessages = () => { ? tablesSettings.worldwideRegion : tablesSettings.countryHighseas; + let locationName = locationsQuery.data?.name; + if (locale === 'es') { + locationName = locationsQuery.data?.name_es; + } + if (locale === 'fr') { + locationName = locationsQuery.data?.name_fr; + } + const parsedTitle = - tableSettings.title[locationsQuery.data?.type]?.replace( - '{location}', - locationsQuery.data?.name - ) || tableSettings.title.fallback; + tableSettings.title[tab][locationsQuery.data?.type]?.replace('{location}', locationName) || + tableSettings.title[tab].fallback; return { title: parsedTitle, component: tableSettings.component, }; - }, [tablesSettings, locationsQuery.data]); + }, [locale, tablesSettings, tab, locationsQuery.data]); return (
- +

{table.title}

+ {pagination.pageIndex + 1 > 2 && ( + + )} + {pagination.pageIndex > 2 && '…'} + {table.getCanPreviousPage() && ( + + )} + + {table.getCanNextPage() && ( + + )} + {table.getPageCount() - (pagination.pageIndex + 1) > 2 && '…'} + {table.getPageCount() - (pagination.pageIndex + 1) > 1 && ( + + )} + +
+
+ ); +}; + +export default Pagination; diff --git a/frontend/src/containers/map/content/details/table/scrolling-indicators/index.tsx b/frontend/src/containers/map/content/details/table/scrolling-indicators/index.tsx index 10dfe8a7..54a10c71 100644 --- a/frontend/src/containers/map/content/details/table/scrolling-indicators/index.tsx +++ b/frontend/src/containers/map/content/details/table/scrolling-indicators/index.tsx @@ -20,7 +20,7 @@ const ScrollingIndicators: React.FC = ({ className, ch return ( - {xScrollPosition !== 'end' && ( + {(xScrollPosition === 'start' || xScrollPosition === 'middle') && ( <> = ({ className, ch )} - {xScrollPosition !== 'start' && ( + {(xScrollPosition === 'middle' || xScrollPosition === 'end') && ( <> diff --git a/frontend/src/containers/map/content/details/table/sorting-button/index.tsx b/frontend/src/containers/map/content/details/table/sorting-button/index.tsx index 928c752f..0fc22f4e 100644 --- a/frontend/src/containers/map/content/details/table/sorting-button/index.tsx +++ b/frontend/src/containers/map/content/details/table/sorting-button/index.tsx @@ -3,8 +3,8 @@ import { ArrowDownNarrowWide, ArrowUpNarrowWide, ArrowUpDown } from 'lucide-reac import { useTranslations } from 'next-intl'; import { Button } from '@/components/ui/button'; -import { GlobalRegionalTableColumns } from '@/containers/map/content/details/tables/global-regional/useColumns'; -import { NationalHighseasTableColumns } from '@/containers/map/content/details/tables/national-highseas/useColumns'; +import { GlobalRegionalTableColumns } from '@/containers/map/content/details/tables/global-regional/hooks'; +import { NationalHighseasTableColumns } from '@/containers/map/content/details/tables/national-highseas/hooks'; import { FCWithMessages } from '@/types'; const BUTTON_CLASSNAMES = '-ml-4'; diff --git a/frontend/src/containers/map/content/details/table/tooltip-button/index.tsx b/frontend/src/containers/map/content/details/table/tooltip-button/index.tsx index 75fdc389..3c369c0f 100644 --- a/frontend/src/containers/map/content/details/table/tooltip-button/index.tsx +++ b/frontend/src/containers/map/content/details/table/tooltip-button/index.tsx @@ -1,8 +1,8 @@ import { Column } from '@tanstack/react-table'; import TooltipButton from '@/components/tooltip-button'; -import type { GlobalRegionalTableColumns } from '@/containers/map/content/details/tables/global-regional/useColumns'; -import type { NationalHighseasTableColumns } from '@/containers/map/content/details/tables/national-highseas/useColumns'; +import type { GlobalRegionalTableColumns } from '@/containers/map/content/details/tables/global-regional/hooks'; +import type { NationalHighseasTableColumns } from '@/containers/map/content/details/tables/national-highseas/hooks'; type TableTooltipButtonProps = { column: diff --git a/frontend/src/containers/map/content/details/tables/global-regional/hooks.tsx b/frontend/src/containers/map/content/details/tables/global-regional/hooks.tsx new file mode 100644 index 00000000..61f6e68c --- /dev/null +++ b/frontend/src/containers/map/content/details/tables/global-regional/hooks.tsx @@ -0,0 +1,474 @@ +import { useMemo } from 'react'; + +import Link from 'next/link'; + +import { AccessorKeyColumnDef, PaginationState, SortingState } from '@tanstack/react-table'; +import { useLocale } from 'next-intl'; +import { useTranslations } from 'next-intl'; + +import FiltersButton from '@/components/filters-button'; +import Icon from '@/components/ui/icon'; +import { PAGES } from '@/constants/pages'; +import HeaderItem from '@/containers/map/content/details/table/header-item'; +import { cellFormatter } from '@/containers/map/content/details/table/helpers'; +import SortingButton from '@/containers/map/content/details/table/sorting-button'; +import TooltipButton from '@/containers/map/content/details/table/tooltip-button'; +import { useMapSearchParams } from '@/containers/map/content/map/sync-settings'; +import Mountain from '@/styles/icons/mountain.svg'; +import Wave from '@/styles/icons/wave.svg'; +import { useGetDataInfos } from '@/types/generated/data-info'; +import { useGetEnvironments } from '@/types/generated/environment'; +import { useGetLocations } from '@/types/generated/location'; +import { useGetProtectionCoverageStats } from '@/types/generated/protection-coverage-stat'; +import { ProtectionCoverageStatListResponseMetaPagination } from '@/types/generated/strapi.schemas'; + +export type GlobalRegionalTableColumns = { + location: { + name: string; + name_es: string; + name_fr: string; + code: string; + mpaa_fully_highly_protected_area: number; + }; + environment: { + name: string; + slug: string; + }; + coverage: number; + protectedArea: number; + pas: number; + oecms: number; + global_contribution: number; +}; + +const TOOLTIP_MAPPING = { + environment: 'environment', + location: 'name-country', + coverage: 'coverage', + pas: 'pas', + oecms: 'oecms', + area: 'protected-area', + fullyHighlyProtected: 'fully-highly-protected', + globalContribution: 'global-contribution', +}; + +const useTooltips = () => { + const locale = useLocale(); + + const { data: dataInfo } = useGetDataInfos( + { locale }, + { + query: { + select: ({ data }) => data, + placeholderData: { data: [] }, + }, + } + ); + + const tooltips = {}; + + Object.entries(TOOLTIP_MAPPING).map(([key, value]) => { + const tooltip = dataInfo.find(({ attributes }) => attributes.slug === value)?.attributes + ?.content; + + if (!tooltip) return; + tooltips[key] = tooltip; + }); + + return tooltips; +}; + +const useFiltersOptions = () => { + const locale = useLocale(); + + const { data: environmentOptions } = useGetEnvironments<{ name: string; value: string }[]>( + { + locale, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['name', 'slug'], + 'pagination[limit]': -1, + }, + { + query: { + select: ({ data }) => + data.map((environment) => ({ + name: environment.attributes.name, + value: environment.attributes.slug, + })), + placeholderData: { data: [] }, + }, + } + ); + + return { + environment: environmentOptions, + }; +}; + +export const useColumns = ( + environment: 'marine' | 'terrestrial' | null, + filters: Record, + onChangeFilters: (newFilters: Record) => void +) => { + const t = useTranslations('containers.map'); + const locale = useLocale(); + + const searchParams = useMapSearchParams(); + const tooltips = useTooltips(); + + const filtersOptions = useFiltersOptions(); + + const columns: AccessorKeyColumnDef[] = useMemo(() => { + let locationNameKey = 'name'; + if (locale === 'es') { + locationNameKey = 'name_es'; + } else if (locale === 'fr') { + locationNameKey = 'name_fr'; + } + + return [ + { + id: `location.${locationNameKey}`, + accessorKey: `location.${locationNameKey}`, + header: ({ column }) => ( + + + {t('name')} + + + ), + cell: ({ row }) => { + const { location, environment } = row.original; + return ( + + {environment.slug === 'marine' && } + {environment.slug === 'terrestrial' && ( + + )} + + {location[locationNameKey]} + + + ); + }, + }, + { + id: 'environment.name', + accessorKey: 'environment.name', + header: ({ column }) => ( + + {!environment && ( + onChangeFilters({ ...filters, [field]: values })} + /> + )} + {t('ecosystem')} + + + ), + cell: ({ row }) => { + const { environment } = row.original; + return {environment.name}; + }, + }, + { + id: 'coverage', + accessorKey: 'coverage', + header: ({ column }) => ( + + + {t('coverage')} + + + ), + cell: ({ row }) => { + const { coverage: value } = row.original; + const formattedCoverage = cellFormatter.percentage(locale, value); + + return ( + + {t.rich('percentage-bold', { + b1: (chunks) => chunks, + b2: (chunks) => {chunks}, + percentage: formattedCoverage, + })} + + ); + }, + }, + { + id: 'protectedArea', + accessorKey: 'protectedArea', + header: ({ column }) => ( + + + {t('area')} + + + ), + cell: ({ row }) => { + const { protectedArea: value } = row.original; + const formattedValue = cellFormatter.area(locale, value); + return {t('area-km2', { area: formattedValue })}; + }, + }, + { + id: 'pas', + accessorKey: 'pas', + header: ({ column }) => ( + + + {t('pas')} + + + ), + cell: ({ row }) => { + const { pas: value } = row.original; + if (Number.isNaN(value)) return t('n-a'); + + const formattedValue = cellFormatter.percentage(locale, value); + return {t('percentage', { percentage: formattedValue })}; + }, + }, + { + id: 'oecms', + accessorKey: 'oecms', + header: ({ column }) => ( + + + {t('oecms')} + + + ), + cell: ({ row }) => { + const { oecms: value } = row.original; + if (Number.isNaN(value)) return t('n-a'); + + const formattedValue = cellFormatter.percentage(locale, value); + return {t('percentage', { percentage: formattedValue })}; + }, + }, + ...(environment === 'marine' + ? [ + { + id: 'location.mpaa_fully_highly_protected_area', + accessorKey: 'location.mpaa_fully_highly_protected_area', + header: ({ column }) => ( + + + {t('fully-highly-protected')} + + + ), + cell: ({ row }) => { + const { location } = row.original; + const formattedValue = cellFormatter.percentage( + locale, + location.mpaa_fully_highly_protected_area + ); + return ( + {t('percentage', { percentage: formattedValue })} + ); + }, + }, + ] + : []), + { + id: 'global_contribution', + accessorKey: 'global_contribution', + header: ({ column }) => ( + + + {t('global-contribution')} + + + ), + cell: ({ row }) => { + const { global_contribution: value } = row.original; + if (!value) return t('no-data'); + const formattedValue = cellFormatter.percentage(locale, value); + return {t('percentage', { percentage: formattedValue })}; + }, + }, + ]; + }, [locale, environment, t, tooltips, searchParams, filters, onChangeFilters, filtersOptions]); + + return columns; +}; + +export const useData = ( + locationCode: string, + environment: 'marine' | 'terrestrial' | null, + sorting: SortingState, + filters: Record, + pagination: PaginationState +) => { + const locale = useLocale(); + + const { data: locationType, isSuccess: isLocationSuccess } = useGetLocations( + { + locale, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['type'], + filters: { + code: locationCode, + }, + }, + { + query: { + select: ({ data }) => data[0]?.attributes.type, + }, + } + ); + + // By default, we always sort by location + let sort = 'location.name:asc,environment.name:asc'; + if (sorting.length > 0) { + sort = `${sorting[0].id}:${sorting[0].desc ? 'desc' : 'asc'}`; + + // In addition to sorting by the column the user asked about, we'll also always sort by + // environment + if (sorting[0].id !== 'environment.name') { + sort = `${sort},environment.name:asc`; + } + } + + const { data } = useGetProtectionCoverageStats< + [GlobalRegionalTableColumns[], ProtectionCoverageStatListResponseMetaPagination] + >( + { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['coverage', 'protectedArea', 'pas', 'oecms', 'global_contribution'], + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + populate: { + location: { + fields: ['name', 'name_es', 'name_fr', 'code', 'mpaa_fully_highly_protected_area'], + populate: { + ...(environment === 'marine' + ? { + mpaa_protection_level_stats: { + fields: ['percentage'], + filters: { + mpaa_protection_level: { + slug: { + $eq: 'fully-highly-protected', + }, + }, + }, + }, + } + : {}), + }, + }, + environment: { + fields: ['slug', 'name', 'locale'], + populate: { + localizations: { + fields: ['name', 'locale'], + }, + }, + }, + }, + filters: { + ...(environment + ? { + environment: { + slug: { + $eq: environment, + }, + }, + } + : {}), + location: { + ...(locationType === 'region' + ? { + groups: { + code: { + $eq: locationCode, + }, + }, + } + : { + type: { + $in: ['country', 'highseas'], + }, + }), + }, + is_last_year: { + $eq: true, + }, + ...Object.entries(filters).reduce((res, [key, values]) => { + if (!values || values.length === 0) { + return res; + } + + const reversePathItems = key.split('.').reverse(); + + return reversePathItems.reduce((res, pathItem, index) => { + if (index === 0) { + return { [pathItem]: { $in: values } }; + } + + return { [pathItem]: res }; + }, {}); + }, {}), + }, + 'pagination[pageSize]': pagination.pageSize, + 'pagination[page]': pagination.pageIndex + 1, + sort, + }, + { + query: { + enabled: isLocationSuccess, + placeholderData: [], + keepPreviousData: true, + select: (data) => { + return [ + data.data?.map(({ attributes }): GlobalRegionalTableColumns => { + const location = attributes.location?.data.attributes; + const environment = attributes.environment?.data.attributes; + + const localizedEnvironment = [ + environment, + ...(environment.localizations.data.map((environment) => environment.attributes) ?? + []), + ].find((data) => data.locale === locale); + + return { + location: { + name: location?.name, + name_es: location?.name_es, + name_fr: location?.name_fr, + code: location.code, + mpaa_fully_highly_protected_area: location.mpaa_fully_highly_protected_area, + }, + environment: { + name: localizedEnvironment.name, + slug: localizedEnvironment.slug, + }, + coverage: attributes.coverage, + protectedArea: attributes.protectedArea, + pas: attributes.pas, + oecms: attributes.oecms, + global_contribution: attributes.global_contribution, + }; + }) ?? [], + data.meta?.pagination ?? {}, + ]; + }, + }, + } + ); + + return data; +}; diff --git a/frontend/src/containers/map/content/details/tables/global-regional/index.tsx b/frontend/src/containers/map/content/details/tables/global-regional/index.tsx index 8d41c1cf..e48c32ac 100644 --- a/frontend/src/containers/map/content/details/tables/global-regional/index.tsx +++ b/frontend/src/containers/map/content/details/tables/global-regional/index.tsx @@ -1,279 +1,86 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/router'; -import { useLocale } from 'next-intl'; +import { SortingState, PaginationState } from '@tanstack/react-table'; +import { usePreviousImmediate } from 'rooks'; +import FiltersButton from '@/components/filters-button'; import TooltipButton from '@/components/tooltip-button'; import Table from '@/containers/map/content/details/table'; -import useColumns from '@/containers/map/content/details/tables/global-regional/useColumns'; +import { useSyncMapContentSettings } from '@/containers/map/sync-settings'; import { FCWithMessages } from '@/types'; -import { useGetLocations } from '@/types/generated/location'; -import type { LocationListResponseDataItem } from '@/types/generated/strapi.schemas'; import SortingButton from '../../table/sorting-button'; +import { useColumns, useData } from './hooks'; + const GlobalRegionalTable: FCWithMessages = () => { const { query: { locationCode = 'GLOB' }, } = useRouter(); - const locale = useLocale(); - const globalLocationQuery = useGetLocations( - { - locale, - filters: { - code: 'GLOB', - }, - }, - { - query: { - select: ({ data }) => data?.[0]?.attributes, - }, - } - ); + const [{ tab }] = useSyncMapContentSettings(); + const previousTab = usePreviousImmediate(tab); - const locationsQuery = useGetLocations( - { - locale, - filters: { - code: locationCode, - }, - }, - { - query: { - queryKey: ['locations', locationCode], - select: ({ data }) => data?.[0]?.attributes, - }, - } - ); + const [filters, setFilters] = useState>({}); - const columns = useColumns(); + const columns = useColumns( + tab === 'marine' || tab === 'terrestrial' ? tab : null, + filters, + setFilters + ); - // Get location data and calculate data to display on the table - const { data: locationsData }: { data: LocationListResponseDataItem[] } = useGetLocations( - { - // We will use the data from the `localizations` field because the models “Protection Coverage - // Stats” and “Mpaa Protection Level Stats” are not localised and their relationship to the - // “Location” model only points to a specific localised version. As such, we're forced to load - // all the locales of the “Location” model and then figure out which version has the relation - // to the other model. - locale: 'en', - filters: - locationsQuery.data?.type === 'region' - ? { - groups: { - code: { - $eq: locationsQuery.data?.code, - }, - }, - } - : { - type: { - $eq: ['country', 'highseas'], - }, - }, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - fields: ['code', 'name', 'type', 'totalMarineArea'], - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - populate: { - // This part is for the English version only - protection_coverage_stats: { - fields: ['cumSumProtectedArea', 'protectedAreasCount', 'year'], - populate: { - protection_status: { - fields: ['slug', 'name'], - }, - }, - }, - mpaa_protection_level_stats: { - fields: ['area'], - populate: { - mpaa_protection_level: { - fields: ['slug', 'name'], - }, - }, - }, - // fishing_protection_level_stats: { - // fields: ['area'], - // populate: { - // fishing_protection_level: { - // fields: ['slug', 'name'], - // }, - // }, - // }, - // This part is for the Spanish and French versions - localizations: { - fields: ['code', 'name', 'type', 'totalMarineArea', 'locale'], - populate: { - protection_coverage_stats: { - fields: ['cumSumProtectedArea', 'protectedAreasCount', 'year'], - populate: { - protection_status: { - fields: ['slug', 'name'], - }, - }, - }, - mpaa_protection_level_stats: { - fields: ['area'], - populate: { - mpaa_protection_level: { - fields: ['slug', 'name'], - }, - }, - }, - // fishing_protection_level_stats: { - // fields: ['area'], - // populate: { - // fishing_protection_level: { - // fields: ['slug', 'name'], - // }, - // }, - // }, - }, - }, + const defaultSorting = useMemo( + () => [ + { + id: columns[0].accessorKey, + desc: false, }, - // populate: '*', - 'pagination[limit]': -1, - }, - { - query: { - select: ({ data }) => data, - placeholderData: { data: [] }, - }, - } + ], + [columns] ); - // Calculate table data - const parsedData = useMemo(() => { - return locationsData.map(({ attributes: location }) => { - const localizedLocation = [ - { ...location, locale: 'en' }, - ...(location.localizations.data.map( - // The types below are wrong. There is definitely an `attributes` key inside - // `localizations`. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - (localization) => localization.attributes - ) ?? []), - ].find((data) => data.locale === locale); - - // Base stats needed for calculations - const protectionCoverageStats = - [ - location.protection_coverage_stats.data, - ...(location.localizations.data.map( - // The types below are wrong. There is definitely an `attributes` key inside - // `localizations`. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - (localization) => localization.attributes.protection_coverage_stats.data - ) ?? []), - ].find((data) => data?.length) ?? []; - - const mpaaProtectionLevelStats = - [ - location.mpaa_protection_level_stats.data, - ...(location.localizations.data.map( - // The types below are wrong. There is definitely an `attributes` key inside - // `localizations`. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - (localization) => localization.attributes.mpaa_protection_level_stats.data - ) ?? []), - ].find((data) => data?.length) ?? []; - - // const fishingProtectionLevelStats = - // [ - // location.fishing_protection_level_stats.data, - // ...(location.localizations.data.map( - // // The types below are wrong. There is definitely an `attributes` key inside - // // `localizations`. - // // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // // @ts-ignore - // (localization) => localization.attributes.fishing_protection_level_stats.data - // ) ?? []), - // ].find((data) => data?.length) ?? []; - - // Find coverage stats data for the last available year in the data - const lastCoverageDataYear = Math.max( - ...protectionCoverageStats.map(({ attributes }) => attributes.year) - ); - const coverageStats = protectionCoverageStats.filter( - ({ attributes }) => attributes.year === lastCoverageDataYear - ); - - // Coverage calculations (MPA + OECM) - const protectedArea = coverageStats.reduce( - (acc, { attributes }) => acc + attributes?.cumSumProtectedArea, - 0 - ); - const coveragePercentage = (protectedArea * 100) / location.totalMarineArea; - - // MPAs calculations - const numMPAs = - coverageStats.find( - ({ attributes }) => attributes?.protection_status?.data?.attributes?.slug === 'mpa' - )?.attributes?.protectedAreasCount || 0; - - // OECMs calculations - const numOECMs = - coverageStats.find( - ({ attributes }) => attributes?.protection_status?.data?.attributes?.slug === 'oecm' - )?.attributes?.protectedAreasCount || 0; - - const percentageMPAs = (numMPAs * 100) / (numMPAs + numOECMs); - const percentageOECMs = (numOECMs * 100) / (numMPAs + numOECMs); - - // Fully/Highly Protected calculations - const fullyHighlyProtected = mpaaProtectionLevelStats.filter( - ({ attributes }) => - attributes?.mpaa_protection_level?.data?.attributes?.slug === 'fully-highly-protected' - ); - const fullyHighlyProtectedArea = fullyHighlyProtected.reduce( - (acc, { attributes }) => acc + attributes?.area, - 0 - ); - const fullyHighlyProtectedAreaPercentage = - (fullyHighlyProtectedArea * 100) / location.totalMarineArea; - - // Highly Protected LFP calculations - // const lfpHighProtected = fishingProtectionLevelStats.filter( - // ({ attributes }) => - // attributes?.fishing_protection_level?.data?.attributes?.slug === 'highly' - // ); - // const lfpHighProtectedArea = lfpHighProtected.reduce( - // (acc, { attributes }) => acc + attributes?.area, - // 0 - // ); - // const lfpHighProtectedPercentage = (lfpHighProtectedArea * 100) / location.totalMarineArea; - - // Global contributions calculations - const globalContributionPercentage = - (protectedArea * 100) / globalLocationQuery?.data?.totalMarineArea; + const [sorting, setSorting] = useState(defaultSorting); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 100, + }); + + const [data, { total }] = useData( + locationCode as string, + tab === 'marine' || tab === 'terrestrial' ? tab : null, + sorting, + filters, + pagination + ); - return { - location: localizedLocation.name, - locationCode: location.code, - coverage: coveragePercentage, - area: protectedArea, - locationType: location.type, - mpas: percentageMPAs, - oecms: percentageOECMs, - fullyHighlyProtected: fullyHighlyProtectedAreaPercentage, - // highlyProtectedLfp: lfpHighProtectedPercentage, - globalContribution: globalContributionPercentage, - }; - }); - }, [locale, globalLocationQuery?.data, locationsData]); + // When the tab changes, we reset the filters and the sorting + useEffect(() => { + if (tab !== previousTab) { + setFilters({}); + setSorting(defaultSorting); + } + }, [tab, previousTab, defaultSorting]); - const tableData = parsedData; + // When the filters or the sorting changes, the page number is reset + useEffect(() => { + setPagination((prevPagination) => ({ ...prevPagination, pageIndex: 0 })); + }, [filters, sorting]); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - return ; + return ( +
+ ); }; GlobalRegionalTable.messages = [ @@ -282,6 +89,7 @@ GlobalRegionalTable.messages = [ // Dependencies of `useColumns` ...SortingButton.messages, ...TooltipButton.messages, + ...FiltersButton.messages, ]; export default GlobalRegionalTable; diff --git a/frontend/src/containers/map/content/details/tables/global-regional/useColumns.tsx b/frontend/src/containers/map/content/details/tables/global-regional/useColumns.tsx deleted file mode 100644 index 4fac4ae6..00000000 --- a/frontend/src/containers/map/content/details/tables/global-regional/useColumns.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { useMemo } from 'react'; - -import Link from 'next/link'; - -import { ColumnDef, SortingFnOption } from '@tanstack/react-table'; -import { useLocale } from 'next-intl'; -import { useTranslations } from 'next-intl'; - -import { PAGES } from '@/constants/pages'; -import HeaderItem from '@/containers/map/content/details/table/header-item'; -import { cellFormatter } from '@/containers/map/content/details/table/helpers'; -import SortingButton from '@/containers/map/content/details/table/sorting-button'; -import TooltipButton from '@/containers/map/content/details/table/tooltip-button'; -import useTooltips from '@/containers/map/content/details/tables/global-regional/useTooltips'; -import { useMapSearchParams } from '@/containers/map/content/map/sync-settings'; - -export type GlobalRegionalTableColumns = { - location: string; - locationCode: string; - coverage: number; - locationType: string; - mpas: number; - oecms: number; - area: number; - fullyHighlyProtected: number; - highlyProtectedLfp: number; - globalContribution: number; -}; - -const useColumns = () => { - const t = useTranslations('containers.map'); - const locale = useLocale(); - - const searchParams = useMapSearchParams(); - const tooltips = useTooltips(); - - const columns: ColumnDef[] = useMemo(() => { - return [ - { - accessorKey: 'location', - sortingFn: 'localeStringCompare' as SortingFnOption, - header: ({ column }) => ( - - - {t('name')} - - - ), - cell: ({ row }) => { - const { location, locationCode } = row.original; - return ( - - - {location} - - - ); - }, - }, - { - accessorKey: 'coverage', - header: ({ column }) => ( - - - {t('coverage')} - - - ), - cell: ({ row }) => { - const { coverage: value } = row.original; - const formattedCoverage = cellFormatter.percentage(locale, value); - - return ( - - {t.rich('percentage-bold', { - b1: (chunks) => chunks, - b2: (chunks) => {chunks}, - percentage: formattedCoverage, - })} - - ); - }, - }, - { - accessorKey: 'area', - header: ({ column }) => ( - - - {t('area')} - - - ), - cell: ({ row }) => { - const { area: value } = row.original; - const formattedValue = cellFormatter.area(locale, value); - return {t('area-km2', { area: formattedValue })}; - }, - }, - { - accessorKey: 'mpas', - header: ({ column }) => ( - - - {t('mpas')} - - - ), - cell: ({ row }) => { - const { mpas: value } = row.original; - if (Number.isNaN(value)) return t('n-a'); - - const formattedValue = cellFormatter.percentage(locale, value); - return {t('percentage', { percentage: formattedValue })}; - }, - }, - { - accessorKey: 'oecms', - header: ({ column }) => ( - - - {t('oecms')} - - - ), - cell: ({ row }) => { - const { oecms: value } = row.original; - if (Number.isNaN(value)) return t('n-a'); - - const formattedValue = cellFormatter.percentage(locale, value); - return {t('percentage', { percentage: formattedValue })}; - }, - }, - { - accessorKey: 'fullyHighlyProtected', - header: ({ column }) => ( - - - {t('fully-highly-protected')} - - - ), - cell: ({ row }) => { - const { fullyHighlyProtected: value } = row.original; - const formattedValue = cellFormatter.percentage(locale, value); - return {t('percentage', { percentage: formattedValue })}; - }, - }, - // { - // accessorKey: 'highlyProtectedLfp', - // header: ({ column }) => ( - // - // - // {t('highly-protected-lfp')} - // - // - // ), - // cell: ({ row }) => { - // const { highlyProtectedLfp: value } = row.original; - // if (!value) return <>No data; - // const formattedValue = cellFormatter.percentage(locale, value); - // return {t('percentage', { percentage: formattedValue })}; - // }, - // }, - { - accessorKey: 'globalContribution', - header: ({ column }) => ( - - - {t('global-contribution')} - - - ), - cell: ({ row }) => { - const { globalContribution: value } = row.original; - if (!value) return t('no-data'); - const formattedValue = cellFormatter.percentage(locale, value); - return {t('percentage', { percentage: formattedValue })}; - }, - }, - ]; - }, [locale, searchParams, t, tooltips]); - - return columns; -}; - -export default useColumns; diff --git a/frontend/src/containers/map/content/details/tables/global-regional/useTooltips.tsx b/frontend/src/containers/map/content/details/tables/global-regional/useTooltips.tsx deleted file mode 100644 index 170f133f..00000000 --- a/frontend/src/containers/map/content/details/tables/global-regional/useTooltips.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useLocale } from 'next-intl'; - -import { useGetDataInfos } from '@/types/generated/data-info'; - -const TOOLTIP_MAPPING = { - location: 'name-country', - coverage: 'coverage', - locationType: 'location-type', - mpas: 'mpas', - oecms: 'oecms', - area: 'protected-area', - fullyHighlyProtected: 'fully-highly-protected', - highlyProtectedLfp: 'highly-protected-lfp', - globalContribution: 'global-contribution', -}; - -const useTooltips = () => { - const locale = useLocale(); - - const { data: dataInfo } = useGetDataInfos( - { locale }, - { - query: { - select: ({ data }) => data, - placeholderData: { data: [] }, - }, - } - ); - - const tooltips = {}; - - Object.entries(TOOLTIP_MAPPING).map(([key, value]) => { - const tooltip = dataInfo.find(({ attributes }) => attributes.slug === value)?.attributes - ?.content; - - if (!tooltip) return; - tooltips[key] = tooltip; - }); - - return tooltips; -}; - -export default useTooltips; diff --git a/frontend/src/containers/map/content/details/tables/national-highseas/hooks.tsx b/frontend/src/containers/map/content/details/tables/national-highseas/hooks.tsx new file mode 100644 index 00000000..0b303435 --- /dev/null +++ b/frontend/src/containers/map/content/details/tables/national-highseas/hooks.tsx @@ -0,0 +1,864 @@ +import { useCallback, useMemo } from 'react'; + +import { AccessorKeyColumnDef, PaginationState, SortingState } from '@tanstack/react-table'; +import { useLocale, useTranslations } from 'next-intl'; + +import FiltersButton from '@/components/filters-button'; +import ExpansionControls from '@/containers/map/content/details/table/expansion-controls'; +import HeaderItem from '@/containers/map/content/details/table/header-item'; +import { cellFormatter } from '@/containers/map/content/details/table/helpers'; +import SortingButton from '@/containers/map/content/details/table/sorting-button'; +import TooltipButton from '@/containers/map/content/details/table/tooltip-button'; +import { useGetDataInfos } from '@/types/generated/data-info'; +import { useGetDataSources } from '@/types/generated/data-source'; +import { useGetEnvironments } from '@/types/generated/environment'; +import { useGetMpaIucnCategories } from '@/types/generated/mpa-iucn-category'; +import { useGetMpaaEstablishmentStages } from '@/types/generated/mpaa-establishment-stage'; +import { useGetMpaaProtectionLevels } from '@/types/generated/mpaa-protection-level'; +import { useGetPas } from '@/types/generated/pa'; +import { useGetProtectionStatuses } from '@/types/generated/protection-status'; +import { + Pa, + PaChildrenDataItemAttributes, + PaListResponse, + PaListResponseMetaPagination, +} from '@/types/generated/strapi.schemas'; + +interface NationalHighseasTableRow { + name: string; + coverage: number; + area: number; + environment: { + name: string; + slug: string; + }; + data_source: { + title: string; + slug: string; + }; + protection_status: { + name: string; + slug: string; + }; + iucn_category: { + name: string; + slug: string; + }; + mpaa_establishment_stage: { + name: string; + slug: string; + }; + mpaa_protection_level: { + name: string; + slug: string; + }; +} + +export type NationalHighseasTableColumns = NationalHighseasTableRow & { + subRows?: NationalHighseasTableRow[]; +}; + +const TOOLTIP_MAPPING = { + protectedArea: 'name-pa', + coverage: 'coverage-wdpa', + protectedAreaType: 'protected-area-type', + establishmentStage: 'establishment-stage', + protectionLevel: 'protection-level', + fishingProtectionLevel: 'fishing-protection-level', + area: 'protected-area-mpa', + dataSource: 'details-data-source', + iucnCategory: 'details-iucn-category', +}; + +const useTooltips = () => { + const locale = useLocale(); + + const { data: dataInfo } = useGetDataInfos( + { locale }, + { + query: { + select: ({ data }) => data, + placeholderData: { data: [] }, + }, + } + ); + + const tooltips = {}; + + Object.entries(TOOLTIP_MAPPING).map(([key, value]) => { + const tooltip = dataInfo.find(({ attributes }) => attributes.slug === value)?.attributes + ?.content; + + if (!tooltip) return; + tooltips[key] = tooltip; + }); + + return tooltips; +}; + +const useFiltersOptions = () => { + const locale = useLocale(); + + const { data: environmentOptions } = useGetEnvironments<{ name: string; value: string }[]>( + { + locale, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['name', 'slug'], + 'pagination[limit]': -1, + }, + { + query: { + select: ({ data }) => + data.map((environment) => ({ + name: environment.attributes.name, + value: environment.attributes.slug, + })), + placeholderData: { data: [] }, + }, + } + ); + + const { data: dataSourceOptions } = useGetDataSources<{ name: string; value: string }[]>( + { + locale, // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['title', 'slug'], + 'pagination[limit]': -1, + }, + { + query: { + select: ({ data }) => + data + .map((dataSource) => ({ + name: dataSource.attributes.title, + value: dataSource.attributes.slug, + })) + .filter(({ value }) => + // ? Even though there are more data sources, we limit the display to these + ['mpatlas', 'protected-planet']?.includes(value) + ), + placeholderData: { data: [] }, + }, + } + ); + + const { data: protectionStatusOptions } = useGetProtectionStatuses< + { name: string; value: string }[] + >( + { + locale, // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['name', 'slug'], + 'pagination[limit]': -1, + }, + { + query: { + select: ({ data }) => + data.map((protectionStatus) => ({ + name: protectionStatus.attributes.name, + value: protectionStatus.attributes.slug, + })), + placeholderData: { data: [] }, + }, + } + ); + + const { data: iucnCategoryOptions } = useGetMpaIucnCategories<{ name: string; value: string }[]>( + { + locale, // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['name', 'slug'], + 'pagination[limit]': -1, + }, + { + query: { + select: ({ data }) => + data.map((iucnCategory) => ({ + name: iucnCategory.attributes.name, + value: iucnCategory.attributes.slug, + })), + placeholderData: { data: [] }, + }, + } + ); + + const { data: mpaaEstablishmentStageOptions } = useGetMpaaEstablishmentStages< + { name: string; value: string }[] + >( + { + locale, // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['name', 'slug'], + 'pagination[limit]': -1, + }, + { + query: { + select: ({ data }) => + data.map((mpaaEstablishmentStage) => ({ + name: mpaaEstablishmentStage.attributes.name, + value: mpaaEstablishmentStage.attributes.slug, + })), + placeholderData: { data: [] }, + }, + } + ); + + const { data: mpaaProtectionLevelOptions } = useGetMpaaProtectionLevels< + { name: string; value: string }[] + >( + { + locale, // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['name', 'slug'], + 'pagination[limit]': -1, + }, + { + query: { + select: ({ data }) => + data.map((mpaaProtectionLevel) => ({ + name: mpaaProtectionLevel.attributes.name, + value: mpaaProtectionLevel.attributes.slug, + })), + placeholderData: { data: [] }, + }, + } + ); + + return { + environment: environmentOptions, + dataSource: dataSourceOptions, + protectionStatus: protectionStatusOptions, + iucnCategory: iucnCategoryOptions, + mpaaEstablishmentStage: mpaaEstablishmentStageOptions, + mpaaProtectionLevel: mpaaProtectionLevelOptions, + }; +}; + +export const useColumns = ( + environment: 'marine' | 'terrestrial' | null, + filters: Record, + onChangeFilters: (newFilters: Record) => void +) => { + const t = useTranslations('containers.map'); + const locale = useLocale(); + + const tooltips = useTooltips(); + + const filtersOptions = useFiltersOptions(); + + const columns: AccessorKeyColumnDef[] = useMemo(() => { + return [ + { + id: 'name', + accessorKey: 'name', + header: ({ column }) => ( + + + {t('name')} + + + ), + cell: ({ row }) => { + const { + original: { name }, + } = row; + return ( + + {name} + + ); + }, + }, + { + id: 'environment.name', + accessorKey: 'environment.name', + header: ({ column }) => ( + + {!environment && ( + onChangeFilters({ ...filters, [field]: values })} + /> + )} + {t('ecosystem')} + + + ), + cell: ({ row }) => { + const { environment } = row.original; + return {environment.name}; + }, + }, + { + id: 'coverage', + accessorKey: 'coverage', + header: ({ column }) => ( + + + {t('coverage')} + + + ), + cell: ({ row }) => { + const { coverage: value } = row.original; + if (!value) return <>—; + + const formattedCoverage = cellFormatter.percentage(locale, value); + + return ( + + {t.rich('percentage-bold', { + b1: (chunks) => chunks, + b2: (chunks) => {chunks}, + percentage: formattedCoverage, + })} + + ); + }, + }, + { + id: 'area', + accessorKey: 'area', + header: ({ column }) => ( + + + {t('area')} + + + ), + cell: ({ row }) => { + const { area: value } = row.original; + const formattedValue = cellFormatter.area(locale, value); + return {t('area-km2', { area: formattedValue })}; + }, + }, + { + id: 'data_source.title', + accessorKey: 'data_source.title', + header: ({ column }) => ( + + onChangeFilters({ ...filters, [field]: values })} + /> + {t('data-source')} + + + ), + cell: ({ row }) => { + const { data_source } = row.original; + const formattedValue = data_source?.title || t('n-a'); + return <>{formattedValue}; + }, + }, + { + id: 'protection_status.name', + accessorKey: 'protection_status.name', + header: ({ column }) => ( + + onChangeFilters({ ...filters, [field]: values })} + /> + {t('type')} + + + ), + cell: ({ row }) => { + const { protection_status } = row.original; + const formattedValue = protection_status?.name ?? '-'; + return <>{formattedValue}; + }, + }, + { + id: 'iucn_category.name', + accessorKey: 'iucn_category.name', + header: ({ column }) => ( + + onChangeFilters({ ...filters, [field]: values })} + /> + {t('iucn-category')} + + + ), + cell: ({ row }) => { + const { iucn_category } = row.original; + const formattedValue = iucn_category?.name || t('n-a'); + return <>{formattedValue}; + }, + }, + ...(environment === 'marine' + ? [ + { + id: 'mpaa_establishment_stage.name', + accessorKey: 'mpaa_establishment_stage.name', + header: ({ column }) => ( + + onChangeFilters({ ...filters, [field]: values })} + /> + {t('establishment-stage')} + + + ), + cell: ({ row }) => { + const { mpaa_establishment_stage } = row.original; + const formattedValue = mpaa_establishment_stage.name || t('not-assessed'); + return <>{formattedValue}; + }, + }, + ] + : []), + ...(environment === 'marine' + ? [ + { + id: 'mpaa_protection_level.name', + accessorKey: 'mpaa_protection_level.name', + header: ({ column }) => ( + + onChangeFilters({ ...filters, [field]: values })} + /> + {t('protection-level')} + + + ), + cell: ({ row }) => { + const { mpaa_protection_level } = row.original; + const formattedValue = mpaa_protection_level.name || t('not-assessed'); + return <>{formattedValue}; + }, + }, + ] + : []), + ]; + }, [locale, environment, t, tooltips, filters, onChangeFilters, filtersOptions]); + + return columns; +}; + +export const useData = ( + locationCode: string, + environment: 'marine' | 'terrestrial' | null, + sorting: SortingState, + filters: Record, + pagination: PaginationState +) => { + const locale = useLocale(); + + const queryFields = useMemo(() => ['name', 'coverage', 'area'], []); + + const queryPopulate = useMemo( + () => ({ + environment: { + fields: ['slug', 'name', 'locale'], + populate: { + localizations: { + fields: ['name', 'locale'], + }, + }, + }, + data_source: { + fields: ['slug', 'title', 'locale'], + populate: { + localizations: { + fields: ['slug', 'title', 'locale'], + }, + }, + }, + protection_status: { + fields: ['slug', 'name', 'locale'], + populate: { + localizations: { + fields: ['slug', 'name', 'locale'], + }, + }, + }, + iucn_category: { + fields: ['slug', 'name', 'locale'], + populate: { + localizations: { + fields: ['slug', 'name', 'locale'], + }, + }, + }, + ...(environment === 'marine' + ? { + mpaa_establishment_stage: { + fields: ['slug', 'name', 'locale'], + populate: { + localizations: { + fields: ['slug', 'name', 'locale'], + }, + }, + }, + } + : {}), + ...(environment === 'marine' + ? { + mpaa_protection_level: { + fields: ['slug', 'name', 'locale'], + populate: { + localizations: { + fields: ['slug', 'name', 'locale'], + }, + }, + }, + } + : {}), + }), + [environment] + ); + + const querySort = useMemo(() => { + // By default, we always sort by protected area name + let res = 'name:asc,environment.name:asc'; + if (sorting.length > 0) { + res = `${sorting[0].id}:${sorting[0].desc ? 'desc' : 'asc'}`; + + // In addition to sorting by the column the user asked about, we'll also always sort by + // environment + if (sorting[0].id !== 'environment.name') { + res = `${res},environment.name:asc`; + } + } + + return res; + }, [sorting]); + + const queryFilters = useMemo( + () => ({ + ...(environment + ? { + environment: { + slug: { + $eq: environment, + }, + }, + } + : {}), + location: { + code: { + $eq: locationCode, + }, + }, + ...Object.entries(filters).reduce((res, [key, values]) => { + if (!values || values.length === 0) { + return res; + } + + const reversePathItems = key.split('.').reverse(); + + return reversePathItems.reduce((res, pathItem, index) => { + if (index === 0) { + return { [pathItem]: { $in: values } }; + } + + return { [pathItem]: res }; + }, {}); + }, {}), + }), + [environment, filters, locationCode] + ); + + const isFiltering = useMemo( + () => + Object.values(filters) + .filter(Boolean) + .some((value) => value.length > 0), + [filters] + ); + + const processData = useCallback( + (data: PaListResponse) => { + return [ + data.data?.map(({ attributes }): NationalHighseasTableColumns => { + const getData = (pa: Pa | PaChildrenDataItemAttributes) => { + const environment = pa.environment?.data?.attributes; + const localizedEnvironment = [ + environment, + ...(environment?.localizations.data.map((environment) => environment.attributes) ?? + []), + ].find((data) => data?.locale === locale); + + const dataSource = pa.data_source?.data?.attributes; + const localizedDataSource = [ + dataSource, + ...(dataSource?.localizations.data.map((dataSource) => dataSource.attributes) ?? []), + ].find((data) => data?.locale === locale); + + const protectionStatus = pa.protection_status?.data?.attributes; + const localizedProtectionStatus = [ + protectionStatus, + ...(protectionStatus?.localizations.data.map( + (protectionStatus) => protectionStatus.attributes + ) ?? []), + ].find((data) => data?.locale === locale); + + const iucnCategory = pa.iucn_category?.data?.attributes; + const localizedIucnCategory = [ + iucnCategory, + ...(iucnCategory?.localizations.data.map((iucnCategory) => iucnCategory.attributes) ?? + []), + ].find((data) => data?.locale === locale); + + const mpaaEstablishmentStage = pa.mpaa_establishment_stage?.data?.attributes; + const localizedMpaaEstablishmentStage = [ + mpaaEstablishmentStage, + ...(mpaaEstablishmentStage?.localizations.data.map( + (mpaaEstablishmentStage) => mpaaEstablishmentStage.attributes + ) ?? []), + ].find((data) => data?.locale === locale); + + const mpaaProtectionLevel = pa.mpaa_protection_level?.data?.attributes; + const localizedMpaaProtectionLevel = [ + mpaaProtectionLevel, + ...(mpaaProtectionLevel?.localizations.data.map( + (mpaaProtectionLevel) => mpaaProtectionLevel.attributes + ) ?? []), + ].find((data) => data?.locale === locale); + + return { + name: pa.name, + coverage: pa.coverage, + area: pa.area, + environment: { + name: localizedEnvironment?.name, + slug: localizedEnvironment?.slug, + }, + data_source: { + title: localizedDataSource?.title, + slug: localizedDataSource?.slug, + }, + protection_status: { + name: localizedProtectionStatus?.name, + slug: localizedProtectionStatus?.slug, + }, + iucn_category: { + name: localizedIucnCategory?.name, + slug: localizedIucnCategory?.slug, + }, + mpaa_establishment_stage: { + name: localizedMpaaEstablishmentStage?.name, + slug: localizedMpaaEstablishmentStage?.slug, + }, + mpaa_protection_level: { + name: localizedMpaaProtectionLevel?.name, + slug: localizedMpaaProtectionLevel?.slug, + }, + }; + }; + + return { + ...getData(attributes), + ...(attributes.children.data.length > 0 + ? { + subRows: attributes.children.data.map(({ attributes }) => getData(attributes)), + } + : {}), + }; + }) ?? [], + data.meta?.pagination ?? {}, + ]; + }, + [locale] + ); + + // If the user isn't filtering, only one request is sufficient to get all of the table's data + const { data: noFilteringData } = useGetPas< + [NationalHighseasTableColumns[], PaListResponseMetaPagination] + >( + { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: queryFields, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + populate: { + ...queryPopulate, + children: { + fields: queryFields, + populate: queryPopulate, + filters: queryFilters, + sort: querySort, + }, + }, + filters: { + ...queryFilters, + parent: { + name: { + $null: true, + }, + }, + }, + 'pagination[pageSize]': pagination.pageSize, + 'pagination[page]': pagination.pageIndex + 1, + sort: querySort, + }, + { + query: { + placeholderData: [], + enabled: !isFiltering, + keepPreviousData: true, + select: processData, + }, + } + ); + + // If the user is filtering, we need to make several requests to Strapi. This is because a parent + // row needs to be filtered (among others) based on the values of the children rows. Strapi + // doesn't cover this use case so we're forced to multiply the requests. + // While unoptimal, this solution is still better than doing front-end filtering because we're + // loading way less data. + + // This request gets the list of all parents (no pagination) that match the filters. No sorting. + // This list is incomplete because some parents may not match the filters but their children do. + const { data: matchingParentsIds } = useGetPas( + { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['id'], + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + filters: { + $and: [ + { + parent: { + name: { + $null: true, + }, + }, + }, + queryFilters, + ], + }, + 'pagination[limit]': -1, + }, + { + query: { + placeholderData: [], + enabled: isFiltering, + keepPreviousData: true, + select: (data) => data.data.map(({ id }) => id), + }, + } + ); + + // This request gets the list of all the parents (no pagination) for which at least one child + // matches the filters. No sorting. + const { data: parentIdsWithMatchingChildren } = useGetPas( + { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['id'], + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + populate: { + parent: { + fields: ['id'], + }, + }, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + filters: { + $and: [ + { + parent: { + name: { + $null: false, + }, + }, + }, + queryFilters, + ], + }, + 'pagination[limit]': -1, + }, + { + query: { + placeholderData: [], + enabled: isFiltering, + keepPreviousData: true, + select: (data) => data.data.map(({ attributes }) => attributes.parent.data.id), + }, + } + ); + + // List of all the parents (no pagination) that either match the filters or for which at least one + // of child matches the filters. No sorting. + const parentsIds = useMemo(() => { + return Array.from( + new Set([...(matchingParentsIds ?? []), ...(parentIdsWithMatchingChildren ?? [])]) + ); + }, [matchingParentsIds, parentIdsWithMatchingChildren]); + + // Final query that gets the table's data using the list of parent ids + const { data: filteringData } = useGetPas< + [NationalHighseasTableColumns[], PaListResponseMetaPagination] + >( + { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: queryFields, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + populate: { + ...queryPopulate, + children: { + fields: queryFields, + populate: queryPopulate, + filters: queryFilters, + sort: querySort, + }, + }, + filters: { + parent: { + name: { + $null: true, + }, + }, + id: { + $in: parentsIds, + }, + }, + 'pagination[pageSize]': pagination.pageSize, + 'pagination[page]': pagination.pageIndex + 1, + sort: querySort, + }, + { + query: { + placeholderData: [], + enabled: isFiltering && parentsIds.length > 0, + keepPreviousData: true, + select: processData, + }, + } + ); + + if (isFiltering) { + if (parentsIds.length > 0) { + return filteringData; + } + + return [[] as NationalHighseasTableColumns[], { total: 0 }] as const; + } + + return noFilteringData; +}; diff --git a/frontend/src/containers/map/content/details/tables/national-highseas/index.tsx b/frontend/src/containers/map/content/details/tables/national-highseas/index.tsx index 5fe3d747..3b729ad2 100644 --- a/frontend/src/containers/map/content/details/tables/national-highseas/index.tsx +++ b/frontend/src/containers/map/content/details/tables/national-highseas/index.tsx @@ -1,164 +1,86 @@ -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/router'; -import { useLocale } from 'next-intl'; +import { PaginationState, SortingState } from '@tanstack/react-table'; +import { usePreviousImmediate } from 'rooks'; import FiltersButton from '@/components/filters-button'; import TooltipButton from '@/components/tooltip-button'; -import { applyFilters } from '@/containers/map/content/details/helpers'; import Table from '@/containers/map/content/details/table'; -import useColumns from '@/containers/map/content/details/tables/national-highseas/useColumns'; +import { useSyncMapContentSettings } from '@/containers/map/sync-settings'; import { FCWithMessages } from '@/types'; -import { useGetLocations } from '@/types/generated/location'; -import { useGetMpas } from '@/types/generated/mpa'; -import { MpaListResponseDataItem } from '@/types/generated/strapi.schemas'; import SortingButton from '../../table/sorting-button'; +import { useData, useColumns } from './hooks'; + const NationalHighseasTable: FCWithMessages = () => { const { query: { locationCode = 'GLOB' }, } = useRouter(); - const locale = useLocale(); - const locationsQuery = useGetLocations( - { - locale, - filters: { - code: locationCode, - }, - }, - { - query: { - queryKey: ['locations', locationCode], - select: ({ data }) => data?.[0]?.attributes, + const [{ tab }] = useSyncMapContentSettings(); + const previousTab = usePreviousImmediate(tab); + + const [filters, setFilters] = useState>({}); + + const columns = useColumns( + tab === 'marine' || tab === 'terrestrial' ? tab : null, + filters, + setFilters + ); + + const defaultSorting = useMemo( + () => [ + { + id: columns[0].accessorKey, + desc: false, }, - } + ], + [columns] ); - const [filters, setFilters] = useState({ - protectedAreaType: [], - establishmentStage: [], - protectionLevel: [], - dataSource: [], - iucnCategory: [], + const [sorting, setSorting] = useState(defaultSorting); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 100, }); - const handleOnFiltersChange = (field, values) => { - setFilters({ ...filters, [field]: values }); - }; - - const columns = useColumns({ filters, onFiltersChange: handleOnFiltersChange }); - - const mpaEntryPopulate = { - mpaa_establishment_stage: { - fields: ['name', 'slug'], - }, - mpa: { - fields: ['name', 'wdpaid', 'area'], - populate: { - protection_status: { - fields: ['slug', 'name'], - }, - }, - }, - location: { - fields: ['code', 'total_marine_area', 'marine_bounds'], - }, - mpaa_protection_level: { - fields: ['slug', 'name'], - }, - protection_status: { - fields: ['slug', 'name'], - }, - data_source: { - fields: ['slug'], - }, - iucn_category: { - fields: ['slug'], - }, - }; - - const { data: mpasData }: { data: MpaListResponseDataItem[] } = useGetMpas( - { - locale, - filters: { - location: { - code: { - $eq: locationsQuery.data?.code, - }, - }, - is_child: false, - }, + const [data, { total }] = useData( + locationCode as string, + tab === 'marine' || tab === 'terrestrial' ? tab : null, + sorting, + filters, + pagination + ); + + // When the tab changes, we reset the filters and the sorting + useEffect(() => { + if (tab !== previousTab) { + setFilters({}); + setSorting(defaultSorting); + } + }, [tab, previousTab, defaultSorting]); + + // When the filters or the sorting changes, the page number is reset + useEffect(() => { + setPagination((prevPagination) => ({ ...prevPagination, pageIndex: 0 })); + }, [filters, sorting]); + + return ( +
data, - placeholderData: { data: [] }, - }, - } + columns={columns} + data={data} + sorting={sorting} + onSortingChange={setSorting} + pagination={pagination} + onPaginationChange={setPagination} + rowCount={total ?? 0} + /> ); - - const parsedData = useMemo(() => { - const buildMpaRow = (mpa) => { - const protectionStatus = mpa?.protection_status?.data?.attributes; - const establishmentStage = mpa?.mpaa_establishment_stage?.data?.attributes; - const mpaaProtectionLevel = mpa?.mpaa_protection_level?.data?.attributes; - const dataSource = mpa?.data_source?.data?.attributes; - const iucnCategory = mpa?.iucn_category?.data?.attributes; - - const coveragePercentage = (mpa.area / locationsQuery.data?.totalMarineArea) * 100; - - return { - protectedArea: mpa?.name, - coverage: coveragePercentage, - protectedAreaType: protectionStatus?.slug, - establishmentStage: establishmentStage?.slug, - protectionLevel: mpaaProtectionLevel?.slug, - area: mpa?.area, - dataSource: dataSource?.slug, - iucnCategory: iucnCategory?.slug, - // ? LayerPreview: We're not displaying the layer preview at this moment, but we want to preserve the code - // map: { - // wdpaId: mpa?.wdpaid, - // bounds: mpa?.bbox, - // dataSource: dataSource?.slug, - // }, - }; - }; - - return mpasData?.map(({ attributes: mpa }) => { - const mpaChildren = mpa?.children?.data; - - const mpaData = buildMpaRow(mpa); - const mpaChildrenData = mpaChildren - .map(({ attributes: childMpa }) => buildMpaRow(childMpa)) - .filter((row) => !!row); - - return { - ...mpaData, - ...(mpaChildrenData?.length && { subRows: mpaChildrenData }), - }; - }); - }, [locationsQuery, mpasData]); - - const tableData = useMemo(() => { - return applyFilters(parsedData, filters); - }, [filters, parsedData]); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return
; }; NationalHighseasTable.messages = [ diff --git a/frontend/src/containers/map/content/details/tables/national-highseas/useColumns.tsx b/frontend/src/containers/map/content/details/tables/national-highseas/useColumns.tsx deleted file mode 100644 index 7c100410..00000000 --- a/frontend/src/containers/map/content/details/tables/national-highseas/useColumns.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import { ComponentProps, useMemo } from 'react'; - -import { ColumnDef, SortingFnOption } from '@tanstack/react-table'; -import { useLocale, useTranslations } from 'next-intl'; - -import FiltersButton from '@/components/filters-button'; -import LayerPreview from '@/components/layer-preview'; -import ExpansionControls from '@/containers/map/content/details/table/expansion-controls'; -import HeaderItem from '@/containers/map/content/details/table/header-item'; -import { cellFormatter } from '@/containers/map/content/details/table/helpers'; -import SortingButton from '@/containers/map/content/details/table/sorting-button'; -import TooltipButton from '@/containers/map/content/details/table/tooltip-button'; -import useFiltersOptions from '@/containers/map/content/details/tables/national-highseas/useFiltersOptions'; -import useTooltips from '@/containers/map/content/details/tables/national-highseas/useTooltips'; - -export type NationalHighseasTableColumns = { - protectedArea: string; - coverage: number; - protectedAreaType: string; - establishmentStage: string; - protectionLevel: string; - area: number; - dataSource: string; - iucnCategory: string; - map: { - wdpaId: string; - bounds: [number, number, number, number]; - dataSource: ComponentProps['dataSource']; - }; -}; - -type UseColumnsProps = { - filters: { [key: string]: string[] }; - onFiltersChange: (field: string, values: string[]) => void; -}; - -const useColumns = ({ filters, onFiltersChange }: UseColumnsProps) => { - const t = useTranslations('containers.map'); - const locale = useLocale(); - - const { - protectionStatus: protectionStatusOptions, - establishmentStage: establishmentStageOptions, - protectionLevel: protectionLevelOptions, - dataSource: dataSourceOptions, - iucnCategory: iucnCategoryOptions, - } = useFiltersOptions(); - - const tooltips = useTooltips(); - - // Define columns - const columns: ColumnDef[] = useMemo(() => { - return [ - { - accessorKey: 'protectedArea', - sortingFn: 'localeStringCompare' as SortingFnOption, - header: ({ column }) => ( - - - {t('name')} - - - ), - cell: ({ row }) => { - const { - original: { protectedArea }, - } = row; - return ( - - {protectedArea} - - ); - }, - }, - // ? LayerPreview: We're not displaying the layer preview at this moment, but we want to preserve the code - // { - // accessorKey: 'map', - // header: null, - // cell: ({ row }) => { - // const { bounds, wdpaId, dataSource } = row.original?.map || {}; - - // return ( - //
- // - //
- // ); - // }, - // }, - { - accessorKey: 'coverage', - header: ({ column }) => ( - - - {t('coverage')} - - - ), - cell: ({ row }) => { - const { coverage: value } = row.original; - if (!value) return <>—; - - const formattedCoverage = cellFormatter.percentage(locale, value); - - return ( - - {t.rich('percentage-bold', { - b1: (chunks) => chunks, - b2: (chunks) => {chunks}, - percentage: formattedCoverage, - })} - - ); - }, - }, - { - accessorKey: 'area', - header: ({ column }) => ( - - - {t('area')} - - - ), - cell: ({ row }) => { - const { area: value } = row.original; - const formattedValue = cellFormatter.area(locale, value); - return {t('area-km2', { area: formattedValue })}; - }, - }, - { - accessorKey: 'dataSource', - header: ({ column }) => ( - - - {t('data-source')} - - - ), - cell: ({ row }) => { - const { dataSource: value } = row.original; - const formattedValue = - dataSourceOptions.find((entry) => value === entry?.value)?.name || t('n-a'); - return <>{formattedValue}; - }, - }, - { - accessorKey: 'protectedAreaType', - header: ({ column }) => ( - - - {t('type')} - - - ), - cell: ({ row }) => { - const { protectedAreaType: value } = row.original; - const formattedValue = protectionStatusOptions.find( - (entry) => value === entry?.value - )?.name; - return <>{formattedValue ?? '-'}; - }, - }, - { - accessorKey: 'iucnCategory', - header: ({ column }) => ( - - - {t('iucn-category')} - - - ), - cell: ({ row }) => { - const { iucnCategory: value } = row.original; - const formattedValue = - iucnCategoryOptions.find((entry) => value === entry?.value)?.name || t('n-a'); - return <>{formattedValue}; - }, - }, - { - accessorKey: 'establishmentStage', - header: ({ column }) => ( - - - {t('establishment-stage')} - - - ), - cell: ({ row }) => { - const { establishmentStage: value } = row.original; - const formattedValue = - establishmentStageOptions.find((entry) => value === entry?.value)?.name || - t('not-assessed'); - return <>{formattedValue}; - }, - }, - { - accessorKey: 'protectionLevel', - header: ({ column }) => ( - - - {t('protection-level')} - - - ), - cell: ({ row }) => { - const { protectionLevel: value } = row.original; - const formattedValue = - protectionLevelOptions.find((entry) => value === entry?.value)?.name || - t('not-assessed'); - return <>{formattedValue}; - }, - }, - ]; - }, [ - t, - tooltips, - locale, - dataSourceOptions, - iucnCategoryOptions, - protectionStatusOptions, - filters, - onFiltersChange, - establishmentStageOptions, - protectionLevelOptions, - ]); - - return columns; -}; - -export default useColumns; diff --git a/frontend/src/containers/map/content/details/tables/national-highseas/useFiltersOptions.ts b/frontend/src/containers/map/content/details/tables/national-highseas/useFiltersOptions.ts deleted file mode 100644 index 74803d84..00000000 --- a/frontend/src/containers/map/content/details/tables/national-highseas/useFiltersOptions.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { useMemo } from 'react'; - -import { useLocale } from 'next-intl'; - -import { useGetDataSources } from '@/types/generated/data-source'; -import { useGetMpaIucnCategories } from '@/types/generated/mpa-iucn-category'; -import { useGetMpaaEstablishmentStages } from '@/types/generated/mpaa-establishment-stage'; -import { useGetMpaaProtectionLevels } from '@/types/generated/mpaa-protection-level'; -import { useGetProtectionStatuses } from '@/types/generated/protection-status'; - -const useFiltersOptions = () => { - const locale = useLocale(); - - // Fetch protection statuses and build options for the filter - const { data: protectionStatuses } = useGetProtectionStatuses( - { locale }, - { - query: { - select: ({ data }) => data, - placeholderData: { data: [] }, - }, - } - ); - - const protectionStatusOptions = useMemo(() => { - return protectionStatuses.map(({ attributes }) => ({ - name: attributes?.name, - value: attributes?.slug, - })); - }, [protectionStatuses]); - - // Fetch establishment stages and build options for the filter - const { data: establishmentStages } = useGetMpaaEstablishmentStages( - { locale }, - { - query: { - select: ({ data }) => [...data, { attributes: { name: 'N/A', slug: 'N/A' } }], - placeholderData: { data: [] }, - }, - } - ); - - const establishmentStageOptions = useMemo(() => { - return establishmentStages.map(({ attributes }) => ({ - name: attributes?.name, - value: attributes?.slug, - })); - }, [establishmentStages]); - - // Fetch data sources and build options for the filter - const { data: dataSources } = useGetDataSources( - { locale }, - { - query: { - select: ({ data }) => - data?.filter(({ attributes }) => - // ? Even though there are more data sources, we limit the display to these - ['mpatlas', 'protected-planet']?.includes(attributes?.slug) - ), - placeholderData: { data: [] }, - }, - } - ); - - const dataSourceOptions = useMemo(() => { - return dataSources.map(({ attributes }) => ({ - name: attributes?.title, - value: attributes?.slug, - })); - }, [dataSources]); - - // Fetch IUCN category options and build options for the filter - const { data: iucnCategories } = useGetMpaIucnCategories( - { locale }, - { - query: { - select: ({ data }) => data, - placeholderData: { data: [] }, - }, - } - ); - - const iucnCategoryOptions = useMemo(() => { - return iucnCategories.map(({ attributes }) => ({ - name: attributes?.name, - value: attributes?.slug, - })); - }, [iucnCategories]); - - // Fetch protection levels and build options for the filter - const { data: protectionLevels } = useGetMpaaProtectionLevels( - { locale }, - { - query: { - select: ({ data }) => - data.filter( - ({ attributes }) => - // ? these protection values are used internally, but they should not be visible to the user - !['fully-highly-protected', 'less-protected-unknown'].includes(attributes?.slug) - ), - placeholderData: { data: [] }, - }, - } - ); - - const protectionLevelOptions = useMemo(() => { - return protectionLevels.map(({ attributes }) => ({ - name: attributes?.name, - value: attributes?.slug, - })); - }, [protectionLevels]); - - return { - protectionStatus: protectionStatusOptions, - establishmentStage: establishmentStageOptions, - dataSource: dataSourceOptions, - iucnCategory: iucnCategoryOptions, - protectionLevel: protectionLevelOptions, - }; -}; - -export default useFiltersOptions; diff --git a/frontend/src/containers/map/content/details/tables/national-highseas/useTooltips.tsx b/frontend/src/containers/map/content/details/tables/national-highseas/useTooltips.tsx deleted file mode 100644 index d31b4543..00000000 --- a/frontend/src/containers/map/content/details/tables/national-highseas/useTooltips.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useLocale } from 'next-intl'; - -import { useGetDataInfos } from '@/types/generated/data-info'; - -const TOOLTIP_MAPPING = { - protectedArea: 'name-pa', - coverage: 'coverage-wdpa', - protectedAreaType: 'protected-area-type', - establishmentStage: 'establishment-stage', - protectionLevel: 'protection-level', - fishingProtectionLevel: 'fishing-protection-level', - area: 'protected-area-mpa', - dataSource: 'details-data-source', - iucnCategory: 'details-iucn-category', -}; - -const useTooltips = () => { - const locale = useLocale(); - - const { data: dataInfo } = useGetDataInfos( - { locale }, - { - query: { - select: ({ data }) => data, - placeholderData: { data: [] }, - }, - } - ); - - const tooltips = {}; - - Object.entries(TOOLTIP_MAPPING).map(([key, value]) => { - const tooltip = dataInfo.find(({ attributes }) => attributes.slug === value)?.attributes - ?.content; - - if (!tooltip) return; - tooltips[key] = tooltip; - }); - - return tooltips; -}; - -export default useTooltips; diff --git a/frontend/src/containers/map/content/index.tsx b/frontend/src/containers/map/content/index.tsx index 5ba64682..332d342a 100644 --- a/frontend/src/containers/map/content/index.tsx +++ b/frontend/src/containers/map/content/index.tsx @@ -1,44 +1,18 @@ -import { useTranslations } from 'next-intl'; - -import { Button } from '@/components/ui/button'; -import Icon from '@/components/ui/icon'; import { useSyncMapContentSettings } from '@/containers/map/sync-settings'; -import CloseIcon from '@/styles/icons/close.svg'; import { FCWithMessages } from '@/types'; import Details from './details'; import Map from './map'; const MapContent: FCWithMessages = () => { - const t = useTranslations('containers.map'); - const [{ showDetails, tab }, setSettings] = useSyncMapContentSettings(); - - const handleOnCloseClick = () => { - setSettings((prevSettings) => ({ ...prevSettings, showDetails: false })); - }; + const [{ showDetails }] = useSyncMapContentSettings(); return ( <> {showDetails && (
- {tab === 'marine' &&
} - {/* TODO: The following element is a placeholder */} - {tab !== 'marine' && ( -
-
- -
-
{t('coming-soon')}
-
- )} +
)} diff --git a/frontend/src/containers/map/content/map/popup/eez/index.tsx b/frontend/src/containers/map/content/map/popup/eez/index.tsx index 77cfb83e..0a6c5402 100644 --- a/frontend/src/containers/map/content/map/popup/eez/index.tsx +++ b/frontend/src/containers/map/content/map/popup/eez/index.tsx @@ -5,7 +5,6 @@ import { useMap } from 'react-map-gl'; import { useParams } from 'next/navigation'; import { useRouter } from 'next/router'; -import { useQueries } from '@tanstack/react-query'; import type { Feature } from 'geojson'; import { useAtom, useAtomValue } from 'jotai'; import { useLocale, useTranslations } from 'next-intl'; @@ -17,9 +16,8 @@ import { bboxLocation, layersInteractiveIdsAtom, popupAtom } from '@/containers/ import { formatPercentage, formatKM } from '@/lib/utils/formats'; import { FCWithMessages } from '@/types'; import { useGetLayersId } from '@/types/generated/layer'; -import { getGetLocationsQueryOptions } from '@/types/generated/location'; -import { getGetProtectionCoverageStatsQueryOptions } from '@/types/generated/protection-coverage-stat'; -import { ProtectionCoverageStatListResponseDataItem } from '@/types/generated/strapi.schemas'; +import { useGetProtectionCoverageStats } from '@/types/generated/protection-coverage-stat'; +import { ProtectionCoverageStat } from '@/types/generated/strapi.schemas'; import { LayerTyped } from '@/types/layers'; const EEZLayerPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => { @@ -37,31 +35,20 @@ const EEZLayerPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => { const layersInteractiveIds = useAtomValue(layersInteractiveIdsAtom); - const layerQuery = useGetLayersId<{ - source: LayerTyped['config']['source']; - click: LayerTyped['interaction_config']['events'][0]; - }>( + const { data: source } = useGetLayersId( layerId, { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - locale, - populate: 'metadata', + fields: ['config'], }, { query: { - select: ({ data }) => ({ - source: (data.attributes as LayerTyped).config?.source, - click: (data.attributes as LayerTyped)?.interaction_config?.events.find( - (ev) => ev.type === 'click' - ), - }), + select: ({ data }) => (data.attributes as LayerTyped).config?.source, }, } ); - const { source } = layerQuery.data; - const DATA = useMemo(() => { if (source?.type === 'vector' && rendered && popup && map) { const point = map.project(popup.lngLat); @@ -93,122 +80,63 @@ const EEZLayerPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => { return DATA_REF.current; }, [popup, source, layersInteractiveIds, map, rendered]); - const locationCodes = Object.keys(DATA || {}) + const selectedLocationCode: string = Object.keys(DATA || {}) .filter((key) => key.startsWith('ISO_') && DATA[key]) .map((key) => DATA[key]) - .filter((code, index, codes) => { + .find((code, index, codes) => { if (codes.length > 1) return code === locationCode; return true; }); - const locationQueries = useQueries({ - queries: locationCodes.map((code) => - getGetLocationsQueryOptions( - { - locale, - filters: { - code, + const { data: protectionCoverageStats, isFetching } = + useGetProtectionCoverageStats( + { + locale, + filters: { + location: { + code: selectedLocationCode, }, - }, - { - query: { - enabled: Boolean(code), - select: ({ data }) => data?.[0]?.attributes, + is_last_year: { + $eq: true, }, - } - ) - ), - }); - - const locationsData = useMemo( - () => - locationQueries - .map((query) => { - if (['loading', 'error'].includes(query.status)) return null; - - return query.data; - }) - .filter((d) => Boolean(d)), - [locationQueries] - ); - - const protectionCoverageStatsQueries = useQueries({ - queries: locationCodes.map((code) => - getGetProtectionCoverageStatsQueryOptions( - { - locale, - filters: { - location: { - code, + environment: { + slug: { + $eq: 'marine', }, }, - populate: 'location', - // @ts-expect-error this is an issue with Orval typing - 'sort[year]': 'desc', - 'pagination[limit]': -1, }, - { - query: { - select: ({ data }) => { - if (!data?.length) return undefined; - - const latestYear = data[0].attributes.year; - - const latestStats = data.filter((d) => d.attributes.year === latestYear); - - const cumSumProtectedArea = latestStats.reduce( - (acc, entry) => acc + entry.attributes.cumSumProtectedArea, - 0 - ); - - return { - cumSumProtectedArea, - }; - }, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + populate: { + location: { + fields: ['code', 'marine_bounds', 'totalMarineArea'], }, - } - ) - ), - }); - - const protectionCoverageStatsData: { cumSumProtectedArea: number }[] = useMemo( - () => - protectionCoverageStatsQueries - .map((query) => { - if (!query.isSuccess) return null; - - return query.data as { - cumSumProtectedArea: ProtectionCoverageStatListResponseDataItem['attributes']['cumSumProtectedArea']; - }; - }) - .filter((d) => Boolean(d)), - [protectionCoverageStatsQueries] - ); - - const totalCumSumProtectedArea = useMemo(() => { - if (!protectionCoverageStatsData.length) return 0; - - return protectionCoverageStatsData.reduce( - (acc, { cumSumProtectedArea }) => acc + cumSumProtectedArea, - 0 + }, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fields: ['coverage', 'protectedArea'], + 'pagination[limit]': 1, + }, + { + query: { + enabled: !!selectedLocationCode, + select: ({ data }) => { + if (!data?.length) return undefined; + return data[0].attributes; + }, + }, + } ); - }, [protectionCoverageStatsData]); - - const totalMarineArea = useMemo(() => { - if (!locationsData.length) return 0; - - return locationsData.reduce((acc, { totalMarineArea }) => acc + totalMarineArea, 0); - }, [locationsData]); - const coveragePercentage = useMemo(() => { - if (locationsData.length) { - return formatPercentage(locale, (totalCumSumProtectedArea / totalMarineArea) * 100, { + const formattedCoverage = useMemo(() => { + if (protectionCoverageStats?.coverage !== undefined) { + return formatPercentage(locale, protectionCoverageStats.coverage, { displayPercentageSign: false, }); } return '-'; - }, [locale, totalCumSumProtectedArea, totalMarineArea, locationsData]); + }, [locale, protectionCoverageStats]); const EEZName = useMemo(() => { let name = null; @@ -234,14 +162,14 @@ const EEZLayerPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => { }, [map]); const handleLocationSelected = useCallback(async () => { - if (!locationsData[0]) return undefined; + if (!protectionCoverageStats?.location?.data.attributes) return undefined; - const { code, marine_bounds: bounds } = locationsData[0]; + const { code, marine_bounds: bounds } = protectionCoverageStats.location.data.attributes; await push(`${PAGES.progressTracker}/${code.toUpperCase()}?${searchParams.toString()}`); setLocationBBox(bounds as CustomMapProps['bounds']['bbox']); setPopup({}); - }, [push, searchParams, setLocationBBox, locationsData, setPopup]); + }, [push, searchParams, setLocationBBox, protectionCoverageStats, setPopup]); useEffect(() => { map?.on('render', handleMapRender); @@ -253,43 +181,43 @@ const EEZLayerPopup: FCWithMessages<{ layerId: number }> = ({ layerId }) => { }; }, [map, handleMapRender]); - const isFetchedLocations = locationQueries.every((query) => query.isFetched); - const isFetchingLocations = - !isFetchedLocations && locationQueries.some((query) => query.isFetching); - const isEmptyLocations = isFetchedLocations && locationQueries.some((query) => !query.data); - if (!DATA) return null; return (

{EEZName}

- {isFetchingLocations && {t('loading')}} - {isEmptyLocations && {t('no-data-available')}} - {!isEmptyLocations && ( + {isFetching &&
{t('loading')}
} + {!isFetching && !protectionCoverageStats && ( +
{t('no-data-available')}
+ )} + {!isFetching && !!protectionCoverageStats && ( <>
{t('marine-conservation-coverage')}
- {coveragePercentage !== '-' && + {formattedCoverage !== '-' && t.rich('percentage-bold', { - percentage: coveragePercentage, + percentage: formattedCoverage, b1: (chunks) => ( {chunks} ), b2: (chunks) => {chunks}, })} - {coveragePercentage === '-' && ( - {coveragePercentage} + {formattedCoverage === '-' && ( + {formattedCoverage} )}
{t('marine-protected-area', { - protectedArea: formatKM(locale, totalCumSumProtectedArea), - totalArea: formatKM(locale, totalMarineArea), + protectedArea: formatKM(locale, protectionCoverageStats.protectedArea), + totalArea: formatKM( + locale, + protectionCoverageStats.location.data.attributes.totalMarineArea + ), })}
- {locationCodes?.length === 1 && ( + {!!selectedLocationCode && (