Added more todos
This commit is contained in:
parent
06e215a209
commit
b12ae87caf
@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useRef, useState } from "react";
|
||||
|
||||
type Role = "ADMIN" | "GUEST" | "SCIENTIST";
|
||||
const roleLabels: Record<Role, string> = {
|
||||
|
||||
@ -10,149 +10,150 @@ import { getRelativeDate } from "@utils/formatters";
|
||||
import GeologicalEvent from "@appTypes/Event";
|
||||
import axios from "axios";
|
||||
|
||||
// todo (optional) add in filtering of map earthquakes
|
||||
|
||||
// --- SEARCH MODAL COMPONENT ---
|
||||
function EarthquakeSearchModal({ open, onClose, onSelect }) {
|
||||
const [search, setSearch] = useState("");
|
||||
const [results, setResults] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [results, setResults] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSearch = async (e) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setResults([]);
|
||||
try {
|
||||
const res = await axios.post("/api/earthquakes/search", { query: search });
|
||||
setResults(res.data.earthquakes || []);
|
||||
} catch (e) {
|
||||
alert("Failed to search.");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
const handleSearch = async (e) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setResults([]);
|
||||
try {
|
||||
const res = await axios.post("/api/earthquakes/search", { query: search });
|
||||
setResults(res.data.earthquakes || []);
|
||||
} catch (e) {
|
||||
alert("Failed to search.");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (!open) return null;
|
||||
return (
|
||||
<div className="fixed z-50 inset-0 bg-black/40 flex items-center justify-center">
|
||||
<div className="bg-white rounded-lg shadow-lg p-6 max-w-lg w-full relative">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<h2 className="font-bold text-xl mb-4">Search Earthquakes</h2>
|
||||
<form onSubmit={handleSearch} className="flex gap-2 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. Mexico, EV-7.4-Mexico-00035"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
className="flex-grow px-3 py-2 border rounded"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
||||
>
|
||||
{loading ? "Searching..." : "Search"}
|
||||
</button>
|
||||
</form>
|
||||
<div>
|
||||
{results.length === 0 && !loading && search !== "" && (
|
||||
<p className="text-gray-400 text-sm">No results found.</p>
|
||||
)}
|
||||
<ul>
|
||||
{results.map(eq => (
|
||||
<li
|
||||
key={eq.id}
|
||||
className="border-b py-2 cursor-pointer hover:bg-gray-50 flex items-center justify-between"
|
||||
onClick={() => { onSelect(eq); onClose(); }}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div>
|
||||
<strong>{eq.code}</strong> {" "}
|
||||
<span>{eq.location}</span> <span className="text-xs text-gray-500">{new Date(eq.date).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div className={`rounded-full px-2 py-1 ml-2 text-white font-semibold ${eq.magnitude >= 7 ? "bg-red-500" : eq.magnitude >= 6 ? "bg-orange-400" : "bg-yellow-400"}`}>
|
||||
{eq.magnitude}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (!open) return null;
|
||||
return (
|
||||
<div className="fixed z-50 inset-0 bg-black/40 flex items-center justify-center">
|
||||
<div className="bg-white rounded-lg shadow-lg p-6 max-w-lg w-full relative">
|
||||
<button onClick={onClose} className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg">
|
||||
×
|
||||
</button>
|
||||
<h2 className="font-bold text-xl mb-4">Search Earthquakes</h2>
|
||||
<form onSubmit={handleSearch} className="flex gap-2 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. Mexico, EV-7.4-Mexico-00035"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="flex-grow px-3 py-2 border rounded"
|
||||
required
|
||||
/>
|
||||
<button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
|
||||
{loading ? "Searching..." : "Search"}
|
||||
</button>
|
||||
</form>
|
||||
<div>
|
||||
{results.length === 0 && !loading && search !== "" && <p className="text-gray-400 text-sm">No results found.</p>}
|
||||
<ul>
|
||||
{results.map((eq) => (
|
||||
<li
|
||||
key={eq.id}
|
||||
className="border-b py-2 cursor-pointer hover:bg-gray-50 flex items-center justify-between"
|
||||
onClick={() => {
|
||||
onSelect(eq);
|
||||
onClose();
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div>
|
||||
<strong>{eq.code}</strong> <span>{eq.location}</span>{" "}
|
||||
<span className="text-xs text-gray-500">{new Date(eq.date).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`rounded-full px-2 py-1 ml-2 text-white font-semibold ${
|
||||
eq.magnitude >= 7 ? "bg-red-500" : eq.magnitude >= 6 ? "bg-orange-400" : "bg-yellow-400"
|
||||
}`}
|
||||
>
|
||||
{eq.magnitude}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --- MAIN PAGE COMPONENT ---
|
||||
export default function Earthquakes() {
|
||||
const [selectedEventId, setSelectedEventId] = useState("");
|
||||
const [hoveredEventId, setHoveredEventId] = useState("");
|
||||
const [selectedEventId, setSelectedEventId] = useState("");
|
||||
const [hoveredEventId, setHoveredEventId] = useState("");
|
||||
|
||||
// Search modal state
|
||||
const [searchModalOpen, setSearchModalOpen] = useState(false);
|
||||
// Search modal state
|
||||
const [searchModalOpen, setSearchModalOpen] = useState(false);
|
||||
|
||||
// Fetch recent earthquakes as before
|
||||
const { data, error, isLoading } = useSWR("/api/earthquakes", createPoster({ rangeDaysPrev: 5 }));
|
||||
// Fetch recent earthquakes as before
|
||||
const { data, error, isLoading } = useSWR("/api/earthquakes", createPoster({ rangeDaysPrev: 5 }));
|
||||
|
||||
// Prepare events for maps/sidebar
|
||||
const earthquakeEvents = useMemo(
|
||||
() =>
|
||||
data && data.earthquakes
|
||||
? 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())
|
||||
: [],
|
||||
[data]
|
||||
);
|
||||
// Prepare events for maps/sidebar
|
||||
const earthquakeEvents = useMemo(
|
||||
() =>
|
||||
data && data.earthquakes
|
||||
? 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())
|
||||
: [],
|
||||
[data]
|
||||
);
|
||||
|
||||
// Optional: show details of selected search result (not implemented here)
|
||||
// const [selectedSearchResult, setSelectedSearchResult] = useState(null);
|
||||
// Optional: show details of selected search result (not implemented here)
|
||||
// const [selectedSearchResult, setSelectedSearchResult] = useState(null);
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-3.5rem)] w-full overflow-hidden">
|
||||
<div className="flex-grow h-full">
|
||||
<Map
|
||||
events={earthquakeEvents}
|
||||
selectedEventId={selectedEventId}
|
||||
setSelectedEventId={setSelectedEventId}
|
||||
hoveredEventId={hoveredEventId}
|
||||
setHoveredEventId={setHoveredEventId}
|
||||
mapType="Earthquakes"
|
||||
/>
|
||||
</div>
|
||||
<Sidebar
|
||||
logTitle="Log an Earthquake"
|
||||
logSubtitle="Record new earthquakes - time/date, location, magnitude, observatory and scientists"
|
||||
recentsTitle="Recent Earthquakes"
|
||||
events={earthquakeEvents}
|
||||
selectedEventId={selectedEventId}
|
||||
setSelectedEventId={setSelectedEventId}
|
||||
hoveredEventId={hoveredEventId}
|
||||
setHoveredEventId={setHoveredEventId}
|
||||
button1Name="Log an Earthquake"
|
||||
button2Name="Search Earthquakes"
|
||||
onButton2Click={() => setSearchModalOpen(true)} // <-- important!
|
||||
/>
|
||||
<EarthquakeSearchModal
|
||||
open={searchModalOpen}
|
||||
onClose={() => setSearchModalOpen(false)}
|
||||
onSelect={eq => {
|
||||
setSelectedEventId(eq.code); // select on map/sidebar
|
||||
// setSelectedSearchResult(eq); // you can use this if you want to show detail modal
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-3.5rem)] w-full overflow-hidden">
|
||||
<div className="flex-grow h-full">
|
||||
<Map
|
||||
events={earthquakeEvents}
|
||||
selectedEventId={selectedEventId}
|
||||
setSelectedEventId={setSelectedEventId}
|
||||
hoveredEventId={hoveredEventId}
|
||||
setHoveredEventId={setHoveredEventId}
|
||||
mapType="Earthquakes"
|
||||
/>
|
||||
</div>
|
||||
<Sidebar
|
||||
logTitle="Log an Earthquake"
|
||||
logSubtitle="Record new earthquakes - time/date, location, magnitude, observatory and scientists"
|
||||
recentsTitle="Recent Earthquakes"
|
||||
events={earthquakeEvents}
|
||||
selectedEventId={selectedEventId}
|
||||
setSelectedEventId={setSelectedEventId}
|
||||
hoveredEventId={hoveredEventId}
|
||||
setHoveredEventId={setHoveredEventId}
|
||||
button1Name="Log an Earthquake"
|
||||
button2Name="Search Earthquakes"
|
||||
onButton2Click={() => setSearchModalOpen(true)} // <-- important!
|
||||
/>
|
||||
<EarthquakeSearchModal
|
||||
open={searchModalOpen}
|
||||
onClose={() => setSearchModalOpen(false)}
|
||||
onSelect={(eq) => {
|
||||
setSelectedEventId(eq.code); // select on map/sidebar
|
||||
// setSelectedSearchResult(eq); // you can use this if you want to show detail modal
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -11,6 +11,10 @@ import { Observatory } from "@prismaclient";
|
||||
import { getRelativeDate } from "@utils/formatters";
|
||||
import GeologicalEvent from "@appTypes/Event";
|
||||
|
||||
// todo add in showing of observatory stats when searching
|
||||
// todo add in deleting observatory when searching
|
||||
// todo add in changing colour of observatory icons if non-functional
|
||||
|
||||
export default function Observatories() {
|
||||
const [selectedEventId, setSelectedEventId] = useState("");
|
||||
const [hoveredEventId, setHoveredEventId] = useState("");
|
||||
|
||||
@ -7,6 +7,8 @@ import { ExtendedArtefact } from "@appTypes/ApiTypes";
|
||||
import { Currency } from "@appTypes/StoreModel";
|
||||
import { useStoreState } from "@hooks/store";
|
||||
|
||||
// todo hide from shop after purchase
|
||||
|
||||
export default function Shop() {
|
||||
const [artefacts, setArtefacts] = useState<ExtendedArtefact[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@ -16,6 +18,7 @@ export default function Shop() {
|
||||
async function fetchArtefacts() {
|
||||
setLoading(true);
|
||||
try {
|
||||
// todo only show only non-required artefacts
|
||||
const res = await fetch("/api/artefacts");
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user