Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions front/app/api/map/communes/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,27 @@ export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const codes = searchParams.getAll('codes');
const year = searchParams.get('year') || '2024';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const year = searchParams.get('year') || '2024';
const year = searchParams.get('year') || '2023';

Les derniers scores de transparence ont été calculés sur 2023 et pas 2024. C'est pour ça qu'on a que des E sur les régions et départements (ce qui n'est pas le cas en 2023 même si ça reste pas élevé...)


if (!codes || codes.length === 0) {
return NextResponse.json({ error: 'No commune codes provided' }, { status: 400 });
}

const values = [...codes, year];
const placeholders = codes.map((_, index) => `$${index + 1}`).join(',');
const query = `
SELECT
SELECT
c.*,
b.subventions_score,
b.mp_score
FROM collectivites c
LEFT JOIN bareme b
ON c.siren = b.siren AND b.annee = 2024
ON c.siren = b.siren AND b.annee = $${values.length}
WHERE c.code_insee IN (${placeholders})
AND c.type = 'COM'
`;

const communes = await getQueryFromPool(query, codes);
const communes = await getQueryFromPool(query, values);

return NextResponse.json({ communes });
} catch (error) {
Expand All @@ -37,7 +39,7 @@ export async function GET(request: NextRequest) {
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { codes } = body;
const { codes, year = 2024 } = body;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { codes, year = 2024 } = body;
const { codes, year = 2023 } = body;


if (!codes || !Array.isArray(codes) || codes.length === 0) {
return NextResponse.json(
Expand All @@ -48,20 +50,21 @@ export async function POST(request: NextRequest) {

console.log(`Received POST request for ${codes.length} communes`);

const values = [...codes, year];
const placeholders = codes.map((_, index) => `$${index + 1}`).join(',');
const query = `
SELECT
SELECT
c.*,
b.subventions_score,
b.mp_score
FROM collectivites c
LEFT JOIN bareme b
ON c.siren = b.siren AND b.annee = 2024
ON c.siren = b.siren AND b.annee = $${values.length}
WHERE c.code_insee IN (${placeholders})
AND c.type = 'COM'
`;

const communes = await getQueryFromPool(query, codes);
const communes = await getQueryFromPool(query, values);

return NextResponse.json({ communes });
} catch (error) {
Expand Down
8 changes: 5 additions & 3 deletions front/app/api/map/departements/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,27 @@ export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const codes = searchParams.getAll('codes');
const year = searchParams.get('year') || '2024';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const year = searchParams.get('year') || '2024';
const year = searchParams.get('year') || '2023';


if (!codes || codes.length === 0) {
return NextResponse.json({ error: 'No commune codes provided' }, { status: 400 });
}

const values = [...codes, year];
const placeholders = codes.map((_, index) => `$${index + 1}`).join(',');
const query = `
SELECT
SELECT
c.*,
b.subventions_score,
b.mp_score
FROM collectivites c
LEFT JOIN bareme b
ON c.siren = b.siren AND b.annee = 2024
ON c.siren = b.siren AND b.annee = $${values.length}
WHERE c.code_insee IN (${placeholders})
AND c.type = 'DEP'
`;

const departements = await getQueryFromPool(query, codes);
const departements = await getQueryFromPool(query, values);
return NextResponse.json({ departements });
} catch (error) {
console.error('Database error:', error);
Expand Down
9 changes: 6 additions & 3 deletions front/app/api/map/regions/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,27 @@ export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const codes = searchParams.getAll('codes');
const year = searchParams.get('year') || '2024';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const year = searchParams.get('year') || '2024';
const year = searchParams.get('year') || '2023';


if (!codes || codes.length === 0) {
return NextResponse.json({ error: 'No region codes provided' }, { status: 400 });
}

const values = [...codes, year];
const placeholders = codes.map((_, index) => `$${index + 1}`).join(',');
const query = `
SELECT
SELECT
c.*,
b.subventions_score,
b.mp_score
FROM collectivites c
LEFT JOIN bareme b
ON c.siren = b.siren AND b.annee = 2024
ON c.siren = b.siren AND b.annee = $${values.length}
WHERE c.code_insee_region IN (${placeholders})
AND c.type = 'REG'
`;

const regions = await getQueryFromPool(query, codes);
const regions = await getQueryFromPool(query, values);
return NextResponse.json({ regions });
} catch (error) {
console.error('Database error:', error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,12 @@ const MobileMPSubvCard = memo(({ siren1, siren2, year, comparisonType }: MobileM
return <Loading />;
}

if (
isError1 ||
isError2 ||
!data1 ||
!data2 ||
data1.top5 === undefined ||
data2.top5 === undefined
) {
// Check if both communities have no data
const hasData1 = !isError1 && data1 && data1.top5 !== undefined;
const hasData2 = !isError2 && data2 && data2.top5 !== undefined;

// Only show empty state if BOTH communities have no data
if (!hasData1 && !hasData2) {
return (
<EmptyState
title={`Aucune donnée de ${comparisonName} disponible`}
Expand All @@ -218,45 +216,67 @@ const MobileMPSubvCard = memo(({ siren1, siren2, year, comparisonType }: MobileM

return (
<Card className='space-y-6 p-4'>
{/* Comparaison Montant Total */}
<div className='border-b pb-4'>
{renderInfoBlock(
'Montant total',
formatCompactPrice(data1.total_amount),
formatCompactPrice(data2.total_amount),
)}
</div>
{/* Comparaison Montant Total - Only show if both have data */}
{hasData1 && hasData2 && (
<div className='border-b pb-4'>
{renderInfoBlock(
'Montant total',
formatCompactPrice(data1.total_amount),
formatCompactPrice(data2.total_amount),
)}
</div>
)}

{/* Comparaison Nombre */}
<div className='border-b pb-4'>
{renderInfoBlock(
`Nombre de ${comparisonName}`,
data1.total_number.toString(),
data2.total_number.toString(),
)}
</div>
{/* Comparaison Nombre - Only show if both have data */}
{hasData1 && hasData2 && (
<div className='border-b pb-4'>
{renderInfoBlock(
`Nombre de ${comparisonName}`,
data1.total_number.toString(),
data2.total_number.toString(),
)}
</div>
)}

{/* Tableaux détaillés */}
<div className='space-y-6'>
<TableInfoBlock
totalAmount={data1.total_amount}
totalNumber={data1.total_number}
top5Items={data1.top5}
comparisonName={comparisonName}
columnLabel={getColumnLabel(comparisonType)}
communityName={formatLocationName(data1?.community_name || 'N/A')}
bgColor='bg-brand-3'
/>
{hasData1 ? (
<TableInfoBlock
totalAmount={data1.total_amount}
totalNumber={data1.total_number}
top5Items={data1.top5}
comparisonName={comparisonName}
columnLabel={getColumnLabel(comparisonType)}
communityName={formatLocationName(data1?.community_name || 'N/A')}
bgColor='bg-brand-3'
/>
) : (
<EmptyState
title={`Aucune donnée de ${comparisonName} disponible`}
description={`Il n'y a pas de données de ${comparisonName} disponibles pour cette période pour ${formatLocationName(data1?.community_name || 'cette collectivité')}.`}
siren={siren1}
className='h-full'
/>
)}

<TableInfoBlock
totalAmount={data2.total_amount}
totalNumber={data2.total_number}
top5Items={data2.top5}
comparisonName={comparisonName}
columnLabel={getColumnLabel(comparisonType)}
communityName={formatLocationName(data2?.community_name || 'N/A')}
bgColor='bg-primary-light'
/>
{hasData2 ? (
<TableInfoBlock
totalAmount={data2.total_amount}
totalNumber={data2.total_number}
top5Items={data2.top5}
comparisonName={comparisonName}
columnLabel={getColumnLabel(comparisonType)}
communityName={formatLocationName(data2?.community_name || 'N/A')}
bgColor='bg-primary-light'
/>
) : (
<EmptyState
title={`Aucune donnée de ${comparisonName} disponible`}
description={`Il n'y a pas de données de ${comparisonName} disponibles pour cette période pour ${formatLocationName(data2?.community_name || 'cette collectivité')}.`}
siren={siren2}
className='h-full'
/>
)}
</div>
</Card>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { RefObject } from 'react';
import type { RefObject } from 'react';

import {
Bar,
Expand Down Expand Up @@ -151,7 +151,7 @@ export default function DesktopEvolutionChart({
)}
</Bar>
<XAxis dataKey='year' axisLine tickLine />
<YAxis tickFormatter={hasRealData ? (value) => formatValue(value) : () => ''} />
<YAxis tickFormatter={hasRealData ? (value) => formatValue(value) : () => ''} hide />
</RechartsBarChart>
</ResponsiveContainer>
</div>
Expand Down
22 changes: 17 additions & 5 deletions front/app/community/[siren]/components/hooks/useChartData.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useMemo } from 'react';

import { formatMonetaryValue, getMonetaryUnit } from '#utils/utils';
import { formatMonetaryValue, formatNumberInteger, getMonetaryUnit } from '#utils/utils';

export type ChartDataType = 'marches-publics' | 'subventions';
export type DisplayMode = 'amounts' | 'counts';

type ChartDataItem = {
year: number;
Expand All @@ -12,17 +13,28 @@ type ChartDataItem = {
type UseChartDataProps = {
data: ChartDataItem[];
chartType: ChartDataType;
displayMode?: DisplayMode;
};

export const useChartData = ({ data, chartType }: UseChartDataProps) => {
export const useChartData = ({ data, chartType, displayMode = 'amounts' }: UseChartDataProps) => {
// Calculate values
const allValues = useMemo(() => data.flatMap((d) => [d.value]), [data]);
const maxValue = useMemo(() => (allValues.length > 0 ? Math.max(...allValues) : 0), [allValues]);
const avgValue = useMemo(() => maxValue / 2, [maxValue]);

// Determine unit and format function
const unit = useMemo(() => getMonetaryUnit(maxValue), [maxValue]);
const formatValue = useMemo(() => (value: number) => formatMonetaryValue(value, unit), [unit]);
// Determine unit and format function based on display mode
const unit = useMemo(
() => (displayMode === 'amounts' ? getMonetaryUnit(maxValue) : undefined),
[displayMode, maxValue],
);

const formatValue = useMemo(
() =>
displayMode === 'counts'
? (value: number) => formatNumberInteger(value)
: (value: number) => formatMonetaryValue(value, unit!),
[displayMode, unit],
);

// Chart colors based on type
const { barColor, borderColor } = useMemo(() => {
Expand Down
1 change: 1 addition & 0 deletions front/components/DataViz/ComparisonChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ export default function ComparisonChart({
axisLine={{ stroke: '#e5e7eb' }}
tickFormatter={(value) => formatValue(value)}
domain={[0, yAxisMax]}
hide
/>
<Tooltip content={<CustomTooltip theme={theme} />} />

Expand Down
2 changes: 1 addition & 1 deletion front/components/DataViz/EvolutionChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function EvolutionChart({
}));

// Use shared chart data logic for formatting - always call hooks
const chartData = useChartData({ data: basicChartData, chartType: dataType });
const chartData = useChartData({ data: basicChartData, chartType: dataType, displayMode });
const { unit, formatValue, avgValue, chartDataForDisplay } = chartData;

// Show error state only if there's an actual error
Expand Down
2 changes: 1 addition & 1 deletion front/components/DataViz/MobileEvolutionChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default function MobileEvolutionChart({
});

// Use shared chart data logic for formatting - always call hooks
const chartData = useChartData({ data: streamingState.data, chartType: dataType });
const chartData = useChartData({ data: streamingState.data, chartType: dataType, displayMode });
const { unit, formatValue } = chartData;

// Show error state only if there's an actual error
Expand Down
46 changes: 46 additions & 0 deletions front/components/Map/AdminLevelControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client';

export type AdminLevel = 'regions' | 'departements' | 'communes';

interface AdminLevelControlProps {
selectedLevel: AdminLevel;
onSelectLevel: (level: AdminLevel) => void;
}

const levels: { value: AdminLevel; label: string }[] = [
{ value: 'regions', label: 'Régions' },
{ value: 'departements', label: 'Départements' },
{ value: 'communes', label: 'Communes' },
];

export default function AdminLevelControl({
selectedLevel,
onSelectLevel,
}: AdminLevelControlProps) {
return (
<div className='mb-4 mb-8 lg:mb-8'>
<div className='mb-2 mb-4 flex items-center lg:mb-4'>
<span className='mr-2 flex h-7 w-7 items-center justify-center rounded-full bg-primary font-kanit-bold text-sm font-bold text-white'>
5
</span>
<h4 className='text-sm text-primary lg:text-base'>Niveau d'affichage</h4>
</div>

{/* Three buttons for level selection */}
<div className='flex flex-col gap-2'>
{levels.map((level) => (
<button
key={level.value}
type='button'
onClick={() => onSelectLevel(level.value)}
className={`rounded-tl-br-lg border border-black px-4 py-2 font-kanit-bold text-sm transition ${
selectedLevel === level.value ? 'bg-[#062aad] text-white' : 'bg-white text-[#062aad]'
}`}
>
{level.label}
</button>
))}
</div>
</div>
);
}
Loading