feat: Refactor mentor components and implement project management features

- Removed unused imports and interfaces in HomeProjectList component.
- Added ArrowDownNarrowWide icon to ProjectTimeline component for visual enhancement.
- Updated layout imports to reflect new component structure.
- Created BaseProjectsPage for mentor to manage projects with navigation.
- Introduced MentorCertificatePage as a placeholder for future certificate functionality.
- Deleted obsolete HomeMentorPage and replaced it with HomePage for mentor dashboard.
- Added InternsPage and updated MentorInternPage for better intern management.
- Implemented ProjectForumTab and ProjectsInternTab for project details and intern listings.
- Developed TaskTab for task management within projects.
- Created SidebarMentor for navigation in mentor dashboard.
- Enhanced login functionality with user role-based navigation.
- Updated mockData to include task submission counts and certificate data structure.
- Added new assets for certificate representation.
This commit is contained in:
Kemas M. Aryadary Rasyad
2026-06-05 14:33:03 +07:00
parent 8146545779
commit e59e63a21e
38 changed files with 1408 additions and 331 deletions
@@ -1,7 +1,7 @@
import HomeHeader from "./components/HomeHeader";
import HomeProjectList from "./components/HomeProjectList";
import HomeCalendar from "./components/HomeCalendar";
import TaskTimeline from "./components/TaskTImeline";
import TaskTimeline from "./components/TaskTimeline";
// PR: tambahin breadcrumb
export default function HomeInternPage() {
@@ -1,8 +1,8 @@
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { mockProjects } from '../../../lib/mockData';
import ForumTab from './ProjectForum';
import ParticipantsTab from './ProjectParticipants';
import ForumTab from './ForumTab';
import ParticipantsTab from './ParticipantTab';
import { ChevronLeft } from 'lucide-react';
// ─── ProjectDetailPage ─────────────────────────────────────────────────────────
+22 -7
View File
@@ -1,18 +1,33 @@
import { certificateData } from "../../../lib/mockData";
import CertificateAvailable from "./components/CertificateAvailabe";
import CertificateNotAvailable from "./components/CertificateNotAvailable";
const SertificatePage = () => {
const data = certificateData; // ganti isReceived di object atas
return (
<div className="px-15 py-10">
<div className="px-15 py-10 bg-white min-h-screen">
{/* Header */}
<div className="mb-6 flex flex-col gap-1">
<h1 className="text-4xl font-bold mb-2">Certificates</h1>
<p className="font-light text-gray-500">View and download internship certificates</p>
</div>
<div>
<div>
</div>
{/* Content */}
<div>
{data.isReceived ? (
<CertificateAvailable data={data} />
) : (
<CertificateNotAvailable
progress={data.projectProgress}
remainingTasks={data.remainingTasks}
/>
)}
</div>
</div>
)
}
);
};
export default SertificatePage
@@ -0,0 +1,142 @@
import { useState } from "react";
import { Download, History , Link, Award, Printer } from "lucide-react";
import { type CertificateData, type CertificateHistory } from "../../../../lib/mockData";
import certificateImg from "../../../../assets/certificate.png";
export default function CertificateAvailable({ data }: { data: CertificateData }) {
const { certificate, history } = data;
const [copied, setCopied] = useState(false);
const handleCopyLink = () => {
navigator.clipboard.writeText(window.location.href);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="flex flex-col lg:flex-row gap-6 items-start">
{/* LEFT: Certificate Preview + Actions */}
<div className="w-full lg:w-4/6 flex flex-col gap-4">
{/* Certificate Card */}
<div className="flex rounded-xl p-10 mb-3 border border-gray-300 shadow-md items-center justify-center">
<img
src={certificateImg}
alt="Certificate"
className="rounded-xl w-[550px] h-[350px]"
/>
</div>
{/* Program Summary */}
<div className="grid grid-cols-2 gap-5">
<div className="bg-white rounded-2xl border border-gray-200 shadow-sm p-5">
<div className="flex items-center gap-2 mb-4">
<h3 className="font-semibold text-gray-800 text-sm">Ringkasan Program</h3>
</div>
<div className="space-y-3">
{[
{ label: "Nama Program", value: certificate.programName },
{ label: "Durasi", value: certificate.duration },
{
label: "Nilai Akhir",
value: (
<span className="text-red-600 font-bold">
{certificate.finalScore}/100 ({certificate.grade})
</span>
),
},
{
label: "Status",
value: (
<span className="bg-green-100 text-green-700 text-xs font-bold px-2 py-0.5 rounded-full">
{certificate.status}
</span>
),
},
].map((row) => (
<div
key={row.label}
className="flex justify-between items-center text-sm border-b border-gray-50 pb-2 last:border-0"
>
<span className="text-gray-500">{row.label}</span>
<span className="text-gray-800 font-medium">{row.value}</span>
</div>
))}
</div>
</div>
<div className="bg-white rounded-2xl border border-gray-200 shadow-sm p-5">
<div className="flex flex-col gap-3">
{/* Unduh Sertifikat */}
<button className="flex items-center justify-center gap-2 w-full bg-red-700 hover:bg-red-800 active:scale-95 transition-all text-white font-semibold py-3.5 rounded-xl text-sm">
<Download className="w-4 h-4" />
Unduh Sertifikat (PDF)
</button>
{/* Bagikan ke LinkedIn */}
<button className="flex items-center justify-center gap-2 w-full bg-blue-600 hover:bg-blue-700 active:scale-95 transition-all text-white font-semibold py-3.5 rounded-xl text-sm">
Bagikan ke LinkedIn
</button>
{/* Cetak & Salin Link */}
<div className="grid grid-cols-2 gap-3">
<button className="flex flex-col items-center justify-center gap-1.5 border border-gray-200 hover:bg-gray-50 active:scale-95 transition-all text-red-700 font-medium py-3.5 rounded-xl text-sm">
<Printer className="w-5 h-5" />
Cetak
</button>
<button
onClick={handleCopyLink}
className="flex flex-col items-center justify-center gap-1.5 border border-gray-200 hover:bg-gray-50 active:scale-95 transition-all text-red-700 font-medium py-3.5 rounded-xl text-sm"
>
<Link className="w-5 h-5" />
{copied ? "Copied!" : "Salin Link"}
</button>
</div>
</div>
</div>
</div>
</div>
{/* RIGHT: Certificate History */}
<div className="w-full lg:w-2/6 flex-shrink-0">
<div className="bg-white rounded-2xl border border-gray-200 shadow-sm p-5">
<div className="flex items-center gap-2 mb-4">
<History className="w-5 h-5 text-red-700" />
<h3 className="font-semibold text-gray-800 text-lg">Certificate History</h3>
</div>
<div className="space-y-3">
{history.map((item: CertificateHistory) => (
<div
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"
>
<div className="w-full flex justify-center">
<img
src={certificateImg}
alt="Certificate"
className="rounded-xl h-[200px]"
/>
</div>
<div className="w-full">
<p className="text-xs">{item.month}</p>
<p className="text-sm font-semibold text-gray-800 leading-tight">{item.title}</p>
<p className="text-sm text-gray-500">
Grade: {item.grade}
</p>
</div>
</div>
))}
</div>
<div className="w-full my-3 flex justify-end">
<button className="text-sm text-red-500 font-semibold hover:underline">
See more
</button>
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,43 @@
import { Lock, Clock } from "lucide-react";
export default function CertificateNotAvailable({ progress, remainingTasks }: { progress: number; remainingTasks: number }) {
return (
<div className="bg-white w-full rounded-2xl border border-gray-100 shadow-sm p-20 flex flex-col items-center text-center max-w-3/5 mx-auto z-10">
{/* Icon */}
<div className="relative mb-6">
<div className=" w-33 h-33 border-2 border-dashed border-gray-600 rounded-xl flex justify-center items-center">
<div className="w-20 h-20 flex items-center rounded-lg justify-center bg-gray-300">
<Lock className="w-10 h-10 text-red-700" />
</div>
<div className="absolute -bottom-2 -right-2 w-6 h-6 bg-amber-500 rounded- flex items-center justify-center shadow-md">
<Clock className="w-4 h-4 text-yellow-800" />
</div>
</div>
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-3">Sertifikat Belum Tersedia</h2>
<p className="text-gray-500 text-sm leading-relaxed mb-6">
Anda harus menyelesaikan semua tugas proyek sebelum dapat mengunduh sertifikat kelulusan.
Silakan periksa kembali progres tugas Anda di menu Proyek.
</p>
{/* Progress Bar */}
<div className="w-full">
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-semibold text-gray-700">Progres Proyek</span>
<span className="text-sm font-bold text-red-700">{progress}%</span>
</div>
<div className="w-full h-2.5 bg-gray-100 rounded-full overflow-hidden">
<div
className="h-full bg-red-700 rounded-full transition-all duration-700"
style={{ width: `${progress}%` }}
/>
</div>
<p className="text-xs text-gray-400 mt-2 text-left">
Selesaikan {remainingTasks} tugas lagi untuk membuka akses.
</p>
</div>
</div>
);
}
@@ -1,79 +0,0 @@
import { Briefcase, Globe, Users } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
const footerLinks = {
Internship: [
{ label: "Beranda", path: "/" },
{ label: "Tentang", path: "/tentang" },
{ label: "Life At Humic", path: "/life" },
],
"Internify Logo": [
{ label: "Humic Website", path: "/humic" },
{ label: "Our Activity", path: "/activity" },
{ label: "Life At Humic", path: "/life-humic" },
{ label: "Our Developer", path: "/developer" },
],
};
const socials = [
{ label: "Website", icon: Globe, href: "https://placeholder.com" },
{ label: "Team", icon: Users, href: "https://placeholder.com" },
{ label: "Work", icon: Briefcase, href: "https://placeholder.com" },
];
export function FooterIntern() {
const navigate = useNavigate();
return (
<footer className="bg-white border-t border-gray-100 px-14 py-8 flex gap-12 flex-wrap">
{/* Brand info */}
<div className="flex flex-col gap-2 flex-shrink-0 w-52">
<p className="text-sm font-bold text-gray-900">Internify Logo</p>
<address className="text-[12.5px] text-gray-400 leading-relaxed not-italic">
Gedung Kultubai Selatan, Blok F<br />
Jl. Telekomunikasi, Terusan Buah Batu Bandung<br />
Jawa Barat, Indonesia. 40257
</address>
<div className="flex gap-2 mt-1">
{socials.map(({ label, icon: Icon, href }) => (
<a
key={label}
href={href}
target="_blank"
rel="noopener noreferrer"
aria-label={label}
className="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center text-gray-400 hover:bg-red-50 hover:text-red-600 transition-colors"
>
<Icon className="w-4 h-4" />
</a>
))}
</div>
</div>
{/* Link columns */}
<div className="flex gap-12 flex-1 justify-end flex-wrap">
{Object.entries(footerLinks).map(([title, links]) => (
<div key={title} className="flex flex-col gap-2.5 min-w-[110px]">
<p className="text-[13px] font-bold text-gray-900">{title}</p>
<ul className="flex flex-col gap-2">
{links.map(({ label, path }) => (
<li key={label}>
<button
onClick={() => navigate(path)}
className="text-[12.5px] text-gray-400 hover:text-red-600 transition-colors text-left bg-transparent border-none p-0 cursor-pointer"
>
{label}
</button>
</li>
))}
</ul>
</div>
))}
</div>
</footer>
);
}
export default FooterIntern
@@ -83,7 +83,7 @@ const HomeCalendar = () => {
}
return (
<div className=" px-13 py-5">
<div className=" px-13 py-5 border border-gray-200 rounded-2xl">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<h2 className="text-base font-semibold text-gray-800">
@@ -1,15 +1,6 @@
import { Code2, ChessKing, Beef } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { mockProjects } from '../../../../lib/mockData'
interface Project {
name: string
role: string
logo: React.ElementType
iconBg: string
iconColor: string
}
const HomeProjectList = () => {
const navigate = useNavigate();
return (
@@ -1,4 +1,5 @@
import type { Task } from "../../../../lib/mockData";
import { ArrowDownNarrowWide } from "lucide-react";
const formatDate = (date: Date) =>
new Date(date).toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric' });
@@ -9,10 +10,11 @@ export default function ProjectTimeline({ tasks }: { tasks: Task[] }) {
return (
<div>
<div className="flex items-center gap-1.5 mb-4">
<div className="flex items-center gap-1.5 mb-4 justify-between">
<span className="font-semibold text-sm text-[#1a1a1a]">
Timeline
</span>
<ArrowDownNarrowWide className="w-5 h-5 text-gray-500"/>
</div>
<ul className="relative pl-4">