markers and groups added
@@ -1,4 +1,5 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
TODO
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
marker legend
|
||||||
|
|
||||||
|
Warning: validateDOMNesting(...): <a> cannot appear as a child of <tr>.
|
||||||
@@ -3,8 +3,9 @@ import Layout from "@/components/Layout";
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<header className="grid h-[calc(100%-64px)] bg-green-400 place-items-center">
|
<header className="grid h-[calc(100%-64px)] place-items-center">
|
||||||
<div>
|
<div className="relative h-full w-full bg-[url('/images/map-bg.jpg')] brightness-[0.2]"></div>
|
||||||
|
<div className="absolute z-10">
|
||||||
<h1>Heading 1</h1>
|
<h1>Heading 1</h1>
|
||||||
<h2>Heading 2</h2>
|
<h2>Heading 2</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import { Tournament } from "@/types";
|
|||||||
* 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
|
||||||
*/
|
*/
|
||||||
const TournamentMapNoSSR = dynamic(
|
const TournamentMap = dynamic(() => import("@/components/TournamentMap"), {
|
||||||
() => import("../../components/TournamentMap"),
|
ssr: false,
|
||||||
{
|
loading: () => (
|
||||||
ssr: false,
|
<div className="h-screen grid text-black place-items-center">
|
||||||
}
|
<p>Loading map...</p>
|
||||||
);
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves tournament data from /api/tournaments
|
* Retrieves tournament data from /api/tournaments
|
||||||
@@ -27,13 +29,16 @@ async function getTournaments() {
|
|||||||
|
|
||||||
export default async function Tournaments() {
|
export default async function Tournaments() {
|
||||||
const tournamentData: Tournament[] = await getTournaments();
|
const tournamentData: Tournament[] = await getTournaments();
|
||||||
console.log(tournamentData);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<main className="grid lg:grid-cols-2">
|
<main className="grid lg:grid-cols-2">
|
||||||
<TournamentMapNoSSR />
|
<div className="relative h-screen">
|
||||||
<TournamentTable tournamentData={tournamentData} />
|
<TournamentMap tournamentData={tournamentData} />
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-800">
|
||||||
|
<TournamentTable tournamentData={tournamentData} />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,100 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { MapContainer, TileLayer, Marker } from "react-leaflet";
|
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
|
||||||
import { LatLngLiteral } from "leaflet";
|
import L from "leaflet";
|
||||||
|
import "leaflet-defaulticon-compatibility";
|
||||||
|
|
||||||
export default function TournamentMap() {
|
import {
|
||||||
|
MapContainer,
|
||||||
|
TileLayer,
|
||||||
|
Marker,
|
||||||
|
Popup,
|
||||||
|
LayersControl,
|
||||||
|
LayerGroup,
|
||||||
|
} from "react-leaflet";
|
||||||
|
|
||||||
|
import { LatLngLiteral } from "leaflet";
|
||||||
|
import { TournamentDataProps } from "@/types";
|
||||||
|
|
||||||
|
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
|
||||||
|
// TODO move to own hook/util
|
||||||
|
// TODO wrap in useEffect on initial load []
|
||||||
|
function layerGroups(timeControl: string, colour: string) {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
center={center}
|
center={center}
|
||||||
zoom={5}
|
zoom={5}
|
||||||
scrollWheelZoom={false}
|
scrollWheelZoom={false}
|
||||||
style={{ height: "50vh" }}
|
style={{
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<TileLayer
|
<TileLayer
|
||||||
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"
|
||||||
/>
|
/>
|
||||||
|
<LayersControl>
|
||||||
|
{classicalMarkers}
|
||||||
|
{rapidMarkers}
|
||||||
|
{blitzMarkers}
|
||||||
|
{otherMarkers}
|
||||||
|
</LayersControl>
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Tournament, TournamentTableProps } from "@/types";
|
import { TournamentDataProps } from "@/types";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function TournamentTable({
|
export default function TournamentTable({
|
||||||
tournamentData,
|
tournamentData,
|
||||||
}: TournamentTableProps) {
|
}: TournamentDataProps) {
|
||||||
|
// TODO put most of this is a custom hook
|
||||||
const [tournamentFilter, setTournamentFilter] = useState(""); // text from search bar
|
const [tournamentFilter, setTournamentFilter] = useState(""); // text from search bar
|
||||||
const [filteredTournamentData, setFilteredTournamentData] =
|
const [filteredTournamentData, setFilteredTournamentData] =
|
||||||
useState(tournamentData);
|
useState(tournamentData);
|
||||||
@@ -13,22 +14,36 @@ export default function TournamentTable({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilteredTournamentData(
|
setFilteredTournamentData(
|
||||||
tournamentData.filter((t) =>
|
tournamentData.filter((t) =>
|
||||||
t.location.includes(tournamentFilter.toUpperCase())
|
t.town.includes(tournamentFilter.toUpperCase())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, [tournamentFilter]);
|
}, [tournamentFilter]);
|
||||||
|
|
||||||
const tournaments = filteredTournamentData.map((t: Tournament) => (
|
const tournaments = filteredTournamentData.map((t) => (
|
||||||
<tr
|
<tr
|
||||||
className="border-b border-gray-700 border-opacity-20 bg-gray-800 text-white transition duration-300 ease-in-out hover:bg-gray-400 hover:text-black"
|
className="border-b border-gray-700 border-opacity-20 bg-gray-800 text-white transition duration-300 ease-in-out hover:bg-gray-400 hover:text-black"
|
||||||
key={t._id}
|
key={t._id}
|
||||||
>
|
>
|
||||||
<td className="p-3">{t.date}</td>
|
<td className="p-3">
|
||||||
<td className="p-3">{t.location}</td>
|
<a href={t.url} target="_blank">
|
||||||
<a href={t.url} target="_blank">
|
{t.date}
|
||||||
<td className="p-3">{t.tournament}</td>
|
</a>
|
||||||
</a>
|
</td>
|
||||||
<td className="p-3">{t.time_control}</td>
|
<td className="p-3">
|
||||||
|
<a href={t.url} target="_blank">
|
||||||
|
{t.town}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td className="p-3">
|
||||||
|
<a href={t.url} target="_blank">
|
||||||
|
{t.tournament}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td className="p-3">
|
||||||
|
<a href={t.url} target="_blank">
|
||||||
|
{t.time_control}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"eslint": "8.41.0",
|
"eslint": "8.41.0",
|
||||||
"eslint-config-next": "13.4.3",
|
"eslint-config-next": "13.4.3",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
|
"leaflet-defaulticon-compatibility": "^0.1.1",
|
||||||
"mongodb": "^5.5.0",
|
"mongodb": "^5.5.0",
|
||||||
"next": "13.4.3",
|
"next": "13.4.3",
|
||||||
"postcss": "8.4.23",
|
"postcss": "8.4.23",
|
||||||
@@ -5870,6 +5871,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
|
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/leaflet-defaulticon-compatibility": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet-defaulticon-compatibility/-/leaflet-defaulticon-compatibility-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-vDBFdlUAwjSEGep9ih8kfJilf6yN8V9zTbF5NC/1ZwLeGko3RUQepspPnGCRMFV51dY3Lb3hziboicrFz+rxQA=="
|
||||||
|
},
|
||||||
"node_modules/leven": {
|
"node_modules/leven": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"eslint": "8.41.0",
|
"eslint": "8.41.0",
|
||||||
"eslint-config-next": "13.4.3",
|
"eslint-config-next": "13.4.3",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
|
"leaflet-defaulticon-compatibility": "^0.1.1",
|
||||||
"mongodb": "^5.5.0",
|
"mongodb": "^5.5.0",
|
||||||
"next": "13.4.3",
|
"next": "13.4.3",
|
||||||
"postcss": "8.4.23",
|
"postcss": "8.4.23",
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 608 B |
|
After Width: | Height: | Size: 436 KiB |
@@ -1,6 +1,6 @@
|
|||||||
export interface Tournament {
|
export interface Tournament {
|
||||||
_id: string;
|
_id: string;
|
||||||
location: string;
|
town: string;
|
||||||
department: string;
|
department: string;
|
||||||
tournament: string;
|
tournament: string;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -9,6 +9,6 @@ export interface Tournament {
|
|||||||
coordinates: [number, number];
|
coordinates: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TournamentTableProps {
|
export interface TournamentDataProps {
|
||||||
tournamentData: Tournament[];
|
tournamentData: Tournament[];
|
||||||
}
|
}
|
||||||
|
|||||||