From d57c61f78fdfb3c6e9c533a23f738061aecee5f8 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sat, 19 Oct 2024 22:58:49 -0400 Subject: [PATCH 1/2] =?UTF-8?q?Nom=20du=20dossier=20pour=20chaque=20quiz?= =?UTF-8?q?=20devrait=20=C3=AAtre=20affich=C3=A9=20lorsque=20"tous=20les?= =?UTF-8?q?=20dossiers"=20sont=20affich=C3=A9s=20Fixes=20#78=20Uses=20Card?= =?UTF-8?q?s=20in=20Material=20UI=20to=20display=20the=20quizzes=20by=20fo?= =?UTF-8?q?lder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/Types/QuizType.tsx | 1 + client/src/__tests__/Types/QuizType.test.tsx | 3 + .../__tests__/services/QuizService.test.tsx | 4 +- .../src/pages/Teacher/Dashboard/Dashboard.tsx | 254 +++++++++--------- .../src/pages/Teacher/Dashboard/dashboard.css | 41 ++- 5 files changed, 171 insertions(+), 132 deletions(-) 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/__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__/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/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx index 7226fe4..e6bddef 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, @@ -33,13 +36,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 () => { @@ -58,33 +91,14 @@ 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[] = []; @@ -92,6 +106,8 @@ const Dashboard: React.FC = () => { 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[]) } @@ -99,17 +115,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) => { @@ -134,7 +152,7 @@ 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[] = []; @@ -142,14 +160,16 @@ const Dashboard: React.FC = () => { 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[]); } @@ -158,13 +178,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); @@ -190,30 +203,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 { @@ -263,7 +252,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) { @@ -273,17 +262,16 @@ 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") + console.log("show all quizzes") var quizzes: QuizType[] = []; for (const folder of folders as FolderType[]) { @@ -293,19 +281,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[]); @@ -314,15 +303,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); } @@ -392,7 +382,7 @@ const Dashboard: React.FC = () => { @@ -415,7 +405,7 @@ const Dashboard: React.FC = () => { @@ -423,7 +413,7 @@ const Dashboard: React.FC = () => { @@ -432,7 +422,7 @@ const Dashboard: React.FC = () => { aria-label="delete" color="primary" onClick={handleDeleteFolder} - disabled={selectedFolder == ''} // cannot action on all + disabled={selectedFolderId == ''} // cannot action on all > @@ -460,74 +450,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} /> @@ -535,3 +523,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; +} */ From 4959e02acfa56a8663977e2d6538e40c84f6974c Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Fri, 10 Jan 2025 11:09:06 -0500 Subject: [PATCH 2/2] Fix broken merge, npm audit fix --- client/package-lock.json | 24 +++++++++---------- .../src/pages/Teacher/Dashboard/Dashboard.tsx | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 873505d..0b49c17 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -2678,9 +2678,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz", - "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5695,9 +5695,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -9126,9 +9126,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.8.tgz", - "integrity": "sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", "funding": [ { "type": "github", @@ -9511,9 +9511,9 @@ } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { diff --git a/client/src/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx index 58d409d..9fc8cc9 100644 --- a/client/src/pages/Teacher/Dashboard/Dashboard.tsx +++ b/client/src/pages/Teacher/Dashboard/Dashboard.tsx @@ -414,7 +414,7 @@ const Dashboard: React.FC = () => {