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:
@@ -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 ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user