From fd77ceae889072afa433f02cee73b25bb3dfa8fe Mon Sep 17 00:00:00 2001 From: IZZY Date: Sun, 1 Jun 2025 17:17:25 +0100 Subject: [PATCH] Observatory search page! --- src/app/observatories/page.tsx | 169 +++++++++---------- src/components/SearchObservatoriesModal.tsx | 170 ++++++++++++++++++++ 2 files changed, 258 insertions(+), 81 deletions(-) create mode 100644 src/components/SearchObservatoriesModal.tsx diff --git a/src/app/observatories/page.tsx b/src/app/observatories/page.tsx index 382d797..68114d8 100644 --- a/src/app/observatories/page.tsx +++ b/src/app/observatories/page.tsx @@ -3,7 +3,8 @@ import { useState, useMemo } from "react"; import useSWR from "swr"; import Sidebar from "@/components/Sidebar"; import Map from "@components/Map"; -import LogObservatoryModal from "@/components/LogObservatoryModal"; // Adjust if your path is different +import LogObservatoryModal from "@/components/LogObservatoryModal"; +import SearchObservatoriesModal from "@/components/SearchObservatoriesModal"; // <-- add this import import { fetcher } from "@utils/axiosHelpers"; import { Observatory } from "@prismaclient"; import { getRelativeDate } from "@utils/formatters"; @@ -11,90 +12,96 @@ import GeologicalEvent from "@appTypes/GeologicalEvent"; import { useStoreState } from "@hooks/store"; function NoAccessModal({ open, onClose }) { - if (!open) return null; - return ( -
-
- -

No Access

-

Sorry, You do not have access rights, please log in or contact an Admin.

- -
-
- ); + if (!open) return null; + return ( +
+
+ +

No Access

+

Sorry, You do not have access rights, please log in or contact an Admin.

+ +
+
+ ); } export default function Observatories() { - const [selectedEventId, setSelectedEventId] = useState(""); - const [hoveredEventId, setHoveredEventId] = useState(""); - const [logModalOpen, setLogModalOpen] = useState(false); - const [noAccessModalOpen, setNoAccessModalOpen] = useState(false); + const [selectedEventId, setSelectedEventId] = useState(""); + const [hoveredEventId, setHoveredEventId] = useState(""); + const [logModalOpen, setLogModalOpen] = useState(false); + const [noAccessModalOpen, setNoAccessModalOpen] = useState(false); + const [searchModalOpen, setSearchModalOpen] = useState(false); // <-- NEW STATE - const user = useStoreState((state) => state.user); - const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST"; - const canLogObservatory = role === "SCIENTIST" || role === "ADMIN"; + const user = useStoreState((state) => state.user); + const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST"; + const canLogObservatory = role === "SCIENTIST" || role === "ADMIN"; + const { data, error, isLoading, mutate } = useSWR("/api/observatories", fetcher); - const { data, error, isLoading, mutate } = useSWR("/api/observatories", fetcher); + const observatoryEvents = useMemo( + () => + data && data.observatories + ? data.observatories + .map((x: Observatory): GeologicalEvent & { isFunctional: boolean } => ({ + id: x.id.toString(), + title: ` ${x.name}`, + longitude: x.longitude, + latitude: x.latitude, + isFunctional: !!x.isFunctional, // if isFunctional = 1/0, coerce to boolean + text1: "", + text2: getRelativeDate(x.dateEstablished), + date: x.dateEstablished, + })) + .sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime()) + : [], + [data] + ); - const observatoryEvents = useMemo( - () => - data && data.observatories - ? data.observatories - .map((x: Observatory): GeologicalEvent & { isFunctional: boolean } => ({ - id: x.id.toString(), - title: ` ${x.name}`, - longitude: x.longitude, - latitude: x.latitude, - isFunctional: x.isFunctional, // <-- include this! - text1: "", - text2: getRelativeDate(x.dateEstablished), - date: x.dateEstablished, - })) - .sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime()) - : [], - [data] - ); + const handleLogClick = () => { + if (canLogObservatory) { + setLogModalOpen(true); + } else { + setNoAccessModalOpen(true); + } + }; - const handleLogClick = () => { - if (canLogObservatory) { - setLogModalOpen(true); - } else { - setNoAccessModalOpen(true); - } - }; - - return ( -
-
- -
- - setLogModalOpen(false)} onSuccess={() => mutate()} /> - setNoAccessModalOpen(false)} /> -
- ); -} + return ( +
+
+ +
+ setSearchModalOpen(true)} // <-- This line enables the search modal + /> + setLogModalOpen(false)} onSuccess={() => mutate()} /> + setNoAccessModalOpen(false)} /> + setSearchModalOpen(false)} + observatories={data?.observatories ?? []} + /> +
+ ); +} \ No newline at end of file diff --git a/src/components/SearchObservatoriesModal.tsx b/src/components/SearchObservatoriesModal.tsx new file mode 100644 index 0000000..1f34a53 --- /dev/null +++ b/src/components/SearchObservatoriesModal.tsx @@ -0,0 +1,170 @@ +import React, { useState, useMemo } from "react"; + +interface Observatory { + id: number; + name: string; + location: string; + longitude: number; + latitude: number; + dateEstablished: string; + dateClosed?: string | null; + isFunctional: number; // 0 or 1! + // ...other fields can be ignored +} + +interface SearchObservatoriesModalProps { + open: boolean; + onClose: () => void; + observatories: Observatory[]; +} + +function statusColor(isFunctional: number) { + return isFunctional === 1 ? "bg-green-500" : "bg-red-500"; +} + +function formatDate(date: string | null | undefined) { + if (!date) return "-"; + // Handles both ISO and possibly SQL datetime strings. + const parsed = new Date(date); + if (parsed.getFullYear() < 1900) return "-"; + return parsed.toLocaleDateString(); +} + +const SearchObservatoriesModal: React.FC = ({ + open, + onClose, + observatories, + }) => { + const [tab, setTab] = useState<"name" | "location">("name"); + const [query, setQuery] = useState(""); + const [expandedId, setExpandedId] = useState(null); + + const filtered = useMemo(() => { + if (!query) return observatories; + const q = query.toLowerCase(); + if (tab === "name") { + return observatories.filter((o) => + o.name?.toLowerCase().includes(q) + ); + } else { + return observatories.filter((o) => + o.location?.toLowerCase().includes(q) + ); + } + }, [observatories, query, tab]); + + if (!open) return null; + + return ( +
+
+ +
+
+

Search Observatories

+ {/* Open/Closed key */} +
+
+ + Open +
+
+ + Closed +
+
+
+
+ + +
+
+
+ setQuery(e.target.value)} + autoFocus + /> +
    + {filtered.length === 0 && ( +
  • No observatories found.
  • + )} + {filtered.map((obs) => ( +
  • + + {expandedId === obs.id && ( +
    +
    + Latitude: {obs.latitude} +
    +
    + Longitude: {obs.longitude} +
    +
    + Date Established: {formatDate(obs.dateEstablished)} +
    + {/* Date Closed removed as requested */} +
    + )} +
  • + ))} +
+
+
+ +
+
+
+ ); + }; + +export default SearchObservatoriesModal; \ No newline at end of file