90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
ComposableMap,
|
|
Geographies,
|
|
Geography,
|
|
} from "react-simple-maps";
|
|
|
|
const geoUrl =
|
|
"https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json";
|
|
|
|
export default function GeographicOverview({
|
|
data,
|
|
}: {
|
|
data: { country: string; scans: number }[];
|
|
}) {
|
|
const lookup = Object.fromEntries(
|
|
data.map((x) => [x.country.toLowerCase(), x.scans])
|
|
);
|
|
|
|
const max = Math.max(...data.map((d) => d.scans), 1);
|
|
|
|
function getColor(country: string) {
|
|
const scans = lookup[country.toLowerCase()] || 0;
|
|
|
|
if (scans === 0) return "#eceff5";
|
|
|
|
const ratio = scans / max;
|
|
|
|
if (ratio > 0.7) return "#4338ca";
|
|
if (ratio > 0.4) return "#6366f1";
|
|
if (ratio > 0.2) return "#8b5cf6";
|
|
|
|
return "#c4b5fd";
|
|
}
|
|
|
|
return (
|
|
<div className="grid lg:grid-cols-2 gap-6">
|
|
|
|
<ComposableMap
|
|
projectionConfig={{ scale: 140 }}
|
|
style={{ width: "100%", height: "300px" }}
|
|
>
|
|
<Geographies geography={geoUrl}>
|
|
{({ geographies }) =>
|
|
geographies.map((geo) => (
|
|
<Geography
|
|
key={geo.rsmKey}
|
|
geography={geo}
|
|
fill={getColor(
|
|
geo.properties.name
|
|
)}
|
|
stroke="#fff"
|
|
strokeWidth={0.5}
|
|
/>
|
|
))
|
|
}
|
|
</Geographies>
|
|
</ComposableMap>
|
|
|
|
<div>
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="text-slate-500">
|
|
<th className="text-left">Country</th>
|
|
<th className="text-right">Scans</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
{data.slice(0, 6).map((c) => (
|
|
<tr key={c.country}>
|
|
<td>{c.country}</td>
|
|
<td className="text-right font-semibold">
|
|
{c.scans.toLocaleString()}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
|
|
{data.length > 6 && (
|
|
<div className="mt-3 text-sm text-indigo-600 font-medium cursor-pointer">
|
|
+{data.length - 6} more countries
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
} |