Compare commits
No commits in common. "3b2927e896222449a55b0a0cc325cfac29b67b10" and "31a0c622d5fa41e3dcb87bba6ce591139422b592" have entirely different histories.
3b2927e896
...
31a0c622d5
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
node_modules
|
||||
.next
|
||||
generated
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -55,7 +55,6 @@
|
||||
},
|
||||
"importSorter.generalConfiguration.sortOnBeforeSave": true,
|
||||
"cSpell.words": [
|
||||
"prismaclient",
|
||||
"vars"
|
||||
]
|
||||
}
|
||||
|
||||
1140
package-lock.json
generated
1140
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@types/bcryptjs": "^5.0.2",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
// Datasource configuration
|
||||
datasource db {
|
||||
provider = "sqlserver"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../src/generated/prisma/client"
|
||||
}
|
||||
|
||||
// User model
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
@ -15,19 +11,9 @@ model User {
|
||||
name String
|
||||
email String @unique
|
||||
passwordHash String
|
||||
role String @default("GUEST") @db.VarChar(10) // Allowed: ADMIN, SCIENTIST, GUEST
|
||||
role String @default("GUEST") @db.VarChar(10) // ADMIN, SCIENTIST, GUEST
|
||||
scientist Scientist? @relation
|
||||
purchasedArtefacts Artefact[] @relation("UserPurchasedArtefacts")
|
||||
requests Request[] @relation("UserRequests")
|
||||
}
|
||||
|
||||
model Request {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
requestType String @db.VarChar(20) // Allowed: NEW_USER, CHANGE_LEVEL, DELETE
|
||||
requestingUser User @relation("UserRequests", fields: [requestingUserId], references: [id])
|
||||
requestingUserId Int
|
||||
outcome String @default("IN_PROGRESS") @db.VarChar(20) // Allowed: FULFILLED, REJECTED, IN_PROGRESS, CANCELLED, OTHER
|
||||
}
|
||||
|
||||
// Scientist model
|
||||
@ -46,27 +32,24 @@ model Scientist {
|
||||
artefacts Artefact[] @relation("ScientistArtefactCreator")
|
||||
}
|
||||
|
||||
// Earthquake model
|
||||
model Earthquake {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
date DateTime
|
||||
code String @unique
|
||||
magnitude Float
|
||||
type String // e.g. 'volcanic'
|
||||
latitude Float
|
||||
longitude Float
|
||||
location String
|
||||
depth String
|
||||
|
||||
latitude String
|
||||
longitude String
|
||||
magnitude Float
|
||||
depth Float
|
||||
creatorId Int?
|
||||
creator Scientist? @relation("ScientistEarthquakeCreator", fields: [creatorId], references: [id])
|
||||
|
||||
creator Scientist? @relation("ScientistEarthquakeCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
artefacts Artefact[]
|
||||
observatories Observatory[] @relation("EarthquakeObservatory")
|
||||
}
|
||||
|
||||
// Observatory model
|
||||
model Observatory {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
@ -76,38 +59,27 @@ model Observatory {
|
||||
longitude String
|
||||
latitude String
|
||||
dateEstablished Int?
|
||||
isFunctional Boolean
|
||||
functional Boolean
|
||||
seismicSensorOnline Boolean @default(true)
|
||||
creatorId Int?
|
||||
creator Scientist? @relation("ScientistObservatoryCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
earthquakes Earthquake[] @relation("EarthquakeObservatory")
|
||||
}
|
||||
|
||||
// Artefact model
|
||||
model Artefact {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
name String
|
||||
type String @db.VarChar(50) // Lava, Tephra, Ash, Soil
|
||||
warehouseArea String
|
||||
description String
|
||||
warehouseArea String // Examples: "ZoneA-Shelf1", "ZoneB-Rack2", "ZoneC-Bin3"
|
||||
earthquakeId Int
|
||||
earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
|
||||
creatorId Int?
|
||||
creator Scientist? @relation("ScientistArtefactCreator", fields: [creatorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
isRequired Boolean @default(true)
|
||||
dateAddedToShop DateTime?
|
||||
shopPrice Float?
|
||||
isSold Boolean @default(false)
|
||||
required Boolean @default(true)
|
||||
shopPrice Float? // In Euros
|
||||
purchasedById Int?
|
||||
purchasedBy User? @relation("UserPurchasedArtefacts", fields: [purchasedById], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||
isCollected Boolean @default(false)
|
||||
}
|
||||
|
||||
model Pallet {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
warehouseArea String
|
||||
palletNote String
|
||||
pickedUp Boolean @default(false)
|
||||
}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
Name,Type,WarehouseArea,Description,earthquakeID,Price,Required,PickedUp,Picture
|
||||
Echo Bomb,Lava,ShelvingAreaA,A dense glossy black volcanic bomb with minor vesicles.,EV-7.4-Mexico-00035,120,no,no,EchoBomb.PNG
|
||||
Silvershade Ash,Ash,ShelvingAreaD,Fine light-grey volcanic ash collected near a village.,EV-6.0-Iceland-00018,40,no,no,SilvershadeAsh.PNG
|
||||
Strata Core,Soil,LoggingArea,Soil core with visible stratification showing evidence of liquefaction.,ET-6.9-Brazil-00046,30,no,no,StrataCore.PNG
|
||||
Opal Clast,Tephra,ShelvingAreaM,Pumice clast with sharp edges and clear layering.,EV-8.3-Iceland-00127,65,no,no,OpalClast.PNG
|
||||
Magnetite Ash,Ash,PalletDeliveryArea,Bagged black ash sample with high magnetic content.,EV-5.3-Myanmar-00088,28,yes,no,MagnetiteAsh.PNG
|
||||
Ropeform Fragment,Lava,ShelvingAreaR,Ropey pahoehoe lava fragment with preserved ripples.,EV-6.9-Ethiopia-00012,85,no,no,NoImageFound.PNG
|
||||
Glassy Bed,Soil,ShelvingAreaB,Sandy soil deposit with glassy volcanic spherules visible under microscope.,EV-7.8-USA-00167,48,no,no,NoImageFound.PNG
|
||||
Groundwater Shard,Tephra,ShelvingAreaJ,Layered tephra shard showing interaction with groundwater.,EV-4.9-Chile-00083,49,no,no,NoImageFound.PNG
|
||||
Sulfur Ghost,Ash,ShelvingAreaG,Grey volcanic ash particle with very high sulfur content.,EV-6.4-Armenia-00219,38,no,no,NoImageFound.PNG
|
||||
Obsidian Pillar,Lava,ShelvingAreaC,Dense basalt sample with well-developed columnar structure.,EV-7.7-Jordan-00037,100,yes,no,NoImageFound.PNG
|
||||
Peat Nest,Soil,ShelvingAreaP,Peaty soil from earthquake liquefaction area with organic inclusions.,ET-5.1-New Zealand-00032,52,no,no,NoImageFound.PNG
|
||||
Biotite Veil,Ash,PalletDeliveryArea,Ash with visible biotite flakes: lightly compacted.,EV-3.1-USA-00101,32,no,no,NoImageFound.PNG
|
||||
Foamcore Pumice,Lava,ShelvingAreaF,High-porosity pumice formed during an explosive event.,EV-8.4-Barbados-00071,77,no,no,NoImageFound.PNG
|
||||
Redslide Soil,Soil,ShelvingAreaQ,Aggregated red clay soil collected from a collapsed hillside.,EC-7.5-Nicaragua-00078,26,no,no,NoImageFound.PNG
|
||||
Spectrum Clast,Tephra,LoggingArea,Multi-coloured tephra clast showing mixed eruption sources.,EV-8.3-Antarctica-00030,54,no,no,NoImageFound.PNG
|
||||
Olivine Stone,Lava,ShelvingAreaH,Dense polished lava with visible olivine inclusions.,EV-9.0-Russia-00171,132,no,no,NoImageFound.PNG
|
||||
Blended Bar,Soil,ShelvingAreaN,Sandy soil deeply mixed with volcanic tephra fragments.,EV-6.4-Panama-00197,45,no,no,NoImageFound.PNG
|
||||
Potash Powder,Ash,ShelvingAreaX,Very fine mantle ash with high potassium content: glassy texture.,EV-4.4-Ecuador-00253,56,no,no,NoImageFound.PNG
|
||||
Shatterrock,Lava,ShelvingAreaS,Massive hand specimen of aa lava showing sharp vesicular cavities.,EV-7.4-South Africa-00228,117,no,no,NoImageFound.PNG
|
||||
Humic Loam,Soil,PalletDeliveryArea,Dark humic soil showing traces of ash fallout.,EV-3.2-Argentina-00175,20,no,no,NoImageFound.PNG
|
||||
Charcoal Drift,Ash,ShelvingAreaK,Coarse ash mixed with fragmented charcoal.,EV-8.6-Myanmar-00056,47,yes,no,NoImageFound.PNG
|
||||
Spindle Bomb,Lava,ShelvingAreaU,Spindle-shaped lava bomb: partially vitrified.,EV-8.9-Japan-00076,84,no,no,NoImageFound.PNG
|
||||
Bubblewall Tephra,Tephra,ShelvingAreaV,Vitric tephra particle with bubble wall shards: transparent edges.,EV-7.5-Greece-00248,88,no,no,NoImageFound.PNG
|
||||
Layercake Earth,Soil,ShelvingAreaZ,Compacted soil with tephra lenses: showing sediment mixing.,EV-6.0-Guatemala-00165,39,no,no,NoImageFound.PNG
|
||||
Creamdust Sample,Ash,ShelvingAreaW,Bagged cream-coloured ash collected from roof dusting incident.,EV-4.1-Maldives-00185,25,no,no,NoImageFound.PNG
|
||||
Faultblock Tephra,Tephra,ShelvingAreaL,Blocky tephra fragment: impacted during ground rupture.,EV-5.6-Antarctica-00251,51,no,no,NoImageFound.PNG
|
||||
Basalt Boulder,Lava,LoggingArea,Weathered basalt boulder from lava flow front.,EV-5.4-Spain-00129,69,no,no,NoImageFound.PNG
|
||||
Glassloam,Soil,ShelvingAreaT,Loam with embedded tephric glass and altered feldspar.,EV-3.7-Argentina-00208,30,no,no,NoImageFound.PNG
|
||||
Nanoshade Ash,Ash,ShelvingAreaO,Ash sample with nano-crystalline silica.,EV-7.4-Mauritius-00183,42,no,no,NoImageFound.PNG
|
||||
Ironflame Scoria,Lava,ShelvingAreaE,Vesicular scoria: deep red: indicative of high iron content.,EV-3.4-Indonesia-00138,44,no,no,NoImageFound.PNG
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 8.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
File diff suppressed because it is too large
Load Diff
@ -1,34 +1,27 @@
|
||||
"use client";
|
||||
import React, { useState, useRef } from "react";
|
||||
type Role = "ADMIN" | "GUEST" | "SCIENTIST";
|
||||
const roleLabels: Record<Role, string> = {
|
||||
ADMIN: "Admin",
|
||||
GUEST: "Guest",
|
||||
SCIENTIST: "Scientist",
|
||||
};
|
||||
type Role = "admin" | "user" | "editor";
|
||||
type User = {
|
||||
id: number;
|
||||
email: string;
|
||||
name: string;
|
||||
role: Role;
|
||||
password: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
const initialUsers: User[] = [
|
||||
{ email: "john@example.com", name: "John Doe", role: "ADMIN", password: "secret1", createdAt: "2024-06-21T09:15:01Z" ,id:1},
|
||||
{ email: "jane@example.com", name: "Jane Smith", role: "GUEST", password: "secret2", createdAt: "2024-06-21T10:01:09Z" ,id:2},
|
||||
{ email: "bob@example.com", name: "Bob Brown", role: "SCIENTIST", password: "secret3", createdAt: "2024-06-21T12:13:45Z" ,id:3},
|
||||
{ email: "alice@example.com", name: "Alice Johnson",role: "GUEST", password: "secret4", createdAt: "2024-06-20T18:43:20Z" ,id:4},
|
||||
{ email: "eve@example.com", name: "Eve Black", role: "ADMIN", password: "secret5", createdAt: "2024-06-20T19:37:10Z" ,id:5},
|
||||
{ email: "dave@example.com", name: "Dave Clark", role: "GUEST", password: "pw", createdAt: "2024-06-19T08:39:10Z" ,id:6},
|
||||
{ email: "fred@example.com", name: "Fred Fox", role: "GUEST", password: "pw", createdAt: "2024-06-19T09:11:52Z" ,id:7},
|
||||
{ email: "ginny@example.com", name: "Ginny Hall", role: "SCIENTIST", password: "pw", createdAt: "2024-06-17T14:56:27Z" ,id:8},
|
||||
{ email: "harry@example.com", name: "Harry Lee", role: "ADMIN", password: "pw", createdAt: "2024-06-16T19:28:11Z" ,id:9},
|
||||
{ email: "ivy@example.com", name: "Ivy Volt", role: "ADMIN", password: "pw", createdAt: "2024-06-15T21:04:05Z" ,id:10},
|
||||
{ email: "kate@example.com", name: "Kate Moss", role: "SCIENTIST", password: "pw", createdAt: "2024-06-14T11:16:35Z" ,id:11},
|
||||
{ email: "leo@example.com", name: "Leo Garrison", role: "GUEST", password: "pw", createdAt: "2024-06-12T08:02:51Z" ,id:12},
|
||||
{ email: "isaac@example.com", name: "Isaac Yang", role: "GUEST", password: "pw", createdAt: "2024-06-12T15:43:29Z" ,id:13},
|
||||
const initialUsers: User[] = [ // todo - add user reading function
|
||||
{ email: "john@example.com", name: "John Doe", role: "admin", password: "secret1" },
|
||||
{ email: "jane@example.com", name: "Jane Smith", role: "user", password: "secret2" },
|
||||
{ email: "bob@example.com", name: "Bob Brown", role: "editor", password: "secret3" },
|
||||
{ email: "alice@example.com", name: "Alice Johnson", role: "user", password: "secret4" },
|
||||
{ email: "eve@example.com", name: "Eve Black", role: "admin", password: "secret5" },
|
||||
{ email: "dave@example.com", name: "Dave Clark", role: "user", password: "pw" },
|
||||
{ email: "fred@example.com", name: "Fred Fox", role: "user", password: "pw" },
|
||||
{ email: "ginny@example.com", name: "Ginny Hall", role: "editor", password: "pw" },
|
||||
{ email: "harry@example.com", name: "Harry Lee", role: "admin", password: "pw" },
|
||||
{ email: "ivy@example.com", name: "Ivy Volt", role: "admin", password: "pw" },
|
||||
{ email: "kate@example.com", name: "Kate Moss", role: "editor", password: "pw" },
|
||||
{ email: "leo@example.com", name: "Leo Garrison", role: "user", password: "pw" },
|
||||
{ email: "isaac@example.com", name: "Isaac Yang", role: "user", password: "pw" },
|
||||
];
|
||||
const sortFields = [ // Sort box options
|
||||
{ label: "Name", value: "name" },
|
||||
@ -44,7 +37,7 @@ export default function AdminPage() {
|
||||
const [users, setUsers] = useState<User[]>(initialUsers);
|
||||
const [selectedEmail, setSelectedEmail] = useState<string | null>(null);
|
||||
|
||||
// Local edit state for SCIENTIST form
|
||||
// Local edit state for editor form
|
||||
const [editUser, setEditUser] = useState<User | null>(null);
|
||||
// Reset editUser when the selected user changes
|
||||
React.useEffect(() => {
|
||||
@ -135,7 +128,7 @@ export default function AdminPage() {
|
||||
setEditUser(null);
|
||||
};
|
||||
|
||||
const allRoles: Role[] = ["ADMIN", "GUEST", "SCIENTIST"];
|
||||
const allRoles: Role[] = ["admin", "user", "editor"];
|
||||
|
||||
// Tooltip handling for email field
|
||||
const [showEmailTooltip, setShowEmailTooltip] = useState(false);
|
||||
@ -154,15 +147,14 @@ export default function AdminPage() {
|
||||
value={searchText}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="border rounded-lg px-2 py-1 text-sm bg-white hover:bg-neutral-100 transition font-semibold"
|
||||
style={{ width: "80px" }} // fixed width, adjust as needed
|
||||
onClick={() => setSearchField(field => field === "name" ? "email" : "name")}
|
||||
title={`Switch to searching by ${searchField === "name" ? "Email" : "Name"}`}
|
||||
<select
|
||||
value={searchField}
|
||||
onChange={e => setSearchField(e.target.value as "name" | "email")}
|
||||
className="border rounded-lg px-2 py-1 text-sm"
|
||||
>
|
||||
{searchField === "name" ? "Email" : "Name"}
|
||||
</button>
|
||||
<option value="name">Name</option>
|
||||
<option value="email">Email</option>
|
||||
</select>
|
||||
</div>
|
||||
{/* Filter and Sort Buttons */}
|
||||
<div className="flex gap-2 items-center mb-2">
|
||||
@ -195,7 +187,7 @@ export default function AdminPage() {
|
||||
className={`w-full text-left px-3 py-1 hover:bg-blue-50 border-b border-gray-100 last:border-0
|
||||
${roleFilter===role ? "font-bold text-blue-600" : ""}`}
|
||||
>
|
||||
{roleLabels[role]}
|
||||
{role}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@ -254,7 +246,7 @@ export default function AdminPage() {
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium truncate">{user.name}</span>
|
||||
<span className="ml-1 text-xs px-2 py-0.5 rounded-lg bg-gray-200 text-gray-700">{roleLabels[user.role]}</span>
|
||||
<span className="ml-1 text-xs px-2 py-0.5 rounded-lg bg-gray-200 text-gray-700">{user.role}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-0.5">
|
||||
<span className="text-xs text-gray-600 truncate">{user.email}</span>
|
||||
@ -273,14 +265,6 @@ export default function AdminPage() {
|
||||
<div className="max-w-lg mx-auto bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-lg font-bold mb-6">Edit User</h2>
|
||||
<form className="space-y-4" onSubmit={handleUpdate}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<label className="text-sm font-medium text-gray-700">Account Creation Time:</label>
|
||||
<span className="text-sm text-gray-500">{editUser.createdAt}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<label className="text-sm font-medium text-gray-700">Account ID Number:</label>
|
||||
<span className="text-sm text-gray-500">{editUser.id}</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email (unique):
|
||||
@ -325,7 +309,7 @@ export default function AdminPage() {
|
||||
onChange={handleEditChange}
|
||||
>
|
||||
{allRoles.map((role) => (
|
||||
<option key={role} value={role}>{roleLabels[role]}</option>
|
||||
<option key={role} value={role}>{role}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const usingPrisma = false;
|
||||
let prisma: PrismaClient;
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
// CSV location
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/artefacts.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CsvRow = {
|
||||
Type: string;
|
||||
Name: string;
|
||||
Description: string;
|
||||
WarehouseArea: string;
|
||||
EarthquakeId: string;
|
||||
Required?: string;
|
||||
ShopPrice?: string;
|
||||
PickedUp?: string;
|
||||
};
|
||||
|
||||
function stringToBool(val: string | undefined, defaultValue: boolean = false): boolean {
|
||||
if (!val) return defaultValue;
|
||||
return /^true$/i.test(val.trim());
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Read file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
// 3. Map records to artefact input
|
||||
const artefacts = records.map((row) => ({
|
||||
name: row.Name,
|
||||
description: row.Description,
|
||||
type: row.Type,
|
||||
warehouseArea: row.WarehouseArea,
|
||||
// todo get earthquakeId where code === row.EarthquakeCode
|
||||
earthquakeId: parseInt(row.EarthquakeId, 10),
|
||||
required: stringToBool(row.Required, true), // default TRUE
|
||||
shopPrice: row.ShopPrice && row.ShopPrice !== "" ? parseFloat(row.ShopPrice) : null,
|
||||
pickedUp: stringToBool(row.PickedUp, false), // default FALSE
|
||||
// todo add random selection for creatorId
|
||||
creatorId: null,
|
||||
purchasedById: null,
|
||||
}));
|
||||
|
||||
// 4. Bulk insert
|
||||
await prisma.artefact.createMany({
|
||||
data: artefacts,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: artefacts.length,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,18 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { parse } from "csv-parse/sync";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
// Path to your earthquakes.csv
|
||||
// Define the path to your CSV file.
|
||||
// Place your earthquakes.csv in your project root or `public` directory
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/earthquakes.csv");
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CsvRow = {
|
||||
Date: string;
|
||||
Code: string;
|
||||
Magnitude: string;
|
||||
Type: string;
|
||||
Latitude: string;
|
||||
Longitude: string;
|
||||
Location: string;
|
||||
@ -24,28 +23,36 @@ export async function POST() {
|
||||
try {
|
||||
// 1. Read the CSV file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse the CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
skip_empty_lines: true
|
||||
});
|
||||
// 3. Transform to fit Earthquake model
|
||||
const earthquakes = records.map((row) => ({
|
||||
|
||||
// 3. Transform each CSV row to Earthquake model
|
||||
// Since your prisma model expects: name, date (DateTime), location, magnitude (float), depth (float). We'll fill casualties/creatorId as zero/null for now.
|
||||
const earthquakes = records.map(row => {
|
||||
// You may want to add better parsing & validation depending on your actual data
|
||||
return {
|
||||
date: new Date(row.Date),
|
||||
code: row.Code,
|
||||
magnitude: parseFloat(row.Magnitude),
|
||||
type: row.Type,
|
||||
latitude: parseFloat(row.Latitude),
|
||||
longitude: parseFloat(row.Longitude),
|
||||
location: row.Location,
|
||||
depth: row.Depth, // store as received
|
||||
// todo add random selection for creatorId
|
||||
creatorId: null,
|
||||
}));
|
||||
magnitude: parseFloat(row.Magnitude),
|
||||
latitude: row.Latitude,
|
||||
longitude: row.Longitude,
|
||||
depth: parseFloat(row.Depth.replace(" km", "")),
|
||||
// todo add creatorId
|
||||
creatorId: null
|
||||
};
|
||||
});
|
||||
|
||||
// 4. Bulk create earthquakes in database:
|
||||
// Consider chunking if your CSV is large!
|
||||
await prisma.earthquake.createMany({
|
||||
data: earthquakes,
|
||||
skipDuplicates: true, // in case the route is called twice
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, count: earthquakes.length });
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
// CSV location (update filename as needed)
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/observatories.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CsvRow = {
|
||||
Name: string;
|
||||
Location: string;
|
||||
Latitude: string;
|
||||
Longitude: string;
|
||||
DateEstablished?: string;
|
||||
Functional: string;
|
||||
SeismicSensorOnline?: string;
|
||||
};
|
||||
|
||||
function stringToBool(val: string | undefined): boolean {
|
||||
// Accepts "TRUE", "true", "True", etc.
|
||||
if (!val) return false;
|
||||
return /^true$/i.test(val.trim());
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Read file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
// 3. Map records to Prisma inputs
|
||||
const observatories = records.map((row) => ({
|
||||
name: row.Name,
|
||||
location: row.Location,
|
||||
latitude: row.Latitude,
|
||||
longitude: row.Longitude,
|
||||
dateEstablished: row.DateEstablished ? parseInt(row.DateEstablished, 10) : null,
|
||||
functional: stringToBool(row.Functional),
|
||||
seismicSensorOnline: row.SeismicSensorOnline ? stringToBool(row.SeismicSensorOnline) : true, // default true per schema
|
||||
// todo add random selection of creatorId
|
||||
creatorId: null,
|
||||
}));
|
||||
|
||||
// 4. Bulk insert
|
||||
await prisma.observatory.createMany({
|
||||
data: observatories,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: observatories.length,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/requests.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type RequestType = "NEW_USER" | "CHANGE_LEVEL" | "DELETE";
|
||||
type RequestOutcome = "FULFILLED" | "REJECTED" | "IN_PROGRESS" | "CANCELLED" | "OTHER";
|
||||
|
||||
type CsvRow = {
|
||||
RequestType: string;
|
||||
RequestingUserId: string;
|
||||
Outcome?: string;
|
||||
};
|
||||
|
||||
const validRequestTypes: RequestType[] = ["NEW_USER", "CHANGE_LEVEL", "DELETE"];
|
||||
const validOutcomes: RequestOutcome[] = ["FULFILLED", "REJECTED", "IN_PROGRESS", "CANCELLED", "OTHER"];
|
||||
|
||||
function normalizeRequestType(type: string | undefined): RequestType {
|
||||
if (!type) return "NEW_USER";
|
||||
const norm = type.trim().toUpperCase().replace(" ", "_");
|
||||
return (validRequestTypes.includes(norm as RequestType) ? norm : "NEW_USER") as RequestType;
|
||||
}
|
||||
|
||||
function normalizeOutcome(outcome: string | undefined): RequestOutcome {
|
||||
if (!outcome) return "IN_PROGRESS";
|
||||
const norm = outcome.trim().toUpperCase().replace(" ", "_");
|
||||
return (validOutcomes.includes(norm as RequestOutcome) ? norm : "IN_PROGRESS") as RequestOutcome;
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
const requests = records.map((row) => ({
|
||||
requestType: normalizeRequestType(row.RequestType),
|
||||
requestingUserId: parseInt(row.RequestingUserId, 10),
|
||||
outcome: normalizeOutcome(row.Outcome),
|
||||
}));
|
||||
|
||||
const filteredRequests = requests.filter((r) => !isNaN(r.requestingUserId));
|
||||
|
||||
await prisma.request.createMany({
|
||||
data: filteredRequests,
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, count: filteredRequests.length });
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
// Path to CSV file
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/scientists.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CsvRow = {
|
||||
Name: string;
|
||||
Level?: string;
|
||||
UserId: string;
|
||||
SuperiorId?: string;
|
||||
};
|
||||
|
||||
function normalizeLevel(level: string | undefined): string {
|
||||
// Only allow JUNIOR, SENIOR; default JUNIOR
|
||||
if (!level || !level.trim()) return "JUNIOR";
|
||||
const lv = level.trim().toUpperCase();
|
||||
return ["JUNIOR", "SENIOR"].includes(lv) ? lv : "JUNIOR";
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Read the CSV file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse the CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
// 3. Transform each record for Prisma
|
||||
// todo add senior scientists first
|
||||
const scientists = records.map((row) => ({
|
||||
name: row.Name,
|
||||
level: normalizeLevel(row.Level),
|
||||
userId: parseInt(row.UserId, 10),
|
||||
// todo get superior id by name from db
|
||||
superiorId: row.SuperiorId && row.SuperiorId.trim() !== "" ? parseInt(row.SuperiorId, 10) : null,
|
||||
}));
|
||||
|
||||
// 4. Bulk create scientists in database
|
||||
await prisma.scientist.createMany({
|
||||
data: scientists,
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, count: scientists.length });
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
// Path to users.csv - adjust as needed
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/users.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CsvRow = {
|
||||
Name: string;
|
||||
Email: string;
|
||||
PasswordHash: string;
|
||||
Role?: string;
|
||||
};
|
||||
|
||||
function normalizeRole(role: string | undefined): string {
|
||||
// Only allow ADMIN, SCIENTIST, GUEST; default GUEST
|
||||
if (!role || !role.trim()) return "GUEST";
|
||||
const r = role.trim().toUpperCase();
|
||||
return ["ADMIN", "SCIENTIST", "GUEST"].includes(r) ? r : "GUEST";
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Read the CSV file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse the CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
// 3. Transform each CSV row to User model format
|
||||
const users = records.map((row) => ({
|
||||
name: row.Name,
|
||||
email: row.Email,
|
||||
passwordHash: row.PasswordHash,
|
||||
role: normalizeRole(row.Role),
|
||||
}));
|
||||
|
||||
// 4. Bulk create users in database
|
||||
await prisma.user.createMany({
|
||||
data: users,
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, count: users.length });
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
@ -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 "@prismaclient";
|
||||
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;
|
||||
@ -20,16 +20,23 @@ export async function POST(req: Request) {
|
||||
console.log("Email:", email); // ! remove
|
||||
console.log("Password:", password); // ! remove
|
||||
|
||||
let user = await prisma.user.findUnique({
|
||||
let user;
|
||||
|
||||
if (usingPrisma) {
|
||||
user = await prisma.user.findUnique({
|
||||
where: {
|
||||
email, // use the email to uniquely identify the user
|
||||
},
|
||||
});
|
||||
} else {
|
||||
user = findUserByEmail(userData, email);
|
||||
}
|
||||
|
||||
if (user && bcryptjs.compareSync(password, user.passwordHash)) {
|
||||
if (user && bcryptjs.compareSync(password, usingPrisma ? user.hashedPassword : user.password)) {
|
||||
// todo remove password from returned user
|
||||
|
||||
// get user and relations
|
||||
if (usingPrisma)
|
||||
user = await prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
include: {
|
||||
@ -47,7 +54,7 @@ export async function POST(req: Request) {
|
||||
});
|
||||
|
||||
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
|
||||
const token = await new SignJWT({ userId: user!.id })
|
||||
const token = await new SignJWT({ userId: user.id })
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setExpirationTime("2w")
|
||||
.sign(secret);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const usingPrisma = false;
|
||||
let prisma: PrismaClient;
|
||||
@ -37,7 +37,7 @@ export async function GET(request: Request) {
|
||||
|
||||
// todo get earthquakes associated with observatories
|
||||
let observatories;
|
||||
if (usingPrisma) observatories = await prisma.observatory.findMany();
|
||||
if (usingPrisma) observatories = await prisma.observatories.findMany();
|
||||
|
||||
if (observatories) {
|
||||
return NextResponse.json({ message: "Got observatories successfully", observatories }, { status: 200 });
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
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 "@prismaclient";
|
||||
import { env } from "@utils/env";
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { env } from '@utils/env';
|
||||
|
||||
import { findUserByEmail, passwordStrengthCheck, readUserCsv, User, writeUserCsv } from "../functions/csvReadWrite";
|
||||
import {
|
||||
findUserByEmail, passwordStrengthCheck, readUserCsv, User, writeUserCsv
|
||||
} from '../functions/csvReadWrite';
|
||||
|
||||
const usingPrisma = false;
|
||||
let prisma: PrismaClient;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
import { env } from "@utils/env";
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { verifyJwt } from "@utils/verifyJwt";
|
||||
|
||||
const usingPrisma = false;
|
||||
@ -88,7 +88,7 @@ export async function POST(req: Request) {
|
||||
];
|
||||
|
||||
let artefacts;
|
||||
if (usingPrisma) artefacts = await prisma.artefact.findMany();
|
||||
if (usingPrisma) artefacts = await prisma.artefacts.findMany();
|
||||
|
||||
if (artefacts) {
|
||||
return NextResponse.json({ message: "Got artefacts successfully", artefacts }, { status: 200 });
|
||||
|
||||
@ -39,7 +39,7 @@ 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" />
|
||||
<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 artefacts from seismic events
|
||||
|
||||
@ -189,11 +189,6 @@ const artefacts: Artefact[] = [
|
||||
export default function Shop() {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [selectedArtefact, setSelectedArtefact] = useState<Artefact | null>(null);
|
||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||
const [artefactToBuy, setArtefactToBuy] = useState<Artefact | null>(null);
|
||||
const [showThankYouModal, setShowThankYouModal] = useState(false);
|
||||
const [orderNumber, setOrderNumber] = useState<string | null>(null);
|
||||
|
||||
const artefactsPerPage = 12;
|
||||
const indexOfLastArtefact = currentPage * artefactsPerPage;
|
||||
const indexOfFirstArtefact = indexOfLastArtefact - artefactsPerPage;
|
||||
@ -203,41 +198,25 @@ export default function Shop() {
|
||||
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),
|
||||
[conversionRates]
|
||||
);
|
||||
const convertPrice = useCallback((price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2), []);
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (indexOfLastArtefact < artefacts.length) setCurrentPage((prev) => prev + 1);
|
||||
if (indexOfLastArtefact < artefacts.length) {
|
||||
setCurrentPage((prev) => prev + 1);
|
||||
}
|
||||
};
|
||||
const handlePreviousPage = () => {
|
||||
if (currentPage > 1) setCurrentPage((prev) => prev - 1);
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage((prev) => prev - 1);
|
||||
}
|
||||
};
|
||||
|
||||
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={() => setSelectedArtefact(artefact)}
|
||||
>
|
||||
<Image src={artefact.image} alt={artefact.name} width={500} height={300} className="w-full h-56 object-cover" />
|
||||
<div className="p-4">
|
||||
<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(artefact.price, selectedCurrency)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function Modal({ artefact }: { artefact: Artefact }) {
|
||||
if (!artefact) return null;
|
||||
const handleOverlayClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) setSelectedArtefact(null);
|
||||
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
setSelectedArtefact(null);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
@ -247,10 +226,10 @@ export default function Shop() {
|
||||
<div className="bg-white rounded-xl shadow-2xl max-w-lg w-full p-6">
|
||||
<h3 className="text-2xl font-bold mb-4">{artefact.name}</h3>
|
||||
<Image
|
||||
height={5000}
|
||||
width={5000}
|
||||
src={artefact.image}
|
||||
alt={artefact.name}
|
||||
width={500}
|
||||
height={300}
|
||||
className="w-full h-64 object-cover rounded-md"
|
||||
/>
|
||||
<p className="text-xl font-bold">
|
||||
@ -264,11 +243,7 @@ export default function Shop() {
|
||||
<p className="text-neutral-500 mb-2">{artefact.dateReleased}</p>
|
||||
<div className="flex justify-end gap-4 mt-4 mr-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setArtefactToBuy(artefact); // Set artefact for payment modal
|
||||
setShowPaymentModal(true); // Show payment modal
|
||||
setSelectedArtefact(null); // Close this modal
|
||||
}}
|
||||
onClick={() => alert("Purchased Successfully!")}
|
||||
className="px-10 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Buy
|
||||
@ -279,156 +254,26 @@ export default function Shop() {
|
||||
);
|
||||
}
|
||||
|
||||
function PaymentModal({ artefact, onClose }: { artefact: Artefact; onClose: () => void }) {
|
||||
const [cardNumber, setCardNumber] = useState("");
|
||||
const [expiry, setExpiry] = useState("");
|
||||
const [cvc, setCvc] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [remember, setRemember] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
function validateEmail(email: string) {
|
||||
return (
|
||||
email.includes("@") &&
|
||||
(email.endsWith(".com") || email.endsWith(".co.uk") || email.endsWith(".org") || email.endsWith(".org.uk"))
|
||||
);
|
||||
}
|
||||
function validateCardNumber(number: string) {
|
||||
return /^\d{12,19}$/.test(number.replace(/\s/g, "")); // 12-19 digits
|
||||
}
|
||||
function validateCVC(number: string) {
|
||||
return /^\d{3,4}$/.test(number);
|
||||
}
|
||||
function validateExpiry(exp: string) {
|
||||
return /^\d{2}\/\d{2}$/.test(exp);
|
||||
}
|
||||
|
||||
function handlePay() {
|
||||
setError("");
|
||||
if (!validateEmail(email)) {
|
||||
setError("Please enter a valid email ending");
|
||||
return;
|
||||
}
|
||||
if (!validateCardNumber(cardNumber)) {
|
||||
setError("Card number must be 12-19 digits.");
|
||||
return;
|
||||
}
|
||||
if (!validateExpiry(expiry)) {
|
||||
setError("Expiry must be in MM/YY format.");
|
||||
return;
|
||||
}
|
||||
if (!validateCVC(cvc)) {
|
||||
setError("CVC must be 3 or 4 digits.");
|
||||
return;
|
||||
}
|
||||
const genOrder = () => "#" + Math.random().toString(36).substring(2, 10).toUpperCase();
|
||||
setOrderNumber(genOrder());
|
||||
onClose();
|
||||
setShowThankYouModal(true);
|
||||
}
|
||||
const handleOverlayClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
};
|
||||
function ArtefactCard({ artefact }: { artefact: Artefact }) {
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-neutral-900 bg-opacity-60 flex justify-center items-center z-10"
|
||||
onClick={handleOverlayClick}
|
||||
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
|
||||
onClick={() => setSelectedArtefact(artefact)}
|
||||
>
|
||||
<div className="bg-white rounded-xl shadow-2xl max-w-md w-full p-6">
|
||||
<h2 className="text-2xl font-bold mb-4">Buy {artefact.name}</h2>
|
||||
{/* ...Image... */}
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handlePay();
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="w-full mb-2 px-3 py-2 border rounded"
|
||||
placeholder="Email Address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
type="email"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
<input
|
||||
className="w-full mb-2 px-3 py-2 border rounded"
|
||||
placeholder="Cardholder Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
className="w-full mb-2 px-3 py-2 border rounded"
|
||||
placeholder="Card Number"
|
||||
value={cardNumber}
|
||||
onChange={(e) => setCardNumber(e.target.value.replace(/\D/g, ""))}
|
||||
maxLength={19}
|
||||
required
|
||||
inputMode="numeric"
|
||||
pattern="\d*"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
className="w-1/2 mb-2 px-3 py-2 border rounded"
|
||||
placeholder="MM/YY"
|
||||
value={expiry}
|
||||
onChange={(e) => setExpiry(e.target.value.replace(/[^0-9/]/g, ""))}
|
||||
maxLength={5}
|
||||
required
|
||||
inputMode="numeric"
|
||||
/>
|
||||
<input
|
||||
className="w-1/2 mb-2 px-3 py-2 border rounded"
|
||||
placeholder="CVC"
|
||||
value={cvc}
|
||||
onChange={(e) => setCvc(e.target.value.replace(/\D/g, ""))}
|
||||
maxLength={4}
|
||||
required
|
||||
inputMode="numeric"
|
||||
/>
|
||||
</div>
|
||||
<label className="inline-flex items-center mb-4">
|
||||
<input type="checkbox" checked={remember} onChange={() => setRemember((r) => !r)} className="mr-2" />
|
||||
Remember me
|
||||
</label>
|
||||
{error && <p className="text-red-600 mb-2">{error}</p>}
|
||||
<div className="flex justify-end gap-2 mt-2">
|
||||
<button type="button" onClick={onClose} className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md mr-2">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
|
||||
Pay
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<img src={artefact.image} alt={artefact.name} className="w-full h-56 object-cover" />
|
||||
<div className="p-4">
|
||||
<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(artefact.price, selectedCurrency)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThankYouModal({ orderNumber, onClose }: { orderNumber: string; onClose: () => void }) {
|
||||
const handleOverlayClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-neutral-900 bg-opacity-60 flex justify-center items-center z-50"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div className="bg-white rounded-xl shadow-2xl max-w-md w-full p-8 text-center">
|
||||
<h2 className="text-3xl font-bold mb-4">Thank you for your purchase!</h2>
|
||||
<p className="mb-4">Your order number is:</p>
|
||||
<p className="text-2xl font-mono font-bold mb-6">{orderNumber}</p>
|
||||
<button className="px-8 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="min-h-screen relative flex flex-col"
|
||||
@ -438,8 +283,10 @@ export default function Shop() {
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
>
|
||||
{/* Overlay */}
|
||||
<div className="absolute inset-0 bg-black bg-opacity-50 z-0"></div>
|
||||
<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">
|
||||
Artefact Shop
|
||||
</h1>
|
||||
@ -447,11 +294,17 @@ export default function Shop() {
|
||||
Discover extraordinary historical artefacts and collectibles from major seismic events from around the world - now
|
||||
available for purchase.
|
||||
</p>
|
||||
|
||||
{/* 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>
|
||||
|
||||
{/* Pagination Footer */}
|
||||
<footer className="mt-10 bg-white bg-opacity-90 border-neutral-300 py-3 text-center flex justify-center items-center w-100 max-w-7xl rounded-lg">
|
||||
<button
|
||||
onClick={handlePreviousPage}
|
||||
@ -474,19 +327,9 @@ export default function Shop() {
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
{selectedArtefact && <Modal artefact={selectedArtefact} />}
|
||||
{artefactToBuy && showPaymentModal && (
|
||||
<PaymentModal
|
||||
artefact={artefactToBuy}
|
||||
onClose={() => {
|
||||
setShowPaymentModal(false);
|
||||
setArtefactToBuy(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showThankYouModal && orderNumber && (
|
||||
<ThankYouModal orderNumber={orderNumber} onClose={() => setShowThankYouModal(false)} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,68 +1,68 @@
|
||||
"use client";
|
||||
import { Dispatch, SetStateAction, useMemo, useState } from "react";
|
||||
import { FaTimes } from "react-icons/fa";
|
||||
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from "react-icons/fa6";
|
||||
import { useState, useMemo } from "react";
|
||||
import { FaCalendarPlus, FaWarehouse, FaCartShopping } from "react-icons/fa6";
|
||||
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from "react-icons/io5";
|
||||
|
||||
// import { Artefact } from "@appTypes/Prisma";
|
||||
|
||||
import type { Artefact } from "@prismaclient";
|
||||
|
||||
interface WarehouseArtefact extends Artefact {
|
||||
location: string;
|
||||
}
|
||||
import { FaTimes } from "react-icons/fa";
|
||||
import { SetStateAction, Dispatch } from "react";
|
||||
// import type { Artefact } from "@prisma/client";
|
||||
import { Artefact } from "@appTypes/Prisma";
|
||||
|
||||
// Warehouse Artefacts Data
|
||||
const warehouseArtefacts: WarehouseArtefact[] = [
|
||||
const warehouseArtefacts: Artefact[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Solidified Lava Chunk",
|
||||
description: "A chunk of solidified lava from the 2023 Iceland eruption.",
|
||||
location: "Reykjanes, Iceland",
|
||||
earthquakeId: "EQ2023ICL",
|
||||
isRequired: true,
|
||||
isSold: false,
|
||||
isCollected: false,
|
||||
createdAt: "2025-05-04",
|
||||
dateAdded: "2025-05-04",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Tephra Sample",
|
||||
description: "Foreign debris from the 2022 Tonga volcanic eruption.",
|
||||
location: "Tonga",
|
||||
earthquakeId: "EQ2022TGA",
|
||||
isRequired: false,
|
||||
isSold: true,
|
||||
isCollected: true,
|
||||
createdAt: "2025-05-03",
|
||||
dateAdded: "2025-05-03",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Ash Sample",
|
||||
description: "Volcanic ash from the 2021 La Palma eruption.",
|
||||
location: "La Palma, Spain",
|
||||
earthquakeId: "EQ2021LPA",
|
||||
isRequired: false,
|
||||
isSold: false,
|
||||
isCollected: false,
|
||||
createdAt: "2025-05-04",
|
||||
dateAdded: "2025-05-04",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Ground Soil",
|
||||
description: "Soil sample from the 2020 Croatia earthquake site.",
|
||||
location: "Zagreb, Croatia",
|
||||
earthquakeId: "EQ2020CRO",
|
||||
isRequired: true,
|
||||
isSold: false,
|
||||
isCollected: false,
|
||||
createdAt: "2025-05-02",
|
||||
dateAdded: "2025-05-02",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Basalt Fragment",
|
||||
description: "Basalt rock from the 2019 New Zealand eruption.",
|
||||
location: "White Island, New Zealand",
|
||||
earthquakeId: "EQ2019NZL",
|
||||
isRequired: false,
|
||||
isSold: true,
|
||||
isCollected: false,
|
||||
createdAt: "2025-05-04",
|
||||
dateAdded: "2025-05-04",
|
||||
},
|
||||
];
|
||||
|
||||
@ -184,7 +184,7 @@ function ArtefactTable({
|
||||
{ label: "Required", key: "isRequired", width: "6%" },
|
||||
{ label: "Sold", key: "isSold", width: "5%" },
|
||||
{ label: "Collected", key: "isCollected", width: "7%" },
|
||||
{ label: "Date Added", key: "createdAt", width: "8%" },
|
||||
{ label: "Date Added", key: "dateAdded", width: "8%" },
|
||||
];
|
||||
|
||||
return (
|
||||
@ -204,7 +204,7 @@ function ArtefactTable({
|
||||
setFilters({ ...filters, [key]: value } as Record<string, string>);
|
||||
if (value === "") clearSortConfig();
|
||||
}}
|
||||
type={key === "createdAt" ? "date" : "text"}
|
||||
type={key === "dateAdded" ? "date" : "text"}
|
||||
options={["isRequired", "isSold", "isCollected"].includes(key) ? ["", "true", "false"] : undefined}
|
||||
/>
|
||||
{sortConfig?.key === key && (
|
||||
@ -500,7 +500,7 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
|
||||
const [isRequired, setIsRequired] = useState(artefact.isRequired);
|
||||
const [isSold, setIsSold] = useState(artefact.isSold);
|
||||
const [isCollected, setIsCollected] = useState(artefact.isCollected);
|
||||
const [createdAt, setDateAdded] = useState(artefact.createdAt.toDateString());
|
||||
const [dateAdded, setDateAdded] = useState(artefact.dateAdded);
|
||||
const [error, setError] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
@ -511,7 +511,7 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!name || !description || !location || !earthquakeId || !createdAt) {
|
||||
if (!name || !description || !location || !earthquakeId || !dateAdded) {
|
||||
setError("All fields are required.");
|
||||
return;
|
||||
}
|
||||
@ -602,7 +602,7 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
|
||||
</div>
|
||||
<input
|
||||
type="date"
|
||||
value={createdAt}
|
||||
value={dateAdded}
|
||||
onChange={(e) => setDateAdded(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Date Added"
|
||||
@ -657,14 +657,13 @@ const applyFilters = (artefacts: Artefact[], filters: Record<string, string>): A
|
||||
return (
|
||||
(filters.id === "" || artefact.id.toString().includes(filters.id)) &&
|
||||
(filters.name === "" || artefact.name.toLowerCase().includes(filters.name.toLowerCase())) &&
|
||||
// todo fix
|
||||
(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.createdAt === "" || artefact.createdAt.toDateString() === filters.createdAt)
|
||||
(filters.dateAdded === "" || artefact.dateAdded === filters.dateAdded)
|
||||
);
|
||||
});
|
||||
};
|
||||
@ -684,7 +683,7 @@ export default function Warehouse() {
|
||||
isRequired: "",
|
||||
isSold: "",
|
||||
isCollected: "",
|
||||
createdAt: "",
|
||||
dateAdded: "",
|
||||
});
|
||||
const [isFiltering, setIsFiltering] = useState(false);
|
||||
const [sortConfig, setSortConfig] = useState<{
|
||||
@ -708,11 +707,9 @@ export default function Warehouse() {
|
||||
|
||||
// Overview stats
|
||||
const totalArtefacts = warehouseArtefacts.length;
|
||||
const today = new Date();
|
||||
const artefactsAddedToday = warehouseArtefacts.filter((a) => a.createdAt.toDateString() === today.toDateString()).length;
|
||||
const artefactsSoldToday = warehouseArtefacts.filter(
|
||||
(a) => a.isSold && a.createdAt.toDateString() === today.toDateString()
|
||||
).length;
|
||||
const today = "2025-05-04";
|
||||
const artefactsAddedToday = warehouseArtefacts.filter((a) => a.dateAdded === today).length;
|
||||
const artefactsSoldToday = warehouseArtefacts.filter((a) => a.isSold && a.dateAdded === today).length;
|
||||
|
||||
const clearFilters = () => {
|
||||
setFilters({
|
||||
@ -724,7 +721,7 @@ export default function Warehouse() {
|
||||
isRequired: "",
|
||||
isSold: "",
|
||||
isCollected: "",
|
||||
createdAt: "",
|
||||
dateAdded: "",
|
||||
});
|
||||
setSortConfig(null); // Clear sorting
|
||||
};
|
||||
|
||||
1
src/databases/Artefacts.csv
Normal file
1
src/databases/Artefacts.csv
Normal file
@ -0,0 +1 @@
|
||||
Artefacts
|
||||
|
1573
src/databases/EarthquakeEvents.csv
Normal file
1573
src/databases/EarthquakeEvents.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,56 +2,59 @@ import random
|
||||
import datetime
|
||||
|
||||
def generate_earthquake_data(start_date, end_date, file_name):
|
||||
# List of 200 real-world seismic locations, reformatted to remove commas
|
||||
locations = [
|
||||
"San Andreas Fault: USA", "Cascade Range: USA", "Denali Fault System: USA",
|
||||
"New Madrid Seismic Zone: USA", "Wasatch Fault Zone: USA", "Hayward Fault: USA",
|
||||
"Guerrero Gap: Mexico", "Cocos Plate Subduction Zone: Mexico", "Motagua Fault Zone: Guatemala",
|
||||
"Caribbean Plate Boundary: Jamaica", "Los Angeles Basin: USA", "Seattle Tacoma Fault Zone: USA",
|
||||
"San Juan Fault Zone: Argentina", "Nazca Ridge Subduction: Peru", "Cotopaxi Region: Ecuador",
|
||||
"Colombian Andes Plate Boundary: Colombia", "Atacama Fault Zone: Chile", "Cape Fold Belt: South Africa",
|
||||
"Alpine Fault: New Zealand", "Hikurangi Subduction Zone: New Zealand", "Papua Fold Belt: Papua New Guinea",
|
||||
"Nias Islands Earthquake Zone: Indonesia", "Java Trench: Indonesia", "Banda Arc: Indonesia",
|
||||
"Sumatra Andaman Megathrust: Indonesia", "Kashmir Region: India", "Himalayan Subduction Zone: Nepal",
|
||||
"Chiang Mai Rift Zone: Thailand", "Active Faults: Myanmar", "Red River Fault Zone: Vietnam",
|
||||
"Taiwan Collision Zone: Taiwan", "Ryukyu Trench: Japan", "Kanto Region Fault: Japan",
|
||||
"Kyushu Subduction Zone: Japan", "Kuril Kamchatka Trench: Russia", "Lake Baikal Rift Zone: Russia",
|
||||
"Berbera Rift System: Somalia", "Armenian Highlands: Armenia", "Zagros Mountains Fault: Iran",
|
||||
"Makran Subduction Zone: Pakistan", "Hindu Kush Earthquake Belt: Afghanistan", "Caspian Sea Collision Zone: Iran",
|
||||
"Jordan Rift Valley: Jordan", "Dead Sea Fault Zone: Israel", "Eastern Anatolian Fault: Turkey",
|
||||
"Hellenic Arc: Greece", "Mediterranean Subduction Complex: Italy", "Pyrenees Fault System: Spain",
|
||||
"Aegean Seismic Zone: Greece", "Alborz Mountains Fault Zone: Iran", "Ligurian Alps: Italy",
|
||||
"Iceland Seismic Zone: Iceland", "Mid Atlantic Ridge: Iceland",
|
||||
"Azores Triple Junction: Portugal", "Reykjanes Ridge: Iceland", "Scandinavian Fault Zone: Norway",
|
||||
"Barents Sea Rift: Norway", "East African Rift: Ethiopia", "South Madagascar Seismic Zone: Madagascar",
|
||||
"Cape Verde Rift: Cape Verde", "Victoria Seismic Belt: Australia",
|
||||
"Bismarck Plate Subduction Zone: Papua New Guinea", "Fiji Plate Boundary: Fiji",
|
||||
"Solomon Islands Seismic Zone: Solomon Islands", "New Hebrides Subduction: Vanuatu",
|
||||
"Tonga Kermadec Arc: Tonga", "Samoa Seismic Zone: Samoa",
|
||||
"South Sandwich Plate Collision Zone: South Georgia and the South Sandwich Islands",
|
||||
"Drake Passage Convergence Zone: Argentina", "Scotia Plate Boundary: Argentina",
|
||||
"Antarctic Seismic Belt: Antarctica", "Ross Sea Fault Zone: Antarctica", "Carlsberg Ridge: Maldives",
|
||||
"East Pacific Rise: Chile", "Indian Ocean Ridge: Mauritius", "Macquarie Plate Boundary: Australia",
|
||||
"Chagos Laccadive Ridge: Maldives", "Moho Tectonic Zone: Ethiopia",
|
||||
"Azores Cape Verde Fault Line: Portugal", "South Shetland Trench: Antarctica",
|
||||
"Luale Tectonic Boundary: Angola", "Banda Sea Subduction Zone: Indonesia",
|
||||
"Guinea Ridge Zone: Guinea", "Mauritius Seismic Area: Mauritius", "Moluccas Sea Plate Collision: Indonesia",
|
||||
"Yucatan Fault Zone: Mexico", "Offshore Nicaragua Subduction Zone: Nicaragua",
|
||||
"Central Honduras Earthquake Belt: Honduras", "Puerto Rico Trench: Puerto Rico",
|
||||
"Trinidad Seismic Zone: Trinidad and Tobago", "Barbadian Subduction Area: Barbados",
|
||||
"Northern Andes Seismic Belt: Venezuela", "South Atlantic Rift: Brazil",
|
||||
"Acre Seismic Boundary: Brazil", "Rio Grande Rift Zone: USA", "Offshore Baja California: Mexico",
|
||||
"Guarare Seismic Region: Panama", "Offshore Vancouver Island Subduction: Canada",
|
||||
"Yellowstone Volcanic Zone: USA"
|
||||
"San Andreas Fault USA", "Cascade Range USA", "Denali Fault System Alaska USA",
|
||||
"New Madrid Seismic Zone USA", "Wasatch Fault Zone USA", "Hayward Fault California USA",
|
||||
"Guerrero Gap Mexico", "Cocos Plate Subduction Zone Mexico", "Motagua Fault Zone Guatemala",
|
||||
"Caribbean North American Plate Boundary", "Los Angeles Basin USA", "Seattle Tacoma Fault Zone USA",
|
||||
"San Juan Fault Zone Argentina", "Nazca Ridge Subduction Peru", "Cotopaxi Region Ecuador",
|
||||
"Colombian Andes Plate Boundary Colombia", "Atacama Fault Zone Chile", "Cape Fold Belt South Africa",
|
||||
"Alpine Fault New Zealand", "Hikurangi Subduction Zone New Zealand", "Papua Fold Belt Papua New Guinea",
|
||||
"Nias Islands Earthquake Zone Indonesia", "Java Trench Indonesia", "Banda Arc Indonesia",
|
||||
"Sumatra Andaman Megathrust Indonesia", "Kashmir Region India", "Himalayan Subduction Zone Nepal",
|
||||
"Chiang Mai Rift Zone Thailand", "Active Faults in Myanmar", "Red River Fault Zone Vietnam",
|
||||
"Taiwan Collision Zone Taiwan", "Ryukyu Trench Japan", "Kanto Region Fault Japan",
|
||||
"Kyushu Subduction Zone Japan", "Kuril Kamchatka Trench Russia", "Lake Baikal Rift Zone Russia",
|
||||
"Berbera Rift System Somalia", "Armenian Highlands Collision Zone Armenia", "Zagros Mountains Fault Iran",
|
||||
"Makran Subduction Zone Pakistan", "Hindu Kush Earthquake Belt Afghanistan", "Caspian Sea Collision Zone Iran",
|
||||
"Jordan Rift Valley Israel", "Dead Sea Fault Zone Israel", "Eastern Anatolian Fault Turkey",
|
||||
"Hellenic Arc Greece", "Mediterranean Subduction Complex Italy", "Pyrenees Fault System Spain",
|
||||
"Aegean Seismic Zone Greece", "Alborz Mountains Fault Zone Iran", "Ligurian Alps Italy",
|
||||
"Iceland Seismic Zone Iceland", "Mid Atlantic Ridge Atlantic Ocean", "Azores Triple Junction Portugal",
|
||||
"Reykjanes Ridge Iceland", "Scandinavian Fault Zone Norway", "Barents Sea Rift Norway",
|
||||
"East African Rift Ethiopia", "South Madagascar Seismic Zone Madagascar", "Cape Verde Rift Atlantic",
|
||||
"Victoria Seismic Belt Australia", "Bismarck Plate Subduction Zone Papua New Guinea",
|
||||
"Fiji Plate Boundary Pacific Ocean", "Solomon Islands Seismic Zone Solomon Islands",
|
||||
"New Hebrides Subduction Vanuatu", "Tonga Kermadec Arc Tonga", "Samoa Seismic Zone Pacific Ocean",
|
||||
"South Sandwich Plate Collision Zone South Sandwich Islands", "Drake Passage Convergence Zone Antarctica",
|
||||
"Scotia Plate Boundary Antarctica", "Antarctic Seismic Belt Antarctica", "Ross Sea Fault Zone Antarctica",
|
||||
"Carlsberg Ridge Indian Ocean", "East Pacific Rise Pacific Ocean", "Indian Ocean Ridge Indian Ocean",
|
||||
"Macquarie Plate Boundary Australia", "Chagos Laccadive Ridge Indian Ocean", "Moho Tectonic Zone Ethiopia",
|
||||
"Azores Cape Verde Fault Line Atlantic Ocean", "South Shetland Trench Antarctica",
|
||||
"Luale Tectonic Boundary Angola", "Banda Sea Subduction Zone Indonesia",
|
||||
"Guinea Ridge Zone Guinea", "Mauritius Seismic Area Indian Ocean", "Moluccas Sea Plate Collision Indonesia",
|
||||
"Yucatan Fault Zone Central America", "Offshore Nicaragua Subduction Zone Nicaragua",
|
||||
"Central Honduras Earthquake Belt Honduras", "Puerto Rico Trench Caribbean Plate",
|
||||
"Trinidad Seismic Zone Trinidad and Tobago", "Barbadian Subduction Area Barbados",
|
||||
"Northern Andes Seismic Belt Venezuela", "South Atlantic Rift Brazil", "Acre Seismic Boundary Brazil",
|
||||
"Rio Grande Rift Zone USA", "Offshore Baja California USA", "Guarare Seismic Region Panama",
|
||||
"Offshore Vancouver Island Subduction Canada", "Yellowstone Volcanic Zone USA",
|
||||
"Adelaide Fold Belt Australia", "Tasman Plate Boundary New Zealand", "Offshore Queensland Australia",
|
||||
"Gansu Fault Zone China", "Xian Seismic Belt China", "Tibet Rift Zone China",
|
||||
"Chengdu Seismic Zone China", "Fujian Fault Zone China", "Jiuzhaigou Seismic Area China",
|
||||
"Karakoram Fault Zone India", "Andaman Nicobar Subduction India", "Mumbai Rift Zone India",
|
||||
"Cape York Seismic Zone Papua New Guinea", "Merewether Fault Australia", "Gulf of Aden Rift Zone",
|
||||
"Oman Subduction Zone", "Ras Al Khaimah Fault Zone UAE", "Djibouti Rift Zone Africa",
|
||||
"Mogadishu Seismic Zone Somalia", "Mozambique Channel Rift Mozambique", "Botswana Seismic Zone Africa",
|
||||
"Victoria Lake Microplate Africa", "Nairobi Rift Axis Kenya", "Sumba Island Subduction Zone Indonesia"
|
||||
]
|
||||
types = ["tectonic", "volcanic", "collapse", "explosion"]
|
||||
type_prefix = {"tectonic": "ET", "volcanic": "EV", "collapse": "EC", "explosion": "EE"}
|
||||
|
||||
start = datetime.datetime.strptime(start_date, '%Y-%m-%d')
|
||||
end = datetime.datetime.strptime(end_date, '%Y-%m-%d')
|
||||
delta = end - start
|
||||
|
||||
earthquake_list = []
|
||||
unique_counter = 1 # For sequential unique codes
|
||||
|
||||
for i in range(delta.days + 1):
|
||||
date = start + datetime.timedelta(days=i)
|
||||
@ -60,16 +63,9 @@ def generate_earthquake_data(start_date, end_date, file_name):
|
||||
lat = round(random.uniform(-90, 90), 4)
|
||||
lon = round(random.uniform(-180, 180), 4)
|
||||
location = random.choice(locations)
|
||||
country = location.split(": ")[1]
|
||||
place = location.split(": ")[0]
|
||||
depth = random.randint(5, 150)
|
||||
eq_type = random.choice(types)
|
||||
prefix = type_prefix[eq_type]
|
||||
|
||||
code = f"{prefix}-{magnitude}-{country}-{unique_counter:05d}"
|
||||
# Output: Date,Code,Magnitude,Type,Lat,Lon,Location,Depth
|
||||
earthquake_list.append(f"{date.date()},{code},{magnitude},{eq_type},{lat},{lon},{place},{depth} km")
|
||||
unique_counter += 1
|
||||
# Format data using comma-separated values (CSV)
|
||||
earthquake_list.append(f"{date.date()},{magnitude},{lat},{lon},{location},{depth} km")
|
||||
|
||||
with open(file_name, "w") as file:
|
||||
file.write("\n".join(earthquake_list))
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
Name,Location,Latitude,Longitude,Date Established,Functional
|
||||
Pacific Apex Seismic Center,"Aleutian Trench, Alaska, USA",53.0000,-168.0000,1973-06-15,Yes
|
||||
Cascadia Quake Research Institute,"Oregon Coast, USA",44.5000,-124.0000,1985-03-22,Yes
|
||||
Andes Fault Survey Observatory,"Nazca-South American Plate, Santiago, Chile",-33.4500,-70.6667,1992-10-10,Yes
|
||||
|
@ -1,19 +1,21 @@
|
||||
Dr. Emily Neighbour Carter,Junior,Dr. Rajiv Menon
|
||||
Name,Level,Superior
|
||||
Dr. Emily Neighbour Carter,Senior,None
|
||||
Dr. Rajiv Menon,Senior,None
|
||||
Dr. Izzy Patterson,Senior,None
|
||||
Dr. Hiroshi Takeda,Senior,None
|
||||
Dr. Miriam Hassan,Senior,None
|
||||
Dr. Alice Johnson,Senior,None
|
||||
Tim Howitz,Admin,None
|
||||
Dr. Alice Johnson,Junior,Dr. Emily Neighbour
|
||||
Dr. Tim Howitz,Junior,Dr. Rajiv Menon
|
||||
Dr. Natalia Petrova,Junior,Dr. Izzy Patteron
|
||||
Dr. Li Cheng,Junior,Dr. Rajiv Menon
|
||||
Dr. Javier Ortega,Junior,Dr. Izzy Patterson
|
||||
Dr. Priya Sharma,Junior,Dr. Hiroshi Takeda
|
||||
Dr. Lukeshan Thananchayan,Junior,Dr. Miriam Hassan
|
||||
Dr. Elena Fischer,Junior,Dr. Alice Johnson
|
||||
Dr. Elena Fischer,Junior,Dr. Emily Neighbour
|
||||
Dr. Mohammed Al-Farsi,Junior,Dr. Miriam Hassan
|
||||
Dr. Jane Wong,Junior,Dr. Hiroshi Takeda
|
||||
Dr. Carlos Gutierrez,Junior,Dr. Rajiv Menon
|
||||
Dr. Fiona MacLeod,Junior,Dr. Emily Neighbour
|
||||
Dr. Wei Zhao,Junior,Dr. Miriam Hassan
|
||||
Dr. Antonio Rosales,Junior,Dr. Izzy Patterson
|
||||
Dr. Kate Wilson,Junior,Dr. Hiroshi Takeda
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
"@utils/*": ["./src/utils/*"],
|
||||
"@appTypes/*": ["./src/types/*"],
|
||||
"@zod/*": ["./src/zod/*"],
|
||||
"@prismaclient": ["./src/generated/prisma/client"],
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user