diff --git a/package-lock.json b/package-lock.json
index db6057c..2baa5c1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +20,7 @@
"fs": "^0.0.1-security",
"jwt-decode": "^4.0.0",
"leaflet": "^1.9.4",
+ "lodash": "^4.17.21",
"mapbox-gl": "^3.10.0",
"next": "15.1.7",
"path": "^0.12.7",
@@ -5470,6 +5471,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -8524,4 +8531,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index b7bbc99..99e4e22 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"fs": "^0.0.1-security",
"jwt-decode": "^4.0.0",
"leaflet": "^1.9.4",
+ "lodash": "^4.17.21",
"mapbox-gl": "^3.10.0",
"next": "15.1.7",
"path": "^0.12.7",
@@ -47,4 +48,4 @@
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
-}
\ No newline at end of file
+}
diff --git a/src/app/warehouse/page.tsx b/src/app/warehouse/page.tsx
index 67e0c65..9f77be1 100644
--- a/src/app/warehouse/page.tsx
+++ b/src/app/warehouse/page.tsx
@@ -1,56 +1,826 @@
"use client";
-import { useMemo, useState } from "react";
+import { useState, useMemo } from "react";
+import { FaInbox, FaTimes, FaBox, FaCalendarPlus, FaShoppingCart } from "react-icons/fa";
+import { IoFilter, IoFilterCircleOutline, IoFilterOutline } from "react-icons/io5";
+import { SetStateAction, Dispatch } from "react";
-import Sidebar from "@/components/Sidebar";
+// Artifact type
+interface Artifact {
+ id: number;
+ name: string;
+ description: string;
+ location: string;
+ earthquakeId: string;
+ isRequired: boolean;
+ isSold: boolean;
+ isCollected: boolean;
+ dateAdded: string;
+}
-export default function Warehouse() {
- const [selectedEventId, setSelectedEventId] = useState("");
- const [hoveredEventId, setHoveredEventId] = useState("");
- const events = useMemo(
- () => [
- {
- id: "1234",
- title: "Artifact found - Germany Earthquake",
- text1: "Mortar and pestle",
- text2: "10 minutes ago",
- longitude: 10.4515, // Near Berlin, Germany
- latitude: 52.52,
- },
- {
- id: "2134",
- title: "All artifacts from California earthquake transported",
- text1: "India to London",
- text2: "15 hours ago",
- longitude: -122.4194, // Near San Francisco, California, USA
- latitude: 37.7749,
- },
- {
- id: "2341",
- title: "7 artifacts destroyed - Spain earthquake",
- text1: "edVases and pots from Madrid",
- text2: "3 weeks ago",
- longitude: -3.7038, // Near Madrid, Spain
- latitude: 40.4168,
- },
- ],
- []
- );
+// Warehouse Artifacts Data
+const warehouseArtifacts: Artifact[] = [
+ {
+ 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,
+ 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,
+ 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,
+ 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,
+ 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,
+ dateAdded: "2025-05-04",
+ },
+];
+// Filter Component
+function FilterInput({
+ value,
+ onChange,
+ type = "text",
+ options,
+}: {
+ value: string;
+ onChange: (value: string) => void;
+ type?: string;
+ options?: string[];
+}) {
return (
-
-
warehouse image pasted in here
-
+
+
+
+
+
+
+ {options ? (
+
+ {options.map((opt) => (
+
onChange(opt)}>
+ {opt || "All"}
+
+ ))}
+
+ ) : (
+
onChange(e.target.value)}
+ className="w-full p-1 border border-neutral-300 rounded-md text-sm"
+ placeholder="Filter..."
+ aria-label="Filter input"
+ />
+ )}
+
+
+ {value && (
+
+ {value === "true" ? "Yes" : value === "false" ? "No" : value}
+ onChange("")} />
+
+ )}
+
+ );
+}
+
+// Table Component
+function ArtifactTable({
+ artifacts,
+ filters,
+ setFilters,
+ setEditArtifact,
+ clearSort,
+}: {
+ artifacts: Artifact[];
+ filters: Record
;
+ setFilters: Dispatch<
+ SetStateAction<{
+ id: string;
+ name: string;
+ earthquakeId: string;
+ location: string;
+ description: string;
+ isRequired: string;
+ isSold: string;
+ isCollected: string;
+ dateAdded: string;
+ }>
+ >;
+ setEditArtifact: (artifact: Artifact) => void;
+ clearSort: () => void;
+}) {
+ const [sortConfig, setSortConfig] = useState<{
+ key: keyof Artifact;
+ direction: "asc" | "desc";
+ } | null>(null);
+
+ const handleSort = (key: keyof Artifact) => {
+ setSortConfig((prev) => {
+ if (!prev || prev.key !== key) {
+ return { key, direction: "asc" };
+ }
+ return {
+ key,
+ direction: prev.direction === "asc" ? "desc" : "asc",
+ };
+ });
+ };
+
+ const clearSortConfig = () => {
+ setSortConfig(null);
+ clearSort();
+ };
+
+ const sortedArtifacts = useMemo(() => {
+ if (!sortConfig) return artifacts;
+ const sorted = [...artifacts].sort((a, b) => {
+ const aValue = a[sortConfig.key];
+ const bValue = b[sortConfig.key];
+ if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1;
+ if (aValue > bValue) return sortConfig.direction === "asc" ? 1 : -1;
+ return 0;
+ });
+ return sorted;
+ }, [artifacts, sortConfig]);
+
+ return (
+
+
+
+ {[
+ { label: "ID", key: "id" },
+ { label: "Name", key: "name" },
+ { label: "Earthquake ID", key: "earthquakeId" },
+ { label: "Location", key: "location" },
+ { label: "Description", key: "description" },
+ { label: "Required", key: "isRequired" },
+ { label: "Sold", key: "isSold" },
+ { label: "Collected", key: "isCollected" },
+ { label: "Date Added", key: "dateAdded" },
+ ].map(({ label, key }) => (
+ | handleSort(key as keyof Artifact)}
+ >
+
+ {label}
+ {
+ setFilters({ ...filters, [key]: value } as {
+ id: string;
+ name: string;
+ earthquakeId: string;
+ location: string;
+ description: string;
+ isRequired: string;
+ isSold: string;
+ isCollected: string;
+ dateAdded: string;
+ });
+ if (value === "") clearSortConfig();
+ }}
+ type={key === "dateAdded" ? "date" : "text"}
+ options={["isRequired", "isSold", "isCollected"].includes(key) ? ["", "true", "false"] : undefined}
+ />
+ {sortConfig?.key === key && {sortConfig.direction === "asc" ? "↑" : "↓"}}
+
+ |
+ ))}
+
+
+
+ {sortedArtifacts.map((artifact) => (
+ setEditArtifact(artifact)}
+ >
+ | {artifact.id} |
+ {artifact.name} |
+ {artifact.earthquakeId} |
+ {artifact.location} |
+ {artifact.description} |
+ {artifact.isRequired ? "Yes" : "No"} |
+ {artifact.isSold ? "Yes" : "No"} |
+ {artifact.isCollected ? "Yes" : "No"} |
+ {artifact.dateAdded} |
+
+ ))}
+
+
+ );
+}
+
+// Modal Component for Logging Artifact
+function LogModal({ onClose }: { onClose: () => void }) {
+ const [name, setName] = useState("");
+ const [description, setDescription] = useState("");
+ const [location, setLocation] = useState("");
+ const [earthquakeId, setEarthquakeId] = useState("");
+ const [storageLocation, setStorageLocation] = useState("");
+ const [isRequired, setIsRequired] = useState(true);
+ const [error, setError] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
+ if (e.target === e.currentTarget) {
+ onClose();
+ }
+ };
+
+ const handleLog = async () => {
+ if (!name || !description || !location || !earthquakeId || !storageLocation) {
+ setError("All fields are required.");
+ return;
+ }
+ setIsSubmitting(true);
+ try {
+ await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
+ alert(`Logged ${name} to storage: ${storageLocation}`);
+ onClose();
+ } catch {
+ setError("Failed to log artifact. Please try again.");
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
Log New Artifact
+ {error &&
{error}
}
+
+
+
+
+
+
+
+ );
+}
+
+// Modal Component for Bulk Logging
+function BulkLogModal({ onClose }: { onClose: () => void }) {
+ const [palletNote, setPalletNote] = useState("");
+ const [storageLocation, setStorageLocation] = useState("");
+ const [error, setError] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
+ if (e.target === e.currentTarget) {
+ onClose();
+ }
+ };
+
+ const handleLog = async () => {
+ if (!palletNote || !storageLocation) {
+ setError("All fields are required.");
+ return;
+ }
+ setIsSubmitting(true);
+ try {
+ await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
+ alert(`Logged bulk pallet to storage: ${storageLocation}`);
+ onClose();
+ } catch {
+ setError("Failed to log pallet. Please try again.");
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
Log Bulk Pallet
+ {error &&
{error}
}
+
+
+
+
+
+
+
+
+ );
+}
+
+// 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);
+ const [error, setError] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
+ if (e.target === e.currentTarget) {
+ onClose();
+ }
+ };
+
+ const handleSave = async () => {
+ if (!name || !description || !location || !earthquakeId || !dateAdded) {
+ setError("All fields are required.");
+ return;
+ }
+ setIsSubmitting(true);
+ try {
+ await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
+ alert(`Updated artifact ${name}`);
+ onClose();
+ } catch {
+ setError("Failed to update artifact. Please try again.");
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
Edit Artifact
+ {error &&
{error}
}
+
+
+
+
+
+
+
+ );
+}
+
+// Filter Logic
+const applyFilters = (artifacts: Artifact[], filters: Record): Artifact[] => {
+ return artifacts.filter((artifact) => {
+ 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)
+ );
+ });
+};
+
+// Warehouse Component
+export default function Warehouse() {
+ const [currentPage, setCurrentPage] = useState(1);
+ const [showLogModal, setShowLogModal] = useState(false);
+ const [showBulkLogModal, setShowBulkLogModal] = useState(false);
+ const [editArtifact, setEditArtifact] = useState(null);
+ const [filters, setFilters] = useState({
+ id: "",
+ name: "",
+ earthquakeId: "",
+ location: "",
+ description: "",
+ isRequired: "",
+ isSold: "",
+ isCollected: "",
+ dateAdded: "",
+ });
+ const [isFiltering, setIsFiltering] = useState(false);
+ const [sortConfig, setSortConfig] = useState<{
+ key: keyof Artifact;
+ direction: "asc" | "desc";
+ } | null>(null);
+
+ const artifactsPerPage = 10;
+ const indexOfLastArtifact = currentPage * artifactsPerPage;
+ const indexOfFirstArtifact = indexOfLastArtifact - artifactsPerPage;
+
+ // Apply filters with loading state
+ const filteredArtifacts = useMemo(() => {
+ setIsFiltering(true);
+ const result = applyFilters(warehouseArtifacts, filters);
+ setIsFiltering(false);
+ return result;
+ }, [filters]);
+
+ const currentArtifacts = filteredArtifacts.slice(indexOfFirstArtifact, indexOfLastArtifact);
+
+ // Overview stats
+ const totalArtifacts = warehouseArtifacts.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 handleNextPage = () => {
+ if (indexOfLastArtifact < filteredArtifacts.length) {
+ setCurrentPage((prev) => prev + 1);
+ }
+ };
+
+ const handlePreviousPage = () => {
+ if (currentPage > 1) {
+ setCurrentPage((prev) => prev - 1);
+ }
+ };
+
+ const clearFilters = () => {
+ setFilters({
+ id: "",
+ name: "",
+ earthquakeId: "",
+ location: "",
+ description: "",
+ isRequired: "",
+ isSold: "",
+ isCollected: "",
+ dateAdded: "",
+ });
+ setSortConfig(null); // Clear sorting
+ };
+
+ return (
+
+ {/* Main Content */}
+
+
+ {/* Overview Stats */}
+
+
+
+ Total Artifacts: {totalArtifacts}
+
+
+
+ Added Today: {artifactsAddedToday}
+
+
+
+ Sold Today: {artifactsSoldToday}
+
+
+
+ {/* Logging Buttons */}
+
+
+
+
+
+
+ {/* Table Card */}
+
+ {isFiltering && (
+
+ )}
+
+
setSortConfig(null)}
+ />
+
+
+
+
+
+ {/* Modals */}
+ {showLogModal &&
setShowLogModal(false)} />}
+ {showBulkLogModal && setShowBulkLogModal(false)} />}
+ {editArtifact && setEditArtifact(null)} />}
);
}