"use client"; import { useEffect, useState, useMemo } from "react"; import { useLocale, useTranslation } from "@/i18n/navigation"; import { useTheme } from "@/app/context/ThemeContext"; import { MembersMap } from "@/app/lib/charts"; import { MAP_CITIES } from "@/app/data/map-cities"; import { apiFetch } from "@/app/lib/api-client"; export default function MembersMapPage() { const { t } = useTranslation("membersMap"); const locale = useLocale(); const { theme } = useTheme(); const [mapCities, setMapCities] = useState(MAP_CITIES); const [hoveredCity, setHoveredCity] = useState(null); useEffect(() => { apiFetch<{ items: typeof MAP_CITIES }>("/api/stats/member-map") .then((data) => data.items?.length && setMapCities(data.items)) .catch(() => setMapCities(MAP_CITIES)); }, []); const totalMembers = useMemo( () => mapCities.reduce((sum, c) => sum + c.nomadsNow, 0), [mapCities] ); const sortedCities = useMemo( () => [...mapCities].sort((a, b) => b.nomadsNow - a.nomadsNow), [mapCities] ); return (

{t("title")}

{t("subtitle")}

{hoveredCity && (

{locale === "zh" ? hoveredCity.name : hoveredCity.nameEn} {hoveredCity.emoji}

{hoveredCity.nomadsNow} {t("nomadsInCity")}

)}

{t("totalMembers")}

{totalMembers.toLocaleString()}+

{t("cities")}

    {sortedCities.map((city) => (
  • setHoveredCity(city)} onMouseLeave={() => setHoveredCity(null)} > {city.emoji} {locale === "zh" ? city.name : city.nameEn} {city.nomadsNow}
  • ))}

数据来自本地 PocketBase 城市集合,按城市成员数匿名聚合。

); }