diff --git a/src/App.tsx b/src/App.tsx index 42885ef..11dce24 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,7 +31,6 @@ import EditLowongan from "./Pages/EditLowongan"; import HomeInternPage from "./Pages/Internify/intern/HomeInternPage"; import { InternLayout, MentorLayout } from "./Pages/Internify/layout"; import ProjectsPage from "./Pages/Internify/intern/ProjectsPage"; -import SertificatePage from "./Pages/Internify/intern/InternCertiDetail"; import ProjectDetailPage from "./Pages/Internify/intern/ProjectDetailPage"; import HomeMentorPage from "./Pages/Internify/mentor/HomeMentorPage"; import MentorProjectsPage from "./Pages/Internify/mentor/BaseProjectsPage"; @@ -54,6 +53,7 @@ import AdminMentorPage from "./Pages/MentorPage"; import CreateMentorForm from "./Pages/CreateMentorForm"; import EditMentorForm from "./Pages/EditMentorForm"; import NotFoundPage from "./Pages/NotFoundPage"; +import InternCertificateDetail from "./Pages/Internify/intern/InternCertDetail"; function App() { return ( @@ -103,7 +103,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/Pages/internify/intern/InternCertiDetail.tsx b/src/Pages/internify/intern/InternCertDetail.tsx similarity index 83% rename from src/Pages/internify/intern/InternCertiDetail.tsx rename to src/Pages/internify/intern/InternCertDetail.tsx index b28b2e6..dbe43e9 100644 --- a/src/Pages/internify/intern/InternCertiDetail.tsx +++ b/src/Pages/internify/intern/InternCertDetail.tsx @@ -4,19 +4,27 @@ import { useParams, useLocation } from "react-router-dom"; import CertificateAvailable from "./components/CertificateAvailabe"; import CertificateNotAvailable from "./components/CertificateNotAvailable"; import SertificateHistory from "./components/CertificateHistory"; -import { useMyCertificates, useClaimCertificate } from "../../../hooks/useCertificates"; + +import { useClaimCertificate } from "../../../hooks/useCertificates"; import { useProjectDetail } from "../../../hooks/useProjects"; + +import { useProjectCertificates } from "../../../hooks/useCertificates"; import type { Project } from "../../../types/project.types"; -const SertificatePage = () => { +export default function InternCertificateDetail () { const { slug } = useParams<{ slug: string }>(); const location = useLocation(); const projectFromList = location.state?.project as Project | undefined; const { project: projectDetail, loading: projectLoading } = useProjectDetail(slug ?? ""); - const { certificates, loading: certLoading, refetch: refetchCertificates } = useMyCertificates(); + const { certificates, loading: certLoading, refetch: refetchCertificates } = useProjectCertificates(projectDetail?.id ?? 0); const { claim, loading: claiming, error: claimError } = useClaimCertificate(); + const myCertificateForProject = useMemo(() => { + if (!projectDetail) return null; + return certificates.find((cert) => cert.id_project === projectDetail.id); + }, [projectDetail, certificates]); + const project = useMemo(() => { if (!projectDetail) return null; return { @@ -28,13 +36,6 @@ const SertificatePage = () => { const allTasksDone = !projectLoading && !!project && project.task_done >= project.total_tasks; - const myCertificateForProject = useMemo(() => { - if (!project || !certificates.length) return null; - return certificates.find((cert) => cert.project.id === project.id) ?? null; - }, [certificates, project]); - - const alreadyDone = !!myCertificateForProject; - const progress = project && project.total_tasks > 0 ? Math.round((project.task_done / project.total_tasks) * 100) @@ -53,7 +54,7 @@ const SertificatePage = () => { if (isLoading) { return (
- Loading... + Memuat Data...
); } @@ -66,10 +67,11 @@ const SertificatePage = () => {
- {alreadyDone ? ( + {myCertificateForProject ? ( ) : ( {
); -}; - -export default SertificatePage; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/Pages/internify/intern/components/CertificateAvailabe.tsx b/src/Pages/internify/intern/components/CertificateAvailabe.tsx index 89d4f7c..c1df8f8 100644 --- a/src/Pages/internify/intern/components/CertificateAvailabe.tsx +++ b/src/Pages/internify/intern/components/CertificateAvailabe.tsx @@ -1,72 +1,91 @@ // CertificateAvailable.tsx -import { useState, useRef } from "react"; +import { useState, useEffect } from "react"; import { Download, Link, Printer } from "lucide-react"; import type { ProjectDetail } from "../../../../types/project.types"; import type { Certificate } from "../../../../types/certificate.types"; -import { downloadCertificateAsImage, downloadCertificateAsPdf } from "../../../utils/DownloadImagetoDevice"; -import { resolveImageUrl } from "../../../utils/SertificateGenerator"; +import { downloadCertificatePdf, downloadCertificateImage } from "../../../utils/Certificates"; +import { generateCertificate, resolveImageUrl } from "../../../utils/SertificateGenerator"; interface CertificateAvailableProps { project: ProjectDetail | null; certificate: Certificate; + templateUrl: string; } -export default function CertificateAvailable({ project, certificate }: CertificateAvailableProps) { - const [copied, setCopied] = useState(false); +export default function CertificateAvailable({ project, certificate, templateUrl }: CertificateAvailableProps) { + // const [copied, setCopied] = useState(false); const [downloadingImg, setDownloadingImg] = useState(false); const [downloadingPdf, setDownloadingPdf] = useState(false); - const certificateRef = useRef(null); + const [previewUrl, setPreviewUrl] = useState(null); + const [loadingPreview, setLoadingPreview] = useState(true); - const handleCopyLink = () => { - navigator.clipboard.writeText(window.location.href); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; + useEffect(() => { + if (!templateUrl) return; + + generateCertificate(resolveImageUrl(templateUrl), certificate.user.full_name) + .then((blob) => { + const url = URL.createObjectURL(blob); + setPreviewUrl(url); + }) + .finally(() => setLoadingPreview(false)); + + // Cleanup supaya tidak memory leak + return () => { + if (previewUrl) URL.revokeObjectURL(previewUrl); + }; + }, [templateUrl, certificate.user.full_name]); - // Hitung durasi dari start_date - end_date const duration = project ? (() => { - const start = new Date(project.start_date); - const end = new Date(project.end_date); - const diffMs = end.getTime() - start.getTime(); - const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24)); - const months = Math.floor(diffDays / 30); - const days = diffDays % 30; - if (months > 0 && days > 0) return `${months} Bulan ${days} Hari`; - if (months > 0) return `${months} Bulan`; - return `${diffDays} Hari`; - })() + const start = new Date(project.start_date); + const end = new Date(project.end_date); + const diffMs = end.getTime() - start.getTime(); + const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24)); + const months = Math.floor(diffDays / 30); + const days = diffDays % 30; + if (months > 0 && days > 0) return `${months} Bulan ${days} Hari`; + if (months > 0) return `${months} Bulan`; + return `${diffDays} Hari`; + })() : "-"; - const handleDownloadImage = async () => { + const handleDownloadImage = async () => { setDownloadingImg(true); - await downloadCertificateAsImage( - certificateRef.current, - `Sertifikat - ${certificate.certificate_no}`, - "png", - certificate.image_path - ); - setDownloadingImg(false); + try { + await downloadCertificateImage(certificate, templateUrl, "png"); + } finally { + setDownloadingImg(false); + } }; const handleDownloadPdf = async () => { setDownloadingPdf(true); - await downloadCertificateAsPdf( - certificateRef.current, - `Sertifikat - ${certificate.certificate_no}` - ); - setDownloadingPdf(false); + try { + await downloadCertificatePdf(certificate, templateUrl); + } finally { + setDownloadingPdf(false); + } }; return (
{/* Certificate Card */}
- Certificate + {loadingPreview ? ( +
+ Memuat sertifikat... +
+ ) : previewUrl ? ( + Certificate + ) : ( +
+ Gagal memuat sertifikat. +
+ )}
{/* Program Summary */} @@ -79,15 +98,7 @@ export default function CertificateAvailable({ project, certificate }: Certifica {[ { label: "Nama Program", value: project?.project_name ?? "-" }, { label: "Durasi", value: duration }, - { label: "Total Tugas", value: project ? `${project.total_tasks} Tugas` : "-" }, - { - label: "Status", - value: ( - - {certificate.certificate_status} - - ), - }, + { label: "No. Sertifikat", value: certificate.certificate_no ?? "-" }, ].map((row) => (
- {downloadingPdf? "Memproses": "Unduh Sertifikat"} + {downloadingPdf ? "Memproses" : "Unduh Sertifikat"} {/* Bagikan ke LinkedIn */} @@ -124,14 +135,14 @@ export default function CertificateAvailable({ project, certificate }: Certifica disabled={downloadingImg} className="flex flex-col items-center justify-center gap-1.5 border border-card-outline hover:bg-gray-50 active:scale-95 transition-all text-red-700 font-medium py-3.5 rounded-xl text-sm"> - {downloadingImg? "Memproses" : "Cetak"} + {downloadingImg ? "Memproses" : "Cetak"}
diff --git a/src/Pages/internify/intern/components/CertificateHistory.tsx b/src/Pages/internify/intern/components/CertificateHistory.tsx index 34d20d4..38048c9 100644 --- a/src/Pages/internify/intern/components/CertificateHistory.tsx +++ b/src/Pages/internify/intern/components/CertificateHistory.tsx @@ -40,13 +40,6 @@ export default function SertificateHistory() { key={item.id} className="flex flex-col gap-3 p-3 bg-white border border-gray-200 shadow-sm rounded-xl hover:bg-gray-50 transition-colors cursor-pointer" > -
- Certificate -

{new Date(item.issued_at).toLocaleDateString("id-ID", { diff --git a/src/Pages/internify/intern/components/HomeProjectList.tsx b/src/Pages/internify/intern/components/HomeProjectList.tsx index fb273dc..91e8ac3 100644 --- a/src/Pages/internify/intern/components/HomeProjectList.tsx +++ b/src/Pages/internify/intern/components/HomeProjectList.tsx @@ -1,12 +1,19 @@ import { useNavigate } from 'react-router-dom'; import { useProjects } from '../../../../hooks/useProjects'; +import * as LucideIcons from 'lucide-react'; +import type { LucideProps } from 'lucide-react'; const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-'); +const getDynamicIcon = (iconName: string) => { + const formatted = iconName.charAt(0).toUpperCase() + iconName.slice(1); + const Icon = (LucideIcons as unknown as Record>)[formatted]; + return Icon ?? LucideIcons.FolderOpen; +}; + const HomeProjectList = () => { const navigate = useNavigate(); const { projects, loading, error } = useProjects(); - if (loading) return (

{[...Array(3)].map((_, i) => ( @@ -27,7 +34,12 @@ const HomeProjectList = () => { onClick={() => navigate(`/intern/projects/${toSlug(project.slug)}`)} className="flex items-center gap-4 border border-card-outline rounded-lg px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors duration-150" > -
+
+ {(() => { + const Icon = getDynamicIcon(project.project_icon); + return ; + })()} +

{project.project_name}

{project.status}

diff --git a/src/Pages/internify/intern/components/SidebarIntern.tsx b/src/Pages/internify/intern/components/SidebarIntern.tsx index 135c8c7..93e5abb 100644 --- a/src/Pages/internify/intern/components/SidebarIntern.tsx +++ b/src/Pages/internify/intern/components/SidebarIntern.tsx @@ -65,7 +65,7 @@ export default function SidebarIntern({ isOpen, closeSidebar }: SidebarProps) { {/* Main nav */}
- {/* ── Bottom Section: Table + Preview Card ── */} -
- - {/* Recipient Directory Table */} -
- - {/* Table Header */} -
-

Recipient Directory

- {/* Search */} -
- - -
-
- - {/* Column Headers */} -
- Intern Name - Credential ID - Actions -
- - {/* Rows */} -
- {paginated.length === 0 ? ( -
- No interns match your search. -
- ) : ( - paginated.map((cert: Certificate) => ( -
- {/* Name + role */} -
- -
-

{cert.user.full_name}

- {/*

{cert.role}

*/} -
-
- - {/* Credential ID */} -

{cert.uuid}

- - {/* Actions */} -
- - - -
-
- )) - )} -
- - {/* Pagination Footer */} -
-

- Showing {filtered.length === 0 ? 0 : (page - 1) * PER_PAGE + 1} to{" "} - {Math.min(page * PER_PAGE, filtered.length)} of {certificates.length} interns -

-
- - {Array.from({ length: totalPages }, (_, i) => i + 1).map((n) => ( - - ))} - -
-
-
- - {/* Preview Card (placeholder — gambar belum diimplementasi) */} - {/*
-
-

- Institutional Excellence -

-

- Certified via Mentor Portal v2.4 -

-
-
*/} - -
+ {/* Table */} +
); } \ No newline at end of file diff --git a/src/Pages/internify/mentor/InternsPage.tsx b/src/Pages/internify/mentor/InternsPage.tsx index 59544a1..bc0b194 100644 --- a/src/Pages/internify/mentor/InternsPage.tsx +++ b/src/Pages/internify/mentor/InternsPage.tsx @@ -1,12 +1,15 @@ import { useState, useMemo } from "react"; import { Search, SlidersHorizontal, Plus, Trash2 } from "lucide-react"; import { useAllInterns, useRemoveMember, useProjects } from "../../../hooks/useProjects"; -import AddInternsModal from "./components/AddInternsToProjects"; +import AddInternsModal from "./components/AddInternsToProjects"; +import type { InternDetail } from "../../../types/project.types"; +import type { Project } from "../../../types/project.types"; export default function InternsManagement() { const [searchEmail, setSearchEmail] = useState(""); const [selectedProject, setSelectedProject] = useState("All Projects"); const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedUserId, setSelectedUserId] = useState(null); const { interns, loading, error, refetch } = useAllInterns(); const { remove, loading: removing } = useRemoveMember(); @@ -23,22 +26,24 @@ export default function InternsManagement() { }); }, [interns, searchEmail, selectedProject]); - // Hitung statistik dari data asli const totalInterns = interns.length; - // Active assignments = total member dari semua project yang statusnya active const activeAssignments = activeProjects.length; + // Buka modal untuk assign intern tertentu (dari tombol + di baris) + const openAssignFor = (userId: number | null) => { + setSelectedUserId(userId); + setIsModalOpen(true); + }; + // Handler hapus intern dari project - // Catatan: useRemoveMember memerlukan id_project & id_user. - // Karena InternDetail tidak menyimpan id_project, aksi ini saat ini hanya placeholder. - // Untuk implementasi penuh, perlu endpoint khusus atau data id_project dari InternDetail. - const handleDeleteIntern = async (internId: number) => { - const confirmed = window.confirm("Yakin ingin menghapus intern ini dari project?"); - if (!confirmed) return; - alert(`Remove intern ID ${internId} — perlu id_project dari backend untuk dilanjutkan.`); - // Contoh penggunaan jika id_project tersedia: - // const res = await remove({ id_project: intern.id_project, id_user: internId }); - // if (res !== null) refetch(); + const handleDeleteIntern = async (intern: InternDetail) => { + const matchedProject = activeProjects.find((project: Project) => project.project_name === intern.projectName); + if (!matchedProject) { + console.error("Project not found for intern:", intern); + return; + } + const res = await remove({ id_project: matchedProject.id, id_user: intern.id }); + if (res !== null) refetch(); }; if (loading) return ( @@ -127,7 +132,7 @@ export default function InternsManagement() {
{/* Tombol Delete: remove intern dari project */} + + + + +
+
+ )) + )} +
+ + {/* Pagination */} +
+

+ Showing {filtered.length === 0 ? 0 : (page - 1) * PER_PAGE + 1} to{" "} + {Math.min(page * PER_PAGE, filtered.length)} of {certificates.length} interns +

+
+ + {Array.from({ length: totalPages }, (_, i) => i + 1).map((n) => ( + + ))} + +
+
+ + ); +} \ No newline at end of file diff --git a/src/Pages/internify/mentor/components/CreateProjectDialog.tsx b/src/Pages/internify/mentor/components/CreateProjectDialog.tsx index 0b1181d..7492d4a 100644 --- a/src/Pages/internify/mentor/components/CreateProjectDialog.tsx +++ b/src/Pages/internify/mentor/components/CreateProjectDialog.tsx @@ -3,13 +3,15 @@ import { X } from "lucide-react"; import { PROJECT_ICON_MAP, PROJECT_ICON_CODES } from "../../../../lib/ProjectIcons"; import { LuSendHorizontal, LuMail } from "react-icons/lu"; import { useCreateProject } from "../../../../hooks/useProjects"; +import type { CreateProjectPayload } from "../../../../types/project.types"; interface CreateProjectModalProps { isOpen: boolean; onClose: () => void; + onSuccess?: () => void; } -export default function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps) { +export default function CreateProjectModal({ isOpen, onClose, onSuccess }: CreateProjectModalProps) { const dialogRef = useRef(null); const [selectedIcon, setSelectedIcon] = useState(PROJECT_ICON_CODES[0]); const [projectName, setProjectName] = useState(""); @@ -61,23 +63,23 @@ export default function CreateProjectModal({ isOpen, onClose }: CreateProjectMod setInvitedEmails(invitedEmails.filter(email => email !== emailToRemove)); }; - // Handler Submit Utama const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - const payload: any = { + const payload: CreateProjectPayload = { project_icon: selectedIcon, project_name: projectName, description: projectDescription, start_date: startDate, end_date: endDate, }; - + if (invitedEmails.length > 0) { payload.member_emails = invitedEmails; } const result = await create(payload); if (result) { + onSuccess?.(); // panggil refetch di parent (kalau ada) onClose(); } }; diff --git a/src/Pages/internify/mentor/components/EligibleInternTable.tsx b/src/Pages/internify/mentor/components/EligibleInternTable.tsx index 689e5bd..42f2bd0 100644 --- a/src/Pages/internify/mentor/components/EligibleInternTable.tsx +++ b/src/Pages/internify/mentor/components/EligibleInternTable.tsx @@ -15,6 +15,7 @@ import type { InternSubmissionProgress } from "../../../../hooks/useInternProgre import { useProjectDetail } from "../../../../hooks/useProjects"; import JSZip from "jszip"; import { generateCertificate } from "../../../utils/SertificateGenerator"; +import { useGenerateCertificates } from "../../../../hooks/useCertificates"; import Avatar from "../../Avatar"; import ProgressBar from "../../ProgressBar"; @@ -47,20 +48,19 @@ export default function EligibleInternTable({ }: EligibleInternTableProps) { const navigate = useNavigate(); const { project } = useProjectDetail(String(projectId)); + const { generate: SubmitCertificates, loading: generatingCertificates, error, success, reset } = useGenerateCertificates(); const initialSelected = new Set( interns.filter(i => i.is_eligible).map(i => i.id_user) ); - // Pemilihan Id const [selected, setSelected] = useState>(initialSelected); - // state saat generate sertifikat const [generating, setGenerating] = useState(false); const [generateError, setGenerateError] = useState(null); + // Halaman (Pagination) const [page, setPage] = useState(1); const totalPages = Math.ceil(interns.length / PER_PAGE); const paginated = interns.slice((page - 1) * PER_PAGE, page * PER_PAGE); - const eligibleIds = interns.filter(i => i.is_eligible).map(i => i.id_user); const allEligibleSelected = eligibleIds.every(id => selected.has(id)); @@ -78,56 +78,48 @@ export default function EligibleInternTable({ }; const handleGenerate = async () => { - // NOTE: DI RESPONSE GAK DIKASIH TAHU DIMANA SIMPAN TEMPLATE SERTIF NYA - if (!project?.certificate_template) { - setGenerateError("Template sertifikat belum diupload."); - return; - } - if (selected.size === 0) { - setGenerateError("Pilih minimal satu intern."); - return; - } + if (!project?.certificate_template) { + setGenerateError("Template sertifikat belum diupload."); + return; + } + if (selected.size === 0) { + setGenerateError("Pilih minimal satu intern."); + return; + } - setGenerating(true); - setGenerateError(null); + setGenerating(true); + setGenerateError(null); - try { - const selectedInterns = interns.filter((i) => selected.has(i.id_user)); - const zip = new JSZip(); + try { + // 1. Generate blob PNG per intern & kumpulkan ke ZIP + const selectedInterns = interns.filter((i) => selected.has(i.id_user)); + const zip = new JSZip(); - // Generate semua sertifikat secara paralel - await Promise.all( - selectedInterns.map(async (intern) => { - const blob = await generateCertificate( - project.certificate_template, - intern.full_name - ); - // Nama file: "Sertifikat - Nama Intern.png" - const filename = `Sertifikat - ${intern.full_name}.png`; - zip.file(filename, blob); - }) - ); + await Promise.all( + selectedInterns.map(async (intern) => { + const blob = await generateCertificate( + project.certificate_template, + intern.full_name + ); + zip.file(`Sertifikat - ${intern.full_name}.png`, blob); + }) + ); - // Download ZIP - const zipBlob = await zip.generateAsync({ type: "blob" }); - const url = URL.createObjectURL(zipBlob); - const a = document.createElement("a"); - a.href = url; - a.download = `Sertifikat-Project-${projectId}.zip`; - a.click(); - URL.revokeObjectURL(url); + // 2. POST ke backend untuk mencatat sertifikat sudah di-generate + const ok = await SubmitCertificates(projectId, Array.from(selected)); + if (!ok) return; - // Navigasi ke result page - navigate(`/mentor/certificates/${projectId}/result`); - } catch (err) { - console.error(err); - setGenerateError( - err instanceof Error ? err.message : "Gagal generate sertifikat." - ); - } finally { - setGenerating(false); - } - }; + // 3. Navigasi ke result page + navigate(`/mentor/certificates/${projectId}/result`); + } catch (err) { + console.error(err); + setGenerateError( + err instanceof Error ? err.message : "Gagal generate sertifikat." + ); + } finally { + setGenerating(false); + } +}; if (loading) { return ( diff --git a/src/Pages/internify/mentor/components/HomeMentorProject.tsx b/src/Pages/internify/mentor/components/HomeMentorProject.tsx index 438d3e9..6c338fd 100644 --- a/src/Pages/internify/mentor/components/HomeMentorProject.tsx +++ b/src/Pages/internify/mentor/components/HomeMentorProject.tsx @@ -3,6 +3,14 @@ import { useState } from 'react'; import { Plus } from 'lucide-react'; import CreateProjectModal from './CreateProjectDialog'; import { useProjects } from '../../../../hooks/useProjects'; +import * as LucideIcons from 'lucide-react'; +import type { LucideProps } from 'lucide-react'; + +const getDynamicIcon = (iconName: string) => { + const formatted = iconName.charAt(0).toUpperCase() + iconName.slice(1); + const Icon = (LucideIcons as unknown as Record>)[formatted]; + return Icon ?? LucideIcons.FolderOpen; +}; const HomeMentorProject = () => { const navigate = useNavigate(); @@ -36,7 +44,11 @@ const HomeMentorProject = () => { onClick={() => navigate(`/mentor/projects/${project.slug}`)} className="flex items-center gap-4 border border-box-border rounded-lg px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors duration-150" > -
+
+ {(() => { + const Icon = getDynamicIcon(project.project_icon); + return ; + })()}

{project.project_name}

diff --git a/src/Pages/internify/mentor/components/MentorProjectCard.tsx b/src/Pages/internify/mentor/components/MentorProjectCard.tsx index 184df84..2dd044c 100644 --- a/src/Pages/internify/mentor/components/MentorProjectCard.tsx +++ b/src/Pages/internify/mentor/components/MentorProjectCard.tsx @@ -6,10 +6,10 @@ import type { Project } from '../../../../types/project.types'; import { useArchiveProject } from '../../../../hooks/useProjects'; const getDynamicIcon = (iconName: string) => { - const formatted = iconName.charAt(0).toUpperCase() + iconName.slice(1); - const Icon = (LucideIcons as unknown as Record>)[formatted]; - return Icon ?? LucideIcons.FolderOpen; - }; + const formatted = iconName.charAt(0).toUpperCase() + iconName.slice(1); + const Icon = (LucideIcons as unknown as Record>)[formatted]; + return Icon ?? LucideIcons.FolderOpen; +}; const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-'); @@ -61,8 +61,20 @@ export default function MentorProjectCard(project: Project) { {project.total_members}/{project.total_members}
+
+ {project.status === "active" ? ( + Active + ) : project.status === "completed" ? ( + Completed + ) : project.status === "archived" ? ( + Archived + ) : ( + Inactive + )} +
+ {/* Footer */}