From 7d324668ab42db8ee11dc666c4fa7b085aafb52f Mon Sep 17 00:00:00 2001 From: Antoine Quesnel Date: Fri, 14 Nov 2025 14:01:34 +0100 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20Changer=20la=20fa=C3=A7on=20de=20cro?= =?UTF-8?q?p=20les=20cases=20de=20"R=C3=A9partition"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/components/DataViz/Treemap.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/front/components/DataViz/Treemap.tsx b/front/components/DataViz/Treemap.tsx index a6275cf56..5dbdd8447 100644 --- a/front/components/DataViz/Treemap.tsx +++ b/front/components/DataViz/Treemap.tsx @@ -393,9 +393,12 @@ function Treemap({ className='pointer-events-none' >
{formatFirstLetterToUppercase(leaf.data.name)} From f4f5818075acc5a2f2b7bc31a1be113c06830cc3 Mon Sep 17 00:00:00 2001 From: Antoine Quesnel Date: Fri, 14 Nov 2025 16:09:55 +0100 Subject: [PATCH 2/4] refactor: gestion des scopes pour la comparaison --- .../interpeller/[siren]/step3/page.tsx | 4 +- .../marches_publics/comparison/route.ts | 5 ++- .../[siren]/subventions/comparison/route.ts | 5 ++- .../components/FicheHeader/FicheHeader.tsx | 2 +- .../FicheMarchesPublics/Comparison.tsx | 2 +- .../FicheSubventions/Comparison.tsx | 2 +- front/app/community/[siren]/page.tsx | 4 +- front/app/models/community.ts | 3 +- .../Communities/CommunityBasics.tsx | 4 +- .../DataViz/ComparisonContainer.tsx | 41 ++++++++++++------- .../components/DataViz/EvolutionContainer.tsx | 4 +- front/hooks/useTabState.ts | 38 ++--------------- .../communities/fetchCommunities-server.ts | 4 +- .../fetchMarchesPublicsComparison-server.ts | 30 ++++---------- .../fetchSubventionsComparison-server.ts | 31 ++++---------- front/utils/format.ts | 12 +++++- .../helpers/getDefaultComparisonScope.ts | 16 ++++---- front/utils/types.ts | 12 ++++++ front/utils/utils.ts | 31 +++++++++++--- 19 files changed, 124 insertions(+), 126 deletions(-) diff --git a/front/app/(visualiser)/interpeller/[siren]/step3/page.tsx b/front/app/(visualiser)/interpeller/[siren]/step3/page.tsx index b509bbaf9..5f291279c 100644 --- a/front/app/(visualiser)/interpeller/[siren]/step3/page.tsx +++ b/front/app/(visualiser)/interpeller/[siren]/step3/page.tsx @@ -18,7 +18,7 @@ async function getCommunity(siren: string) { export default async function InterpellateStep3({ params }: CommunityPageProps) { const { siren } = await params; const community = await getCommunity(siren); - const { type, nom } = community; + const { formattedType, nom } = community; const interpellateCommunityTemplateHtml = getEmailTemplate('interpellate-community'); return ( @@ -37,7 +37,7 @@ export default async function InterpellateStep3({ params }: CommunityPageProps) diff --git a/front/app/api/communities/[siren]/marches_publics/comparison/route.ts b/front/app/api/communities/[siren]/marches_publics/comparison/route.ts index b8fb06d42..245ead76a 100644 --- a/front/app/api/communities/[siren]/marches_publics/comparison/route.ts +++ b/front/app/api/communities/[siren]/marches_publics/comparison/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { fetchMarchesPublicsComparison } from '#utils/fetchers/marches-publics/fetchMarchesPublicsComparison-server'; +import { ScopeType } from '#utils/types'; export async function GET( request: NextRequest, @@ -9,13 +10,13 @@ export async function GET( try { const { siren } = await params; const { searchParams } = new URL(request.url); - const scope = searchParams.get('scope') || 'régional'; + const scopeType = searchParams.get('scope') as ScopeType || ScopeType.Region; if (siren === undefined) { throw new Error('Siren is not defined'); } - const data = await fetchMarchesPublicsComparison(siren, scope); + const data = await fetchMarchesPublicsComparison(siren, scopeType); return NextResponse.json(data); } catch (error) { diff --git a/front/app/api/communities/[siren]/subventions/comparison/route.ts b/front/app/api/communities/[siren]/subventions/comparison/route.ts index 8ebb0e6d8..76acccb49 100644 --- a/front/app/api/communities/[siren]/subventions/comparison/route.ts +++ b/front/app/api/communities/[siren]/subventions/comparison/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { fetchSubventionsComparison } from '#utils/fetchers/subventions/fetchSubventionsComparison-server'; +import { ScopeType } from '#utils/types'; export async function GET( request: NextRequest, @@ -9,13 +10,13 @@ export async function GET( try { const { siren } = await params; const { searchParams } = new URL(request.url); - const scope = searchParams.get('scope') || 'régional'; + const scopeType = searchParams.get('scope') as ScopeType || ScopeType.Region; if (siren === undefined) { throw new Error('Siren is not defined'); } - const data = await fetchSubventionsComparison(siren, scope); + const data = await fetchSubventionsComparison(siren, scopeType); return NextResponse.json(data); } catch (error) { diff --git a/front/app/community/[siren]/components/FicheHeader/FicheHeader.tsx b/front/app/community/[siren]/components/FicheHeader/FicheHeader.tsx index b3dee6e9c..6f927ecb4 100644 --- a/front/app/community/[siren]/components/FicheHeader/FicheHeader.tsx +++ b/front/app/community/[siren]/components/FicheHeader/FicheHeader.tsx @@ -18,7 +18,7 @@ const descriptionText = export function FicheHeader({ community, similarCommunityList }: FicheHeaderProps) { const communityTitle = community.nom; - const communityType = community.type; + const communityType = community.formattedType; const location = community.code_postal ? `${community.code_postal}` : ''; const departementName = community.nom_departement; diff --git a/front/app/community/[siren]/components/FicheMarchesPublics/Comparison.tsx b/front/app/community/[siren]/components/FicheMarchesPublics/Comparison.tsx index 405fa5ed4..9ed4e4ec2 100644 --- a/front/app/community/[siren]/components/FicheMarchesPublics/Comparison.tsx +++ b/front/app/community/[siren]/components/FicheMarchesPublics/Comparison.tsx @@ -6,7 +6,7 @@ import type { CommunityType } from '#utils/types'; type ComparisonProps = { siren: string; - communityType?: CommunityType; + communityType: CommunityType; }; export default function Comparison({ siren, communityType }: ComparisonProps) { diff --git a/front/app/community/[siren]/components/FicheSubventions/Comparison.tsx b/front/app/community/[siren]/components/FicheSubventions/Comparison.tsx index 755517641..343384c74 100644 --- a/front/app/community/[siren]/components/FicheSubventions/Comparison.tsx +++ b/front/app/community/[siren]/components/FicheSubventions/Comparison.tsx @@ -6,7 +6,7 @@ import type { CommunityType } from '#utils/types'; type ComparisonProps = { siren: string; - communityType?: CommunityType; + communityType: CommunityType; }; export default function Comparison({ siren, communityType }: ComparisonProps) { diff --git a/front/app/community/[siren]/page.tsx b/front/app/community/[siren]/page.tsx index dda7d46d4..bd159958f 100644 --- a/front/app/community/[siren]/page.tsx +++ b/front/app/community/[siren]/page.tsx @@ -66,12 +66,12 @@ export default async function CommunityPage({ params }: CommunityPageProps) {
diff --git a/front/app/models/community.ts b/front/app/models/community.ts index 9b66c95e0..cb0714823 100644 --- a/front/app/models/community.ts +++ b/front/app/models/community.ts @@ -7,7 +7,8 @@ export type Community = { /** Primary key [char9] */ siren: string; /** Primary key */ - type: CommunityType | string; + type: CommunityType; + formattedType: string; nom: string; code_insee: string; code_insee_departement: string; diff --git a/front/components/Communities/CommunityBasics.tsx b/front/components/Communities/CommunityBasics.tsx index 5bb642a1a..37f4fe41e 100644 --- a/front/components/Communities/CommunityBasics.tsx +++ b/front/components/Communities/CommunityBasics.tsx @@ -1,12 +1,12 @@ import type { Community } from '#app/models/community'; export default function CommunityBasics({ community }: { community: Community }) { - const { nom, type, code_postal, nom_departement } = community; + const { nom, formattedType, code_postal, nom_departement } = community; return (

{nom}

- {type} {nom_departement && <>· {nom_departement}} {code_postal && <>· {code_postal}} + {formattedType} {nom_departement && <>· {nom_departement}} {code_postal && <>· {code_postal}}

); diff --git a/front/components/DataViz/ComparisonContainer.tsx b/front/components/DataViz/ComparisonContainer.tsx index 0e9b02210..4c67f0fed 100644 --- a/front/components/DataViz/ComparisonContainer.tsx +++ b/front/components/DataViz/ComparisonContainer.tsx @@ -11,11 +11,10 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '#components/ui/dropdown-menu'; -import { type Scope, useComparisonScope } from '#hooks/useTabState'; import { downloadSVGChart } from '#utils/downloader/downloadSVGChart'; import { getDefaultComparisonScope } from '#utils/helpers/getDefaultComparisonScope'; import { hasRealComparisonData } from '#utils/placeholderDataGenerators'; -import type { CommunityType } from '#utils/types'; +import { CommunityType, ScopeType } from '#utils/types'; import { getMonetaryUnit } from '#utils/utils'; import { useQuery } from '@tanstack/react-query'; import { ChevronDown } from 'lucide-react'; @@ -24,17 +23,16 @@ import { TabHeader } from '../../app/community/[siren]/components/TabHeader'; import LoadingOverlay from '../ui/LoadingOverlay'; import ComparisonChart, { type ComparisonData, type ComparisonTheme } from './ComparisonChart'; import MobileComparisonChart from './MobileComparisonChart'; +import { formatScopeType } from '#utils/format'; type ComparisonContainerProps = { siren: string; - communityType?: CommunityType; + communityType: CommunityType; apiEndpoint: string; theme: ComparisonTheme; dataType: 'marches-publics' | 'subventions'; }; -const SCOPES = ['Départemental', 'Régional', 'National'] as const; - // Generic fetch function const fetchComparisonData = async (endpoint: string, scope: string): Promise => { const response = await fetch(`${endpoint}?scope=${scope.toLowerCase()}`); @@ -46,14 +44,26 @@ const fetchComparisonData = async (endpoint: string, scope: string): Promise { + if(communtyType === CommunityType.Region) { + return [ScopeType.Nation]; + }else if(communtyType === CommunityType.Departement) { + return [ScopeType.Nation, ScopeType.Region]; + }else{ + return [ScopeType.Nation, ScopeType.Region, ScopeType.Departement]; + } +}; + // Extracted components for better composition const ScopeDropdown = ({ selectedScope, onScopeChange, - disabled, + communityType, + disabled }: { - selectedScope: Scope; - onScopeChange: (scope: Scope) => void; + selectedScope: ScopeType; + onScopeChange: (scope: ScopeType) => void; + communityType: CommunityType; disabled?: boolean; }) => ( @@ -63,14 +73,14 @@ const ScopeDropdown = ({ className='h-12 gap-2 rounded-bl-none rounded-br-lg rounded-tl-lg rounded-tr-none border-gray-300 bg-white px-4 hover:bg-gray-50' disabled={disabled} > - {selectedScope} + {formatScopeType(selectedScope)} - {SCOPES.map((scope) => ( + {getAvailableScopes(communityType).map((scope) => ( onScopeChange(scope)}> - {scope} + {formatScopeType(scope)} ))} @@ -221,7 +231,7 @@ const ChartWithLegend = ({ {data[0]?.regionalLabel || 'Moyenne régionale'} -
Montants exprimés en {unit}
+
Montants exprimés {unit}
@@ -235,8 +245,8 @@ export default function ComparisonContainer({ theme, dataType, }: ComparisonContainerProps) { - const defaultScope = communityType ? getDefaultComparisonScope(communityType) : 'Départemental'; - const [selectedScope, setSelectedScope] = useComparisonScope(defaultScope); + const defaultScope = communityType ? getDefaultComparisonScope(communityType) : ScopeType.Departement; + const [selectedScope, setSelectedScope] = useState(defaultScope); const [isMobile, setIsMobile] = useState(false); const chartContainerRef = useRef(null); @@ -338,7 +348,7 @@ export default function ComparisonContainer({ ); }; - const handleScopeChange = (scope: Scope) => { + const handleScopeChange = (scope: ScopeType) => { if (scope !== selectedScope) { setSelectedScope(scope); } @@ -359,6 +369,7 @@ export default function ComparisonContainer({ = { - Départemental: SCOPE_VALUES.DEPARTEMENTAL, - Régional: SCOPE_VALUES.REGIONAL, - National: SCOPE_VALUES.NATIONAL, -}; - -// Map URL values to display values -const URL_SCOPE_MAP: Record = { - [SCOPE_VALUES.DEPARTEMENTAL]: 'Départemental', - [SCOPE_VALUES.REGIONAL]: 'Régional', - [SCOPE_VALUES.NATIONAL]: 'National', -}; - -export function useComparisonScope(defaultValue: Scope = 'Départemental') { - const [urlScope, setUrlScope] = useQueryState( +export function useComparisonScope(defaultValue: ScopeType = ScopeType.Departement) { + return useQueryState( 'scope', - parseAsString.withDefault(SCOPE_URL_MAP[defaultValue]), + parseAsString.withDefault(defaultValue), ); - - const displayScope = URL_SCOPE_MAP[urlScope] || defaultValue; - - const setScope = (scope: Scope) => { - setUrlScope(SCOPE_URL_MAP[scope]); - }; - - return [displayScope, setScope] as const; } diff --git a/front/utils/fetchers/communities/fetchCommunities-server.ts b/front/utils/fetchers/communities/fetchCommunities-server.ts index 1ee57e9f7..1e644bfb3 100644 --- a/front/utils/fetchers/communities/fetchCommunities-server.ts +++ b/front/utils/fetchers/communities/fetchCommunities-server.ts @@ -1,7 +1,7 @@ import type { Community } from '#app/models/community'; import { getQueryFromPool } from '#utils/db'; import { formatCommunityType, formatDepartmentName, formatLocationName } from '#utils/format'; -import { CommunityType } from '#utils/types.js'; +import { CommunityType } from '#utils/types'; import type { Pagination } from '../types'; import { type CommunitiesOptions, createSQLQueryParams } from './createSQLQueryParams'; @@ -23,7 +23,7 @@ export async function fetchCommunities( return communities.map((community) => ({ ...community, nom: formatLocationName(community.nom), - type: formatCommunityType(community.type as CommunityType), + formattedType: formatCommunityType(community.type as CommunityType), nom_departement: community.nom_departement ? formatDepartmentName(community.nom_departement) : null, diff --git a/front/utils/fetchers/marches-publics/fetchMarchesPublicsComparison-server.ts b/front/utils/fetchers/marches-publics/fetchMarchesPublicsComparison-server.ts index 9a4783b06..c815e689d 100644 --- a/front/utils/fetchers/marches-publics/fetchMarchesPublicsComparison-server.ts +++ b/front/utils/fetchers/marches-publics/fetchMarchesPublicsComparison-server.ts @@ -1,20 +1,22 @@ import type { MarchesPublicsComparisonData } from '#app/models/comparison'; import { getQueryFromPool } from '#utils/db'; +import { formatScopeType } from '#utils/format'; +import { ScopeType } from '#utils/types'; +import { formatFirstLetterToLowercase } from '#utils/utils'; import { DataTable } from '../constants'; const TABLE_NAME = DataTable.MarchesPublics; const COMMUNITIES_TABLE = DataTable.Communities; -function createSQLQueryParams(siren: string, scope: string): [string, (string | number)[]] { +function createSQLQueryParams(siren: string, scopeType: ScopeType): [string, (string | number)[]] { const values = [siren, siren, siren]; // We need siren multiple times // Build scope condition dynamically - handle both French and English scope names let scopeCondition: string | null = ''; - const normalizedScope = scope.toLowerCase(); - if (normalizedScope === 'régional' || normalizedScope === 'regional') { + if (scopeType === ScopeType.Region) { scopeCondition = 'code_insee_region'; - } else if (normalizedScope === 'départemental' || normalizedScope === 'departmental') { + } else if (scopeType === ScopeType.Departement) { scopeCondition = 'code_insee_dept'; } else { // For national, we don't filter by region/department @@ -87,10 +89,10 @@ function createSQLQueryParams(siren: string, scope: string): [string, (string | */ export async function fetchMarchesPublicsComparison( siren: string, - scope = 'régional', + scopeType: ScopeType = ScopeType.Region, ): Promise { try { - const params = createSQLQueryParams(siren, scope); + const params = createSQLQueryParams(siren, scopeType); const rows = (await getQueryFromPool(...params)) as Array<{ year: string; community: number; @@ -102,22 +104,8 @@ export async function fetchMarchesPublicsComparison( if (!rows || rows.length === 0) return []; const communityName = rows[0].community_name; - const communityType = rows[0].community_type; - const communityLabel = `Budget de ${communityName}`; - - // Generate proper French labels - const normalizedScope = scope.toLowerCase(); - let scopeLabel = ''; - if (normalizedScope === 'régional' || normalizedScope === 'regional') { - scopeLabel = 'régionale'; - } else if (normalizedScope === 'départemental' || normalizedScope === 'departmental') { - scopeLabel = 'départementale'; - } else { - scopeLabel = 'nationale'; - } - - const regionalLabel = `Moyenne ${scopeLabel} des collectivités ${communityType.toLowerCase()}s`; + const regionalLabel = `Moyenne des budgets des collectivités ${formatFirstLetterToLowercase(formatScopeType(scopeType))}es`; return rows.map((row) => ({ year: row.year, diff --git a/front/utils/fetchers/subventions/fetchSubventionsComparison-server.ts b/front/utils/fetchers/subventions/fetchSubventionsComparison-server.ts index d2a90b130..c828a1e31 100644 --- a/front/utils/fetchers/subventions/fetchSubventionsComparison-server.ts +++ b/front/utils/fetchers/subventions/fetchSubventionsComparison-server.ts @@ -1,20 +1,21 @@ import type { SubventionsComparisonData } from '#app/models/comparison'; import { getQueryFromPool } from '#utils/db'; +import { formatScopeType } from '#utils/format'; +import { ScopeType } from '#utils/types'; +import { formatFirstLetterToLowercase } from '#utils/utils'; import { DataTable } from '../constants'; const TABLE_NAME = DataTable.Subventions; const COMMUNITIES_TABLE = DataTable.Communities; -function createSQLQueryParams(siren: string, scope: string): [string, (string | number)[]] { +function createSQLQueryParams(siren: string, scopeType: ScopeType): [string, (string | number)[]] { const values = [siren, siren, siren]; // We need siren multiple times - // Build scope condition dynamically - handle both French and English scope names let scopeCondition: string | null = ''; - const normalizedScope = scope.toLowerCase(); - if (normalizedScope === 'régional' || normalizedScope === 'regional') { + if (scopeType === ScopeType.Region) { scopeCondition = 'code_insee_region'; - } else if (normalizedScope === 'départemental' || normalizedScope === 'departmental') { + } else if (scopeType === ScopeType.Departement) { scopeCondition = 'code_insee_dept'; } else { // For national, we don't filter by region/department @@ -87,10 +88,10 @@ function createSQLQueryParams(siren: string, scope: string): [string, (string | */ export async function fetchSubventionsComparison( siren: string, - scope = 'régional', + scopeType : ScopeType= ScopeType.Region, ): Promise { try { - const params = createSQLQueryParams(siren, scope); + const params = createSQLQueryParams(siren, scopeType); const rows = (await getQueryFromPool(...params)) as Array<{ year: string; community: number; @@ -102,22 +103,8 @@ export async function fetchSubventionsComparison( if (!rows || rows.length === 0) return []; const communityName = rows[0].community_name; - const communityType = rows[0].community_type; - const communityLabel = `Budget de ${communityName}`; - - // Generate proper French labels - const normalizedScope = scope.toLowerCase(); - let scopeLabel = ''; - if (normalizedScope === 'régional' || normalizedScope === 'regional') { - scopeLabel = 'régionale'; - } else if (normalizedScope === 'départemental' || normalizedScope === 'departmental') { - scopeLabel = 'départementale'; - } else { - scopeLabel = 'nationale'; - } - - const regionalLabel = `Moyenne ${scopeLabel} des collectivités ${communityType.toLowerCase()}s`; + const regionalLabel = `Moyenne des budgets des collectivités ${formatFirstLetterToLowercase(formatScopeType(scopeType))}es`; return rows.map((row) => ({ year: row.year, diff --git a/front/utils/format.ts b/front/utils/format.ts index 4e15958b9..e7b892663 100644 --- a/front/utils/format.ts +++ b/front/utils/format.ts @@ -1,4 +1,4 @@ -import { CommunityType } from './types'; +import { CommunityType, ScopeType } from './types'; export function formatDate( date: Date | string | number | undefined, @@ -33,6 +33,16 @@ export function formatCommunityType(type: CommunityType): string { return typeLabels[type] || type; } +export function formatScopeType(type: ScopeType): string { + const typeLabels: Record = { + [ScopeType.Departement]: 'Départemental', + [ScopeType.Region]: 'Régional', + [ScopeType.Nation]: 'National', + }; + + return typeLabels[type] || type; +} + /** * Normalize French location names (cities, departments) formatting * according to French typographic conventions diff --git a/front/utils/helpers/getDefaultComparisonScope.ts b/front/utils/helpers/getDefaultComparisonScope.ts index c470a8636..258956bc4 100644 --- a/front/utils/helpers/getDefaultComparisonScope.ts +++ b/front/utils/helpers/getDefaultComparisonScope.ts @@ -1,6 +1,4 @@ -import { CommunityType } from '#utils/types'; - -export type Scope = 'Départemental' | 'Régional' | 'National'; +import { CommunityType, ScopeType } from '#utils/types'; /** * Détermine le scope de comparaison par défaut selon le type de collectivité @@ -11,21 +9,21 @@ export type Scope = 'Départemental' | 'Régional' | 'National'; * - Commune/Métropole/EPCI : Départemental (compare avec autres du département) * - CTU : National (collectivités spéciales) */ -export function getDefaultComparisonScope(communityType: CommunityType): Scope { +export function getDefaultComparisonScope(communityType: CommunityType): ScopeType { switch (communityType) { case CommunityType.Region: - return 'National'; + return ScopeType.Nation; case CommunityType.Departement: - return 'Régional'; + return ScopeType.Region; case CommunityType.Commune: case CommunityType.Metropole: case CommunityType.CA: case CommunityType.CC: case CommunityType.EPT: - return 'Départemental'; + return ScopeType.Departement; case CommunityType.CTU: - return 'National'; + return ScopeType.Nation; default: - return 'Départemental'; // Fallback par défaut + return ScopeType.Departement; // Fallback par défaut } } diff --git a/front/utils/types.ts b/front/utils/types.ts index a150e3003..e4401b704 100644 --- a/front/utils/types.ts +++ b/front/utils/types.ts @@ -13,3 +13,15 @@ export enum CommunityType { /** Etablissement public territorial */ EPT = 'EPT', } + +export enum OrderMagnitudeMonetaryUnit { + Thousands = "en milliers d'€", + Millions = "en millions d'€", + Billions = "en milliards d'€", +} + +export enum ScopeType { + Departement = "departemental", + Region = "regional", + Nation = "national", +} \ No newline at end of file diff --git a/front/utils/utils.ts b/front/utils/utils.ts index a8fdd0f5a..78136b967 100644 --- a/front/utils/utils.ts +++ b/front/utils/utils.ts @@ -3,7 +3,7 @@ import { twMerge } from 'tailwind-merge'; import { GRAPH_START_YEAR } from './constants'; import type { Direction } from './fetchers/types'; -import { CommunityType } from './types'; +import { CommunityType, OrderMagnitudeMonetaryUnit } from './types'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -104,21 +104,34 @@ export function formatCompactPriceInteger( /** * Determine the appropriate unit (M€ or k€) based on max value */ -export function getMonetaryUnit(maxValue: number): 'M€' | 'k€' { - return maxValue >= 1000000 ? 'M€' : 'k€'; +export function getMonetaryUnit(maxValue: number): OrderMagnitudeMonetaryUnit { + if(maxValue < 1_000_000){ + return OrderMagnitudeMonetaryUnit.Thousands; + }else if(maxValue >= 1_000_000 && maxValue < 1_000_000_000){ + return OrderMagnitudeMonetaryUnit.Millions; + }else{ + return OrderMagnitudeMonetaryUnit.Billions; + } } /** * Get the divisor for the unit (1000000 for M€, 1000 for k€) */ -export function getMonetaryDivisor(unit: 'M€' | 'k€'): number { - return unit === 'M€' ? 1000000 : 1000; +export function getMonetaryDivisor(unit: OrderMagnitudeMonetaryUnit): number { + switch (unit) { + case OrderMagnitudeMonetaryUnit.Billions: + return 1_000_000_000; + case OrderMagnitudeMonetaryUnit.Millions: + return 1_000_000; + case OrderMagnitudeMonetaryUnit.Thousands: + return 1_000; + } } /** * Format a value with the appropriate unit, max 1 decimal place */ -export function formatMonetaryValue(value: number, unit: 'M€' | 'k€'): string { +export function formatMonetaryValue(value: number, unit: OrderMagnitudeMonetaryUnit): string { const divisor = getMonetaryDivisor(unit); const normalizedValue = value / divisor; return new Intl.NumberFormat('fr-FR', { @@ -161,6 +174,12 @@ export function formatFirstLetterToUppercase(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); } +export function formatFirstLetterToLowercase(str: string): string { + if (!str?.trim()) return ''; + if (str.length === 1) return str.toLowerCase(); + return str.charAt(0).toLowerCase() + str.slice(1); +} + export function formatSentenceCase(str: string): string { if (!str?.trim()) return ''; From e7b07f50798eb251a66d3434e78148c97a72330f Mon Sep 17 00:00:00 2001 From: Antoine Quesnel Date: Fri, 14 Nov 2025 16:26:17 +0100 Subject: [PATCH 3/4] refactor: update amount unit --- front/app/(comprendre)/methodologie/page.tsx | 2 +- .../[comparedSiren]/components/HeaderComparison.tsx | 2 +- front/app/community/[siren]/components/CommunityDetails.tsx | 6 +++--- front/utils/utils.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/front/app/(comprendre)/methodologie/page.tsx b/front/app/(comprendre)/methodologie/page.tsx index 4506c5567..ce884502b 100644 --- a/front/app/(comprendre)/methodologie/page.tsx +++ b/front/app/(comprendre)/methodologie/page.tsx @@ -136,7 +136,7 @@ export default function Page() {
  • l'indice de transparence des marchés publics
  • l'indice de transparence agrégé des 2 indices précédents
  • -

    Calcul de l'indice de transparence des subventions

    +

    Calcul de l'indice de transparence des marchés publics

    L'indice de transparence des marchés publics est établi selon la conjonction de 3 facteurs principaux : diff --git a/front/app/community/[siren]/comparison/[comparedSiren]/components/HeaderComparison.tsx b/front/app/community/[siren]/comparison/[comparedSiren]/components/HeaderComparison.tsx index 2cc6e0d9c..8194e14c6 100644 --- a/front/app/community/[siren]/comparison/[comparedSiren]/components/HeaderComparison.tsx +++ b/front/app/community/[siren]/comparison/[comparedSiren]/components/HeaderComparison.tsx @@ -116,7 +116,7 @@ function MobileHeaderCard({ {/* Section Budget Total */}

    {renderInfoBlock( - 'Budget total (M€)', + 'Budget total (en millions d’€)', budgetTotal1 ? formatNumberInteger(Math.round(budgetTotal1 / 1_000_000)) : '—', budgetTotal2 ? formatNumberInteger(Math.round(budgetTotal2 / 1_000_000)) : '—', 'bg-brand-3', diff --git a/front/app/community/[siren]/components/CommunityDetails.tsx b/front/app/community/[siren]/components/CommunityDetails.tsx index 40d4bec00..9de946986 100644 --- a/front/app/community/[siren]/components/CommunityDetails.tsx +++ b/front/app/community/[siren]/components/CommunityDetails.tsx @@ -25,7 +25,7 @@ export function CommunityDetails({ community, compare, left, budgetTotal }: Comm

    - {label === 'Budget total' && unit === 'M€' ? ( + {label === 'Budget total' ? ( <> - Budget total (M€) + Budget total (en millions d’€) {label} ) : ( diff --git a/front/utils/utils.ts b/front/utils/utils.ts index 78136b967..7e1f806c8 100644 --- a/front/utils/utils.ts +++ b/front/utils/utils.ts @@ -102,7 +102,7 @@ export function formatCompactPriceInteger( } /** - * Determine the appropriate unit (M€ or k€) based on max value + * Determine the appropriate unit based on max value */ export function getMonetaryUnit(maxValue: number): OrderMagnitudeMonetaryUnit { if(maxValue < 1_000_000){ @@ -115,7 +115,7 @@ export function getMonetaryUnit(maxValue: number): OrderMagnitudeMonetaryUnit { } /** - * Get the divisor for the unit (1000000 for M€, 1000 for k€) + * Get the divisor for the unit */ export function getMonetaryDivisor(unit: OrderMagnitudeMonetaryUnit): number { switch (unit) { From 53e6c8dc42b81e96d989d25f5ec3930fc7594baf Mon Sep 17 00:00:00 2001 From: Antoine Quesnel Date: Fri, 21 Nov 2025 15:39:06 +0100 Subject: [PATCH 4/4] remove unused import --- front/app/community/[siren]/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/front/app/community/[siren]/page.tsx b/front/app/community/[siren]/page.tsx index bd159958f..4fbe843f0 100644 --- a/front/app/community/[siren]/page.tsx +++ b/front/app/community/[siren]/page.tsx @@ -6,7 +6,6 @@ import { fetchCommunityBudgetTotal } from '#utils/fetchers/communities-accounts/ import { fetchCommunities } from '#utils/fetchers/communities/fetchCommunities-server'; import { fetchSimilarCommunityList } from '#utils/fetchers/communities/fetchSimilarCommunityList-server'; import { fetchMostRecentTransparencyScore } from '#utils/fetchers/communities/fetchTransparencyScore-server'; -import type { CommunityType } from '#utils/types'; import { TransparencyScore } from '@/components/TransparencyScore/constants'; import { FicheHeader } from './components/FicheHeader/FicheHeader';