"use client";
import axios from "axios";
import useSWR from "swr";
import { Dispatch, SetStateAction, useMemo, useState } from "react";
import { FaTimes } from "react-icons/fa";
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from "react-icons/fa6";
import { IoFilter, IoToday } from "react-icons/io5";
import { ExtendedArtefact } from "@appTypes/ApiTypes";
import { fetcher } from "@utils/axiosHelpers";
// Filter Component
function FilterInput({
value,
onChange,
type = "text",
options,
}: {
value: string;
onChange: (value: string) => void;
type?: string;
options?: string[];
}) {
const showSelectedFilter = type === "text" && !["true", "false"].includes(options?.at(-1)!);
return (
{options ? (
{options.map((opt) => (
onChange(opt)}>
{opt ? (opt === "true" ? "Yes" : "No") : "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 && showSelectedFilter && (
{value === "true" ? "Yes" : value === "false" ? "No" : value}
onChange("")} />
)}
);
}
// Modal Component for Logging Artefact
function LogModal({ onClose }: { onClose: () => void }) {
const [name, setName] = useState("");
const [type, setType] = useState("");
const [description, setDescription] = useState("");
const [location, setLocation] = useState("");
const [earthquakeCode, setEarthquakeCode] = useState("");
const [warehouseLocation, setWarehouseLocation] = 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();
}
};
// todo add uploading image
async function handleLog() {
if (!name || !type || !description || !location || !earthquakeCode || !warehouseLocation) {
setError("All fields are required.");
return;
}
setIsSubmitting(true);
try {
await axios.post("/api/warehouse/log", {
name,
type,
description,
location,
earthquakeCode,
warehouseLocation,
});
// todo replace with better alert
alert(`Logged ${name} to storage: ${warehouseLocation}`);
onClose();
} catch {
setError("Failed to log artefact. Please try again.");
} finally {
setIsSubmitting(false);
}
}
return (
Log New Artefact
{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 Artefact
function EditModal({ artefact, onClose }: { artefact: ExtendedArtefact; onClose: () => void }) {
const [name, setName] = useState(artefact.name);
const [description, setDescription] = useState(artefact.description);
const [location, setLocation] = useState(artefact.location);
const [earthquakeCode, setEarthquakeCode] = useState(artefact.earthquakeCode);
const [isRequired, setIsRequired] = useState(artefact.isRequired);
const [isSold, setIsSold] = useState(artefact.isSold);
const [isCollected, setIsCollected] = useState(artefact.isCollected);
const [createdAt, setDateAdded] = useState(new Date(artefact.createdAt).toLocaleDateString("en-GB"));
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 || !earthquakeCode || !createdAt) {
setError("All fields are required.");
return;
}
setIsSubmitting(true);
try {
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
alert(`Updated artefact ${name}`);
onClose();
} catch {
setError("Failed to update artefact. Please try again.");
} finally {
setIsSubmitting(false);
}
};
return (
Edit Artefact
{error &&
{error}
}
);
}
// Filter Logic
const applyFilters = (artefacts: ExtendedArtefact[], filters: Record): ExtendedArtefact[] => {
return artefacts.filter((artefact) => {
return (
(filters.id === "" || artefact.id.toString().includes(filters.id)) &&
(filters.name === "" || artefact.name.toLowerCase().includes(filters.name.toLowerCase())) &&
(filters.earthquakeCode === "" || artefact.earthquakeCode.toLowerCase().includes(filters.earthquakeCode.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 === "" || new Date(artefact.createdAt).toLocaleDateString("en-GB") === filters.createdAt)
);
});
};
// Table Component
function ArtefactTable({
artefacts,
filters,
setFilters,
setEditArtefact,
clearSort,
}: {
artefacts: ExtendedArtefact[];
filters: Record;
setFilters: Dispatch>>;
setEditArtefact: (artefact: ExtendedArtefact) => void;
clearSort: () => void;
}) {
const [sortConfig, setSortConfig] = useState<{
key: keyof ExtendedArtefact;
direction: "asc" | "desc";
} | null>(null);
const handleSort = (key: keyof ExtendedArtefact) => {
setSortConfig((prev) => {
if (!prev || prev.key !== key) {
return { key, direction: "asc" };
} else if (prev.direction === "asc") {
return { key, direction: "desc" };
}
return null;
});
};
const clearSortConfig = () => {
setSortConfig(null);
clearSort();
};
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;
if (aValue > bValue) return sortConfig.direction === "asc" ? 1 : -1;
return 0;
});
return sorted;
}, [artefacts, sortConfig]);
const columns: { label: string; key: keyof ExtendedArtefact; width: string }[] = [
{ label: "ID", key: "id", width: "5%" },
{ label: "Name", key: "name", width: "11%" },
{ label: "Earthquake Code", key: "earthquakeCode", width: "12%" },
{ label: "Location", key: "location", width: "12%" },
{ label: "Description", key: "description", width: "20%" },
{ 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%" },
];
return (
{columns.map(({ label, key, width }) => (
handleSort(key as keyof ExtendedArtefact)}>
{label}
{
setFilters({ ...filters, [key]: value } as Record);
if (value === "") clearSortConfig();
}}
type={key === "createdAt" ? "date" : "text"}
options={["isRequired", "isSold", "isCollected"].includes(key) ? ["", "true", "false"] : undefined}
/>
{sortConfig?.key === key && (
{sortConfig.direction === "asc" ? "↑" : "↓"}
)}
|
))}
{sortedArtefacts.map((artefact) => (
setEditArtefact(artefact)}
>
{columns.map(({ key, width }) => (
|
{key === "isRequired"
? artefact.isRequired
? "Yes"
: "No"
: key === "isSold"
? artefact.isSold
? "Yes"
: "No"
: key === "isCollected"
? artefact.isCollected
? "Yes"
: "No"
: key === "createdAt"
? artefact.createdAt
? new Date(artefact.createdAt).toLocaleDateString("en-GB")
: ""
: artefact[key]?.toString() || ""}
|
))}
))}
);
}
// Warehouse Component
export default function Warehouse() {
const [currentPage, setCurrentPage] = useState(1);
const [showLogModal, setShowLogModal] = useState(false);
const [showBulkLogModal, setShowBulkLogModal] = useState(false);
const [editArtefact, setEditArtefact] = useState(null);
const [filters, setFilters] = useState>({
id: "",
name: "",
earthquakeId: "",
location: "",
description: "",
isRequired: "",
isSold: "",
isCollected: "",
createdAt: "",
earthquakeCode: "",
});
const [isFiltering, setIsFiltering] = useState(false);
const [sortConfig, setSortConfig] = useState<{
key: keyof ExtendedArtefact;
direction: "asc" | "desc";
} | null>(null);
const { data, error, isLoading, mutate } = useSWR("/api/warehouse", fetcher);
const filteredArtefacts = useMemo(() => {
if (!data || !data.artefacts) return [];
setIsFiltering(true);
const result = applyFilters(data.artefacts, filters);
setIsFiltering(false);
return result;
}, [filters, data]);
const currentArtefacts = filteredArtefacts;
const totalArtefacts = data?.artefacts.length || 0;
const today = new Date();
const artefactsAddedToday = data?.artefacts.filter(
(a: ExtendedArtefact) => new Date(a.createdAt).toDateString() === today.toDateString()
).length;
const artefactsSoldToday = data?.artefacts.filter(
(a: ExtendedArtefact) => a.isSold && new Date(a.createdAt).toDateString() === today.toDateString()
).length;
const clearFilters = () => {
setFilters({
id: "",
name: "",
earthquakeId: "",
location: "",
description: "",
isRequired: "",
isSold: "",
isCollected: "",
createdAt: "",
earthquakeCode: "",
});
setSortConfig(null);
};
return (
{/* Artefact Counts */}
Total Artefacts: {totalArtefacts}
Added Today: {artefactsAddedToday}
Sold Today: {artefactsSoldToday}
{/* Buttons */}
{/* Table Container */}
{isFiltering && (
)}
setSortConfig(null)}
/>
{/* Modals */}
{showLogModal &&
setShowLogModal(false)} />}
{showBulkLogModal && setShowBulkLogModal(false)} />}
{editArtefact && setEditArtefact(null)} />}
);
}