diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 72d1741..c4aa74a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,9 +21,13 @@ jobs: with: node-version: '18' - - name: Install Dependencies and Run Tests + - name: Install Dependencies, lint and Run Tests run: | + echo "Installing dependencies..." npm ci + echo "Running ESLint..." + npx eslint . + echo "Running tests..." npm test working-directory: ${{ matrix.directory }} diff --git a/LICENSE b/LICENSE index 51674b4..f48f9ad 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2023 ETS-PFE004-Plateforme-sondage-minitest +Copyright (c) 2024 Louis-Antoine Caron, Mathieu Roy, Mélanie St-Hilaire, Samy Waddah Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/client/.env b/client/.env deleted file mode 100644 index fa03bbb..0000000 --- a/client/.env +++ /dev/null @@ -1,2 +0,0 @@ -VITE_BACKEND_URL=http://localhost:4400 -VITE_AZURE_BACKEND_URL=http://localhost:4400 diff --git a/client/.env.development b/client/.env.development new file mode 100644 index 0000000..e37f392 --- /dev/null +++ b/client/.env.development @@ -0,0 +1,2 @@ +VITE_BACKEND_URL=http://localhost:4400 +VITE_BACKEND_SOCKET_URL=http://localhost:4400 diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs deleted file mode 100644 index d6c9537..0000000 --- a/client/.eslintrc.cjs +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} diff --git a/client/babel.config.cjs b/client/babel.config.cjs index 1d2b002..2bda178 100644 --- a/client/babel.config.cjs +++ b/client/babel.config.cjs @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ module.exports = { presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'] }; diff --git a/client/eslint.config.js b/client/eslint.config.js new file mode 100644 index 0000000..ed8593c --- /dev/null +++ b/client/eslint.config.js @@ -0,0 +1,29 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; +import pluginReact from "eslint-plugin-react"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { + files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], + languageOptions: { + globals: globals.browser, + }, + rules: { + "no-unused-vars": ["error", { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" // Ignore catch clause parameters that start with _ + }], + }, + settings: { + react: { + version: "detect", // Automatically detect the React version + }, + }, + }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + pluginReact.configs.flat.recommended, +]; diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 84b514c..557c00d 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { @@ -11,7 +12,8 @@ module.exports = { //moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], setupFiles: ['./jest.setup.cjs'], moduleNameMapper: { - '\\.(css|less|scss|sass)$': 'identity-obj-proxy' + '\\.(css|less|scss|sass)$': 'identity-obj-proxy', + '^src/constants$': '/src/__mocks__/constantsMock.tsx', }, transformIgnorePatterns: ['node_modules/(?!nanoid/)'], }; diff --git a/client/jest.setup.cjs b/client/jest.setup.cjs index 9882982..30fd66a 100644 --- a/client/jest.setup.cjs +++ b/client/jest.setup.cjs @@ -1,2 +1,3 @@ +/* eslint-disable no-undef */ process.env.VITE_BACKEND_URL = 'http://localhost:4000/'; process.env.VITE_BACKEND_SOCKET_URL = 'https://ets-glitch-backend.glitch.me/'; diff --git a/client/package.json b/client/package.json index a0954df..6b1c567 100644 --- a/client/package.json +++ b/client/package.json @@ -43,6 +43,7 @@ "@babel/preset-env": "^7.23.3", "@babel/preset-react": "^7.23.3", "@babel/preset-typescript": "^7.23.3", + "@eslint/js": "^9.18.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.1", @@ -54,13 +55,16 @@ "@typescript-eslint/eslint-plugin": "^8.5.0", "@typescript-eslint/parser": "^8.5.0", "@vitejs/plugin-react-swc": "^3.3.2", - "eslint": "^9.10.0", + "eslint": "^9.18.0", + "eslint-plugin-react": "^7.37.3", "eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912", "eslint-plugin-react-refresh": "^0.4.12", + "globals": "^15.14.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "ts-jest": "^29.1.1", "typescript": "^5.6.2", + "typescript-eslint": "^8.19.1", "vite": "^5.4.5", "vite-plugin-environment": "^1.1.3" } diff --git a/client/src/App.tsx b/client/src/App.tsx index ab9ddff..8f8ecf8 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,3 +1,4 @@ +import React from 'react'; // App.tsx import { Routes, Route } from 'react-router-dom'; diff --git a/client/src/Types/QuizType.tsx b/client/src/Types/QuizType.tsx index af82c41..b5e2b08 100644 --- a/client/src/Types/QuizType.tsx +++ b/client/src/Types/QuizType.tsx @@ -2,6 +2,7 @@ export interface QuizType { _id: string; folderId: string; + folderName: string; userId: string; title: string; content: string[]; diff --git a/client/src/__mocks__/constantsMock.tsx b/client/src/__mocks__/constantsMock.tsx new file mode 100644 index 0000000..9cdc04d --- /dev/null +++ b/client/src/__mocks__/constantsMock.tsx @@ -0,0 +1,13 @@ +console.log('constantsMock.tsx is loaded'); + +// constants.tsx +const ENV_VARIABLES = { + MODE: 'production', + VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || "", + VITE_BACKEND_SOCKET_URL: process.env.VITE_BACKEND_SOCKET_URL || "", +}; + +console.log(`ENV_VARIABLES.VITE_BACKEND_URL=${ENV_VARIABLES.VITE_BACKEND_URL}`); +console.log(`ENV_VARIABLES.VITE_BACKEND_SOCKET_URL=${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`); + +export { ENV_VARIABLES }; diff --git a/client/src/__tests__/Types/QuizType.test.tsx b/client/src/__tests__/Types/QuizType.test.tsx index 22f3db3..8e21513 100644 --- a/client/src/__tests__/Types/QuizType.test.tsx +++ b/client/src/__tests__/Types/QuizType.test.tsx @@ -9,6 +9,7 @@ describe('isQuizValid function', () => { const validQuiz: QuizType = { _id: '1', folderId: 'test', + folderName: 'test', userId: 'user', created_at: new Date('2021-10-01'), updated_at: new Date('2021-10-02'), @@ -24,6 +25,7 @@ describe('isQuizValid function', () => { const invalidQuiz: QuizType = { _id: '2', folderId: 'test', + folderName: 'test', userId: 'user', title: '', created_at: new Date('2021-10-01'), @@ -39,6 +41,7 @@ describe('isQuizValid function', () => { const invalidQuiz: QuizType = { _id: '2', folderId: 'test', + folderName: 'test', userId: 'user', title: 'sample', created_at: new Date('2021-10-01'), diff --git a/client/src/__tests__/components/ConfirmDialog/ConfirmDialog.test.tsx b/client/src/__tests__/components/ConfirmDialog/ConfirmDialog.test.tsx index 649b150..e119ca8 100644 --- a/client/src/__tests__/components/ConfirmDialog/ConfirmDialog.test.tsx +++ b/client/src/__tests__/components/ConfirmDialog/ConfirmDialog.test.tsx @@ -1,4 +1,5 @@ // Modal.test.tsx +import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import ConfirmDialog from '../../../components/ConfirmDialog/ConfirmDialog'; diff --git a/client/src/__tests__/components/Editor/Editor.test.tsx b/client/src/__tests__/components/Editor/Editor.test.tsx index d5d6921..0a7c9b5 100644 --- a/client/src/__tests__/components/Editor/Editor.test.tsx +++ b/client/src/__tests__/components/Editor/Editor.test.tsx @@ -1,4 +1,5 @@ // Editor.test.tsx +import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import Editor from '../../../components/Editor/Editor'; diff --git a/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx b/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx index a33726b..f4eea86 100644 --- a/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import GIFTTemplatePreview from '../../../components/GiftTemplate/GIFTTemplatePreview'; diff --git a/client/src/__tests__/components/GiftTemplate/TextType.test.ts b/client/src/__tests__/components/GiftTemplate/TextType.test.ts index edff190..ade4211 100644 --- a/client/src/__tests__/components/GiftTemplate/TextType.test.ts +++ b/client/src/__tests__/components/GiftTemplate/TextType.test.ts @@ -32,7 +32,7 @@ describe('TextType', () => { // Hint -- if the output changes because of a change in the code or library, you can update // by running the test and copying the "Received string:" in jest output // when it fails (assuming the output is correct) - const expectedOutput = 'E=mc2E=mc^2E=mc2'; + const expectedOutput = 'E=mc2E=mc^2'; expect(textType({ text: input })).toContain(expectedOutput); }); @@ -42,19 +42,17 @@ describe('TextType', () => { format: 'plain' }; // hint: katex-display is the class that indicates a separate equation - const expectedOutput = 'a+b=ca + b = ca+b=c ? E=mc2E=mc^2E=mc2'; + const expectedOutput = 'a+b=ca + b = c ? E=mc2E=mc^2'; expect(textType({ text: input })).toContain(expectedOutput); }); it('should format text with a katex matrix correctly', () => { const input: TextFormat = { - text: `Donnez le déterminant de la matrice suivante.$$\\begin\{pmatrix\} - a&b \\\\ - c&d -\\end\{pmatrix\}`, + // eslint-disable-next-line no-useless-escape + text: `Donnez le déterminant de la matrice suivante.$$\\begin\{pmatrix\}\n a&b \\\\\n c&d\n\\end\{pmatrix\}`, format: 'plain' }; - const expectedOutput = 'Donnez le déterminant de la matrice suivante.\\begin{pmatrix}
a&b \\\\
c&d
\\end{pmatrix}'; + const expectedOutput = 'Donnez le déterminant de la matrice suivante.\\begin{pmatrix}
a&b \\\\
c&d
\\end{pmatrix}'; expect(textType({ text: input })).toContain(expectedOutput); }); diff --git a/client/src/__tests__/components/GiftTemplate/constants/styles.test.tsx b/client/src/__tests__/components/GiftTemplate/constants/styles.test.tsx index 7d60560..7347b67 100644 --- a/client/src/__tests__/components/GiftTemplate/constants/styles.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/constants/styles.test.tsx @@ -1,4 +1,5 @@ //styles.test.tsx +import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; @@ -27,6 +28,7 @@ function convertStylesToObject(styles: string): React.CSSProperties { styles.split(';').forEach((style) => { const [property, value] = style.split(':'); if (property && value) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any (styleObject as any)[property.trim()] = value.trim(); } }); diff --git a/client/src/__tests__/components/GiftTemplate/templates/AnswerIcon.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/AnswerIcon.test.tsx index c67d754..b053976 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/AnswerIcon.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/AnswerIcon.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; import AnswerIcon from '../../../../components/GiftTemplate/templates/AnswerIcon'; diff --git a/client/src/__tests__/components/ImportModal/ImportModal.test.tsx b/client/src/__tests__/components/ImportModal/ImportModal.test.tsx index ed36f37..400b586 100644 --- a/client/src/__tests__/components/ImportModal/ImportModal.test.tsx +++ b/client/src/__tests__/components/ImportModal/ImportModal.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import DragAndDrop from '../../../components/ImportModal/ImportModal'; @@ -69,4 +70,4 @@ describe('DragAndDrop Component', () => { target: { files: [file] }, }); }); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/LaunchQuizDialog/LaunchQuizDialog.test.tsx b/client/src/__tests__/components/LaunchQuizDialog/LaunchQuizDialog.test.tsx index cb6e626..9709cd0 100644 --- a/client/src/__tests__/components/LaunchQuizDialog/LaunchQuizDialog.test.tsx +++ b/client/src/__tests__/components/LaunchQuizDialog/LaunchQuizDialog.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import LaunchQuizDialog from '../../../components/LaunchQuizDialog/LaunchQuizDialog'; diff --git a/client/src/__tests__/components/LoadingCircle/LoadingCircle.test.tsx b/client/src/__tests__/components/LoadingCircle/LoadingCircle.test.tsx index 4351cff..11c9ee5 100644 --- a/client/src/__tests__/components/LoadingCircle/LoadingCircle.test.tsx +++ b/client/src/__tests__/components/LoadingCircle/LoadingCircle.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import LoadingCircle from '../../../components/LoadingCircle/LoadingCircle'; diff --git a/client/src/__tests__/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.test.tsx b/client/src/__tests__/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.test.tsx index 407e38c..573bcae 100644 --- a/client/src/__tests__/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import MultipleChoiceQuestion from '../../../../components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion'; diff --git a/client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx b/client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx index 52b0f6d..5dbb5e7 100644 --- a/client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx @@ -1,4 +1,5 @@ // NumericalQuestion.test.tsx +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import NumericalQuestion from '../../../../components/Questions/NumericalQuestion/NumericalQuestion'; diff --git a/client/src/__tests__/components/Questions/Question.test.tsx b/client/src/__tests__/components/Questions/Question.test.tsx index cbe2228..c92606a 100644 --- a/client/src/__tests__/components/Questions/Question.test.tsx +++ b/client/src/__tests__/components/Questions/Question.test.tsx @@ -1,4 +1,5 @@ // Question.test.tsx +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import Questions from '../../../components/Questions/Question'; diff --git a/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx b/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx index 3cc618e..bb45604 100644 --- a/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx @@ -1,4 +1,5 @@ // ShortAnswerQuestion.test.tsx +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import ShortAnswerQuestion from '../../../../components/Questions/ShortAnswerQuestion/ShortAnswerQuestion'; @@ -11,6 +12,7 @@ describe('ShortAnswerQuestion Component', () => { questionTitle: 'Sample Question', choices: [ { + id: '1', feedback: { format: 'text', text: 'Correct answer feedback' @@ -22,6 +24,7 @@ describe('ShortAnswerQuestion Component', () => { } }, { + id: '2', feedback: null, isCorrect: false, text: { @@ -58,7 +61,7 @@ describe('ShortAnswerQuestion Component', () => { expect(submitButton).toBeDisabled(); }); - it('not submited answer if nothing is entered', () => { + it('not submitted answer if nothing is entered', () => { const submitButton = screen.getByText('Répondre'); fireEvent.click(submitButton); diff --git a/client/src/__tests__/components/Questions/TrueFalseQuestion/TrueFalseQuestion.test.tsx b/client/src/__tests__/components/Questions/TrueFalseQuestion/TrueFalseQuestion.test.tsx index 0c21eff..afb015b 100644 --- a/client/src/__tests__/components/Questions/TrueFalseQuestion/TrueFalseQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/TrueFalseQuestion/TrueFalseQuestion.test.tsx @@ -1,4 +1,5 @@ // TrueFalseQuestion.test.tsx +import React from 'react'; import { render, fireEvent, screen, act } from '@testing-library/react'; import '@testing-library/jest-dom'; import TrueFalseQuestion from '../../../../components/Questions/TrueFalseQuestion/TrueFalseQuestion'; diff --git a/client/src/__tests__/components/ReturnButton/ReturnButton.test.tsx b/client/src/__tests__/components/ReturnButton/ReturnButton.test.tsx index 9793571..d2861a9 100644 --- a/client/src/__tests__/components/ReturnButton/ReturnButton.test.tsx +++ b/client/src/__tests__/components/ReturnButton/ReturnButton.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import ReturnButton from '../../../components/ReturnButton/ReturnButton'; diff --git a/client/src/__tests__/components/StudentWaitPage/StudentWaitPage.test.tsx b/client/src/__tests__/components/StudentWaitPage/StudentWaitPage.test.tsx index c5c8dad..3e171c3 100644 --- a/client/src/__tests__/components/StudentWaitPage/StudentWaitPage.test.tsx +++ b/client/src/__tests__/components/StudentWaitPage/StudentWaitPage.test.tsx @@ -1,4 +1,5 @@ // Importez le type UserType s'il n'est pas déjà importé +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import StudentWaitPage from '../../../components/StudentWaitPage/StudentWaitPage'; diff --git a/client/src/__tests__/pages/Home/Home.test.tsx b/client/src/__tests__/pages/Home/Home.test.tsx index fcb7828..f2bc746 100644 --- a/client/src/__tests__/pages/Home/Home.test.tsx +++ b/client/src/__tests__/pages/Home/Home.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import { BrowserRouter } from 'react-router-dom'; @@ -32,4 +33,4 @@ describe('Home', () => { fireEvent.click(teacherButton); expect(window.location.pathname).toBe('/teacher/dashboard'); }); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx index 3c633bb..fa6475f 100644 --- a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen, fireEvent, act } from '@testing-library/react'; import '@testing-library/jest-dom'; import { parse } from 'gift-pegjs'; diff --git a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx index 57ab031..1524e2e 100644 --- a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx @@ -1,4 +1,5 @@ //TeacherModeQuiz.test.tsx +import React from 'react'; import { render, fireEvent, act } from '@testing-library/react'; import { screen } from '@testing-library/dom'; import '@testing-library/jest-dom'; diff --git a/client/src/__tests__/pages/Teacher/Dashboard/Dashboard.test.tsx b/client/src/__tests__/pages/Teacher/Dashboard/Dashboard.test.tsx index 97500a7..f711673 100644 --- a/client/src/__tests__/pages/Teacher/Dashboard/Dashboard.test.tsx +++ b/client/src/__tests__/pages/Teacher/Dashboard/Dashboard.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { MemoryRouter } from 'react-router-dom'; diff --git a/client/src/__tests__/pages/Teacher/EditorQuiz/EditorQuiz.test.tsx b/client/src/__tests__/pages/Teacher/EditorQuiz/EditorQuiz.test.tsx index d75c9ee..bf9125e 100644 --- a/client/src/__tests__/pages/Teacher/EditorQuiz/EditorQuiz.test.tsx +++ b/client/src/__tests__/pages/Teacher/EditorQuiz/EditorQuiz.test.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { MemoryRouter } from 'react-router-dom'; diff --git a/client/src/__tests__/services/QuizService.test.tsx b/client/src/__tests__/services/QuizService.test.tsx index 10ed28d..85fc059 100644 --- a/client/src/__tests__/services/QuizService.test.tsx +++ b/client/src/__tests__/services/QuizService.test.tsx @@ -31,8 +31,8 @@ Object.defineProperty(window, 'localStorage', { // NOTE: this suite seems to be designed around local storage of quizzes (older version, before a database) describe.skip('QuizService', () => { const mockQuizzes: QuizType[] = [ - { folderId: 'test', userId: 'user', _id: 'quiz1', title: 'Quiz One', content: ['Q1', 'Q2'], created_at: new Date('2024-09-15'), updated_at: new Date('2024-09-15') }, - { folderId: 'test', userId: 'user', _id: 'quiz2', title: 'Quiz Two', content: ['Q3', 'Q4'], created_at: new Date('2024-09-15'), updated_at: new Date('2024-09-15') }, + { folderId: 'test', folderName: 'test', userId: 'user', _id: 'quiz1', title: 'Quiz One', content: ['Q1', 'Q2'], created_at: new Date('2024-09-15'), updated_at: new Date('2024-09-15') }, + { folderId: 'test', folderName: 'test', userId: 'user', _id: 'quiz2', title: 'Quiz Two', content: ['Q3', 'Q4'], created_at: new Date('2024-09-15'), updated_at: new Date('2024-09-15') }, ]; beforeEach(() => { diff --git a/client/src/__tests__/services/WebsocketService.test.tsx b/client/src/__tests__/services/WebsocketService.test.tsx index b4965dc..5a98e3e 100644 --- a/client/src/__tests__/services/WebsocketService.test.tsx +++ b/client/src/__tests__/services/WebsocketService.test.tsx @@ -1,16 +1,10 @@ //WebsocketService.test.tsx import WebsocketService from '../../services/WebsocketService'; import { io, Socket } from 'socket.io-client'; -import { ENV_VARIABLES } from '../../constants'; +import { ENV_VARIABLES } from 'src/constants'; jest.mock('socket.io-client'); -// jest.mock('../../constants', () => ({ -// ENV_VARIABLES: { -// VITE_BACKEND_SOCKET_URL: 'https://ets-glitch-backend.glitch.me/' -// } -// })); - describe('WebSocketService', () => { let mockSocket: Partial; diff --git a/client/src/components/DisconnectButton/DisconnectButton.tsx b/client/src/components/DisconnectButton/DisconnectButton.tsx index 831a1c7..04502d4 100644 --- a/client/src/components/DisconnectButton/DisconnectButton.tsx +++ b/client/src/components/DisconnectButton/DisconnectButton.tsx @@ -1,4 +1,5 @@ // GoBackButton.tsx +import React from 'react'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import ConfirmDialog from '../ConfirmDialog/ConfirmDialog'; @@ -33,7 +34,7 @@ const DisconnectButton: React.FC = ({ }; const handleOnReturn = () => { - if (!!onReturn) { + if (onReturn) { onReturn(); } else { navigate(-1); diff --git a/client/src/components/Footer/Footer.tsx b/client/src/components/Footer/Footer.tsx index 77b8edf..331d8ec 100644 --- a/client/src/components/Footer/Footer.tsx +++ b/client/src/components/Footer/Footer.tsx @@ -1,20 +1,16 @@ import * as React from 'react'; import './footer.css'; -interface FooterProps { +type FooterProps = object; //empty object -} - -const Footer: React.FC = ({ }) => { +const Footer: React.FC = () => { return (
- Réalisé avec ❤ à Montréal par des finissant•e•s de l'ETS + Réalisé avec ❤ à Montréal par des finissant•e•s de l'ETS
diff --git a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx index 6e13395..671ae9d 100644 --- a/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx +++ b/client/src/components/GIFTCheatSheet/GiftCheatSheet.tsx @@ -28,9 +28,9 @@ const GiftCheatSheet: React.FC = () => { const QuestionNum ="Question {#=Nombre\n} //OU \nQuestion {#=Nombre:Tolérance\n} // OU \nQuestion {#=PetitNombre..GrandNombre\n}\n// La tolérance est un pourcentage.\n// La réponse doit être comprise entre PetitNombre et GrandNombre"; return (
-

Informations pratiques sur l'éditeur

+

Informations pratiques sur l'éditeur

- L'éditeur utilise le format GIFT (General Import Format Template) créé pour la + L'éditeur utilise le format GIFT (General Import Format Template) créé pour la plateforme Moodle afin de générer les mini-tests. Ci-dessous vous pouvez retrouver la syntaxe pour chaque type de question : @@ -126,7 +126,7 @@ const GiftCheatSheet: React.FC = () => {

7. Paramètres optionnels

Si vous souhaitez utiliser certains caractères spéciaux dans vos énoncés, - réponses ou feedback, vous devez 'échapper' ces derniers en ajoutant un \ + réponses ou feedback, vous devez «échapper» ces derniers en ajoutant un \ devant:

@@ -140,9 +140,9 @@ const GiftCheatSheet: React.FC = () => {
                 

8. LaTeX et Markdown

Les formats LaTeX et Markdown sont supportés dans cette application. Vous devez cependant penser - à 'échapper' les caractères spéciaux mentionnés plus haut. + à «échapper» les caractères spéciaux mentionnés plus haut.

-

Exemple d'équation:

+

Exemple d'équation:

                     {'$$x\\= \\frac\\{y^2\\}\\{4\\}$$'}
                     {'\n$x\\= \\frac\\{y^2\\}\\{4\\}$'}
@@ -167,16 +167,16 @@ const GiftCheatSheet: React.FC = () => {
                         {'")'}
                     
                 
-

Exemple d'une question Vrai/Faux avec l'image d'un chat:

+

Exemple d'une question Vrai/Faux avec l'image d'un chat:

                     
                         {'[markdown]Ceci est un chat: \n![Image de chat](https\\://www.example.com\\:8000/chat.jpg "Chat mignon")\n{T}'}
                     
                 
-

Note : les images étant spécifiées avec la syntaxe Markdown dans GIFT, on doit échapper les caractères spéciales (:) dans l'URL de l'image.

+

Note : les images étant spécifiées avec la syntaxe Markdown dans GIFT, on doit échapper les caractères spéciales (:) dans l'URL de l'image.

Note : On ne peut utiliser les images dans les messages de rétroaction (GIFT), car les rétroactions ne supportent pas le texte avec formatage (Markdown).

- Attention: l'ancienne fonctionnalité avec les balises {''} n'est plus + Attention: l'ancienne fonctionnalité avec les balises {''} n'est plus supportée.

@@ -184,7 +184,7 @@ const GiftCheatSheet: React.FC = () => {

10. Informations supplémentaires

- GIFT supporte d'autres formats de questions que nous ne gérons pas sur cette + GIFT supporte d'autres formats de questions que nous ne gérons pas sur cette application.

Vous pouvez retrouver la Documentation de GIFT (en anglais):

diff --git a/client/src/components/GiftTemplate/templates/TextType.ts b/client/src/components/GiftTemplate/templates/TextType.ts index c6d8ecf..02a7a14 100644 --- a/client/src/components/GiftTemplate/templates/TextType.ts +++ b/client/src/components/GiftTemplate/templates/TextType.ts @@ -30,7 +30,7 @@ export function formatLatex(text: string): string { */ export default function textType({ text }: TextTypeOptions) { const formatText = formatLatex(text.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped - + let parsedText = ''; switch (text.format) { case 'moodle': case 'plain': @@ -40,7 +40,7 @@ export default function textType({ text }: TextTypeOptions) { // Strip outer paragraph tags (not a great approach with regex) return formatText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2'); case 'markdown': - const parsedText = marked.parse(formatText, { breaks: true }) as string; // https://github.com/markedjs/marked/discussions/3219 + parsedText = marked.parse(formatText, { breaks: true }) as string; // https://github.com/markedjs/marked/discussions/3219 return parsedText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2'); default: throw new Error(`Unsupported text format: ${text.format}`); diff --git a/client/src/components/ImportModal/ImportModal.tsx b/client/src/components/ImportModal/ImportModal.tsx index a00b8bb..41ab6ca 100644 --- a/client/src/components/ImportModal/ImportModal.tsx +++ b/client/src/components/ImportModal/ImportModal.tsx @@ -168,7 +168,7 @@ const DragAndDrop: React.FC = ({ handleOnClose, handleOnImport, open, sel Déposer des fichiers ici ou
- cliquez pour ouvrir l'explorateur des fichiers + cliquez pour ouvrir l'explorateur des fichiers

diff --git a/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx b/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx index e174b23..3bd307b 100644 --- a/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx +++ b/client/src/components/LaunchQuizDialog/LaunchQuizDialog.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { Button, Dialog, diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index f08ad27..b7d1c72 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -300,7 +300,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti -
Nom d'utilisateur
+
Nom d'utilisateur
{Array.from({ length: maxQuestions }, (_, index) => ( = (props) => { (choice.isCorrect ? '✅' : '❌')}
{alphabet[i]}
- {formatLatex(choice.text.text)} +
{choice.feedback && showAnswer && ( diff --git a/client/src/components/Questions/Question.tsx b/client/src/components/Questions/Question.tsx index c5af0a6..b3f21eb 100644 --- a/client/src/components/Questions/Question.tsx +++ b/client/src/components/Questions/Question.tsx @@ -42,7 +42,7 @@ const Question: React.FC = ({ questionTypeComponent = ( ({ ...choice, id: index.toString() }))} handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} globalFeedback={question.globalFeedback?.text} @@ -78,7 +78,7 @@ const Question: React.FC = ({ questionTypeComponent = ( ({ ...choice, id: index.toString() }))} handleOnSubmitAnswer={handleOnSubmitAnswer} showAnswer={showAnswer} globalFeedback={question.globalFeedback?.text} diff --git a/client/src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.tsx b/client/src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.tsx index 87d077a..3f134d6 100644 --- a/client/src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.tsx +++ b/client/src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.tsx @@ -10,6 +10,7 @@ type Choices = { isCorrect: boolean; text: { format: string; text: string }; weigth?: number; + id: string; }; interface Props { @@ -33,7 +34,9 @@ const ShortAnswerQuestion: React.FC = (props) => { <>
{choices.map((choice) => ( -
{choice.text.text}
+
+ {choice.text.text} +
))}
{globalFeedback &&
{globalFeedback}
} diff --git a/client/src/components/ReturnButton/ReturnButton.tsx b/client/src/components/ReturnButton/ReturnButton.tsx index 0792cfb..a184356 100644 --- a/client/src/components/ReturnButton/ReturnButton.tsx +++ b/client/src/components/ReturnButton/ReturnButton.tsx @@ -1,4 +1,5 @@ // GoBackButton.tsx +import React from 'react'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import ConfirmDialog from '../ConfirmDialog/ConfirmDialog'; @@ -33,7 +34,7 @@ const ReturnButton: React.FC = ({ }; const handleOnReturn = () => { - if (!!onReturn) { + if (onReturn) { onReturn(); } else { navigate(-1); diff --git a/client/src/components/StudentWaitPage/StudentWaitPage.tsx b/client/src/components/StudentWaitPage/StudentWaitPage.tsx index 5937f08..9989b71 100644 --- a/client/src/components/StudentWaitPage/StudentWaitPage.tsx +++ b/client/src/components/StudentWaitPage/StudentWaitPage.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { Box, Button, Chip } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; import { PlayArrow } from '@mui/icons-material'; diff --git a/client/src/constants.tsx b/client/src/constants.tsx index 09afd51..1fc104b 100644 --- a/client/src/constants.tsx +++ b/client/src/constants.tsx @@ -1,8 +1,8 @@ // constants.tsx const ENV_VARIABLES = { MODE: 'production', - VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || "", - VITE_BACKEND_SOCKET_URL: process.env.VITE_BACKEND_SOCKET_URL || "", + VITE_BACKEND_URL: import.meta.env.VITE_BACKEND_URL || "", + VITE_BACKEND_SOCKET_URL: import.meta.env.VITE_BACKEND_SOCKET_URL || "", }; console.log(`ENV_VARIABLES.VITE_BACKEND_URL=${ENV_VARIABLES.VITE_BACKEND_URL}`); diff --git a/client/src/main.tsx b/client/src/main.tsx index e4c3824..e73c979 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App.tsx'; @@ -27,10 +28,16 @@ const theme = createTheme({ } }); -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - -); +const rootElement = document.getElementById('root'); +if (rootElement) { + + ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + ); +} else { + console.error('Root element not found'); +} diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx index 1c865e8..040f22e 100644 --- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx +++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Socket } from 'socket.io-client'; -import { ENV_VARIABLES } from '../../../constants'; +import { ENV_VARIABLES } from 'src/constants'; import StudentModeQuiz from '../../../components/StudentModeQuiz/StudentModeQuiz'; import TeacherModeQuiz from '../../../components/TeacherModeQuiz/TeacherModeQuiz'; diff --git a/client/src/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx index 4b05431..4b9583a 100644 --- a/client/src/pages/Teacher/Dashboard/Dashboard.tsx +++ b/client/src/pages/Teacher/Dashboard/Dashboard.tsx @@ -18,8 +18,11 @@ import { IconButton, InputAdornment, Button, + Card, Tooltip, - NativeSelect + NativeSelect, + CardContent, + styled, } from '@mui/material'; import { Search, @@ -34,13 +37,43 @@ import { // DriveFileMove } from '@mui/icons-material'; +// Create a custom-styled Card component +const CustomCard = styled(Card)({ + overflow: 'visible', // Override the overflow property + position: 'relative', + margin: '40px 0 20px 0', // Add top margin to make space for the tab + borderRadius: '8px', + paddingTop: '20px', // Ensure content inside the card doesn't overlap with the tab +}); + const Dashboard: React.FC = () => { const navigate = useNavigate(); const [quizzes, setQuizzes] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [showImportModal, setShowImportModal] = useState(false); const [folders, setFolders] = useState([]); - const [selectedFolder, setSelectedFolder] = useState(''); // Selected folder + const [selectedFolderId, setSelectedFolderId] = useState(''); // Selected folder + + // Filter quizzes based on search term + // const filteredQuizzes = quizzes.filter(quiz => + // quiz.title.toLowerCase().includes(searchTerm.toLowerCase()) + // ); + const filteredQuizzes = useMemo(() => { + return quizzes.filter( + (quiz) => + quiz && quiz.title && quiz.title.toLowerCase().includes(searchTerm.toLowerCase()) + ); + }, [quizzes, searchTerm]); + + + // Group quizzes by folder + const quizzesByFolder = filteredQuizzes.reduce((acc, quiz) => { + if (!acc[quiz.folderName]) { + acc[quiz.folderName] = []; + } + acc[quiz.folderName].push(quiz); + return acc; + }, {} as Record); useEffect(() => { const fetchData = async () => { @@ -49,7 +82,7 @@ const Dashboard: React.FC = () => { return; } else { - let userFolders = await ApiService.getUserFolders(); + const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); } @@ -59,40 +92,23 @@ const Dashboard: React.FC = () => { fetchData(); }, []); - - - - - - - - - - - - - - - - - - const handleSelectFolder = (event: React.ChangeEvent) => { - setSelectedFolder(event.target.value); + setSelectedFolderId(event.target.value); }; - useEffect(() => { const fetchQuizzesForFolder = async () => { - if (selectedFolder == '') { + if (selectedFolderId == '') { const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load console.log("show all quizes") - var quizzes: QuizType[] = []; + let quizzes: QuizType[] = []; for (const folder of folders as FolderType[]) { const folderQuizzes = await ApiService.getFolderContent(folder._id); console.log("folder: ", folder.title, " quiz: ", folderQuizzes); + // add the folder.title to the QuizType if the folderQuizzes is an array + addFolderTitleToQuizzes(folderQuizzes, folder.title); quizzes = quizzes.concat(folderQuizzes as QuizType[]) } @@ -100,17 +116,19 @@ const Dashboard: React.FC = () => { } else { console.log("show some quizzes") - const folderQuizzes = await ApiService.getFolderContent(selectedFolder); + const folderQuizzes = await ApiService.getFolderContent(selectedFolderId); console.log("folderQuizzes: ", folderQuizzes); + // get the folder title from its id + const folderTitle = folders.find((folder) => folder._id === selectedFolderId)?.title || ''; + addFolderTitleToQuizzes(folderQuizzes, folderTitle); setQuizzes(folderQuizzes as QuizType[]); - } }; fetchQuizzesForFolder(); - }, [selectedFolder]); + }, [selectedFolderId]); const handleSearch = (event: React.ChangeEvent) => { @@ -135,22 +153,24 @@ const Dashboard: React.FC = () => { const handleDuplicateQuiz = async (quiz: QuizType) => { try { await ApiService.duplicateQuiz(quiz._id); - if (selectedFolder == '') { + if (selectedFolderId == '') { const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load - console.log("show all quizes") - var quizzes: QuizType[] = []; + console.log("show all quizzes") + let quizzes: QuizType[] = []; for (const folder of folders as FolderType[]) { const folderQuizzes = await ApiService.getFolderContent(folder._id); console.log("folder: ", folder.title, " quiz: ", folderQuizzes); - quizzes = quizzes.concat(folderQuizzes as QuizType[]) + addFolderTitleToQuizzes(folderQuizzes, folder.title); + quizzes = quizzes.concat(folderQuizzes as QuizType[]); } setQuizzes(quizzes as QuizType[]); } else { console.log("show some quizzes") - const folderQuizzes = await ApiService.getFolderContent(selectedFolder); + const folderQuizzes = await ApiService.getFolderContent(selectedFolderId); + addFolderTitleToQuizzes(folderQuizzes, selectedFolderId); setQuizzes(folderQuizzes as QuizType[]); } @@ -159,13 +179,6 @@ const Dashboard: React.FC = () => { } }; - const filteredQuizzes = useMemo(() => { - return quizzes.filter( - (quiz) => - quiz && quiz.title && quiz.title.toLowerCase().includes(searchTerm.toLowerCase()) - ); - }, [quizzes, searchTerm]); - const handleOnImport = () => { setShowImportModal(true); @@ -183,6 +196,7 @@ const Dashboard: React.FC = () => { // questions[i] = QuestionService.ignoreImgTags(questions[i]); const parsedItem = parse(questions[i]); Template(parsedItem[0]); + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return false; } @@ -191,30 +205,6 @@ const Dashboard: React.FC = () => { return true; }; - // const handleMoveQuiz = async (quiz: QuizType, newFolderId: string) => { - // try { - // await ApiService.moveQuiz(quiz._id, newFolderId); - // if (selectedFolder == '') { - // const folders = await ApiService.getUserFolders(); - // var quizzes: QuizType[] = []; - - // for (const folder of folders as FolderType[]) { - // const folderQuizzes = await ApiService.getFolderContent(folder._id); - // quizzes = quizzes.concat(folderQuizzes as QuizType[]) - // } - - // setQuizzes(quizzes as QuizType[]); - // } - // else { - // const folderQuizzes = await ApiService.getFolderContent(selectedFolder); - // setQuizzes(folderQuizzes as QuizType[]); - // } - // } catch (error) { - // console.error('Error moving quiz:', error); - // } - // }; - - const downloadTxtFile = async (quiz: QuizType) => { try { @@ -227,7 +217,7 @@ const Dashboard: React.FC = () => { //const { title, content } = selectedQuiz; let quizContent = ""; - let title = selectedQuiz.title; + const title = selectedQuiz.title; console.log(selectedQuiz.content); selectedQuiz.content.forEach((question, qIndex) => { const formattedQuestion = question.trim(); @@ -264,7 +254,7 @@ const Dashboard: React.FC = () => { const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); const newlyCreatedFolder = userFolders[userFolders.length - 1] as FolderType; - setSelectedFolder(newlyCreatedFolder._id); + setSelectedFolderId(newlyCreatedFolder._id); } } catch (error) { @@ -274,18 +264,17 @@ const Dashboard: React.FC = () => { const handleDeleteFolder = async () => { - try { const confirmed = window.confirm('Voulez-vous vraiment supprimer ce dossier?'); if (confirmed) { - await ApiService.deleteFolder(selectedFolder); + await ApiService.deleteFolder(selectedFolderId); const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); } const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load - console.log("show all quizes") - var quizzes: QuizType[] = []; + console.log("show all quizzes") + let quizzes: QuizType[] = []; for (const folder of folders as FolderType[]) { const folderQuizzes = await ApiService.getFolderContent(folder._id); @@ -294,19 +283,20 @@ const Dashboard: React.FC = () => { } setQuizzes(quizzes as QuizType[]); - setSelectedFolder(''); + setSelectedFolderId(''); } catch (error) { console.error('Error deleting folder:', error); } }; + const handleRenameFolder = async () => { try { // folderId: string GET THIS FROM CURRENT FOLDER // currentTitle: string GET THIS FROM CURRENT FOLDER const newTitle = prompt('Entrée le nouveau nom du fichier', "Nouveau nom de dossier"); if (newTitle) { - await ApiService.renameFolder(selectedFolder, newTitle); + await ApiService.renameFolder(selectedFolderId, newTitle); const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); @@ -315,15 +305,16 @@ const Dashboard: React.FC = () => { console.error('Error renaming folder:', error); } }; + const handleDuplicateFolder = async () => { try { // folderId: string GET THIS FROM CURRENT FOLDER - await ApiService.duplicateFolder(selectedFolder); + await ApiService.duplicateFolder(selectedFolderId); // TODO set the selected folder to be the duplicated folder const userFolders = await ApiService.getUserFolders(); setFolders(userFolders as FolderType[]); const newlyCreatedFolder = userFolders[userFolders.length - 1] as FolderType; - setSelectedFolder(newlyCreatedFolder._id); + setSelectedFolderId(newlyCreatedFolder._id); } catch (error) { console.error('Error duplicating folder:', error); } @@ -393,7 +384,7 @@ const Dashboard: React.FC = () => { @@ -416,7 +407,7 @@ const Dashboard: React.FC = () => { @@ -424,7 +415,7 @@ const Dashboard: React.FC = () => { @@ -433,7 +424,7 @@ const Dashboard: React.FC = () => { aria-label="delete" color="primary" onClick={handleDeleteFolder} - disabled={selectedFolder == ''} // cannot action on all + disabled={selectedFolderId == ''} // cannot action on all >
@@ -461,74 +452,72 @@ const Dashboard: React.FC = () => {
+ {Object.keys(quizzesByFolder).map(folderName => ( + +
{folderName}
+ + {quizzesByFolder[folderName].map((quiz: QuizType) => ( +
+
+ + + +
- {filteredQuizzes.map((quiz: QuizType) => ( -
-
- - - -
+
+ + downloadTxtFile(quiz)} + > + -
- - downloadTxtFile(quiz)} - > - + + handleEditQuiz(quiz)} + > + - - handleEditQuiz(quiz)} - > - + + handleDuplicateQuiz(quiz)} + > + - {/* - handleMoveQuiz(quiz)} - > - */} + + handleRemoveQuiz(quiz)} + > + - - handleDuplicateQuiz(quiz)} - > - - - - handleRemoveQuiz(quiz)} - > - - - - handleShareQuiz(quiz)} - > - -
-
+ + handleShareQuiz(quiz)} + > + +
+
+ ))} +
+
))}
- setShowImportModal(false)} handleOnImport={handleOnImport} - selectedFolder={selectedFolder} + selectedFolder={selectedFolderId} /> @@ -536,3 +525,11 @@ const Dashboard: React.FC = () => { }; export default Dashboard; +function addFolderTitleToQuizzes(folderQuizzes: string | QuizType[], folderName: string) { + if (Array.isArray(folderQuizzes)) + folderQuizzes.forEach((quiz) => { + quiz.folderName = folderName; + console.log(`quiz: ${quiz.title} folder: ${quiz.folderName}`); + }); +} + diff --git a/client/src/pages/Teacher/Dashboard/dashboard.css b/client/src/pages/Teacher/Dashboard/dashboard.css index 8d62d3c..a17f46a 100644 --- a/client/src/pages/Teacher/Dashboard/dashboard.css +++ b/client/src/pages/Teacher/Dashboard/dashboard.css @@ -77,4 +77,43 @@ div:has(> #select-folder) { display: flex; flex-direction: row; align-items: center; -} \ No newline at end of file +} + +.dashboard .list .quiz .actions { + flex-shrink: 0; + display: flex; + flex-direction: row; + align-items: center; +} + +.folder-card { + position: relative; + /* margin: 40px 0 20px 0; /* Add top margin to make space for the tab */ + border-radius: 8px; + color: #f9f9f9; + --outline-color: #e1e1e1; + border: 2px solid var(--outline-color); +} + +.folder-tab { + position: absolute; + top: -33px; + left: 9px; + padding: 5px 10px; + border-radius: 8px 8px 0 0; + font-weight: bold; + white-space: nowrap; /* Prevent text from wrapping */ + display: inline-block; /* Ensure the tab width is based on content */ + border: 2px solid var(--outline-color); + border-bottom-style: none; + background-color: white; /* Optional: background color to match the card */ + color: #3f51b5; /* Text color to match the outline */ +} + +/* .folder-card:nth-child(odd) { + background-color: #f9f9f9; +} + +.folder-card:nth-child(even) { + background-color: #e0e0e0; +} */ diff --git a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx index 15ceeb2..e17c91c 100644 --- a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx +++ b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx @@ -162,8 +162,10 @@ const QuizForm: React.FC = () => { if (fileInputRef.current) { fileInputRef.current.value = ''; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`) + } }; @@ -245,7 +247,7 @@ const QuizForm: React.FC = () => { onClose={() => setDialogOpen(false)} > Erreur - Veuillez d'abord choisir une image à téléverser. + Veuillez d'abord choisir une image à téléverser.