import { createLazyFileRoute } from "@tanstack/react-router"; import { Card, Flex, Pagination, Stack, Radio, Text, Textarea, Loader, ActionIcon, CloseButton, Group, } from "@mantine/core"; import { useQuery, useMutation } from "@tanstack/react-query"; import { uploadFileMutationOptions, submitValidationMutationOptions, submitOptionMutationOptions, getAverageScoreQueryOptions, fetchAspects, getQuestionsAllQueryOptions, toggleFlagAnswer, } from "@/modules/assessmentManagement/queries/assessmentQueries"; import { TbFlagFilled, TbUpload, TbChevronRight, TbChevronUp } from "react-icons/tb"; import FinishAssessmentModal from "@/modules/assessmentManagement/modals/ConfirmModal"; import { useState, useRef, useEffect } from "react"; const getQueryParam = (param: string) => { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(param); }; export const Route = createLazyFileRoute("/_dashboardLayout/assessment/")({ component: AssessmentPage, }); interface ToggleFlagResponse { message: string; answer: { id: string; createdAt: string | null; updatedAt: string | null; optionId: string | null; assessmentId: string | null; isFlagged: boolean | null; filename: string | null; validationInformation: string; }; } export default function AssessmentPage() { const [page, setPage] = useState(1); const limit = 10; const questionRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const [files, setFiles] = useState([]); const [dragActive, setDragActive] = useState(false); const [flaggedQuestions, setFlaggedQuestions] = useState<{ [key: string]: boolean; }>({}); const fileInputRef = useRef(null); const [modalOpen, setModalOpen] = useState(false); const [selectedAspectId, setSelectedAspectId] = useState(null); const [selectedSubAspectId, setSelectedSubAspectId] = useState(null); const [assessmentId, setAssessmentId] = useState(null); const [answers, setAnswers] = useState<{ [key: string]: string }>({}); const [validationInformation, setValidationInformation] = useState<{ [key: string]: string }>({}); const [uploadedFiles, setUploadedFiles] = useState<{ [key: string]: File | null }>({}); // Fetch aspects and sub-aspects const aspectsQuery = useQuery({ queryKey: ["aspects"], queryFn: fetchAspects, }); // Fetching questions data using useQuery const { data, isLoading, isError, error } = useQuery( getQuestionsAllQueryOptions(page, limit) ); const handleFinishClick = () => { setModalOpen(true); }; useEffect(() => { const id = getQueryParam("id"); if (!id) { setAssessmentId(null); } else { setAssessmentId(id); } // Check if aspectsQuery.data is defined if (aspectsQuery.data?.data && aspectsQuery.data.data.length > 0) { // If no sub-aspect is selected, find a suitable default if (selectedSubAspectId === null) { const firstMatchingSubAspect = aspectsQuery.data.data .flatMap((aspect) => aspect.subAspects) // Get all sub-aspects .find((subAspect) => data?.data.some((question) => question.subAspectId === subAspect.id) ); if (firstMatchingSubAspect) { setSelectedSubAspectId(firstMatchingSubAspect.id); // Find the parent aspect and set its id as the selectedAspectId const parentAspect = aspectsQuery.data.data.find(aspect => aspect.subAspects.some(sub => sub.id === firstMatchingSubAspect.id) ); if (parentAspect) { setSelectedAspectId(parentAspect.id); // Use `id` from the parent aspect } } } else { // Update the aspectId based on the selected sub-aspect const matchingAspect = aspectsQuery.data.data.find((aspect) => aspect.subAspects.some((subAspect) => subAspect.id === selectedSubAspectId) ); if (matchingAspect) { setSelectedAspectId(matchingAspect.id); // Use `id` from the matching aspect } else { console.warn("No matching aspect found for selected sub-aspect."); setSelectedAspectId(null); } } } }, [aspectsQuery.data, selectedSubAspectId, data?.data]); const handleConfirmFinish = () => { if (assessmentId) { // Menggunakan history.pushState untuk mengubah URL tanpa reload const newUrl = `/assessmentResult?id=${assessmentId}`; window.history.pushState({}, "", newUrl); // Setelah mengubah URL, Anda bisa menjalankan logika lain jika diperlukan console.log("Navigated to:", newUrl); } }; // Tambahkan state untuk aspek yang terbuka const [openAspects, setOpenAspects] = useState<{ [key: string]: boolean }>({}); const toggleAspect = (aspectId: string) => { setOpenAspects((prev) => ({ ...prev, [aspectId]: !prev[aspectId], // Toggle state untuk aspek yang diklik })); }; // Fetch average scores by aspect const averageScoreQuery = useQuery(getAverageScoreQueryOptions(assessmentId || "")); const aspects = averageScoreQuery.data?.aspects || []; // Filter aspects by selected aspectId const filteredAspects = selectedAspectId ? aspects.filter((aspect) => aspect.aspectId === selectedAspectId) // Use 'id' instead of 'aspectId' : aspects; // Get the currently selected aspect to show all related sub-aspects const currentAspect = aspects.find(aspect => aspect.aspectId === selectedAspectId); const filteredSubAspects = currentAspect ? currentAspect.subAspects : []; // Mutation function to toggle flag const toggleFlagMutation = useMutation({ mutationFn: toggleFlagAnswer, onSuccess: (response) => { if (response && response.answer) { const { answer } = response; setFlaggedQuestions((prevFlags) => ({ ...prevFlags, [answer.id]: answer.isFlagged !== null ? answer.isFlagged : false, })); } }, onError: (error) => { console.error("Error toggling flag:", error); }, }); // Usage of the mutation in your component const submitOptionMutation = useMutation({ ...submitOptionMutationOptions, // Spread the mutation options here onSuccess: () => { // Refetch the average scores after a successful submission averageScoreQuery.refetch(); }, onError: (error) => { console.error("Error submitting option:", error); }, }); 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; } // Simpan jawaban ke localStorage dengan ID assessment const updatedAnswers = { ...answers, [questionId]: optionId }; localStorage.setItem(`assessmentAnswers_${assessmentId}`, JSON.stringify(updatedAnswers)); // Update state setAnswers(updatedAnswers); // Call the mutation to submit the option submitOptionMutation.mutate({ 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); } } }, [assessmentId]); // Handle perubahan di Textarea const handleTextareaChange = (questionId: string, value: string) => { setValidationInformation((prev) => ({ ...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, }); } }; // Mutation for file upload const uploadFileMutation = useMutation(uploadFileMutationOptions()); // 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]); const handleDrop = (event: React.DragEvent, question: { questionId: string }) => { event.preventDefault(); setDragActive(false); const droppedFiles = Array.from(event.dataTransfer.files); if (droppedFiles.length > 0) { const formData = new FormData(); formData.append('file', droppedFiles[0]); // Hanya menyertakan file pertama // 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 } // Tambahkan questionId ke FormData if (question.questionId) { formData.append('questionId', question.questionId); } else { console.error("questionId is null"); return; // Atau tangani sesuai kebutuhan } uploadFileMutation.mutate(formData); // Unggah file // Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci setUploadedFiles(prev => ({ ...prev, [question.questionId]: droppedFiles[0], // Simpan file berdasarkan questionId })); localStorage.setItem(`uploadedFile_${assessmentId}_${question.questionId}`, JSON.stringify({ name: droppedFiles[0].name, type: droppedFiles[0].type, lastModified: droppedFiles[0].lastModified, })); // Simpan info file ke local storage } }; const handleClick = () => { if (fileInputRef.current) { fileInputRef.current.click(); } }; const handleFileChange = (event: React.ChangeEvent, question: { questionId: string }) => { if (event.target.files) { const fileArray = Array.from(event.target.files); if (fileArray.length > 0) { const formData = new FormData(); formData.append('file', fileArray[0]); // Hanya menyertakan file pertama // Tambahkan assessmentId ke FormData if (assessmentId) { formData.append('assessmentId', assessmentId); } else { console.error("assessmentId is null"); return; // Atau tangani sesuai kebutuhan } // Tambahkan questionId ke FormData if (question.questionId) { formData.append('questionId', question.questionId); } else { console.error("questionId is null"); return; // Atau tangani sesuai kebutuhan } uploadFileMutation.mutate(formData); // Unggah file // Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci setUploadedFiles(prev => ({ ...prev, [question.questionId]: fileArray[0], // Simpan file berdasarkan questionId })); localStorage.setItem(`uploadedFile_${assessmentId}_${question.questionId}`, JSON.stringify({ name: fileArray[0].name, type: fileArray[0].type, lastModified: fileArray[0].lastModified, })); // Simpan info file ke local storage } } }; const handleRemoveFile = (question: { questionId: string }) => { setUploadedFiles(prev => ({ ...prev, [question.questionId]: null, // Hapus file yang diunggah untuk pertanyaan ini })); localStorage.removeItem(`uploadedFile_${assessmentId}_${question.questionId}`); // Hapus info file dari local storage }; // Function to scroll to the specific question const scrollToQuestion = (questionId: string) => { const questionElement = questionRefs.current[questionId]; if (questionElement) { questionElement.scrollIntoView({ behavior: "smooth" }); } }; // Handle pagination const handlePageChange = (newPage: number) => { setPage(newPage); }; // Render conditions if (isLoading) { return ; } if (isError) { return ( Error: {error?.message || "Terjadi kesalahan saat memuat pertanyaan."} ); } const totalQuestions = data?.data?.length || 0; if (!assessmentId) { return ( Error: Data Asesmen tidak ditemukan. Harap akses halaman melalui link yang valid. ); } const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; const paginatedQuestions = data?.data.slice(startIndex, endIndex) || []; const filteredQuestions = paginatedQuestions.filter((question) => { return question.subAspectId === selectedSubAspectId; // Misalnya, jika `question` memiliki `subAspectId` }); return (
{/* LEFT-SIDE */} {/* Aspek dan Sub-Aspek */}
{aspectsQuery.data?.data .filter((aspect) => aspect.subAspects.some((subAspect) => data?.data.some((question) => question.subAspectId === subAspect.id) ) ) .map((aspect) => (
toggleAspect(aspect.id)} >
{aspect.name}
{openAspects[aspect.id] ? ( ) : ( )}
{openAspects[aspect.id] && (
{aspect.subAspects .filter((subAspect) => data?.data.some((question) => question.subAspectId === subAspect.id) ) .map((subAspect) => (
setSelectedSubAspectId(subAspect.id)} >
{subAspect.name}
))}
)}
))}
{/* Pertanyaan */} Harap menjawab semua pertanyaan yang tersedia Semua jawaban Anda akan ditinjau {filteredQuestions.length === 0 ? ( Pertanyaan tidak ada untuk sub-aspek yang dipilih. ) : ( filteredQuestions.map((question: any, index: number) => { const questionId = question.questionId; if (!questionId) return null; return (
(questionRefs.current[questionId] = el)} className="space-y-4" > {startIndex + index + 1}.
{question.questionText}
{/* Action Icon */} { setFlaggedQuestions((prevFlags) => ({ ...prevFlags, [questionId]: !prevFlags[questionId], })); toggleFlagMutation.mutate(questionId); }} title={ !answers[question.questionId] ? "Pilih jawaban terlebih dahulu" : "Tandai" } color={flaggedQuestions[questionId] ? "red" : "white"} style={{ border: "1px gray solid", borderRadius: "4px", backgroundColor: flaggedQuestions[questionId] ? "red" : "white", }} disabled={!answers[question.questionId]} >
{/* Opsi Radio Button */} {question.options?.length > 0 ? (
{question.options.map((option: any) => ( ))}
) : ( Tidak ada opsi untuk pertanyaan ini. )} {/* Textarea */}