Added close earthquake log and search modals on click outside
This commit is contained in:
parent
128517b388
commit
dd650c6ba6
@ -229,6 +229,7 @@ export default function Profile() {
|
|||||||
}
|
}
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
|
// todo add delete user route
|
||||||
const res = await axios.post(
|
const res = await axios.post(
|
||||||
"/api/delete-user",
|
"/api/delete-user",
|
||||||
{ userId: user!.id },
|
{ userId: user!.id },
|
||||||
|
|||||||
@ -95,6 +95,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
|
|||||||
}
|
}
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
|
// todo!! add log api route
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
|
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
|
||||||
alert(`Logged ${name} to storage: ${storageLocation}`);
|
alert(`Logged ${name} to storage: ${storageLocation}`);
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { FormEvent, useState, useRef } from "react";
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
|
|
||||||
@ -7,10 +7,18 @@ const typeOptions = [
|
|||||||
{ value: "volcanic", label: "Volcanic" },
|
{ value: "volcanic", label: "Volcanic" },
|
||||||
{ value: "tectonic", label: "Tectonic" },
|
{ value: "tectonic", label: "Tectonic" },
|
||||||
{ value: "collapse", label: "Collapse" },
|
{ value: "collapse", label: "Collapse" },
|
||||||
{ value: "explosion", label: "Explosion" }
|
{ value: "explosion", label: "Explosion" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
export default function EarthquakeLogModal({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSuccess,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
}) {
|
||||||
const [date, setDate] = useState<Date | null>(new Date());
|
const [date, setDate] = useState<Date | null>(new Date());
|
||||||
const [magnitude, setMagnitude] = useState("");
|
const [magnitude, setMagnitude] = useState("");
|
||||||
const [type, setType] = useState(typeOptions[0].value);
|
const [type, setType] = useState(typeOptions[0].value);
|
||||||
@ -21,15 +29,14 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
const [depth, setDepth] = useState("");
|
const [depth, setDepth] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [successCode, setSuccessCode] = useState<string | null>(null);
|
const [successCode, setSuccessCode] = useState<string | null>(null);
|
||||||
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
async function handleLatLonChange(lat: string, lon: string) {
|
async function handleLatLonChange(lat: string, lon: string) {
|
||||||
setLatitude(lat);
|
setLatitude(lat);
|
||||||
setLongitude(lon);
|
setLongitude(lon);
|
||||||
if (/^-?\d+(\.\d+)?$/.test(lat) && /^-?\d+(\.\d+)?$/.test(lon)) {
|
if (/^-?\d+(\.\d+)?$/.test(lat) && /^-?\d+(\.\d+)?$/.test(lon)) {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(
|
const resp = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json&zoom=10`);
|
||||||
`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json&zoom=10`
|
|
||||||
);
|
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
setCity(
|
setCity(
|
||||||
@ -49,7 +56,7 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit(e) {
|
async function handleSubmit(e: FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (!date || !magnitude || !type || !city || !country || !latitude || !longitude || !depth) {
|
if (!date || !magnitude || !type || !city || !country || !latitude || !longitude || !depth) {
|
||||||
@ -69,8 +76,8 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
country: country.trim(),
|
country: country.trim(),
|
||||||
latitude: parseFloat(latitude),
|
latitude: parseFloat(latitude),
|
||||||
longitude: parseFloat(longitude),
|
longitude: parseFloat(longitude),
|
||||||
depth
|
depth,
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
@ -88,13 +95,19 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleOutsideClick(e: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
// Success popup overlay
|
// Success popup overlay
|
||||||
if (successCode) {
|
if (successCode) {
|
||||||
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" onClick={handleOutsideClick}>
|
||||||
<div className="bg-white rounded-lg px-8 py-8 max-w-md w-full relative shadow-lg">
|
<div className="bg-white rounded-lg px-8 py-8 max-w-md w-full relative shadow-lg" ref={modalRef}>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSuccessCode(null);
|
setSuccessCode(null);
|
||||||
@ -103,12 +116,10 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
className="absolute right-4 top-4 text-xl text-gray-400 hover:text-gray-700"
|
className="absolute right-4 top-4 text-xl text-gray-400 hover:text-gray-700"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="text-xl font-semibold mb-3">
|
<h2 className="text-xl font-semibold mb-3">Thank you for logging an earthquake!</h2>
|
||||||
Thank you for logging an earthquake!
|
|
||||||
</h2>
|
|
||||||
<div className="mb-0">The Earthquake Identifier is</div>
|
<div className="mb-0">The Earthquake Identifier is</div>
|
||||||
<div className="font-mono text-lg mb-4 bg-slate-100 rounded px-2 py-1 inline-block text-blue-800">{successCode}</div>
|
<div className="font-mono text-lg mb-4 bg-slate-100 rounded px-2 py-1 inline-block text-blue-800">{successCode}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,13 +129,10 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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" onClick={handleOutsideClick}>
|
||||||
<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" ref={modalRef}>
|
||||||
<button
|
<button onClick={onClose} className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg">
|
||||||
onClick={onClose}
|
×
|
||||||
className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
</button>
|
||||||
<h2 className="font-bold text-xl mb-4">Log Earthquake</h2>
|
<h2 className="font-bold text-xl mb-4">Log Earthquake</h2>
|
||||||
<form onSubmit={handleSubmit} className="space-y-3">
|
<form onSubmit={handleSubmit} className="space-y-3">
|
||||||
@ -132,7 +140,7 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
<label className="block text-sm font-medium">Date</label>
|
<label className="block text-sm font-medium">Date</label>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={date}
|
selected={date}
|
||||||
onChange={date => setDate(date)}
|
onChange={(date) => setDate(date)}
|
||||||
className="border rounded px-3 py-2 w-full"
|
className="border rounded px-3 py-2 w-full"
|
||||||
dateFormat="yyyy-MM-dd"
|
dateFormat="yyyy-MM-dd"
|
||||||
maxDate={new Date()}
|
maxDate={new Date()}
|
||||||
@ -151,7 +159,7 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
max="10"
|
max="10"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
value={magnitude}
|
value={magnitude}
|
||||||
onChange={e => {
|
onChange={(e) => {
|
||||||
const val = e.target.value;
|
const val = e.target.value;
|
||||||
if (parseFloat(val) > 10) return;
|
if (parseFloat(val) > 10) return;
|
||||||
setMagnitude(val);
|
setMagnitude(val);
|
||||||
@ -161,13 +169,8 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium">Type</label>
|
<label className="block text-sm font-medium">Type</label>
|
||||||
<select
|
<select className="border rounded px-3 py-2 w-full" value={type} onChange={(e) => setType(e.target.value)} required>
|
||||||
className="border rounded px-3 py-2 w-full"
|
{typeOptions.map((opt) => (
|
||||||
value={type}
|
|
||||||
onChange={e => setType(e.target.value)}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
{typeOptions.map(opt => (
|
|
||||||
<option key={opt.value} value={opt.value}>
|
<option key={opt.value} value={opt.value}>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
</option>
|
</option>
|
||||||
@ -176,14 +179,12 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium">City/Area</label>
|
<label className="block text-sm font-medium">City/Area</label>
|
||||||
<span className="block text-xs text-gray-400">
|
<span className="block text-xs text-gray-400">(Use Lat/Lon then press Enter for reverse lookup)</span>
|
||||||
(Use Lat/Lon then press Enter for reverse lookup)
|
|
||||||
</span>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border rounded px-3 py-2 w-full"
|
className="border rounded px-3 py-2 w-full"
|
||||||
value={city}
|
value={city}
|
||||||
onChange={e => setCity(e.target.value)}
|
onChange={(e) => setCity(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -193,7 +194,7 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
type="text"
|
type="text"
|
||||||
className="border rounded px-3 py-2 w-full"
|
className="border rounded px-3 py-2 w-full"
|
||||||
value={country}
|
value={country}
|
||||||
onChange={e => setCountry(e.target.value)}
|
onChange={(e) => setCountry(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -204,7 +205,7 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
type="number"
|
type="number"
|
||||||
className="border rounded px-3 py-2 w-full"
|
className="border rounded px-3 py-2 w-full"
|
||||||
value={latitude}
|
value={latitude}
|
||||||
onChange={e => handleLatLonChange(e.target.value, longitude)}
|
onChange={(e) => handleLatLonChange(e.target.value, longitude)}
|
||||||
placeholder="e.g. 36.12"
|
placeholder="e.g. 36.12"
|
||||||
step="any"
|
step="any"
|
||||||
required
|
required
|
||||||
@ -216,7 +217,7 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
type="number"
|
type="number"
|
||||||
className="border rounded px-3 py-2 w-full"
|
className="border rounded px-3 py-2 w-full"
|
||||||
value={longitude}
|
value={longitude}
|
||||||
onChange={e => handleLatLonChange(latitude, e.target.value)}
|
onChange={(e) => handleLatLonChange(latitude, e.target.value)}
|
||||||
placeholder="e.g. -115.17"
|
placeholder="e.g. -115.17"
|
||||||
step="any"
|
step="any"
|
||||||
required
|
required
|
||||||
@ -229,16 +230,12 @@ export default function EarthquakeLogModal({ open, onClose, onSuccess }) {
|
|||||||
type="text"
|
type="text"
|
||||||
className="border rounded px-3 py-2 w-full"
|
className="border rounded px-3 py-2 w-full"
|
||||||
value={depth}
|
value={depth}
|
||||||
onChange={e => setDepth(e.target.value)}
|
onChange={(e) => setDepth(e.target.value)}
|
||||||
placeholder="e.g. 10 km"
|
placeholder="e.g. 10 km"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 w-full" disabled={loading}>
|
||||||
type="submit"
|
|
||||||
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 w-full"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? "Logging..." : "Log Earthquake"}
|
{loading ? "Logging..." : "Log Earthquake"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -23,6 +23,8 @@ const COLUMNS = [
|
|||||||
{ label: "Date", key: "date" },
|
{ label: "Date", key: "date" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// todo modify slightly
|
||||||
|
|
||||||
export default function EarthquakeSearchModal({
|
export default function EarthquakeSearchModal({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
@ -72,15 +74,12 @@ export default function EarthquakeSearchModal({
|
|||||||
|
|
||||||
// Filter logic
|
// Filter logic
|
||||||
const filteredRows = useMemo(() => {
|
const filteredRows = useMemo(() => {
|
||||||
return results.filter((row) =>
|
return results.filter(
|
||||||
(!filters.code ||
|
(row) =>
|
||||||
row.code.toLowerCase().includes(filters.code.toLowerCase())) &&
|
(!filters.code || row.code.toLowerCase().includes(filters.code.toLowerCase())) &&
|
||||||
(!filters.location ||
|
(!filters.location || (row.location || "").toLowerCase().includes(filters.location.toLowerCase())) &&
|
||||||
(row.location || "").toLowerCase().includes(filters.location.toLowerCase())) &&
|
(!filters.magnitude || String(row.magnitude).startsWith(filters.magnitude)) &&
|
||||||
(!filters.magnitude ||
|
(!filters.date || row.date.slice(0, 10) === filters.date)
|
||||||
String(row.magnitude).startsWith(filters.magnitude)) &&
|
|
||||||
(!filters.date ||
|
|
||||||
row.date.slice(0, 10) === filters.date)
|
|
||||||
);
|
);
|
||||||
}, [results, filters]);
|
}, [results, filters]);
|
||||||
|
|
||||||
@ -111,10 +110,9 @@ export default function EarthquakeSearchModal({
|
|||||||
return (
|
return (
|
||||||
<div className="fixed z-50 inset-0 bg-black/40 flex items-center justify-center animate-fadein">
|
<div className="fixed z-50 inset-0 bg-black/40 flex items-center justify-center animate-fadein">
|
||||||
<div className="bg-white rounded-lg shadow-xl p-6 w-full max-w-2xl relative">
|
<div className="bg-white rounded-lg shadow-xl p-6 w-full max-w-2xl relative">
|
||||||
<button
|
<button onClick={onClose} className="absolute right-4 top-4 text-2xl text-gray-400 hover:text-red-500 font-bold">
|
||||||
onClick={onClose}
|
×
|
||||||
className="absolute right-4 top-4 text-2xl text-gray-400 hover:text-red-500 font-bold"
|
</button>
|
||||||
>×</button>
|
|
||||||
<h2 className="font-bold text-xl mb-4">Search Earthquakes</h2>
|
<h2 className="font-bold text-xl mb-4">Search Earthquakes</h2>
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
@ -160,9 +158,7 @@ export default function EarthquakeSearchModal({
|
|||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{error && (
|
{error && <div className="text-red-600 font-medium mb-2">{error}</div>}
|
||||||
<div className="text-red-600 font-medium mb-2">{error}</div>
|
|
||||||
)}
|
|
||||||
{/* Filter Row */}
|
{/* Filter Row */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
@ -171,17 +167,10 @@ export default function EarthquakeSearchModal({
|
|||||||
key={col.key}
|
key={col.key}
|
||||||
type={col.key === "magnitude" ? "number" : col.key === "date" ? "date" : "text"}
|
type={col.key === "magnitude" ? "number" : col.key === "date" ? "date" : "text"}
|
||||||
value={filters[col.key] || ""}
|
value={filters[col.key] || ""}
|
||||||
onChange={e =>
|
onChange={(e) => setFilters((f) => ({ ...f, [col.key]: e.target.value }))}
|
||||||
setFilters(f => ({ ...f, [col.key]: e.target.value }))
|
|
||||||
}
|
|
||||||
className="border border-neutral-200 rounded px-2 py-1 text-xs"
|
className="border border-neutral-200 rounded px-2 py-1 text-xs"
|
||||||
style={{
|
style={{
|
||||||
width:
|
width: col.key === "magnitude" ? 70 : col.key === "date" ? 130 : 120,
|
||||||
col.key === "magnitude"
|
|
||||||
? 70
|
|
||||||
: col.key === "date"
|
|
||||||
? 130
|
|
||||||
: 120,
|
|
||||||
}}
|
}}
|
||||||
placeholder={`Filter ${col.label}`}
|
placeholder={`Filter ${col.label}`}
|
||||||
aria-label={`Filter ${col.label}`}
|
aria-label={`Filter ${col.label}`}
|
||||||
@ -200,14 +189,15 @@ export default function EarthquakeSearchModal({
|
|||||||
key={col.key}
|
key={col.key}
|
||||||
className={`font-semibold px-3 py-2 cursor-pointer select-none text-left`}
|
className={`font-semibold px-3 py-2 cursor-pointer select-none text-left`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSort(sort && sort.key === col.key
|
setSort(
|
||||||
|
sort && sort.key === col.key
|
||||||
? { key: col.key as keyof Earthquake, dir: sort.dir === "asc" ? "desc" : "asc" }
|
? { key: col.key as keyof Earthquake, dir: sort.dir === "asc" ? "desc" : "asc" }
|
||||||
: { key: col.key as keyof Earthquake, dir: "asc" })
|
: { key: col.key as keyof Earthquake, dir: "asc" }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{col.label}
|
{col.label}
|
||||||
{sort?.key === col.key &&
|
{sort?.key === col.key && (sort.dir === "asc" ? " ↑" : " ↓")}
|
||||||
(sort.dir === "asc" ? " ↑" : " ↓")}
|
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
@ -220,7 +210,7 @@ export default function EarthquakeSearchModal({
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{sortedRows.map(eq => (
|
{sortedRows.map((eq) => (
|
||||||
<tr
|
<tr
|
||||||
key={eq.id}
|
key={eq.id}
|
||||||
className="hover:bg-blue-50 cursor-pointer border-b"
|
className="hover:bg-blue-50 cursor-pointer border-b"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user