diff --git a/app/[lang]/tournois/TournamentMap.tsx b/app/[lang]/tournois/TournamentMap.tsx index 53f4d3d..856202e 100644 --- a/app/[lang]/tournois/TournamentMap.tsx +++ b/app/[lang]/tournois/TournamentMap.tsx @@ -28,6 +28,7 @@ import { rapidAtom, blitzAtom, otherAtom, + normsOnlyAtom, } from "@/app/atoms"; import Legend from "./Legend"; @@ -93,6 +94,8 @@ const stopBouncingMarkers = () => { const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => { const tournaments = useAtomValue(tournamentsAtom); + const normsOnly = useAtomValue(normsOnlyAtom); + const markerRefs = useRef | null>>({}); const clusterRef = useRef(null); const expandedClusterMarkerRef = useRef(null); diff --git a/app/[lang]/tournois/TournamentMarker.tsx b/app/[lang]/tournois/TournamentMarker.tsx index 07f9c7c..b52c3b5 100644 --- a/app/[lang]/tournois/TournamentMarker.tsx +++ b/app/[lang]/tournois/TournamentMarker.tsx @@ -6,6 +6,7 @@ import L from "leaflet"; import { Marker, Popup, MarkerProps } from "react-leaflet"; import { useTranslations } from "next-intl"; import { useSetAtom } from "jotai"; +import { FaTrophy } from "react-icons/fa"; import { debouncedHoveredMapTournamentIdAtom } from "@/app/atoms"; @@ -63,14 +64,20 @@ export const TournamentMarker = forwardRef< {...markerProps} > -

- {tournament.date} -
+

+ {tournament.date} + {tournament.tournament} -

- {t("approx")} + {tournament.norm && ( +
+ + {t("norm")} +
+ )} + {t("approx")} +
); diff --git a/app/[lang]/tournois/TournamentTable.tsx b/app/[lang]/tournois/TournamentTable.tsx index 5dce438..becf063 100644 --- a/app/[lang]/tournois/TournamentTable.tsx +++ b/app/[lang]/tournois/TournamentTable.tsx @@ -5,10 +5,13 @@ import { useTranslations } from "next-intl"; import { useAtomValue, useSetAtom, useAtom } from "jotai"; import { twMerge } from "tailwind-merge"; import { FaExternalLinkAlt } from "react-icons/fa"; +import { FaTrophy } from "react-icons/fa"; +import { Tooltip } from "react-tooltip"; import { filteredTournamentsListAtom, syncVisibleAtom, + normsOnlyAtom, hoveredMapTournamentIdAtom, debouncedHoveredMapTournamentIdAtom, debouncedHoveredListTournamentIdAtom, @@ -24,6 +27,7 @@ export default function TournamentTable() { const filteredTournaments = useAtomValue(filteredTournamentsListAtom); const [syncVisible, setSyncVisible] = useAtom(syncVisibleAtom); + const [normsOnly, setNormsOnly] = useAtom(normsOnlyAtom); const hoveredMapTournamentId = useAtomValue(hoveredMapTournamentIdAtom); const debouncedHoveredMapTournamentId = useAtomValue( debouncedHoveredMapTournamentIdAtom, @@ -52,7 +56,8 @@ export default function TournamentTable() { >
-
+ +
+ +
@@ -115,7 +130,17 @@ export default function TournamentTable() { > {tournament.date} {tournament.town} - {tournament.tournament} + + + {tournament.norm && ( + + )} + {tournament.tournament} + + {t("timeControlEnum", { tc: tournament.timeControl })} @@ -129,6 +154,13 @@ export default function TournamentTable() { )} + + +
+ + {t("norm")} +
+
); } diff --git a/app/[lang]/tournois/page.tsx b/app/[lang]/tournois/page.tsx index f17d899..f144d0c 100644 --- a/app/[lang]/tournois/page.tsx +++ b/app/[lang]/tournois/page.tsx @@ -15,6 +15,7 @@ export interface TournamentData { tournament: string; url: string; time_control: "Cadence Lente" | "Rapide" | "Blitz" | string; + norm_tournament: boolean; date: string; coordinates: [number, number]; } @@ -58,6 +59,7 @@ const getTournaments = async () => { _id: t._id.toString(), timeControl: tcMap[t.time_control] ?? TimeControl.Other, latLng: { lat: t.coordinates[0], lng: t.coordinates[1] }, + norm: t.norm_tournament, })); } catch (error) { errorLog(error); diff --git a/app/atoms.ts b/app/atoms.ts index f217643..7e94d84 100644 --- a/app/atoms.ts +++ b/app/atoms.ts @@ -7,6 +7,7 @@ import atomWithDebounce from "@/utils/atomWithDebounce"; export const tournamentsAtom = atom([]); export const mapBoundsAtom = atom(null); export const syncVisibleAtom = atom(true); +export const normsOnlyAtom = atom(false); export const searchStringAtom = atom(""); export const classicAtom = atom(true); @@ -43,20 +44,24 @@ export const filteredTournamentsListAtom = atom((get) => { const tournaments = get(filteredTournamentsByTimeControlAtom); const mapBounds = get(mapBoundsAtom); const syncVisible = get(syncVisibleAtom); - + const normsOnly = get(normsOnlyAtom); const searchString = get(searchStringAtom).trim(); + const filteredByNorm = normsOnly + ? tournaments.filter((t) => t.norm) + : tournaments; + // When searching, we search all the tournament, regardless of the map display if (searchString !== "") - return tournaments.filter((t) => + return filteredByNorm.filter((t) => t.town.includes(searchString.toUpperCase()), ); // If we not syncing to the map, return all tournaments - if (mapBounds === null || !syncVisible) return tournaments; + if (mapBounds === null || !syncVisible) return filteredByNorm; // Filter by those in the current map bounds - return tournaments.filter((tournament) => + return filteredByNorm.filter((tournament) => mapBounds.contains(tournament.latLng), ); }); diff --git a/messages/en.json b/messages/en.json index da3b400..ebd160b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -29,12 +29,14 @@ "searchLabel": "Search", "searchPlaceholder": "Search", "syncWithMapCheckbox": "Tournaments visible on the map", + "normsOnly": "Only norm tournaments", "noneFound": "No tournaments found", "date": "Date", "town": "Town", "tournament": "Tournament", "timeControl": "Time Control", "approx": "Géo-localisation is approximative", + "norm": "Norm Tournament", "timeControlEnum": "{tc, select, Classic {Classic} Rapid {Rapid} Blitz {Blitz} Other {Other} other {{tc}}}", "clearButton": "Clear" }, diff --git a/messages/fr.json b/messages/fr.json index 46af5b3..e9cb259 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -29,12 +29,14 @@ "searchLabel": "Rechercher", "searchPlaceholder": "Rechercher", "syncWithMapCheckbox": "Tournois visibles sur la carte", + "normsOnly": "Uniquement tournois à normes", "noneFound": "Pas de tournois trouvé", "date": "Date", "town": "Ville", "tournament": "Tournois", "timeControl": "Cadence", "approx": "Géolocalisation approximative", + "norm": "Tournoi à norme", "timeControlEnum": "{tc, select, Classic {Cadence Lente} Rapid {Rapide} Blitz {Blitz} Other {Autres} other {{tc}}}", "clearButton": "Effacer" }, diff --git a/package-lock.json b/package-lock.json index a0da857..7e69f00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "react-icons": "^4.10.1", "react-leaflet": "^4.2.1", "react-leaflet-cluster": "^2.1.0", + "react-tooltip": "^5.18.0", "sharp": "^0.32.1", "tailwind-merge": "^1.13.2", "tailwindcss": "3.3.2", @@ -327,6 +328,19 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz", + "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==" + }, + "node_modules/@floating-ui/dom": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.5.tgz", + "integrity": "sha512-96KnRWkRnuBSSFbj0sFGwwOUd8EkiecINVl0O9wiZlZ64EkpyAOG3Xc2vKKNJmru0Z7RqWNymA+6b8OZqjgyyw==", + "dependencies": { + "@floating-ui/core": "^1.3.1" + } + }, "node_modules/@formatjs/ecma402-abstract": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.0.tgz", @@ -1914,6 +1928,11 @@ "node": ">=8" } }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -6153,6 +6172,19 @@ "react-leaflet": "^4.0.0" } }, + "node_modules/react-tooltip": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.18.0.tgz", + "integrity": "sha512-qjDK/skUJJ27sc9lTWeNxp2rLzmenBTskSsRiDOCPnupGSz2GhL5IZxDizK/sOsk0hn5iSCywt+3jKxUJ3Y4Sw==", + "dependencies": { + "@floating-ui/dom": "^1.0.0", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 9e5a326..04cffbb 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "react-icons": "^4.10.1", "react-leaflet": "^4.2.1", "react-leaflet-cluster": "^2.1.0", + "react-tooltip": "^5.18.0", "sharp": "^0.32.1", "tailwind-merge": "^1.13.2", "tailwindcss": "3.3.2", diff --git a/types.ts b/types.ts index e677bac..5d5cecc 100644 --- a/types.ts +++ b/types.ts @@ -16,6 +16,7 @@ export interface Tournament { timeControl: TimeControl; date: string; latLng: LatLngLiteral; + norm: boolean; } export type ScrollableElement = Window | HTMLElement;