diff --git a/src/Pages/internify/Breadcumbs.tsx b/src/Pages/internify/Breadcumbs.tsx index 5f7b511..b4c43f3 100644 --- a/src/Pages/internify/Breadcumbs.tsx +++ b/src/Pages/internify/Breadcumbs.tsx @@ -1,91 +1,108 @@ -import { Link, useLocation } from "react-router-dom"; -import useBreadcrumbs from "use-react-router-breadcrumbs"; -import { ChevronRight } from "lucide-react"; -import { useMyProjects, useProjects } from "../../hooks/useProjects"; -import { useProjectTasks } from "../../hooks/useTasks"; - -type BreadcrumbMatch = { - match: { params: { slug?: string; taskSlug?: string } }; -}; - -const unslugify = (slug = '') => - slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); - -const ProjectBreadcrumb = ({ match }: BreadcrumbMatch) => { - const { projects } = useProjects(); - const slug = match.params.slug; - const project = projects?.find((p) => p.slug === slug); - return <>{project?.project_name ?? unslugify(slug)}; -}; - -const TaskBreadcrumb = ({ match }: BreadcrumbMatch) => { - const {slug, taskSlug} = match.params; - - const { projects } = useMyProjects(); - const project = projects?.find((p) => p.slug === slug); - - const { tasks } = useProjectTasks(project?.id ?? 0); - const task = tasks?.find((t) => t.slug === taskSlug); - return <>{task?.title ?? unslugify(taskSlug)}; -}; - -const breadcrumbRoutes = [ - // --- AREA INTERN --- - { path: '/intern', breadcrumb: 'Home' }, - { path: '/intern/projects', breadcrumb: 'Projects' }, - { path: '/intern/projects/:slug', breadcrumb: ProjectBreadcrumb }, - { path: '/intern/certificates', breadcrumb: 'Certificates' }, - { path: '/intern/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb }, - { path: '/intern/faq', breadcrumb: 'FAQ' }, - { path: '/intern/notifications', breadcrumb: 'Notifications' }, - { path: '/intern/settings', breadcrumb: 'Settings' }, - - // --- AREA MENTOR / ADMIN --- - { path: '/mentor', breadcrumb: 'Home' }, - { path: '/mentor/projects', breadcrumb: 'List Projects' }, - { path: '/mentor/projects/:slug', breadcrumb: ProjectBreadcrumb }, - { path: '/mentor/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb }, - { path: '/mentor/certificates', breadcrumb: 'Certificates' }, - { path: '/mentor/intern', breadcrumb: 'List Intern' }, - { path: '/mentor/faq', breadcrumb: 'FAQ' }, - { path: '/mentor/notifications', breadcrumb: 'Notifications' }, - { path: '/mentor/settings', breadcrumb: 'Settings' }, -]; - -export default function Breadcrumbs(){ - const breadcrumbs = useBreadcrumbs(breadcrumbRoutes); - const location = useLocation(); - - const rootPaths = ['/intern', '/mentor']; - const isOnRootPath = rootPaths.includes(location.pathname); - const fixbreadcumbs = breadcrumbs.filter(({ match }) => { - if (match.pathname === '/') return false; - if (rootPaths.includes(match.pathname) && !isOnRootPath) return false; - return true; - }); - return ( - - ); -}; \ No newline at end of file +import { Link, useLocation } from "react-router-dom"; +import useBreadcrumbs from "use-react-router-breadcrumbs"; +import { ChevronRight } from "lucide-react"; +import { useMyProjects, useProjects } from "../../hooks/useProjects"; +import { useProjectTasks } from "../../hooks/useTasks"; + +type BreadcrumbMatch = { + match: { params: { slug?: string; taskSlug?: string; nameIntern?: string } }; +}; + +const unslugify = (slug = '') => + slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); + +const ProjectBreadcrumb = ({ match }: BreadcrumbMatch) => { + const { projects } = useProjects(); + const slug = match.params.slug; + const project = projects?.find((p) => p.slug === slug); + return <>{project?.project_name ?? unslugify(slug)}; +}; + +const TaskBreadcrumb = ({ match }: BreadcrumbMatch) => { + const {slug, taskSlug} = match.params; + + const { projects } = useMyProjects(); + const project = projects?.find((p) => p.slug === slug); + + const { tasks } = useProjectTasks(project?.id ?? 0); + const task = tasks?.find((t) => t.slug === taskSlug); + return <>{task?.title ?? unslugify(taskSlug)}; +}; + +// Breadcrumb untuk halaman detail sertifikat (menggunakan nama project dari slug) +const CertBreadcrumb = ({ match }: BreadcrumbMatch) => { + const { projects } = useProjects(); + const slug = match.params.slug; + const project = projects?.find((p) => p.slug === slug); + return <>{project?.project_name ?? unslugify(slug)}; +}; + +// Breadcrumb untuk halaman submission intern (unslugify nameIntern) +const NameInternBreadcrumb = ({ match }: BreadcrumbMatch) => { + return <>{unslugify(match.params.nameIntern)}; +}; + +const breadcrumbRoutes = [ + // --- AREA INTERN --- + { path: '/intern', breadcrumb: 'Home' }, + { path: '/intern/projects', breadcrumb: 'Projects' }, + { path: '/intern/projects/:slug', breadcrumb: ProjectBreadcrumb }, + { path: '/intern/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb }, + { path: '/intern/certificates', breadcrumb: 'Certificates' }, + { path: '/intern/certificates/:slug', breadcrumb: CertBreadcrumb }, + { path: '/intern/faq', breadcrumb: 'FAQ' }, + { path: '/intern/notifications', breadcrumb: 'Notifications' }, + { path: '/intern/settings', breadcrumb: 'Settings' }, + + // --- AREA MENTOR / ADMIN --- + { path: '/mentor', breadcrumb: 'Home' }, + { path: '/mentor/projects', breadcrumb: 'List Projects' }, + { path: '/mentor/projects/:slug', breadcrumb: ProjectBreadcrumb }, + { path: '/mentor/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb }, + { path: '/mentor/projects/:slug/:taskSlug/:nameIntern', breadcrumb: NameInternBreadcrumb }, + { path: '/mentor/certificates', breadcrumb: 'Certificates' }, + { path: '/mentor/certificates/:slug', breadcrumb: CertBreadcrumb }, + { path: '/mentor/certificates/:slug/result', breadcrumb: 'Result' }, + { path: '/mentor/intern', breadcrumb: 'List Intern' }, + { path: '/mentor/faq', breadcrumb: 'FAQ' }, + { path: '/mentor/notifications', breadcrumb: 'Notifications' }, + { path: '/mentor/settings', breadcrumb: 'Settings' }, +]; + +export default function Breadcrumbs(){ + const breadcrumbs = useBreadcrumbs(breadcrumbRoutes); + const location = useLocation(); + + const rootPaths = ['/intern', '/mentor']; + const isOnRootPath = rootPaths.includes(location.pathname); + const fixbreadcumbs = breadcrumbs.filter(({ match }) => { + if (match.pathname === '/') return false; + if (rootPaths.includes(match.pathname) && !isOnRootPath) return false; + return true; + }); + return ( + + ); +}; \ No newline at end of file diff --git a/src/Pages/internify/NotificationList.tsx b/src/Pages/internify/NotificationList.tsx index b886540..2941442 100644 --- a/src/Pages/internify/NotificationList.tsx +++ b/src/Pages/internify/NotificationList.tsx @@ -134,7 +134,7 @@ export default function NotificationList() {
diff --git a/src/Pages/internify/intern/components/CertificateAvailabe.tsx b/src/Pages/internify/intern/components/CertificateAvailabe.tsx index 2675a91..86d0e80 100644 --- a/src/Pages/internify/intern/components/CertificateAvailabe.tsx +++ b/src/Pages/internify/intern/components/CertificateAvailabe.tsx @@ -82,6 +82,23 @@ export default function CertificateAvailable({ project, certificate, templateUrl } }; + const handleShareLinkedIn = () => { + const verifyUrl = `${window.location.origin}/verify-certificate/${certificate.uuid}`; + // Gunakan "Add Certification to Profile" LinkedIn — tidak butuh API key/approval + const params = new URLSearchParams({ + startTask: "CERTIFICATION_NAME", + name: certificate.project.project_name, + organizationName: "Internify", + certUrl: verifyUrl, + certId: certificate.certificate_no, + }); + window.open( + `https://www.linkedin.com/profile/add?${params.toString()}`, + "_blank", + "width=600,height=600,noopener,noreferrer" + ); + }; + return (
{/* Certificate Card */} @@ -139,8 +156,15 @@ export default function CertificateAvailable({ project, certificate, templateUrl {/* Bagikan ke LinkedIn */} - {/* Cetak & Salin Link */} diff --git a/src/Pages/internify/mentor/CertificateForm.tsx b/src/Pages/internify/mentor/CertificateForm.tsx index 701c3f5..6da738f 100644 --- a/src/Pages/internify/mentor/CertificateForm.tsx +++ b/src/Pages/internify/mentor/CertificateForm.tsx @@ -11,7 +11,7 @@ import { customToast } from "../../utils/showToast"; export default function CertificateDetail() { const navigate = useNavigate(); const { slug } = useParams<{ slug: string }>(); - const { project, loading, error } = useProjectDetail(slug ?? ""); + const { project, loading, error, refetch } = useProjectDetail(slug ?? ""); const { upload, loading: uploading, error: uploadError } = useUploadCertificateTemplate(); const { data: internProgress, loading: progressLoading } = useProjectInternProgress(slug ?? ""); const eligibleCount = internProgress.filter(i => i.is_eligible).length; @@ -45,11 +45,12 @@ export default function CertificateDetail() { description: err instanceof Error ? err.message : "Terjadi kesalahan saat upload.", }), } - ).then((result) => { + ).then(async(result) => { if (result) { setUploadSuccess(true); setSelectedFile(null); if (fileInputRef.current) fileInputRef.current.value = ""; + await refetch(); } }).catch(() => {}); }; @@ -143,7 +144,6 @@ export default function CertificateDetail() { )}
- {/* Tombol Clear */} {selectedFile && (
diff --git a/src/Pages/internify/mentor/components/EligibleInternTable.tsx b/src/Pages/internify/mentor/components/EligibleInternTable.tsx index b10b101..6dc11f1 100644 --- a/src/Pages/internify/mentor/components/EligibleInternTable.tsx +++ b/src/Pages/internify/mentor/components/EligibleInternTable.tsx @@ -12,19 +12,19 @@ import { Loader2 } from "lucide-react"; import type { InternSubmissionProgress } from "../../../../hooks/useInternProgress"; -import { useProjectDetail } from "../../../../hooks/useProjects"; import JSZip from "jszip"; import { generateCertificate, resolveImageUrl } from "../../../utils/SertificateGenerator"; import { useGenerateCertificates } from "../../../../hooks/useCertificates"; import Avatar from "../../Avatar"; import ProgressBar from "../../ProgressBar"; import { customToast } from "../../../utils/showToast"; +import type { Project } from "../../../../types/project.types"; interface EligibleInternTableProps { interns: InternSubmissionProgress[]; loading: boolean; eligibleCount: number; - projectId: number + project: Project } const PER_PAGE = 5; @@ -45,11 +45,11 @@ export default function EligibleInternTable({ interns, loading, eligibleCount, - projectId + project // Durasi tanggal Proyek }: EligibleInternTableProps) { const navigate = useNavigate(); - const { project } = useProjectDetail(String(projectId)); + // const { project } = useProjectDetail(String(projectId)); const { generate: SubmitCertificates } = useGenerateCertificates(); const initialSelected = new Set( @@ -93,7 +93,7 @@ export default function EligibleInternTable({ const ok = await customToast.promise( (async () => { - const result = await SubmitCertificates(projectId, Array.from(selected)); + const result = await SubmitCertificates(project.id, Array.from(selected)); if (!result) throw new Error("Tidak ada data sertifikat yang dikembalikan."); const zip = new JSZip(); @@ -128,7 +128,7 @@ export default function EligibleInternTable({ ).then(() => true).catch(() => false); setGenerating(false); - if (ok) navigate(`/mentor/certificates/${projectId}/result`); + if (ok) navigate(`/mentor/certificates/${project.id}/result`); }; if (loading) { diff --git a/src/types/project.types.ts b/src/types/project.types.ts index dc90821..1712245 100644 --- a/src/types/project.types.ts +++ b/src/types/project.types.ts @@ -28,6 +28,7 @@ export interface Project { total_tasks: number; created_at: string; updated_at: string; + certificate_template: string | null; } // Detail project (includes members array)