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