From cf17e87be47c635caa2c19233d86c3fcf01b992f Mon Sep 17 00:00:00 2001
From: Conchez-Boueytou Robin
Date: Sat, 28 Mar 2026 15:23:24 +0100
Subject: [PATCH 01/26] :sparkles: ajout de la gestion des organisations et
services dans la page
---
.../(app)/mon-organisation/+page.server.ts | 16 +++++--
.../(app)/mon-organisation/+page.svelte | 46 +++++++++++++++++++
2 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/frontend/src/routes/(app)/mon-organisation/+page.server.ts b/frontend/src/routes/(app)/mon-organisation/+page.server.ts
index c6b7c626..c0bfae37 100644
--- a/frontend/src/routes/(app)/mon-organisation/+page.server.ts
+++ b/frontend/src/routes/(app)/mon-organisation/+page.server.ts
@@ -25,7 +25,7 @@ function formatMonthlyData(data: Array<{ label: string; value: number }>, locale
return result;
}
-export const load: PageServerLoad = async ({ fetch, request }) => {
+export const load: PageServerLoad = async ({ fetch, request, url }) => {
try {
const cookieHeader = request.headers.get('cookie') || '';
let locale = 'fr';
@@ -33,7 +33,9 @@ export const load: PageServerLoad = async ({ fetch, request }) => {
locale = 'en';
}
- const response = await fetch(`${BACKEND_URL}/mon-organisation`, {
+ let query = url.searchParams.toString();
+ console.log("query: " + query);
+ const response = await fetch(`${BACKEND_URL}/mon-organisation${query ? '?' + query : ''}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -42,7 +44,7 @@ export const load: PageServerLoad = async ({ fetch, request }) => {
credentials: 'include'
});
const result = await response.json();
-
+ console.log(result);
if (!result.success) {
return {
organisationData: null,
@@ -74,6 +76,10 @@ export const load: PageServerLoad = async ({ fetch, request }) => {
weeklyConsumption: result.weekly_consumption || [],
monthlyConsumption: formatMonthlyData(result.monthly_consumption || [], locale),
topPollutingSites: result.top_polluting_sites || [],
+ isAdmin: result.is_admin || false,
+ services: result.services || [],
+ currentOrgId: url.searchParams.get('org_id') || null,
+ currentServiceId: url.searchParams.get('service_id') || null,
};
} catch (error) {
@@ -89,6 +95,10 @@ export const load: PageServerLoad = async ({ fetch, request }) => {
weeklyConsumption: [],
monthlyConsumption: formatMonthlyData([], 'fr'),
topPollutingSites: [],
+ isAdmin: false,
+ services: [],
+ currentOrgId: null,
+ currentServiceId: null,
};
}
}
\ No newline at end of file
diff --git a/frontend/src/routes/(app)/mon-organisation/+page.svelte b/frontend/src/routes/(app)/mon-organisation/+page.svelte
index 9bd64222..0372dd04 100644
--- a/frontend/src/routes/(app)/mon-organisation/+page.svelte
+++ b/frontend/src/routes/(app)/mon-organisation/+page.svelte
@@ -8,6 +8,7 @@
import Advice from "$lib/components/widgets/Advice.svelte";
import type { PageData } from './$types';
import { t } from 'svelte-i18n';
+ import { goto } from '$app/navigation';
export let data: PageData;
@@ -40,6 +41,26 @@
$: consumptionData = selectedPeriod === 'daily' ? dailyConsumption
: selectedPeriod === 'weekly' ? weeklyConsumption
: monthlyConsumption;
+
+ $: isAdmin = data.isAdmin;
+ $: services = data.services || [];
+ $: userOrgs = data.userFull?.organisation || [];
+
+ let selectedOrgId = data.currentOrgId || '';
+ let selectedServiceId = data.currentServiceId || '';
+
+ $: {
+ if (!selectedOrgId && userOrgs.length > 0) {
+ selectedOrgId = userOrgs[0].id.toString();
+ }
+ }
+
+ function handleFilterChange() {
+ let qs = new URLSearchParams();
+ if (selectedOrgId) qs.set('org_id', selectedOrgId);
+ if (selectedServiceId) qs.set('service_id', selectedServiceId);
+ goto(`?${qs.toString()}`);
+ }
@@ -54,6 +75,31 @@
{ description }
{/if}
+
+ {#if isAdmin}
+
+ {#if userOrgs.length > 1}
+
+
+
+
+ {/if}
+
+
+
+
+
+
+ {/if}
{#if !noDatas}
From 26791b7d4fadf065252c6da012202d708ddb606c Mon Sep 17 00:00:00 2001
From: Conchez-Boueytou Robin
Date: Sat, 28 Mar 2026 15:23:38 +0100
Subject: [PATCH 02/26] =?UTF-8?q?:sparkles:=20am=C3=A9lioration=20de=20la?=
=?UTF-8?q?=20gestion=20des=20erreurs=20d'image=20dans=20OrganizationAvera?=
=?UTF-8?q?geDailyCarbonFootprint?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../widgets/OrganizationAverageDailyCarbonFootprint.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/lib/components/widgets/OrganizationAverageDailyCarbonFootprint.svelte b/frontend/src/lib/components/widgets/OrganizationAverageDailyCarbonFootprint.svelte
index 33f3e3d6..8359ada1 100644
--- a/frontend/src/lib/components/widgets/OrganizationAverageDailyCarbonFootprint.svelte
+++ b/frontend/src/lib/components/widgets/OrganizationAverageDailyCarbonFootprint.svelte
@@ -36,7 +36,7 @@
{#if equivalentAverage.icon}
-
e.currentTarget.src = '/images/equivalent.png'}>
+
{ const target = e.currentTarget; if(target instanceof HTMLImageElement) target.src = '/images/equivalent.png'; }}>
{/if}
From 05bef16bfdc3175d6cee5180da047685fd3b743c Mon Sep 17 00:00:00 2001
From: Conchez-Boueytou Robin
Date: Sat, 28 Mar 2026 15:23:53 +0100
Subject: [PATCH 03/26] =?UTF-8?q?:sparkles:=20ajout=20de=20la=20gestion=20?=
=?UTF-8?q?des=20requ=C3=AAtes=20pour=20les=20organisations=20et=20service?=
=?UTF-8?q?s=20dans=20le=20contr=C3=B4leur=20Mo?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/src/controllers/mo_controller.rs | 51 ++++++++++++++++++++----
1 file changed, 43 insertions(+), 8 deletions(-)
diff --git a/backend/src/controllers/mo_controller.rs b/backend/src/controllers/mo_controller.rs
index c02d6254..ce5a1489 100644
--- a/backend/src/controllers/mo_controller.rs
+++ b/backend/src/controllers/mo_controller.rs
@@ -1,8 +1,17 @@
+use axum::extract::Query;
+use serde::Deserialize;
+#[derive(Deserialize, Default)]
+#[serde(default)]
+pub struct MoQuery {
+ pub org_id: Option,
+ pub service_id: Option,
+}
use crate::dto::top_polluting_site_dto::TopPollutingSite;
use crate::green_score::calculate_green_score;
use crate::service::advice_service::AdviceService;
use crate::service::equivalent_service::EquivalentService;
use crate::service::organisation_service::OrganisationService;
+use crate::service::service_service::ServiceService;
use crate::models::equivalent::Equivalent;
use axum::extract::State;
use axum::Json;
@@ -25,17 +34,33 @@ pub struct MyOrganizationResponse {
pub weekly_consumption: Vec,
pub monthly_consumption: Vec,
pub top_polluting_sites: Vec,
+ pub is_admin: bool,
+ pub services: Option>,
}
-pub async fn mo(State(pool): State, AuthenticatedUser(user_full): AuthenticatedUser) -> Result, AppError> {
+pub async fn mo(
+ State(pool): State,
+ Query(query): Query,
+ AuthenticatedUser(user_full): AuthenticatedUser
+) -> Result, AppError> {
- let organization = user_full.organisation.first().ok_or(AppError::NotFound("User is not in an organization".to_string()))?;
+ let organization = if let Some(oid) = query.org_id {
+ user_full.organisation.iter().find(|o| o.id == oid).cloned().ok_or(AppError::NotFound("User is not in the specified organization".to_string()))?
+ } else {
+ user_full.organisation.first().cloned().ok_or(AppError::NotFound("User is not in an organization".to_string()))?
+ };
let organization_id = organization.id;
let user_id = user_full.user.id;
- let organization_informations = OrganisationService::organization_informations(&pool, organization_id, user_id).await
+ let service_id = if organization.est_admin {
+ query.service_id
+ } else {
+ user_full.service.map(|s| s.id)
+ };
+
+ let organization_informations = OrganisationService::organization_informations(&pool, organization_id, user_id, service_id).await
.map_err(AppError::from)?;
let advices: Vec = vec![
@@ -48,15 +73,23 @@ pub async fn mo(State(pool): State, AuthenticatedUser(user_full): Aut
let equivalents = EquivalentService::equivalent(&pool, Some(user_id), 2, organization_informations.total_consumption).await
.ok();
- let daily_consumption = OrganisationService::get_daily_organization_consumption(&pool, organization_id).await
+ let daily_consumption = OrganisationService::get_daily_organization_consumption(&pool, organization_id, service_id).await
.map_err(AppError::from)?;
- let weekly_consumption = OrganisationService::get_weekly_organization_consumption(&pool, organization_id).await
+ let weekly_consumption = OrganisationService::get_weekly_organization_consumption(&pool, organization_id, service_id).await
.map_err(AppError::from)?;
- let monthly_consumption = OrganisationService::get_monthly_organization_consumption(&pool, organization_id).await
+ let monthly_consumption = OrganisationService::get_monthly_organization_consumption(&pool, organization_id, service_id).await
.map_err(AppError::from)?;
- let top_polluting_sites = OrganisationService::get_top5_polluting_sites_by_organization(&pool, organization_id).await
+ let top_polluting_sites = OrganisationService::get_top5_polluting_sites_by_organization(&pool, organization_id, service_id).await
.map_err(AppError::from)?;
+ let is_admin = organization.est_admin;
+
+ let services = if is_admin {
+ ServiceService::get_organisation_services(&pool, organization_id).await.ok()
+ } else {
+ None
+ };
+
Ok(Json(MyOrganizationResponse {
success: true,
mo_infos: Some(organization_informations),
@@ -67,6 +100,8 @@ pub async fn mo(State(pool): State, AuthenticatedUser(user_full): Aut
daily_consumption,
weekly_consumption,
monthly_consumption,
- top_polluting_sites
+ top_polluting_sites,
+ is_admin,
+ services,
}))
}
\ No newline at end of file
From 759131c388663c8fa024e6ce68ca5cc997ced4aa Mon Sep 17 00:00:00 2001
From: Conchez-Boueytou Robin
Date: Sat, 28 Mar 2026 15:24:04 +0100
Subject: [PATCH 04/26] =?UTF-8?q?:sparkles:=20ajout=20de=20la=20gestion=20?=
=?UTF-8?q?des=20services=20dans=20les=20requ=C3=AAtes=20de=20consommation?=
=?UTF-8?q?=20pour=20les=20organisations?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../monitored_website_repository.rs | 138 ++++++++++++------
1 file changed, 96 insertions(+), 42 deletions(-)
diff --git a/backend/src/repository/monitored_website_repository.rs b/backend/src/repository/monitored_website_repository.rs
index 3144a32d..74dff5f2 100644
--- a/backend/src/repository/monitored_website_repository.rs
+++ b/backend/src/repository/monitored_website_repository.rs
@@ -52,22 +52,32 @@ impl MonitoredWebsiteRepository {
pub async fn get_top5_polluting_sites_by_organization(
pool: &MySqlPool,
- org_id: i64
+ org_id: i64, service_id: Option
) -> Result, Error> {
- let results = sqlx::query_as::<_, TopPollutingSite>(
+ let mut query = sqlx::QueryBuilder::new(
"SELECT
mw.url_domain,
SUM(mw.carbon_footprint) as total_footprint
FROM monitored_website mw
JOIN user u
ON u.id = mw.user_id
- WHERE u.organisation_id = ?
- AND mw.url_domain IS NOT NULL
+ JOIN organisation_user ou
+ ON ou.user_id = u.id
+ WHERE ou.organisation_id = "
+ );
+ query.push_bind(org_id);
+
+ if let Some(sid) = service_id {
+ query.push(" AND u.service_id = ");
+ query.push_bind(sid);
+ }
+
+ query.push(" AND mw.url_domain IS NOT NULL
GROUP BY mw.url_domain
ORDER BY total_footprint
- DESC LIMIT 5"
- )
- .bind(org_id)
+ DESC LIMIT 5");
+
+ let results = query.build_query_as::()
.fetch_all(pool)
.await?;
Ok(results)
@@ -96,9 +106,9 @@ impl MonitoredWebsiteRepository {
pub async fn average_daily_carbon_footprint_for_organization(
pool: &MySqlPool,
- org_id: i64
+ org_id: i64, service_id: Option
) -> f64 {
- sqlx::query_as::<_, (f64,)>(
+ let mut query = sqlx::QueryBuilder::new(
"SELECT ROUND(
COALESCE(
SUM(mw.carbon_footprint) / NULLIF(DATEDIFF(CURDATE(), MIN(DATE(mw.creation_date))) + 1, 0)
@@ -106,9 +116,17 @@ impl MonitoredWebsiteRepository {
, 2) AS average_daily_carbon_footprint
FROM monitored_website mw
JOIN user u ON u.id = mw.user_id
- WHERE u.organisation_id = ?"
- )
- .bind(org_id)
+ JOIN organisation_user ou ON ou.user_id = u.id
+ WHERE ou.organisation_id = "
+ );
+ query.push_bind(org_id);
+
+ if let Some(sid) = service_id {
+ query.push(" AND u.service_id = ");
+ query.push_bind(sid);
+ }
+
+ query.build_query_as::<(f64,)>()
.fetch_one(pool)
.await
.map(|(val,)| val)
@@ -117,16 +135,25 @@ impl MonitoredWebsiteRepository {
pub async fn total_organization_consumption(
pool: &MySqlPool,
- org_id: i64
+ org_id: i64, service_id: Option
) -> Result