diff --git a/src/app/earthquakes/page.tsx b/src/app/earthquakes/page.tsx index 4e8cf30..5632ba1 100644 --- a/src/app/earthquakes/page.tsx +++ b/src/app/earthquakes/page.tsx @@ -8,6 +8,7 @@ import Sidebar from "@components/Sidebar"; import { createPoster } from "@utils/axiosHelpers"; import { Earthquake } from "@prismaclient"; import { getRelativeDate } from "@utils/formatters"; +import GeologicalEvent from "@appTypes/Event"; export default function Earthquakes() { const [selectedEventId, setSelectedEventId] = useState(""); @@ -18,14 +19,20 @@ export default function Earthquakes() { const earthquakeEvents = useMemo( () => data && data.earthquakes - ? data.earthquakes.map((x: Earthquake) => ({ - id: x.id, - title: `Earthquake in ${x.code.split("-")[2]}`, - magnitude: x.magnitude, - longitude: x.longitude, - latitude: x.latitude, - text2: getRelativeDate(x.date), - })) + ? data.earthquakes + .map( + (x: Earthquake): GeologicalEvent => ({ + id: x.code, + title: `Earthquake in ${x.code.split("-")[2]}`, + magnitude: x.magnitude, + longitude: x.longitude, + latitude: x.latitude, + text1: "", + text2: getRelativeDate(x.date), + date: x.date, + }) + ) + .sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime()) // Remove Date conversion : [], [data] ); diff --git a/src/app/observatories/page.tsx b/src/app/observatories/page.tsx index bf475a3..e147def 100644 --- a/src/app/observatories/page.tsx +++ b/src/app/observatories/page.tsx @@ -1,4 +1,5 @@ "use client"; +import { useMemo } from "react"; import { useState } from "react"; import useSWR from "swr"; @@ -6,18 +7,41 @@ import useSWR from "swr"; import Sidebar from "@/components/Sidebar"; import Map from "@components/Map"; import { fetcher } from "@utils/axiosHelpers"; +import { Observatory } from "@prismaclient"; +import { getRelativeDate } from "@utils/formatters"; +import GeologicalEvent from "@appTypes/Event"; export default function Observatories() { const [selectedEventId, setSelectedEventId] = useState(""); const [hoveredEventId, setHoveredEventId] = useState(""); - // todo properly integrate loading const { data, error, isLoading } = useSWR("/api/observatories", fetcher); + // todo add in earthquake events + const observatoryEvents = useMemo( + () => + data && data.observatories + ? data.observatories + .map( + (x: Observatory): GeologicalEvent => ({ + id: x.id.toString(), + title: `New Observatory - ${x.name}`, + longitude: x.longitude, + latitude: x.latitude, + text1: "", + text2: getRelativeDate(x.dateEstablished), + date: x.dateEstablished, + }) + ) + .sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime()) // Remove Date conversion + : [], + [data] + ); + return (
>; - hoveredEventId: Event["id"]; + hoveredEventId: GeologicalEvent["id"]; setHoveredEventId: Dispatch>; mapType: String; } -// Map component with location-style pulsing dots, animations, and tooltips function MapComponent({ events, selectedEventId, @@ -32,9 +29,9 @@ function MapComponent({ const markers = useRef<{ [key: string]: mapboxgl.Marker }>({}); const [mapBounds, setMapBounds] = useState(); - const fitToBounds = useCallback((bounds: LngLatBounds) => { + const fitToBounds = useCallback((bounds: LngLatBounds, padding: number = 150) => { if (map.current && bounds) { - map.current!.fitBounds(bounds, { padding: 150, maxZoom: 10 }); + map.current!.fitBounds(bounds, { padding, maxZoom: 10, zoom: 1 }); } }, []); @@ -49,7 +46,7 @@ function MapComponent({ Object.values(markers.current).forEach((marker) => marker.getPopup()?.remove()); }, []); - const flyToEvent = useCallback((event: Event) => { + const flyToEvent = useCallback((event: GeologicalEvent) => { if (map.current) { map.current.flyTo({ center: [event.longitude, event.latitude], @@ -76,15 +73,13 @@ function MapComponent({ } map.current.on("load", () => { - // Fit map to bounds const bounds = new mapboxgl.LngLatBounds(); events.forEach((event) => { bounds.extend([event.longitude, event.latitude]); }); - fitToBounds(bounds); + fitToBounds(bounds, 0); setMapBounds(bounds); - // Add markers with location pulse events.forEach((event) => { const quakeElement = document.createElement("div"); const dotElement = document.createElement("div"); @@ -92,37 +87,31 @@ function MapComponent({ if (event.magnitude) { const color = getMagnitudeColor(event.magnitude); - - // Create marker container - quakeElement.style.width = "50px"; // Increased size to accommodate pulse + quakeElement.style.width = "50px"; quakeElement.style.height = "50px"; quakeElement.style.position = "absolute"; quakeElement.style.display = "flex"; quakeElement.style.alignItems = "center"; quakeElement.style.justifyContent = "center"; - // Central dot dotElement.style.width = "10px"; dotElement.style.height = "10px"; dotElement.style.backgroundColor = color; dotElement.style.borderRadius = "50%"; dotElement.style.position = "absolute"; - dotElement.style.zIndex = "2"; // Ensure dot is above pulse + dotElement.style.zIndex = "2"; - // Pulsing ring pulseElement.className = "location-pulse"; - pulseElement.style.width = "20px"; // Initial size + pulseElement.style.width = "20px"; pulseElement.style.height = "20px"; - pulseElement.style.backgroundColor = `${color}80`; // Color with 50% opacity (hex alpha) + pulseElement.style.backgroundColor = `${color}80`; pulseElement.style.borderRadius = "50%"; pulseElement.style.position = "absolute"; pulseElement.style.zIndex = "1"; } - // Observatory marker - // const observatoryElement = document.createElement("div"); - const root = createRoot(observatoryElement); // `createRoot` is now the standard API + const root = createRoot(observatoryElement); root.render(); quakeElement.appendChild(pulseElement); @@ -143,9 +132,8 @@ function MapComponent({ marker.setPopup(popup); markers.current[event.id] = marker; - // Add hover events const markerDomElement = marker.getElement(); - markerDomElement.style.cursor = "pointer"; // Optional: indicate interactivity + markerDomElement.style.cursor = "pointer"; markerDomElement.addEventListener("mouseenter", () => setHoveredEventId(event.id)); markerDomElement.addEventListener("mouseleave", () => setHoveredEventId("")); @@ -153,11 +141,19 @@ function MapComponent({ setSelectedEventId((prevEventId) => (prevEventId === event.id ? "" : event.id)) ); - // Cleanup event listeners on unmount - markerDomElement.dataset.listenersAdded = "true"; // Mark for cleanup + markerDomElement.dataset.listenersAdded = "true"; }); }); + // Add map click handler to deselect when clicking outside markers + map.current.on("click", (e) => { + const target = e.originalEvent.target as HTMLElement; + // Check if the click target is not a marker element + if (!target.closest(".mapboxgl-marker")) { + setSelectedEventId(""); + } + }); + map.current.on("error", (e) => { console.error("Mapbox error:", e); }); @@ -170,22 +166,17 @@ function MapComponent({ useEffect(() => { const event = events.find((x) => x.id === selectedEventId); if (event) flyToEvent(event); - else if (!selectedEventId) { - if (mapBounds) fitToBounds(mapBounds); + else if (!selectedEventId && mapBounds) { + fitToBounds(mapBounds, 0); } - }, [events, selectedEventId, mapBounds, fitToBounds, clearAllPopups, flyToEvent]); + }, [events, selectedEventId, mapBounds, fitToBounds, flyToEvent]); useEffect(() => { - // Clear all popups first clearAllPopups(); - - // Handle both events if they exist and are different if (hoveredEventId && selectedEventId && hoveredEventId !== selectedEventId) { showPopup(hoveredEventId); showPopup(selectedEventId); - } - // Handle single event case (either hovered or selected) - else if (hoveredEventId || selectedEventId) { + } else if (hoveredEventId || selectedEventId) { showPopup(hoveredEventId || selectedEventId); } }, [hoveredEventId, selectedEventId, clearAllPopups, showPopup]); @@ -197,7 +188,6 @@ function MapComponent({ ); } -// CSS for location-style pulsing animation const pulseStyles = ` .location-pulse { animation: locationPulse 2s infinite; diff --git a/src/components/navbar.tsx b/src/components/Navbar.tsx similarity index 99% rename from src/components/navbar.tsx rename to src/components/Navbar.tsx index db12b3e..9da51ba 100644 --- a/src/components/navbar.tsx +++ b/src/components/Navbar.tsx @@ -23,7 +23,7 @@ export default function Navbar({}: // currencySelector, // { label: "Our Mission", path: "/our-mission" }, // { label: "The Team", path: "/the-team" }, // { label: "Contact Us", path: "/contact-us" }] - // { label: "Learn", path: "/learn" }] + // { label: "Learn", path: "/learn" }] const router = useRouter(); const [isModalOpen, setIsModalOpen] = useState(false); diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index fdfa802..d44b733 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,18 +1,18 @@ import Link from "next/link"; -import React, { Dispatch, SetStateAction } from "react"; +import React, { Dispatch, SetStateAction, useEffect, useRef } from "react"; import { TbHexagon } from "react-icons/tb"; -import Event from "@appTypes/Event"; +import GeologicalEvent from "@appTypes/Event"; import getMagnitudeColor from "@utils/getMagnitudeColour"; interface SidebarProps { logTitle: string; logSubtitle: string; recentsTitle: string; - events: Event[]; - selectedEventId: Event["id"]; + events: GeologicalEvent[]; + selectedEventId: GeologicalEvent["id"]; setSelectedEventId: Dispatch>; - hoveredEventId: Event["id"]; + hoveredEventId: GeologicalEvent["id"]; setHoveredEventId: Dispatch>; button1Name: string; button2Name: string; @@ -48,6 +48,20 @@ export default function Sidebar({ button1Name, button2Name, }: SidebarProps) { + const eventsContainerRef = useRef(null); + + useEffect(() => { + if (selectedEventId && eventsContainerRef.current) { + const selectedEventElement = eventsContainerRef.current.querySelector(`[data-event-id="${selectedEventId}"]`); + if (selectedEventElement) { + selectedEventElement.scrollIntoView({ + block: "center", + behavior: "smooth", + }); + } + } + }, [selectedEventId]); + return (
@@ -66,13 +80,14 @@ export default function Sidebar({
-

{recentsTitle}

+

{recentsTitle}

-
+
{events.map((event) => ( diff --git a/src/types/Event.ts b/src/types/Event.ts index 7f013b4..040697a 100644 --- a/src/types/Event.ts +++ b/src/types/Event.ts @@ -1,5 +1,6 @@ -interface Event { +interface GeologicalEvent { id: string; + date: Date; title: string; magnitude?: number; longitude: number; @@ -8,4 +9,4 @@ interface Event { text2: string; } -export default Event; +export default GeologicalEvent;