diff --git a/src/actions/topicAction.js b/src/actions/topicAction.js index 49e3410..06cc6f5 100644 --- a/src/actions/topicAction.js +++ b/src/actions/topicAction.js @@ -45,18 +45,26 @@ export const getTopics = (subjectName, standard, chapterNames) => async (dispatc try { dispatch({ type: GET_TOPICS_REQUEST }); - const { data } = await axios.get( - `${server}/api/get/topic?subjectName=${subjectName}&standard=${standard}&chapterName=${chapterNames}`, - { - withCredentials: true, - } - ); + // Ensure chapterNames is properly formatted as a comma-separated string + const chapters = Array.isArray(chapterNames) ? chapterNames.join(',') : chapterNames; + + // Make API call to get topics + const { data } = await axios.get(`${server}/api/get/topic`, { + params: { + subjectName, + standard, + chapterName: chapters, + }, + withCredentials: true, // Include credentials if needed + }); dispatch({ type: GET_TOPICS_SUCCESS, payload: data.topics, }); } catch (error) { + console.error('Error fetching topics:', error); // Log the error for debugging + dispatch({ type: GET_TOPICS_FAIL, payload: error.response?.data?.message || error.message, diff --git a/src/components/EditModel.jsx b/src/components/EditModel.jsx index 7043a07..49e78f8 100644 --- a/src/components/EditModel.jsx +++ b/src/components/EditModel.jsx @@ -1,6 +1,5 @@ -// EditQuestionModal.jsx -import { Select } from 'antd'; import React, { useEffect, useState } from 'react'; +import { Select } from 'antd'; import { getSubjects } from "../actions/subjectAction"; import { getChapters } from "../actions/chapterAction"; import { getTopics } from "../actions/topicAction"; @@ -18,12 +17,12 @@ const EditModel = ({ isOpen, onClose, selectedQuestion, onSave, setIsModalOpen } const { subtopics } = useSelector((state) => state.getSubtopic); const [standard, setStandard] = useState(selectedQuestion.standard || ''); const [subject, setSubject] = useState(selectedQuestion.subject || ''); - const [chapter, setChapter] = useState(selectedQuestion.chapter || ''); + const [chapter, setChapter] = useState(selectedQuestion.chapter || []); const [topic, setTopic] = useState(selectedQuestion.topics || []); const [subtopic, setSubtopic] = useState(selectedQuestion.subtopics || []); const dispatch = useDispatch(); const [error, setError] = useState(''); - if (!isOpen) return null; + useEffect(() => { if (standard) { dispatch(getSubjects(standard)); @@ -31,36 +30,39 @@ const EditModel = ({ isOpen, onClose, selectedQuestion, onSave, setIsModalOpen } if (subject && standard) { dispatch(getChapters(subject, standard)); } - + const fetchTopics = async () => { if (subject && standard && chapter.length > 0) { - setError(''); // Clear any error if chapters are selected - let allTopics = []; // To store all topics from selected chapters - for (const chap of chapter) { - const response = await dispatch(getTopics(subject, standard, chap)); - allTopics = [...allTopics, ...response.topics]; // Combine topics from all chapters + setError(''); + try { + // Fetch topics for all selected chapters in one go + const chapterNames = chapter.join(','); // Convert array to comma-separated string + const response = await dispatch(getTopics(subject, standard, chapterNames)); + + if (response && response.payload) { + setTopic(response.payload); + } + } catch (error) { + console.error('Error fetching topics:', error); } - setTopic(allTopics); // Set the combined topics if different from current state } else if (subject && standard && chapter.length === 0) { setError('Chapter selection is required.'); } }; - - fetchTopics(); // Call the function to fetch topics - - if (subject && standard && chapter.length > 0 && topic) { + + fetchTopics(); + + if (subject && standard && chapter.length > 0 && topic.length > 0) { dispatch(getSubtopics(subject, standard, chapter, topic)); } - }, [dispatch, standard, subject, chapter, topic, subtopic]); - + }, [dispatch, standard, subject, chapter, topic]); // Add `topic` to dependency array + const handleSaveChanges = async () => { - // Check if chapter is selected if (!chapter || chapter.length === 0) { setError('Chapter selection is required.'); - return; // Prevent form submission if chapter is not selected + return; } - - // Create the updated question object after validation passes + const updatedQuestion = { standard, subject, @@ -68,16 +70,15 @@ const EditModel = ({ isOpen, onClose, selectedQuestion, onSave, setIsModalOpen } topics: topic, subtopics: subtopic, }; - + try { - // Send the PUT request to update the question const response = await axios.put(`${server}/api/updatequestion/${selectedQuestion._id}`, updatedQuestion); const data = response.data; - + if (response.status === 200) { onSave(data.question); toast.success("Updated successfully"); - setIsModalOpen(false); // Close the modal after successful update + setIsModalOpen(false); } else { console.error('Failed to update question:', data.message); toast.error('Failed to update question: ' + data.message); @@ -87,8 +88,6 @@ const EditModel = ({ isOpen, onClose, selectedQuestion, onSave, setIsModalOpen } toast.error('Error updating question: ' + error.message); } }; - - const handleOverlayClick = (e) => { if (e.target === e.currentTarget) { @@ -104,10 +103,11 @@ const EditModel = ({ isOpen, onClose, selectedQuestion, onSave, setIsModalOpen } >
e.stopPropagation()} + onClick={(e) => e.stopPropagation()} >

Edit Details

+ {/* Standard Selector */}
{ setSubject(value); - setChapter(null); - setTopic(null); - setSubtopic(null) + setChapter([]); + setTopic([]); + setSubtopic([]); }} options={subjectList?.map((name) => ({ value: name, @@ -152,11 +153,12 @@ const EditModel = ({ isOpen, onClose, selectedQuestion, onSave, setIsModalOpen } Subject
+ + {/* Chapter Selector */}
{ setTopic(value); + setSubtopic([]); // Clear subtopics when topics change }} options={topicList?.map((el) => ({ value: el.name, @@ -202,6 +206,8 @@ const EditModel = ({ isOpen, onClose, selectedQuestion, onSave, setIsModalOpen } Topic
+ + {/* Subtopic Selector */}
setSelectedSubtopics(value)} + loading={isSubtopicsLoading} + options={subtopics.map((subtopic) => ({ + label: subtopic.name, // Adjust this based on the actual API response field + value: subtopic.name, // Adjust this based on the actual API response field + }))} + /> + )} + {/* Render recursive subtopics if they exist */} + {subtopics.map((subtopic) => ( +
+ {/* Recursive call to render further levels of subtopics */} + {renderSubtopicSelectors(subtopic.subtopics, level + 1)} +
+ ))} +
+
+ ); + }; + const handleSelectAllAllQuestions = () => { + if (filteredQuestions.length === 0) return; + const newSelectedQuestions = !selectAllAllQuestions ? {} : {}; + if (!selectAllAllQuestions) { + filteredQuestions.forEach(question => { + newSelectedQuestions[question._id] = true; + }); + } + setSelectedQuestions(newSelectedQuestions); + setSelectAllAllQuestions(!selectAllAllQuestions); + }; + const handleSelectAllMyQuestions = () => { + if (filteredMyQuestions.length === 0) return; + const newSelectedQuestions = !selectAllMyQuestions ? {} : {}; + if (!selectAllMyQuestions) { + filteredMyQuestions.forEach(question => { + newSelectedQuestions[question._id] = true; + }); + } + setSelectedQuestions(newSelectedQuestions); + setSelectAllMyQuestions(!selectAllMyQuestions); + }; + const handleSelectQuestion = (questionId) => { + const newSelectedQuestions = { ...selectedQuestions }; + if (newSelectedQuestions[questionId]) { + delete newSelectedQuestions[questionId]; + } else { + newSelectedQuestions[questionId] = true; + } + setSelectedQuestions(newSelectedQuestions); + }; + const handleEditClick = (question) => { + if (areQuestionsSelected) { + setIsModalOpen(true); + } + }; + + const handleModalOk = async () => { + const questionIdsArray = Object.keys(selectedQuestions).filter((key) => selectedQuestions[key] === true); + + // Ensure at least one question is selected + if (questionIdsArray.length === 0) { + toast.error('Please select at least one question.'); + return; + } + + // Get the last selected topic (new topic) and the first selected topic (selected topic to keep) + const newTopic = selectedTopic1.length > 0 ? selectedTopic1[selectedTopic1.length - 1] : ''; + const selectedTopicToKeep = selectedTopic.length > 0 ? selectedTopic[0] : ''; + + try { + // Prepare the payload with selected question IDs + let payload = { + questionIds: questionIdsArray + }; + + // Case 1: Add a new topic if no previously selected topic exists + if (newTopic && !selectedTopicToKeep) { + payload = { + ...payload, + topic: newTopic // Add the new topic + }; + console.log("Adding new topic:", payload); + } + + // Case 2: Replace the selected topic with the new one if both exist + if (newTopic && selectedTopicToKeep) { + payload = { + ...payload, + topic: newTopic, // Replace old topic with the new one + selectedTopic: selectedTopicToKeep // Send the selected topic for replacement + }; + console.log("Replacing the old topic with new topic:", payload); + } + + // Case 3: Retain the selected topic if no new topic is provided + if (selectedTopicToKeep && !newTopic) { + payload = { + ...payload, + topic: selectedTopicToKeep // Retain the selected topic + }; + console.log("Retaining selected topic:", payload); + } + + // Send the request to the backend + const response = await axios.put(`${server}/api/updatequestiontopic`, payload); + + // Handle successful response + toast.success(response.data.message || 'Questions updated successfully.'); + + // Reset modal and selections + setIsModalOpen(false); + setSelectedQuestions({}); + setSelectedTopic1([]); // Clear the topics + + } catch (error) { + const errorMessage = error.response?.data?.message || 'An error occurred while updating questions.'; + toast.error(errorMessage); + console.error('Error updating questions:', errorMessage); + } + }; + + + + const handleModalCancel = () => { + setIsModalOpen(false); + }; + const handleTopicChange = (value) => { + setSelectedTopic1(value); + setSelectedSubtopics1([]); + }; + + useEffect(() => { + setSelectedTopic1(selectedTopic); + setSelectedSubtopics1(selectedSubtopics) + }, [selectedTopic, selectedSubtopics]); + const renderSubtopicSelector = (subtopics, level = 1) => { + if (!subtopics || subtopics.length === 0) { + return null; // Return early if there are no subtopics + } + + return ( +
+
+ + + (option.label ?? "").toLowerCase().includes(input.toLowerCase()) + } onChange={(value) => { setSelectedChapter(value); - setSelectedTopic(""); - setSelectedQuestion(null); - setCurrentPage(1) - setMyCurrentPage(1) + setSelectedTopic([]); + setSelectedSubtopics([]); }} - filterOption={(input, option) => - (option.label ?? "") - .toLowerCase() - .includes(input.toLowerCase()) - } options={chapterList?.map((chapter) => ({ value: chapter.name, label: chapter.name, }))} + value={selectedChapter} /> +
+
+ - -
- )} - - {activeTabIndex === 1 && ( -
- - -
- )} -
- - {isAdmin && ( - + + + {selectedUser && ( + + )} + {selectedUser && userRank && ( + + )} +
+ {activeTabIndex === 0 && ( +
+ + +
+ )} + + {activeTabIndex === 1 && ( +
+ + +
+ )} +
+ {isAdmin && ( + +
+ {loading ? ( + + ) : ( + <> +
+

+ Total Questions: {totalQuestions} +

+
+ + {totalQuestions > 0 && filteredQuestions.length > 0 ? ( + <> + {selectedTopic.length > 0 && ( +
+ 0 && + filteredQuestions.every(question => selectedQuestions[question._id])} + onChange={handleSelectAllAllQuestions} + > + Select All + + +
+ )} + + + {selectedTopic ? ( + filteredQuestions.map((question, index) => ( +
handleQuestionClick(question)} + className="cursor-pointer text-gray-900 p-2 flex items-start space-x-4" + > + {selectedTopic.length > 0 && ( + handleSelectQuestion(question._id)} + onClick={(e) => e.stopPropagation()} + /> + )} +
+ + Q. {(currentPage - 1) * questionsPerPage + index + 1} + + +
+
+ )) + ) : ( +
+ Please select a topic to view questions. +
+ )} + + ) : ( +
+ No questions available. +
+ )} + + )} +
+
+ +

+ + {totalQuestions === 0 ? "0 / " : `${currentPage} / `} + + + {totalQuestions === 0 ? "0" : totalPages} + +

+ +
+
+ )} + {activeTabIndex === 1 ? ( +
+ + +
+ ) : null} +
{loading ? ( ) : ( <> +

- Total Questions: {totalQuestions} + Total Questions: {fixedMyTotalQuestions}

- - {totalQuestions === 0 ? ( -
- No questions found. -
+
+ {fixedMyTotalQuestions === 0 ? ( +
No questions found.
) : ( - filteredQuestions.map((question, index) => ( -
handleQuestionClick(question)} - className="cursor-pointer text-gray-900 p-2 " - > - - Q. - {(currentPage - 1) * questionsPerPage + index + 1} - - -
- )) + <> + {selectedTopic.length > 0 && ( +
+ 0 && + filteredMyQuestions.every(question => selectedQuestions[question._id])} + onChange={handleSelectAllMyQuestions} + > + Select All + + +
+ )} + + + {filteredMyQuestions.map((question, index) => ( +
handleQuestionClick(question)} + className="cursor-pointer text-gray-900 p-2 flex items-start space-x-4" + > + {selectedTopic.length > 0 && ( + handleSelectQuestion(question._id)} + onClick={(e) => e.stopPropagation()} + /> + )} + + Q. {(myCurrentPage - 1) * questionsPerPage + index + 1} + + +
+ ))} + )} )}
+ + {/* Pagination Controls */}

- {totalQuestions === 0 ? "0 / " : `${currentPage} / `} + {fixedMyTotalQuestions === 0 ? "0 / " : `${myCurrentPage} / `} - {totalQuestions === 0 ? "0" : totalPages} + {fixedMyTotalQuestions === 0 ? "0" : myTotalPages}

-
- )} - - {activeTabIndex === 1 ? ( -
- - -
- ) : null} - - -
- {loading ? ( - - ) : ( - <> -

- Total Questions: {questionsLength} -

- - {questionsLength === 0 ? ( -
- No questions found. -
- ) : ( - filteredMyQuestions.map((question, index) => ( -
handleQuestionClick(question)} - className="cursor-pointer text-gray-900 p-2" - > - - Q.{" "} - {(myCurrentPage - 1) * questionsPerPage + index + 1} - - -
- )) - )} - - )} -
-
- -

- - {questionsLength === 0 ? "0 / " : `${myCurrentPage} / `} - - - {questionsLength === 0 ? "0" : myTotalPages} - -

- - -
-
-
- - ) : ( + + + ) : (
Loading...
)} + +
+
+
+ +