feat: add CountryHistogram and AnalyticsMap components and update build script to use legacy-peer-deps
This commit is contained in:
@@ -60,6 +60,12 @@ const countryToISO: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function AnalyticsMap({ data, title }: AnalyticsMapProps) {
|
export default function AnalyticsMap({ data, title }: AnalyticsMapProps) {
|
||||||
|
const [mounted, setMounted] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const maxCount = useMemo(() => {
|
const maxCount = useMemo(() => {
|
||||||
if (data.length === 0) return 1;
|
if (data.length === 0) return 1;
|
||||||
return Math.max(...data.map((d) => d.count));
|
return Math.max(...data.map((d) => d.count));
|
||||||
@@ -132,53 +138,60 @@ export default function AnalyticsMap({ data, title }: AnalyticsMapProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 flex justify-center bg-slate-950/20 h-[600px]">
|
<div className="p-4 flex justify-center bg-slate-950/20 h-[600px] items-center">
|
||||||
<ComposableMap
|
{!mounted ? (
|
||||||
projectionConfig={{
|
<div className="flex flex-col items-center gap-4 animate-pulse">
|
||||||
rotate: [-10, 0, 0],
|
<div className="w-16 h-16 border-4 border-indigo-500/20 border-t-indigo-500 rounded-full animate-spin"></div>
|
||||||
scale: 147
|
<p className="text-slate-500 text-xs font-bold uppercase tracking-widest">Chargement de la carte...</p>
|
||||||
}}
|
</div>
|
||||||
width={800}
|
) : (
|
||||||
height={600}
|
<ComposableMap
|
||||||
style={{ width: "100%", height: "100%" }}
|
projectionConfig={{
|
||||||
>
|
rotate: [-10, 0, 0],
|
||||||
<ZoomableGroup
|
scale: 147
|
||||||
zoom={position.zoom}
|
}}
|
||||||
center={position.coordinates as [number, number]}
|
width={800}
|
||||||
onMoveEnd={handleMoveEnd}
|
height={600}
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
>
|
>
|
||||||
<Sphere stroke="#334155" strokeWidth={0.5} id="sphere" fill="transparent" />
|
<ZoomableGroup
|
||||||
<Graticule stroke="#334155" strokeWidth={0.5} />
|
zoom={position.zoom}
|
||||||
{data.length > 0 && (
|
center={position.coordinates as [number, number]}
|
||||||
<Geographies geography={geoUrl}>
|
onMoveEnd={handleMoveEnd}
|
||||||
{({ geographies }) =>
|
>
|
||||||
geographies.map((geo) => {
|
<Sphere stroke="#334155" strokeWidth={0.5} id="sphere" fill="transparent" />
|
||||||
const countryName = geo.properties.name;
|
<Graticule stroke="#334155" strokeWidth={0.5} />
|
||||||
const countryId = geo.id;
|
{data.length > 0 && (
|
||||||
const countryISO = geo.properties.iso_a3;
|
<Geographies geography={geoUrl}>
|
||||||
|
{({ geographies }) =>
|
||||||
|
geographies.map((geo) => {
|
||||||
|
const countryName = geo.properties.name;
|
||||||
|
const countryId = geo.id;
|
||||||
|
const countryISO = geo.properties.iso_a3;
|
||||||
|
|
||||||
const count = mappedData[countryName] || mappedData[countryISO] || mappedData[countryId] || 0;
|
const count = mappedData[countryName] || mappedData[countryISO] || mappedData[countryId] || 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Geography
|
<Geography
|
||||||
key={geo.rsmKey}
|
key={geo.rsmKey}
|
||||||
geography={geo}
|
geography={geo}
|
||||||
fill={count > 0 ? colorScale(count) : "#0f172a"}
|
fill={count > 0 ? colorScale(count) : "#0f172a"}
|
||||||
stroke="#1e293b"
|
stroke="#1e293b"
|
||||||
strokeWidth={0.5}
|
strokeWidth={0.5}
|
||||||
style={{
|
style={{
|
||||||
default: { outline: "none" },
|
default: { outline: "none" },
|
||||||
hover: { fill: "#4f46e5", outline: "none", cursor: "pointer" },
|
hover: { fill: "#4f46e5", outline: "none", cursor: "pointer" },
|
||||||
pressed: { outline: "none" }
|
pressed: { outline: "none" }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</Geographies>
|
</Geographies>
|
||||||
)}
|
)}
|
||||||
</ZoomableGroup>
|
</ZoomableGroup>
|
||||||
</ComposableMap>
|
</ComposableMap>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Legend / Instructions */}
|
{/* Legend / Instructions */}
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ interface CountryHistogramProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CountryHistogram({ data, title }: CountryHistogramProps) {
|
export default function CountryHistogram({ data, title }: CountryHistogramProps) {
|
||||||
|
const [mounted, setMounted] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Take top 10 for readability
|
// Take top 10 for readability
|
||||||
const chartData = data.slice(0, 10).map(item => ({
|
const chartData = data.slice(0, 10).map(item => ({
|
||||||
name: item.country || 'Inconnu',
|
name: item.country || 'Inconnu',
|
||||||
@@ -32,8 +38,11 @@ export default function CountryHistogram({ data, title }: CountryHistogramProps)
|
|||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="h-[400px] w-full">
|
<div className="h-[400px] w-full flex items-center justify-center">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
{!mounted ? (
|
||||||
|
<p className="text-slate-500 text-xs font-bold uppercase tracking-widest animate-pulse">Chargement du graphique...</p>
|
||||||
|
) : (
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart
|
<BarChart
|
||||||
data={chartData}
|
data={chartData}
|
||||||
margin={{
|
margin={{
|
||||||
@@ -91,6 +100,7 @@ export default function CountryHistogram({ data, title }: CountryHistogramProps)
|
|||||||
/>
|
/>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"dev:admin": "npm run dev --prefix admin -- -p 3001",
|
"dev:admin": "npm run dev --prefix admin -- -p 3001",
|
||||||
"all": "concurrently \"npm run dev\" \"npm run dev:admin\"",
|
"all": "concurrently \"npm run dev\" \"npm run dev:admin\"",
|
||||||
"build": "prisma generate && next build && npm run build:admin",
|
"build": "prisma generate && next build && npm run build:admin",
|
||||||
"build:admin": "npm install --prefix admin && prisma generate && npm run build --prefix admin",
|
"build:admin": "npm install --prefix admin --legacy-peer-deps && prisma generate && npm run build --prefix admin",
|
||||||
"generate": "npx prisma generate && cd admin && npx prisma generate && cd ..",
|
"generate": "npx prisma generate && cd admin && npx prisma generate && cd ..",
|
||||||
"start": "npx prisma migrate deploy && next start",
|
"start": "npx prisma migrate deploy && next start",
|
||||||
"start:admin": "npm run start --prefix admin",
|
"start:admin": "npm run start --prefix admin",
|
||||||
|
|||||||
Reference in New Issue
Block a user