fixed apply button to close after internship closed and added filter logic in internships list
This commit is contained in:
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user