fixed apply button to close after internship closed and added filter logic in internships list

This commit is contained in:
Billy Marcello
2025-12-21 22:27:19 +07:00
parent bdc8d469ed
commit ab98285f91
2 changed files with 137 additions and 52 deletions
+18 -3
View File
@@ -28,6 +28,10 @@ const InternshipDetails = () => {
if (!intern) if (!intern)
return <div className="p-10 text-center">Internship not found.</div>; return <div className="p-10 text-center">Internship not found.</div>;
const isExpired =
new Date(intern.durasi_akhir) < new Date() ||
intern.status_lowongan !== "dibuka";
return ( return (
<div className="bg-[#F8F9FA] min-h-screen flex flex-col"> <div className="bg-[#F8F9FA] min-h-screen flex flex-col">
{/* Navbar Section */} {/* Navbar Section */}
@@ -115,10 +119,21 @@ const InternshipDetails = () => {
{/* Apply Button */} {/* Apply Button */}
<div className="self-start md:self-center"> <div className="self-start md:self-center">
<button <button
className="bg-[#C3423F] hover:bg-[#b36462] cursor-pointer text-white text-sm md:text-[14px] font-semibold px-6 py-2 md:px-9 md:py-3 rounded-2xl shadow-xl" disabled={isExpired}
onClick={() => navigate(`/register-intern/${intern.id}`)} onClick={() => {
if (!isExpired) {
navigate(`/register-intern/${intern.id}`);
}
}}
className={`text-white text-sm md:text-[14px] font-semibold px-6 py-2 md:px-9 md:py-3 rounded-2xl shadow-xl transition-all
${
isExpired
? "bg-gray-400 cursor-not-allowed"
: "bg-[#C3423F] hover:bg-[#b36462] cursor-pointer"
}
`}
> >
Apply {isExpired ? "Pendaftaran Ditutup" : "Apply"}
</button> </button>
</div> </div>
</div> </div>
+119 -49
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import SidebarAdmin from "../Layout/SidebarAdmin"; import SidebarAdmin from "../Layout/SidebarAdmin";
import NavbarAdmin from "../Layout/NavbarAdmin"; import NavbarAdmin from "../Layout/NavbarAdmin";
@@ -11,10 +11,17 @@ type Application = {
status: string; status: string;
}; };
type StatusFilter = "all" | "diterima" | "ditolak";
const InternshipList = () => { const InternshipList = () => {
const [applications, setApplications] = useState<Application[]>([]); const [applications, setApplications] = useState<Application[]>([]);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
//FILTER STATE
const [selectedPosisi, setSelectedPosisi] = useState<string>("all");
const [selectedStatus, setSelectedStatus] = useState<StatusFilter>("all");
const itemsPerPage = 10; const itemsPerPage = 10;
const navigate = useNavigate(); const navigate = useNavigate();
@@ -66,18 +73,45 @@ const InternshipList = () => {
} }
}; };
// OPTIONS POSISI
const posisiOptions = useMemo(() => {
const set = new Set<string>();
applications.forEach((a) => {
const p = (a.posisi ?? "").trim();
if (p) set.add(p);
});
return ["all", ...Array.from(set).sort((a, b) => a.localeCompare(b))];
}, [applications]);
// FILTER + SEARCH
const filteredApplications = useMemo(() => {
const term = searchTerm.toLowerCase();
return applications.filter((app) => {
const nama = (app.nama_depan ?? "").toLowerCase();
const posisi = (app.posisi ?? "").toLowerCase();
const status = (app.status ?? "diproses").toLowerCase();
const matchSearch = nama.includes(term) || posisi.includes(term);
const matchPosisi =
selectedPosisi === "all" ? true : app.posisi === selectedPosisi;
const matchStatus =
selectedStatus === "all" ? true : status === selectedStatus;
return matchSearch && matchPosisi && matchStatus;
});
}, [applications, searchTerm, selectedPosisi, selectedStatus]);
// Reset page kalau filter/search berubah biar ga nyangkut di page besar
useEffect(() => {
setCurrentPage(1);
}, [searchTerm, selectedPosisi, selectedStatus]);
// Pagination Logic // Pagination Logic
const indexOfLastItem = currentPage * itemsPerPage; const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const filteredApplications = applications.filter((app) => {
const term = searchTerm.toLowerCase();
const nama = (app.nama_depan ?? "").toLowerCase();
const posisi = (app.posisi ?? "").toLowerCase();
return nama.includes(term) || posisi.includes(term);
});
const currentItems = filteredApplications.slice( const currentItems = filteredApplications.slice(
indexOfFirstItem, indexOfFirstItem,
indexOfLastItem indexOfLastItem
@@ -124,15 +158,11 @@ const InternshipList = () => {
const handleUpdateBatch = async () => { const handleUpdateBatch = async () => {
const exportSuccess = await handleExportExcel(); const exportSuccess = await handleExportExcel();
if (!exportSuccess) return;
if (!exportSuccess) {
return;
}
const confirmDelete = window.confirm( const confirmDelete = window.confirm(
"Apakah Anda yakin ingin menghapus seluruh data pelamar setelah melakukan ekspor?" "Apakah Anda yakin ingin menghapus seluruh data pelamar setelah melakukan ekspor?"
); );
if (!confirmDelete) return; if (!confirmDelete) return;
const token = document.cookie const token = document.cookie
@@ -158,15 +188,23 @@ const InternshipList = () => {
} }
}; };
const handleResetFilter = () => {
setSelectedPosisi("all");
setSelectedStatus("all");
setSearchTerm("");
};
return ( return (
<div className="min-h-screen w-full overflow-hidden"> <div className="min-h-screen w-full overflow-hidden">
<NavbarAdmin /> <NavbarAdmin />
<div className="flex flex-row"> <div className="flex flex-row">
<SidebarAdmin /> <SidebarAdmin />
<div className="p-10 bg-white flex-col flex-wrap w-full"> <div className="p-10 bg-white flex-col flex-wrap w-full">
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6 gap-4 flex-wrap">
<h2 className="font-semibold text-[24px]">Daftar Pelamar</h2> <h2 className="font-semibold text-[24px]">Daftar Pelamar</h2>
<div className="flex gap-2 flex-wrap">
<div className="flex gap-2 flex-wrap items-center">
{/* SEARCH */}
<input <input
type="text" type="text"
placeholder="Cari Nama, Posisi" placeholder="Cari Nama, Posisi"
@@ -174,6 +212,42 @@ const InternshipList = () => {
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="px-4 py-2 rounded-2xl w-[20rem] border border-slate-400" className="px-4 py-2 rounded-2xl w-[20rem] border border-slate-400"
/> />
{/*FILTER POSISI*/}
<select
value={selectedPosisi}
onChange={(e) => setSelectedPosisi(e.target.value)}
className="px-4 py-2 rounded-2xl border border-slate-400 bg-white"
>
{posisiOptions.map((p) => (
<option key={p} value={p}>
{p === "all" ? "Semua Posisi" : p}
</option>
))}
</select>
{/*FILTER STATUS (diterima / ditolak)*/}
<select
value={selectedStatus}
onChange={(e) =>
setSelectedStatus(e.target.value as StatusFilter)
}
className="px-4 py-2 rounded-2xl border border-slate-400 bg-white"
>
<option value="all">Semua Status</option>
<option value="diterima">Diterima</option>
<option value="ditolak">Ditolak</option>
</select>
{/* RESET FILTER */}
<button
onClick={handleResetFilter}
className="px-4 py-2 rounded-lg border border-slate-400 hover:bg-slate-50"
>
Reset
</button>
{/* Update Batch dan Ekspor */}
<button <button
onClick={handleUpdateBatch} onClick={handleUpdateBatch}
className="bg-[#923120] hover:bg-[#8d5050] cursor-pointer text-white px-4 py-2 rounded-lg flex flex-row items-center gap-2" className="bg-[#923120] hover:bg-[#8d5050] cursor-pointer text-white px-4 py-2 rounded-lg flex flex-row items-center gap-2"
@@ -196,6 +270,7 @@ const InternshipList = () => {
</svg> </svg>
</span> </span>
</button> </button>
<button <button
onClick={handleExportExcel} onClick={handleExportExcel}
className="bg-[#227014] hover:bg-green-700 cursor-pointer text-white px-4 py-2 rounded-lg" className="bg-[#227014] hover:bg-green-700 cursor-pointer text-white px-4 py-2 rounded-lg"
@@ -205,6 +280,13 @@ const InternshipList = () => {
</div> </div>
</div> </div>
{/* INFO JUMLAH */}
<div className="mb-4 text-sm text-slate-600">
Menampilkan{" "}
<span className="font-semibold">{filteredApplications.length}</span>{" "}
data (setelah filter).
</div>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="min-w-full"> <table className="min-w-full">
<thead> <thead>
@@ -216,8 +298,8 @@ const InternshipList = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{currentItems.map((app, idx) => ( {currentItems.map((app) => (
<tr key={idx} className="hover:bg-gray-50"> <tr key={app.id} className="hover:bg-gray-50">
<td className="px-6 py-3">{app.nama_depan}</td> <td className="px-6 py-3">{app.nama_depan}</td>
<td className="px-6 py-3">{app.posisi}</td> <td className="px-6 py-3">{app.posisi}</td>
<td className="px-6 py-3"> <td className="px-6 py-3">
@@ -231,20 +313,6 @@ const InternshipList = () => {
</span> </span>
</td> </td>
<td className="px-6 py-3"> <td className="px-6 py-3">
{/* <button
onClick={() => handleStatusUpdate("diterima")}
className="bg-[#3BB525] hover:bg-green-600 cursor-pointer text-white p-2 rounded-xl"
>
Terima
</button>
<button
onClick={() => handleStatusUpdate("ditolak")}
className="bg-[#C3423F] hover:bg-red-600 cursor-pointer text-white p-2 rounded-xl"
>
Tolak
</button> */}
<button <button
className="bg-blue-500 hover:bg-blue-600 cursor-pointer text-white p-2 rounded-xl" className="bg-blue-500 hover:bg-blue-600 cursor-pointer text-white p-2 rounded-xl"
onClick={() => onClick={() =>
@@ -277,26 +345,28 @@ const InternshipList = () => {
</tbody> </tbody>
</table> </table>
{applications.length === 0 && ( {filteredApplications.length === 0 && (
<p className="text-center text-gray-500 mt-5">Tidak ada data.</p> <p className="text-center text-gray-500 mt-5">Tidak ada data.</p>
)} )}
{/* Pagination Controls */} {/* Pagination Controls */}
<div className="flex justify-center mt-6 gap-2"> {totalPages > 1 && (
{Array.from({ length: totalPages }, (_, i) => ( <div className="flex justify-center mt-6 gap-2">
<button {Array.from({ length: totalPages }, (_, i) => (
key={i} <button
onClick={() => setCurrentPage(i + 1)} key={i}
className={`px-4 py-2 rounded-lg border ${ onClick={() => setCurrentPage(i + 1)}
currentPage === i + 1 className={`px-4 py-2 rounded-lg border ${
? "bg-blue-500 text-white" currentPage === i + 1
: "bg-white text-gray-700" ? "bg-blue-500 text-white"
}`} : "bg-white text-gray-700"
> }`}
{i + 1} >
</button> {i + 1}
))} </button>
</div> ))}
</div>
)}
</div> </div>
</div> </div>
</div> </div>