"use client"; import { useState, useEffect } from "react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; import { PlusCircle, Pencil, Trash2, Search, X, Loader2, GraduationCap } from "lucide-react"; import { useToast } from "@/components/ui/toast-provider"; import UploadFileNilaiMahasiswa from "./upload-file-nilai-mahasiswa"; // Define the NilaiMahasiswa type interface NilaiMahasiswa { id_nilai: number; id_mahasiswa: number; id_mk: number; nim: string; nama: string; kode_mk: string; nama_mk: string; nilai_huruf: "A" | "B+" | "B" | "C+" | "C" | "D+" | "D" | "E"; nilai_angka: number; semester: number; created_at: string; updated_at: string; } // Define MataKuliah type for dropdown interface MataKuliah { id_mk: number; kode_mk: string; nama_mk: string; } export default function DataTableNilaiMahasiswa() { const { showSuccess, showError } = useToast(); // State for data const [nilaiMahasiswa, setNilaiMahasiswa] = useState([]); const [filteredData, setFilteredData] = useState([]); const [mataKuliahList, setMataKuliahList] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // State for filtering const [searchTerm, setSearchTerm] = useState(""); const [filterSemester, setFilterSemester] = useState(""); const [filterNilaiHuruf, setFilterNilaiHuruf] = useState(""); // State for pagination const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [paginatedData, setPaginatedData] = useState([]); // State for form const [formMode, setFormMode] = useState<"add" | "edit">("add"); const [formData, setFormData] = useState>({ nilai_huruf: "A", nilai_angka: 4.0, semester: 1 }); const [isSubmitting, setIsSubmitting] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false); // State for delete confirmation const [deleteId, setDeleteId] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); // Fetch data on component mount useEffect(() => { fetchNilaiMahasiswa(); fetchMataKuliah(); }, []); // Filter data when search term or filter changes useEffect(() => { filterData(); }, [searchTerm, filterSemester, filterNilaiHuruf, nilaiMahasiswa]); // Update paginated data when filtered data or pagination settings change useEffect(() => { paginateData(); }, [filteredData, currentPage, pageSize]); // Fetch mata kuliah for dropdown const fetchMataKuliah = async () => { try { const response = await fetch("/api/keloladata/data-mata-kuliah"); if (response.ok) { const data = await response.json(); setMataKuliahList(data); } } catch (err) { console.error("Error fetching mata kuliah:", err); } }; // Fetch nilai mahasiswa data from API const fetchNilaiMahasiswa = async () => { try { setLoading(true); let url = "/api/keloladata/data-nilai-mahasiswa"; // Add filters to URL if they exist const params = new URLSearchParams(); if (searchTerm) { params.append("search", searchTerm); } if (filterSemester && filterSemester !== "all") { params.append("semester", filterSemester); } if (filterNilaiHuruf && filterNilaiHuruf !== "all") { params.append("nilai_huruf", filterNilaiHuruf); } if (params.toString()) { url += `?${params.toString()}`; } const response = await fetch(url); if (!response.ok) { throw new Error("Failed to fetch data"); } const data = await response.json(); setNilaiMahasiswa(data); setFilteredData(data); setError(null); } catch (err) { setError("Error fetching data. Please try again later."); console.error("Error fetching data:", err); } finally { setLoading(false); } }; // Filter data based on search term and filters const filterData = () => { let filtered = [...nilaiMahasiswa]; // Filter by search term if (searchTerm) { filtered = filtered.filter( (item) => (item.nim && item.nim.toLowerCase().includes(searchTerm.toLowerCase())) || (item.nama && item.nama.toLowerCase().includes(searchTerm.toLowerCase())) || (item.kode_mk && item.kode_mk.toLowerCase().includes(searchTerm.toLowerCase())) || (item.nama_mk && item.nama_mk.toLowerCase().includes(searchTerm.toLowerCase())) ); } // Filter by semester if (filterSemester && filterSemester !== "all") { filtered = filtered.filter((item) => item.semester === parseInt(filterSemester)); } // Filter by nilai huruf if (filterNilaiHuruf && filterNilaiHuruf !== "all") { filtered = filtered.filter((item) => item.nilai_huruf === filterNilaiHuruf); } setFilteredData(filtered); // Reset to first page when filters change setCurrentPage(1); }; // Paginate data const paginateData = () => { const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; setPaginatedData(filteredData.slice(startIndex, endIndex)); }; // Get total number of pages const getTotalPages = () => { return Math.ceil(filteredData.length / pageSize); }; // Handle page change const handlePageChange = (page: number) => { setCurrentPage(page); }; // Handle page size change const handlePageSizeChange = (size: string) => { setPageSize(Number(size)); setCurrentPage(1); // Reset to first page when changing page size }; // Reset form data const resetForm = () => { setFormData({ nilai_huruf: "A", nilai_angka: 4.0, semester: 1 }); }; // Handle form input changes const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); }; // Handle select input changes const handleSelectChange = (name: string, value: string) => { setFormData((prev) => ({ ...prev, [name]: value })); // Auto-update nilai_angka based on nilai_huruf if (name === "nilai_huruf") { const nilaiAngkaMap: Record = { "A": 4.0, "B+": 3.5, "B": 3.0, "C+": 2.5, "C": 2.0, "D+": 1.5, "D": 1.0, "E": 0.0 }; setFormData((prev) => ({ ...prev, [name]: value as NilaiMahasiswa["nilai_huruf"], nilai_angka: nilaiAngkaMap[value] || 0 })); } }; // Open form dialog for adding new nilai const handleAdd = () => { setFormMode("add"); resetForm(); setIsDialogOpen(true); }; // Open form dialog for editing nilai const handleEdit = (data: NilaiMahasiswa) => { setFormMode("edit"); setFormData(data); setIsDialogOpen(true); }; // Open delete confirmation dialog const handleDeleteConfirm = (id: number) => { setDeleteId(id); setIsDeleteDialogOpen(true); }; // Submit form for add/edit const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { setIsSubmitting(true); if (formMode === "add") { // Add new nilai const response = await fetch("/api/keloladata/data-nilai-mahasiswa", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(formData), }); const responseData = await response.json(); if (!response.ok) { // Handle specific NIM not found error if (response.status === 404 && responseData.message.includes("tidak terdaftar")) { showError("Gagal!", `NIM ${formData.nim} tidak terdaftar`); throw new Error(`NIM ${formData.nim} tidak terdaftar dalam database. Silakan cek kembali NIM yang dimasukkan.`); } showError("Gagal!", responseData.message || "Failed to add nilai"); throw new Error(responseData.message || "Failed to add nilai"); } // Show success message with student info showSuccess("Berhasil!", "Nilai mahasiswa berhasil ditambahkan"); } else { // Edit existing nilai const response = await fetch(`/api/keloladata/data-nilai-mahasiswa?id=${formData.id_nilai}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(formData), }); const responseData = await response.json(); if (!response.ok) { // Handle specific NIM not found error if (response.status === 404 && responseData.message.includes("tidak terdaftar")) { showError("Gagal!", `NIM ${formData.nim} tidak terdaftar`); throw new Error(`NIM ${formData.nim} tidak terdaftar dalam database. Silakan cek kembali NIM yang dimasukkan.`); } showError("Gagal!", responseData.message || "Failed to update nilai"); throw new Error(responseData.message || "Failed to update nilai"); } showSuccess("Berhasil!", "Nilai mahasiswa berhasil diperbarui"); } // Refresh data after successful operation await fetchNilaiMahasiswa(); setIsDialogOpen(false); resetForm(); } catch (err) { console.error("Error submitting form:", err); } finally { setIsSubmitting(false); } }; // Delete nilai const handleDelete = async () => { if (!deleteId) return; try { setIsDeleting(true); const response = await fetch(`/api/keloladata/data-nilai-mahasiswa?id=${deleteId}`, { method: "DELETE", }); if (!response.ok) { const errorData = await response.json(); showError("Gagal!", errorData.message || "Failed to delete nilai"); throw new Error(errorData.message || "Failed to delete nilai"); } // Refresh data after successful deletion await fetchNilaiMahasiswa(); setIsDeleteDialogOpen(false); setDeleteId(null); showSuccess("Berhasil!", "Nilai mahasiswa berhasil dihapus"); } catch (err) { console.error("Error deleting nilai:", err); } finally { setIsDeleting(false); } }; // Generate pagination items const renderPaginationItems = () => { const totalPages = getTotalPages(); const items = []; // Always show first page items.push( handlePageChange(1)} className="dark:text-white" > 1 ); // Show ellipsis if needed if (currentPage > 3) { items.push( ); } // Show pages around current page for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) { if (i === 1 || i === totalPages) continue; // Skip first and last pages as they're always shown items.push( handlePageChange(i)} className="dark:text-white" > {i} ); } // Show ellipsis if needed if (currentPage < totalPages - 2) { items.push( ); } // Always show last page if there's more than one page if (totalPages > 1) { items.push( handlePageChange(totalPages)} className="dark:text-white" > {totalPages} ); } return items; }; // Calculate the range of entries being displayed const getDisplayRange = () => { if (filteredData.length === 0) return { start: 0, end: 0 }; const start = (currentPage - 1) * pageSize + 1; const end = Math.min(currentPage * pageSize, filteredData.length); return { start, end }; }; // Get badge color based on nilai huruf const getNilaiBadgeColor = (nilai: string) => { switch (nilai) { case "A": return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"; case "B+": return "bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200"; case "B": return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"; case "C+": return "bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-200"; case "C": return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"; case "D+": return "bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200"; case "D": return "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"; case "E": return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"; default: return "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200"; } }; return (

Data Nilai Mahasiswa

{/* Filters */}
setSearchTerm(e.target.value)} /> {searchTerm && ( setSearchTerm("")} /> )}
{/* Show entries selector */}
Show entries
{/* Table */} {loading ? (
) : error ? (
{error}
) : (
NIM Nama Mahasiswa Kode MK Nama Mata Kuliah Semester Nilai Huruf Nilai Angka Aksi {paginatedData.length === 0 ? ( Tidak ada data yang sesuai dengan filter ) : ( paginatedData.map((nilai) => ( {nilai.nim} {nilai.nama} {nilai.kode_mk} {nilai.nama_mk} Semester {nilai.semester} {nilai.nilai_huruf} {nilai.nilai_angka.toFixed(2)}
)) )}
)} {/* Pagination info and controls */} {!loading && !error && filteredData.length > 0 && (
Showing {getDisplayRange().start} to {getDisplayRange().end} of {filteredData.length} entries
handlePageChange(Math.max(1, currentPage - 1))} className={`dark:text-white ${currentPage === 1 ? "pointer-events-none opacity-50" : ""}`} /> {renderPaginationItems()} handlePageChange(Math.min(getTotalPages(), currentPage + 1))} className={`dark:text-white ${currentPage === getTotalPages() ? "pointer-events-none opacity-50" : ""}`} />
)} {/* Add/Edit Dialog */} {formMode === "add" ? "Tambah Nilai" : "Edit Nilai"}
{/* Delete Confirmation Dialog */} Konfirmasi Hapus

Apakah Anda yakin ingin menghapus data nilai ini?

Tindakan ini tidak dapat dibatalkan.

); }