diff --git a/apps/frontend/src/routes/_assessmentLayout/assessment/index.lazy.tsx b/apps/frontend/src/routes/_assessmentLayout/assessment/index.lazy.tsx index 468eaac..9bf6fa3 100644 --- a/apps/frontend/src/routes/_assessmentLayout/assessment/index.lazy.tsx +++ b/apps/frontend/src/routes/_assessmentLayout/assessment/index.lazy.tsx @@ -24,9 +24,10 @@ import { import { useQuery, useMutation } from "@tanstack/react-query"; import { getAnswersQueryOptions, + getAllAnswer, submitAssessmentMutationOptions, uploadFileMutationOptions, - submitValidationMutationOptions, + submitValidationQuery, submitOptionMutationOptions, getAverageScoreQueryOptions, fetchAspects, @@ -35,6 +36,7 @@ import { } from "@/modules/assessmentManagement/queries/assessmentQueries"; import { TbFlagFilled, TbUpload, TbChevronRight, TbChevronDown } from "react-icons/tb"; import FinishAssessmentModal from "@/modules/assessmentManagement/modals/ConfirmModal"; +import FileUpload from "@/modules/assessmentManagement/fileUpload/fileUpload"; import ValidationModal from "@/modules/assessmentManagement/modals/ValidationModal"; import FileSizeValidationModal from "@/modules/assessmentManagement/modals/FileSizeValidationModal"; import { useState, useRef, useEffect } from "react"; @@ -98,23 +100,6 @@ export default function AssessmentPage() { getQuestionsAllQueryOptions(page, limit) ); - // Fungsi untuk memeriksa pertanyaan yang belum dijawab - const checkUnansweredQuestions = () => { - // Misalkan data berisi pertanyaan dan jawaban - const unanswered = data?.data.filter(question => { - // Pastikan questionId tidak null dan tidak ada jawaban untuk questionId tersebut - return question.questionId !== null && !answers[question.questionId]; - }) || []; // Ganti question.id dengan question.questionId dan tambahkan pengecekan null - setUnansweredQuestions(unanswered.length); // Aman, karena unanswered selalu array - - // Jika ada pertanyaan yang belum dijawab, buka modal peringatan - if (unanswered.length > 0) { - setValidationModalOpen(true); - } else { - setModalOpen(true); // Jika tidak ada, buka modal konfirmasi selesai asesmen - } - }; - const handleFinishClick = () => { // Memanggil fungsi untuk memeriksa pertanyaan yang belum dijawab checkUnansweredQuestions(); @@ -172,65 +157,74 @@ export default function AssessmentPage() { // Fetching answers for the assessment const { data: answersData } = useQuery( - getAnswersQueryOptions(assessmentId || "", page, limit) // Memanggil query untuk mengambil jawaban dengan argumen yang diperlukan + getAnswersQueryOptions(assessmentId || "", page, limit), ); + if (answersData && answersData.data) { + const transformedData = answersData.data.reduce( + (acc: Record, item: any) => { + if (item.questionId && item.optionId) { + acc[item.questionId] = item.optionId; + } + return acc; + }, + {} + ); + + } else { + console.error("No data found or data is undefined."); + } + // Effect untuk mengatur answers dari data yang diambil useEffect(() => { - const assessmentId = getQueryParam("id"); // Ambil assessmentId dari query params + const assessmentId = getQueryParam("id"); if (!assessmentId) { - console.error("Assessment ID tidak ditemukan"); - return; + console.error("Assessment ID tidak ditemukan"); + return; } - - // Ambil jawaban dari localStorage berdasarkan ID assessment - const savedAnswers = localStorage.getItem(`assessmentAnswers_${assessmentId}`); - if (savedAnswers) { - setAnswers(JSON.parse(savedAnswers)); // Set state answers dengan data dari local storage - } - }, []); - // Mengambil jawaban dari local storage saat komponen dimuat - useEffect(() => { - const storedAnswers = localStorage.getItem('assessmentAnswers'); - if (storedAnswers) { - setAnswers(JSON.parse(storedAnswers)); // Set state answers dengan data dari local storage + // Set answers from `answersData` if data is available + if (answersData && Array.isArray(answersData.data)) { + const answersFromDatabase = answersData.data.reduce( + (acc: Record, item: any) => { + if (item.questionId && item.optionId) { + acc[item.questionId] = item.optionId; + } + return acc; + }, + {} + ); + setAnswers(answersFromDatabase); // Set the transformed data directly to state + } else { + console.error("No data found or data is undefined."); } - }, []); + }, [answersData]); + + // Fungsi untuk memeriksa pertanyaan yang belum dijawab + const checkUnansweredQuestions = () => { + + // Filter pertanyaan yang belum dijawab berdasarkan data `answers` + const unanswered = data?.data.filter(question => { + return question.questionId !== null && !answers[question.questionId]; + }) || []; + + setUnansweredQuestions(unanswered.length); + + // Tampilkan modal berdasarkan jumlah pertanyaan yang belum dijawab + if (unanswered.length > 0) { + setValidationModalOpen(true); + } else { + setModalOpen(true); + } + }; const handleConfirmFinish = async (assessmentId: string) => { try { - // Cek pertanyaan yang belum dijawab - let unansweredCount = 0; - - // Cek radio button - data?.data.forEach((question) => { - // Pastikan questionId tidak null sebelum memeriksa answers - if (question.questionId && !answers[question.questionId]) { - unansweredCount += 1; - } - }); - - // Cek textarea - Object.keys(validationInformation).forEach((key) => { - // Pastikan key tidak null dan tidak ada validasi informasi untuk key tersebut - if (key && !validationInformation[key]) { - unansweredCount += 1; - } - }); - - if (unansweredCount > 0) { - // Tampilkan modal validasi jika ada pertanyaan yang belum dijawab - setUnansweredQuestions(unansweredCount); - setValidationModalOpen(true); - return; - } - - // Memanggil mutation untuk mengubah status asesmen menjadi 'selesai' di backend + // Skip counting unanswered questions here to prevent duplication const mutation = submitAssessmentMutationOptions(assessmentId); const response = await mutation.mutationFn(); - // Setelah status diubah, navigasikan ke halaman hasil asesmen + // Navigate to results const newUrl = `/assessmentResult?id=${assessmentId}`; window.history.pushState({}, "", newUrl); console.log("Navigated to:", newUrl); @@ -238,7 +232,7 @@ export default function AssessmentPage() { } catch (error) { console.error("Error finishing assessment:", error); } finally { - setModalOpen(false); // Menutup modal setelah selesai + setModalOpen(false); } }; @@ -265,44 +259,50 @@ export default function AssessmentPage() { const currentAspect = aspects.find(aspect => aspect.aspectId === selectedAspectId); const filteredSubAspects = currentAspect ? currentAspect.subAspects : []; - // Inisialisasi flaggedQuestions dari localStorage saat komponen dimuat + // Inisialisasi flaggedQuestions dari database saat komponen dimuat useEffect(() => { - const savedFlags = localStorage.getItem("flaggedQuestions"); - if (savedFlags) { - setFlaggedQuestions(JSON.parse(savedFlags)); + const initialFlagData = answersData?.data.reduce((acc, item) => { + if (item.questionId != null && item.isFlagged != null) { + acc[item.questionId] = item.isFlagged; + } + return acc; + }, {} as Record); + + if (initialFlagData) { + setFlaggedQuestions(initialFlagData); } - }, []); - - // Simpan perubahan flag ke localStorage setiap kali flaggedQuestions berubah - useEffect(() => { - if (Object.keys(flaggedQuestions).length > 0) { - localStorage.setItem("flaggedQuestions", JSON.stringify(flaggedQuestions)); - } - }, [flaggedQuestions]); + }, [answersData]); // Mutation function to toggle flag - const toggleFlagMutation = useMutation({ - mutationFn: toggleFlagAnswer, + const { mutate: toggleFlag } = useMutation({ + mutationFn: (questionId: string) => toggleFlagAnswer(questionId), onSuccess: (response) => { if (response && response.answer) { const { answer } = response; - setFlaggedQuestions((prevFlags) => { - const newFlags = { - ...prevFlags, - [answer.id]: answer.isFlagged !== null ? answer.isFlagged : false, - }; - // Simpan perubahan ke localStorage - localStorage.setItem("flaggedQuestions", JSON.stringify(newFlags)); - return newFlags; - }); + setFlaggedQuestions((prevFlags) => ({ + ...prevFlags, + [answer.id]: answer.isFlagged !== null ? answer.isFlagged : false, + })); } }, - onError: (error) => { console.error("Error toggling flag:", error); }, }); + // Fungsi untuk toggle flag + const handleToggleFlag = (questionId: string) => { + const newFlagState = !flaggedQuestions[questionId]; + + // Update flaggedQuestions dan kirim ke server + setFlaggedQuestions((prevFlags) => ({ + ...prevFlags, + [questionId]: newFlagState, + })); + + toggleFlag(questionId); + }; + // Usage of the mutation in your component const submitOptionMutation = useMutation({ ...submitOptionMutationOptions, // Spread the mutation options here @@ -315,115 +315,108 @@ export default function AssessmentPage() { }, }); - useEffect(() => { - const assessmentId = getQueryParam("id"); - - if (!assessmentId) { - console.error("Assessment ID tidak ditemukan"); - return; - } - - // Ambil jawaban dari localStorage berdasarkan ID assessment - const savedAnswers = localStorage.getItem(`assessmentAnswers_${assessmentId}`); - if (savedAnswers) { - setAnswers(JSON.parse(savedAnswers)); - } - }, []); - const handleAnswerChange = (questionId: string, optionId: string) => { const assessmentId = getQueryParam("id"); - if (!assessmentId) { - console.error("Assessment ID tidak ditemukan"); - return; + console.error("Assessment ID tidak ditemukan"); + return; } - - // Simpan jawaban ke localStorage dengan ID assessment + + // Update answers in the state const updatedAnswers = { ...answers, [questionId]: optionId }; - localStorage.setItem(`assessmentAnswers_${assessmentId}`, JSON.stringify(updatedAnswers)); // Simpan berdasarkan ID assessment - - // Update state setAnswers(updatedAnswers); - - // Call the mutation to submit the option + + // Send the updated answer to the backend submitOptionMutation.mutate({ - optionId, - assessmentId, - questionId, - isFlagged: false, - filename: undefined, + optionId, + assessmentId, + questionId, + isFlagged: false, + filename: undefined, }); }; - // Mutation untuk mengirim data ke backend - const { mutate: submitValidation } = useMutation(submitValidationMutationOptions()); - - // Mengambil data dari localStorage saat komponen dimuat - useEffect(() => { - const storedValidationInfo = localStorage.getItem(`validationInfo_${assessmentId}`); - if (storedValidationInfo) { - try { - const parsedValidationInfo = JSON.parse(storedValidationInfo); - setValidationInformation(parsedValidationInfo); - } catch (error) { - console.error("Error parsing validation information:", error); - } + const validationResult = answersData?.data.reduce((acc, item) => { + if (item.questionId != null && item.validationInformation != null) { + acc[item.questionId] = item.validationInformation; } - }, [assessmentId]); + return acc; + }, {} as Record); + + // Mengambil data dari database saat komponen dimuat + useEffect(() => { + if (validationResult) { + setValidationInformation(validationResult); + } + }, [answersData, assessmentId]); + + // Mutation untuk mengirim data ke backend + const { mutate: submitValidation } = useMutation({ + mutationFn: (form: { + assessmentId: string; + questionId: string; + validationInformation: string; + }) => submitValidationQuery(form), + onSuccess: () => { + // Tindakan yang diambil setelah berhasil + console.log("Validation updated successfully!"); + }, + onError: (error) => { + console.error("Error updating validation:", error); + }, + }); // Handle perubahan di Textarea const handleTextareaChange = (questionId: string, value: string) => { + // Memperbarui state validationInformation setValidationInformation((prev) => ({ - ...prev, - [questionId]: value, + ...prev, + [questionId]: value, })); - - // Update the localStorage with the new validation information as JSON - const updatedValidationInformation = { - ...validationInformation, - [questionId]: value, - }; - localStorage.setItem(`validationInfo_${assessmentId}`, JSON.stringify(updatedValidationInformation)); - - // Ensure assessmentId and questionId are not null before submitting - if (assessmentId && questionId) { - // Send the validation data to the server - submitValidation({ - assessmentId, - questionId, - validationInformation: value, - }); + + // Pastikan assessmentId tidak null sebelum mengirimkan data ke server + if (assessmentId) { + // Kirim data validasi ke server + submitValidation({ + assessmentId, + questionId, + validationInformation: value, + }); + } else { + console.error("Assessment ID tidak ditemukan"); } }; // Mutation for file upload const uploadFileMutation = useMutation(uploadFileMutationOptions()); + // Inisialisasi uploadedFiles dari data yang diterima (answersData) + useEffect(() => { + if (answersData && answersData.data) { + + const transformedFileData = answersData.data.reduce((acc, item) => { + + if (item.questionId && item.filename) { + acc[item.questionId] = new File([""], item.filename, { type: "application/pdf" }); + } + + return acc; + }, {} as Record); + + setUploadedFiles(transformedFileData); + } + }, [answersData]); + // Drag and Drop handlers const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); setDragActive(true); }; - + const handleDragLeave = () => { setDragActive(false); }; - // Load uploaded files from local storage when the component mounts - useEffect(() => { - const keys = Object.keys(localStorage); - keys.forEach((key) => { - if (key.startsWith(`uploadedFile_${assessmentId}_`)) { // Menggunakan assessmentId - const fileData = JSON.parse(localStorage.getItem(key) || '{}'); - const questionId = key.replace(`uploadedFile_${assessmentId}_`, ''); // Ambil questionId dari kunci - setUploadedFiles(prev => ({ - ...prev, - [questionId]: new File([fileData], fileData.name, { type: fileData.type }), // Buat objek File baru - })); - } - }); - }, [assessmentId]); - // Max file size in bytes (64 MB) const MAX_FILE_SIZE = 64 * 1024 * 1024; @@ -437,45 +430,37 @@ export default function AssessmentPage() { // Validate file size if (file.size > MAX_FILE_SIZE) { - setExceededFileName(file.name); // Simpan nama file yang melebihi ukuran - setModalOpenFileSize(true); // Tampilkan modal + setExceededFileName(file.name); + setModalOpenFileSize(true); return; } const formData = new FormData(); - formData.append('file', file); // Hanya menyertakan file pertama + formData.append('file', file); - // Pastikan assessmentId tidak null sebelum menambahkannya ke FormData if (assessmentId) { formData.append('assessmentId', assessmentId); } else { console.error("assessmentId is null"); - return; // Atau tangani sesuai kebutuhan + return; } - // Tambahkan questionId ke FormData if (question.questionId) { formData.append('questionId', question.questionId); } else { console.error("questionId is null"); - return; // Atau tangani sesuai kebutuhan + return; } - uploadFileMutation.mutate(formData); // Unggah file + uploadFileMutation.mutate(formData); // Upload file - // Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci + // Update state to reflect the uploaded file (store the File object, not just the name) setUploadedFiles(prev => ({ ...prev, - [question.questionId]: file, // Simpan file berdasarkan questionId - })); - - localStorage.setItem(`uploadedFile_${assessmentId}_${question.questionId}`, JSON.stringify({ - name: file.name, - type: file.type, - lastModified: file.lastModified, + [question.questionId]: file, // Store the file itself })); } - }; + }; const handleClick = () => { if (fileInputRef.current) { @@ -491,53 +476,44 @@ export default function AssessmentPage() { // Validate file size if (file.size > MAX_FILE_SIZE) { - setExceededFileName(file.name); // Simpan nama file yang melebihi ukuran - setModalOpenFileSize(true); // Tampilkan modal - return; // Hentikan eksekusi fungsi jika ukuran file melebihi batas + setExceededFileName(file.name); + setModalOpenFileSize(true); + return; } const formData = new FormData(); - formData.append('file', file); // Hanya menyertakan file pertama + formData.append('file', file); - // Tambahkan assessmentId ke FormData if (assessmentId) { formData.append('assessmentId', assessmentId); } else { console.error("assessmentId is null"); - return; // Atau tangani sesuai kebutuhan + return; } - // Tambahkan questionId ke FormData if (question.questionId) { formData.append('questionId', question.questionId); } else { console.error("questionId is null"); - return; // Atau tangani sesuai kebutuhan + return; } - uploadFileMutation.mutate(formData); // Unggah file + uploadFileMutation.mutate(formData); // Upload file - // Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci + // Update state to reflect the uploaded file (store the File object) setUploadedFiles(prev => ({ ...prev, - [question.questionId]: file, // Simpan file berdasarkan questionId - })); - - localStorage.setItem(`uploadedFile_${assessmentId}_${question.questionId}`, JSON.stringify({ - name: file.name, - type: file.type, - lastModified: file.lastModified, + [question.questionId]: file, // Store the File object, not just the name })); } } }; const handleRemoveFile = (question: { questionId: string }) => { - setUploadedFiles(prev => ({ - ...prev, - [question.questionId]: null, // Hapus file yang diunggah untuk pertanyaan ini + setUploadedFiles((prev) => ({ + ...prev, + [question.questionId]: null, })); - localStorage.removeItem(`uploadedFile_${assessmentId}_${question.questionId}`); // Hapus info file dari local storage }; // Function to scroll to the specific question @@ -694,18 +670,7 @@ export default function AssessmentPage() { {/* Action Icon/Flag */} { - setFlaggedQuestions((prevFlags) => { - const newFlags = { - ...prevFlags, - [questionId]: !prevFlags[questionId], - }; - // Simpan perubahan ke localStorage - localStorage.setItem("flaggedQuestions", JSON.stringify(newFlags)); - return newFlags; - }); - toggleFlagMutation.mutate(questionId); - }} + onClick={() => handleToggleFlag(questionId)} title="Tandai" className={`m-2 rounded-md border-1 flex items-center justify-center h-7 w-7 ${flaggedQuestions[questionId] ? "border-white bg-red-500" : "border-gray-100 bg-white"}`} > @@ -760,70 +725,23 @@ export default function AssessmentPage() { placeholder="Berikan keterangan terkait jawaban di atas" value={validationInformation[question.questionId] || ""} onChange={(event) => handleTextareaChange(question.questionId, event.currentTarget.value)} - disabled={!answers[question.questionId]} /> {/* File Upload */} -
- {question.needFile === true && ( -
handleDrop(event, question)} // Mengoper question sebagai argumen - onClick={handleClick} - > - - -
- - Klik untuk unggah atau geser file disini - - - PNG, JPG, PDF - - - (Max.File size : 64 MB) - -
-
- handleFileChange(event, question)} // Mengoper question sebagai argumen - style={{ display: "none" }} - accept="image/png, image/jpeg, application/pdf" - disabled={!answers[question.questionId]} - /> -
- )} -
- -
- {uploadedFiles[question.questionId] && ( - - File yang diunggah: - - {uploadedFiles[question.questionId]?.name} {/* Tampilkan nama file yang diunggah */} - handleRemoveFile(question)} // Mengoper question sebagai argumen - /> - - - )} -
- - {/* File Size Validation Modal */} - setModalOpenFileSize(false)} - fileName={exceededFileName} + {/* Garis pembatas setiap soal */}