diff --git a/client/src/components/AdminTable/AdminTable.tsx b/client/src/components/AdminTable/AdminTable.tsx index be5b594..45d4c13 100644 --- a/client/src/components/AdminTable/AdminTable.tsx +++ b/client/src/components/AdminTable/AdminTable.tsx @@ -149,10 +149,10 @@ const AdminTable: React.FC = ({ /> - Confirm Deletion + Confirmation - Are you sure you want to delete this record? + Voulez-vous vraiment supprimer? diff --git a/client/src/components/ImageGallery/ImageGallery.tsx b/client/src/components/ImageGallery/ImageGallery.tsx index ab78bb3..7d2b4b2 100644 --- a/client/src/components/ImageGallery/ImageGallery.tsx +++ b/client/src/components/ImageGallery/ImageGallery.tsx @@ -13,7 +13,8 @@ import { DialogContentText, Tabs, Tab, - TextField + TextField, Snackbar, + Alert } from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; @@ -21,12 +22,15 @@ import CloseIcon from "@mui/icons-material/Close"; import { ImageType } from "../../Types/ImageType"; import ApiService from "../../services/ApiService"; import { Upload } from "@mui/icons-material"; +import { ENV_VARIABLES } from '../../constants'; +import { escapeForGIFT } from "src/utils/giftUtils"; interface ImagesProps { handleCopy?: (id: string) => void; + handleDelete?: (id: string) => void; } -const ImageGallery: React.FC = ({ handleCopy }) => { +const ImageGallery: React.FC = ({ handleCopy, handleDelete }) => { const [images, setImages] = useState([]); const [totalImg, setTotalImg] = useState(0); const [imgPage, setImgPage] = useState(1); @@ -38,39 +42,60 @@ const ImageGallery: React.FC = ({ handleCopy }) => { const [tabValue, setTabValue] = useState(0); const [importedImage, setImportedImage] = useState(null); const [preview, setPreview] = useState(null); + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(""); + const [snackbarSeverity, setSnackbarSeverity] = useState<"success" | "error">("success"); + + const fetchImages = async () => { + setLoading(true); + const data = await ApiService.getImages(imgPage, imgLimit); + setImages(data.images); + setTotalImg(data.total); + setLoading(false); + }; useEffect(() => { - const fetchImages = async () => { - setLoading(true); - const data = await ApiService.getImages(imgPage, imgLimit); - setImages(data.images); - setTotalImg(data.total); - setLoading(false); - }; fetchImages(); }, [imgPage]); - const handleDelete = async () => { + const defaultHandleDelete = async (id: string) => { if (imageToDelete) { setLoading(true); - const isDeleted = await ApiService.deleteImage(imageToDelete.id); + const isDeleted = await ApiService.deleteImage(id); setLoading(false); + if (isDeleted) { - setImages(images.filter((image) => image.id !== imageToDelete.id)); - setSelectedImage(null); - setImageToDelete(null); - setOpenDeleteDialog(false); + setImgPage(1); + fetchImages(); + setSnackbarMessage("Image supprimée avec succès!"); + setSnackbarSeverity("success"); + } else { + setSnackbarMessage("Erreur lors de la suppression de l'image. Veuillez réessayer."); + setSnackbarSeverity("error"); } + + setSnackbarOpen(true); + setSelectedImage(null); + setImageToDelete(null); + setOpenDeleteDialog(false); } }; const defaultHandleCopy = (id: string) => { if (navigator.clipboard) { - navigator.clipboard.writeText(id); + const link = `${ENV_VARIABLES.IMG_URL}/api/image/get/${id}`; + const imgTag = `[markdown]![alt_text](${escapeForGIFT(link)} "texte de l'infobulle")`; + setSnackbarMessage("Le lien Markdown de l’image a été copié dans le presse-papiers"); + setSnackbarSeverity("success"); + setSnackbarOpen(true); + navigator.clipboard.writeText(imgTag); + } + if(handleCopy) { + handleCopy(id); } }; - const handleCopyFunction = handleCopy || defaultHandleCopy; + const handleDeleteFunction = handleDelete || defaultHandleDelete; const handleImageUpload = (event: React.ChangeEvent) => { const file = event.target.files ? event.target.files[0] : null; @@ -81,6 +106,44 @@ const ImageGallery: React.FC = ({ handleCopy }) => { } }; + const handleSaveImage = async () => { + try { + if (!importedImage) { + setSnackbarMessage("Veuillez choisir une image à téléverser."); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + return; + } + + const imageUrl = await ApiService.uploadImage(importedImage); + + if (imageUrl.includes("ERROR")) { + setSnackbarMessage("Une erreur est survenue. Veuillez réessayer plus tard."); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + return; + } + fetchImages(); + + setSnackbarMessage("Téléversée avec succès !"); + setSnackbarSeverity("success"); + setSnackbarOpen(true); + + setImportedImage(null); + setPreview(null); + setTabValue(0); + } catch (error) { + setSnackbarMessage(`Une erreur est survenue.\n${error}\nVeuillez réessayer plus tard.`); + setSnackbarSeverity("error"); + setSnackbarOpen(true); + } + }; + + const handleCloseSnackbar = () => { + setSnackbarOpen(false); + }; + + return ( setTabValue(newValue)}> @@ -97,7 +160,7 @@ const ImageGallery: React.FC = ({ handleCopy }) => { <> {images.map((obj) => ( - setSelectedImage(obj)}> + setSelectedImage(obj)}> = ({ handleCopy }) => { style={{ width: "100%", height: 250, objectFit: "cover", borderRadius: 8 }} /> + + { + e.stopPropagation(); + defaultHandleCopy(obj.id); + }} + color="primary" + data-testid={`gallery-tab-copy-${obj.id}`} > + + + + + { + e.stopPropagation(); + setImageToDelete(obj); + setOpenDeleteDialog(true); + }} + color="error" + data-testid={`gallery-tab-delete-${obj.id}`} > + + + + ))} @@ -146,16 +232,16 @@ const ImageGallery: React.FC = ({ handleCopy }) => { maxHeight: "600px", }} /> - - )} = ({ handleCopy }) => { - + + + + {snackbarMessage} + + ); }; diff --git a/client/src/components/ImageGallery/ImageGalleryModal/ImageGalleryModal.tsx b/client/src/components/ImageGallery/ImageGalleryModal/ImageGalleryModal.tsx index f208a4e..f960352 100644 --- a/client/src/components/ImageGallery/ImageGalleryModal/ImageGalleryModal.tsx +++ b/client/src/components/ImageGallery/ImageGalleryModal/ImageGalleryModal.tsx @@ -7,8 +7,15 @@ import { } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import ImageGallery from "../ImageGallery"; +import { ImageSearch } from "@mui/icons-material"; -const ImageGalleryModal: React.FC = () => { + +interface ImageGalleryModalProps { + handleCopy?: (id: string) => void; +} + + +const ImageGalleryModal: React.FC = ({ handleCopy }) => { const [open, setOpen] = useState(false); const handleOpen = () => setOpen(true); @@ -16,14 +23,18 @@ const ImageGalleryModal: React.FC = () => { return ( <> - + { - + diff --git a/client/src/constants.tsx b/client/src/constants.tsx index ad5b80b..0cbbb9f 100644 --- a/client/src/constants.tsx +++ b/client/src/constants.tsx @@ -2,6 +2,7 @@ const ENV_VARIABLES = { MODE: process.env.MODE || "production", VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || "", + IMG_URL: process.env.MODE == "development" ? process.env.VITE_BACKEND_URL : process.env.VITE_IMG_URL, BACKEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.BACKEND_PORT}`:''}` : process.env.VITE_BACKEND_URL || '', FRONTEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.PORT}`:''}` : '' };