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 }) => ( + + ))} + + + + {sortedArtifacts.map((artifact) => ( + setEditArtifact(artifact)} + > + + + + + + + + + + + ))} + +
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" ? "↑" : "↓"}} +
+
{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}

} +
+ 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" + disabled={isSubmitting} + /> +