Pull Request branch dev-clone to main #1

Merged
gitea merged 429 commits from dev-clone into main 2024-12-23 09:31:34 +00:00
Showing only changes of commit e7b37a6279 - Show all commits

View File

@ -4,14 +4,15 @@ import {
Flex, Flex,
Pagination, Pagination,
Stack, Stack,
Radio,
Text, Text,
Textarea,
Loader, Loader,
ActionIcon, ActionIcon,
CloseButton, CloseButton,
Group, Group,
} from "@mantine/core"; } from "@mantine/core";
import { Textarea } from "@/shadcn/components/ui/textarea";
import { Label } from "@/shadcn/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/shadcn/components/ui/radio-group";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
import { import {
submitAssessmentMutationOptions, submitAssessmentMutationOptions,
@ -26,6 +27,7 @@ import {
import { TbFlagFilled, TbUpload, TbChevronRight, TbChevronDown } from "react-icons/tb"; import { TbFlagFilled, TbUpload, TbChevronRight, TbChevronDown } from "react-icons/tb";
import FinishAssessmentModal from "@/modules/assessmentManagement/modals/ConfirmModal"; import FinishAssessmentModal from "@/modules/assessmentManagement/modals/ConfirmModal";
import ValidationModal from "@/modules/assessmentManagement/modals/ValidationModal"; import ValidationModal from "@/modules/assessmentManagement/modals/ValidationModal";
import FileSizeValidationModal from "@/modules/assessmentManagement/modals/FileSizeValidationModal";
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
const getQueryParam = (param: string) => { const getQueryParam = (param: string) => {
@ -62,6 +64,7 @@ export default function AssessmentPage() {
}>({}); }>({});
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [modalOpenFileSize, setModalOpenFileSize] = useState(false);
const [selectedAspectId, setSelectedAspectId] = useState<string | null>(null); const [selectedAspectId, setSelectedAspectId] = useState<string | null>(null);
const [selectedSubAspectId, setSelectedSubAspectId] = useState<string | null>(null); const [selectedSubAspectId, setSelectedSubAspectId] = useState<string | null>(null);
const [assessmentId, setAssessmentId] = useState<string | null>(null); const [assessmentId, setAssessmentId] = useState<string | null>(null);
@ -70,6 +73,7 @@ export default function AssessmentPage() {
const [uploadedFiles, setUploadedFiles] = useState<{ [key: string]: File | null }>({}); const [uploadedFiles, setUploadedFiles] = useState<{ [key: string]: File | null }>({});
const [unansweredQuestions, setUnansweredQuestions] = useState(0); const [unansweredQuestions, setUnansweredQuestions] = useState(0);
const [validationModalOpen, setValidationModalOpen] = useState(false); const [validationModalOpen, setValidationModalOpen] = useState(false);
const [exceededFileName, setExceededFileName] = useState("");
// Fetch aspects and sub-aspects // Fetch aspects and sub-aspects
const aspectsQuery = useQuery({ const aspectsQuery = useQuery({
@ -380,13 +384,26 @@ export default function AssessmentPage() {
}); });
}, [assessmentId]); }, [assessmentId]);
// Max file size in bytes (25 MB)
const MAX_FILE_SIZE = 25 * 1024 * 1024;
const handleDrop = (event: React.DragEvent<HTMLDivElement>, question: { questionId: string }) => { const handleDrop = (event: React.DragEvent<HTMLDivElement>, question: { questionId: string }) => {
event.preventDefault(); event.preventDefault();
setDragActive(false); setDragActive(false);
const droppedFiles = Array.from(event.dataTransfer.files); const droppedFiles = Array.from(event.dataTransfer.files);
if (droppedFiles.length > 0) { if (droppedFiles.length > 0) {
const file = droppedFiles[0];
// Validate file size
if (file.size > MAX_FILE_SIZE) {
setExceededFileName(file.name); // Simpan nama file yang melebihi ukuran
setModalOpenFileSize(true); // Tampilkan modal
return;
}
const formData = new FormData(); const formData = new FormData();
formData.append('file', droppedFiles[0]); // Hanya menyertakan file pertama formData.append('file', file); // Hanya menyertakan file pertama
// Pastikan assessmentId tidak null sebelum menambahkannya ke FormData // Pastikan assessmentId tidak null sebelum menambahkannya ke FormData
if (assessmentId) { if (assessmentId) {
@ -409,13 +426,14 @@ export default function AssessmentPage() {
// Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci // Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci
setUploadedFiles(prev => ({ setUploadedFiles(prev => ({
...prev, ...prev,
[question.questionId]: droppedFiles[0], // Simpan file berdasarkan questionId [question.questionId]: file, // Simpan file berdasarkan questionId
})); }));
localStorage.setItem(`uploadedFile_${assessmentId}_${question.questionId}`, JSON.stringify({ localStorage.setItem(`uploadedFile_${assessmentId}_${question.questionId}`, JSON.stringify({
name: droppedFiles[0].name, name: file.name,
type: droppedFiles[0].type, type: file.type,
lastModified: droppedFiles[0].lastModified, lastModified: file.lastModified,
})); // Simpan info file ke local storage }));
} }
}; };
@ -429,8 +447,17 @@ export default function AssessmentPage() {
if (event.target.files) { if (event.target.files) {
const fileArray = Array.from(event.target.files); const fileArray = Array.from(event.target.files);
if (fileArray.length > 0) { if (fileArray.length > 0) {
const file = fileArray[0];
// 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
}
const formData = new FormData(); const formData = new FormData();
formData.append('file', fileArray[0]); // Hanya menyertakan file pertama formData.append('file', file); // Hanya menyertakan file pertama
// Tambahkan assessmentId ke FormData // Tambahkan assessmentId ke FormData
if (assessmentId) { if (assessmentId) {
@ -453,13 +480,14 @@ export default function AssessmentPage() {
// Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci // Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci
setUploadedFiles(prev => ({ setUploadedFiles(prev => ({
...prev, ...prev,
[question.questionId]: fileArray[0], // Simpan file berdasarkan questionId [question.questionId]: file, // Simpan file berdasarkan questionId
})); }));
localStorage.setItem(`uploadedFile_${assessmentId}_${question.questionId}`, JSON.stringify({ localStorage.setItem(`uploadedFile_${assessmentId}_${question.questionId}`, JSON.stringify({
name: fileArray[0].name, name: file.name,
type: fileArray[0].type, type: file.type,
lastModified: fileArray[0].lastModified, lastModified: file.lastModified,
})); // Simpan info file ke local storage }));
} }
} }
}; };
@ -652,33 +680,36 @@ export default function AssessmentPage() {
{/* Opsi Radio Button */} {/* Opsi Radio Button */}
{question.options?.length > 0 ? ( {question.options?.length > 0 ? (
<div className="mx-11"> <div className="mx-11">
<Radio.Group value={answers[question.questionId] || ""}> <RadioGroup
<div className="flex flex-col gap-2"> value={answers[question.questionId] || ""}
{question.options.map((option: any) => ( onValueChange={(value) => handleAnswerChange(question.questionId, value)}
<label className="flex flex-col gap-2"
key={option.optionId} >
className={`cursor-pointer transition-transform transform hover:scale-105 shadow-md hover:shadow-lg flex items-center border rounded-lg p-3 text-sm ${ {question.options.map((option: any) => (
answers[question.questionId] === option.optionId <div
? "bg-blue-500 text-white" key={option.optionId}
: "bg-gray-200 text-black" className={`cursor-pointer transition-transform transform hover:scale-105 shadow-md hover:shadow-lg flex items-center border rounded-lg p-3 text-sm ${
}`} answers[question.questionId] === option.optionId
onClick={() => document.getElementById(option.optionId)?.click()} ? "bg-blue-500 text-white"
: "bg-gray-200 text-black"
}`}
onClick={() => handleAnswerChange(question.questionId, option.optionId)}
>
<RadioGroupItem
value={option.optionId}
id={option.optionId}
checked={answers[question.questionId] === option.optionId}
className="bg-white checked:bg-white checked:border-blue-500 pointer-events-none rounded-full"
/>
<Label
htmlFor={option.optionId}
className="ml-2 font-bold cursor-pointer flex-1"
> >
<Radio {option.optionText}
id={option.optionId} </Label>
className="font-bold" </div>
value={option.optionId} ))}
label={option.optionText} </RadioGroup>
size="xs"
radius="xl"
style={{ pointerEvents: "none" }}
checked={answers[question.questionId] === option.optionId} // Untuk menampilkan jawaban yang sudah dipilih
onChange={() => handleAnswerChange(question.questionId, option.optionId)} // Memanggil handleAnswerChange dengan questionId dan optionId
/>
</label>
))}
</div>
</Radio.Group>
</div> </div>
) : ( ) : (
<Text color="red">Tidak ada opsi untuk pertanyaan ini.</Text> <Text color="red">Tidak ada opsi untuk pertanyaan ini.</Text>
@ -717,6 +748,9 @@ export default function AssessmentPage() {
<Text className="text-sm text-gray-400"> <Text className="text-sm text-gray-400">
PNG, JPG, PDF PNG, JPG, PDF
</Text> </Text>
<Text className="text-sm text-gray-400">
(Max.File size : 25 MB)
</Text>
</div> </div>
</Flex> </Flex>
<input <input
@ -746,6 +780,13 @@ export default function AssessmentPage() {
)} )}
</div> </div>
{/* File Size Validation Modal */}
<FileSizeValidationModal
opened={modalOpenFileSize}
onClose={() => setModalOpenFileSize(false)}
fileName={exceededFileName}
/>
{/* Garis pembatas setiap soal */} {/* Garis pembatas setiap soal */}
<div> <div>
<hr className="border-t-2 border-gray-300 mx-11 mt-6 mb-6" /> <hr className="border-t-2 border-gray-300 mx-11 mt-6 mb-6" />