fixed internship list and internship detail admin
This commit is contained in:
@@ -4,16 +4,35 @@ import NavbarAdmin from "../Layout/NavbarAdmin";
|
|||||||
import SidebarAdmin from "../Layout/SidebarAdmin";
|
import SidebarAdmin from "../Layout/SidebarAdmin";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
type Applicant = {
|
||||||
|
id: number;
|
||||||
|
nama_depan: string;
|
||||||
|
nama_belakang: string;
|
||||||
|
email: string;
|
||||||
|
kontak: string;
|
||||||
|
jurusan: string;
|
||||||
|
motivasi: string;
|
||||||
|
relevant_skills: string;
|
||||||
|
cv_path: string;
|
||||||
|
portofolio_path: string;
|
||||||
|
posisi: string;
|
||||||
|
kelompok_peminatan?: string;
|
||||||
|
status: string;
|
||||||
|
};
|
||||||
|
|
||||||
const InternshipDetailsAdmin = () => {
|
const InternshipDetailsAdmin = () => {
|
||||||
const { id } = useParams(); // id lamaran magang
|
const { id } = useParams(); // id lamaran magang
|
||||||
const [applicant, setApplicant] = useState<any>(null);
|
const [applicant, setApplicant] = useState<Applicant | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
const token = document.cookie
|
|
||||||
.split("; ")
|
|
||||||
.find((row) => row.startsWith("token="))
|
|
||||||
?.split("=")[1];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const token = document.cookie
|
||||||
|
.split("; ")
|
||||||
|
.find((row) => row.startsWith("token="))
|
||||||
|
?.split("=")[1];
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.get(
|
.get(
|
||||||
`${import.meta.env.VITE_API_BASE_URL}/lamaran-magang-api/get/${id}`,
|
`${import.meta.env.VITE_API_BASE_URL}/lamaran-magang-api/get/${id}`,
|
||||||
@@ -24,14 +43,74 @@ const InternshipDetailsAdmin = () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setApplicant(res.data.data[0]);
|
console.log("Detail lamaran:", res.data);
|
||||||
|
|
||||||
|
const raw =
|
||||||
|
Array.isArray(res.data?.data) && res.data.data.length > 0
|
||||||
|
? res.data.data[0]
|
||||||
|
: Array.isArray(res.data)
|
||||||
|
? res.data[0]
|
||||||
|
: res.data?.data ?? res.data;
|
||||||
|
|
||||||
|
if (!raw) {
|
||||||
|
setError("Data lamaran tidak ditemukan.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Raw detail:", raw); // cek struktur asli
|
||||||
|
|
||||||
|
const mapped: Applicant = {
|
||||||
|
id: raw.id,
|
||||||
|
|
||||||
|
// dari relasi mahasiswa
|
||||||
|
nama_depan: raw.mahasiswa?.nama_depan ?? "",
|
||||||
|
nama_belakang: raw.mahasiswa?.nama_belakang ?? "",
|
||||||
|
email: raw.mahasiswa?.email ?? raw.email ?? "",
|
||||||
|
kontak: raw.mahasiswa?.kontak ?? raw.kontak ?? "",
|
||||||
|
jurusan: raw.mahasiswa?.jurusan ?? raw.jurusan ?? "",
|
||||||
|
|
||||||
|
// dari relasi lowongan_magang
|
||||||
|
posisi: raw.lowongan_magang?.posisi ?? raw.posisi ?? "",
|
||||||
|
kelompok_peminatan:
|
||||||
|
raw.lowongan_magang?.kelompok_peminatan ??
|
||||||
|
raw.kelompok_peminatan ??
|
||||||
|
"",
|
||||||
|
|
||||||
|
// 🔥 ini dia: ambil dari mahasiswa
|
||||||
|
motivasi: raw.mahasiswa?.motivasi ?? raw.motivasi ?? "",
|
||||||
|
|
||||||
|
relevant_skills:
|
||||||
|
raw.mahasiswa?.relevant_skills ??
|
||||||
|
raw.relevant_skills ??
|
||||||
|
raw.relevant_skill ??
|
||||||
|
"",
|
||||||
|
|
||||||
|
// sekalian amankan CV & portofolio kalau memang ikut di object mahasiswa
|
||||||
|
cv_path: raw.cv_path ?? raw.mahasiswa?.cv_path ?? "",
|
||||||
|
portofolio_path:
|
||||||
|
raw.portofolio_path ?? raw.mahasiswa?.portofolio_path ?? "",
|
||||||
|
|
||||||
|
status: raw.status ?? "diproses",
|
||||||
|
};
|
||||||
|
|
||||||
|
setApplicant(mapped);
|
||||||
|
setError(null);
|
||||||
})
|
})
|
||||||
|
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Gagal fetch detail lamaran", err);
|
console.error("Gagal fetch detail lamaran", err);
|
||||||
|
setError("Gagal mengambil detail lamaran.");
|
||||||
});
|
});
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
const token = document.cookie
|
||||||
|
.split("; ")
|
||||||
|
.find((row) => row.startsWith("token="))
|
||||||
|
?.split("=")[1];
|
||||||
|
|
||||||
const handleStatusUpdate = (status: "diterima" | "ditolak") => {
|
const handleStatusUpdate = (status: "diterima" | "ditolak") => {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.patch(
|
.patch(
|
||||||
`${import.meta.env.VITE_API_BASE_URL}/lamaran-magang-api/update/${id}`,
|
`${import.meta.env.VITE_API_BASE_URL}/lamaran-magang-api/update/${id}`,
|
||||||
@@ -44,13 +123,17 @@ const InternshipDetailsAdmin = () => {
|
|||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
alert(`Status berhasil diubah menjadi ${status}`);
|
alert(`Status berhasil diubah menjadi ${status}`);
|
||||||
setApplicant((prev: any) => ({ ...prev, status }));
|
setApplicant((prev) => (prev ? { ...prev, status } : prev));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Gagal update status", err);
|
console.error("Gagal update status", err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <p className="p-10 text-red-500">{error}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!applicant) return <p className="p-10">Loading...</p>;
|
if (!applicant) return <p className="p-10">Loading...</p>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -117,9 +200,10 @@ const InternshipDetailsAdmin = () => {
|
|||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<h2 className="font-semibold">Skill:</h2>
|
<h2 className="font-semibold">Skill:</h2>
|
||||||
{applicant.relevant_skills
|
{(applicant.relevant_skills || "")
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((skill: string, idx: number) => (
|
.filter((s) => s.trim() !== "")
|
||||||
|
.map((skill, idx) => (
|
||||||
<span
|
<span
|
||||||
key={idx}
|
key={idx}
|
||||||
className="text-[#9F9F9F] font-semibold px-3 py-1 border border-[#9F9F9F] rounded-3xl"
|
className="text-[#9F9F9F] font-semibold px-3 py-1 border border-[#9F9F9F] rounded-3xl"
|
||||||
|
|||||||
@@ -32,7 +32,16 @@ const InternshipList = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setApplications(res.data.data || []);
|
const raw = Array.isArray(res.data.data) ? res.data.data : [];
|
||||||
|
|
||||||
|
const mapped: Application[] = raw.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
nama_depan: item.mahasiswa?.nama_depan ?? "",
|
||||||
|
posisi: item.lowongan_magang?.posisi ?? "",
|
||||||
|
status: item.status ?? "diproses",
|
||||||
|
}));
|
||||||
|
|
||||||
|
setApplications(mapped);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Gagal mengambil data lamaran:", err);
|
console.error("Gagal mengambil data lamaran:", err);
|
||||||
@@ -42,7 +51,6 @@ const InternshipList = () => {
|
|||||||
fetchApplications();
|
fetchApplications();
|
||||||
|
|
||||||
const interval = setInterval(fetchApplications, 10000);
|
const interval = setInterval(fetchApplications, 10000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -62,11 +70,13 @@ const InternshipList = () => {
|
|||||||
const indexOfLastItem = currentPage * itemsPerPage;
|
const indexOfLastItem = currentPage * itemsPerPage;
|
||||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||||
|
|
||||||
const filteredApplications = applications.filter(
|
const filteredApplications = applications.filter((app) => {
|
||||||
(app) =>
|
const term = searchTerm.toLowerCase();
|
||||||
app.nama_depan.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
const nama = (app.nama_depan ?? "").toLowerCase();
|
||||||
app.posisi.toLowerCase().includes(searchTerm.toLowerCase())
|
const posisi = (app.posisi ?? "").toLowerCase();
|
||||||
);
|
|
||||||
|
return nama.includes(term) || posisi.includes(term);
|
||||||
|
});
|
||||||
|
|
||||||
const currentItems = filteredApplications.slice(
|
const currentItems = filteredApplications.slice(
|
||||||
indexOfFirstItem,
|
indexOfFirstItem,
|
||||||
@@ -213,17 +223,17 @@ const InternshipList = () => {
|
|||||||
<td className="px-6 py-3">
|
<td className="px-6 py-3">
|
||||||
<span
|
<span
|
||||||
className={`px-3 py-1 rounded-full font-semibold text-sm ${getStatusStyle(
|
className={`px-3 py-1 rounded-full font-semibold text-sm ${getStatusStyle(
|
||||||
app.status
|
app.status ?? "diproses"
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{app.status.charAt(0).toUpperCase() +
|
{(app.status ?? "diproses").charAt(0).toUpperCase() +
|
||||||
app.status.slice(1)}
|
(app.status ?? "diproses").slice(1)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-3">
|
<td className="px-6 py-3">
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(`/InternshipsDetailsAdmin/${app.id}`)
|
navigate(`/internships-details-admin/${app.id}`)
|
||||||
}
|
}
|
||||||
className="bg-blue-500 hover:bg-blue-600 cursor-pointer text-white px-4 py-2 rounded-lg"
|
className="bg-blue-500 hover:bg-blue-600 cursor-pointer text-white px-4 py-2 rounded-lg"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -183,6 +183,11 @@ const PartnershipsList = () => {
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{partnerships.length === 0 && (
|
||||||
|
<p className="text-gray-500 mt-4 text-center">Belum ada data partnerships.</p>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user