markers and groups added

This commit is contained in:
Owen Rees
2023-06-05 16:26:17 +02:00
parent 1407125ba5
commit b28510b63d
29 changed files with 135 additions and 27 deletions
+1
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
marker legend
Warning: validateDOMNesting(...): <a> cannot appear as a child of <tr>.
+3 -2
View File
@@ -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>
+12 -7
View File
@@ -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, ssr: false,
} loading: () => (
); <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">
<TournamentMap tournamentData={tournamentData} />
</div>
<div className="bg-gray-800">
<TournamentTable tournamentData={tournamentData} /> <TournamentTable tournamentData={tournamentData} />
</div>
</main> </main>
</Layout> </Layout>
); );
+80 -4
View File
@@ -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='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <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>
); );
+23 -8
View File
@@ -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">
<td className="p-3">{t.tournament}</td> {t.date}
</a> </a>
<td className="p-3">{t.time_control}</td> </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>
)); ));
+6
View File
@@ -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",
+1
View File
@@ -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",
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

+2 -2
View File
@@ -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[];
} }