Compare commits
2 Commits
6d25e72292
...
6cd95fa0e4
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cd95fa0e4 | |||
| f88f783de9 |
@ -6,134 +6,117 @@ import Sidebar from "@components/Sidebar";
|
|||||||
import { createPoster } from "@utils/axiosHelpers";
|
import { createPoster } from "@utils/axiosHelpers";
|
||||||
import { Earthquake } from "@prismaclient";
|
import { Earthquake } from "@prismaclient";
|
||||||
import { getRelativeDate } from "@utils/formatters";
|
import { getRelativeDate } from "@utils/formatters";
|
||||||
import GeologicalEvent from "@appTypes/Event";
|
import GeologicalEvent from "@appTypes/GeologicalEvent";
|
||||||
import EarthquakeSearchModal from "@components/EarthquakeSearchModal";
|
import EarthquakeSearchModal from "@components/EarthquakeSearchModal";
|
||||||
import EarthquakeLogModal from "@components/EarthquakeLogModal"; // If you use a separate log modal
|
import EarthquakeLogModal from "@components/EarthquakeLogModal"; // If you use a separate log modal
|
||||||
import { useStoreState } from "@hooks/store";
|
import { useStoreState } from "@hooks/store";
|
||||||
|
|
||||||
// Optional: "No Access Modal" - as in your original
|
// Optional: "No Access Modal" - as in your original
|
||||||
function NoAccessModal({ open, onClose }) {
|
function NoAccessModal({ open, onClose }) {
|
||||||
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-8 max-w-xs w-full text-center relative">
|
<div className="bg-white rounded-lg shadow-lg p-8 max-w-xs w-full text-center relative">
|
||||||
<button
|
<button onClick={onClose} className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg" aria-label="Close">
|
||||||
onClick={onClose}
|
×
|
||||||
className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg"
|
</button>
|
||||||
aria-label="Close"
|
<h2 className="font-bold text-xl mb-4">Access Denied</h2>
|
||||||
>
|
<p className="text-gray-600 mb-3">
|
||||||
×
|
Sorry, you do not have access rights to Log an Earthquake. Please Log in here, or contact an Admin if you believe this
|
||||||
</button>
|
is a mistake
|
||||||
<h2 className="font-bold text-xl mb-4">Access Denied</h2>
|
</p>
|
||||||
<p className="text-gray-600 mb-3">
|
<button onClick={onClose} className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 mt-2">
|
||||||
Sorry, you do not have access rights to Log an Earthquake. Please Log in here, or contact an Admin if you believe this is a mistake
|
OK
|
||||||
</p>
|
</button>
|
||||||
<button
|
</div>
|
||||||
onClick={onClose}
|
</div>
|
||||||
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 mt-2"
|
);
|
||||||
>OK</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function Earthquakes() {
|
export default function Earthquakes() {
|
||||||
const [selectedEventId, setSelectedEventId] = useState("");
|
const [selectedEventId, setSelectedEventId] = useState("");
|
||||||
const [hoveredEventId, setHoveredEventId] = useState("");
|
const [hoveredEventId, setHoveredEventId] = useState("");
|
||||||
const [searchModalOpen, setSearchModalOpen] = useState(false);
|
const [searchModalOpen, setSearchModalOpen] = useState(false);
|
||||||
const [logModalOpen, setLogModalOpen] = useState(false);
|
const [logModalOpen, setLogModalOpen] = useState(false);
|
||||||
const [noAccessModalOpen, setNoAccessModalOpen] = useState(false);
|
const [noAccessModalOpen, setNoAccessModalOpen] = useState(false);
|
||||||
|
|
||||||
// Your user/role logic
|
// Your user/role logic
|
||||||
const user = useStoreState((state) => state.user);
|
const user = useStoreState((state) => state.user);
|
||||||
const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST";
|
const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST";
|
||||||
const canLogEarthquake = role === "SCIENTIST" || role === "ADMIN";
|
const canLogEarthquake = role === "SCIENTIST" || role === "ADMIN";
|
||||||
|
|
||||||
// Fetch earthquakes (10 days recent)
|
// Fetch earthquakes (10 days recent)
|
||||||
const { data, error, isLoading, mutate } = useSWR(
|
const { data, error, isLoading, mutate } = useSWR("/api/earthquakes", createPoster({ rangeDaysPrev: 10 }));
|
||||||
"/api/earthquakes",
|
|
||||||
createPoster({ rangeDaysPrev: 10 })
|
|
||||||
);
|
|
||||||
|
|
||||||
// Shape for Map/Sidebar
|
// Shape for Map/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.location || (x.code && x.code.split("-")[2])}`,
|
title: `Earthquake in ${x.location || (x.code && 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,
|
||||||
})
|
code: x.code,
|
||||||
)
|
})
|
||||||
.sort(
|
)
|
||||||
(a: GeologicalEvent, b: GeologicalEvent) =>
|
.sort((a: GeologicalEvent, b: GeologicalEvent) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||||
new Date(b.date).getTime() - new Date(a.date).getTime()
|
: [],
|
||||||
)
|
[data]
|
||||||
: [],
|
);
|
||||||
[data]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handler for log
|
// Handler for log
|
||||||
const handleLogClick = () => {
|
const handleLogClick = () => {
|
||||||
if (canLogEarthquake) {
|
if (canLogEarthquake) {
|
||||||
setLogModalOpen(true);
|
setLogModalOpen(true);
|
||||||
} else {
|
} else {
|
||||||
setNoAccessModalOpen(true);
|
setNoAccessModalOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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"
|
||||||
onButton1Click={handleLogClick}
|
onButton1Click={handleLogClick}
|
||||||
onButton2Click={() => setSearchModalOpen(true)}
|
onButton2Click={() => setSearchModalOpen(true)}
|
||||||
button1Disabled={!canLogEarthquake}
|
button1Disabled={!canLogEarthquake}
|
||||||
/>
|
/>
|
||||||
{/* ---- SEARCH MODAL ---- */}
|
{/* ---- SEARCH MODAL ---- */}
|
||||||
<EarthquakeSearchModal
|
<EarthquakeSearchModal
|
||||||
open={searchModalOpen}
|
open={searchModalOpen}
|
||||||
onClose={() => setSearchModalOpen(false)}
|
onClose={() => setSearchModalOpen(false)}
|
||||||
onSelect={(eq) => setSelectedEventId(eq.code)}
|
onSelect={(eq) => setSelectedEventId(eq.code)}
|
||||||
/>
|
/>
|
||||||
{/* ---- LOGGING MODAL ---- */}
|
{/* ---- LOGGING MODAL ---- */}
|
||||||
<EarthquakeLogModal
|
<EarthquakeLogModal open={logModalOpen} onClose={() => setLogModalOpen(false)} onSuccess={() => mutate()} />
|
||||||
open={logModalOpen}
|
{/* ---- NO ACCESS ---- */}
|
||||||
onClose={() => setLogModalOpen(false)}
|
<NoAccessModal open={noAccessModalOpen} onClose={() => setNoAccessModalOpen(false)} />
|
||||||
onSuccess={() => mutate()}
|
</div>
|
||||||
/>
|
);
|
||||||
{/* ---- NO ACCESS ---- */}
|
}
|
||||||
<NoAccessModal
|
|
||||||
open={noAccessModalOpen}
|
|
||||||
onClose={() => setNoAccessModalOpen(false)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--background: #ffffff;
|
||||||
--foreground: #171717;
|
--foreground: #171717;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @media (prefers-color-scheme: dark) {
|
/* @media (prefers-color-scheme: dark) {
|
||||||
@ -15,216 +15,216 @@
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Increase specificity and use !important where necessary */
|
/* Increase specificity and use !important where necessary */
|
||||||
.mapboxgl-popup .mapboxgl-popup-content {
|
.mapboxgl-popup .mapboxgl-popup-content {
|
||||||
@apply rounded-xl p-4 px-5 drop-shadow-lg border border-neutral-300 max-w-xs !important;
|
@apply rounded-xl p-4 px-5 drop-shadow-lg border border-neutral-300 max-w-xs !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the popup tip */
|
/* Hide the popup tip */
|
||||||
.mapboxgl-popup .mapboxgl-popup-tip {
|
.mapboxgl-popup .mapboxgl-popup-tip {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Child elements */
|
/* Child elements */
|
||||||
.mapboxgl-popup-content h3 {
|
.mapboxgl-popup-content h3 {
|
||||||
@apply text-sm font-medium text-neutral-800 !important;
|
@apply text-sm font-medium text-neutral-800 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapboxgl-popup-content p {
|
.mapboxgl-popup-content p {
|
||||||
@apply text-xs text-neutral-600 !important;
|
@apply text-xs text-neutral-600 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapboxgl-popup-content p+p {
|
.mapboxgl-popup-content p + p {
|
||||||
@apply text-neutral-500 !important;
|
@apply text-neutral-500 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-link {
|
.icon-link {
|
||||||
/* default styles if needed */
|
/* default styles if needed */
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-link:hover,
|
.icon-link:hover,
|
||||||
.icon-link:focus {
|
.icon-link:focus {
|
||||||
background-color: #16424b;
|
background-color: #16424b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-link:hover h3,
|
.icon-link:hover h3,
|
||||||
.icon-link:focus h3,
|
.icon-link:focus h3,
|
||||||
.icon-link:hover p,
|
.icon-link:hover p,
|
||||||
.icon-link:focus p {
|
.icon-link:focus p {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-link:hover h3,
|
.icon-link:hover h3,
|
||||||
.icon-link:hover p,
|
.icon-link:hover p,
|
||||||
.icon-link:focus h3,
|
.icon-link:focus h3,
|
||||||
.icon-link:focus p {
|
.icon-link:focus p {
|
||||||
color: #111;
|
color: #111;
|
||||||
/* or black */
|
/* or black */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- LAVA FLOOD OVERLAY ---- */
|
/* ---- LAVA FLOOD OVERLAY ---- */
|
||||||
.lava-flood-overlay {
|
.lava-flood-overlay {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: -100vh;
|
top: -100vh;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
transition: top 0.9s cubic-bezier(.6, 0, .2, 1);
|
transition: top 0.9s cubic-bezier(0.6, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lava-flood-overlay.lava-active {
|
.lava-flood-overlay.lava-active {
|
||||||
top: 0;
|
top: 0;
|
||||||
transition: top 0.33s cubic-bezier(.6, 0, .2, 1);
|
transition: top 0.33s cubic-bezier(0.6, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lava-flood-overlay img,
|
.lava-flood-overlay img,
|
||||||
.lava-gradient {
|
.lava-gradient {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
filter: brightness(1.15) saturate(1.8) drop-shadow(0 0 80px #ff5500);
|
filter: brightness(1.15) saturate(1.8) drop-shadow(0 0 80px #ff5500);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- INSANE SCREEN SHAKE (LinkedIn) ---- */
|
/* ---- INSANE SCREEN SHAKE (LinkedIn) ---- */
|
||||||
@keyframes supershake {
|
@keyframes supershake {
|
||||||
0% {
|
0% {
|
||||||
transform: translate(0, 0) rotate(0);
|
transform: translate(0, 0) rotate(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
5% {
|
5% {
|
||||||
transform: translate(-20px, 5px) rotate(-2deg);
|
transform: translate(-20px, 5px) rotate(-2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
10% {
|
10% {
|
||||||
transform: translate(18px, -8px) rotate(2deg);
|
transform: translate(18px, -8px) rotate(2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
15% {
|
15% {
|
||||||
transform: translate(-22px, 8px) rotate(-4deg);
|
transform: translate(-22px, 8px) rotate(-4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
20% {
|
20% {
|
||||||
transform: translate(22px, -2px) rotate(4deg);
|
transform: translate(22px, -2px) rotate(4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
25% {
|
25% {
|
||||||
transform: translate(-18px, 12px) rotate(-2deg);
|
transform: translate(-18px, 12px) rotate(-2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
30% {
|
30% {
|
||||||
transform: translate(18px, -10px) rotate(2deg);
|
transform: translate(18px, -10px) rotate(2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
35% {
|
35% {
|
||||||
transform: translate(-22px, 14px) rotate(-4deg);
|
transform: translate(-22px, 14px) rotate(-4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
40% {
|
40% {
|
||||||
transform: translate(22px, -12px) rotate(4deg);
|
transform: translate(22px, -12px) rotate(4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
45% {
|
45% {
|
||||||
transform: translate(-18px, 8px) rotate(-2deg);
|
transform: translate(-18px, 8px) rotate(-2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
transform: translate(18px, -14px) rotate(4deg);
|
transform: translate(18px, -14px) rotate(4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
55% {
|
55% {
|
||||||
transform: translate(-22px, 12px) rotate(-4deg);
|
transform: translate(-22px, 12px) rotate(-4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
60% {
|
60% {
|
||||||
transform: translate(22px, -8px) rotate(2deg);
|
transform: translate(22px, -8px) rotate(2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
65% {
|
65% {
|
||||||
transform: translate(-18px, 10px) rotate(-2deg);
|
transform: translate(-18px, 10px) rotate(-2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
transform: translate(18px, -12px) rotate(2deg);
|
transform: translate(18px, -12px) rotate(2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
75% {
|
75% {
|
||||||
transform: translate(-22px, 14px) rotate(-4deg);
|
transform: translate(-22px, 14px) rotate(-4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
80% {
|
80% {
|
||||||
transform: translate(22px, -10px) rotate(4deg);
|
transform: translate(22px, -10px) rotate(4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
85% {
|
85% {
|
||||||
transform: translate(-18px, 8px) rotate(-2deg);
|
transform: translate(-18px, 8px) rotate(-2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
90% {
|
90% {
|
||||||
transform: translate(18px, -14px) rotate(2deg);
|
transform: translate(18px, -14px) rotate(2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
95% {
|
95% {
|
||||||
transform: translate(-20px, 5px) rotate(-2deg);
|
transform: translate(-20px, 5px) rotate(-2deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: translate(0, 0) rotate(0);
|
transform: translate(0, 0) rotate(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shake-screen {
|
.shake-screen {
|
||||||
animation: supershake 1s cubic-bezier(.36, .07, .19, .97) both;
|
animation: supershake 1s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- CRACK + COLLAPSE OVERLAY (X icon) ---- */
|
/* ---- CRACK + COLLAPSE OVERLAY (X icon) ---- */
|
||||||
.crack-overlay {
|
.crack-overlay {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
transition: transform 1.1s cubic-bezier(.65, .05, .45, 1), opacity 0.5s;
|
transition: transform 1.1s cubic-bezier(0.65, 0.05, 0.45, 1), opacity 0.5s;
|
||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
.crack {
|
.crack {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.crack1 {
|
.crack1 {
|
||||||
width: 35vw;
|
width: 35vw;
|
||||||
left: 10vw;
|
left: 10vw;
|
||||||
top: 22vh;
|
top: 22vh;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.crack2 {
|
.crack2 {
|
||||||
width: 32vw;
|
width: 32vw;
|
||||||
right: 12vw;
|
right: 12vw;
|
||||||
top: 42vh;
|
top: 42vh;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
transform: rotate(-8deg);
|
transform: rotate(-8deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add more .crackN classes if using more cracks */
|
/* Add more .crackN classes if using more cracks */
|
||||||
|
|
||||||
/* Collapse falling effect */
|
/* Collapse falling effect */
|
||||||
.crack-collapse {
|
.crack-collapse {
|
||||||
transform: perspective(900px) rotateX(75deg) translateY(80vh) scale(0.9);
|
transform: perspective(900px) rotateX(75deg) translateY(80vh) scale(0.9);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: transform 1.1s cubic-bezier(.65, .05, .45, 1), opacity 0.6s;
|
transition: transform 1.1s cubic-bezier(0.65, 0.05, 0.45, 1), opacity 0.6s;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,110 +7,94 @@ import LogObservatoryModal from "@/components/LogObservatoryModal"; // Adjust if
|
|||||||
import { fetcher } from "@utils/axiosHelpers";
|
import { fetcher } from "@utils/axiosHelpers";
|
||||||
import { Observatory } from "@prismaclient";
|
import { Observatory } from "@prismaclient";
|
||||||
import { getRelativeDate } from "@utils/formatters";
|
import { getRelativeDate } from "@utils/formatters";
|
||||||
import GeologicalEvent from "@appTypes/Event";
|
import GeologicalEvent from "@appTypes/GeologicalEvent";
|
||||||
import { useStoreState } from "@hooks/store";
|
import { useStoreState } from "@hooks/store";
|
||||||
|
|
||||||
function NoAccessModal({ open, onClose }) {
|
function NoAccessModal({ open, onClose }) {
|
||||||
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-8 max-w-xs w-full text-center relative">
|
<div className="bg-white rounded-lg shadow-lg p-8 max-w-xs w-full text-center relative">
|
||||||
<button
|
<button onClick={onClose} className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg" aria-label="Close">
|
||||||
onClick={onClose}
|
×
|
||||||
className="absolute right-4 top-4 text-gray-500 hover:text-black text-lg"
|
</button>
|
||||||
aria-label="Close"
|
<h2 className="font-bold text-xl mb-4">No Access</h2>
|
||||||
>×</button>
|
<p className="text-gray-600 mb-3">Sorry, You do not have access rights, please log in or contact an Admin.</p>
|
||||||
<h2 className="font-bold text-xl mb-4">No Access</h2>
|
<button onClick={onClose} className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 mt-2">
|
||||||
<p className="text-gray-600 mb-3">Sorry, You do not have access rights, please log in or contact an Admin.</p>
|
OK
|
||||||
<button
|
</button>
|
||||||
onClick={onClose}
|
</div>
|
||||||
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 mt-2"
|
</div>
|
||||||
>OK</button>
|
);
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Observatories() {
|
export default function Observatories() {
|
||||||
const [selectedEventId, setSelectedEventId] = useState("");
|
const [selectedEventId, setSelectedEventId] = useState("");
|
||||||
const [hoveredEventId, setHoveredEventId] = useState("");
|
const [hoveredEventId, setHoveredEventId] = useState("");
|
||||||
const [logModalOpen, setLogModalOpen] = useState(false);
|
const [logModalOpen, setLogModalOpen] = useState(false);
|
||||||
const [noAccessModalOpen, setNoAccessModalOpen] = useState(false);
|
const [noAccessModalOpen, setNoAccessModalOpen] = useState(false);
|
||||||
|
|
||||||
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(
|
const user = useStoreState((state) => state.user);
|
||||||
"/api/observatories",
|
const role: "GUEST" | "SCIENTIST" | "ADMIN" = user?.role ?? "GUEST";
|
||||||
fetcher
|
const canLogObservatory = role === "SCIENTIST" || role === "ADMIN";
|
||||||
);
|
|
||||||
|
|
||||||
const observatoryEvents = useMemo(
|
const { data, error, isLoading, mutate } = useSWR("/api/observatories", fetcher);
|
||||||
() =>
|
|
||||||
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 = () => {
|
const observatoryEvents = useMemo(
|
||||||
if (canLogObservatory) {
|
() =>
|
||||||
setLogModalOpen(true);
|
data && data.observatories
|
||||||
} else {
|
? data.observatories
|
||||||
setNoAccessModalOpen(true);
|
.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]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
const handleLogClick = () => {
|
||||||
<div className="flex h-[calc(100vh-3.5rem)] w-full overflow-hidden">
|
if (canLogObservatory) {
|
||||||
<div className="flex-grow h-full">
|
setLogModalOpen(true);
|
||||||
<Map
|
} else {
|
||||||
events={observatoryEvents}
|
setNoAccessModalOpen(true);
|
||||||
selectedEventId={selectedEventId}
|
}
|
||||||
setSelectedEventId={setSelectedEventId}
|
};
|
||||||
hoveredEventId={hoveredEventId}
|
|
||||||
setHoveredEventId={setHoveredEventId}
|
return (
|
||||||
mapType="observatories"
|
<div className="flex h-[calc(100vh-3.5rem)] w-full overflow-hidden">
|
||||||
/>
|
<div className="flex-grow h-full">
|
||||||
</div>
|
<Map
|
||||||
<Sidebar
|
events={observatoryEvents}
|
||||||
logTitle="Observatory Mapping"
|
selectedEventId={selectedEventId}
|
||||||
logSubtitle="Record and search observatories - time/date set-up, location, scientists and recent earthquakes"
|
setSelectedEventId={setSelectedEventId}
|
||||||
recentsTitle="New Observatories"
|
hoveredEventId={hoveredEventId}
|
||||||
events={observatoryEvents}
|
setHoveredEventId={setHoveredEventId}
|
||||||
selectedEventId={selectedEventId}
|
mapType="observatories"
|
||||||
setSelectedEventId={setSelectedEventId}
|
/>
|
||||||
hoveredEventId={hoveredEventId}
|
</div>
|
||||||
setHoveredEventId={setHoveredEventId}
|
<Sidebar
|
||||||
button1Name="Log a New Observatory"
|
logTitle="Observatory Mapping"
|
||||||
button2Name="Search Observatories"
|
logSubtitle="Record and search observatories - time/date set-up, location, scientists and recent earthquakes"
|
||||||
onButton1Click={handleLogClick}
|
recentsTitle="New Observatories"
|
||||||
button1Disabled={!canLogObservatory}
|
events={observatoryEvents}
|
||||||
/>
|
selectedEventId={selectedEventId}
|
||||||
<LogObservatoryModal
|
setSelectedEventId={setSelectedEventId}
|
||||||
open={logModalOpen}
|
hoveredEventId={hoveredEventId}
|
||||||
onClose={() => setLogModalOpen(false)}
|
setHoveredEventId={setHoveredEventId}
|
||||||
onSuccess={() => mutate()}
|
button1Name="Log a New Observatory"
|
||||||
/>
|
button2Name="Search Observatories"
|
||||||
<NoAccessModal
|
onButton1Click={handleLogClick}
|
||||||
open={noAccessModalOpen}
|
button1Disabled={!canLogObservatory}
|
||||||
onClose={() => setNoAccessModalOpen(false)}
|
/>
|
||||||
/>
|
<LogObservatoryModal open={logModalOpen} onClose={() => setLogModalOpen(false)} onSuccess={() => mutate()} />
|
||||||
</div>
|
<NoAccessModal open={noAccessModalOpen} onClose={() => setNoAccessModalOpen(false)} />
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import Link from "next/link";
|
|||||||
import React, { Dispatch, SetStateAction, useState } from "react";
|
import React, { Dispatch, SetStateAction, useState } from "react";
|
||||||
import { TbHexagon } from "react-icons/tb";
|
import { TbHexagon } from "react-icons/tb";
|
||||||
|
|
||||||
import Event from "@appTypes/Event";
|
import Event from "@appTypes/GeologicalEvent";
|
||||||
import getMagnitudeColor from "@utils/getMagnitudeColour";
|
import getMagnitudeColor from "@utils/getMagnitudeColour";
|
||||||
|
|
||||||
function setButton1(button1Name) {
|
function setButton1(button1Name) {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef, useSta
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { GiObservatory } from "react-icons/gi";
|
import { GiObservatory } from "react-icons/gi";
|
||||||
|
|
||||||
import GeologicalEvent from "@appTypes/Event";
|
import GeologicalEvent from "@appTypes/GeologicalEvent";
|
||||||
import getMagnitudeColor from "@utils/getMagnitudeColour";
|
import getMagnitudeColor from "@utils/getMagnitudeColour";
|
||||||
|
|
||||||
interface MapComponentProps {
|
interface MapComponentProps {
|
||||||
@ -111,12 +111,12 @@ function MapComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const observatoryElement = document.createElement("div");
|
const observatoryElement = document.createElement("div");
|
||||||
const root = createRoot(observatoryElement);
|
const root = createRoot(observatoryElement);
|
||||||
root.render(
|
root.render(
|
||||||
<GiObservatory
|
<GiObservatory
|
||||||
className={`text-2xl drop-shadow-lg ${event.isFunctional === false ? "text-gray-400" : "text-blue-600"}`}
|
className={`text-2xl drop-shadow-lg ${event.isFunctional === false ? "text-gray-400" : "text-blue-600"}`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
quakeElement.appendChild(pulseElement);
|
quakeElement.appendChild(pulseElement);
|
||||||
quakeElement.appendChild(dotElement);
|
quakeElement.appendChild(dotElement);
|
||||||
@ -128,6 +128,7 @@ function MapComponent({
|
|||||||
const popup = new mapboxgl.Popup({ offset: 25, closeButton: false, anchor: "bottom" }).setHTML(`
|
const popup = new mapboxgl.Popup({ offset: 25, closeButton: false, anchor: "bottom" }).setHTML(`
|
||||||
<div>
|
<div>
|
||||||
<h3>${event.title}</h3>
|
<h3>${event.title}</h3>
|
||||||
|
${mapType !== "observatories" ? `<p style="margin-bottom:3px;">${event.code}` : null}
|
||||||
${mapType === "observatories" ? `<p>${event.text1}</p>` : `<p>Magnitude: ${event.magnitude}</p>`}
|
${mapType === "observatories" ? `<p>${event.text1}</p>` : `<p>Magnitude: ${event.magnitude}</p>`}
|
||||||
<p>${event.text2}</p>
|
<p>${event.text2}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,124 +1,128 @@
|
|||||||
import React, { Dispatch, SetStateAction, useEffect, useRef } from "react";
|
import React, { Dispatch, SetStateAction, useEffect, useRef } from "react";
|
||||||
import { TbHexagon } from "react-icons/tb";
|
import { TbHexagon } from "react-icons/tb";
|
||||||
import GeologicalEvent from "@appTypes/Event";
|
import GeologicalEvent from "@appTypes/GeologicalEvent";
|
||||||
import getMagnitudeColor from "@utils/getMagnitudeColour";
|
import getMagnitudeColor from "@utils/getMagnitudeColour";
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
logTitle: string;
|
logTitle: string;
|
||||||
logSubtitle: string;
|
logSubtitle: string;
|
||||||
recentsTitle: string;
|
recentsTitle: string;
|
||||||
events: GeologicalEvent[];
|
events: GeologicalEvent[];
|
||||||
selectedEventId: GeologicalEvent["id"];
|
selectedEventId: GeologicalEvent["id"];
|
||||||
setSelectedEventId: Dispatch<SetStateAction<string>>;
|
setSelectedEventId: Dispatch<SetStateAction<string>>;
|
||||||
hoveredEventId: GeologicalEvent["id"];
|
hoveredEventId: GeologicalEvent["id"];
|
||||||
setHoveredEventId: Dispatch<SetStateAction<string>>;
|
setHoveredEventId: Dispatch<SetStateAction<string>>;
|
||||||
button1Name: string;
|
button1Name: string;
|
||||||
button2Name: string;
|
button2Name: string;
|
||||||
onButton2Click?: () => void;
|
onButton2Click?: () => void;
|
||||||
onButton1Click?: () => void;
|
onButton1Click?: () => void;
|
||||||
button1Disabled?: boolean;
|
button1Disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MagnitudeNumber({ magnitude }: { magnitude: number }) {
|
function MagnitudeNumber({ magnitude }: { magnitude: number }) {
|
||||||
const magnitudeStr = magnitude.toFixed(1);
|
const magnitudeStr = magnitude.toFixed(1);
|
||||||
const [whole, decimal] = magnitudeStr.split(".");
|
const [whole, decimal] = magnitudeStr.split(".");
|
||||||
return (
|
return (
|
||||||
<div className="relative" style={{ color: getMagnitudeColor(magnitude) }}>
|
<div className="relative" style={{ color: getMagnitudeColor(magnitude) }}>
|
||||||
<TbHexagon size={40} className="drop-shadow-sm" />
|
<TbHexagon size={40} className="drop-shadow-sm" />
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className="flex items-baseline font-mono font-bold tracking-tight">
|
<div className="flex items-baseline font-mono font-bold tracking-tight">
|
||||||
<span className="text-xl -mr-1">{whole}</span>
|
<span className="text-xl -mr-1">{whole}</span>
|
||||||
<span className="text-xs ml-[1.5px] -mr-[2.5px]">.</span>
|
<span className="text-xs ml-[1.5px] -mr-[2.5px]">.</span>
|
||||||
<span className="text-xs -mr-[1px]">{decimal}</span>
|
<span className="text-xs -mr-[1px]">{decimal}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Sidebar({
|
export default function Sidebar({
|
||||||
logTitle,
|
logTitle,
|
||||||
logSubtitle,
|
logSubtitle,
|
||||||
recentsTitle,
|
recentsTitle,
|
||||||
events,
|
events,
|
||||||
selectedEventId,
|
selectedEventId,
|
||||||
setSelectedEventId,
|
setSelectedEventId,
|
||||||
hoveredEventId,
|
hoveredEventId,
|
||||||
setHoveredEventId,
|
setHoveredEventId,
|
||||||
button1Name,
|
button1Name,
|
||||||
button2Name,
|
button2Name,
|
||||||
onButton2Click,
|
onButton2Click,
|
||||||
onButton1Click,
|
onButton1Click,
|
||||||
button1Disabled = false,
|
button1Disabled = false,
|
||||||
}: SidebarProps) {
|
}: SidebarProps) {
|
||||||
const eventsContainerRef = useRef<HTMLDivElement>(null);
|
const eventsContainerRef = useRef<HTMLDivElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEventId && eventsContainerRef.current) {
|
if (selectedEventId && eventsContainerRef.current) {
|
||||||
const selectedEventElement = eventsContainerRef.current.querySelector(`[data-event-id="${selectedEventId}"]`);
|
const selectedEventElement = eventsContainerRef.current.querySelector(`[data-event-id="${selectedEventId}"]`);
|
||||||
if (selectedEventElement) {
|
if (selectedEventElement) {
|
||||||
selectedEventElement.scrollIntoView({
|
selectedEventElement.scrollIntoView({
|
||||||
block: "center",
|
block: "center",
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedEventId]);
|
}, [selectedEventId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full w-80 bg-gradient-to-b from-neutral-100 to-neutral-50 shadow-lg">
|
<div className="flex flex-col h-full w-80 bg-gradient-to-b from-neutral-100 to-neutral-50 shadow-lg">
|
||||||
<div className="py-6 flex flex-col h-full">
|
<div className="py-6 flex flex-col h-full">
|
||||||
<div className="px-6 pb-8 border-b border-neutral-200">
|
<div className="px-6 pb-8 border-b border-neutral-200">
|
||||||
<h2 className="text-2xl font-bold text-neutral-800 mb-2">{logTitle}</h2>
|
<h2 className="text-2xl font-bold text-neutral-800 mb-2">{logTitle}</h2>
|
||||||
<p className="text-sm text-neutral-600 leading-relaxed">{logSubtitle}</p>
|
<p className="text-sm text-neutral-600 leading-relaxed">{logSubtitle}</p>
|
||||||
<button
|
<button
|
||||||
className={`mt-4 w-full py-2 px-4 rounded-lg transition-colors duration-200 font-medium
|
className={`mt-4 w-full py-2 px-4 rounded-lg transition-colors duration-200 font-medium
|
||||||
${button1Disabled
|
${
|
||||||
? "bg-gray-300 text-gray-500 cursor-not-allowed"
|
button1Disabled
|
||||||
: "bg-blue-600 hover:bg-blue-700 text-white"
|
? "bg-gray-300 text-gray-500 cursor-not-allowed"
|
||||||
}`}
|
: "bg-blue-600 hover:bg-blue-700 text-white"
|
||||||
onClick={onButton1Click}
|
}`}
|
||||||
type="button"
|
onClick={onButton1Click}
|
||||||
|
type="button"
|
||||||
tabIndex={button1Disabled ? -1 : 0}
|
tabIndex={button1Disabled ? -1 : 0}
|
||||||
aria-disabled={button1Disabled ? "true" : "false"}
|
aria-disabled={button1Disabled ? "true" : "false"}
|
||||||
>
|
>
|
||||||
{button1Name}
|
{button1Name}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors duration-200 font-medium"
|
className="mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors duration-200 font-medium"
|
||||||
onClick={onButton2Click}
|
onClick={onButton2Click}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{button2Name}
|
{button2Name}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-6 pt-6">
|
<div className="px-6 pt-6">
|
||||||
<h2 className="text-xl font-bold text-neutral-800 mb-4">{recentsTitle}</h2>
|
<h2 className="text-xl font-bold text-neutral-800 mb-4">{recentsTitle}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 px-6 overflow-y-auto" ref={eventsContainerRef}>
|
<div className="flex-1 px-6 overflow-y-auto" ref={eventsContainerRef}>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{events.map((event) => (
|
{events.map((event) => (
|
||||||
<button
|
<button
|
||||||
key={event.id}
|
key={event.id}
|
||||||
data-event-id={event.id}
|
data-event-id={event.id}
|
||||||
className={`w-full border ${hoveredEventId === event.id ? "bg-neutral-100" : "bg-white"} ${
|
className={`w-full border ${
|
||||||
selectedEventId === event.id ? "border-neutral-800" : "border-neutral-200"
|
selectedEventId === event.id
|
||||||
} rounded-lg p-4 flex items-center gap-4 hover:bg-neutral-100 transition-colors duration-150 shadow-sm text-left`}
|
? "border-neutral-500 border-2 shadow-md"
|
||||||
onClick={() => {
|
: hoveredEventId === event.id
|
||||||
setSelectedEventId((prevEventId) => (prevEventId !== event.id ? event.id : ""));
|
? "bg-neutral-100 shadow-sm"
|
||||||
}}
|
: "bg-white border-neutral-200 shadow-sm"
|
||||||
onMouseEnter={() => setHoveredEventId(event.id)}
|
} rounded-lg p-4 flex items-center gap-4 hover:bg-neutral-100 transition-colors duration-150 text-left`}
|
||||||
onMouseLeave={() => setHoveredEventId("")}
|
onClick={() => {
|
||||||
>
|
setSelectedEventId((prevEventId) => (prevEventId !== event.id ? event.id : ""));
|
||||||
<div className="flex-1">
|
}}
|
||||||
<p className="text-sm font-medium text-neutral-800 line-clamp-1">{event.title}</p>
|
onMouseEnter={() => setHoveredEventId(event.id)}
|
||||||
<p className="text-xs text-neutral-500 mt-1 line-clamp-1">{event.text2}</p>
|
onMouseLeave={() => setHoveredEventId("")}
|
||||||
</div>
|
>
|
||||||
{event.magnitude ? <MagnitudeNumber magnitude={event.magnitude} /> : <></>}
|
<div className="flex-1">
|
||||||
</button>
|
<p className="text-sm font-medium text-neutral-800 line-clamp-1">{event.title}</p>
|
||||||
))}
|
<p className="text-xs text-neutral-500 mt-1 line-clamp-1">{event.text2}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{event.magnitude ? <MagnitudeNumber magnitude={event.magnitude} /> : <></>}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
))}
|
||||||
);
|
</div>
|
||||||
}
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -3,8 +3,10 @@ interface GeologicalEvent {
|
|||||||
date: Date;
|
date: Date;
|
||||||
title: string;
|
title: string;
|
||||||
magnitude?: number;
|
magnitude?: number;
|
||||||
|
code?: string;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
|
isFunctional?: boolean;
|
||||||
text1: string;
|
text1: string;
|
||||||
text2: string;
|
text2: string;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user