From 0ae4d6145cea0391a23086ec527fa01dd15b7f72 Mon Sep 17 00:00:00 2001 From: Emily Neighbour Date: Sun, 1 Jun 2025 11:21:04 +0100 Subject: [PATCH 1/4] homepage info --- src/app/page.tsx | 391 ++++++++++++++++++++++++----------------------- 1 file changed, 196 insertions(+), 195 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 2bcd8ea..c3174e4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,212 +3,213 @@ import Image from "next/image"; import Link from "next/link"; import { TbHexagon } from "react-icons/tb"; import useSWR from "swr"; + import BottomFooter from "@components/BottomFooter"; import { createPoster } from "@utils/axiosHelpers"; import getMagnitudeColor from "@utils/getMagnitudeColour"; // formats the date function getRelativeDate(dateString: string): string { - const date = new Date(dateString); - const now = new Date(); - const diffMs = now.getTime() - date.getTime(); - const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); - if (diffDays === 0) return "today"; - if (diffDays === 1) return "yesterday"; - return date.toLocaleDateString(); + const date = new Date(dateString); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + if (diffDays === 0) return "today"; + if (diffDays === 1) return "yesterday"; + return date.toLocaleDateString(); } // copied from sidebar function MagnitudeNumber({ magnitude }: { magnitude: number }) { - const magnitudeStr = magnitude.toFixed(1); - const [whole, decimal] = magnitudeStr.split("."); - return ( -
- -
-
- {whole} - . - {decimal} -
-
-
- ); + const magnitudeStr = magnitude.toFixed(1); + const [whole, decimal] = magnitudeStr.split("."); + return ( +
+ +
+
+ {whole} + . + {decimal} +
+
+
+ ); } export default function Home() { - const { data, error, isLoading } = useSWR( - "/api/earthquakes", - createPoster({ rangeDaysPrev: 6 }) - ); - // Take 5 most recent - const recents = (data?.earthquakes ?? []) - .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) - .slice(0, 5); + const { data, error, isLoading } = useSWR("/api/earthquakes", createPoster({ rangeDaysPrev: 6 })); + // Take 5 most recent + const recents = (data?.earthquakes ?? []).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, 5); - return ( -
-
-
- Background Image -
-
-
- Title Image -
-
-

-
- - Education Icon -

Earthquakes

-

- Log new earthquakes with their required details or search past seismic events -

- - - Research Icon -

Observatories

-

- Find recently active observatories, and newly opened/closed sites -

- - - Technology Icon -

Artefacts

-

- View or purchase recently discovered artefacts from seismic events -

- -
-

-
-
-
- Background Image -
-
-
-

- Welcome to Tremor Tracker -

-

- TremorTracker is a non-profit website and research company, that aims to provide true, reliable data. Our mission - is seismic education and preparation for all -

-

-

What is an earthquake?

-

- Earthquakes are a shaking of the earth's surface caused by a sudden release of energy underground. They can range - in size, from tiny trembles to large quakes, which can cause destruction and even tsunamis. Hundreds of - earthquakes happen every day—but most are too small to feel. -

-

-

- How do we log earthquakes? -

-

- What information are we interested in? -

-

info

-

-

- What are observatories? -

-

What is their role?

-

info

-
-
-
-
-

-
-

- Recent Earthquake Events -

-

- Learn about the most recent earthquake events from around the world: -

-
-

-
- {error && ( -
-

Failed to load earthquakes.

-
- )} - {isLoading && ( -
-

Loading...

-
- )} - {!isLoading && recents.length === 0 && ( -
-

No earthquakes found.

-
- )} -
- {recents.map((eq) => ( -
-
-
- Earthquake in {eq.location || (eq.code && eq.code.split("-")[2])} -
-
{getRelativeDate(eq.date)}
-
- -
- ))} -
-
-

-
-

- Find Out More! -

-

- Explore more of our website... -

-
-

-
- - Education Icon -

Contact us directly

-

- Visit our socials or leave us a message via phone or email. -

- - - Research Icon -

Our Mission

-

- Find out more about our purpose and the features we offer. -

- - - Technology Icon -

Meet the Team

-

- Learn about our team leads and their responsibilities. -

- -
-

-
-
-
- Background Image -
- -
-
-
- ); -} \ No newline at end of file + return ( +
+
+
+ Background Image +
+
+
+ Title Image +
+
+

+
+ + Education Icon +

Earthquakes

+

+ Log new earthquakes with their required details or search past seismic events +

+ + + Research Icon +

Observatories

+

+ Find recently active observatories, and newly opened/closed sites +

+ + + Aftefacts Icon +

Artefacts

+

+ View or purchase recently discovered artefacts from seismic events +

+ +
+

+
+
+
+ Background Image +
+
+
+

+ Welcome to Tremor Tracker +

+

+ TremorTracker is a non-profit website and research company, that aims to provide seismic education and aid + preparation +

+

+

What is an earthquake?

+

+ An earthquake is a sudden shaking of the Earth’s surface, triggered by a rapid release of energy deep underground. + This usually happens because the Earth’s outer shell, called the crust, is made up of large pieces known as + tectonic plates. These plates are always moving, but sometimes they get stuck at their edges; when stress builds + up and is finally released, it causes the ground to shake—an earthquake. Earthquakes can vary greatly in size—from + barely noticeable tremors to powerful quakes capable of causing widespread destruction. There are several types: + Tectonic, Volcanic and Collapse earthquakes. Understanding why and how earthquakes happen helps scientists predict + where they are most likely to occur and how to lessen their impact. +

+

+

+ How do we log earthquakes? +

+

+ What information are we interested in? +

+

+ Scientists record earthquakes using special instruments called seismometers, which detect and measure the + vibrations in the ground. When an earthquake occurs, the seismometer produces a trace known as a seismogram, + showing the strength and duration of the shaking. Information from seismometers around the world is sent to data + centers, where experts analyze it to pinpoint the earthquake’s location, type, depth, and magnitude. This process + is called “logging” or recording earthquakes, and it helps track seismic activity globally. +

+

+

+ What are observatories? +

+

+ An earthquake observatory is a specialized facility where scientists monitor and study seismic activity. These + observatories are equipped with sensitive instruments that can detect and record even the smallest tremors deep + within the Earth. Observatories collect important data about the strength, location, and timing of each earthquake + that can be shared with the general public. Scientists at the observatory use this data to better understand how + and why earthquakes occur, track earthquake patterns, and issue warnings if a major quake is detected. The + information gathered also helps in designing safer buildings and improving emergency response plans. +

+
+
+
+
+

+
+

Recent Earthquake Events

+

+ Learn about the most recent earthquake events from around the world: +

+
+

+
+ {error && ( +
+

Failed to load earthquakes.

+
+ )} + {isLoading && ( +
+

Loading...

+
+ )} + {!isLoading && recents.length === 0 && ( +
+

No earthquakes found.

+
+ )} +
+ {recents.map((eq) => ( +
+
+
Earthquake in {eq.location || (eq.code && eq.code.split("-")[2])}
+
{getRelativeDate(eq.date)}
+
+ +
+ ))} +
+
+

+
+

Find Out More!

+

Explore more of our website...

+
+

+
+ + Education Icon +

Contact us directly

+

+ Visit our socials or leave us a message via phone or email. +

+ + + Research Icon +

Our Mission

+

+ Find out more about our purpose and the features we offer. +

+ + + Technology Icon +

Meet the Team

+

+ Learn about our team leads and their responsibilities. +

+ +
+

+
+
+
+ Background Image +
+ +
+
+
+ ); +} From 5e22cef64b896c2427fc884217dab3275adbc3f1 Mon Sep 17 00:00:00 2001 From: Emily Neighbour Date: Sun, 1 Jun 2025 11:26:51 +0100 Subject: [PATCH 2/4] learn icon added to homepage --- public/learn.jpg | Bin 0 -> 27799 bytes src/app/page.tsx | 13 ++++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 public/learn.jpg diff --git a/public/learn.jpg b/public/learn.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e46242842662f492108a4feb1bbb1a353a581ed1 GIT binary patch literal 27799 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 P-nDh+^+IcL>hstyC_AJP literal 0 HcmV?d00001 diff --git a/src/app/page.tsx b/src/app/page.tsx index c3174e4..75d36fd 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -180,26 +180,33 @@ export default function Home() {

- Education Icon + Contact Us Icon

Contact us directly

Visit our socials or leave us a message via phone or email.

- Research Icon + Our Mission Icon

Our Mission

Find out more about our purpose and the features we offer.

- Technology Icon + Team Icon

Meet the Team

Learn about our team leads and their responsibilities.

+ + Learn Icon +

Learn

+

+ Find out more about earthquakes, what causes them and how to prepare. +

+

From ec067773f45b321efcdb580b7204ac3b8ea75bd3 Mon Sep 17 00:00:00 2001 From: Lukeshan Thananchayan Date: Sun, 1 Jun 2025 11:35:16 +0100 Subject: [PATCH 3/4] Bug fix --- src/app/management/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/management/page.tsx b/src/app/management/page.tsx index 53be8b0..667fda3 100644 --- a/src/app/management/page.tsx +++ b/src/app/management/page.tsx @@ -49,7 +49,7 @@ const fieldLabels: Record = { name: "Name", level: "Level" }; type RequestModalProps = { open: boolean; onClose: () => void; - requestingUserId: number; + requestingUserId: number | undefined; scientist?: Scientist | null; }; function RequestModal({ open, onClose, requestingUserId, scientist }: RequestModalProps) { From 4608d547f9c1475d6eb94a74e0f241538723652e Mon Sep 17 00:00:00 2001 From: Lukeshan Thananchayan Date: Sun, 1 Jun 2025 12:22:23 +0100 Subject: [PATCH 4/4] Added requests page and backend --- package-lock.json | 7 + package.json | 1 + src/app/api/requests/route.ts | 41 ++++++ src/app/management/page.tsx | 122 +++++++++++++---- src/app/requests/page.tsx | 242 ++++++++++++++++++++++++++++++++++ src/components/Navbar.tsx | 9 ++ 6 files changed, 393 insertions(+), 29 deletions(-) create mode 100644 src/app/api/requests/route.ts create mode 100644 src/app/requests/page.tsx diff --git a/package-lock.json b/package-lock.json index 1ed5173..2574ef0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "csv-parse": "^5.6.0", "csv-parser": "^3.2.0", "date-fns": "^4.1.0", + "DatePicker": "^2.0.0", "dotenv": "^16.5.0", "easy-peasy": "^6.1.0", "express": "^5.1.0", @@ -2867,6 +2868,12 @@ "url": "https://github.com/sponsors/kossnocorp" } }, + "node_modules/DatePicker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/DatePicker/-/DatePicker-2.0.0.tgz", + "integrity": "sha512-x5zuXdURsRHGtLJAufN+LaR7EaisJ8HUDrfoHmOHQ5xfqYQa35kIwaQQeAQgc14puau2t1A2slDTuKqJwfZAdA==", + "license": "ISC" + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", diff --git a/package.json b/package.json index 0c79f56..36742f0 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "csv-parse": "^5.6.0", "csv-parser": "^3.2.0", "date-fns": "^4.1.0", + "DatePicker": "^2.0.0", "dotenv": "^16.5.0", "easy-peasy": "^6.1.0", "express": "^5.1.0", diff --git a/src/app/api/requests/route.ts b/src/app/api/requests/route.ts new file mode 100644 index 0000000..0df7883 --- /dev/null +++ b/src/app/api/requests/route.ts @@ -0,0 +1,41 @@ +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@utils/prisma"; + +// GET requests, just requestingUser only +export async function GET() { + try { + const requests = await prisma.request.findMany({ + orderBy: { createdAt: "desc" }, + include: { + requestingUser: true, + }, + }); + return NextResponse.json({ requests }, { status: 200 }); + } catch (err) { + console.error("Failed to get requests", err); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} + +export async function PUT(req: NextRequest) { + try { + const { id, outcome } = await req.json(); + if (!["FULFILLED", "REJECTED"].includes(outcome)) { + return NextResponse.json({ error: "Invalid outcome" }, { status: 400 }); + } + const existing = await prisma.request.findUnique({ where: { id } }); + if (!existing) { + return NextResponse.json({ error: "Request not found" }, { status: 404 }); + } + const updated = await prisma.request.update({ + where: { id }, + data: { + outcome, + }, + }); + return NextResponse.json({ request: updated }, { status: 200 }); + } catch (err) { + console.error("Update request failed", err); + return NextResponse.json({ error: "Update failed" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/management/page.tsx b/src/app/management/page.tsx index 667fda3..4de9c16 100644 --- a/src/app/management/page.tsx +++ b/src/app/management/page.tsx @@ -23,6 +23,37 @@ type Scientist = { subordinates: Scientist[]; }; // --- Helpers --- + +function normalizeScientist(input: any): Scientist { + return { + id: input.id, + createdAt: + typeof input.createdAt === "string" + ? input.createdAt + : input.createdAt instanceof Date + ? input.createdAt.toISOString() + : String(input.createdAt), + name: input.name, + level: + input.level === "JUNIOR" + ? "JUNIOR" + : input.level === "SENIOR" + ? "SENIOR" + : ("JUNIOR" as Level), // fallback/safe - but this should never happen + user: input.user, + userId: input.userId, + superior: input.superior ? normalizeScientist(input.superior) : null, + superiorId: + input.superiorId === undefined + ? null + : input.superiorId, + subordinates: + Array.isArray(input.subordinates) + ? input.subordinates.map(normalizeScientist) + : [], + }; + } + const initialScientists: Scientist[] = [ { id: 0, @@ -44,8 +75,7 @@ type SortField = (typeof sortFields)[number]["value"]; type SortDir = "asc" | "desc"; const dirLabels: Record = { asc: "ascending", desc: "descending" }; const fieldLabels: Record = { name: "Name", level: "Level" }; - -// --- Updated RequestModal (only level/removal, no comment) +// --- Updated RequestModal (clearer wording for "myself" & only level/removal) type RequestModalProps = { open: boolean; onClose: () => void; @@ -57,7 +87,6 @@ function RequestModal({ open, onClose, requestingUserId, scientist }: RequestMod const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(null); const [error, setError] = useState(null); - async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(null); setSuccess(null); @@ -78,7 +107,7 @@ function RequestModal({ open, onClose, requestingUserId, scientist }: RequestMod const d = await res.json().catch(() => ({})); throw new Error(d?.error || "Request failed"); } - setSuccess("Request submitted for review."); + setSuccess("Your request has been submitted for review."); } catch (e: any) { setError(e?.message || "Unknown error"); } finally { @@ -89,18 +118,31 @@ function RequestModal({ open, onClose, requestingUserId, scientist }: RequestMod return open ? (
-

Request Action

+

+ Request a Change to Your Profile +

+
+ {/* Explain exactly what the request is for */} + {scientist + ? ( + <>You are requesting a change for your own scientist profile: {scientist.name} ({scientist.user.email}). + ) + : ( + <>You are requesting a change for your own scientist profile. + ) + } +
- +
{error &&
{error}
} @@ -160,6 +202,7 @@ export default function ScientistManagementPage() { const isAdmin = userRole === "ADMIN"; const isSeniorScientist = userRole === "SCIENTIST" && user?.scientist?.level === "SENIOR"; const readOnly = isSeniorScientist && !isAdmin; + // Data loading effects useEffect(() => { async function fetchAllUsers() { @@ -302,6 +345,18 @@ export default function ScientistManagementPage() { } }; const usersWithNoScientist = allUsers.filter(u => !u.scientist); + + // Find "my" scientist for the modal as senior scientist + const rawMyScientist = + user?.scientist + ? scientists.find(s => s.id === user.scientist?.id) || user.scientist + : undefined; + + // Only pass to modal if available, and fully normalized: + const myScientist = rawMyScientist + ? normalizeScientist(rawMyScientist) + : undefined; + if (!isAdmin && !isSeniorScientist) { return (
@@ -416,18 +471,34 @@ export default function ScientistManagementPage() { {sortDir === "asc" ? "↑" : "↓"} {isAdmin && ( - + + )} + {/* Senior scientist: Request Change button (for myself) */} + {!isAdmin && isSeniorScientist && !!myScientist && ( + )}
@@ -526,7 +597,7 @@ export default function ScientistManagementPage() { open={requestOpen} onClose={()=>setRequestOpen(false)} requestingUserId={user?.id} - scientist={editScientist} + scientist={myScientist} /> {/* MAIN PANEL */}
@@ -617,14 +688,7 @@ export default function ScientistManagementPage() { )} - {readOnly && ( - - )} + {/* No request change button here for readOnly/SCIENTIST users anymore */}
) : ( diff --git a/src/app/requests/page.tsx b/src/app/requests/page.tsx new file mode 100644 index 0000000..9d0807f --- /dev/null +++ b/src/app/requests/page.tsx @@ -0,0 +1,242 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { useStoreState } from "@hooks/store"; + +// Helper dicts/types +const outcomeColors: Record = { + FULFILLED: "text-green-700 bg-green-100", + REJECTED: "text-red-700 bg-red-100", + IN_PROGRESS: "text-blue-700 bg-blue-100", + CANCELLED: "text-gray-600 bg-gray-100", + OTHER: "text-yellow-800 bg-yellow-100", +}; +const requestTypeLabels: Record = { + NEW_USER: "New User", + CHANGE_LEVEL: "Change Level", + DELETE: "Removal", +}; +function formatDate(val?: string) { + if (!val) return "--"; + return new Date(val).toLocaleString(undefined, { + year: "numeric", + month: "short", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }); +} + +// Minimal types +type User = { + id: number; + name: string; + email: string; + role?: string; + scientist?: { level: string } | null; +}; + +type Request = { + id: number; + createdAt: string; + requestType: string; + requestingUser: User; + outcome: string; +}; + +export default function RequestManagementPage() { + const user = useStoreState((s) => s.user); + + // All hooks first! + const [requests, setRequests] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [actionLoading, setActionLoading] = useState(null); + const [actionError, setActionError] = useState(null); + const [actionSuccess, setActionSuccess] = useState(null); + + // User role logic must remain invariant per render + const userRole = user?.role as string | undefined; + const isAdmin = userRole === "ADMIN"; + const isSeniorScientist = userRole === "SCIENTIST" && user?.scientist?.level === "SENIOR"; + const userId = user?.id; + + // Requests fetch + useEffect(() => { + setLoading(true); + setError(null); + fetch("/api/requests") + .then((res) => { + if (!res.ok) throw new Error("Failed to fetch requests"); + return res.json(); + }) + .then((data) => { + setRequests(data.requests || []); + setLoading(false); + }) + .catch((err) => { + setError("Failed to load requests."); + setLoading(false); + }); + }, []); + + // Filtering for non-admins to only their requests + const filteredRequests = React.useMemo( + () => + isAdmin + ? requests + : requests.filter((r) => r.requestingUser.id === userId), + [isAdmin, requests, userId] + ); + + // Sorted: newest first + filteredRequests.sort((a, b) => + (b.createdAt ?? "").localeCompare(a.createdAt ?? "") + ); + + async function handleAction( + requestId: number, + action: "FULFILLED" | "REJECTED" + ) { + setActionLoading(requestId); + setActionError(null); + setActionSuccess(null); + try { + const res = await fetch("/api/requests", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id: requestId, outcome: action }), + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data?.error || "Failed to update request"); + } + setRequests((prev) => + prev.map((r) => + r.id === requestId ? { ...r, outcome: action } : r + ) + ); + setActionSuccess("Request updated."); + } catch (err: any) { + setActionError(err?.message || "Failed to update request"); + } finally { + setActionLoading(null); + } + } + + // Unauthorized access should return early, but not before hooks! + if (!isAdmin && !isSeniorScientist) { + return ( +
+

Unauthorized Access

+
You do not have access to this page.
+
+ ); + } + + return ( +
+
+

+ {isAdmin ? "All Requests" : "My Requests"} +

+

+ View {isAdmin ? "and manage pending" : "your"} requests related to scientist management. +

+
+ + {loading ? ( +
Loading...
+ ) : error ? ( +
{error}
+ ) : ( +
+ + + + + + + + {isAdmin && } + + + + {filteredRequests.length === 0 ? ( + + + + ) : ( + filteredRequests.map((req) => ( + + + + + + {isAdmin && ( + + )} + + )) + )} + +
DateTypeRequested ByStatusActions
+ No requests found. +
+ {formatDate(req.createdAt)} + + {requestTypeLabels[req.requestType] || req.requestType} + + {req.requestingUser.name} + + ({req.requestingUser.email}) + + + + {req.outcome.replace(/_/g, " ")} + + + {req.outcome === "IN_PROGRESS" ? ( +
+ + +
+ ) : ( + No actions + )} + {(actionError && actionLoading === req.id) && ( +
{actionError}
+ )} + {(actionSuccess && actionLoading === req.id) && ( +
{actionSuccess}
+ )} +
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 4d654b6..44d4bb1 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -142,6 +142,15 @@ export default function Navbar({}: // currencySelector,
) + )} + {user && ( + (user.role === "ADMIN" || + (user.role === "SCIENTIST" && user.scientist?.level === "SENIOR") + ) && ( +
+ +
+ ) )} {user && user.role === "ADMIN" && (