diff --git a/client/src/__tests__/pages/Admin/Images.test.tsx b/client/src/__tests__/pages/Admin/Images.test.tsx
new file mode 100644
index 0000000..3e44ff5
--- /dev/null
+++ b/client/src/__tests__/pages/Admin/Images.test.tsx
@@ -0,0 +1 @@
+//a;ready being tested by ImageGallery.test.tsx
\ No newline at end of file
diff --git a/client/src/__tests__/pages/Admin/Stats.test.tsx b/client/src/__tests__/pages/Admin/Stats.test.tsx
new file mode 100644
index 0000000..62e7084
--- /dev/null
+++ b/client/src/__tests__/pages/Admin/Stats.test.tsx
@@ -0,0 +1,75 @@
+import React from "react";
+import { render, screen, waitFor, act } from "@testing-library/react";
+import Stats from "../../../pages/Admin/Stats";
+import ApiService from '../../../services/ApiService';
+import '@testing-library/jest-dom';
+
+jest.mock('../../../services/ApiService', () => ({
+ getStats: jest.fn(),
+}));
+
+describe("Stats Component", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ (ApiService.getStats as jest.Mock).mockReset();
+ });
+
+ test("renders loading state initially", async () => {
+ (ApiService.getStats as jest.Mock).mockImplementationOnce(() =>
+ new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({
+ quizzes: [],
+ total: 0,
+ });
+ }, 100);
+ })
+ );
+
+ await act(async () => {
+ render();
+ });
+
+ expect(screen.getByRole("progressbar")).toBeInTheDocument();
+ });
+
+ test("fetches and displays data", async () => {
+ const mockStats = {
+ quizzes: [{ _id: "1", title: "Mock Quiz", created_at: "2025-03-01", updated_at: "2025-03-05", email: "teacher@example.com" }],
+ total: 5,
+ };
+
+ (ApiService.getStats as jest.Mock).mockResolvedValueOnce(mockStats);
+
+
+ await act(async () => {
+ render();
+ });
+
+ await waitFor(() => screen.queryByRole("progressbar"));
+
+ expect(screen.getByText("Quiz du Mois")).toBeInTheDocument();
+ expect(screen.getByText(mockStats.quizzes.length)).toBeInTheDocument();
+ expect(screen.getByText("Quiz total")).toBeInTheDocument();
+ expect(screen.getByText(mockStats.quizzes.length)).toBeInTheDocument();
+ expect(screen.getByText("Enseignants")).toBeInTheDocument();
+ expect(screen.getByText(mockStats.total)).toBeInTheDocument();
+ });
+
+ test("should display the AdminTable mock component", async () => {
+ const mockStats = {
+ quizzes: [{ _id: "1", title: "Mock Quiz", created_at: "2025-03-01", updated_at: "2025-03-05", email: "teacher@example.com" }],
+ total: 5,
+ };
+
+ (ApiService.getStats as jest.Mock).mockResolvedValueOnce(mockStats);
+
+
+ await act(async () => {
+ render();
+ });
+
+ expect(screen.getByRole('columnheader', { name: /enseignant/i })).toBeInTheDocument();
+
+ });
+});
diff --git a/client/src/__tests__/pages/Admin/Users.test.tsx b/client/src/__tests__/pages/Admin/Users.test.tsx
new file mode 100644
index 0000000..2a907b4
--- /dev/null
+++ b/client/src/__tests__/pages/Admin/Users.test.tsx
@@ -0,0 +1,93 @@
+import { render, screen, waitFor, act, fireEvent, within } from '@testing-library/react';
+import Users from '../../../pages/Admin/Users';
+import ApiService from '../../../services/ApiService';
+import '@testing-library/jest-dom';
+import { AdminTableType } from '../../../Types/AdminTableType';
+import React from 'react';
+
+jest.mock('../../../services/ApiService');
+jest.mock('../../../components/AdminTable/AdminTable', () => ({
+ __esModule: true,
+ default: ({ data, onDelete }: any) => (
+
+
+
+ | Enseignant |
+ Email |
+ Actions |
+
+
+
+ {data.map((user: any) => (
+
+ | {user.name} |
+ {user.email} |
+
+
+ |
+
+ ))}
+
+
+ ),
+}));
+
+describe('Users Component', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('renders users after fetching data', async () => {
+ const mockUsers: AdminTableType[] = [
+ { _id: '1', name: 'John Doe', email: 'john.doe@example.com', created_at: new Date('2021-01-01'), roles: ['admin'] },
+ { _id: '2', name: 'Jane Smith', email: 'jane.smith@example.com', created_at: new Date('2021-02-01'), roles: ['user'] },
+ ];
+
+ (ApiService.getUsers as jest.Mock).mockResolvedValueOnce(mockUsers);
+
+
+ await act(async () => {
+ render();
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
+ expect(screen.getByText('jane.smith@example.com')).toBeInTheDocument();
+ });
+ });
+
+ it('handles delete user action', async () => {
+ const mockUsers: AdminTableType[] = [];
+
+ (ApiService.getUsers as jest.Mock).mockResolvedValueOnce(mockUsers);
+
+ await act(async () => {
+ render();
+ });
+
+ const columnHeader = screen.getByRole('columnheader', { name: /enseignant/i });
+ expect(columnHeader).toBeInTheDocument();
+ expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
+ });
+
+ it('calls handleDelete when delete button is clicked', async () => {
+
+ const mockUsers: AdminTableType[] = [{ _id: '1', name: 'John Doe', email: 'john.doe@example.com', created_at: new Date('2021-01-01'), roles: ['Admin'] }];
+ (ApiService.getUsers as jest.Mock).mockResolvedValueOnce(mockUsers);
+
+ await act(async () => {
+ render();
+ });
+
+ await waitFor(() => screen.getByText("John Doe"));
+ console.log(screen.debug());
+ const userRow = screen.getByText("John Doe").closest("tr");
+ if (userRow) {
+ const deleteButton = within(userRow).getByRole('button');
+ fireEvent.click(deleteButton);
+ expect(screen.queryByText("John Doe")).not.toBeInTheDocument();
+ }else {
+ throw new Error("User row not found");
+ }
+ });
+});
diff --git a/client/src/components/ImageGallery/ImageGallery.tsx b/client/src/components/ImageGallery/ImageGallery.tsx
new file mode 100644
index 0000000..ab78bb3
--- /dev/null
+++ b/client/src/components/ImageGallery/ImageGallery.tsx
@@ -0,0 +1,225 @@
+import React, { useState, useEffect } from "react";
+import {
+ Box,
+ CircularProgress,
+ Button,
+ IconButton,
+ Card,
+ CardContent,
+ Dialog,
+ DialogContent,
+ DialogActions,
+ DialogTitle,
+ DialogContentText,
+ Tabs,
+ Tab,
+ TextField
+} from "@mui/material";
+import DeleteIcon from "@mui/icons-material/Delete";
+import ContentCopyIcon from "@mui/icons-material/ContentCopy";
+import CloseIcon from "@mui/icons-material/Close";
+import { ImageType } from "../../Types/ImageType";
+import ApiService from "../../services/ApiService";
+import { Upload } from "@mui/icons-material";
+
+interface ImagesProps {
+ handleCopy?: (id: string) => void;
+}
+
+const ImageGallery: React.FC = ({ handleCopy }) => {
+ const [images, setImages] = useState([]);
+ const [totalImg, setTotalImg] = useState(0);
+ const [imgPage, setImgPage] = useState(1);
+ const [imgLimit] = useState(6);
+ const [loading, setLoading] = useState(false);
+ const [selectedImage, setSelectedImage] = useState(null);
+ const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
+ const [imageToDelete, setImageToDelete] = useState(null);
+ const [tabValue, setTabValue] = useState(0);
+ const [importedImage, setImportedImage] = useState(null);
+ const [preview, setPreview] = useState(null);
+
+ 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 () => {
+ if (imageToDelete) {
+ setLoading(true);
+ const isDeleted = await ApiService.deleteImage(imageToDelete.id);
+ setLoading(false);
+ if (isDeleted) {
+ setImages(images.filter((image) => image.id !== imageToDelete.id));
+ setSelectedImage(null);
+ setImageToDelete(null);
+ setOpenDeleteDialog(false);
+ }
+ }
+ };
+
+ const defaultHandleCopy = (id: string) => {
+ if (navigator.clipboard) {
+ navigator.clipboard.writeText(id);
+ }
+ };
+
+ const handleCopyFunction = handleCopy || defaultHandleCopy;
+
+ const handleImageUpload = (event: React.ChangeEvent) => {
+ const file = event.target.files ? event.target.files[0] : null;
+ setImportedImage(file);
+ if (file) {
+ const objectUrl = URL.createObjectURL(file);
+ setPreview(objectUrl);
+ }
+ };
+
+ return (
+
+ setTabValue(newValue)}>
+
+
+
+ {tabValue === 0 && (
+ <>
+ {loading ? (
+
+
+
+ ) : (
+ <>
+
+ {images.map((obj) => (
+ setSelectedImage(obj)}>
+
+
+
+
+ ))}
+
+
+
+
+
+ >
+ )}
+ >
+ )}
+ {tabValue === 1 && (
+
+ {/* Image Preview at the top */}
+ {preview && (
+
+
+
+
+
+ )}
+
+
+
+
+
+ )}
+
+
+ {/* Delete Confirmation Dialog */}
+
+
+ );
+};
+
+export default ImageGallery;
diff --git a/client/src/pages/Admin/Stats.tsx b/client/src/pages/Admin/Stats.tsx
index f1ab115..29f3470 100644
--- a/client/src/pages/Admin/Stats.tsx
+++ b/client/src/pages/Admin/Stats.tsx
@@ -10,7 +10,7 @@ const styles = {
cardHover: 'rgba(65, 105, 225, 0.7)',
};
-const Users: React.FC = () => {
+const Stats: React.FC = () => {
const [quizzes, setQuizzes] = useState([]);
const [monthlyQuizzes, setMonthlyQuizzes] = useState(0);
const [totalUsers, setTotalUsers] = useState(0);
@@ -30,7 +30,7 @@ const Users: React.FC = () => {
return quizDate.getMonth() === currentMonth && quizDate.getFullYear() === currentYear;
});
- setMonthlyQuizzes(filteredMonthlyQuizzes.length === 0 ? 10 : 0);
+ setMonthlyQuizzes(filteredMonthlyQuizzes.length === 0 ? 0 : filteredMonthlyQuizzes.length);
} catch (error) {
console.error("Error fetching quizzes:", error);
} finally {
@@ -58,7 +58,7 @@ const Users: React.FC = () => {
{ label: "Quiz du Mois", value: monthlyQuizzes },
{ label: "Quiz total", value: totalQuizzes },
{ label: "Enseignants", value: totalUsers },
- { label: "Enseignants du Mois", value: totalUsers },
+ { label: "Enseignants du Mois", value: 0 },
];
const labelMap = {
@@ -105,4 +105,4 @@ const Users: React.FC = () => {
);
};
-export default Users;
+export default Stats;