mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
map legend
This commit is contained in:
@@ -1,3 +1 @@
|
|||||||
marker legend
|
marker legend
|
||||||
|
|
||||||
Warning: validateDOMNesting(...): <a> cannot appear as a child of <tr>.
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import clientPromise from "@/lib/mongodb";
|
import clientPromise from "@/lib/mongodb";
|
||||||
|
|
||||||
|
// TODO collate only the country of France - redundant for now but will be needed when new countries are added
|
||||||
|
// probably do this by passing the country name as parameter
|
||||||
/**
|
/**
|
||||||
* Tournament data API endpoint
|
* Tournament data API endpoint
|
||||||
* @route /api/tournaments
|
* @route /api/tournaments/france
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
+6
-14
@@ -1,8 +1,11 @@
|
|||||||
|
import { Tournament } from "@/types";
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Layout from "@/components/Layout";
|
import Layout from "@/components/Layout";
|
||||||
import TournamentTable from "@/components/TournamentTable";
|
import TournamentTable from "@/components/TournamentTable";
|
||||||
import { Tournament } from "@/types";
|
import getTournaments from "@/utils/getTournamentData";
|
||||||
|
|
||||||
|
// TODO can these functions be put into a custom hook?
|
||||||
/**
|
/**
|
||||||
* Imports the tournament map component, ensuring CSR only.
|
* Imports the tournament map component, ensuring CSR only.
|
||||||
* @remarks SSR is not supported by react-leaflet
|
* @remarks SSR is not supported by react-leaflet
|
||||||
@@ -16,23 +19,12 @@ const TournamentMap = dynamic(() => import("@/components/TournamentMap"), {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves tournament data from /api/tournaments
|
|
||||||
* @remarks The result is cached for the revalidation period in seconds
|
|
||||||
*/
|
|
||||||
async function getTournaments() {
|
|
||||||
const res = await fetch("http://localhost:3000/api/tournaments", {
|
|
||||||
next: { revalidate: 300 },
|
|
||||||
});
|
|
||||||
return await res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function Tournaments() {
|
export default async function Tournaments() {
|
||||||
const tournamentData: Tournament[] = await getTournaments();
|
const tournamentData: Tournament[] = await getTournaments("france");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<main className="grid lg:grid-cols-2">
|
<main className="relative grid lg:grid-cols-2">
|
||||||
<div className="relative h-screen">
|
<div className="relative h-screen">
|
||||||
<TournamentMap tournamentData={tournamentData} />
|
<TournamentMap tournamentData={tournamentData} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,76 +1,56 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { TournamentDataProps } from "@/types";
|
||||||
|
import { LatLngLiteral } from "leaflet";
|
||||||
|
|
||||||
|
// Leaflet + icon fixes
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
|
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
|
||||||
import L from "leaflet";
|
import L from "leaflet";
|
||||||
import "leaflet-defaulticon-compatibility";
|
import "leaflet-defaulticon-compatibility";
|
||||||
|
import { MapContainer, TileLayer, LayersControl, useMap } from "react-leaflet";
|
||||||
|
|
||||||
import {
|
import { createLayerGroups } from "@/utils/layerGroups";
|
||||||
MapContainer,
|
import { useEffect } from "react";
|
||||||
TileLayer,
|
|
||||||
Marker,
|
|
||||||
Popup,
|
|
||||||
LayersControl,
|
|
||||||
LayerGroup,
|
|
||||||
} from "react-leaflet";
|
|
||||||
|
|
||||||
import { LatLngLiteral } from "leaflet";
|
|
||||||
import { TournamentDataProps } from "@/types";
|
|
||||||
|
|
||||||
export default function TournamentMap({ tournamentData }: TournamentDataProps) {
|
export default function TournamentMap({ tournamentData }: TournamentDataProps) {
|
||||||
const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 };
|
const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 };
|
||||||
|
|
||||||
// TODO consider putting in page.tsx so that it is SSR
|
const classicalMarkers = createLayerGroups("Cadence Lente", "green", {
|
||||||
// TODO move to own hook/util
|
tournamentData,
|
||||||
// TODO wrap in useEffect on initial load []
|
});
|
||||||
function layerGroups(timeControl: string, colour: string) {
|
const rapidMarkers = createLayerGroups("Rapide", "blue", { tournamentData });
|
||||||
const filteredTournaments = tournamentData.filter(
|
const blitzMarkers = createLayerGroups("Blitz", "yellow", { tournamentData });
|
||||||
(t) => t.time_control === timeControl
|
const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData });
|
||||||
);
|
|
||||||
|
|
||||||
const iconOptions = new L.Icon({
|
// TODO move into its own hook
|
||||||
iconUrl: `images/leaflet/marker-icon-2x-${colour}.png`,
|
function Legend() {
|
||||||
shadowUrl: "images/leaflet/marker-shadow.png",
|
const map = useMap();
|
||||||
iconSize: [25, 41],
|
|
||||||
iconAnchor: [12, 41],
|
|
||||||
popupAnchor: [1, -34],
|
|
||||||
shadowSize: [41, 41],
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<LayersControl.Overlay checked name={timeControl}>
|
if (map) {
|
||||||
<LayerGroup>
|
const legend = L.control({ position: "bottomleft" });
|
||||||
{filteredTournaments.map((t) => {
|
|
||||||
const coordinates = {
|
|
||||||
lat: t.coordinates[0] + Math.random() * (-0.01 - 0.01) + 0.01,
|
|
||||||
lng: t.coordinates[1] + Math.random() * (-0.01 - 0.01) + 0.01,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
legend.onAdd = () => {
|
||||||
<Marker position={coordinates} key={t._id} icon={iconOptions}>
|
const div = L.DomUtil.create("div", "map-legend");
|
||||||
<Popup>
|
div.style =
|
||||||
<p>
|
"background: white; color: black; border: 2px solid grey; border-radius: 6px; padding: 10px;";
|
||||||
{t.date}
|
div.innerHTML = `<ul>
|
||||||
<br />
|
<li><span style='background:#00ac39; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Cadence Lente</li>
|
||||||
<a href={t.url} target="_blank" rel="noopener noreferrer">
|
<li><span style='background:#0086c7; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Rapide</li>
|
||||||
{t.tournament}
|
<li><span style='background:#cec348; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Blitz</li>
|
||||||
</a>
|
<li><span style='background:#d10c3e; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>1h KO</li>
|
||||||
</p>
|
</ul>`;
|
||||||
géolocalisation approximative
|
return div;
|
||||||
</Popup>
|
};
|
||||||
</Marker>
|
|
||||||
);
|
legend.addTo(map);
|
||||||
})}
|
}
|
||||||
</LayerGroup>
|
}, [map]);
|
||||||
</LayersControl.Overlay>
|
return null;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const classicalMarkers = layerGroups("Cadence Lente", "green");
|
|
||||||
const rapidMarkers = layerGroups("Rapide", "blue");
|
|
||||||
const blitzMarkers = layerGroups("Blitz", "yellow");
|
|
||||||
const otherMarkers = layerGroups("1h KO", "red");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="tournament-map" className="w-full lg:col-start-1 lg:col-end-2">
|
<section id="tournament-map" className="w-full lg:col-start-1 lg:col-end-2">
|
||||||
<MapContainer
|
<MapContainer
|
||||||
@@ -89,6 +69,7 @@ export default function TournamentMap({ tournamentData }: TournamentDataProps) {
|
|||||||
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
/>
|
/>
|
||||||
|
<Legend />
|
||||||
<LayersControl>
|
<LayersControl>
|
||||||
{classicalMarkers}
|
{classicalMarkers}
|
||||||
{rapidMarkers}
|
{rapidMarkers}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Retrieves tournament data from /api/tournaments/:country
|
||||||
|
* @remarks The result is cached for the revalidation period in seconds
|
||||||
|
*/
|
||||||
|
export default async function getTournaments(country: string) {
|
||||||
|
const res = await fetch(`http://localhost:3000/api/tournaments/${country}`, {
|
||||||
|
next: { revalidate: 300 },
|
||||||
|
});
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { TournamentDataProps } from "@/types";
|
||||||
|
|
||||||
|
import L from "leaflet";
|
||||||
|
import { LayerGroup, LayersControl, Marker, Popup } from "react-leaflet";
|
||||||
|
|
||||||
|
export const createLayerGroups = (
|
||||||
|
timeControl: string,
|
||||||
|
colour: string,
|
||||||
|
{ tournamentData }: TournamentDataProps
|
||||||
|
) => {
|
||||||
|
const filteredTournaments = tournamentData.filter(
|
||||||
|
(t) => t.time_control === timeControl
|
||||||
|
);
|
||||||
|
|
||||||
|
const iconOptions = new L.Icon({
|
||||||
|
iconUrl: `images/leaflet/marker-icon-2x-${colour}.png`,
|
||||||
|
shadowUrl: "images/leaflet/marker-shadow.png",
|
||||||
|
iconSize: [25, 41],
|
||||||
|
iconAnchor: [12, 41],
|
||||||
|
popupAnchor: [1, -34],
|
||||||
|
shadowSize: [41, 41],
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LayersControl.Overlay checked name={timeControl}>
|
||||||
|
<LayerGroup>
|
||||||
|
{filteredTournaments.map((t) => {
|
||||||
|
const coordinates = {
|
||||||
|
lat: t.coordinates[0] + Math.random() * (-0.01 - 0.01) + 0.01,
|
||||||
|
lng: t.coordinates[1] + Math.random() * (-0.01 - 0.01) + 0.01,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Marker position={coordinates} key={t._id} icon={iconOptions}>
|
||||||
|
<Popup>
|
||||||
|
<p>
|
||||||
|
{t.date}
|
||||||
|
<br />
|
||||||
|
<a href={t.url} target="_blank" rel="noopener noreferrer">
|
||||||
|
{t.tournament}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
géolocalisation approximative
|
||||||
|
</Popup>
|
||||||
|
</Marker>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</LayerGroup>
|
||||||
|
</LayersControl.Overlay>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user