diff --git a/src/components/Catalogue/createGroup.jsx b/src/components/Catalogue/createGroup.jsx
new file mode 100644
index 0000000..194f5b9
--- /dev/null
+++ b/src/components/Catalogue/createGroup.jsx
@@ -0,0 +1,199 @@
+import { useState } from "react";
+import { Button, CloseButton, Dialog, Portal, Field, Input, Text, Box, useBreakpointValue } from "@chakra-ui/react";
+import { Tooltip } from "../../components/ui/tooltip"
+import { HiInformationCircle, HiPlus } from "react-icons/hi2";
+import server, { JSONResponse } from "../../networking";
+import ToastWizard from "../toastWizard";
+import GroupTypeToggle from "./groupTypeToggle";
+
+function CreateGroup({ fetchCatalogue }) {
+ const [groupType, setGroupType] = useState("book");
+ const [title, setTitle] = useState("");
+ const [subtitle, setSubtitle] = useState("");
+ const [isCreating, setIsCreating] = useState(false);
+ const [isOpen, setIsOpen] = useState(false);
+ const isMobile = useBreakpointValue({ base: true, md: false });
+
+ const validateForm = () => {
+ const errors = [];
+
+ if (!title.trim()) {
+ errors.push("Title is required");
+ } else if (title.trim().length > 25) {
+ errors.push("Title must be 25 characters or less");
+ }
+
+ if (!subtitle.trim()) {
+ errors.push("Subtitle is required");
+ } else if (subtitle.trim().length > 100) {
+ errors.push("Subtitle must be 100 characters or less");
+ }
+
+ return errors;
+ };
+
+ const handleCreateGroup = async () => {
+ const validationErrors = validateForm();
+ if (validationErrors.length > 0) {
+ ToastWizard.standard(
+ "error",
+ "All fields must be filled!",
+ validationErrors.join(". ")
+ );
+ return;
+ }
+
+ setIsCreating(true);
+ try {
+ const payload = {
+ title: title.trim(),
+ subtitle: subtitle.trim(),
+ type: groupType,
+ };
+
+ const response = await server.post("/studio/group/create", payload);
+
+ if (response.data instanceof JSONResponse) {
+ if (response.data.isErrorStatus()) {
+ const errObject = {
+ response: {
+ data: response.data
+ }
+ };
+ throw new Error(errObject);
+ }
+
+ // Success case
+ ToastWizard.standard(
+ "success",
+ "Group created successfully"
+ );
+
+ setTitle("");
+ setSubtitle("");
+ setGroupType("book");
+ setIsOpen(false);
+ fetchCatalogue()
+ } else {
+ throw new Error("Unexpected response format");
+ }
+ } catch (err) {
+ if (err.response && err.response.data instanceof JSONResponse) {
+ console.log("Error response in login request:", err.response.data.fullMessage());
+ if (err.response.data.userErrorType()) {
+ ToastWizard.standard("error", "Group creation failed", err.response.data.message);
+ } else {
+ ToastWizard.standard("error", "Group creation failed", "Something went wrong while creating your group. Please try again later.", 3000, true, handleCreateGroup, 'Retry');
+ }
+ } else {
+ console.log("Unexpected error in login request:", err);
+ ToastWizard.standard("error", "Group creation failed", "Something went wrong while creating your group. Please try again later.", 3000, true, handleCreateGroup, 'Retry');
+ }
+ setIsCreating(false);
+ }
+ };
+
+ const isTitleValid = title.trim().length > 0 && title.trim().length <= 25;
+ const isSubtitleValid = subtitle.trim().length > 0 && subtitle.trim().length <= 100;
+ const isFormValid = isTitleValid && isSubtitleValid;
+
+ return (
+ setIsOpen(e.open)}
+ >
+
+
+
+
+
+
+
+
+
+ Create New Group
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {groupType === "book"
+ ? "Book Title"
+ : "Category Name"}
+
+ setTitle(e.target.value)}
+ maxLength={25}
+ />
+
+ {title.length}/25 characters
+
+
+
+
+
+ {groupType === "book"
+ ? "Subtitle"
+ : "Description"}
+
+
+ setSubtitle(e.target.value)
+ }
+ maxLength={100}
+ />
+
+ {subtitle.length}/100 characters
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default CreateGroup;
\ No newline at end of file
diff --git a/src/components/Catalogue/groupTypeToggle.jsx b/src/components/Catalogue/groupTypeToggle.jsx
new file mode 100644
index 0000000..d5d8206
--- /dev/null
+++ b/src/components/Catalogue/groupTypeToggle.jsx
@@ -0,0 +1,73 @@
+import { HStack, SegmentGroup, Text, useBreakpointValue } from "@chakra-ui/react";
+import { LuBook, LuTag } from "react-icons/lu";
+
+const GroupTypeToggle = ({ value, onChange, size = "md" }) => {
+ const isMobile = useBreakpointValue({ base: true, md: false });
+
+ const items = [
+ {
+ value: "book",
+ label: (
+
+
+
+ Book
+
+
+ ),
+ },
+ {
+ value: "category",
+ label: (
+
+
+
+ Category
+
+
+ ),
+ },
+ ];
+
+ const segmentProps = {
+ sm: {
+ p: "1",
+ rounded: "lg",
+ width: "180px"
+ },
+ md: {
+ p: "1",
+ rounded: "lg",
+ width: "210px"
+ },
+ lg: {
+ p: "1.5",
+ rounded: "lg",
+ width: "240px"
+ }
+ };
+
+ const currentProps = segmentProps[size] || segmentProps.md;
+
+ return (
+ onChange(value)}
+ size={size}
+ bg="gray.50"
+ p={currentProps.p}
+ rounded={currentProps.rounded}
+ boxShadow="md"
+ gap="1"
+ mb={6}
+ >
+
+
+
+ );
+};
+
+export default GroupTypeToggle;
\ No newline at end of file
diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx
index 0c33bc3..3d77a96 100644
--- a/src/components/Catalogue/metadataDisplay.jsx
+++ b/src/components/Catalogue/metadataDisplay.jsx
@@ -112,8 +112,11 @@ function MetadataDisplay({ currentItem, isOpen }) {
throw errObject;
}
- // Success case
- setMetadata(response.data.raw.data.metadata);
+ setMetadata({
+ ...response.data.raw.data.metadata,
+ figureInfo: response.data.raw.figureInfo || {}
+ });
+
} else {
throw new Error("Unexpected response format");
}
@@ -279,6 +282,14 @@ function MetadataDisplay({ currentItem, isOpen }) {
}
loading="eager"
/>
+
+ {metadata.figureInfo[id] || "No Label"}
+
))}
diff --git a/src/components/DataStudio/deleteGroup.jsx b/src/components/DataStudio/deleteGroup.jsx
new file mode 100644
index 0000000..e6662fb
--- /dev/null
+++ b/src/components/DataStudio/deleteGroup.jsx
@@ -0,0 +1,104 @@
+import { Button, CloseButton, Dialog, Field, Input, Portal, Text } from '@chakra-ui/react';
+import ToastWizard from '../toastWizard';
+import { useState } from 'react';
+import { MdDeleteForever } from 'react-icons/md';
+import { useNavigate } from 'react-router-dom';
+import server, { JSONResponse } from '../../networking';
+
+function DeleteGroup({ isMobile, colID, colName }) {
+ const navigate = useNavigate()
+ const [dialogOpen, setDialogOpen] = useState(false);
+ const [deletingGroup, setDeletingGroup] = useState(false);
+ const [confirmGroupName, setConfirmGroupName] = useState('');
+
+ const handleDelete = async () => {
+ setDeletingGroup(true)
+ try {
+ const response = await server.post('/studio/group/delete', {groupID: colID})
+
+ if (response.data instanceof JSONResponse) {
+ if (response.data.isErrorStatus()) {
+ const errObject = {
+ response: {
+ data: response.data
+ }
+ };
+ throw new Error(errObject);
+ }
+
+ // Success case
+ navigate(-1)
+ ToastWizard.standard("success", "Group Deleted", "Group deleted successfully.");
+ } else {
+ throw new Error("Unexpected response format");
+ }
+ } catch (err) {
+ if (err.response && err.response.data instanceof JSONResponse) {
+ console.log("Error response in delete group request:", err.response.data.fullMessage());
+ if (err.response.data.userErrorType()) {
+ ToastWizard.standard("error", "Failed to delte group", err.response.data.message);
+ } else {
+ ToastWizard.standard("error", "Failed to delete group", "Unable to delete group. Please try again later.", 3000, true, handleDelete, 'Retry')
+ }
+ } else {
+ console.log("Unexpected error in deleting group:", err);
+ ToastWizard.standard("error", "Failed to delete group", "Unable to delete group. Please try again later.", 3000, true, handleDelete, 'Retry')
+ }
+ }
+ setDeletingGroup(false)
+ }
+
+ return (
+ setDialogOpen(e.open)} placement={'center'}>
+
+
+
+
+
+
+
+
+ Delete group?
+
+
+ This is a destructive action that cannot be undone.
+ The group and all associated data will be permanently deleted.
+ Please re-enter the group's name to proceed with deletion:
+
+ Group Name
+ setConfirmGroupName(e.target.value)}
+ />
+ Type in: {colName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default DeleteGroup
\ No newline at end of file
diff --git a/src/components/DataStudio/editorCard.jsx b/src/components/DataStudio/editorCard.jsx
index 013277d..c845cd8 100644
--- a/src/components/DataStudio/editorCard.jsx
+++ b/src/components/DataStudio/editorCard.jsx
@@ -87,7 +87,7 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) {
return {chunks};
}
-function EditorCard({ metadata, artefactId, refreshArtefactData }) {
+function EditorCard({ metadata = { metadata: {} }, artefactId, refreshArtefactData }) {
const [selectedTranscription, setSelectedTranscription] = useState("tradCN");
const [isEditing, setIsEditing] = useState(false);
const [hoveredIndex, setHoveredIndex] = useState(null);
@@ -155,7 +155,7 @@ function EditorCard({ metadata, artefactId, refreshArtefactData }) {
});
}
- const isMMArtefact = (metadata.metadata.english || metadata.metadata.tradCN || metadata.metadata.simplifiedCN || metadata.metadata.summary) ? true : false;
+ const isMMArtefact = (metadata?.metadata?.english || metadata?.metadata?.tradCN || metadata?.metadata?.simplifiedCN || metadata?.metadata?.summary) ? true : false;
return (
<>
@@ -346,7 +346,7 @@ function EditorCard({ metadata, artefactId, refreshArtefactData }) {
diff --git a/src/components/DataStudio/figureDisplay.jsx b/src/components/DataStudio/figureDisplay.jsx
index d47d465..df6a7d7 100644
--- a/src/components/DataStudio/figureDisplay.jsx
+++ b/src/components/DataStudio/figureDisplay.jsx
@@ -1,24 +1,28 @@
-import { Box, Flex, Text, Image, SkeletonCircle, Portal, Dialog, Button } from '@chakra-ui/react';
-import { MdOutlineDeleteForever } from 'react-icons/md';
-import { useState } from 'react';
-import server, { JSONResponse } from '../../networking';
-import ToastWizard from '../toastWizard';
+import { Box, Flex, Text, Image, SkeletonCircle, Portal, Dialog, Button } from "@chakra-ui/react";
+import { MdOutlineDeleteForever } from "react-icons/md";
+import { useState } from "react";
+import server, { JSONResponse } from "../../networking";
+import ToastWizard from "../toastWizard";
-function FigureDisplaySection({ artefactId, figureIds, getArtefactData, isEditing }) {
+function FigureDisplaySection({ artefactId, figureInfo, getArtefactData, isEditing }) {
const [deletingFigure, setDeletingFigure] = useState(null);
const [removing, setRemoving] = useState(false);
const [isConfirmDeleteOpen, setIsConfirmDeleteOpen] = useState(false);
const [selectedFigureId, setSelectedFigureId] = useState(null);
+ const figureIds = Object.keys(figureInfo || {});
const handleRemoveFigure = async (figureID) => {
setDeletingFigure(figureID);
setRemoving(true);
-
+
try {
- const response = await server.post('/studio/artefact/removeFigure', {
- artefactID: artefactId,
- figureID: figureID
- });
+ const response = await server.post(
+ "/studio/artefact/removeFigure",
+ {
+ artefactID: artefactId,
+ figureID: figureID,
+ }
+ );
if (response.data instanceof JSONResponse) {
if (response.data.isErrorStatus()) {
@@ -31,24 +35,33 @@ function FigureDisplaySection({ artefactId, figureIds, getArtefactData, isEditin
throw new Error("Unexpected response format");
}
} catch (err) {
- console.error("Error removing figure:", err);
-
- let errorMessage = "Failed to remove figure. Please try again.";
if (err.response && err.response.data instanceof JSONResponse) {
- errorMessage = err.response.data.message;
- } else if (err.message) {
- errorMessage = err.message;
+ console.log("Error response in login request:", err.response.data.fullMessage());
+ if (err.response.data.userErrorType()) {
+ ToastWizard.standard("error", "Failed to remove figure.", err.response.data.message);
+ } else {
+ ToastWizard.standard(
+ "error",
+ "Failed to remove figure. Please try again later.",
+ errorMessage,
+ 3000,
+ true,
+ handleRemoveFigure,
+ "Retry"
+ );
+ }
+ } else {
+ console.log("Unexpected error in removing figure:", err);
+ ToastWizard.standard(
+ "error",
+ "Failed to remove figure. Please try again later.",
+ errorMessage,
+ 3000,
+ true,
+ handleRemoveFigure,
+ "Retry"
+ );
}
-
- ToastWizard.standard(
- "error",
- "Remove failed",
- errorMessage,
- 3000,
- true,
- handleRemoveFigure,
- 'Retry'
- );
} finally {
setDeletingFigure(null);
setRemoving(false);
@@ -63,77 +76,125 @@ function FigureDisplaySection({ artefactId, figureIds, getArtefactData, isEditin
{figureIds.length > 0 ? (
- {figureIds.map(id => (
- (
+
- }
- loading="eager"
- />
-
- {/* Delete Overlay - only show when editing */}
- {isEditing && (
- {
- setSelectedFigureId(id);
- setIsConfirmDeleteOpen(true);
- }}
- cursor={deletingFigure === id ? "not-allowed" : "pointer"}
- pointerEvents={deletingFigure === id ? "none" : "auto"}
- >
-
+
+ }
+ loading="eager"
+ />
+
+ {/* Delete Overlay */}
+ {isEditing && (
+ {
+ setSelectedFigureId(id);
+ setIsConfirmDeleteOpen(true);
}}
- />
-
- )}
-
+ cursor={
+ deletingFigure === id
+ ? "not-allowed"
+ : "pointer"
+ }
+ pointerEvents={
+ deletingFigure === id
+ ? "none"
+ : "auto"
+ }
+ >
+
+
+ )}
+
+
+ {/* Label under image */}
+
+ {figureInfo[id] || "No Label"}
+
+
))}
) : (
- No associated figures
+
+ No associated figures
+
)}
- setIsConfirmDeleteOpen(e.open)}>
+ setIsConfirmDeleteOpen(e.open)}
+ >
@@ -143,14 +204,29 @@ function FigureDisplaySection({ artefactId, figureIds, getArtefactData, isEditin
- Are you sure you'd like to remove this figure? This action cannot be undone.
+ Are you sure you'd like to remove this
+ figure? This action cannot be undone.
-
@@ -162,4 +238,4 @@ function FigureDisplaySection({ artefactId, figureIds, getArtefactData, isEditin
);
}
-export default FigureDisplaySection;
\ No newline at end of file
+export default FigureDisplaySection;
diff --git a/src/components/DataStudio/groupViewActionBar.jsx b/src/components/DataStudio/groupViewActionBar.jsx
new file mode 100644
index 0000000..1816383
--- /dev/null
+++ b/src/components/DataStudio/groupViewActionBar.jsx
@@ -0,0 +1,171 @@
+import { ActionBar, Button, Dialog, Portal } from '@chakra-ui/react';
+import { isEqual } from 'lodash';
+import { useEffect, useState } from 'react';
+import ToastWizard from '../toastWizard';
+import server, { JSONResponse } from '../../networking';
+
+function GroupViewActionBar({ itemData, originalData, setItemData, refreshData, itemId, isEditing, setIsEditing }) {
+ const [changesMade, setChangesMade] = useState(false);
+ const [isConfirmSaveOpen, setIsConfirmSaveOpen] = useState(false);
+ const [saveLoading, setSaveLoading] = useState(false);
+
+ useEffect(() => {
+ setChangesMade(!isEqual(itemData, originalData) && isEditing);
+ }, [itemData, originalData, isEditing]);
+
+ // Validation functions
+ const validateField = (fieldName, value) => {
+ if (value === null || value === undefined) return true;
+
+ switch (fieldName) {
+ case 'name':
+ return typeof value === 'string' && value.trim().length >= 1 && value.trim().length <= 25;
+ case 'description':
+ return typeof value === 'string' && value.trim().length <= 200;
+ default:
+ return true;
+ }
+ };
+
+ const validateUpdateDict = (updateDict) => {
+ const errors = [];
+
+ if (!validateField('name', updateDict.name)) {
+ errors.push('Name must be between 1-25 characters');
+ }
+
+ if (!validateField('description', updateDict.description)) {
+ errors.push('Description must be 200 characters or less');
+ }
+
+ return errors;
+ };
+
+ const handleCancelChanges = () => {
+ setItemData(originalData);
+ ToastWizard.standard("warning", "Changes Reset", "All changes reverted.", 3000);
+ };
+
+ const handleSaveChanges = async () => {
+ setSaveLoading(true);
+
+ const updateDict = {
+ collectionID: itemId,
+ name: itemData.name || '',
+ description: itemData.description || ''
+ };
+
+ // Validate the update dictionary
+ const validationErrors = validateUpdateDict(updateDict);
+ if (validationErrors.length > 0) {
+ setSaveLoading(false);
+ ToastWizard.standard(
+ "error",
+ "Validation Error",
+ validationErrors.join('. '),
+ 5000
+ );
+ return;
+ }
+
+ try {
+ const response = await server.post('/studio/group/updateDetails', updateDict);
+
+ if (response.data instanceof JSONResponse) {
+ if (response.data.isErrorStatus()) {
+ const errObject = {
+ response: {
+ data: response.data
+ }
+ };
+ throw new Error(errObject);
+ }
+
+ // Success case
+ refreshData();
+ setIsConfirmSaveOpen(false);
+ setIsEditing(false);
+
+ ToastWizard.standard("success", "Collection updated", response.data.message);
+ } else {
+ throw new Error("Unexpected response format");
+ }
+ } catch (err) {
+ if (err.response && err.response.data instanceof JSONResponse) {
+ console.log("Error response in update collection data request:", err.response.data.fullMessage());
+ if (err.response.data.userErrorType()) {
+ ToastWizard.standard("error", "Collection update failed.", err.response.data.message);
+ } else {
+ ToastWizard.standard(
+ "error",
+ "Collection update failed.",
+ err.response.data.message,
+ 3000,
+ true,
+ handleSaveChanges,
+ "Retry"
+ );
+ }
+ } else {
+ console.log("Unexpected error in collection update request:", err);
+ ToastWizard.standard(
+ "error",
+ "Collection update failed.",
+ "Failed to update your collection information. Please try again.",
+ 3000,
+ true,
+ handleSaveChanges,
+ "Retry"
+ );
+ }
+ }
+ setSaveLoading(false);
+ };
+
+ return (
+
+
+
+
+
+ Reset
+
+
+ setIsConfirmSaveOpen(e.open)}>
+
+
+ Save Changes
+
+
+
+
+
+
+
+ Update Collection
+
+
+
+ Are you sure you'd like to save these changes? This action cannot be undone.
+
+
+
+ setIsConfirmSaveOpen(false)}>
+ Cancel
+
+
+ Save Changes
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default GroupViewActionBar;
\ No newline at end of file
diff --git a/src/components/DataStudio/groupViewDetails.jsx b/src/components/DataStudio/groupViewDetails.jsx
new file mode 100644
index 0000000..bac0590
--- /dev/null
+++ b/src/components/DataStudio/groupViewDetails.jsx
@@ -0,0 +1,116 @@
+import { Box, Text, Input, Textarea, Flex, Button } from "@chakra-ui/react";
+import { useState, useEffect } from "react";
+import { MdOutlineEdit, MdOutlineCancel } from "react-icons/md";
+import ToastWizard from "../toastWizard";
+import GroupViewActionBar from "./GroupViewActionBar";
+
+const GroupViewDetails = ({ isMobile, collection, colID, refreshCollectionData }) => {
+ const [isEditing, setIsEditing] = useState(false);
+ const [originalCollectionData, setOriginalCollectionData] = useState({});
+ const [collectionData, setCollectionData] = useState({});
+
+ // Initialize data when collection prop changes
+ useEffect(() => {
+ if (collection) {
+ const processedData = {
+ name: collection.name || '',
+ description: collection.description || ''
+ };
+ setOriginalCollectionData(processedData);
+ setCollectionData(processedData);
+ }
+ }, [collection]);
+
+ const handleEditClick = () => {
+ setIsEditing(true);
+ ToastWizard.standard("info", "Editor Mode Enabled", "You are currently in editor mode. Remember to save your changes!", 3000);
+ };
+
+ const handleCancelClick = () => {
+ setCollectionData(originalCollectionData);
+ setIsEditing(false);
+ ToastWizard.standard("warning", "Editor Mode Disabled", "All changes reverted.", 3000);
+ };
+
+ const handleNameChange = (e) => {
+ setCollectionData({
+ ...collectionData,
+ name: e.target.value
+ });
+ };
+
+ const handleDescriptionChange = (e) => {
+ setCollectionData({
+ ...collectionData,
+ description: e.target.value
+ });
+ };
+
+ return (
+ <>
+
+
+ {isEditing ? (
+
+ ) : (
+
+ {collectionData.name || "No Name"}
+
+ )}
+
+
+ {isEditing ? : }
+ {!isMobile ? {isEditing ? 'Cancel' : 'Edit'} : ""}
+
+
+
+ {isEditing ? (
+
+ ) : (
+ collectionData.description && (
+
+ {collectionData.description || "No Description"}
+
+ )
+ )}
+
+
+ {isEditing && (
+
+ )}
+ >
+ );
+};
+
+export default GroupViewDetails;
\ No newline at end of file
diff --git a/src/pages/ArtefactEditor.jsx b/src/pages/ArtefactEditor.jsx
index c101e17..bf222cf 100644
--- a/src/pages/ArtefactEditor.jsx
+++ b/src/pages/ArtefactEditor.jsx
@@ -133,7 +133,10 @@ function ArtefactEditor() {
}
// Success case
- setMetadata(response.data.raw.data);
+ setMetadata({
+ ...response.data.raw.data,
+ figureInfo: response.data.raw.figureInfo || {}
+ });
} else {
throw new Error("Unexpected response format");
}
diff --git a/src/pages/Catalogue.jsx b/src/pages/Catalogue.jsx
index b406cdf..454e3fc 100644
--- a/src/pages/Catalogue.jsx
+++ b/src/pages/Catalogue.jsx
@@ -5,8 +5,9 @@ import { AnimatePresence, motion } from "framer-motion";
import MMSection from "../components/Catalogue/mmSection.jsx";
import Section from "../components/Catalogue/section.jsx";
import CentredSpinner from "../components/CentredSpinner.jsx";
-import server, { JSONResponse } from '../networking'
+import server, { JSONResponse } from "../networking";
import ToastWizard from "../components/toastWizard.js";
+import CreateGroup from "../components/Catalogue/createGroup.jsx";
function Catalogue() {
const [selectedMMTitle, setSelectedMMTitle] = useState(null);
@@ -14,7 +15,7 @@ function Catalogue() {
const [books, setBooks] = useState([]);
const [categories, setCategories] = useState({});
const [loading, setLoading] = useState(true);
- const { loaded } = useSelector(state => state.auth);
+ const { loaded } = useSelector((state) => state.auth);
async function fetchCatalogue() {
try {
@@ -24,19 +25,22 @@ function Catalogue() {
if (response.data.isErrorStatus()) {
const errObject = {
response: {
- data: response.data
- }
+ data: response.data,
+ },
};
throw errObject;
}
// Success case
- const { categories = {}, books = [] } = response.data.raw.data || {};
+ const { categories = {}, books = [] } =
+ response.data.raw.data || {};
setCategories(categories);
- setBooks(books.map(book => ({
- ...book,
- artefacts: book.mmArtefacts || [],
- })));
+ setBooks(
+ books.map((book) => ({
+ ...book,
+ artefacts: book.mmArtefacts || [],
+ }))
+ );
} else {
throw new Error("Unexpected response format");
}
@@ -44,21 +48,22 @@ function Catalogue() {
if (err.response && err.response.data instanceof JSONResponse) {
if (err.response.data.userErrorType()) {
ToastWizard.standard(
- "error",
- "Could not load catalogue",
- err.response.data.message || "There was a problem loading the catalogue. Please try again later."
+ "error",
+ "Could not load catalogue",
+ err.response.data.message ||
+ "There was a problem loading the catalogue. Please try again later."
);
} else {
ToastWizard.standard(
- "error",
- "Something went wrong",
+ "error",
+ "Something went wrong",
"We are having trouble loading the catalogue. Please try again in a moment."
);
}
} else {
ToastWizard.standard(
- "error",
- "Connection issue",
+ "error",
+ "Connection issue",
"We could not connect to the server. Please check your internet connection or try again later."
);
}
@@ -107,14 +112,19 @@ function Catalogue() {
<>
{/* Fixed Headers */}
Catalogue Browser
+
+
{hasBooks && (
diff --git a/src/pages/GroupView.jsx b/src/pages/GroupView.jsx
index 9eea4f1..bfac40c 100644
--- a/src/pages/GroupView.jsx
+++ b/src/pages/GroupView.jsx
@@ -1,4 +1,4 @@
-import { Flex, Box, Text, Grid } from '@chakra-ui/react';
+import { Flex, Box, Text, Grid, useBreakpointValue } from '@chakra-ui/react';
import { useParams, useNavigate } from 'react-router-dom';
import { FaCircleArrowLeft } from 'react-icons/fa6';
import { useEffect, useState } from 'react';
@@ -6,12 +6,15 @@ import CentredSpinner from '../components/CentredSpinner';
import server, { JSONResponse } from '../networking'
import ToastWizard from '../components/toastWizard';
import GroupCardItem from '../components/DataStudio/groupCardItem';
+import GroupViewDetails from '../components/DataStudio/groupViewDetails';
+import DeleteGroup from '../components/DataStudio/deleteGroup';
function GroupView() {
const { colID } = useParams();
const navigate = useNavigate();
const [collection, setCollection] = useState(null);
const [loading, setLoading] = useState(true);
+ const isMobile = useBreakpointValue({ base: true, md: false });
const fetchCollection = async () => {
try {
@@ -118,27 +121,24 @@ function GroupView() {
return (
- {/* Header with back button */}
-
- navigate(-1)}
- size={40}
- color="darkBlue"
- />
-
- Group View
-
+
+ {/* Header with back button */}
+
+ navigate(-1)}
+ size={40}
+ color="darkBlue"
+ />
+
+ Group View
+
+
+
+ {collection.type !== "batch" && }
-
- {collection.name}
- {collection.description && (
-
- {collection.description}
-
- )}
-
+
{/* Items grid */}