Merge branch 'master' of ssh://stash.dyson.global.corp:7999/~thowitz/tremor-tracker

This commit is contained in:
IZZY 2025-05-13 12:07:32 +01:00
commit f25b8789c0
13 changed files with 335 additions and 280 deletions

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

View File

@ -1,11 +1,11 @@
import bcryptjs from 'bcryptjs';
import { SignJWT } from 'jose';
import { NextResponse } from 'next/server';
import bcryptjs from "bcryptjs";
import { SignJWT } from "jose";
import { NextResponse } from "next/server";
import { PrismaClient } from '@prisma/client';
import { env } from '@utils/env';
import { PrismaClient } from "@prisma/client";
import { env } from "@utils/env";
import { findUserByEmail, readUserCsv, User } from '../functions/csvReadWrite';
import { findUserByEmail, readUserCsv, User } from "../functions/csvReadWrite";
const usingPrisma = false;
let prisma: PrismaClient;
@ -45,12 +45,12 @@ export async function POST(req: Request) {
include: {
earthquakes: true,
observatories: true,
artifacts: true,
artefacts: true,
superior: true,
subordinates: true,
},
},
purchasedArtifacts: true,
purchasedArtefacts: true,
},
});

View File

@ -1,15 +1,15 @@
import { NextResponse } from 'next/server';
import { NextResponse } from "next/server";
import { PrismaClient } from '@prisma/client';
import { env } from '@utils/env';
import { verifyJwt } from '@utils/verifyJwt';
import { PrismaClient } from "@prisma/client";
import { env } from "@utils/env";
import { verifyJwt } from "@utils/verifyJwt";
const usingPrisma = false;
let prisma: PrismaClient;
if (usingPrisma) prisma = new PrismaClient();
// Artifact type
interface Artifact {
// Artefact type
interface Artefact {
id: number;
name: string;
description: string;
@ -29,7 +29,7 @@ export async function POST(req: Request) {
if (!token) return NextResponse.json({ message: "Unauthorised" }, { status: 401 });
await verifyJwt({ token, secret: env.JWT_SECRET_KEY });
const warehouseArtifacts: Artifact[] = [
const warehouseArtefacts: Artefact[] = [
{
id: 1,
name: "Solidified Lava Chunk",
@ -87,17 +87,17 @@ export async function POST(req: Request) {
},
];
let artifacts;
if (usingPrisma) artifacts = await prisma.artifacts.findMany();
let artefacts;
if (usingPrisma) artefacts = await prisma.artefacts.findMany();
if (artifacts) {
return NextResponse.json({ message: "Got artifacts successfully", artifacts }, { status: 200 });
if (artefacts) {
return NextResponse.json({ message: "Got artefacts successfully", artefacts }, { status: 200 });
} else {
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: warehouseArtifacts }, { status: 200 });
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: warehouseArtefacts }, { status: 200 });
// return NextResponse.json({ message: "Failed to get earthquakes" }, { status: 401 });
}
} catch (error) {
console.error("Error in artifacts endpoint:", error);
console.error("Error in artefacts endpoint:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
} finally {
if (usingPrisma) await prisma.$disconnect();

View File

@ -1,6 +1,6 @@
"use client";
import Image from 'next/image';
import React, { useState } from 'react';
import Image from "next/image";
import React, { useState } from "react";
const ContactUs = () => {
const [formData, setFormData] = useState({
@ -37,8 +37,8 @@ const ContactUs = () => {
{/* Header */}
<h1 className="text-4xl font-bold text-center text-neutral-50 mb-6">Contact Us</h1>
<p className="text-lg text-center max-w-2xl text-neutral-200 mb-6">
Have questions or concerns about earthquakes, observatories or artifacts? Contact us via phone, email, social media or using the form below with the relevant
contact details.
Have questions or concerns about earthquakes, observatories or artefacts? Contact us via phone, email, social media or
using the form below with the relevant contact details.
</p>
{/* Content Section */}

View File

@ -32,7 +32,7 @@ const store = createStore<StoreModel>({
name: "Emily Neighbour",
role: "ADMIN",
scientist: undefined,
purchasedArtifacts: [],
purchasedArtefacts: [],
},
});

View File

@ -39,10 +39,10 @@ export default function Home() {
href="/shop"
className="flex flex-col items-center p-6 hover:bg-white hover:bg-opacity-10 rounded-xl transition-colors duration-300"
>
<Image height={100} width={100} src="/artifactIcon.jpg" alt="Technology Icon" className="h-40 w-40 mb-4" />
<h3 className="text-xl font-bold text-black mb-4">Artifacts</h3>
<Image height={100} width={100} src="/artefactIcon.jpg" alt="Technology Icon" className="h-40 w-40 mb-4" />
<h3 className="text-xl font-bold text-black mb-4">Artefacts</h3>
<p className="text-md text-black text-center max-w-xs opacity-90">
View or purchase recently discovered artifacts from seismic events
View or purchase recently discovered artefacts from seismic events
</p>
</Link>
</div>

View File

@ -1,19 +1,22 @@
"use client";
import Image from 'next/image';
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
import Image from "next/image";
import { Dispatch, SetStateAction, useCallback, useState } from "react";
import Artifact from '@appTypes/Artifact';
import { Currency } from '@appTypes/StoreModel';
import { useStoreState } from '@hooks/store';
import Artefact from "@appTypes/Artefact";
import { Currency } from "@appTypes/StoreModel";
import { useStoreState } from "@hooks/store";
// Artifacts Data
const artifacts: Artifact[] = [
// Artefacts Data
const artefacts: Artefact[] = [
{
id: 1,
name: "Golden Scarab",
description: "An ancient Egyptian artifact symbolizing rebirth.",
description: "An ancient Egyptian artefact symbolizing rebirth.",
location: "Cairo, Egypt",
image: "/artifact1.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact1.jpg",
price: 150,
},
{
@ -21,7 +24,10 @@ const artifacts: Artifact[] = [
name: "Aztec Sunstone",
description: "A replica of the Aztec calendar (inscriptions intact).",
location: "Peru",
image: "/artifact2.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact2.jpg",
price: 200,
},
{
@ -29,7 +35,10 @@ const artifacts: Artifact[] = [
name: "Medieval Chalice",
description: "Used by royalty in medieval ceremonies.",
location: "Cambridge, England",
image: "/artifact3.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact3.jpg",
price: 120,
},
{
@ -37,7 +46,10 @@ const artifacts: Artifact[] = [
name: "Roman Coin",
description: "An authentic Roman coin from the 2nd century CE.",
location: "Rome, Italy",
image: "/artifact4.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact4.jpg",
price: 80,
},
{
@ -45,7 +57,10 @@ const artifacts: Artifact[] = [
name: "Samurai Mask",
description: "Replica of Japanese Samurai battle masks.",
location: "Tokyo, Japan",
image: "/artifact5.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact5.jpg",
price: 300,
},
{
@ -53,7 +68,10 @@ const artifacts: Artifact[] = [
name: "Ancient Greek Vase",
description: "Depicts Greek mythology, found in the Acropolis.",
location: "Athens, Greece",
image: "/artifact6.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact6.jpg",
price: 250,
},
{
@ -61,7 +79,10 @@ const artifacts: Artifact[] = [
name: "Incan Pendant",
description: "Represents the Sun God Inti.",
location: "India",
image: "/artifact7.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact7.jpg",
price: 175,
},
{
@ -69,7 +90,10 @@ const artifacts: Artifact[] = [
name: "Persian Carpet Fragment",
description: "Ancient Persian artistry.",
location: "Petra, Jordan",
image: "/artifact8.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact8.jpg",
price: 400,
},
{
@ -77,7 +101,10 @@ const artifacts: Artifact[] = [
name: "Stone Buddha",
description: "Authentic stone Buddha carving.",
location: "India",
image: "/artifact9.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact9.jpg",
price: 220,
},
{
@ -85,7 +112,10 @@ const artifacts: Artifact[] = [
name: "Victorian Brooch",
description: "A beautiful Victorian-era brooch with a ruby centre.",
location: "Oxford, England",
image: "/artifact10.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact10.jpg",
price: 150,
},
{
@ -93,7 +123,10 @@ const artifacts: Artifact[] = [
name: "Ancient Scroll",
description: "A mysterious scroll from ancient times.",
location: "Madrid, Spain",
image: "/artifact11.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact11.jpg",
price: 500,
},
{
@ -101,7 +134,10 @@ const artifacts: Artifact[] = [
name: "Ming Dynasty Porcelain",
description: "Porcelain from China's Ming Dynasty.",
location: "Beijing, China",
image: "/artifact12.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact12.jpg",
price: 300,
},
{
@ -109,15 +145,21 @@ const artifacts: Artifact[] = [
name: "African Tribal Mask",
description: "A unique tribal mask from Africa.",
location: "Nigeria",
image: "/artifact13.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact13.jpg",
price: 250,
},
{
id: 14,
name: "Crystal Skull",
description: "A mystical pre-Columbian artifact.",
description: "A mystical pre-Columbian artefact.",
location: "Colombia",
image: "/artifact14.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact14.jpg",
price: 1000,
},
{
@ -125,7 +167,10 @@ const artifacts: Artifact[] = [
name: "Medieval Armor Fragment",
description: "A fragment of medieval armor.",
location: "Normandy, France",
image: "/artifact15.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact15.jpg",
price: 400,
},
{
@ -133,30 +178,30 @@ const artifacts: Artifact[] = [
name: "Medieval Helmet Fragment",
description: "A fragment of a medieval helmet.",
location: "Normandy, France",
image: "/artifact16.jpg",
earthquakeID: "h",
observatory: "jhd",
dateReleased: "12/02/2025",
image: "/artefact16.jpg",
price: 500,
},
];
export default function Shop() {
const [currentPage, setCurrentPage] = useState(1);
const [selectedArtifact, setSelectedArtifact] = useState<Artifact | null>(null);
const artifactsPerPage = 12;
const indexOfLastArtifact = currentPage * artifactsPerPage;
const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
const currentArtifacts = artifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
const [selectedArtefact, setSelectedArtefact] = useState<Artefact | null>(null);
const artefactsPerPage = 12;
const indexOfLastArtefact = currentPage * artefactsPerPage;
const indexOfFirstArtefact = indexOfLastArtefact - artefactsPerPage;
const currentArtefacts = artefacts.slice(indexOfFirstArtefact, indexOfLastArtefact);
const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency);
const conversionRates = useStoreState((state) => state.currency.conversionRates);
const currencyTickers = useStoreState((state) => state.currency.tickers);
const convertPrice = useCallback(
(price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2),
[]
);
const convertPrice = useCallback((price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2), []);
const handleNextPage = () => {
if (indexOfLastArtifact < artifacts.length) {
if (indexOfLastArtefact < artefacts.length) {
setCurrentPage((prev) => prev + 1);
}
};
@ -166,11 +211,11 @@ export default function Shop() {
}
};
function Modal({ artifact }: { artifact: Artifact }) {
if (!artifact) return null;
function Modal({ artefact }: { artefact: Artefact }) {
if (!artefact) return null;
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
if (e.target === e.currentTarget) {
setSelectedArtifact(null);
setSelectedArtefact(null);
}
};
return (
@ -179,20 +224,23 @@ export default function Shop() {
onClick={handleOverlayClick}
>
<div className="bg-white rounded-xl shadow-2xl max-w-lg w-full p-6">
<h3 className="text-2xl font-bold mb-4">{artifact.name}</h3>
<h3 className="text-2xl font-bold mb-4">{artefact.name}</h3>
<Image
height={5000}
width={5000}
src={artifact.image}
alt={artifact.name}
src={artefact.image}
alt={artefact.name}
className="w-full h-64 object-cover rounded-md"
/>
<p className="text-xl font-bold">
{currencyTickers[selectedCurrency]}
{convertPrice(artifact.price, selectedCurrency)}
{convertPrice(artefact.price, selectedCurrency)}
</p>
<p className="text-neutral-600 mt-2">{artifact.description}</p>
<p className="text-neutral-500 font-bold mt-1">Location: {artifact.location}</p>
<p className="text-neutral-600 mt-2">{artefact.description}</p>
<p className="text-neutral-500 font-bold mt-1">Location: {artefact.location}</p>
<p className="text-neutral-500 mb-2">{artefact.earthquakeID}</p>
<p className="text-neutral-500 mb-2">{artefact.observatory}</p>
<p className="text-neutral-500 mb-2">{artefact.dateReleased}</p>
<div className="flex justify-end gap-4 mt-4 mr-2">
<button
onClick={() => alert("Purchased Successfully!")}
@ -206,19 +254,20 @@ export default function Shop() {
);
}
function ArtifactCard({ artifact }: { artifact: Artifact }) {
function ArtefactCard({ artefact }: { artefact: Artefact }) {
return (
<div
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
onClick={() => setSelectedArtifact(artifact)}
onClick={() => setSelectedArtefact(artefact)}
>
<img src={artifact.image} alt={artifact.name} className="w-full h-56 object-cover" />
<img src={artefact.image} alt={artefact.name} className="w-full h-56 object-cover" />
<div className="p-4">
<h3 className="text-lg font-semibold">{artifact.name}</h3>
<p className="text-neutral-500 mb-2">{artifact.location}</p>
<h3 className="text-lg font-semibold">{artefact.name}</h3>
<p className="text-neutral-500 mb-2">{artefact.location}</p>
<p className="text-neutral-500 mb-2">{artefact.earthquakeID}</p>
<p className="text-black font-bold text-md mt-2">
{currencyTickers[selectedCurrency]}
{convertPrice(artifact.price, selectedCurrency)}
{convertPrice(artefact.price, selectedCurrency)}
</p>
</div>
</div>
@ -229,9 +278,9 @@ export default function Shop() {
<div
className="min-h-screen relative flex flex-col"
style={{
backgroundImage: "url('/artifacts.jpg')",
backgroundSize: 'cover',
backgroundPosition: 'center'
backgroundImage: "url('/artefacts.jpg')",
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
{/* Overlay */}
@ -239,16 +288,19 @@ export default function Shop() {
<div className="relative z-10 flex flex-col items-center w-full px-2 py-12">
{/* Title & Subheading */}
<h1 className="text-4xl md:text-4xl font-bold text-center text-white mb-2 tracking-tight drop-shadow-lg">
Artifact Shop
Artefact Shop
</h1>
<p className="text-lg md:text-xl text-center text-white mb-10 drop-shadow-md max-w-2xl">
Discover extraordinary historical artifacts and collectibles from major seismic events from around the world - now available for purchase.
Discover extraordinary historical artefacts and collectibles from major seismic events from around the world - now
available for purchase.
</p>
{/* Artifact Grid */}
<div className="w-full max-w-7xl grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 p-2"> {/* gap-10 for more spacing */}
{currentArtifacts.map((artifact) => (
<ArtifactCard key={artifact.id} artifact={artifact} />
{/* Artefact Grid */}
<div className="w-full max-w-7xl grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 p-2">
{" "}
{/* gap-10 for more spacing */}
{currentArtefacts.map((artefact) => (
<ArtefactCard key={artefact.id} artefact={artefact} />
))}
</div>
@ -266,9 +318,9 @@ export default function Shop() {
<p className="mx-3 text-lg font-bold">{currentPage}</p>
<button
onClick={handleNextPage}
disabled={indexOfLastArtifact >= artifacts.length}
disabled={indexOfLastArtefact >= artefacts.length}
className={`mx-2 px-4 py-1 bg-blue-500 text-white rounded-md font-bold shadow-md ${
indexOfLastArtifact >= artifacts.length ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
indexOfLastArtefact >= artefacts.length ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-600"
}`}
>
Next &rarr;
@ -277,7 +329,7 @@ export default function Shop() {
</div>
{/* Modal */}
{selectedArtifact && <Modal artifact={selectedArtifact} />}
{selectedArtefact && <Modal artefact={selectedArtefact} />}
</div>
);
}

View File

@ -1,12 +1,12 @@
"use client";
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
import { FaTimes } from 'react-icons/fa';
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from 'react-icons/fa6';
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from 'react-icons/io5';
import { Dispatch, SetStateAction, useMemo, useState } from "react";
import { FaTimes } from "react-icons/fa";
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from "react-icons/fa6";
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from "react-icons/io5";
// import type { Artifact } from "@prisma/client";
// import type { Artefact } from "@prisma/client";
interface Artifact {
interface Artefact {
id: number;
name: string;
description: string;
@ -18,8 +18,8 @@ interface Artifact {
dateAdded: string;
}
// Warehouse Artifacts Data
const warehouseArtifacts: Artifact[] = [
// Warehouse Artefacts Data
const warehouseArtefacts: Artefact[] = [
{
id: 1,
name: "Solidified Lava Chunk",
@ -140,14 +140,14 @@ function FilterInput({
}
// Table Component
function ArtifactTable({
artifacts,
function ArtefactTable({
artefacts,
filters,
setFilters,
setEditArtifact,
setEditArtefact,
clearSort,
}: {
artifacts: Artifact[];
artefacts: Artefact[];
filters: Record<string, string>;
setFilters: Dispatch<
SetStateAction<{
@ -162,15 +162,15 @@ function ArtifactTable({
dateAdded: string;
}>
>;
setEditArtifact: (artifact: Artifact) => void;
setEditArtefact: (artefact: Artefact) => void;
clearSort: () => void;
}) {
const [sortConfig, setSortConfig] = useState<{
key: keyof Artifact;
key: keyof Artefact;
direction: "asc" | "desc";
} | null>(null);
const handleSort = (key: keyof Artifact) => {
const handleSort = (key: keyof Artefact) => {
setSortConfig((prev) => {
if (!prev || prev.key !== key) {
return { key, direction: "asc" };
@ -186,9 +186,9 @@ function ArtifactTable({
clearSort();
};
const sortedArtifacts = useMemo(() => {
if (!sortConfig) return artifacts;
const sorted = [...artifacts].sort((a, b) => {
const sortedArtefacts = useMemo(() => {
if (!sortConfig) return artefacts;
const sorted = [...artefacts].sort((a, b) => {
const aValue = a[sortConfig.key];
const bValue = b[sortConfig.key];
if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1;
@ -196,9 +196,9 @@ function ArtifactTable({
return 0;
});
return sorted;
}, [artifacts, sortConfig]);
}, [artefacts, sortConfig]);
const columns: { label: string; key: keyof Artifact; width: string }[] = [
const columns: { label: string; key: keyof Artefact; width: string }[] = [
{ label: "ID", key: "id", width: "5%" },
{ label: "Name", key: "name", width: "12%" },
{ label: "Earthquake ID", key: "earthquakeId", width: "10%" },
@ -217,7 +217,7 @@ function ArtifactTable({
{columns.map(({ label, key, width }) => (
<th key={key} className="text-sm px-5 font-semibold text-neutral-800 cursor-pointer" style={{ width }}>
<div className="flex h-11 items-center">
<div className="flex h-full items-center" onClick={() => handleSort(key as keyof Artifact)}>
<div className="flex h-full items-center" onClick={() => handleSort(key as keyof Artefact)}>
<div className="select-none">{label}</div>
</div>
<div className="h-full relative">
@ -250,11 +250,11 @@ function ArtifactTable({
</tr>
</thead>
<tbody>
{sortedArtifacts.map((artifact) => (
{sortedArtefacts.map((artefact) => (
<tr
key={artifact.id}
key={artefact.id}
className="border-b border-neutral-200 hover:bg-neutral-100 cursor-pointer"
onClick={() => setEditArtifact(artifact)}
onClick={() => setEditArtefact(artefact)}
>
{columns.map(({ key, width }) => (
<td
@ -263,18 +263,18 @@ function ArtifactTable({
style={{ width }}
>
{key === "isRequired"
? artifact.isRequired
? artefact.isRequired
? "Yes"
: "No"
: key === "isSold"
? artifact.isSold
? artefact.isSold
? "Yes"
: "No"
: key === "isCollected"
? artifact.isCollected
? artefact.isCollected
? "Yes"
: "No"
: artifact[key]}
: artefact[key]}
</td>
))}
</tr>
@ -284,7 +284,7 @@ function ArtifactTable({
);
}
// Modal Component for Logging Artifact
// Modal Component for Logging Artefact
function LogModal({ onClose }: { onClose: () => void }) {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
@ -312,7 +312,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
alert(`Logged ${name} to storage: ${storageLocation}`);
onClose();
} catch {
setError("Failed to log artifact. Please try again.");
setError("Failed to log artefact. Please try again.");
} finally {
setIsSubmitting(false);
}
@ -324,7 +324,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
onClick={handleOverlayClick}
>
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6 border border-neutral-300">
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Log New Artifact</h3>
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Log New Artefact</h3>
{error && <p className="text-red-600 text-sm mb-2">{error}</p>}
<div className="space-y-2">
<input
@ -333,7 +333,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
aria-label="Artifact Name"
aria-label="Artefact Name"
disabled={isSubmitting}
/>
<textarea
@ -341,7 +341,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16"
aria-label="Artifact Description"
aria-label="Artefact Description"
disabled={isSubmitting}
/>
<input
@ -350,7 +350,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
value={location}
onChange={(e) => setLocation(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
aria-label="Artifact Location"
aria-label="Artefact Location"
disabled={isSubmitting}
/>
<input
@ -377,7 +377,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
checked={isRequired}
onChange={(e) => setIsRequired(e.target.checked)}
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
aria-label="Required Artifact"
aria-label="Required Artefact"
disabled={isSubmitting}
/>
<label className="text-sm text-neutral-600">Required (Not for Sale)</label>
@ -416,7 +416,7 @@ function LogModal({ onClose }: { onClose: () => void }) {
Logging...
</>
) : (
"Log Artifact"
"Log Artefact"
)}
</button>
</div>
@ -524,16 +524,16 @@ function BulkLogModal({ onClose }: { onClose: () => void }) {
);
}
// Modal Component for Editing Artifact
function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => void }) {
const [name, setName] = useState(artifact.name);
const [description, setDescription] = useState(artifact.description);
const [location, setLocation] = useState(artifact.location);
const [earthquakeId, setEarthquakeId] = useState(artifact.earthquakeId);
const [isRequired, setIsRequired] = useState(artifact.isRequired);
const [isSold, setIsSold] = useState(artifact.isSold);
const [isCollected, setIsCollected] = useState(artifact.isCollected);
const [dateAdded, setDateAdded] = useState(artifact.dateAdded);
// Modal Component for Editing Artefact
function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => void }) {
const [name, setName] = useState(artefact.name);
const [description, setDescription] = useState(artefact.description);
const [location, setLocation] = useState(artefact.location);
const [earthquakeId, setEarthquakeId] = useState(artefact.earthquakeId);
const [isRequired, setIsRequired] = useState(artefact.isRequired);
const [isSold, setIsSold] = useState(artefact.isSold);
const [isCollected, setIsCollected] = useState(artefact.isCollected);
const [dateAdded, setDateAdded] = useState(artefact.dateAdded);
const [error, setError] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
@ -551,10 +551,10 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => v
setIsSubmitting(true);
try {
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
alert(`Updated artifact ${name}`);
alert(`Updated artefact ${name}`);
onClose();
} catch {
setError("Failed to update artifact. Please try again.");
setError("Failed to update artefact. Please try again.");
} finally {
setIsSubmitting(false);
}
@ -566,7 +566,7 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => v
onClick={handleOverlayClick}
>
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6 border border-neutral-300">
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Edit Artifact</h3>
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Edit Artefact</h3>
{error && <p className="text-red-600 text-sm mb-2">{error}</p>}
<div className="space-y-2">
<input
@ -574,14 +574,14 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => v
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
aria-label="Artifact Name"
aria-label="Artefact Name"
disabled={isSubmitting}
/>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16"
aria-label="Artifact Description"
aria-label="Artefact Description"
disabled={isSubmitting}
/>
<input
@ -589,7 +589,7 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => v
value={location}
onChange={(e) => setLocation(e.target.value)}
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
aria-label="Artifact Location"
aria-label="Artefact Location"
disabled={isSubmitting}
/>
<input
@ -606,7 +606,7 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => v
checked={isRequired}
onChange={(e) => setIsRequired(e.target.checked)}
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
aria-label="Required Artifact"
aria-label="Required Artefact"
disabled={isSubmitting}
/>
<label className="text-sm text-neutral-600">Required (Not for Sale)</label>
@ -617,7 +617,7 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => v
checked={isSold}
onChange={(e) => setIsSold(e.target.checked)}
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
aria-label="Sold Artifact"
aria-label="Sold Artefact"
disabled={isSubmitting}
/>
<label className="text-sm text-neutral-600">Sold</label>
@ -628,7 +628,7 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => v
checked={isCollected}
onChange={(e) => setIsCollected(e.target.checked)}
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
aria-label="Collected Artifact"
aria-label="Collected Artefact"
disabled={isSubmitting}
/>
<label className="text-sm text-neutral-600">Collected</label>
@ -685,18 +685,18 @@ function EditModal({ artifact, onClose }: { artifact: Artifact; onClose: () => v
}
// Filter Logic
const applyFilters = (artifacts: Artifact[], filters: Record<string, string>): Artifact[] => {
return artifacts.filter((artifact) => {
const applyFilters = (artefacts: Artefact[], filters: Record<string, string>): Artefact[] => {
return artefacts.filter((artefact) => {
return (
(filters.id === "" || artifact.id.toString().includes(filters.id)) &&
(filters.name === "" || artifact.name.toLowerCase().includes(filters.name.toLowerCase())) &&
(filters.earthquakeId === "" || artifact.earthquakeId.toLowerCase().includes(filters.earthquakeId.toLowerCase())) &&
(filters.location === "" || artifact.location.toLowerCase().includes(filters.location.toLowerCase())) &&
(filters.description === "" || artifact.description.toLowerCase().includes(filters.description.toLowerCase())) &&
(filters.isRequired === "" || (filters.isRequired === "true" ? artifact.isRequired : !artifact.isRequired)) &&
(filters.isSold === "" || (filters.isSold === "true" ? artifact.isSold : !artifact.isSold)) &&
(filters.isCollected === "" || (filters.isCollected === "true" ? artifact.isCollected : !artifact.isCollected)) &&
(filters.dateAdded === "" || artifact.dateAdded === filters.dateAdded)
(filters.id === "" || artefact.id.toString().includes(filters.id)) &&
(filters.name === "" || artefact.name.toLowerCase().includes(filters.name.toLowerCase())) &&
(filters.earthquakeId === "" || artefact.earthquakeId.toLowerCase().includes(filters.earthquakeId.toLowerCase())) &&
(filters.location === "" || artefact.location.toLowerCase().includes(filters.location.toLowerCase())) &&
(filters.description === "" || artefact.description.toLowerCase().includes(filters.description.toLowerCase())) &&
(filters.isRequired === "" || (filters.isRequired === "true" ? artefact.isRequired : !artefact.isRequired)) &&
(filters.isSold === "" || (filters.isSold === "true" ? artefact.isSold : !artefact.isSold)) &&
(filters.isCollected === "" || (filters.isCollected === "true" ? artefact.isCollected : !artefact.isCollected)) &&
(filters.dateAdded === "" || artefact.dateAdded === filters.dateAdded)
);
});
};
@ -706,7 +706,7 @@ export default function Warehouse() {
const [currentPage, setCurrentPage] = useState(1);
const [showLogModal, setShowLogModal] = useState(false);
const [showBulkLogModal, setShowBulkLogModal] = useState(false);
const [editArtifact, setEditArtifact] = useState<Artifact | null>(null);
const [editArtefact, setEditArtefact] = useState<Artefact | null>(null);
const [filters, setFilters] = useState({
id: "",
name: "",
@ -720,29 +720,29 @@ export default function Warehouse() {
});
const [isFiltering, setIsFiltering] = useState(false);
const [sortConfig, setSortConfig] = useState<{
key: keyof Artifact;
key: keyof Artefact;
direction: "asc" | "desc";
} | null>(null);
const artifactsPerPage = 10;
const indexOfLastArtifact = currentPage * artifactsPerPage;
const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
const artefactsPerPage = 10;
const indexOfLastArtefact = currentPage * artefactsPerPage;
const indexOfFirstArtefact = indexOfLastArtefact - artefactsPerPage;
// Apply filters with loading state
const filteredArtifacts = useMemo(() => {
const filteredArtefacts = useMemo(() => {
setIsFiltering(true);
const result = applyFilters(warehouseArtifacts, filters);
const result = applyFilters(warehouseArtefacts, filters);
setIsFiltering(false);
return result;
}, [filters]);
const currentArtifacts = filteredArtifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
const currentArtefacts = filteredArtefacts.slice(indexOfFirstArtefact, indexOfLastArtefact);
// Overview stats
const totalArtifacts = warehouseArtifacts.length;
const totalArtefacts = warehouseArtefacts.length;
const today = "2025-05-04";
const artifactsAddedToday = warehouseArtifacts.filter((a) => a.dateAdded === today).length;
const artifactsSoldToday = warehouseArtifacts.filter((a) => a.isSold && a.dateAdded === today).length;
const artefactsAddedToday = warehouseArtefacts.filter((a) => a.dateAdded === today).length;
const artefactsSoldToday = warehouseArtefacts.filter((a) => a.isSold && a.dateAdded === today).length;
const clearFilters = () => {
setFilters({
@ -768,15 +768,15 @@ export default function Warehouse() {
<div className="flex gap-8 ml-5 mt-1">
<div className="flex items-center text-md text-neutral-600">
<FaWarehouse className="mr-2 h-8 w-8 text-blue-600 opacity-90" />
Total Artifacts: <span className="font-semibold ml-1">{totalArtifacts}</span>
Total Artefacts: <span className="font-semibold ml-1">{totalArtefacts}</span>
</div>
<div className="flex items-center text-md text-neutral-600">
<IoToday className="mr-2 h-8 w-8 text-blue-600 opacity-90" />
Added Today: <span className="font-semibold ml-1">{artifactsAddedToday}</span>
Added Today: <span className="font-semibold ml-1">{artefactsAddedToday}</span>
</div>
<div className="flex items-center text-md text-neutral-600">
<FaCartShopping className="mr-2 h-8 w-8 text-blue-600 opacity-90" />
Sold Today: <span className="font-semibold ml-1">{artifactsSoldToday}</span>
Sold Today: <span className="font-semibold ml-1">{artefactsSoldToday}</span>
</div>
</div>
@ -792,7 +792,7 @@ export default function Warehouse() {
onClick={() => setShowLogModal(true)}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium"
>
Log Single Artifact
Log Single Artefact
</button>
<button
onClick={() => setShowBulkLogModal(true)}
@ -822,11 +822,11 @@ export default function Warehouse() {
</div>
)}
<div className="h-full overflow-y-none">
<ArtifactTable
artifacts={currentArtifacts}
<ArtefactTable
artefacts={currentArtefacts}
filters={filters}
setFilters={setFilters}
setEditArtifact={setEditArtifact}
setEditArtefact={setEditArtefact}
clearSort={() => setSortConfig(null)}
/>
</div>
@ -837,7 +837,7 @@ export default function Warehouse() {
{/* Modals */}
{showLogModal && <LogModal onClose={() => setShowLogModal(false)} />}
{showBulkLogModal && <BulkLogModal onClose={() => setShowBulkLogModal(false)} />}
{editArtifact && <EditModal artifact={editArtifact} onClose={() => setEditArtifact(null)} />}
{editArtefact && <EditModal artefact={editArtefact} onClose={() => setEditArtefact(null)} />}
</div>
);
}

View File

@ -0,0 +1 @@
Artefacts
1 Artefacts

View File

@ -1 +0,0 @@
Artifacts
1 Artifacts

View File

@ -1,11 +1,14 @@
interface Artifact {
interface Artefact {
// todo change to string
id: number;
name: string;
description: string;
location: string;
earthquakeID: string;
observatory: string;
dateReleased: string;
image: string;
price: number;
}
export default Artifact;
export default Artefact;

View File

@ -1,4 +1,4 @@
import { Action } from 'easy-peasy';
import { Action } from "easy-peasy";
// import type { User } from "@prisma/client";
@ -14,10 +14,10 @@ interface Scientist {
subordinates: Scientist[];
// earthquakes: Earthquake[];
// observatories: Observatory[];
artifacts: Artifact[];
artefacts: Artefact[];
}
interface Artifact {
interface Artefact {
id: number;
name: string;
description: string;
@ -37,7 +37,7 @@ interface User {
passwordHash: string;
role: string;
scientist: Scientist | undefined;
purchasedArtifacts: Artifact[];
purchasedArtefacts: Artefact[];
}
type Currency = "GBP" | "USD" | "EUR";