diff --git a/.swcrc b/.swcrc new file mode 100644 index 0000000..aad97f2 --- /dev/null +++ b/.swcrc @@ -0,0 +1,7 @@ +{ + "jsc": { + "experimental": { + "plugins": [["@swc-jotai/react-refresh", {}]] + } + } +} diff --git a/app/[lang]/components/Navbar.tsx b/app/[lang]/components/Navbar.tsx index 952df12..810e10f 100644 --- a/app/[lang]/components/Navbar.tsx +++ b/app/[lang]/components/Navbar.tsx @@ -1,7 +1,6 @@ import Link from "next-intl/link"; import { useTranslations } from "next-intl"; -import ThemeSwitcher from "./ThemeSwitcher"; import Hamburger from "./Hamburger"; export default function Navbar() { diff --git a/app/[lang]/layout.tsx b/app/[lang]/layout.tsx index 6bb791b..a4e8e6b 100644 --- a/app/[lang]/layout.tsx +++ b/app/[lang]/layout.tsx @@ -6,6 +6,7 @@ import { notFound } from "next/navigation"; import { ReactNode } from "react"; import "@/app/globals.css"; +import Providers from "./providers"; import TestableLayout from "./components/TestableLayout"; const inter = Inter({ subsets: ["latin"] }); @@ -50,9 +51,11 @@ export default async function RootLayout({ return ( - - {children} - + + + {children} + + diff --git a/app/[lang]/providers.tsx b/app/[lang]/providers.tsx new file mode 100644 index 0000000..767e382 --- /dev/null +++ b/app/[lang]/providers.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { Provider } from "jotai"; + +export default function Providers({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/app/[lang]/tournois/SearchBar.tsx b/app/[lang]/tournois/SearchBar.tsx index c7e7b98..bec0624 100644 --- a/app/[lang]/tournois/SearchBar.tsx +++ b/app/[lang]/tournois/SearchBar.tsx @@ -1,16 +1,11 @@ -import { Dispatch, SetStateAction } from "react"; +import { useAtom } from "jotai"; import { useTranslations } from "next-intl"; -type SearchBarProps = { - tournamentFilter: string; - setTournamentFilter: Dispatch>; -}; +import { searchStringAtom } from "@/app/atoms"; -const SearchBar = ({ - tournamentFilter, - setTournamentFilter, -}: SearchBarProps) => { +const SearchBar = () => { const t = useTranslations("Tournaments"); + const [searchString, setSearchString] = useAtom(searchStringAtom); return (
@@ -37,8 +32,8 @@ const SearchBar = ({ id="table-search" className="block w-80 rounded-lg border border-gray-300 bg-gray-50 p-2.5 pl-10 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500" placeholder={t("searchPlaceholder")} - value={tournamentFilter} - onChange={(e) => setTournamentFilter(e.target.value)} + value={searchString} + onChange={(e) => setSearchString(e.target.value)} />
diff --git a/app/[lang]/tournois/TournamentMap.tsx b/app/[lang]/tournois/TournamentMap.tsx index 2603fb1..f875d6e 100644 --- a/app/[lang]/tournois/TournamentMap.tsx +++ b/app/[lang]/tournois/TournamentMap.tsx @@ -1,31 +1,62 @@ "use client"; -import { TournamentDataProps } from "@/types"; +import { Tournament } from "@/types"; import { LatLngLiteral } from "leaflet"; +import { + MapContainer, + TileLayer, + LayersControl, + LayerGroup, +} from "react-leaflet"; +import { useAtomValue } from "jotai"; import "leaflet/dist/leaflet.css"; import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css"; import "leaflet-defaulticon-compatibility"; -import { MapContainer, TileLayer, LayersControl } from "react-leaflet"; +import { tournamentsAtom } from "@/app/atoms"; -import { createLayerGroups } from "@/utils/layerGroups"; import Legend from "./Legend"; +import { TournamentMarker } from "./TournamentMarker"; -export default function TournamentMap({ tournamentData }: TournamentDataProps) { +export default function TournamentMap() { + const tournaments = useAtomValue(tournamentsAtom); const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 }; - const classicalMarkers = createLayerGroups("Cadence Lente", "green", { - tournamentData, - }); - const rapidMarkers = createLayerGroups("Rapide", "blue", { - tournamentData, - }); - const blitzMarkers = createLayerGroups("Blitz", "yellow", { - tournamentData, - }); + const createLayerGroups = ( + timeControl: string, + colour: string, + tournaments: Tournament[] + ) => { + const filteredTournaments = tournaments.filter( + (t) => t.time_control === timeControl + ); - const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData }); + const layerGroup = filteredTournaments.map((tournament) => { + return ( + + ); + }); + + return ( + + {layerGroup} + + ); + }; + + const classicalMarkers = createLayerGroups( + "Cadence Lente", + "green", + tournaments + ); + const rapidMarkers = createLayerGroups("Rapide", "blue", tournaments); + const blitzMarkers = createLayerGroups("Blitz", "yellow", tournaments); + const otherMarkers = createLayerGroups("1h KO", "red", tournaments); return (
diff --git a/app/[lang]/tournois/TournamentMarker.tsx b/app/[lang]/tournois/TournamentMarker.tsx new file mode 100644 index 0000000..7c3924b --- /dev/null +++ b/app/[lang]/tournois/TournamentMarker.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import { Tournament } from "@/types"; +import L, { LatLngLiteral } from "leaflet"; +import { Marker, Popup, MarkerProps } from "react-leaflet"; +import { useTranslations } from "next-intl"; +import { useAtomValue, useSetAtom } from "jotai"; +import "leaflet.smooth_marker_bouncing"; + +import { + debouncedHoveredListTournamentIdAtom, + debouncedHoveredMapTournamentIdAtom, +} from "@/app/atoms"; + +const coordinateRandomisation = (lat: number, lng: number): LatLngLiteral => { + const randomisation = () => Math.random() * (-0.01 - 0.01) + 0.01; + return { + lat: lat + randomisation(), + lng: lng + randomisation(), + }; +}; + +type TournamentMarkerProps = { + tournament: Tournament; + colour: string; +} & Omit; + +export const TournamentMarker = ({ + tournament, + colour, + ...markerProps +}: TournamentMarkerProps) => { + const t = useTranslations("Tournaments"); + const markerRef = useRef | null>(null); + const position = useRef( + coordinateRandomisation( + tournament.coordinates[0], + tournament.coordinates[1] + ) + ); + + const hoveredListTournamentId = useAtomValue( + debouncedHoveredListTournamentIdAtom + ); + const setHoveredMapTournamentId = useSetAtom( + debouncedHoveredMapTournamentIdAtom + ); + + useEffect(() => { + if (!markerRef.current) return; + if (hoveredListTournamentId === tournament._id) { + // @ts-ignore (bounce comes from leaflet.smooth_marker_bouncing and isn't defined by the Typescript class) + markerRef.current.bounce(); + } else { + // @ts-ignore + markerRef.current.stopBouncing(); + } + }, [hoveredListTournamentId, tournament._id]); + + 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 ( + setHoveredMapTournamentId(tournament._id), + mouseout: () => setHoveredMapTournamentId(null), + }} + {...markerProps} + > + +

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

+ {t("approx")} +
+
+ ); +}; diff --git a/app/[lang]/tournois/TournamentTable.tsx b/app/[lang]/tournois/TournamentTable.tsx index 3da6b2f..155287e 100644 --- a/app/[lang]/tournois/TournamentTable.tsx +++ b/app/[lang]/tournois/TournamentTable.tsx @@ -1,65 +1,40 @@ "use client"; -import { TournamentDataProps } from "@/types"; -import { useEffect, useState } from "react"; import { useTranslations } from "next-intl"; +import { useAtomValue, useSetAtom } from "jotai"; +import { twMerge } from "tailwind-merge"; + +import { + filteredTournamentsAtom, + hoveredMapTournamentIdAtom, + debouncedHoveredMapTournamentIdAtom, + debouncedHoveredListTournamentIdAtom, +} from "@/app/atoms"; import SearchBar from "./SearchBar"; import ScrollToTopButton from "./ScrollToTopButton"; +import { useEffect } from "react"; -export default function TournamentTable({ - tournamentData, -}: TournamentDataProps) { - let tableData; +export default function TournamentTable() { const t = useTranslations("Tournaments"); - const [searchQuery, setSearchQuery] = useState(""); // text from search bar - const [filteredTournamentData, setFilteredTournamentData] = - useState(tournamentData); + + const filteredTournaments = useAtomValue(filteredTournamentsAtom); + const hoveredMapTournamentId = useAtomValue(hoveredMapTournamentIdAtom); + const debouncedHoveredMapTournamentId = useAtomValue( + debouncedHoveredMapTournamentIdAtom + ); + const setHoveredListTournamentId = useSetAtom( + debouncedHoveredListTournamentIdAtom + ); useEffect(() => { - setFilteredTournamentData( - tournamentData.filter((t) => t.town.includes(searchQuery.toUpperCase())) - ); - }, [searchQuery, tournamentData]); + if (debouncedHoveredMapTournamentId === null) return; - // TODO move this section into its own function - if (filteredTournamentData.length === 0) { - tableData = ( - - - {t("noneFound")} - - + const tournamentRow = document.getElementById( + debouncedHoveredMapTournamentId ); - } else { - tableData = filteredTournamentData.map((data) => ( - - - - {data.date} - - - - - {data.town} - - - - - {data.tournament} - - - - - {data.time_control} - - - - )); - } + tournamentRow?.scrollIntoView({ behavior: "smooth" }); + }, [debouncedHoveredMapTournamentId]); return (
- +
@@ -96,7 +68,50 @@ export default function TournamentTable({ - {tableData} + + {filteredTournaments.length === 0 ? ( + + + {t("noneFound")} + + + ) : ( + filteredTournaments.map((tournament) => ( + setHoveredListTournamentId(tournament._id)} + onMouseLeave={() => setHoveredListTournamentId(null)} + className={twMerge( + "scroll-m-20 bg-white text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-900", + hoveredMapTournamentId === tournament._id && + "bg-gray-200 dark:bg-gray-900" + )} + > + + + {tournament.date} + + + + + {tournament.town} + + + + + {tournament.tournament} + + + + + {tournament.time_control} + + + + )) + )} +
); diff --git a/app/[lang]/tournois/TournamentsDisplay.tsx b/app/[lang]/tournois/TournamentsDisplay.tsx new file mode 100644 index 0000000..c9fd9dd --- /dev/null +++ b/app/[lang]/tournois/TournamentsDisplay.tsx @@ -0,0 +1,49 @@ +"use client"; + +import dynamic from "next/dynamic"; +import { useTranslations } from "next-intl"; +import { useHydrateAtoms } from "jotai/utils"; + +import { Tournament } from "@/types"; +import { tournamentsAtom } from "@/app/atoms"; + +import TournamentTable from "./TournamentTable"; + +const LoadingMap = () => { + const t = useTranslations("Tournaments"); + return ( +
+

{t("loading")}

+
+ ); +}; + +/** + * Imports the tournament map component, ensuring CSR only. + * @remarks SSR is not supported by react-leaflet + */ +const TournamentMap = dynamic(() => import("./TournamentMap"), { + ssr: false, + loading: LoadingMap, +}); + +type TournamentsDisplayProps = { + tournaments: Tournament[]; +}; + +export default async function TournamentsDisplay({ + tournaments, +}: TournamentsDisplayProps) { + useHydrateAtoms([[tournamentsAtom, tournaments]]); + + return ( +
+
+ +
+
+ +
+
+ ); +} diff --git a/app/[lang]/tournois/page.tsx b/app/[lang]/tournois/page.tsx index 5bb57e2..cb97587 100644 --- a/app/[lang]/tournois/page.tsx +++ b/app/[lang]/tournois/page.tsx @@ -1,38 +1,45 @@ import clientPromise from "@/lib/mongodb"; -import dynamic from "next/dynamic"; -import { useTranslations } from "next-intl"; -import { dateOrderingFrance } from "@/utils/dbDateOrdering"; import { errorLog } from "@/utils/logger"; +import { Tournament } from "@/types"; -import TournamentTable from "./TournamentTable"; +import TournamentsDisplay from "./TournamentsDisplay"; -export const revalidate = 3600; // revalidate cache every 6 hours; - -const LoadingMap = () => { - const t = useTranslations("Tournaments"); - return ( -
-

{t("loading")}

-
- ); -}; - -/** - * Imports the tournament map component, ensuring CSR only. - * @remarks SSR is not supported by react-leaflet - */ -const TournamentMap = dynamic(() => import("./TournamentMap"), { - ssr: false, - loading: LoadingMap, -}); +export const revalidate = 3600; // Revalidate cache every 6 hours const getTournaments = async () => { try { const client = await clientPromise; const db = client.db("tournamentsFranceDB"); - const data = await dateOrderingFrance(db); - return JSON.stringify(data); + const data = await db + .collection("tournaments") + .aggregate([ + { + $addFields: { + dateParts: { + $dateFromString: { + dateString: "$date", + format: "%d/%m/%Y", + }, + }, + }, + }, + { + $sort: { + dateParts: 1, + }, + }, + { + $unset: "dateParts", + }, + ]) + .toArray(); + + // We map the ObjectId to a string so that it can be serialized and sent to a client component + return data.map((t) => ({ + ...t, + _id: t._id.toString(), + })); } catch (error) { errorLog(error); throw new Error("Error fetching tournament data"); @@ -40,16 +47,7 @@ const getTournaments = async () => { }; export default async function Tournaments() { - const tournamentData = await getTournaments(); + const tournaments = await getTournaments(); - return ( -
-
- -
-
- -
-
- ); + return ; } diff --git a/app/api/v1/tournaments/france/route.ts b/app/api/v1/tournaments/france/route.ts deleted file mode 100644 index a09f79b..0000000 --- a/app/api/v1/tournaments/france/route.ts +++ /dev/null @@ -1,29 +0,0 @@ -import clientPromise from "@/lib/mongodb"; -import { dateOrderingFrance } from "@/utils/dbDateOrdering"; -import { errorLog } from "@/utils/logger"; - -/** - * Tournament data API endpoint - * @route /api/v1/tournaments/france - * @public - */ -export const revalidate = 3600; // revalidate cache every 6 hours -export async function GET() { - const headers = { - "Content-Type": "application/json", - }; - try { - const client = await clientPromise; - const db = client.db("tournamentsFranceDB"); - const results = await dateOrderingFrance(db); - const data = results.map(({ _id, ...rest }) => ({ - id: _id, - ...rest, - })); - - return new Response(JSON.stringify(data), { status: 200, headers }); - } catch (error) { - errorLog(error); - return new Response(JSON.stringify(error), { status: 500, headers }); - } -} diff --git a/app/atoms.ts b/app/atoms.ts new file mode 100644 index 0000000..6488ee4 --- /dev/null +++ b/app/atoms.ts @@ -0,0 +1,24 @@ +import { Tournament } from '@/types'; +import { atom } from 'jotai'; + +import atomWithDebounce from "@/utils/atomWithDebounce"; + +export const tournamentsAtom = atom([]); +export const searchStringAtom = atom(''); + +export const { + currentValueAtom: hoveredMapTournamentIdAtom, + debouncedValueAtom: debouncedHoveredMapTournamentIdAtom, +} = atomWithDebounce(null); + +export const { + debouncedValueAtom: debouncedHoveredListTournamentIdAtom, +} = atomWithDebounce(null); + +export const filteredTournamentsAtom = atom((get) => { + const tournaments = get(tournamentsAtom); + const searchString = get(searchStringAtom).trim(); + + if (searchString === '') return tournaments; + return tournaments.filter((t) => t.town.includes(searchString.toUpperCase())) +}) diff --git a/package-lock.json b/package-lock.json index ac5ac46..cc4147f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,10 @@ "autoprefixer": "10.4.14", "eslint": "8.43.0", "eslint-config-next": "^13.4.7", + "jotai": "^2.2.1", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.1", + "leaflet.smooth_marker_bouncing": "^3.0.3", "mongodb": "^5.6.0", "next": "^13.4.7", "next-intl": "^3.0.0-beta.7", @@ -28,10 +30,12 @@ "react-icons": "^4.10.1", "react-leaflet": "^4.2.1", "sharp": "^0.32.1", + "tailwind-merge": "^1.13.2", "tailwindcss": "3.3.2", "typescript": "5.1.6" }, "devDependencies": { + "@swc-jotai/react-refresh": "^0.0.8", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/leaflet": "^1.9.3", @@ -741,6 +745,12 @@ "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", "dev": true }, + "node_modules/@swc-jotai/react-refresh": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@swc-jotai/react-refresh/-/react-refresh-0.0.8.tgz", + "integrity": "sha512-fHMenTg1jeETEShCh/wlDxRpR6DZAXIuDuFIB9fZ4yf+JfrWzQkPIvPN3E0efSpOham9ucRspS0uI82PxBbCjg==", + "dev": true + }, "node_modules/@swc/helpers": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", @@ -4423,6 +4433,22 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jotai": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.2.1.tgz", + "integrity": "sha512-Gz4tpbRQy9OiFgBwF9F7TieDn0UTE3C0IFSDuxHjOIvgn2tACH30UKz6p/wIlfoZROXSTCIxEvYEa7Y25WM+8g==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4558,6 +4584,11 @@ "resolved": "https://registry.npmjs.org/leaflet-defaulticon-compatibility/-/leaflet-defaulticon-compatibility-0.1.1.tgz", "integrity": "sha512-vDBFdlUAwjSEGep9ih8kfJilf6yN8V9zTbF5NC/1ZwLeGko3RUQepspPnGCRMFV51dY3Lb3hziboicrFz+rxQA==" }, + "node_modules/leaflet.smooth_marker_bouncing": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/leaflet.smooth_marker_bouncing/-/leaflet.smooth_marker_bouncing-3.0.3.tgz", + "integrity": "sha512-Y+1MJ1nX0vy/NjvzW4Kq2gE3Pnpz3fgP3dZJQNMQW90bFQ8d2TjXrqazP5oWKNUWjvrVVzfMv/FrB7vUFmsLDA==" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6649,6 +6680,15 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tailwind-merge": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.13.2.tgz", + "integrity": "sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", diff --git a/package.json b/package.json index 9c29655..0b05154 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,10 @@ "autoprefixer": "10.4.14", "eslint": "8.43.0", "eslint-config-next": "^13.4.7", + "jotai": "^2.2.1", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.1", + "leaflet.smooth_marker_bouncing": "^3.0.3", "mongodb": "^5.6.0", "next": "^13.4.7", "next-intl": "^3.0.0-beta.7", @@ -33,10 +35,12 @@ "react-icons": "^4.10.1", "react-leaflet": "^4.2.1", "sharp": "^0.32.1", + "tailwind-merge": "^1.13.2", "tailwindcss": "3.3.2", "typescript": "5.1.6" }, "devDependencies": { + "@swc-jotai/react-refresh": "^0.0.8", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/leaflet": "^1.9.3", diff --git a/types.ts b/types.ts index 573d2f3..a7c14c5 100644 --- a/types.ts +++ b/types.ts @@ -9,8 +9,4 @@ export interface Tournament { coordinates: [number, number]; } -export interface TournamentDataProps { - tournamentData: Tournament[]; -} - export type ScrollableElement = Window | HTMLElement; diff --git a/utils/atomWithDebounce.ts b/utils/atomWithDebounce.ts new file mode 100644 index 0000000..fbf076a --- /dev/null +++ b/utils/atomWithDebounce.ts @@ -0,0 +1,66 @@ +import { atom, SetStateAction } from "jotai"; + +export default function atomWithDebounce( + initialValue: T, + delayMilliseconds = 500, + shouldDebounceOnReset = false +) { + const prevTimeoutAtom = atom | undefined>( + undefined + ); + + // DO NOT EXPORT currentValueAtom as using this atom to set state can cause + // inconsistent state between currentValueAtom and debouncedValueAtom + const _currentValueAtom = atom(initialValue); + const isDebouncingAtom = atom(false); + + const debouncedValueAtom = atom( + initialValue, + (get, set, update: SetStateAction) => { + clearTimeout(get(prevTimeoutAtom)); + + const prevValue = get(_currentValueAtom); + const nextValue = + typeof update === "function" + ? (update as (prev: T) => T)(prevValue) + : update; + + const onDebounceStart = () => { + set(_currentValueAtom, nextValue); + set(isDebouncingAtom, true); + }; + + const onDebounceEnd = () => { + set(debouncedValueAtom, nextValue); + set(isDebouncingAtom, false); + }; + + onDebounceStart(); + + if (!shouldDebounceOnReset && nextValue === initialValue) { + onDebounceEnd(); + return; + } + + const nextTimeoutId = setTimeout(() => { + onDebounceEnd(); + }, delayMilliseconds); + + // set previous timeout atom in case it needs to get cleared + set(prevTimeoutAtom, nextTimeoutId); + } + ); + + // exported atom setter to clear timeout if needed + const clearTimeoutAtom = atom(null, (get, set, _arg) => { + clearTimeout(get(prevTimeoutAtom)); + set(isDebouncingAtom, false); + }); + + return { + currentValueAtom: atom((get) => get(_currentValueAtom)), + isDebouncingAtom, + clearTimeoutAtom, + debouncedValueAtom + }; +} diff --git a/utils/dbDateOrdering.ts b/utils/dbDateOrdering.ts deleted file mode 100644 index 3a6ed06..0000000 --- a/utils/dbDateOrdering.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Db } from "mongodb"; - -/** - * Converts date from string into a date to allow ordering - */ -export const dateOrderingFrance = async (db: Db) => { - return await db - .collection("tournaments") - .aggregate([ - { - $addFields: { - dateParts: { - $dateFromString: { - dateString: "$date", - format: "%d/%m/%Y", - }, - }, - }, - }, - { - $sort: { - dateParts: 1, - }, - }, - { - $unset: "dateParts", - }, - ]) - .toArray(); -}; diff --git a/utils/layerGroups.tsx b/utils/layerGroups.tsx deleted file mode 100644 index cc115d7..0000000 --- a/utils/layerGroups.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { TournamentDataProps, Tournament } from "@/types"; -import L, { LatLngLiteral } from "leaflet"; -import { - LayerGroup, - LayersControl, - Marker, - Popup, - MarkerProps, -} from "react-leaflet"; -import { useTranslations } from "next-intl"; - -const coordinateRandomisation = (lat: number, lng: number): LatLngLiteral => { - const randomisation = () => Math.random() * (-0.01 - 0.01) + 0.01; - return { - lat: lat + randomisation(), - lng: lng + randomisation(), - }; -}; - -type TournamentMarkerProps = { - tournament: Tournament; - colour: string; -} & Omit; - -export const TournamentMarker = ({ - tournament, - colour, - ...markerProps -}: TournamentMarkerProps) => { - const t = useTranslations("Tournaments"); - - 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 ( - - -

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

- {t("approx")} -
-
- ); -}; - -export const createLayerGroups = ( - timeControl: string, - colour: string, - { tournamentData }: TournamentDataProps -) => { - const filteredTournaments = tournamentData.filter( - (t) => t.time_control === timeControl - ); - - const layerGroup = filteredTournaments.map((tournament) => { - return ( - - ); - }); - - return ( - - {layerGroup} - - ); -};