Added edit-artefact api route and rearranged warehouse
This commit is contained in:
parent
74aea1a86a
commit
01b312a14b
@ -29,7 +29,6 @@ export async function POST(req: Request) {
|
||||
|
||||
// Trying to update a different user than themselves
|
||||
// Only available to admins
|
||||
// todo add senior scientists being able to update their juniors
|
||||
if (userId && userId !== user.id) {
|
||||
if (user.role !== "ADMIN") {
|
||||
return NextResponse.json({ message: "Not authorised" }, { status: 401 });
|
||||
|
||||
86
src/app/api/warehouse/edit-artefact/route.ts
Normal file
86
src/app/api/warehouse/edit-artefact/route.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { apiAuthMiddleware } from "@utils/apiAuthMiddleware";
|
||||
import { prisma } from "@utils/prisma";
|
||||
import { writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const id = formData.get("id") as string;
|
||||
const name = formData.get("name") as string | null;
|
||||
const description = formData.get("description") as string | null;
|
||||
const location = formData.get("location") as string | null;
|
||||
const earthquakeCode = formData.get("earthquakeCode") as string | null;
|
||||
const image = formData.get("image") as File | null;
|
||||
|
||||
const authResult = await apiAuthMiddleware();
|
||||
if ("user" in authResult === false) return authResult;
|
||||
|
||||
const { user } = authResult;
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({ error: "Artefact ID required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const artefact = await prisma.artefact.findUnique({
|
||||
where: { id: parseInt(id) },
|
||||
});
|
||||
|
||||
if (!artefact) {
|
||||
return NextResponse.json({ error: "Artefact not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (user.role !== "ADMIN" && user.role !== "SCIENTIST") {
|
||||
return NextResponse.json({ error: "Not authorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (user.role === "SCIENTIST") {
|
||||
const scientist = await prisma.scientist.findUnique({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
include: {
|
||||
subordinates: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!scientist || scientist.level !== "SENIOR") {
|
||||
return NextResponse.json({ message: "Not authorised" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
let earthquakeId = artefact.earthquakeId;
|
||||
if (earthquakeCode) {
|
||||
const linkedEarthquake = await prisma.earthquake.findUnique({ where: { code: earthquakeCode } });
|
||||
if (!linkedEarthquake) {
|
||||
return NextResponse.json({ error: "Earthquake code not found" }, { status: 400 });
|
||||
}
|
||||
earthquakeId = linkedEarthquake.id;
|
||||
}
|
||||
|
||||
let imageName = artefact.imageName;
|
||||
if (image) {
|
||||
const buffer = Buffer.from(await image.arrayBuffer());
|
||||
const extension = image.type === "image/jpeg" ? "jpg" : "png";
|
||||
imageName = `${name || artefact.name}-${new Date().toLocaleDateString("en-GB")}.${extension}`;
|
||||
const imagePath = join(process.cwd(), "public", imageName);
|
||||
await writeFile(imagePath, buffer);
|
||||
}
|
||||
|
||||
const updatedArtefact = await prisma.artefact.update({
|
||||
where: { id: parseInt(id) },
|
||||
data: {
|
||||
name: name || artefact.name,
|
||||
description: description || artefact.description,
|
||||
warehouseArea: location || artefact.warehouseArea,
|
||||
earthquakeId,
|
||||
imageName,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ message: "Artefact updated successfully", artefact: updatedArtefact }, { status: 200 });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -77,198 +77,6 @@ function FilterInput({
|
||||
);
|
||||
}
|
||||
|
||||
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 [image, setImage] = useState<File | null>(null);
|
||||
|
||||
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
const file = e.target.files[0];
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
setError("Image size must be less than 5MB");
|
||||
return;
|
||||
}
|
||||
if (!["image/jpeg", "image/png"].includes(file.type)) {
|
||||
setError("Only JPEG or PNG images are allowed");
|
||||
return;
|
||||
}
|
||||
setImage(file);
|
||||
}
|
||||
};
|
||||
|
||||
async function handleLog() {
|
||||
if (!name || !type || !description || !location || !earthquakeCode || !warehouseLocation) {
|
||||
setError("All fields are required.");
|
||||
return;
|
||||
}
|
||||
if (!validateEarthquakeCode(earthquakeCode)) {
|
||||
setError("Earthquake Code must be in format: EX-M.M-Country-##### (e.g., EC-3.9-Belgium-05467)");
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("name", name);
|
||||
formData.append("type", type);
|
||||
formData.append("description", description);
|
||||
formData.append("location", location);
|
||||
formData.append("earthquakeCode", earthquakeCode);
|
||||
formData.append("warehouseLocation", warehouseLocation);
|
||||
if (image) {
|
||||
formData.append("image", image);
|
||||
}
|
||||
|
||||
await axios.post("/api/warehouse/log", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
|
||||
alert(`Logged ${name} to storage: ${warehouseLocation}`);
|
||||
onClose();
|
||||
} catch {
|
||||
setError("Failed to log artefact. Please try again.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-neutral-900 bg-opacity-50 flex justify-center items-center z-50"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6 border border-neutral-300">
|
||||
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Log New Artefact</h3>
|
||||
{error && <p className="text-red-600 text-sm mb-2">{error}</p>}
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Artefact Name"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type (e.g., Lava, Tephra, Ash)"
|
||||
value={type}
|
||||
onChange={(e) => setType(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="Artefact Type"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16"
|
||||
aria-label="Artefact Description"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Location"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Artefact Location"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Earthquake Code (e.g., EC-3.9-Belgium-05467)"
|
||||
value={earthquakeCode}
|
||||
onChange={(e) => setEarthquakeCode(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="Earthquake ID"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Warehouse Location (e.g., A-12)"
|
||||
value={warehouseLocation}
|
||||
onChange={(e) => setWarehouseLocation(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="Storage Location"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/jpeg,image/png"
|
||||
onChange={handleImageChange}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Artefact Image"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isRequired}
|
||||
onChange={(e) => setIsRequired(e.target.checked)}
|
||||
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
|
||||
aria-label="Required Artefact"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<label className="text-sm text-neutral-600">Required (Not for Sale)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 mt-4">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 bg-neutral-200 text-neutral-800 rounded-md hover:bg-neutral-300 font-medium"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleLog}
|
||||
className={`px-4 py-2 bg-blue-600 text-white rounded-md font-medium flex items-center gap-2 ${
|
||||
isSubmitting ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"
|
||||
}`}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<svg
|
||||
className="animate-spin h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
Logging...
|
||||
</>
|
||||
) : (
|
||||
"Log Artefact"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Modal Component for Bulk Logging
|
||||
function BulkLogModal({ onClose }: { onClose: () => void }) {
|
||||
const [palletNote, setPalletNote] = useState("");
|
||||
@ -372,20 +180,16 @@ function BulkLogModal({ onClose }: { onClose: () => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
// 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"));
|
||||
function LogModal({ onClose }: { onClose: () => void }) {
|
||||
const [name, setName] = useState("");
|
||||
const [type, setType] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [earthquakeCode, setEarthquakeCode] = useState("");
|
||||
const [warehouseLocation, setWarehouseLocation] = useState("");
|
||||
const [isRequired, setIsRequired] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// todo add display of image
|
||||
const [image, setImage] = useState<File | null>(null);
|
||||
|
||||
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
@ -393,8 +197,196 @@ function EditModal({ artefact, onClose }: { artefact: ExtendedArtefact; onClose:
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!name || !description || !location || !earthquakeCode || !createdAt) {
|
||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
const file = e.target.files[0];
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
setError("Image size must be less than 5MB");
|
||||
return;
|
||||
}
|
||||
if (!["image/jpeg", "image/png"].includes(file.type)) {
|
||||
setError("Only JPEG or PNG images are allowed");
|
||||
return;
|
||||
}
|
||||
setImage(file);
|
||||
}
|
||||
};
|
||||
|
||||
async function handleLog() {
|
||||
if (!name || !type || !description || !earthquakeCode || !warehouseLocation) {
|
||||
setError("All fields are required.");
|
||||
return;
|
||||
}
|
||||
if (!validateEarthquakeCode(earthquakeCode)) {
|
||||
setError("Earthquake Code must be in format: EX-M.M-Country-##### (e.g., EC-3.9-Belgium-05467)");
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("name", name);
|
||||
formData.append("type", type);
|
||||
formData.append("description", description);
|
||||
formData.append("earthquakeCode", earthquakeCode);
|
||||
formData.append("warehouseLocation", warehouseLocation);
|
||||
if (image) {
|
||||
formData.append("image", image);
|
||||
}
|
||||
|
||||
await axios.post("/api/warehouse/log", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
|
||||
alert(`Logged ${name} to storage: ${warehouseLocation}`);
|
||||
onClose();
|
||||
} catch {
|
||||
setError("Failed to log artefact. Please try again.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-neutral-900 bg-opacity-50 flex justify-center items-center z-50"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6 border border-neutral-300">
|
||||
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Log New Artefact</h3>
|
||||
{error && <p className="text-red-600 text-sm mb-2">{error}</p>}
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/jpeg,image/png"
|
||||
onChange={handleImageChange}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Artefact Image"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Artefact Name"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type (e.g., Lava, Tephra, Ash)"
|
||||
value={type}
|
||||
onChange={(e) => setType(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="Artefact Type"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16"
|
||||
aria-label="Artefact Description"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Earthquake Code (e.g., EC-3.9-Belgium-05467)"
|
||||
value={earthquakeCode}
|
||||
onChange={(e) => setEarthquakeCode(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="Earthquake ID"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Warehouse Location (e.g., A-12)"
|
||||
value={warehouseLocation}
|
||||
onChange={(e) => setWarehouseLocation(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="Storage Location"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isRequired}
|
||||
onChange={(e) => setIsRequired(e.target.checked)}
|
||||
className="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
|
||||
aria-label="Required Artefact"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<label className="text-sm text-neutral-600">Required (Not for Sale)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 mt-4">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 bg-neutral-200 text-neutral-800 rounded-md hover:bg-neutral-300 font-medium"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleLog}
|
||||
className={`px-4 py-2 bg-blue-600 text-white rounded-md font-medium flex items-center gap-2 ${
|
||||
isSubmitting ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"
|
||||
}`}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<svg
|
||||
className="animate-spin h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
Logging...
|
||||
</>
|
||||
) : (
|
||||
"Log Artefact"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Modal Component for Editing Artefact
|
||||
function EditModal({ artefact, onClose }: { artefact: ExtendedArtefact; onClose: () => void }) {
|
||||
const [name, setName] = useState(artefact.name);
|
||||
const [type, setType] = useState("");
|
||||
const [description, setDescription] = useState(artefact.description);
|
||||
const [warehouseLocation, setWarehouseLocation] = useState("");
|
||||
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 [image, setImage] = useState<File | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// todo add display of image in public dir at /artefact.imageName
|
||||
// todo add display of artefact.createdAt date
|
||||
|
||||
function handleOverlayClick(e: { target: any; currentTarget: any }) {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!name || !type || !description || !earthquakeCode || !warehouseLocation) {
|
||||
setError("All fields are required.");
|
||||
return;
|
||||
}
|
||||
@ -413,7 +405,22 @@ function EditModal({ artefact, onClose }: { artefact: ExtendedArtefact; onClose:
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function handleImageChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
const file = e.target.files[0];
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
setError("Image size must be less than 5MB");
|
||||
return;
|
||||
}
|
||||
if (!["image/jpeg", "image/png"].includes(file.type)) {
|
||||
setError("Only JPEG or PNG images are allowed");
|
||||
return;
|
||||
}
|
||||
setImage(file);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -424,15 +431,35 @@ function EditModal({ artefact, onClose }: { artefact: ExtendedArtefact; onClose:
|
||||
<h3 className="text-lg font-semibold mb-4 text-neutral-800">Edit Artefact</h3>
|
||||
{error && <p className="text-red-600 text-sm mb-2">{error}</p>}
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/jpeg,image/png"
|
||||
onChange={handleImageChange}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Artefact Image"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Artefact Name"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type (e.g., Lava, Tephra, Ash)"
|
||||
value={type}
|
||||
onChange={(e) => setType(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="Artefact Type"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500 h-16"
|
||||
@ -441,20 +468,23 @@ function EditModal({ artefact, onClose }: { artefact: ExtendedArtefact; onClose:
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
className="w-full p-2 border border-neutral-300 rounded-md placeholder-neutral-400 focus:ring-2 focus:ring-blue-500"
|
||||
aria-label="Artefact Location"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Earthquake Code (e.g., EC-3.9-Belgium-05467)"
|
||||
value={earthquakeCode}
|
||||
onChange={(e) => setEarthquakeCode(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="Earthquake ID"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Warehouse Location (e.g., A-12)"
|
||||
value={warehouseLocation}
|
||||
onChange={(e) => setWarehouseLocation(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="Storage Location"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -488,14 +518,6 @@ function EditModal({ artefact, onClose }: { artefact: ExtendedArtefact; onClose:
|
||||
/>
|
||||
<label className="text-sm text-neutral-600">Collected</label>
|
||||
</div>
|
||||
<input
|
||||
type="date"
|
||||
value={createdAt}
|
||||
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"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 mt-4">
|
||||
<button
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user