"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 } from "lucide-react"; import UploadExcelDosen from "@/components/datatable/upload-file-dosen"; import { useToast } from "@/components/ui/toast-provider"; // Define the Dosen type interface Dosen { id_dosen: number; nama_dosen: string; nip: string; } export default function DataTableDosen() { const { showSuccess, showError } = useToast(); // State for data const [dosen, setDosen] = useState([]); const [filteredData, setFilteredData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // State for filtering const [searchTerm, setSearchTerm] = 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>({}); 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(() => { fetchDosen(); }, []); // Filter data when search term changes useEffect(() => { filterData(); }, [searchTerm, dosen]); // Update paginated data when filtered data or pagination settings change useEffect(() => { paginateData(); }, [filteredData, currentPage, pageSize]); // Fetch dosen data from API const fetchDosen = async () => { try { setLoading(true); let url = "/api/keloladata/data-dosen"; // Add search to URL if it exists const params = new URLSearchParams(); if (searchTerm) { params.append("search", searchTerm); } 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(); setDosen(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 const filterData = () => { let filtered = [...dosen]; // Filter by search term if (searchTerm) { filtered = filtered.filter( (item) => (item.nama_dosen?.toLowerCase() || "").includes(searchTerm.toLowerCase()) || (item.nip?.toLowerCase() || "").includes(searchTerm.toLowerCase()) ); } 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({}); }; // Handle form input changes const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); }; // Open form dialog for adding new dosen const handleAdd = () => { setFormMode("add"); resetForm(); setIsDialogOpen(true); }; // Open form dialog for editing dosen const handleEdit = (data: Dosen) => { 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 dosen const response = await fetch("/api/keloladata/data-dosen", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(formData), }); const responseData = await response.json(); if (!response.ok) { // Handle specific errors if (response.status === 409) { showError("Gagal!", responseData.message); throw new Error(responseData.message); } if (response.status === 400) { showError("Gagal!", responseData.message); throw new Error(responseData.message); } showError("Gagal!", "Gagal menambahkan dosen"); throw new Error(responseData.message || "Failed to add dosen"); } showSuccess("Berhasil!", "Dosen berhasil ditambahkan"); } else { // Edit existing dosen const response = await fetch(`/api/keloladata/data-dosen?id=${formData.id_dosen}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(formData), }); const responseData = await response.json(); if (!response.ok) { // Handle specific errors if (response.status === 409) { showError("Gagal!", responseData.message); throw new Error(responseData.message); } if (response.status === 400) { showError("Gagal!", responseData.message); throw new Error(responseData.message); } showError("Gagal!", responseData.message || "Failed to update dosen"); throw new Error(responseData.message || "Failed to update dosen"); } showSuccess("Berhasil!", "Dosen berhasil diperbarui"); } // Refresh data after successful operation await fetchDosen(); setIsDialogOpen(false); resetForm(); } catch (err) { console.error("Error submitting form:", err); } finally { setIsSubmitting(false); } }; // Delete dosen const handleDelete = async () => { if (!deleteId) return; try { setIsDeleting(true); const response = await fetch(`/api/keloladata/data-dosen?id=${deleteId}`, { method: "DELETE", }); const responseData = await response.json(); if (!response.ok) { if (response.status === 409) { showError("Gagal!", responseData.message); throw new Error(responseData.message); } showError("Gagal!", responseData.message || "Failed to delete dosen"); throw new Error(responseData.message || "Failed to delete dosen"); } // Refresh data after successful deletion await fetchDosen(); setIsDeleteDialogOpen(false); setDeleteId(null); showSuccess("Berhasil!", "Dosen berhasil dihapus"); } catch (err) { console.error("Error deleting dosen:", err); } finally { setIsDeleting(false); } }; // Generate pagination items const renderPaginationItems = () => { const totalPages = getTotalPages(); const items = []; // Always show first page items.push( handlePageChange(1)} > 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)} > {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)} > {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 }; }; return (

Data Dosen

{/* Search */}
setSearchTerm(e.target.value)} /> {searchTerm && ( setSearchTerm("")} /> )}
{/* Show entries selector */}
Show entries
{/* Table */} {loading ? (
) : error ? (
{error}
) : (
{/* ID */} Nama Dosen NIP Aksi {paginatedData.length === 0 ? ( Tidak ada data yang sesuai dengan pencarian ) : ( paginatedData.map((dosenItem) => ( {/* {dosenItem.id_dosen} */} {dosenItem.nama_dosen} {dosenItem.nip}
)) )}
)} {/* 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={currentPage === 1 ? "pointer-events-none opacity-50" : ""} /> {renderPaginationItems()} handlePageChange(Math.min(getTotalPages(), currentPage + 1))} className={currentPage === getTotalPages() ? "pointer-events-none opacity-50" : ""} />
)} {/* Add/Edit Dialog */} {formMode === "add" ? "Tambah Dosen" : "Edit Dosen"}

NIP harus terdiri dari 18 karakter

{/* Delete Confirmation Dialog */} Konfirmasi Hapus

Apakah Anda yakin ingin menghapus data dosen ini?

Tindakan ini tidak dapat dibatalkan.

); }