mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
Sync list to map (#42)
This commit is contained in:
Vendored
+1
@@ -9,6 +9,7 @@
|
|||||||
"Française",
|
"Française",
|
||||||
"Lente",
|
"Lente",
|
||||||
"localisation",
|
"localisation",
|
||||||
|
"moveend",
|
||||||
"randomisation",
|
"randomisation",
|
||||||
"Rapide",
|
"Rapide",
|
||||||
"Rees",
|
"Rees",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const SearchBar = () => {
|
|||||||
const [searchString, setSearchString] = useAtom(searchStringAtom);
|
const [searchString, setSearchString] = useAtom(searchStringAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-3 dark:bg-gray-800">
|
<div className="bg-white dark:bg-gray-800">
|
||||||
<label htmlFor="table-search" className="sr-only">
|
<label htmlFor="table-search" className="sr-only">
|
||||||
{t("searchLabel")}
|
{t("searchLabel")}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -7,18 +7,28 @@ import {
|
|||||||
TileLayer,
|
TileLayer,
|
||||||
LayersControl,
|
LayersControl,
|
||||||
LayerGroup,
|
LayerGroup,
|
||||||
|
useMapEvent,
|
||||||
} from "react-leaflet";
|
} from "react-leaflet";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue, useSetAtom } from "jotai";
|
||||||
|
|
||||||
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 "leaflet-defaulticon-compatibility";
|
import "leaflet-defaulticon-compatibility";
|
||||||
|
|
||||||
import { tournamentsAtom } from "@/app/atoms";
|
import { tournamentsAtom, mapBoundsAtom } from "@/app/atoms";
|
||||||
|
|
||||||
import Legend from "./Legend";
|
import Legend from "./Legend";
|
||||||
import { TournamentMarker } from "./TournamentMarker";
|
import { TournamentMarker } from "./TournamentMarker";
|
||||||
|
|
||||||
|
const MapEvents = () => {
|
||||||
|
const setMapBounds = useSetAtom(mapBoundsAtom);
|
||||||
|
const map = useMapEvent("moveend", () => {
|
||||||
|
setMapBounds(map.getBounds());
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export default function TournamentMap() {
|
export default function TournamentMap() {
|
||||||
const tournaments = useAtomValue(tournamentsAtom);
|
const tournaments = useAtomValue(tournamentsAtom);
|
||||||
const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 };
|
const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 };
|
||||||
@@ -68,6 +78,7 @@ export default function TournamentMap() {
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<MapEvents />
|
||||||
<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"
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useAtomValue, useSetAtom } from "jotai";
|
import { useAtomValue, useSetAtom, useAtom } from "jotai";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
filteredTournamentsAtom,
|
filteredTournamentsAtom,
|
||||||
|
syncVisibleAtom,
|
||||||
hoveredMapTournamentIdAtom,
|
hoveredMapTournamentIdAtom,
|
||||||
debouncedHoveredMapTournamentIdAtom,
|
debouncedHoveredMapTournamentIdAtom,
|
||||||
debouncedHoveredListTournamentIdAtom,
|
debouncedHoveredListTournamentIdAtom,
|
||||||
@@ -19,6 +20,7 @@ export default function TournamentTable() {
|
|||||||
const t = useTranslations("Tournaments");
|
const t = useTranslations("Tournaments");
|
||||||
|
|
||||||
const filteredTournaments = useAtomValue(filteredTournamentsAtom);
|
const filteredTournaments = useAtomValue(filteredTournamentsAtom);
|
||||||
|
const [syncVisible, setSyncVisible] = useAtom(syncVisibleAtom);
|
||||||
const hoveredMapTournamentId = useAtomValue(hoveredMapTournamentIdAtom);
|
const hoveredMapTournamentId = useAtomValue(hoveredMapTournamentIdAtom);
|
||||||
const debouncedHoveredMapTournamentId = useAtomValue(
|
const debouncedHoveredMapTournamentId = useAtomValue(
|
||||||
debouncedHoveredMapTournamentIdAtom
|
debouncedHoveredMapTournamentIdAtom
|
||||||
@@ -42,12 +44,22 @@ export default function TournamentTable() {
|
|||||||
id="tournament-table"
|
id="tournament-table"
|
||||||
data-test="tournament-table-div"
|
data-test="tournament-table-div"
|
||||||
>
|
>
|
||||||
<div className="z-10 flex">
|
<div className="z-10 flex w-full flex-wrap items-center justify-between gap-3 p-3">
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<div>
|
<div className="text-gray-900 dark:text-white">
|
||||||
<ScrollToTopButton />
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={syncVisible}
|
||||||
|
onChange={() => setSyncVisible(!syncVisible)}
|
||||||
|
/>{" "}
|
||||||
|
{t("syncWithMapCheckbox")}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ScrollToTopButton />
|
||||||
|
|
||||||
<table
|
<table
|
||||||
className="relative w-full table-fixed text-center text-xs"
|
className="relative w-full table-fixed text-center text-xs"
|
||||||
data-test="tournament-table"
|
data-test="tournament-table"
|
||||||
|
|||||||
+14
-2
@@ -1,10 +1,13 @@
|
|||||||
import { Tournament } from '@/types';
|
import { Tournament } from '@/types';
|
||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
|
import { LatLngBounds } from 'leaflet';
|
||||||
|
|
||||||
import atomWithDebounce from "@/utils/atomWithDebounce";
|
import atomWithDebounce from "@/utils/atomWithDebounce";
|
||||||
|
|
||||||
export const tournamentsAtom = atom<Tournament[]>([]);
|
export const tournamentsAtom = atom<Tournament[]>([]);
|
||||||
export const searchStringAtom = atom('');
|
export const searchStringAtom = atom('');
|
||||||
|
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
|
||||||
|
export const syncVisibleAtom = atom(true);
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
currentValueAtom: hoveredMapTournamentIdAtom,
|
currentValueAtom: hoveredMapTournamentIdAtom,
|
||||||
@@ -18,7 +21,16 @@ export const {
|
|||||||
export const filteredTournamentsAtom = atom((get) => {
|
export const filteredTournamentsAtom = atom((get) => {
|
||||||
const tournaments = get(tournamentsAtom);
|
const tournaments = get(tournamentsAtom);
|
||||||
const searchString = get(searchStringAtom).trim();
|
const searchString = get(searchStringAtom).trim();
|
||||||
|
const mapBounds = get(mapBoundsAtom);
|
||||||
|
const syncVisible = get(syncVisibleAtom);
|
||||||
|
|
||||||
if (searchString === '') return tournaments;
|
// When searching, we search all the tournament, regardless of the map display
|
||||||
return tournaments.filter((t) => t.town.includes(searchString.toUpperCase()))
|
if (searchString !== '')
|
||||||
|
return tournaments.filter((t) => t.town.includes(searchString.toUpperCase()))
|
||||||
|
|
||||||
|
// If we not syncing to the map, return all tournaments
|
||||||
|
if (mapBounds === null || !syncVisible) return tournaments;
|
||||||
|
|
||||||
|
// Filter by those in the current map bounds
|
||||||
|
return tournaments.filter(tournament => mapBounds.contains({ lat: tournament.coordinates[0], lng: tournament.coordinates[1]}))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"searchLabel": "Search",
|
"searchLabel": "Search",
|
||||||
"searchPlaceholder": "Search",
|
"searchPlaceholder": "Search",
|
||||||
|
"syncWithMapCheckbox": "Tournaments visible on the map",
|
||||||
"noneFound": "No tournaments found",
|
"noneFound": "No tournaments found",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"town": "Town",
|
"town": "Town",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
"loading": "Téléchargement...",
|
"loading": "Téléchargement...",
|
||||||
"searchLabel": "Rechercher",
|
"searchLabel": "Rechercher",
|
||||||
"searchPlaceholder": "Rechercher",
|
"searchPlaceholder": "Rechercher",
|
||||||
|
"syncWithMapCheckbox": "Tournois visibles sur la carte",
|
||||||
"noneFound": "Pas de tournois trouvé",
|
"noneFound": "Pas de tournois trouvé",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"town": "Ville",
|
"town": "Ville",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function atomWithDebounce<T>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// exported atom setter to clear timeout if needed
|
// exported atom setter to clear timeout if needed
|
||||||
const clearTimeoutAtom = atom(null, (get, set, _arg) => {
|
const clearTimeoutAtom = atom(null, (get, set) => {
|
||||||
clearTimeout(get(prevTimeoutAtom));
|
clearTimeout(get(prevTimeoutAtom));
|
||||||
set(isDebouncingAtom, false);
|
set(isDebouncingAtom, false);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user