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,
Pagination,
Stack,
Radio,
Text,
Textarea,
Loader,
ActionIcon,
CloseButton,
Group,
} 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 {
submitAssessmentMutationOptions,
@ -26,6 +27,7 @@ import {
import { TbFlagFilled, TbUpload, TbChevronRight, TbChevronDown } from "react-icons/tb";
import FinishAssessmentModal from "@/modules/assessmentManagement/modals/ConfirmModal";
import ValidationModal from "@/modules/assessmentManagement/modals/ValidationModal";
import FileSizeValidationModal from "@/modules/assessmentManagement/modals/FileSizeValidationModal";
import { useState, useRef, useEffect } from "react";
const getQueryParam = (param: string) => {
@ -62,6 +64,7 @@ export default function AssessmentPage() {
}>({});
const fileInputRef = useRef<HTMLInputElement>(null);
const [modalOpen, setModalOpen] = useState(false);
const [modalOpenFileSize, setModalOpenFileSize] = useState(false);
const [selectedAspectId, setSelectedAspectId] = useState<string | null>(null);
const [selectedSubAspectId, setSelectedSubAspectId] = 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 [unansweredQuestions, setUnansweredQuestions] = useState(0);
const [validationModalOpen, setValidationModalOpen] = useState(false);
const [exceededFileName, setExceededFileName] = useState("");
// Fetch aspects and sub-aspects
const aspectsQuery = useQuery({
@ -380,13 +384,26 @@ export default function AssessmentPage() {
});
}, [assessmentId]);
// Max file size in bytes (25 MB)
const MAX_FILE_SIZE = 25 * 1024 * 1024;
const handleDrop = (event: React.DragEvent<HTMLDivElement>, question: { questionId: string }) => {
event.preventDefault();
setDragActive(false);
const droppedFiles = Array.from(event.dataTransfer.files);
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();
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
if (assessmentId) {
@ -409,13 +426,14 @@ export default function AssessmentPage() {
// Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci
setUploadedFiles(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({
name: droppedFiles[0].name,
type: droppedFiles[0].type,
lastModified: droppedFiles[0].lastModified,
})); // Simpan info file ke local storage
name: file.name,
type: file.type,
lastModified: file.lastModified,
}));
}
};
@ -429,8 +447,17 @@ export default function AssessmentPage() {
if (event.target.files) {
const fileArray = Array.from(event.target.files);
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();
formData.append('file', fileArray[0]); // Hanya menyertakan file pertama
formData.append('file', file); // Hanya menyertakan file pertama
// Tambahkan assessmentId ke FormData
if (assessmentId) {
@ -453,13 +480,14 @@ export default function AssessmentPage() {
// Simpan file dalam state dan local storage menggunakan questionId dan assessmentId sebagai kunci
setUploadedFiles(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({
name: fileArray[0].name,
type: fileArray[0].type,
lastModified: fileArray[0].lastModified,
})); // Simpan info file ke local storage
name: file.name,
type: file.type,
lastModified: file.lastModified,
}));
}
}
};
@ -652,33 +680,36 @@ export default function AssessmentPage() {
{/* Opsi Radio Button */}
{question.options?.length > 0 ? (
<div className="mx-11">
<Radio.Group value={answers[question.questionId] || ""}>
<div className="flex flex-col gap-2">
<RadioGroup
value={answers[question.questionId] || ""}
onValueChange={(value) => handleAnswerChange(question.questionId, value)}
className="flex flex-col gap-2"
>
{question.options.map((option: any) => (
<label
<div
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 ${
answers[question.questionId] === option.optionId
? "bg-blue-500 text-white"
: "bg-gray-200 text-black"
}`}
onClick={() => document.getElementById(option.optionId)?.click()}
onClick={() => handleAnswerChange(question.questionId, option.optionId)}
>
<Radio
id={option.optionId}
className="font-bold"
<RadioGroupItem
value={option.optionId}
label={option.optionText}
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
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>
))}
<Label
htmlFor={option.optionId}
className="ml-2 font-bold cursor-pointer flex-1"
>
{option.optionText}
</Label>
</div>
</Radio.Group>
))}
</RadioGroup>
</div>
) : (
<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">
PNG, JPG, PDF
</Text>
<Text className="text-sm text-gray-400">
(Max.File size : 25 MB)
</Text>
</div>
</Flex>
<input
@ -746,6 +780,13 @@ export default function AssessmentPage() {
)}
</div>
{/* File Size Validation Modal */}
<FileSizeValidationModal
opened={modalOpenFileSize}
onClose={() => setModalOpenFileSize(false)}
fileName={exceededFileName}
/>
{/* Garis pembatas setiap soal */}
<div>
<hr className="border-t-2 border-gray-300 mx-11 mt-6 mb-6" />