From 11b719e2cac3db8d0ce5958e53c7dfc8a38ecb3e Mon Sep 17 00:00:00 2001 From: Philippe <83185129+phil3838@users.noreply.github.com> Date: Sat, 5 Apr 2025 15:34:44 -0400 Subject: [PATCH] dragable question in Editor --- client/package-lock.json | 113 +++++++++ client/package.json | 2 + client/src/components/Editor/Editor.tsx | 219 ++++++++++++++---- .../pages/Teacher/EditorQuiz/EditorQuiz.tsx | 2 +- 4 files changed, 284 insertions(+), 52 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index e2c6890..a6590ca 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -28,6 +28,7 @@ "marked": "^14.1.2", "nanoid": "^5.1.2", "react": "^18.3.1", + "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.3.1", "react-modal": "^3.16.3", "react-router-dom": "^6.26.2", @@ -48,6 +49,7 @@ "@types/jest": "^29.5.13", "@types/node": "^22.13.5", "@types/react": "^18.2.15", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18.2.7", "@types/react-latex": "^2.0.3", "@typescript-eslint/eslint-plugin": "^8.25.0", @@ -4555,6 +4557,15 @@ "@types/unist": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -4695,6 +4706,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-beautiful-dnd": { + "version": "13.1.8", + "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz", + "integrity": "sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.3.5", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", @@ -4715,6 +4735,17 @@ "@types/react": "*" } }, + "node_modules/@types/react-redux": { + "version": "7.1.34", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz", + "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", @@ -5978,6 +6009,14 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -9945,6 +9984,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -11188,6 +11232,11 @@ ], "license": "MIT" }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -11200,6 +11249,25 @@ "node": ">=0.10.0" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "deprecated": "react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -11241,6 +11309,35 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19" } }, + "node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "node_modules/react-router": { "version": "6.30.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", @@ -11316,6 +11413,14 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -12841,6 +12946,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", diff --git a/client/package.json b/client/package.json index b6d62e4..b06d5de 100644 --- a/client/package.json +++ b/client/package.json @@ -32,6 +32,7 @@ "marked": "^14.1.2", "nanoid": "^5.1.2", "react": "^18.3.1", + "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.3.1", "react-modal": "^3.16.3", "react-router-dom": "^6.26.2", @@ -52,6 +53,7 @@ "@types/jest": "^29.5.13", "@types/node": "^22.13.5", "@types/react": "^18.2.15", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18.2.7", "@types/react-latex": "^2.0.3", "@typescript-eslint/eslint-plugin": "^8.25.0", diff --git a/client/src/components/Editor/Editor.tsx b/client/src/components/Editor/Editor.tsx index 17d5237..622efb3 100644 --- a/client/src/components/Editor/Editor.tsx +++ b/client/src/components/Editor/Editor.tsx @@ -1,7 +1,10 @@ -import React from 'react'; -import { TextField, Typography, IconButton, Box } from '@mui/material'; +import React, { useState } from 'react'; +import { TextField, Typography, IconButton, Box, Collapse, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import VisibilityIcon from '@mui/icons-material/Visibility'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; interface EditorProps { label: string; @@ -11,6 +14,10 @@ interface EditorProps { } const Editor: React.FC = ({ label, values, onValuesChange, onFocusQuestion }) => { + const [collapsed, setCollapsed] = useState(Array(values.length).fill(false)); + const [dialogOpen, setDialogOpen] = useState(false); + const [deleteIndex, setDeleteIndex] = useState(null); + const handleChange = (index: number) => (event: React.ChangeEvent) => { const newValues = [...values]; newValues[index] = event.target.value; @@ -18,67 +25,177 @@ const Editor: React.FC = ({ label, values, onValuesChange, onFocusQ }; const handleDeleteQuestion = (index: number) => () => { - const newValues = values.filter((_, i) => i !== index); // Remove the question at the specified index - onValuesChange(newValues); + if (values[index].trim() === '') { + const newValues = values.filter((_, i) => i !== index); + onValuesChange(newValues); + setCollapsed((prev) => prev.filter((_, i) => i !== index)); + } else { + setDeleteIndex(index); + setDialogOpen(true); + } + }; + + const handleConfirmDelete = () => { + if (deleteIndex !== null) { + const newValues = values.filter((_, i) => i !== deleteIndex); + onValuesChange(newValues); + setCollapsed((prev) => prev.filter((_, i) => i !== deleteIndex)); + } + setDialogOpen(false); + setDeleteIndex(null); + }; + + const handleCancelDelete = () => { + setDialogOpen(false); + setDeleteIndex(null); }; const handleFocusQuestion = (index: number) => () => { if (onFocusQuestion) { - onFocusQuestion(index); // Call the focus function if provided + onFocusQuestion(index); } + }; + + const handleToggleCollapse = (index: number) => () => { + setCollapsed((prev) => { + const newCollapsed = [...prev]; + newCollapsed[index] = !newCollapsed[index]; + return newCollapsed; + }); + }; + + const onDragEnd = (result: any) => { + if (!result.destination) return; + + const newValues = [...values]; + const [reorderedItem] = newValues.splice(result.source.index, 1); + newValues.splice(result.destination.index, 0, reorderedItem); + onValuesChange(newValues); + + const newCollapsed = [...collapsed]; + const [reorderedCollapsed] = newCollapsed.splice(result.source.index, 1); + newCollapsed.splice(result.destination.index, 0, reorderedCollapsed); + setCollapsed(newCollapsed); + }; + + if (collapsed.length !== values.length) { + setCollapsed((prev) => { + const newCollapsed = [...prev]; + while (newCollapsed.length < values.length) newCollapsed.push(false); + while (newCollapsed.length > values.length) newCollapsed.pop(); + return newCollapsed; + }); } - - return ( + return (
{label} - {values.map((value, index) => ( - - - - Question {index + 1} - - - {/* Focus (Eye) Button */} - - - - {/* Delete Button */} - - - - - - - - ))} + + + {(provided) => ( +
+ {values.map((value, index) => ( + + {(provided) => ( + + + + Question {index + 1} + + + + {collapsed[index] ? : } + + + + + + + + + + + + + + )} + + ))} + {provided.placeholder} +
+ )} +
+
+ + {/* Confirmation Dialog */} + + Suppression + + + Confirmez vous la suppression de Question {deleteIndex !== null ? deleteIndex + 1 : ''} ? + + + + + + +
); }; diff --git a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx index 76de8a3..3a10f03 100644 --- a/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx +++ b/client/src/pages/Teacher/EditorQuiz/EditorQuiz.tsx @@ -272,7 +272,7 @@ const QuizForm: React.FC = () => {