diff --git a/frontend/src/scenes/banner/BannerDefaultPage.jsx b/frontend/src/scenes/banner/BannerDefaultPage.jsx index 723b72d9..338080e1 100644 --- a/frontend/src/scenes/banner/BannerDefaultPage.jsx +++ b/frontend/src/scenes/banner/BannerDefaultPage.jsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { useLocation } from "react-router-dom"; import { ACTIVITY_TYPES_INFO } from "../../data/guideMainPageData"; -import { deleteBanner, getBanners } from "../../services/bannerServices"; +import { addBanner, getBannerById, deleteBanner, getBanners } from "../../services/bannerServices"; import DefaultPageTemplate from "../../templates/DefaultPageTemplate/DefaultPageTemplate"; import { useDialog } from "../../templates/GuideTemplate/GuideTemplateContext"; import BannerPage from "./CreateBannerPage"; @@ -30,6 +30,8 @@ const BannerDefaultPage = () => { itemTypeInfo={ACTIVITY_TYPES_INFO.BANNERS} getItemDetails={getBannerDetails} itemsUpdated={itemsUpdated} + getItemById={getBannerById} + duplicateItem={addBanner} /> {isOpen && ( { @@ -29,6 +29,8 @@ const HintDefaultPage = () => { itemTypeInfo={ACTIVITY_TYPES_INFO.HINTS} getItemDetails={getHintDetails} itemsUpdated={itemsUpdated} + getItemById={getHintById} + duplicateItem={addHint} /> {isOpen && ( { itemType={ACTIVITY_TYPES_INFO.HELPERLINKS} itemTypeInfo={ACTIVITY_TYPES_INFO.HELPERLINKS} getItemDetails={getItemDetails} + getItemById={getHelperById} + duplicateItem={createHelper} /> diff --git a/frontend/src/scenes/popup/PopupDefaultPage.jsx b/frontend/src/scenes/popup/PopupDefaultPage.jsx index 0e17ae93..ee3e4e1a 100644 --- a/frontend/src/scenes/popup/PopupDefaultPage.jsx +++ b/frontend/src/scenes/popup/PopupDefaultPage.jsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import {useLocation} from "react-router-dom" import DefaultPageTemplate from '../../templates/DefaultPageTemplate/DefaultPageTemplate'; import CreatePopupPage from './CreatePopupPage'; -import { getPopups, deletePopup } from '../../services/popupServices'; +import { addPopup, getPopupById, getPopups, deletePopup } from '../../services/popupServices'; import { ACTIVITY_TYPES_INFO } from '../../data/guideMainPageData'; import { useDialog } from '../../templates/GuideTemplate/GuideTemplateContext'; @@ -30,6 +30,8 @@ const PopupDefaultPage = () => { itemTypeInfo={ACTIVITY_TYPES_INFO.POPUPS} getItemDetails={getPopupDetails} itemsUpdated={itemsUpdated} + getItemById={getPopupById} + duplicateItem={addPopup} /> {isOpen && ( { +const DefaultPageTemplate = ({ getItems, deleteItem, setIsEdit, setItemId, itemType, itemTypeInfo, getItemDetails, itemsUpdated, getItemById, duplicateItem }) => { const [items, setItems] = useState([]); const [isPopupOpen, setPopupOpen] = useState(false); const [itemToDelete, setItemToDelete] = useState(); const [itemDeleted, setItemDeleted] = useState(false); + const [itemsDuplicated, setItemsDuplicated] = useState(false); const [loading, setLoading] = useState(true); const [load, setLoad] = useState(true) const { userInfo } = useAuth(); @@ -33,6 +34,7 @@ const DefaultPageTemplate = ({ getItems, deleteItem, setIsEdit, setItemId, itemT setItemId(null); openDialog(); }; + const handleDelete = async () => { try { await deleteItem(itemToDelete); @@ -47,6 +49,30 @@ const DefaultPageTemplate = ({ getItems, deleteItem, setIsEdit, setItemId, itemT } }; + const duplicateHandler = async (id) => { + try { + if (itemType === 'helper links') { + const { createdBy, id: fetchedId, links, ...helper } = await getItemById(id); + const updatedLinks = links.map(({ id, ...data }) => data); + + await duplicateItem(helper, updatedLinks); + + } else { + const { createdBy, id: fetchedId, ...data } = await getItemById(id); + await duplicateItem(data); + } + + toastEmitter.emit(TOAST_EMITTER_KEY, `${itemType.charAt(0).toUpperCase() + itemType.slice(1, -1)} duplicated successfully`); + + setItemsDuplicated((prev) => !prev); + } catch (error) { + const errorMessage = error.response?.data?.message + ? `Error: ${error.response.data.message}` + : 'An unexpected error occurred. Please try again.'; + toastEmitter.emit(TOAST_EMITTER_KEY, errorMessage); + } + }; + const handleOpenPopup = (id) => { setItemToDelete(id); setPopupOpen(true); @@ -74,14 +100,15 @@ const DefaultPageTemplate = ({ getItems, deleteItem, setIsEdit, setItemId, itemT } }; fetchData(); - }, [itemDeleted, itemsUpdated]); + }, [itemDeleted, itemsUpdated, itemsDuplicated]); const mappedItems = useMemo(() => items.map(item => ({ idItem: item.id, ...getItemDetails(item), onDelete: () => handleOpenPopup(item.id), onEdit: () => openEditPopupDialog(item.id), - })), [items, getItemDetails, handleOpenPopup, openNewPopupDialog]); + onDuplicate: () => duplicateHandler(item.id) + })), [items, getItemDetails, handleOpenPopup, openNewPopupDialog, duplicateHandler]); return ( <> @@ -118,6 +145,7 @@ DefaultPageTemplate.propTypes = { itemType: PropTypes.string.isRequired, itemTypeInfo: PropTypes.string.isRequired, getItemDetails: PropTypes.func.isRequired, + duplicateItem: PropTypes.func.isRequired, }; export default DefaultPageTemplate; \ No newline at end of file diff --git a/frontend/src/templates/GuideMainPageTemplate/GuideMainPageComponents/List/List.jsx b/frontend/src/templates/GuideMainPageTemplate/GuideMainPageComponents/List/List.jsx index be0e6fe8..7f1b7f9b 100644 --- a/frontend/src/templates/GuideMainPageTemplate/GuideMainPageComponents/List/List.jsx +++ b/frontend/src/templates/GuideMainPageTemplate/GuideMainPageComponents/List/List.jsx @@ -14,6 +14,7 @@ const List = ({ items }) => { onClick={() => {}} onDelete={item.onDelete} onEdit={item.onEdit} + onDuplicate={item.onDuplicate} /> ))} diff --git a/frontend/src/templates/GuideMainPageTemplate/GuideMainPageComponents/List/ListItem/ListItem.jsx b/frontend/src/templates/GuideMainPageTemplate/GuideMainPageComponents/List/ListItem/ListItem.jsx index eca270e3..279a49c9 100644 --- a/frontend/src/templates/GuideMainPageTemplate/GuideMainPageComponents/List/ListItem/ListItem.jsx +++ b/frontend/src/templates/GuideMainPageTemplate/GuideMainPageComponents/List/ListItem/ListItem.jsx @@ -1,14 +1,15 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { IconButton, useTheme } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; import CircleIcon from '@mui/icons-material/Circle'; +import ContentCopyTwoToneIcon from '@mui/icons-material/ContentCopyTwoTone'; import './ListItem.css'; import { useAuth } from '../../../../../services/authProvider'; import { renderIfAuthorized } from '../../../../../utils/generalHelper'; -const ListItem = ({ title, text, id, onClick, onDelete, onEdit }) => { +const ListItem = ({ title, text, id, onClick, onDelete, onEdit, onDuplicate }) => { const theme = useTheme(); const { userInfo } = useAuth(); const role = userInfo.role; @@ -26,16 +27,20 @@ const ListItem = ({ title, text, id, onClick, onDelete, onEdit }) => { {text &&

{text}

} {id &&

ID: {id}

} +
{renderIfAuthorized(role, 'admin', + <> + + + - )} - {renderIfAuthorized(role, 'admin', + )}
@@ -49,6 +54,7 @@ ListItem.propTypes = { onClick: PropTypes.func, onDelete: PropTypes.func, onEdit: PropTypes.func, + onDuplicate: PropTypes.func, }; export default ListItem;