From a10d02ec0854c1261ae8cc1d31f0a2df6e149436 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Tue, 12 Aug 2025 01:45:56 +0800 Subject: [PATCH 01/19] Added DialogMain, PendingBatchCard, DialogNewBatchCard Component --- src/components/DataImport/DialogMain.jsx | 73 +++++++++++++++++++ src/components/DataImport/DialogNewBatch.jsx | 25 +++++++ .../DataImport/PendingBatchCard.jsx | 40 ++++++++++ src/pages/DataImport.jsx | 14 +++- 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 src/components/DataImport/DialogMain.jsx create mode 100644 src/components/DataImport/DialogNewBatch.jsx create mode 100644 src/components/DataImport/PendingBatchCard.jsx diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx new file mode 100644 index 0000000..956eeba --- /dev/null +++ b/src/components/DataImport/DialogMain.jsx @@ -0,0 +1,73 @@ +import { Button, Dialog, Portal, Flex, Box, Text, VStack } from "@chakra-ui/react"; +import DialogNewBatch from "./DialogNewBatch"; +import hp1 from "../../assets/hp1.png"; +import PendingBatchCard from "./PendingBatchCard"; + +function DialogMain({ pendingBatches }) { + return ( + + + + + + + + + + Upload Artefacts + + + + + + + + + + + + + + + + + + + + + + + Upload to Pending Batches + + + {pendingBatches.length === 0 ? ( + + No pending batches. + + ) : ( + pendingBatches.map((batch) => ( + console.log("Clicked batch", batch.id)} + /> + )) + )} + + + + + + + + + + + + ) +} + +export default DialogMain \ No newline at end of file diff --git a/src/components/DataImport/DialogNewBatch.jsx b/src/components/DataImport/DialogNewBatch.jsx new file mode 100644 index 0000000..5d23222 --- /dev/null +++ b/src/components/DataImport/DialogNewBatch.jsx @@ -0,0 +1,25 @@ +import { Button, Dialog, Portal, Flex, Box, Text } from "@chakra-ui/react" + +function DialogNewBatch() { + return ( + + + + + + + + + + Dialog Title + + + + + + + + ) +} + +export default DialogNewBatch \ No newline at end of file diff --git a/src/components/DataImport/PendingBatchCard.jsx b/src/components/DataImport/PendingBatchCard.jsx new file mode 100644 index 0000000..37f1578 --- /dev/null +++ b/src/components/DataImport/PendingBatchCard.jsx @@ -0,0 +1,40 @@ +import { Box, Flex, Image, Text } from "@chakra-ui/react"; + +const PendingBatchCard = ({ batchName, displayMessage, timestamp, thumbnail, onClick }) => { + return ( + + + {batchName} + + + {batchName} + + + {displayMessage || "Pending upload"} + + + {timestamp} + + + + + ); +}; + +export default PendingBatchCard; \ No newline at end of file diff --git a/src/pages/DataImport.jsx b/src/pages/DataImport.jsx index 8cfca43..804db69 100644 --- a/src/pages/DataImport.jsx +++ b/src/pages/DataImport.jsx @@ -6,7 +6,8 @@ import { useSelector } from "react-redux"; import ToastWizard from '../components/toastWizard' import server, { JSONResponse } from "../networking"; import BatchCard from "../components/DataImport/BatchCard"; -import DialogUpload from "../components/DataImport/DialogUpload"; +// import DialogUpload from "../components/DataImport/DialogUpload"; +import DialogMain from "../components/DataImport/DialogMain.jsx"; import CentredSpinner from "../components/centredSpinner.jsx"; const stageCollection = createListCollection({ @@ -20,7 +21,7 @@ const stageCollection = createListCollection({ ], }); -function formatTimestamp(dateString) { +export function formatTimestamp(dateString) { const created = new Date(dateString); const now = new Date(); @@ -151,6 +152,8 @@ function DataImport() { }); }, [batches, selectedStages]); + const pendingBatches = batches.filter(b => b.stage === "upload_pending"); + if (loading) { return ; } @@ -188,7 +191,12 @@ function DataImport() { {/* Upload Button */} - + ({ + ...batch, + created: formatTimestamp(batch.created) + }))} + /> {/* Second Row: Stage Filter + Upload Button */} From 2253ecde3886eae4a791a276885fe251d98b157b Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Tue, 12 Aug 2025 13:54:59 +0800 Subject: [PATCH 02/19] Modified PBC to have artefactCount instead of status --- src/components/DataImport/DialogMain.jsx | 12 ++++++++--- .../DataImport/PendingBatchCard.jsx | 12 ++++++----- src/pages/DataImport.jsx | 21 ++++++++++++------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index 956eeba..19b28c0 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -14,7 +14,7 @@ function DialogMain({ pendingBatches }) { - Upload Artefacts + Upload Artefacts to @@ -36,10 +36,16 @@ function DialogMain({ pendingBatches }) { + Upload to Pending Batches - + {pendingBatches.length === 0 ? ( No pending batches. @@ -49,7 +55,7 @@ function DialogMain({ pendingBatches }) { console.log("Clicked batch", batch.id)} diff --git a/src/components/DataImport/PendingBatchCard.jsx b/src/components/DataImport/PendingBatchCard.jsx index 37f1578..8d3b462 100644 --- a/src/components/DataImport/PendingBatchCard.jsx +++ b/src/components/DataImport/PendingBatchCard.jsx @@ -1,6 +1,6 @@ import { Box, Flex, Image, Text } from "@chakra-ui/react"; -const PendingBatchCard = ({ batchName, displayMessage, timestamp, thumbnail, onClick }) => { +const PendingBatchCard = ({ batchName, artefactCount, timestamp, thumbnail, onClick }) => { return ( - + {batchName} {batchName} - - {displayMessage || "Pending upload"} + + {artefactCount != null + ? `${artefactCount} Artefact${artefactCount === 1 ? '' : 's'}` + : "No artefacts"} {timestamp} diff --git a/src/pages/DataImport.jsx b/src/pages/DataImport.jsx index 804db69..fecff34 100644 --- a/src/pages/DataImport.jsx +++ b/src/pages/DataImport.jsx @@ -21,7 +21,7 @@ const stageCollection = createListCollection({ ], }); -export function formatTimestamp(dateString) { +function formatTimestamp(dateString) { const created = new Date(dateString); const now = new Date(); @@ -152,7 +152,16 @@ function DataImport() { }); }, [batches, selectedStages]); - const pendingBatches = batches.filter(b => b.stage === "upload_pending"); + const pendingBatches = useMemo(() => + batches + .filter(b => b.stage === "upload_pending") + .map(batch => ({ + ...batch, + created: formatTimestamp(batch.created), + artefactCount: batch.artefactSummary?.total ?? 0 + })) + , [batches]); + if (loading) { return ; @@ -191,12 +200,8 @@ function DataImport() { {/* Upload Button */} - ({ - ...batch, - created: formatTimestamp(batch.created) - }))} - /> + + {/* Second Row: Stage Filter + Upload Button */} From 2b2a80b55dec78ca34c59ff76778879ce5e57b07 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Tue, 12 Aug 2025 14:36:05 +0800 Subject: [PATCH 03/19] Added Custom FileUpload Button and Show File List --- src/components/DataImport/DialogMain.jsx | 6 +- src/components/DataImport/DialogNewBatch.jsx | 25 ---- .../DataImport/DialogUploadFile.jsx | 130 ++++++++++++++++++ 3 files changed, 133 insertions(+), 28 deletions(-) delete mode 100644 src/components/DataImport/DialogNewBatch.jsx create mode 100644 src/components/DataImport/DialogUploadFile.jsx diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index 19b28c0..cf703eb 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -1,5 +1,5 @@ import { Button, Dialog, Portal, Flex, Box, Text, VStack } from "@chakra-ui/react"; -import DialogNewBatch from "./DialogNewBatch"; +import DialogUploadFile from "./DialogUploadFile"; import hp1 from "../../assets/hp1.png"; import PendingBatchCard from "./PendingBatchCard"; @@ -21,7 +21,7 @@ function DialogMain({ pendingBatches }) { - + @@ -76,4 +76,4 @@ function DialogMain({ pendingBatches }) { ) } -export default DialogMain \ No newline at end of file +export default DialogMain; \ No newline at end of file diff --git a/src/components/DataImport/DialogNewBatch.jsx b/src/components/DataImport/DialogNewBatch.jsx deleted file mode 100644 index 5d23222..0000000 --- a/src/components/DataImport/DialogNewBatch.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Button, Dialog, Portal, Flex, Box, Text } from "@chakra-ui/react" - -function DialogNewBatch() { - return ( - - - - - - - - - - Dialog Title - - - - - - - - ) -} - -export default DialogNewBatch \ No newline at end of file diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx new file mode 100644 index 0000000..38d7e61 --- /dev/null +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -0,0 +1,130 @@ +import { Button, Dialog, Portal, Flex, Box, Text, IconButton, CloseButton } from "@chakra-ui/react"; +import { useRef, useState } from "react"; +import { HiUpload } from "react-icons/hi"; + +function DialogUploadFile() { + const fileInputRef = useRef(null); + const [selectedFiles, setSelectedFiles] = useState([]); + + const handleButtonClick = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (event) => { + const files = Array.from(event.target.files); // Convert FileList to array + if (files.length > 0) { + setSelectedFiles((prev) => [...prev, ...files]); + } + }; + + const removeFile = (fileToRemove) => { + setSelectedFiles((prev) => prev.filter((file) => file !== fileToRemove)); + }; + + const clearAll = () => { + setSelectedFiles([]); + }; + + return ( + + + + + + + + + + Upload Artefacts + + + Uploading to: New Batch + + + + + + {/* Selected Files List */} + {selectedFiles.length > 0 && ( + + + + {selectedFiles.length} file(s) selected + + + + + + {selectedFiles.map((file, index) => ( + + + {file.name} + + {(file.size / 1024).toFixed(1)} KB + + + removeFile(file)} + aria-label={`Remove ${file.name}`} + /> + + ))} + + + )} + + + + + + + + + + + + ) +} + +export default DialogUploadFile; From bcfcee1736095bec5f4bd0504991bb2a8d86c4d9 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Tue, 12 Aug 2025 16:37:22 +0800 Subject: [PATCH 04/19] Custom FileItem Component to show error/sucess status of file(s) --- src/components/DataImport/DialogMain.jsx | 2 +- .../DataImport/DialogUploadFile.jsx | 107 ++++++++++++------ src/components/DataImport/FileItem.jsx | 47 ++++++++ 3 files changed, 123 insertions(+), 33 deletions(-) create mode 100644 src/components/DataImport/FileItem.jsx diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index cf703eb..95f3405 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -45,7 +45,7 @@ function DialogMain({ pendingBatches }) { Upload to Pending Batches - + {pendingBatches.length === 0 ? ( No pending batches. diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index 38d7e61..0dde539 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -1,32 +1,91 @@ -import { Button, Dialog, Portal, Flex, Box, Text, IconButton, CloseButton } from "@chakra-ui/react"; +import { Button, Dialog, Portal, Flex, Box, Text, CloseButton } from "@chakra-ui/react"; import { useRef, useState } from "react"; import { HiUpload } from "react-icons/hi"; +import server from "../../networking"; +import ToastWizard from "../../components/toastWizard"; +import FileItem from "./FileItem"; function DialogUploadFile() { const fileInputRef = useRef(null); const [selectedFiles, setSelectedFiles] = useState([]); + const [fileStatuses, setFileStatuses] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(false); const handleButtonClick = () => { fileInputRef.current?.click(); }; const handleFileChange = (event) => { - const files = Array.from(event.target.files); // Convert FileList to array + const files = Array.from(event.target.files); if (files.length > 0) { setSelectedFiles((prev) => [...prev, ...files]); + // Clear statuses for newly added files + const newStatuses = { ...fileStatuses }; + files.forEach((file) => { + delete newStatuses[file.name]; // remove old status + }); + setFileStatuses(newStatuses); } }; const removeFile = (fileToRemove) => { setSelectedFiles((prev) => prev.filter((file) => file !== fileToRemove)); + setFileStatuses((prev) => { + const updated = { ...prev }; + delete updated[fileToRemove.name]; + return updated; + }); }; const clearAll = () => { setSelectedFiles([]); + setFileStatuses({}); + }; + + const handleUpload = async () => { + if (selectedFiles.length === 0 || isSubmitting) return; + + const formData = new FormData(); + selectedFiles.forEach((file) => { + formData.append("file", file); + }); + + setIsSubmitting(true); + + try { + const res = await server.post("/dataImport/upload", formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); + + const data = res.data; + const updates = data.raw?.updates || {}; + const batchID = data.raw?.batchID; + + setFileStatuses(updates); + + ToastWizard.standard("success", "Upload Complete", `Batch ID: ${batchID}. Ready for processing.`); + + } catch (err) { + const updates = err?.response?.data?.raw?.updates; + if (updates) { + setFileStatuses(updates); + } else { + // If no detailed errors, mark all files as failed + const fallbackStatus = {}; + selectedFiles.forEach((file) => { + fallbackStatus[file.name] = "ERROR: Failed to upload."; + }); + setFileStatuses(fallbackStatus); + } + + ToastWizard.standard("error", "Upload Failed", "Some or all files failed to upload."); + } finally { + setIsSubmitting(false); + } }; return ( - + @@ -35,10 +94,10 @@ function DialogUploadFile() { - Upload Artefacts + Upload Artefacts - Uploading to: New Batch + To: New Batch Choose Files @@ -81,28 +140,12 @@ function DialogUploadFile() { bg="gray.50" > {selectedFiles.map((file, index) => ( - - - {file.name} - - {(file.size / 1024).toFixed(1)} KB - - - removeFile(file)} - aria-label={`Remove ${file.name}`} - /> - + file={file} + statusMsg={fileStatuses[file.name]} + onRemove={removeFile} + /> ))} @@ -110,12 +153,12 @@ function DialogUploadFile() { - + { clearAll(); }} /> @@ -124,7 +167,7 @@ function DialogUploadFile() { - ) + ); } -export default DialogUploadFile; +export default DialogUploadFile; \ No newline at end of file diff --git a/src/components/DataImport/FileItem.jsx b/src/components/DataImport/FileItem.jsx new file mode 100644 index 0000000..87ede1a --- /dev/null +++ b/src/components/DataImport/FileItem.jsx @@ -0,0 +1,47 @@ +import { Flex, Box, Text, CloseButton } from "@chakra-ui/react"; + +function FileItem({ file, statusMsg, onRemove }) { + const isError = statusMsg && statusMsg.startsWith("ERROR"); + const isSuccess = statusMsg && statusMsg.startsWith("SUCCESS"); + + return ( + + + + {file.name} + + {(file.size / 1024).toFixed(1)} KB + + + onRemove(file)} + aria-label={`Remove ${file.name}`} + /> + + {statusMsg && ( + + {statusMsg.replace(/^(SUCCESS|ERROR):\s*/, "")} + + )} + + ); +} + +export default FileItem; \ No newline at end of file From 4122d91c3863bbd3e169b48df8bb8b9c6cee1233 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Tue, 12 Aug 2025 18:02:53 +0800 Subject: [PATCH 05/19] Fixed batch number and Added Responsiveness --- src/components/DataImport/DialogMain.jsx | 19 ++++++++------ .../DataImport/DialogUploadFile.jsx | 2 +- src/components/DataImport/FileItem.jsx | 4 +-- .../DataImport/PendingBatchCard.jsx | 21 ++++++++------- src/pages/DataImport.jsx | 26 ++++++++++++++----- 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index 95f3405..491a1e5 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -1,11 +1,11 @@ -import { Button, Dialog, Portal, Flex, Box, Text, VStack } from "@chakra-ui/react"; +import { Button, Dialog, Portal, Flex, Box, Text, VStack, CloseButton } from "@chakra-ui/react"; import DialogUploadFile from "./DialogUploadFile"; import hp1 from "../../assets/hp1.png"; import PendingBatchCard from "./PendingBatchCard"; function DialogMain({ pendingBatches }) { return ( - + @@ -18,7 +18,7 @@ function DialogMain({ pendingBatches }) { - + @@ -35,7 +35,7 @@ function DialogMain({ pendingBatches }) { - + - Upload to Pending Batches + Upload to Pending Batches - + {pendingBatches.length === 0 ? ( No pending batches. @@ -54,7 +54,7 @@ function DialogMain({ pendingBatches }) { pendingBatches.map((batch) => ( + + + - + ) } diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index 0dde539..ad60c83 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -85,7 +85,7 @@ function DialogUploadFile() { }; return ( - + diff --git a/src/components/DataImport/FileItem.jsx b/src/components/DataImport/FileItem.jsx index 87ede1a..1946c35 100644 --- a/src/components/DataImport/FileItem.jsx +++ b/src/components/DataImport/FileItem.jsx @@ -12,9 +12,7 @@ function FileItem({ file, statusMsg, onRemove }) { px={2} borderRadius="md" bg={isError ? "red.50" : isSuccess ? "green.50" : undefined} - _hover={{ - bg: isError ? "red.100" : isSuccess ? "green.100" : "gray.100", - }} + _hover={{ bg: isError ? "red.100" : isSuccess ? "green.100" : "gray.100" }} mb={2} > diff --git a/src/components/DataImport/PendingBatchCard.jsx b/src/components/DataImport/PendingBatchCard.jsx index 8d3b462..732a259 100644 --- a/src/components/DataImport/PendingBatchCard.jsx +++ b/src/components/DataImport/PendingBatchCard.jsx @@ -4,33 +4,34 @@ const PendingBatchCard = ({ batchName, artefactCount, timestamp, thumbnail, onCl return ( - + {batchName} - + {batchName} - + {artefactCount != null ? `${artefactCount} Artefact${artefactCount === 1 ? '' : 's'}` : "No artefacts"} - + {timestamp} diff --git a/src/pages/DataImport.jsx b/src/pages/DataImport.jsx index fecff34..3fa5d5b 100644 --- a/src/pages/DataImport.jsx +++ b/src/pages/DataImport.jsx @@ -152,16 +152,30 @@ function DataImport() { }); }, [batches, selectedStages]); - const pendingBatches = useMemo(() => - batches + const batchData = useMemo(() => { + const sorted = [...batches].sort((a, b) => new Date(a.created) - new Date(b.created)); + + const batchNumberMap = {}; + const numberedBatches = sorted.map((batch, i) => { + batchNumberMap[batch.id] = i + 1; + return { + ...batch, + batchNumber: i + 1, + }; + }); + + const pendingBatches = numberedBatches .filter(b => b.stage === "upload_pending") .map(batch => ({ ...batch, created: formatTimestamp(batch.created), - artefactCount: batch.artefactSummary?.total ?? 0 - })) - , [batches]); + artefactCount: batch.artefactSummary?.total ?? 0, + })); + + return { pendingBatches, batchNumberMap }; + }, [batches]); + const { pendingBatches, batchNumberMap } = batchData; if (loading) { return ; @@ -262,7 +276,7 @@ function DataImport() { filtered.map((batch, idx) => ( Date: Wed, 13 Aug 2025 00:25:39 +0800 Subject: [PATCH 06/19] Modified to change state instead of nested dialog --- src/components/DataImport/DialogMain.jsx | 88 +++++----- .../DataImport/DialogUploadFile.jsx | 152 ++++++++---------- .../DataImport/PendingBatchCard.jsx | 4 +- 3 files changed, 117 insertions(+), 127 deletions(-) diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index 491a1e5..472cd2e 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -2,12 +2,28 @@ import { Button, Dialog, Portal, Flex, Box, Text, VStack, CloseButton } from "@c import DialogUploadFile from "./DialogUploadFile"; import hp1 from "../../assets/hp1.png"; import PendingBatchCard from "./PendingBatchCard"; +import { useState } from "react"; function DialogMain({ pendingBatches }) { + + const [view, setView] = useState("pendingList"); + const [isOpen, setIsOpen] = useState(false); + + const handleOpen = () => { + setView("pendingList"); + setIsOpen(true); + }; + + const handleClose = () => { + setView("pendingList"); + setIsOpen(false); + }; + return ( - + - + @@ -17,41 +33,29 @@ function DialogMain({ pendingBatches }) { Upload Artefacts to - - - - - - - - - - - - - - - - - - - - - - Upload to Pending Batches - - - {pendingBatches.length === 0 ? ( - - No pending batches. - - ) : ( - pendingBatches.map((batch) => ( + {view === "upload" && ( + + )} + + {view === "pendingList" && ( + + + + + + + + + + Pending Batches + + {pendingBatches.length === 0 ? ( + + No pending batches. + + ) : ( + + {pendingBatches.map((batch) => ( console.log("Clicked batch", batch.id)} /> - )) - )} - + ))} + + )} - - + + )} diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index ad60c83..5b6fea4 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -5,7 +5,7 @@ import server from "../../networking"; import ToastWizard from "../../components/toastWizard"; import FileItem from "./FileItem"; -function DialogUploadFile() { +function DialogUploadFile({ onCancel }) { const fileInputRef = useRef(null); const [selectedFiles, setSelectedFiles] = useState([]); const [fileStatuses, setFileStatuses] = useState({}); @@ -22,7 +22,7 @@ function DialogUploadFile() { // Clear statuses for newly added files const newStatuses = { ...fileStatuses }; files.forEach((file) => { - delete newStatuses[file.name]; // remove old status + delete newStatuses[file.name]; }); setFileStatuses(newStatuses); } @@ -65,6 +65,8 @@ function DialogUploadFile() { ToastWizard.standard("success", "Upload Complete", `Batch ID: ${batchID}. Ready for processing.`); + onCancel(); + } catch (err) { const updates = err?.response?.data?.raw?.updates; if (updates) { @@ -85,88 +87,72 @@ function DialogUploadFile() { }; return ( - - - - - - - - - - Upload Artefacts - - - To: New Batch - - + To: New Batch + + + + + + {/* Selected Files List */} + {selectedFiles.length > 0 && ( + + + + {selectedFiles.length} file(s) selected + + + + + + {selectedFiles.map((file, index) => ( + - - - - {/* Selected Files List */} - {selectedFiles.length > 0 && ( - - - - {selectedFiles.length} file(s) selected - - - - - - {selectedFiles.map((file, index) => ( - - ))} - - - )} - - - - { clearAll(); }} /> - - - - - - - + ))} + + + )} + + + + + + ); } diff --git a/src/components/DataImport/PendingBatchCard.jsx b/src/components/DataImport/PendingBatchCard.jsx index 732a259..563bdea 100644 --- a/src/components/DataImport/PendingBatchCard.jsx +++ b/src/components/DataImport/PendingBatchCard.jsx @@ -10,14 +10,14 @@ const PendingBatchCard = ({ batchName, artefactCount, timestamp, thumbnail, onCl shadow="sm" _hover={{ shadow: "md", cursor: "pointer" }} width={{ base: "220px", md: "300px" }} - height={{ base: "140px", md: "180px" }} + height={{ base: "65px", md: "75px" }} onClick={onClick} > {batchName} Date: Wed, 13 Aug 2025 04:00:39 +0800 Subject: [PATCH 07/19] Added UploadExisting State, force user to clear before adding new files to exisitng batch --- src/components/DataImport/DialogMain.jsx | 42 ++++++++++++++++-- .../DataImport/DialogUploadFile.jsx | 44 +++++++++++++------ 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index 472cd2e..6a87c7c 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -8,6 +8,9 @@ function DialogMain({ pendingBatches }) { const [view, setView] = useState("pendingList"); const [isOpen, setIsOpen] = useState(false); + const [currentBatchID, setCurrentBatchID] = useState(null); + const [selectedFiles, setSelectedFiles] = useState([]); + const [fileStatuses, setFileStatuses] = useState({}); const handleOpen = () => { setView("pendingList"); @@ -19,6 +22,11 @@ function DialogMain({ pendingBatches }) { setIsOpen(false); }; + const handleUploadSuccess = (batchID) => { + setCurrentBatchID(batchID); + setView("uploadExisting"); + }; + return ( @@ -33,14 +41,37 @@ function DialogMain({ pendingBatches }) { Upload Artefacts to - {view === "upload" && ( - + {view === "uploadNew" && ( + + )} + + {view === "uploadExisting" && ( + )} {view === "pendingList" && ( - + @@ -62,7 +93,10 @@ function DialogMain({ pendingBatches }) { artefactCount={batch.artefactCount} timestamp={batch.created} thumbnail={hp1} - onClick={() => console.log("Clicked batch", batch.id)} + onClick={() => { + setCurrentBatchID(batch.id); + setView("uploadExisting"); + }} /> ))} diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index 5b6fea4..3c2e200 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -1,20 +1,22 @@ -import { Button, Dialog, Portal, Flex, Box, Text, CloseButton } from "@chakra-ui/react"; +import { Button, Flex, Box, Text } from "@chakra-ui/react"; import { useRef, useState } from "react"; import { HiUpload } from "react-icons/hi"; import server from "../../networking"; import ToastWizard from "../../components/toastWizard"; import FileItem from "./FileItem"; -function DialogUploadFile({ onCancel }) { +function DialogUploadFile({ onCancel, onSuccess, toLabel, buttonLabel, batchID, selectedFiles, setSelectedFiles, fileStatuses, setFileStatuses }) { const fileInputRef = useRef(null); - const [selectedFiles, setSelectedFiles] = useState([]); - const [fileStatuses, setFileStatuses] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const handleButtonClick = () => { fileInputRef.current?.click(); }; + const hasSuccessfullyUploadedFiles = selectedFiles.some(file => + fileStatuses[file.name]?.startsWith("SUCCESS") + ); + const handleFileChange = (event) => { const files = Array.from(event.target.files); if (files.length > 0) { @@ -42,6 +44,13 @@ function DialogUploadFile({ onCancel }) { setFileStatuses({}); }; + const hasPreviousStatus = selectedFiles.some(file => fileStatuses[file.name]); + + const isDisabled = + selectedFiles.length === 0 || + isSubmitting || + hasPreviousStatus; + const handleUpload = async () => { if (selectedFiles.length === 0 || isSubmitting) return; @@ -50,6 +59,10 @@ function DialogUploadFile({ onCancel }) { formData.append("file", file); }); + if (batchID) { + formData.append("batchID", batchID); + } + setIsSubmitting(true); try { @@ -59,14 +72,15 @@ function DialogUploadFile({ onCancel }) { const data = res.data; const updates = data.raw?.updates || {}; - const batchID = data.raw?.batchID; + const returnedBatchID = data.raw?.batchID; setFileStatuses(updates); + ToastWizard.standard("success", "Upload Complete", `Batch ID: ${returnedBatchID}. Ready for processing.`); - ToastWizard.standard("success", "Upload Complete", `Batch ID: ${batchID}. Ready for processing.`); + if (onSuccess) { + onSuccess(returnedBatchID); + } - onCancel(); - } catch (err) { const updates = err?.response?.data?.raw?.updates; if (updates) { @@ -88,7 +102,7 @@ function DialogUploadFile({ onCancel }) { return ( <> - To: New Batch + To: {toLabel} - {/* Selected Files List */} {selectedFiles.length > 0 && ( @@ -142,16 +155,21 @@ function DialogUploadFile({ onCancel }) { )} + {hasPreviousStatus && ( + + Click "Remove All" to upload new files. + + )} + - ); } From 934d084e4180b338811d1c86514963da7abd1212 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 05:39:46 +0800 Subject: [PATCH 08/19] Modified from showing BatchID to BatchNumber --- src/components/DataImport/DialogMain.jsx | 19 +++++++++++++------ .../DataImport/DialogUploadFile.jsx | 15 ++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index 6a87c7c..2eb8e0d 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -9,6 +9,7 @@ function DialogMain({ pendingBatches }) { const [view, setView] = useState("pendingList"); const [isOpen, setIsOpen] = useState(false); const [currentBatchID, setCurrentBatchID] = useState(null); + const [currentBatchNumber, setCurrentBatchNumber] = useState(""); const [selectedFiles, setSelectedFiles] = useState([]); const [fileStatuses, setFileStatuses] = useState({}); @@ -18,17 +19,21 @@ function DialogMain({ pendingBatches }) { }; const handleClose = () => { - setView("pendingList"); setIsOpen(false); + setSelectedFiles([]); + setFileStatuses({}); + setCurrentBatchID(null); + setCurrentBatchNumber(""); }; - const handleUploadSuccess = (batchID) => { - setCurrentBatchID(batchID); + const handleUploadSuccess = (returnedBatchID) => { + setCurrentBatchID(returnedBatchID); setView("uploadExisting"); }; return ( - + @@ -58,9 +63,10 @@ function DialogMain({ pendingBatches }) { { setCurrentBatchID(batch.id); + setCurrentBatchNumber(batch.batchNumber); setView("uploadExisting"); }} /> @@ -107,7 +114,7 @@ function DialogMain({ pendingBatches }) { - + diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index 3c2e200..4ebc410 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -5,7 +5,7 @@ import server from "../../networking"; import ToastWizard from "../../components/toastWizard"; import FileItem from "./FileItem"; -function DialogUploadFile({ onCancel, onSuccess, toLabel, buttonLabel, batchID, selectedFiles, setSelectedFiles, fileStatuses, setFileStatuses }) { +function DialogUploadFile({ onCancel, onSuccess, toLabel, buttonLabel, batchID, batchNumber, selectedFiles, setSelectedFiles, fileStatuses, setFileStatuses }) { const fileInputRef = useRef(null); const [isSubmitting, setIsSubmitting] = useState(false); @@ -13,10 +13,6 @@ function DialogUploadFile({ onCancel, onSuccess, toLabel, buttonLabel, batchID, fileInputRef.current?.click(); }; - const hasSuccessfullyUploadedFiles = selectedFiles.some(file => - fileStatuses[file.name]?.startsWith("SUCCESS") - ); - const handleFileChange = (event) => { const files = Array.from(event.target.files); if (files.length > 0) { @@ -73,10 +69,15 @@ function DialogUploadFile({ onCancel, onSuccess, toLabel, buttonLabel, batchID, const data = res.data; const updates = data.raw?.updates || {}; const returnedBatchID = data.raw?.batchID; + const returnedBatchNumber = data.raw?.batchNumber; + const displayBatchNumber = batchNumber || returnedBatchNumber || returnedBatchID; setFileStatuses(updates); - ToastWizard.standard("success", "Upload Complete", `Batch ID: ${returnedBatchID}. Ready for processing.`); - + ToastWizard.standard( + "success", + "Upload Complete", + `Uploaded files to Batch ${displayBatchNumber}. Ready for processing.` + ); if (onSuccess) { onSuccess(returnedBatchID); } From 0fab6036b2b3b8069727e8da1b79ee4a54273758 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 11:36:37 +0800 Subject: [PATCH 09/19] Cleaned up codes --- src/components/DataImport/DialogMain.jsx | 69 ++++--------------- .../DataImport/DialogUploadFile.jsx | 30 +++++--- src/pages/DataImport.jsx | 10 ++- 3 files changed, 41 insertions(+), 68 deletions(-) diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index 2eb8e0d..fa01c83 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -2,40 +2,26 @@ import { Button, Dialog, Portal, Flex, Box, Text, VStack, CloseButton } from "@c import DialogUploadFile from "./DialogUploadFile"; import hp1 from "../../assets/hp1.png"; import PendingBatchCard from "./PendingBatchCard"; -import { useState } from "react"; +import { useEffect, useState } from "react"; function DialogMain({ pendingBatches }) { - - const [view, setView] = useState("pendingList"); const [isOpen, setIsOpen] = useState(false); - const [currentBatchID, setCurrentBatchID] = useState(null); - const [currentBatchNumber, setCurrentBatchNumber] = useState(""); - const [selectedFiles, setSelectedFiles] = useState([]); - const [fileStatuses, setFileStatuses] = useState({}); - - const handleOpen = () => { - setView("pendingList"); - setIsOpen(true); - }; + const [view, setView] = useState('pendingList'); + const [targetBatchID, setTargetBatchID] = useState(null); const handleClose = () => { setIsOpen(false); - setSelectedFiles([]); - setFileStatuses({}); - setCurrentBatchID(null); - setCurrentBatchNumber(""); - }; - - const handleUploadSuccess = (returnedBatchID) => { - setCurrentBatchID(returnedBatchID); - setView("uploadExisting"); - }; + setTimeout(() => { + setView('pendingList'); + setTargetBatchID(null); + }, 0); + } return ( + placement="center" size={{ base: "md", md: "xl" }} closeOnInteractOutside={false} closeOnEscape={false} open={isOpen} onOpenChange={(e) => setIsOpen(e.open)} unmountOnExit> - @@ -46,38 +32,12 @@ function DialogMain({ pendingBatches }) { Upload Artefacts to - {view === "uploadNew" && ( - - )} - - {view === "uploadExisting" && ( - - )} + {view === 'uploadExisting' && } {view === "pendingList" && ( - + @@ -100,8 +60,7 @@ function DialogMain({ pendingBatches }) { timestamp={batch.created} thumbnail={hp1} onClick={() => { - setCurrentBatchID(batch.id); - setCurrentBatchNumber(batch.batchNumber); + setTargetBatchID(batch.id); setView("uploadExisting"); }} /> @@ -114,7 +73,7 @@ function DialogMain({ pendingBatches }) { - + diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index 4ebc410..ffcc68f 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -1,13 +1,15 @@ import { Button, Flex, Box, Text } from "@chakra-ui/react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { HiUpload } from "react-icons/hi"; import server from "../../networking"; import ToastWizard from "../../components/toastWizard"; import FileItem from "./FileItem"; -function DialogUploadFile({ onCancel, onSuccess, toLabel, buttonLabel, batchID, batchNumber, selectedFiles, setSelectedFiles, fileStatuses, setFileStatuses }) { +function DialogUploadFile({ batchID, setBatchID }) { const fileInputRef = useRef(null); const [isSubmitting, setIsSubmitting] = useState(false); + const [selectedFiles, setSelectedFiles] = useState([]); + const [fileStatuses, setFileStatuses] = useState({}); const handleButtonClick = () => { fileInputRef.current?.click(); @@ -64,25 +66,25 @@ function DialogUploadFile({ onCancel, onSuccess, toLabel, buttonLabel, batchID, try { const res = await server.post("/dataImport/upload", formData, { headers: { "Content-Type": "multipart/form-data" }, + transformRequest: formData => formData }); const data = res.data; const updates = data.raw?.updates || {}; const returnedBatchID = data.raw?.batchID; - const returnedBatchNumber = data.raw?.batchNumber; - const displayBatchNumber = batchNumber || returnedBatchNumber || returnedBatchID; + if (returnedBatchID) { + setBatchID(returnedBatchID); + } setFileStatuses(updates); + ToastWizard.standard( "success", "Upload Complete", - `Uploaded files to Batch ${displayBatchNumber}. Ready for processing.` + `Uploaded files to Batch ${returnedBatchID || ''}. Ready for processing.` ); - if (onSuccess) { - onSuccess(returnedBatchID); - } - } catch (err) { + console.log(`Non-success response in file upload to batch ${batchID || 'New Batch'}`, err) const updates = err?.response?.data?.raw?.updates; if (updates) { setFileStatuses(updates); @@ -101,9 +103,15 @@ function DialogUploadFile({ onCancel, onSuccess, toLabel, buttonLabel, batchID, } }; + useEffect(() => { + return () => { + clearAll(); + } + }, []); + return ( <> - To: {toLabel} + To: {batchID || "New Batch"} - {buttonLabel} ({selectedFiles.length}) + {batchID ? 'Upload to Existing Batch': 'Create New Batch and Upload'} ({selectedFiles.length}) diff --git a/src/pages/DataImport.jsx b/src/pages/DataImport.jsx index 3fa5d5b..e4e6bf0 100644 --- a/src/pages/DataImport.jsx +++ b/src/pages/DataImport.jsx @@ -114,6 +114,8 @@ function DataImport() { }; }); + // here + setBatches(batchList); } else { @@ -176,6 +178,7 @@ function DataImport() { }, [batches]); const { pendingBatches, batchNumberMap } = batchData; + const getBatchNumber = (batchID) => batchNumberMap[batchID]; if (loading) { return ; @@ -214,8 +217,11 @@ function DataImport() { {/* Upload Button */} - - + {/* Second Row: Stage Filter + Upload Button */} From c8ac5c9f9d0d27c2d27a59d54e5631c3b5a1bf50 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 12:42:28 +0800 Subject: [PATCH 10/19] Added Done Button to be visible only after file successfully uploaded once --- src/components/DataImport/DialogMain.jsx | 16 ++++++------- .../DataImport/DialogUploadFile.jsx | 24 +++++++++++++++---- .../DataImport/PendingBatchCard.jsx | 14 +++++------ 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index fa01c83..927eddd 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -2,7 +2,7 @@ import { Button, Dialog, Portal, Flex, Box, Text, VStack, CloseButton } from "@c import DialogUploadFile from "./DialogUploadFile"; import hp1 from "../../assets/hp1.png"; import PendingBatchCard from "./PendingBatchCard"; -import { useEffect, useState } from "react"; +import { useState } from "react"; function DialogMain({ pendingBatches }) { const [isOpen, setIsOpen] = useState(false); @@ -32,22 +32,22 @@ function DialogMain({ pendingBatches }) { Upload Artefacts to - {view === 'uploadExisting' && } + {view === 'uploadExisting' && { handleClose() }} />} {view === "pendingList" && ( - + - + - + - + Pending Batches {pendingBatches.length === 0 ? ( - + No pending batches. ) : ( @@ -55,7 +55,7 @@ function DialogMain({ pendingBatches }) { {pendingBatches.map((batch) => ( { fileInputRef.current?.click(); @@ -77,6 +78,7 @@ function DialogUploadFile({ batchID, setBatchID }) { setBatchID(returnedBatchID); } setFileStatuses(updates); + setUploadSucceeded(true); ToastWizard.standard( "success", @@ -170,15 +172,29 @@ function DialogUploadFile({ batchID, setBatchID }) { )} - + + {uploadSucceeded && ( + + )} + - + ); } diff --git a/src/components/DataImport/PendingBatchCard.jsx b/src/components/DataImport/PendingBatchCard.jsx index 563bdea..616a799 100644 --- a/src/components/DataImport/PendingBatchCard.jsx +++ b/src/components/DataImport/PendingBatchCard.jsx @@ -9,8 +9,8 @@ const PendingBatchCard = ({ batchName, artefactCount, timestamp, thumbnail, onCl bg="white" shadow="sm" _hover={{ shadow: "md", cursor: "pointer" }} - width={{ base: "220px", md: "300px" }} - height={{ base: "65px", md: "75px" }} + width={{ base: "210px", md: "320px" }} + height={{ base: "60px", md: "80px" }} onClick={onClick} > @@ -18,20 +18,20 @@ const PendingBatchCard = ({ batchName, artefactCount, timestamp, thumbnail, onCl src={thumbnail || "/placeholder.jpg"} alt={batchName} height={{ base: "50px", md: "60px" }} - width={{ base: "60px", md: "80px" }} + width={{ base: "50px", md: "80px" }} borderRadius={{ base: "xs", md: "md" }} mr={{ base: "2", md: "4" }} /> - - + + {batchName} - + {artefactCount != null ? `${artefactCount} Artefact${artefactCount === 1 ? '' : 's'}` : "No artefacts"} - + {timestamp} From 0f9242bd9aea05bc43478a8da0be8a52e873fe21 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 14:39:23 +0800 Subject: [PATCH 11/19] Modified to auto update changes of batches --- src/components/DataImport/DialogMain.jsx | 6 +++--- src/components/DataImport/DialogUploadFile.jsx | 8 ++++++-- src/pages/DataImport.jsx | 1 - 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/DialogMain.jsx index 927eddd..7696876 100644 --- a/src/components/DataImport/DialogMain.jsx +++ b/src/components/DataImport/DialogMain.jsx @@ -4,7 +4,7 @@ import hp1 from "../../assets/hp1.png"; import PendingBatchCard from "./PendingBatchCard"; import { useState } from "react"; -function DialogMain({ pendingBatches }) { +function DialogMain({ pendingBatches, fetchBatches }) { const [isOpen, setIsOpen] = useState(false); const [view, setView] = useState('pendingList'); const [targetBatchID, setTargetBatchID] = useState(null); @@ -32,12 +32,12 @@ function DialogMain({ pendingBatches }) { Upload Artefacts to - {view === 'uploadExisting' && { handleClose() }} />} + {view === 'uploadExisting' && } {view === "pendingList" && ( - + diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index b4fcbe0..9e6f4ac 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -5,7 +5,7 @@ import server from "../../networking"; import ToastWizard from "../../components/toastWizard"; import FileItem from "./FileItem"; -function DialogUploadFile({ batchID, setBatchID, onClose }) { +function DialogUploadFile({ batchID, setBatchID, onClose, fetchBatches }) { const fileInputRef = useRef(null); const [isSubmitting, setIsSubmitting] = useState(false); const [selectedFiles, setSelectedFiles] = useState([]); @@ -79,6 +79,7 @@ function DialogUploadFile({ batchID, setBatchID, onClose }) { } setFileStatuses(updates); setUploadSucceeded(true); + fetchBatches(); ToastWizard.standard( "success", @@ -172,7 +173,10 @@ function DialogUploadFile({ batchID, setBatchID, onClose }) { )} - + + {uploadSucceeded && ( @@ -32,7 +39,13 @@ function DialogMain({ pendingBatches, fetchBatches }) { Upload Artefacts to - {view === 'uploadExisting' && } + {view === 'uploadExisting' && + b.id === targetBatchID) || null} + setBatchID={(id) => setTargetBatchID(id)} + onClose={handleClose} + fetchBatches={fetchBatches} + onBackToPending={() => setView("pendingList")} />} {view === "pendingList" && ( diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index 9e6f4ac..8490536 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -4,8 +4,12 @@ import { HiUpload } from "react-icons/hi"; import server from "../../networking"; import ToastWizard from "../../components/toastWizard"; import FileItem from "./FileItem"; +import { IoArrowBackCircleSharp } from "react-icons/io5"; +import PendingBatchCard from "./PendingBatchCard"; +import hp1 from "../../assets/hp1.png"; -function DialogUploadFile({ batchID, setBatchID, onClose, fetchBatches }) { +function DialogUploadFile({ batch, setBatchID, onClose, fetchBatches, onBackToPending }) { + const batchID = batch?.id; const fileInputRef = useRef(null); const [isSubmitting, setIsSubmitting] = useState(false); const [selectedFiles, setSelectedFiles] = useState([]); @@ -114,8 +118,30 @@ function DialogUploadFile({ batchID, setBatchID, onClose, fetchBatches }) { return ( <> - To: {batchID || "New Batch"} + + + + + + + + } variant="outline" - width="full" + width={{ base: "400px", md: "full" }} justifyContent="start" mb={4} onClick={handleButtonClick} @@ -136,68 +162,71 @@ function DialogUploadFile({ batchID, setBatchID, onClose, fetchBatches }) { Choose Files - {selectedFiles.length > 0 && ( - - - - {selectedFiles.length} file(s) selected - - - - - - {selectedFiles.map((file, index) => ( - - ))} + { + selectedFiles.length > 0 && ( + + + + {selectedFiles.length} file(s) selected + + + + + + {selectedFiles.map((file, index) => ( + + ))} + - - )} - - {hasPreviousStatus && ( - - Click "Remove All" to upload new files. - - )} - - - - {uploadSucceeded && ( + ) + } + + { + hasPreviousStatus && ( + + Click "Remove All" to upload new files. + + ) + } + + + + {uploadSucceeded && ( + + )} + - )} - - + ); diff --git a/src/components/DataImport/PendingBatchCard.jsx b/src/components/DataImport/PendingBatchCard.jsx index 616a799..52a2e24 100644 --- a/src/components/DataImport/PendingBatchCard.jsx +++ b/src/components/DataImport/PendingBatchCard.jsx @@ -9,7 +9,7 @@ const PendingBatchCard = ({ batchName, artefactCount, timestamp, thumbnail, onCl bg="white" shadow="sm" _hover={{ shadow: "md", cursor: "pointer" }} - width={{ base: "210px", md: "320px" }} + width={{ base: "210px", md: "350px" }} height={{ base: "60px", md: "80px" }} onClick={onClick} > From ceb421558ccce949726cffd5998cf61a2389d62b Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 17:10:15 +0800 Subject: [PATCH 13/19] Added isLoading, loadingText to show loading msg while uploading --- src/components/DataImport/DialogUploadFile.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index 8490536..58a134a 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -222,7 +222,8 @@ function DialogUploadFile({ batch, setBatchID, onClose, fetchBatches, onBackToPe variant="ArchPrimary" disabled={isDisabled} onClick={handleUpload} - width="fit-content" + isLoading={isSubmitting} + loadingText={batchID ? `Uploading ${selectedFiles.length} file(s)...` : `Creating batch...`} > {batchID ? 'Upload to Existing Batch' : 'Create New Batch and Upload'} ({selectedFiles.length}) From afe19b1d182e4c7fb1c34e1bd3c72b362afae702 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 17:21:26 +0800 Subject: [PATCH 14/19] Deleted DialogUpload, Changed name for DialogMain to UploadArtefact --- src/components/DataImport/DialogUpload.jsx | 256 ------------------ .../DataImport/DialogUploadFile.jsx | 2 +- .../{DialogMain.jsx => UploadArtefact.jsx} | 0 3 files changed, 1 insertion(+), 257 deletions(-) delete mode 100644 src/components/DataImport/DialogUpload.jsx rename src/components/DataImport/{DialogMain.jsx => UploadArtefact.jsx} (100%) diff --git a/src/components/DataImport/DialogUpload.jsx b/src/components/DataImport/DialogUpload.jsx deleted file mode 100644 index ff7c556..0000000 --- a/src/components/DataImport/DialogUpload.jsx +++ /dev/null @@ -1,256 +0,0 @@ -import { Button, FileUpload, VStack, Text, Box, CloseButton, Dialog, Portal, Badge } from "@chakra-ui/react"; -import { HiUpload } from "react-icons/hi"; -import { useState, useRef } from "react"; -import server from "../../networking"; -import ToastWizard from "../../components/toastWizard"; -import { FaPlus } from "react-icons/fa"; - -function DialogUpload() { - const [files, setFiles] = useState([]); - const [fileStatuses, setFileStatuses] = useState({}); - const [isSubmitting, setIsSubmitting] = useState(false); - const [batchID, setBatchID] = useState(null); - const [isConfirmed, setIsConfirmed] = useState(false); - const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); - const inputRef = useRef(); - const isUploading = useRef(false); - - // Handle file upload - const handleFileChange = async (e) => { - if (isUploading.current) return; - isUploading.current = true; - - const selected = Array.from(e.target.files); - setFiles(selected); - setFileStatuses({}); - if (selected.length === 0) return; - - const formData = new FormData(); - selected.forEach((file) => formData.append("file", file)); - - setIsSubmitting(true); - try { - const res = await server.post("/dataImport/upload", formData, { - headers: { "Content-Type": "multipart/form-data" }, - }); - - const data = res.data; - const updates = data.raw?.updates || {}; - const newBatchID = data.raw?.batchID; - - setFileStatuses(updates); - setBatchID(newBatchID); - setIsConfirmed(false); - - ToastWizard.standard("success", "Upload Complete", `Batch ID: ${newBatchID}. Ready for confirmation.`); - } catch (err) { - const updates = err?.response?.data?.raw?.updates; - if (updates) { - setFileStatuses(updates); - } - ToastWizard.standard("error", "Upload Failed", "Failed to upload files."); - } finally { - setIsSubmitting(false); - isUploading.current = false; - } - }; - - const handleConfirmBatch = async () => { - if (!batchID) return; - - setIsSubmitting(true); - try { - const res = await server.post("/dataImport/confirm", { batchID }); - - if (res.status === 200 || res.data.raw.success !== false) { - setIsConfirmed(true); - ToastWizard.standard("success", "Processing Started", `Batch ${batchID} is now being processed.`); - setIsConfirmDialogOpen(false); - - } else { - ToastWizard.standard("error", "Failed to Confirm", res.data.message || "Unknown error."); - } - } catch (err) { - const errorMsg = err?.response?.data?.message || "Failed to confirm batch."; - ToastWizard.standard("error", "Confirm Error", errorMsg); - - } finally { - setIsSubmitting(false); - } - }; - - return ( - - - - - - - - - - - Upload Files - - - - - - - - - - - - - {/* Custom file list with status and remove button */} - {files.length > 0 && ( - - {files.map((file) => { - const status = fileStatuses[file.name]; - return ( - - - - {file.name} - - - {(file.size / 1024).toFixed(1)} KB - - - - {status && ( - - {status.startsWith("ERROR") && ( - - Error - - )} - - {status} - - - { - setFiles((prev) => - prev.filter((f) => f.name !== file.name) - ); - setFileStatuses((prev) => { - const updated = { ...prev }; - delete updated[file.name]; - return updated; - }); - }} - /> - - )} - - ); - })} - - )} - - - - - - - {/* Only show Confirm button after upload */} - {batchID && !isConfirmed && ( - - )} - - {isConfirmed && ( - - Processing started for Batch {batchID} - - )} - - - - - - setIsConfirmDialogOpen(open)}> - - - - - Confirm Processing - - - Are you sure you want to process batch{" "} - - {batchID} - - ? This will start processing the artefacts in the batch. - - - - - - - - - - - - ); -} - -export default DialogUpload; \ No newline at end of file diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/DialogUploadFile.jsx index 58a134a..3d14878 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/DialogUploadFile.jsx @@ -2,7 +2,7 @@ import { Button, Flex, Box, Text } from "@chakra-ui/react"; import { useEffect, useRef, useState } from "react"; import { HiUpload } from "react-icons/hi"; import server from "../../networking"; -import ToastWizard from "../../components/toastWizard"; +import ToastWizard from "../toastWizard"; import FileItem from "./FileItem"; import { IoArrowBackCircleSharp } from "react-icons/io5"; import PendingBatchCard from "./PendingBatchCard"; diff --git a/src/components/DataImport/DialogMain.jsx b/src/components/DataImport/UploadArtefact.jsx similarity index 100% rename from src/components/DataImport/DialogMain.jsx rename to src/components/DataImport/UploadArtefact.jsx From 712adb4f372043714e710b6fbf159faccc313f4c Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 17:25:09 +0800 Subject: [PATCH 15/19] Change function name from DialogName to UploadArtefact --- src/components/DataImport/UploadArtefact.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DataImport/UploadArtefact.jsx b/src/components/DataImport/UploadArtefact.jsx index c2ec5eb..cbc5a85 100644 --- a/src/components/DataImport/UploadArtefact.jsx +++ b/src/components/DataImport/UploadArtefact.jsx @@ -4,7 +4,7 @@ import hp1 from "../../assets/hp1.png"; import PendingBatchCard from "./PendingBatchCard"; import { useState } from "react"; -function DialogMain({ pendingBatches, fetchBatches }) { +function UploadArtefact({ pendingBatches, fetchBatches }) { const [isOpen, setIsOpen] = useState(false); const [view, setView] = useState('pendingList'); const [targetBatchID, setTargetBatchID] = useState(null); @@ -96,4 +96,4 @@ function DialogMain({ pendingBatches, fetchBatches }) { ) } -export default DialogMain; \ No newline at end of file +export default UploadArtefact; \ No newline at end of file From dfd544679b54e05d0ee0b5ac24a0672991a59274 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 17:39:26 +0800 Subject: [PATCH 16/19] Change import from DialogMain to UploadArtefact --- src/pages/DataImport.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/DataImport.jsx b/src/pages/DataImport.jsx index c443436..ea91fae 100644 --- a/src/pages/DataImport.jsx +++ b/src/pages/DataImport.jsx @@ -7,7 +7,7 @@ import ToastWizard from '../components/toastWizard' import server, { JSONResponse } from "../networking"; import BatchCard from "../components/DataImport/BatchCard"; // import DialogUpload from "../components/DataImport/DialogUpload"; -import DialogMain from "../components/DataImport/DialogMain.jsx"; +import UploadArtefact from "../components/DataImport/UploadArtefact.jsx"; import CentredSpinner from "../components/centredSpinner.jsx"; const stageCollection = createListCollection({ @@ -217,7 +217,7 @@ function DataImport() { {/* Upload Button */} - From 22605771a4350d49d6601e1cb7192efe06847ecc Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Wed, 13 Aug 2025 18:35:37 +0800 Subject: [PATCH 17/19] Change File & Function Name, added toastwizard --- ...{DialogUploadFile.jsx => ArtefactUploadView.jsx} | 13 ++++++++----- .../{UploadArtefact.jsx => ImportMenuDialog.jsx} | 10 +++++----- src/pages/DataImport.jsx | 5 ++--- 3 files changed, 15 insertions(+), 13 deletions(-) rename src/components/DataImport/{DialogUploadFile.jsx => ArtefactUploadView.jsx} (94%) rename src/components/DataImport/{UploadArtefact.jsx => ImportMenuDialog.jsx} (95%) diff --git a/src/components/DataImport/DialogUploadFile.jsx b/src/components/DataImport/ArtefactUploadView.jsx similarity index 94% rename from src/components/DataImport/DialogUploadFile.jsx rename to src/components/DataImport/ArtefactUploadView.jsx index 3d14878..70467d4 100644 --- a/src/components/DataImport/DialogUploadFile.jsx +++ b/src/components/DataImport/ArtefactUploadView.jsx @@ -8,7 +8,7 @@ import { IoArrowBackCircleSharp } from "react-icons/io5"; import PendingBatchCard from "./PendingBatchCard"; import hp1 from "../../assets/hp1.png"; -function DialogUploadFile({ batch, setBatchID, onClose, fetchBatches, onBackToPending }) { +function ArtefactUploadView({ batch, setBatchID, onClose, fetchBatches, onBackToPending }) { const batchID = batch?.id; const fileInputRef = useRef(null); const [isSubmitting, setIsSubmitting] = useState(false); @@ -55,7 +55,10 @@ function DialogUploadFile({ batch, setBatchID, onClose, fetchBatches, onBackToPe hasPreviousStatus; const handleUpload = async () => { - if (selectedFiles.length === 0 || isSubmitting) return; + if (selectedFiles.length === 0 || isSubmitting) { + ToastWizard.standard("warning", "No Files Selected", "Please choose at least one file before uploading."); + return; + } const formData = new FormData(); selectedFiles.forEach((file) => { @@ -207,8 +210,8 @@ function DialogUploadFile({ batch, setBatchID, onClose, fetchBatches, onBackToPe {uploadSucceeded && ( - )} -