Sync list to map (#42)

This commit is contained in:
Timothy Armes
2023-07-05 13:36:12 +02:00
parent 1715b1646b
commit deace43225
8 changed files with 48 additions and 10 deletions
+1
View File
@@ -9,6 +9,7 @@
"Française",
"Lente",
"localisation",
"moveend",
"randomisation",
"Rapide",
"Rees",
+1 -1
View File
@@ -8,7 +8,7 @@ const SearchBar = () => {
const [searchString, setSearchString] = useAtom(searchStringAtom);
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">
{t("searchLabel")}
</label>
+13 -2
View File
@@ -7,18 +7,28 @@ import {
TileLayer,
LayersControl,
LayerGroup,
useMapEvent,
} from "react-leaflet";
import { useAtomValue } from "jotai";
import { useAtomValue, useSetAtom } from "jotai";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import "leaflet-defaulticon-compatibility";
import { tournamentsAtom } from "@/app/atoms";
import { tournamentsAtom, mapBoundsAtom } from "@/app/atoms";
import Legend from "./Legend";
import { TournamentMarker } from "./TournamentMarker";
const MapEvents = () => {
const setMapBounds = useSetAtom(mapBoundsAtom);
const map = useMapEvent("moveend", () => {
setMapBounds(map.getBounds());
});
return null;
};
export default function TournamentMap() {
const tournaments = useAtomValue(tournamentsAtom);
const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 };
@@ -68,6 +78,7 @@ export default function TournamentMap() {
height: "100%",
}}
>
<MapEvents />
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
+16 -4
View File
@@ -1,11 +1,12 @@
"use client";
import { useTranslations } from "next-intl";
import { useAtomValue, useSetAtom } from "jotai";
import { useAtomValue, useSetAtom, useAtom } from "jotai";
import { twMerge } from "tailwind-merge";
import {
filteredTournamentsAtom,
syncVisibleAtom,
hoveredMapTournamentIdAtom,
debouncedHoveredMapTournamentIdAtom,
debouncedHoveredListTournamentIdAtom,
@@ -19,6 +20,7 @@ export default function TournamentTable() {
const t = useTranslations("Tournaments");
const filteredTournaments = useAtomValue(filteredTournamentsAtom);
const [syncVisible, setSyncVisible] = useAtom(syncVisibleAtom);
const hoveredMapTournamentId = useAtomValue(hoveredMapTournamentIdAtom);
const debouncedHoveredMapTournamentId = useAtomValue(
debouncedHoveredMapTournamentIdAtom
@@ -42,12 +44,22 @@ export default function TournamentTable() {
id="tournament-table"
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 />
<div>
<ScrollToTopButton />
<div className="text-gray-900 dark:text-white">
<label>
<input
type="checkbox"
checked={syncVisible}
onChange={() => setSyncVisible(!syncVisible)}
/>{" "}
{t("syncWithMapCheckbox")}
</label>
</div>
</div>
<ScrollToTopButton />
<table
className="relative w-full table-fixed text-center text-xs"
data-test="tournament-table"
+14 -2
View File
@@ -1,10 +1,13 @@
import { Tournament } from '@/types';
import { atom } from 'jotai';
import { LatLngBounds } from 'leaflet';
import atomWithDebounce from "@/utils/atomWithDebounce";
export const tournamentsAtom = atom<Tournament[]>([]);
export const searchStringAtom = atom('');
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
export const syncVisibleAtom = atom(true);
export const {
currentValueAtom: hoveredMapTournamentIdAtom,
@@ -18,7 +21,16 @@ export const {
export const filteredTournamentsAtom = atom((get) => {
const tournaments = get(tournamentsAtom);
const searchString = get(searchStringAtom).trim();
const mapBounds = get(mapBoundsAtom);
const syncVisible = get(syncVisibleAtom);
if (searchString === '') return tournaments;
return tournaments.filter((t) => t.town.includes(searchString.toUpperCase()))
// When searching, we search all the tournament, regardless of the map display
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]}))
})
+1
View File
@@ -35,6 +35,7 @@
"loading": "Loading...",
"searchLabel": "Search",
"searchPlaceholder": "Search",
"syncWithMapCheckbox": "Tournaments visible on the map",
"noneFound": "No tournaments found",
"date": "Date",
"town": "Town",
+1
View File
@@ -35,6 +35,7 @@
"loading": "Téléchargement...",
"searchLabel": "Rechercher",
"searchPlaceholder": "Rechercher",
"syncWithMapCheckbox": "Tournois visibles sur la carte",
"noneFound": "Pas de tournois trouvé",
"date": "Date",
"town": "Ville",
+1 -1
View File
@@ -52,7 +52,7 @@ export default function atomWithDebounce<T>(
);
// exported atom setter to clear timeout if needed
const clearTimeoutAtom = atom(null, (get, set, _arg) => {
const clearTimeoutAtom = atom(null, (get, set) => {
clearTimeout(get(prevTimeoutAtom));
set(isDebouncingAtom, false);
});