mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
filtered tournament hooks
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
import { useMap } from "react-leaflet";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import L from "leaflet";
|
||||||
|
|
||||||
|
const Legend = () => {
|
||||||
|
const map = useMap();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (map) {
|
||||||
|
// @ts-ignore
|
||||||
|
const legend = L.control({ position: "bottomleft" });
|
||||||
|
|
||||||
|
legend.onAdd = () => {
|
||||||
|
const div = L.DomUtil.create("div", "map-legend");
|
||||||
|
div.setAttribute(
|
||||||
|
"style",
|
||||||
|
"background: white; color: black; border: 2px solid grey; border-radius: 6px; padding: 10px;"
|
||||||
|
);
|
||||||
|
div.innerHTML = `<ul>
|
||||||
|
<li><span style='background:#00ac39; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Cadence Lente</li>
|
||||||
|
<li><span style='background:#0086c7; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Rapide</li>
|
||||||
|
<li><span style='background:#cec348; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Blitz</li>
|
||||||
|
<li><span style='background:#d10c3e; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>1h KO</li>
|
||||||
|
</ul>`;
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
|
||||||
|
legend.addTo(map);
|
||||||
|
}
|
||||||
|
}, [map]);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Legend;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
|
type SearchBarProps = {
|
||||||
|
tournamentFilter: string;
|
||||||
|
setTournamentFilter: Dispatch<SetStateAction<string>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SearchBar = ({
|
||||||
|
tournamentFilter,
|
||||||
|
setTournamentFilter,
|
||||||
|
}: SearchBarProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="p-3 bg-gray-800">
|
||||||
|
<label htmlFor="table-search" className="sr-only">
|
||||||
|
Search
|
||||||
|
</label>
|
||||||
|
<div className="relative mt-1">
|
||||||
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 text-gray-500 dark:text-gray-400"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="table-search"
|
||||||
|
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-80 pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="Search for items"
|
||||||
|
value={tournamentFilter}
|
||||||
|
onChange={(e) => setTournamentFilter(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchBar;
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
// Types
|
|
||||||
import { TournamentDataProps } from "@/types";
|
import { TournamentDataProps } from "@/types";
|
||||||
import { LatLngLiteral } from "leaflet";
|
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 "leaflet-defaulticon-compatibility";
|
import "leaflet-defaulticon-compatibility";
|
||||||
import { MapContainer, TileLayer, LayersControl, useMap } from "react-leaflet";
|
import { MapContainer, TileLayer, LayersControl, useMap } from "react-leaflet";
|
||||||
|
|
||||||
import { createLayerGroups } from "@/utils/layerGroups";
|
import { createLayerGroups } from "@/utils/layerGroups";
|
||||||
import { useEffect } from "react";
|
import Legend from "@/components/Legend";
|
||||||
|
|
||||||
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 };
|
||||||
@@ -24,33 +21,6 @@ export default function TournamentMap({ tournamentData }: TournamentDataProps) {
|
|||||||
const blitzMarkers = createLayerGroups("Blitz", "yellow", { tournamentData });
|
const blitzMarkers = createLayerGroups("Blitz", "yellow", { tournamentData });
|
||||||
const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData });
|
const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData });
|
||||||
|
|
||||||
// TODO move into its own hook
|
|
||||||
function Legend() {
|
|
||||||
const map = useMap();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (map) {
|
|
||||||
const legend = L.control({ position: "bottomleft" });
|
|
||||||
|
|
||||||
legend.onAdd = () => {
|
|
||||||
const div = L.DomUtil.create("div", "map-legend");
|
|
||||||
div.style =
|
|
||||||
"background: white; color: black; border: 2px solid grey; border-radius: 6px; padding: 10px;";
|
|
||||||
div.innerHTML = `<ul>
|
|
||||||
<li><span style='background:#00ac39; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Cadence Lente</li>
|
|
||||||
<li><span style='background:#0086c7; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Rapide</li>
|
|
||||||
<li><span style='background:#cec348; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Blitz</li>
|
|
||||||
<li><span style='background:#d10c3e; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>1h KO</li>
|
|
||||||
</ul>`;
|
|
||||||
return div;
|
|
||||||
};
|
|
||||||
|
|
||||||
legend.addTo(map);
|
|
||||||
}
|
|
||||||
}, [map]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -2,86 +2,30 @@
|
|||||||
|
|
||||||
import { TournamentDataProps } from "@/types";
|
import { TournamentDataProps } from "@/types";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import useTournamentDataFilter from "@/hooks/useTournamentFilter";
|
||||||
|
import SearchBar from "@/components/SearchBar";
|
||||||
|
|
||||||
export default function TournamentTable({
|
export default function TournamentTable({
|
||||||
tournamentData,
|
tournamentData,
|
||||||
}: TournamentDataProps) {
|
}: 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);
|
useTournamentDataFilter(tournamentData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilteredTournamentData(
|
setFilteredTournamentData(tournamentFilter);
|
||||||
tournamentData.filter((t) =>
|
|
||||||
t.town.includes(tournamentFilter.toUpperCase())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [tournamentFilter]);
|
}, [tournamentFilter]);
|
||||||
|
|
||||||
const tournaments = filteredTournamentData.map((t) => (
|
|
||||||
<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"
|
|
||||||
key={t._id}
|
|
||||||
>
|
|
||||||
<td className="p-3">
|
|
||||||
<a href={t.url} target="_blank">
|
|
||||||
{t.date}
|
|
||||||
</a>
|
|
||||||
</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>
|
|
||||||
));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
id="tournament-table"
|
id="tournament-table"
|
||||||
className="w-full lg:col-start-2 lg:col-end-3"
|
className="w-full lg:col-start-2 lg:col-end-3"
|
||||||
>
|
>
|
||||||
<div className="p-3 bg-gray-800">
|
<SearchBar
|
||||||
<label htmlFor="table-search" className="sr-only">
|
tournamentFilter={tournamentFilter}
|
||||||
Search
|
setTournamentFilter={setTournamentFilter}
|
||||||
</label>
|
|
||||||
<div className="relative mt-1">
|
|
||||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 text-gray-500 dark:text-gray-400"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="table-search"
|
|
||||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-80 pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
||||||
placeholder="Search for items"
|
|
||||||
value={tournamentFilter}
|
|
||||||
onChange={(e) => setTournamentFilter(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
<table className="table-fixed w-full text-center text-xs">
|
||||||
</div>
|
|
||||||
<table className="w-full text-center text-xs">
|
|
||||||
<thead className="bg-gray-600 text-white">
|
<thead className="bg-gray-600 text-white">
|
||||||
<tr className="">
|
<tr className="">
|
||||||
<th className="p-3">Date</th>
|
<th className="p-3">Date</th>
|
||||||
@@ -90,7 +34,7 @@ export default function TournamentTable({
|
|||||||
<th className="p-3">Cadence</th>
|
<th className="p-3">Cadence</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{tournaments}</tbody>
|
<tbody>{filteredTournamentData}</tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Tournament } from "@/types";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
// TODO change 'any'
|
||||||
|
const useTournamentDataFilter = (
|
||||||
|
initialState: Tournament[]
|
||||||
|
): (any | ((searchQuery: string) => void))[] => {
|
||||||
|
const [filtered, setFiltered] = useState(initialState);
|
||||||
|
let state;
|
||||||
|
const setState = (searchQuery: string) => {
|
||||||
|
setFiltered(
|
||||||
|
initialState.filter((t) => t.town.includes(searchQuery.toUpperCase()))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(filtered);
|
||||||
|
|
||||||
|
if (filtered.length === 0) {
|
||||||
|
state = (
|
||||||
|
<tr className="border-b border-gray-700 border-opacity-20 bg-gray-800 text-white">
|
||||||
|
<td colSpan={4} className="p-3">
|
||||||
|
No tournaments found
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state = filtered.map((t) => (
|
||||||
|
<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"
|
||||||
|
key={t._id}
|
||||||
|
>
|
||||||
|
<td className="p-3">
|
||||||
|
<a href={t.url} target="_blank">
|
||||||
|
{t.date}
|
||||||
|
</a>
|
||||||
|
</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>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [state, setState];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTournamentDataFilter;
|
||||||
Reference in New Issue
Block a user