Added more todos

This commit is contained in:
Tim Howitz 2025-05-30 12:58:53 +01:00
parent 06e215a209
commit b12ae87caf
4 changed files with 142 additions and 134 deletions

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from "react";
type Role = "ADMIN" | "GUEST" | "SCIENTIST"; type Role = "ADMIN" | "GUEST" | "SCIENTIST";
const roleLabels: Record<Role, string> = { const roleLabels: Record<Role, string> = {

View File

@ -10,149 +10,150 @@ import { getRelativeDate } from "@utils/formatters";
import GeologicalEvent from "@appTypes/Event"; import GeologicalEvent from "@appTypes/Event";
import axios from "axios"; import axios from "axios";
// todo (optional) add in filtering of map earthquakes
// --- SEARCH MODAL COMPONENT --- // --- SEARCH MODAL COMPONENT ---
function EarthquakeSearchModal({ open, onClose, onSelect }) { function EarthquakeSearchModal({ open, onClose, onSelect }) {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [results, setResults] = useState([]); const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleSearch = async (e) => { const handleSearch = async (e) => {
e.preventDefault(); e.preventDefault();
setLoading(true); setLoading(true);
setResults([]); setResults([]);
try { try {
const res = await axios.post("/api/earthquakes/search", { query: search }); const res = await axios.post("/api/earthquakes/search", { query: search });
setResults(res.data.earthquakes || []); setResults(res.data.earthquakes || []);
} catch (e) { } catch (e) {
alert("Failed to search."); alert("Failed to search.");
} }
setLoading(false); setLoading(false);
}; };
if (!open) return null; if (!open) return null;
return ( return (
<div className="fixed z-50 inset-0 bg-black/40 flex items-center justify-center"> <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"> <div className="bg-white rounded-lg shadow-lg p-6 max-w-lg w-full relative">
<button <button onClick={onClose} className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg">
onClick={onClose} &times;
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>
&times; <form onSubmit={handleSearch} className="flex gap-2 mb-4">
</button> <input
<h2 className="font-bold text-xl mb-4">Search Earthquakes</h2> type="text"
<form onSubmit={handleSearch} className="flex gap-2 mb-4"> placeholder="e.g. Mexico, EV-7.4-Mexico-00035"
<input value={search}
type="text" onChange={(e) => setSearch(e.target.value)}
placeholder="e.g. Mexico, EV-7.4-Mexico-00035" className="flex-grow px-3 py-2 border rounded"
value={search} required
onChange={e => setSearch(e.target.value)} />
className="flex-grow px-3 py-2 border rounded" <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
required {loading ? "Searching..." : "Search"}
/> </button>
<button </form>
type="submit" <div>
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700" {results.length === 0 && !loading && search !== "" && <p className="text-gray-400 text-sm">No results found.</p>}
> <ul>
{loading ? "Searching..." : "Search"} {results.map((eq) => (
</button> <li
</form> key={eq.id}
<div> className="border-b py-2 cursor-pointer hover:bg-gray-50 flex items-center justify-between"
{results.length === 0 && !loading && search !== "" && ( onClick={() => {
<p className="text-gray-400 text-sm">No results found.</p> onSelect(eq);
)} onClose();
<ul> }}
{results.map(eq => ( tabIndex={0}
<li >
key={eq.id} <div>
className="border-b py-2 cursor-pointer hover:bg-gray-50 flex items-center justify-between" <strong>{eq.code}</strong> <span>{eq.location}</span>{" "}
onClick={() => { onSelect(eq); onClose(); }} <span className="text-xs text-gray-500">{new Date(eq.date).toLocaleDateString()}</span>
tabIndex={0} </div>
> <div
<div> className={`rounded-full px-2 py-1 ml-2 text-white font-semibold ${
<strong>{eq.code}</strong> {" "} eq.magnitude >= 7 ? "bg-red-500" : eq.magnitude >= 6 ? "bg-orange-400" : "bg-yellow-400"
<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}
{eq.magnitude} </div>
</div> </li>
</li> ))}
))} </ul>
</ul> </div>
</div> </div>
</div> </div>
</div> );
);
} }
// --- MAIN PAGE COMPONENT --- // --- MAIN PAGE COMPONENT ---
export default function Earthquakes() { export default function Earthquakes() {
const [selectedEventId, setSelectedEventId] = useState(""); const [selectedEventId, setSelectedEventId] = useState("");
const [hoveredEventId, setHoveredEventId] = useState(""); const [hoveredEventId, setHoveredEventId] = useState("");
// Search modal state // Search modal state
const [searchModalOpen, setSearchModalOpen] = useState(false); const [searchModalOpen, setSearchModalOpen] = useState(false);
// Fetch recent earthquakes as before // Fetch recent earthquakes as before
const { data, error, isLoading } = useSWR("/api/earthquakes", createPoster({ rangeDaysPrev: 5 })); const { data, error, isLoading } = useSWR("/api/earthquakes", createPoster({ rangeDaysPrev: 5 }));
// Prepare events for maps/sidebar // Prepare events for maps/sidebar
const earthquakeEvents = useMemo( const earthquakeEvents = useMemo(
() => () =>
data && data.earthquakes data && data.earthquakes
? data.earthquakes ? data.earthquakes
.map( .map(
(x: Earthquake): GeologicalEvent => ({ (x: Earthquake): GeologicalEvent => ({
id: x.code, id: x.code,
title: `Earthquake in ${x.code.split("-")[2]}`, title: `Earthquake in ${x.code.split("-")[2]}`,
magnitude: x.magnitude, magnitude: x.magnitude,
longitude: x.longitude, longitude: x.longitude,
latitude: x.latitude, latitude: x.latitude,
text1: "", text1: "",
text2: getRelativeDate(x.date), text2: getRelativeDate(x.date),
date: x.date, date: x.date,
}) })
) )
.sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime()) .sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime())
: [], : [],
[data] [data]
); );
// Optional: show details of selected search result (not implemented here) // Optional: show details of selected search result (not implemented here)
// const [selectedSearchResult, setSelectedSearchResult] = useState(null); // const [selectedSearchResult, setSelectedSearchResult] = useState(null);
return ( return (
<div className="flex h-[calc(100vh-3.5rem)] w-full overflow-hidden"> <div className="flex h-[calc(100vh-3.5rem)] w-full overflow-hidden">
<div className="flex-grow h-full"> <div className="flex-grow h-full">
<Map <Map
events={earthquakeEvents} events={earthquakeEvents}
selectedEventId={selectedEventId} selectedEventId={selectedEventId}
setSelectedEventId={setSelectedEventId} setSelectedEventId={setSelectedEventId}
hoveredEventId={hoveredEventId} hoveredEventId={hoveredEventId}
setHoveredEventId={setHoveredEventId} setHoveredEventId={setHoveredEventId}
mapType="Earthquakes" mapType="Earthquakes"
/> />
</div> </div>
<Sidebar <Sidebar
logTitle="Log an Earthquake" logTitle="Log an Earthquake"
logSubtitle="Record new earthquakes - time/date, location, magnitude, observatory and scientists" logSubtitle="Record new earthquakes - time/date, location, magnitude, observatory and scientists"
recentsTitle="Recent Earthquakes" recentsTitle="Recent Earthquakes"
events={earthquakeEvents} events={earthquakeEvents}
selectedEventId={selectedEventId} selectedEventId={selectedEventId}
setSelectedEventId={setSelectedEventId} setSelectedEventId={setSelectedEventId}
hoveredEventId={hoveredEventId} hoveredEventId={hoveredEventId}
setHoveredEventId={setHoveredEventId} setHoveredEventId={setHoveredEventId}
button1Name="Log an Earthquake" button1Name="Log an Earthquake"
button2Name="Search Earthquakes" button2Name="Search Earthquakes"
onButton2Click={() => setSearchModalOpen(true)} // <-- important! onButton2Click={() => setSearchModalOpen(true)} // <-- important!
/> />
<EarthquakeSearchModal <EarthquakeSearchModal
open={searchModalOpen} open={searchModalOpen}
onClose={() => setSearchModalOpen(false)} onClose={() => setSearchModalOpen(false)}
onSelect={eq => { onSelect={(eq) => {
setSelectedEventId(eq.code); // select on map/sidebar setSelectedEventId(eq.code); // select on map/sidebar
// setSelectedSearchResult(eq); // you can use this if you want to show detail modal // setSelectedSearchResult(eq); // you can use this if you want to show detail modal
}} }}
/> />
</div> </div>
); );
} }

View File

@ -11,6 +11,10 @@ import { Observatory } from "@prismaclient";
import { getRelativeDate } from "@utils/formatters"; import { getRelativeDate } from "@utils/formatters";
import GeologicalEvent from "@appTypes/Event"; 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() { export default function Observatories() {
const [selectedEventId, setSelectedEventId] = useState(""); const [selectedEventId, setSelectedEventId] = useState("");
const [hoveredEventId, setHoveredEventId] = useState(""); const [hoveredEventId, setHoveredEventId] = useState("");

View File

@ -7,6 +7,8 @@ import { ExtendedArtefact } from "@appTypes/ApiTypes";
import { Currency } from "@appTypes/StoreModel"; import { Currency } from "@appTypes/StoreModel";
import { useStoreState } from "@hooks/store"; import { useStoreState } from "@hooks/store";
// todo hide from shop after purchase
export default function Shop() { export default function Shop() {
const [artefacts, setArtefacts] = useState<ExtendedArtefact[]>([]); const [artefacts, setArtefacts] = useState<ExtendedArtefact[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -16,6 +18,7 @@ export default function Shop() {
async function fetchArtefacts() { async function fetchArtefacts() {
setLoading(true); setLoading(true);
try { try {
// todo only show only non-required artefacts
const res = await fetch("/api/artefacts"); const res = await fetch("/api/artefacts");
const data = await res.json(); const data = await res.json();