From b819874fad6baff823b41d435cc8c41da969a7ff Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 14 Aug 2025 11:40:03 +0800 Subject: [PATCH 01/12] added more icon for group card item --- src/components/Catalogue/cardComponent.jsx | 2 +- src/components/DataStudio/groupCardItem.jsx | 42 ++++++++++++++++++--- src/pages/GroupView.jsx | 4 +- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/components/Catalogue/cardComponent.jsx b/src/components/Catalogue/cardComponent.jsx index 9a67ce8..8401680 100644 --- a/src/components/Catalogue/cardComponent.jsx +++ b/src/components/Catalogue/cardComponent.jsx @@ -14,7 +14,7 @@ const CardComponent = ({ imageSrc, itemTitle, itemDescription, expanded }) => { alt={itemTitle} loading={expanded ? "eager" : "lazy"} onLoad={() => setIsImageLoaded(true)} - height="180px" + height="180px" width="100%" /> diff --git a/src/components/DataStudio/groupCardItem.jsx b/src/components/DataStudio/groupCardItem.jsx index b7315e2..d237f24 100644 --- a/src/components/DataStudio/groupCardItem.jsx +++ b/src/components/DataStudio/groupCardItem.jsx @@ -1,8 +1,8 @@ import { Box } from "@chakra-ui/react"; import CardComponent from "../Catalogue/cardComponent"; +import { FiMoreVertical } from "react-icons/fi"; -const GroupCardItem = ({ itemTitle, itemDescription, imageSrc }) => { - +const GroupCardItem = ({ item }) => { return ( <> {/* Base Card Container */} @@ -10,16 +10,46 @@ const GroupCardItem = ({ itemTitle, itemDescription, imageSrc }) => { cursor="pointer" w="300px" borderRadius="md" + position="relative" + onClick={(e) => { + e.stopPropagation(); + console.log("Card clicked"); + }} > + + {/* 3 Dot Menu Icon */} + { + e.stopPropagation(); + console.log("More options clicked"); + }} + display="flex" + alignItems="center" + justifyContent="center" + p={0.5} + > + + ); }; -export default GroupCardItem; \ No newline at end of file +export default GroupCardItem; diff --git a/src/pages/GroupView.jsx b/src/pages/GroupView.jsx index f7dbc72..caf6193 100644 --- a/src/pages/GroupView.jsx +++ b/src/pages/GroupView.jsx @@ -161,9 +161,7 @@ function GroupView() { {collection.items.map(item => ( ))} From ebd89a1080fa8533dd4f708c398f06b7fae388b3 Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 14 Aug 2025 11:50:11 +0800 Subject: [PATCH 02/12] Added group item menu --- src/components/DataStudio/groupCardItem.jsx | 7 +++-- src/components/DataStudio/groupItemMenu.jsx | 31 +++++++++++++++++++++ src/pages/GroupView.jsx | 1 + 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/components/DataStudio/groupItemMenu.jsx diff --git a/src/components/DataStudio/groupCardItem.jsx b/src/components/DataStudio/groupCardItem.jsx index d237f24..a389449 100644 --- a/src/components/DataStudio/groupCardItem.jsx +++ b/src/components/DataStudio/groupCardItem.jsx @@ -1,8 +1,11 @@ import { Box } from "@chakra-ui/react"; import CardComponent from "../Catalogue/cardComponent"; import { FiMoreVertical } from "react-icons/fi"; +import { useNavigate } from "react-router-dom"; + +const GroupCardItem = ({ item, colID }) => { + const navigate = useNavigate() -const GroupCardItem = ({ item }) => { return ( <> {/* Base Card Container */} @@ -13,7 +16,7 @@ const GroupCardItem = ({ item }) => { position="relative" onClick={(e) => { e.stopPropagation(); - console.log("Card clicked"); + navigate(`/studio/${colID}/${item.id}`); }} > { + return ( + + + + + + + + + + + Edit Artefact + + + Manage Associations + + + + + + ); +}; + +export default GroupItemMenu; \ No newline at end of file diff --git a/src/pages/GroupView.jsx b/src/pages/GroupView.jsx index caf6193..cd668b7 100644 --- a/src/pages/GroupView.jsx +++ b/src/pages/GroupView.jsx @@ -161,6 +161,7 @@ function GroupView() { {collection.items.map(item => ( ))} From b464c789ee103de1a2793bfb5091f698a955c89f Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 14 Aug 2025 12:54:10 +0800 Subject: [PATCH 03/12] added empty component manage associations dialog, enhanced functionality for group item menu --- src/components/DataStudio/groupCardItem.jsx | 42 +++++++------------ src/components/DataStudio/groupItemMenu.jsx | 27 ++++++++---- .../DataStudio/manageAssociationsDialog.jsx | 8 ++++ 3 files changed, 42 insertions(+), 35 deletions(-) create mode 100644 src/components/DataStudio/manageAssociationsDialog.jsx diff --git a/src/components/DataStudio/groupCardItem.jsx b/src/components/DataStudio/groupCardItem.jsx index a389449..c9a084b 100644 --- a/src/components/DataStudio/groupCardItem.jsx +++ b/src/components/DataStudio/groupCardItem.jsx @@ -1,10 +1,12 @@ -import { Box } from "@chakra-ui/react"; +import { Box, useDisclosure } from "@chakra-ui/react"; import CardComponent from "../Catalogue/cardComponent"; -import { FiMoreVertical } from "react-icons/fi"; import { useNavigate } from "react-router-dom"; +import GroupItemMenu from "./groupItemMenu"; +import ManageAssociationsDialog from "./manageAssociationsDialog" const GroupCardItem = ({ item, colID }) => { - const navigate = useNavigate() + const navigate = useNavigate(); + const { isOpen, onOpen, onClose } = useDisclosure(); return ( <> @@ -26,31 +28,19 @@ const GroupCardItem = ({ item, colID }) => { expanded={true} /> - {/* 3 Dot Menu Icon */} - { - e.stopPropagation(); - console.log("More options clicked"); - }} - display="flex" - alignItems="center" - justifyContent="center" - p={0.5} - > - + {/* 3 Dot Menu */} + e.stopPropagation()}> + navigate(`/studio/${colID}/${item.id}`)} + onManage={onOpen} + /> + + {/* Placeholder Modal for Manage Associations */} + {isOpen && ( + + )} ); }; diff --git a/src/components/DataStudio/groupItemMenu.jsx b/src/components/DataStudio/groupItemMenu.jsx index 4aeb822..885b338 100644 --- a/src/components/DataStudio/groupItemMenu.jsx +++ b/src/components/DataStudio/groupItemMenu.jsx @@ -1,24 +1,33 @@ import { Box, Menu, Portal } from "@chakra-ui/react"; import { CgMoreVerticalAlt } from "react-icons/cg"; -const GroupItemMenu = () => { +const GroupItemMenu = ({ onEdit, onManage }) => { return ( - + - - + + - + Edit Artefact - + Manage Associations @@ -28,4 +37,4 @@ const GroupItemMenu = () => { ); }; -export default GroupItemMenu; \ No newline at end of file +export default GroupItemMenu; diff --git a/src/components/DataStudio/manageAssociationsDialog.jsx b/src/components/DataStudio/manageAssociationsDialog.jsx new file mode 100644 index 0000000..5971958 --- /dev/null +++ b/src/components/DataStudio/manageAssociationsDialog.jsx @@ -0,0 +1,8 @@ + +function ManageAssociationsDialog() { + return ( +
ManageAssociationsDialog
+ ) +} + +export default ManageAssociationsDialog \ No newline at end of file From 777eacb221af305d9f4edb4241360f09061057db Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 14 Aug 2025 15:14:22 +0800 Subject: [PATCH 04/12] Added basic structure for manageassociationdialog --- src/components/DataStudio/groupCardItem.jsx | 26 ++++++---- .../DataStudio/manageAssociationsDialog.jsx | 47 +++++++++++++++++-- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/components/DataStudio/groupCardItem.jsx b/src/components/DataStudio/groupCardItem.jsx index c9a084b..3bb9c75 100644 --- a/src/components/DataStudio/groupCardItem.jsx +++ b/src/components/DataStudio/groupCardItem.jsx @@ -1,12 +1,13 @@ -import { Box, useDisclosure } from "@chakra-ui/react"; +import { Box } from "@chakra-ui/react"; import CardComponent from "../Catalogue/cardComponent"; import { useNavigate } from "react-router-dom"; import GroupItemMenu from "./groupItemMenu"; -import ManageAssociationsDialog from "./manageAssociationsDialog" +import ManageAssociationsDialog from "./manageAssociationsDialog"; +import { useState } from "react"; const GroupCardItem = ({ item, colID }) => { const navigate = useNavigate(); - const { isOpen, onOpen, onClose } = useDisclosure(); + const [isOpen, setIsOpen] = useState(false); return ( <> @@ -29,18 +30,25 @@ const GroupCardItem = ({ item, colID }) => { /> {/* 3 Dot Menu */} - e.stopPropagation()}> + e.stopPropagation()} + > navigate(`/studio/${colID}/${item.id}`)} - onManage={onOpen} + onManage={() => setIsOpen(true)} /> - {/* Placeholder Modal for Manage Associations */} - {isOpen && ( - - )} + {/* Centered Manage Associations Dialog */} + setIsOpen(false)} + item={item} + /> ); }; diff --git a/src/components/DataStudio/manageAssociationsDialog.jsx b/src/components/DataStudio/manageAssociationsDialog.jsx index 5971958..6754e9c 100644 --- a/src/components/DataStudio/manageAssociationsDialog.jsx +++ b/src/components/DataStudio/manageAssociationsDialog.jsx @@ -1,8 +1,45 @@ +import { Button, CloseButton, Dialog, Portal } from "@chakra-ui/react"; -function ManageAssociationsDialog() { - return ( -
ManageAssociationsDialog
- ) +function ManageAssociationsDialog({ isOpen, onClose, item }) { + + return ( + + + + + + + Manage Associations + + + +

+ Placeholder content for managing associations. + You can replace this with your actual form or UI + later. +

+
+{/* + + + + + + */} + + + + +
+
+
+
+ ); } -export default ManageAssociationsDialog \ No newline at end of file +export default ManageAssociationsDialog; From ad126528ad745f17dee467059195e111064ed7df Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 14 Aug 2025 15:33:04 +0800 Subject: [PATCH 05/12] Added fetching association --- .../DataStudio/manageAssociationsDialog.jsx | 44 +++++ src/pages/GroupView.jsx | 176 +++++++++--------- 2 files changed, 132 insertions(+), 88 deletions(-) diff --git a/src/components/DataStudio/manageAssociationsDialog.jsx b/src/components/DataStudio/manageAssociationsDialog.jsx index 6754e9c..b70cc00 100644 --- a/src/components/DataStudio/manageAssociationsDialog.jsx +++ b/src/components/DataStudio/manageAssociationsDialog.jsx @@ -1,6 +1,50 @@ import { Button, CloseButton, Dialog, Portal } from "@chakra-ui/react"; +import { useEffect, useState } from "react"; +import ToastWizard from "../toastWizard"; +import server, { JSONResponse } from "../../networking" function ManageAssociationsDialog({ isOpen, onClose, item }) { + const [association, setAssociation] = useState(null) + + const fetchAssociation = async () => { + try { + const response = await server.get(`/cdn/retrieveAssociationInfo/${item.id}`); + + if (response.data instanceof JSONResponse) { + if (response.data.isErrorStatus()) { + const errObject = { + response: { + data: response.data + } + }; + throw new Error(errObject); + } + + // Success case + setAssociation(response.data.raw.data) + } else { + throw new Error("Unexpected response format"); + } + } catch (err) { + if (err.response && err.response.data instanceof JSONResponse) { + console.log("Error response in fetching association:", err.response.data.fullMessage()); + if (err.response.data.userErrorType()) { + ToastWizard.standard("error", "An Error Occured", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') + } else { + ToastWizard.standard("error", "An Error Occured", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') + } + } else { + console.log("Unexpected error in fetching association:", err); + ToastWizard.standard("error", "An Error Occured", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') + } + } + } + + useEffect(() => { + fetchAssociation() + }, [item]) + + console.log(association) return ( { - const fetchCollection = async () => { - try { - setLoading(true); - const response = await server.get(`/cdn/collection/${colID}`); - - if (response.data instanceof JSONResponse) { - if (response.data.isErrorStatus()) { - const errObject = { - response: { - data: response.data - } - }; - throw errObject; - } + const fetchCollection = async () => { + try { + setLoading(true); + const response = await server.get(`/cdn/collection/${colID}`); - // Success case - format the data - let formattedData; - const apiData = response.data.raw.data; + if (response.data instanceof JSONResponse) { + if (response.data.isErrorStatus()) { + const errObject = { + response: { + data: response.data + } + }; + throw errObject; + } - if (apiData.type === 'book') { - formattedData = { - type: 'book', - name: apiData.title, - description: apiData.subtitle || '', - items: apiData.mmArtefacts.map(art => ({ - id: art.id, - title: art.name, - description: art.description, - image: art.image || null - })) - }; - } else if (apiData.type === 'category') { - formattedData = { - type: 'category', - name: apiData.name, - description: apiData.description || '', - items: apiData.members.map(art => ({ - id: art.id, - title: art.name, - description: art.description, - image: art.image - })) - }; - } else if (apiData.type === 'batch') { - formattedData = { - type: 'batch', - name: apiData.name || `Batch ${apiData.id}`, - description: apiData.description || '', - items: apiData.artefacts.map(art => ({ - id: art.id, - title: art.name, - description: art.description, - image: art.image || null - })) - }; - } else { - throw new Error('Unknown collection type'); - } + // Success case - format the data + let formattedData; + const apiData = response.data.raw.data; - setCollection(formattedData); + if (apiData.type === 'book') { + formattedData = { + type: 'book', + name: apiData.title, + description: apiData.subtitle || '', + items: apiData.mmArtefacts.map(art => ({ + id: art.id, + title: art.name, + description: art.description, + image: art.image || null + })) + }; + } else if (apiData.type === 'category') { + formattedData = { + type: 'category', + name: apiData.name, + description: apiData.description || '', + items: apiData.members.map(art => ({ + id: art.id, + title: art.name, + description: art.description, + image: art.image + })) + }; + } else if (apiData.type === 'batch') { + formattedData = { + type: 'batch', + name: apiData.name || `Batch ${apiData.id}`, + description: apiData.description || '', + items: apiData.artefacts.map(art => ({ + id: art.id, + title: art.name, + description: art.description, + image: art.image || null + })) + }; } else { - throw new Error("Unexpected response format"); + throw new Error('Unknown collection type'); } - } catch (err) { - if (err.response && err.response.data instanceof JSONResponse) { - console.log("Error response in collection request:", err.response.data.fullMessage()); - - if (err.response.data.userErrorType()) { - ToastWizard.standard( - "error", - "An Error Occured", - "We could not fetch the collection data. Please try again later.", - 3000, - true, - fetchCollection, - 'Retry' - ); - } else { - ToastWizard.standard( - "error", - "An Error Occured", - "We could not fetch the collection data. Please try again later.", - 3000, - true, - fetchCollection, - 'Retry' - ); - } + + setCollection(formattedData); + } else { + throw new Error("Unexpected response format"); + } + } catch (err) { + if (err.response && err.response.data instanceof JSONResponse) { + console.log("Error response in collection request:", err.response.data.fullMessage()); + + if (err.response.data.userErrorType()) { + ToastWizard.standard( + "error", + "An Error Occured", + "We could not fetch the collection data. Please try again later.", + 3000, + true, + fetchCollection, + 'Retry' + ); } else { - console.log("Unexpected error in collection request:", err); ToastWizard.standard( "error", "An Error Occured", @@ -114,11 +101,24 @@ function GroupView() { 'Retry' ); } - } finally { - setLoading(false); + } else { + console.log("Unexpected error in collection request:", err); + ToastWizard.standard( + "error", + "An Error Occured", + "We could not fetch the collection data. Please try again later.", + 3000, + true, + fetchCollection, + 'Retry' + ); } - }; + } finally { + setLoading(false); + } + }; + useEffect(() => { fetchCollection(); }, [colID]); From 2ff0b6b66d1c34a4928ccda3294e7d0a59639468 Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 14 Aug 2025 16:22:34 +0800 Subject: [PATCH 06/12] added association card --- src/components/DataStudio/associationCard.jsx | 39 +++++++++++++++++++ .../DataStudio/manageAssociationsDialog.jsx | 26 ++++++++----- 2 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 src/components/DataStudio/associationCard.jsx diff --git a/src/components/DataStudio/associationCard.jsx b/src/components/DataStudio/associationCard.jsx new file mode 100644 index 0000000..577ec1a --- /dev/null +++ b/src/components/DataStudio/associationCard.jsx @@ -0,0 +1,39 @@ +import { Card, Box, Text, Flex, Switch } from "@chakra-ui/react"; +import { HiCheck, HiX } from "react-icons/hi"; +import { useState } from "react"; + +function AssociationCard({ associationData }) { + const [isMember, setIsMember] = useState(associationData?.isMember || false); + + const handleToggle = () => { + setIsMember((prev) => !prev); + // Optionally, call backend API to update membership status here + }; + + if (!associationData) return null; + + return ( + + + + {/* Left content: title and description */} + + + {associationData.name || "Untitled"} + + + {associationData.description} + + + + {/* Right: membership switch */} + + {/* */} + + + + + ); +} + +export default AssociationCard; diff --git a/src/components/DataStudio/manageAssociationsDialog.jsx b/src/components/DataStudio/manageAssociationsDialog.jsx index b70cc00..7130bb5 100644 --- a/src/components/DataStudio/manageAssociationsDialog.jsx +++ b/src/components/DataStudio/manageAssociationsDialog.jsx @@ -1,7 +1,8 @@ -import { Button, CloseButton, Dialog, Portal } from "@chakra-ui/react"; +import { Button, CloseButton, Dialog, Portal, Box } from "@chakra-ui/react"; import { useEffect, useState } from "react"; import ToastWizard from "../toastWizard"; import server, { JSONResponse } from "../../networking" +import AssociationCard from "./associationCard"; function ManageAssociationsDialog({ isOpen, onClose, item }) { const [association, setAssociation] = useState(null) @@ -52,6 +53,7 @@ function ManageAssociationsDialog({ isOpen, onClose, item }) { placement="center" onEscapeKeyDown={onClose} onInteractOutside={onClose} + size={"lg"} > @@ -61,20 +63,26 @@ function ManageAssociationsDialog({ isOpen, onClose, item }) { Manage Associations - -

- Placeholder content for managing associations. - You can replace this with your actual form or UI - later. -

+ + + {association + ?.slice() + .sort((a, b) => (b.isMember === true) - (a.isMember === true)) + .map((assoc) => ( + + ))} + -{/* + - */} + From bda186a3fcaa035c2a56df4ab21d6c210fcfde03 Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 14 Aug 2025 17:56:05 +0800 Subject: [PATCH 07/12] Added logic for manage associations --- src/components/DataStudio/associationCard.jsx | 23 ++- .../DataStudio/associationSwitch.jsx | 25 +++ src/components/DataStudio/groupCardItem.jsx | 3 +- .../DataStudio/manageAssociationsDialog.jsx | 152 +++++++++++++++--- src/pages/GroupView.jsx | 1 + 5 files changed, 163 insertions(+), 41 deletions(-) create mode 100644 src/components/DataStudio/associationSwitch.jsx diff --git a/src/components/DataStudio/associationCard.jsx b/src/components/DataStudio/associationCard.jsx index 577ec1a..900e7a8 100644 --- a/src/components/DataStudio/associationCard.jsx +++ b/src/components/DataStudio/associationCard.jsx @@ -1,22 +1,14 @@ -import { Card, Box, Text, Flex, Switch } from "@chakra-ui/react"; -import { HiCheck, HiX } from "react-icons/hi"; -import { useState } from "react"; - -function AssociationCard({ associationData }) { - const [isMember, setIsMember] = useState(associationData?.isMember || false); - - const handleToggle = () => { - setIsMember((prev) => !prev); - // Optionally, call backend API to update membership status here - }; +import { Card, Box, Text, Flex } from "@chakra-ui/react"; +import AssociationSwitch from "./associationSwitch"; +function AssociationCard({ associationData, isMember, onToggle }) { if (!associationData) return null; return ( - {/* Left content: title and description */} + {/* Left: title & description */} {associationData.name || "Untitled"} @@ -28,7 +20,10 @@ function AssociationCard({ associationData }) { {/* Right: membership switch */} - {/* */} + @@ -36,4 +31,4 @@ function AssociationCard({ associationData }) { ); } -export default AssociationCard; +export default AssociationCard; \ No newline at end of file diff --git a/src/components/DataStudio/associationSwitch.jsx b/src/components/DataStudio/associationSwitch.jsx new file mode 100644 index 0000000..234666a --- /dev/null +++ b/src/components/DataStudio/associationSwitch.jsx @@ -0,0 +1,25 @@ +import { Switch } from "@chakra-ui/react"; +import { HiCheck, HiX } from "react-icons/hi"; + +const AssociationSwitch = ({ isMember, onToggle }) => { + return ( + onToggle(details.checked)} + > + + + + } + > + + + + + + ); +}; + +export default AssociationSwitch; \ No newline at end of file diff --git a/src/components/DataStudio/groupCardItem.jsx b/src/components/DataStudio/groupCardItem.jsx index 3bb9c75..97db8f3 100644 --- a/src/components/DataStudio/groupCardItem.jsx +++ b/src/components/DataStudio/groupCardItem.jsx @@ -5,7 +5,7 @@ import GroupItemMenu from "./groupItemMenu"; import ManageAssociationsDialog from "./manageAssociationsDialog"; import { useState } from "react"; -const GroupCardItem = ({ item, colID }) => { +const GroupCardItem = ({ item, colID, fetchCollection }) => { const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); @@ -48,6 +48,7 @@ const GroupCardItem = ({ item, colID }) => { isOpen={isOpen} onClose={() => setIsOpen(false)} item={item} + fetchCollection={fetchCollection} /> ); diff --git a/src/components/DataStudio/manageAssociationsDialog.jsx b/src/components/DataStudio/manageAssociationsDialog.jsx index 7130bb5..8fc13bc 100644 --- a/src/components/DataStudio/manageAssociationsDialog.jsx +++ b/src/components/DataStudio/manageAssociationsDialog.jsx @@ -4,8 +4,12 @@ import ToastWizard from "../toastWizard"; import server, { JSONResponse } from "../../networking" import AssociationCard from "./associationCard"; -function ManageAssociationsDialog({ isOpen, onClose, item }) { - const [association, setAssociation] = useState(null) +function ManageAssociationsDialog({ isOpen, onClose, item, fetchCollection }) { + const [association, setAssociation] = useState(null); + const [originalStates, setOriginalStates] = useState({}); + const [currentStates, setCurrentStates] = useState({}); + const [hasChanges, setHasChanges] = useState(false); + const [isSaving, setIsSaving] = useState(false); const fetchAssociation = async () => { try { @@ -22,7 +26,17 @@ function ManageAssociationsDialog({ isOpen, onClose, item }) { } // Success case - setAssociation(response.data.raw.data) + const associationData = response.data.raw.data; + setAssociation(associationData); + + // Initialize original and current states + const initialStates = {}; + associationData.forEach(assoc => { + initialStates[assoc.id] = assoc.isMember; + }); + setOriginalStates(initialStates); + setCurrentStates(initialStates); + setHasChanges(false); } else { throw new Error("Unexpected response format"); } @@ -30,29 +44,100 @@ function ManageAssociationsDialog({ isOpen, onClose, item }) { if (err.response && err.response.data instanceof JSONResponse) { console.log("Error response in fetching association:", err.response.data.fullMessage()); if (err.response.data.userErrorType()) { - ToastWizard.standard("error", "An Error Occured", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') + ToastWizard.standard("error", "An Error Occurred", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') } else { - ToastWizard.standard("error", "An Error Occured", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') + ToastWizard.standard("error", "An Error Occurred", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') } } else { console.log("Unexpected error in fetching association:", err); - ToastWizard.standard("error", "An Error Occured", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') + ToastWizard.standard("error", "An Error Occurred", "Unable to fetch associations. Please try again later.", 3000, true, fetchAssociation, 'Retry') } } - } + }; - useEffect(() => { - fetchAssociation() - }, [item]) + const handleAssociationChange = (assocId, isMember) => { + const newStates = { ...currentStates, [assocId]: isMember }; + setCurrentStates(newStates); + + // Check if there are any changes + const hasAnyChanges = Object.keys(newStates).some(id => + newStates[id] !== originalStates[id] + ); + setHasChanges(hasAnyChanges); + }; + + const handleReset = () => { + setCurrentStates(originalStates); + setHasChanges(false); + }; + + const handleSave = async () => { + if (!hasChanges) return; + + setIsSaving(true); + try { + // Only include items that have changed + const colIDs = Object.keys(currentStates) + .filter(id => currentStates[id] !== originalStates[id]) + .map(id => ({ + colID: id, + isMember: currentStates[id] + })); + + const payload = { + artefactID: item.id, + colIDs: colIDs + }; + + const response = await server.post('/studio/artefact/updateAssociation', payload); + + if (response.data instanceof JSONResponse) { + if (response.data.isErrorStatus()) { + throw new Error(response.data.fullMessage()); + } - console.log(association) + // Success + ToastWizard.standard("success", "Success", "Associations updated successfully.", 3000); + setOriginalStates(currentStates); + setHasChanges(false); + } else { + throw new Error("Unexpected response format"); + } + } catch (err) { + console.error("Error saving associations:", err); + ToastWizard.standard("error", "Error", "Failed to save associations. Please try again.", 3000); + } finally { + setIsSaving(false); + onClose(); + fetchCollection() + } + }; + + const handleClose = () => { + if (hasChanges) { + // You might want to show a confirmation dialog here + const confirmClose = window.confirm("You have unsaved changes. Are you sure you want to close?"); + if (!confirmClose) return; + } + + // Reset states when closing + setCurrentStates(originalStates); + setHasChanges(false); + onClose(); + }; + + useEffect(() => { + if (isOpen && item) { + fetchAssociation(); + } + }, [item, isOpen]); return ( @@ -65,26 +150,41 @@ function ManageAssociationsDialog({ isOpen, onClose, item }) { - {association - ?.slice() - .sort((a, b) => (b.isMember === true) - (a.isMember === true)) - .map((assoc) => ( - - ))} + {association + ?.slice() + .sort((a, b) => (originalStates[b.id] === true) - (originalStates[a.id] === true)) + .map((assoc) => ( + handleAssociationChange(assoc.id, checked)} + /> + ))} - + - + - + @@ -94,4 +194,4 @@ function ManageAssociationsDialog({ isOpen, onClose, item }) { ); } -export default ManageAssociationsDialog; +export default ManageAssociationsDialog; \ No newline at end of file diff --git a/src/pages/GroupView.jsx b/src/pages/GroupView.jsx index 992ce46..cac6387 100644 --- a/src/pages/GroupView.jsx +++ b/src/pages/GroupView.jsx @@ -163,6 +163,7 @@ function GroupView() { key={item.id} colID={colID} item={item} + fetchCollection={fetchCollection} /> ))} From 49c4128224ef9c6a92200d1391646e304729925b Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 14 Aug 2025 18:22:31 +0800 Subject: [PATCH 08/12] Linked manage assoc dialog to editor card --- src/components/DataStudio/editorCard.jsx | 10 +++++++++- src/components/DataStudio/groupCardItem.jsx | 2 +- src/components/DataStudio/manageAssociationsDialog.jsx | 10 +++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/DataStudio/editorCard.jsx b/src/components/DataStudio/editorCard.jsx index 0b1e50b..013277d 100644 --- a/src/components/DataStudio/editorCard.jsx +++ b/src/components/DataStudio/editorCard.jsx @@ -6,6 +6,7 @@ import MetadataToggle from "./metadataToggle"; import ArtefactEditorActionBar from "./artefactEditorActionBar"; import ToastWizard from "../toastWizard"; import FigureDisplaySection from "./figureDisplay"; +import ManageAssociationsDialog from "./manageAssociationsDialog"; // Helper function to render text with labeled entities highlighted function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { @@ -91,6 +92,7 @@ function EditorCard({ metadata, artefactId, refreshArtefactData }) { const [isEditing, setIsEditing] = useState(false); const [hoveredIndex, setHoveredIndex] = useState(null); const hoverTimeoutRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); // Change detection states const [originalArtefactData, setOriginalArtefactData] = useState({}); @@ -164,7 +166,7 @@ function EditorCard({ metadata, artefactId, refreshArtefactData }) { Artefact Details -