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
@@ -0,0 +1,51 @@
import { mockProjects } from '../../../lib/mockData'
import MentorProjectCard from './components/MentorProjectCard'
import { Plus } from 'lucide-react'
import { useNavigate } from 'react-router-dom' // Import useNavigate
const MentorProjectsPage = () => {
const navigate = useNavigate()
const handleJoinProject = () => {
// Navigasi ke halaman tujuan, silakan ganti '/path-tujuan' sesuai rute kamu
navigate('/path-tujuan')
}
return (
<div className="px-14 py-15 bg-gray-50 min-h-screen">
{/* Breadcrumb simulasi sesuai gambar */}
<div className="text-xs text-gray-500 mb-2">
<span className="text-red-600 font-medium">Home</span> &gt; <span>List Projects</span>
</div>
<div className="flex flex-col gap-1 mb-6">
<h1 className="text-4xl font-bold text-gray-900">List Projects</h1>
<p className="text-lg text-gray-500">Create, manage, and monitor ongoing projects and assignments.</p>
</div>
<div className="flex flex-wrap gap-4 items-stretch">
{/* Render List Project */}
{mockProjects.map(project => (
<MentorProjectCard key={project.id} {...project} />
))}
{/* Tombol "Join New Project" dengan Border Putus-putus */}
<button
onClick={handleJoinProject}
className="w-[180px] min-h-[230px] border-2 border-dashed border-gray-400 rounded-xl flex flex-col items-center justify-center gap-3 p-4 bg-white hover:bg-gray-100 hover:border-red-600 transition-colors group"
>
{/* Lingkaran Plus */}
<div className="w-12 h-12 rounded-full border border-red-600 flex items-center justify-center text-red-600 group-hover:bg-red-50 transition-colors">
<Plus size={24} />
</div>
{/* Teks Deskripsi */}
<span className="text-sm font-semibold text-red-600 text-center leading-tight">
Join New Project
</span>
</button>
</div>
</div>
)
}
export default MentorProjectsPage
@@ -1,11 +0,0 @@
import React from 'react'
const HomeMentorPage = () => {
return (
<div>
</div>
)
}
export default HomeMentorPage
+24
View File
@@ -0,0 +1,24 @@
import HomeHeader from '../intern/components/HomeHeader'
import HomeCalendar from '../intern/components/HomeCalendar'
import HomeMentorProject from './components/HomeMentorProject'
const HomeMentorPage = () => {
return (
<div className="flex-1 px-14 py-10 bg-gray-50">
<HomeHeader />
<div className="flex flex-col gap-5">
<div className="">
<div className="bg-white border border-gray-200 rounded-xl p-6">
<HomeMentorProject />
</div>
</div>
<div className="bg-white rounded-xl">
<HomeCalendar />
</div>
</div>
</div>
)
}
export default HomeMentorPage
@@ -0,0 +1,5 @@
export default function MentorInternPage() {
return(
<div></div>
)
}
@@ -1,11 +0,0 @@
import React from 'react'
const MentorDetailProject = () => {
return (
<div>
</div>
)
}
export default MentorDetailProject
@@ -1,11 +0,0 @@
import React from 'react'
const MentorInternPage = () => {
return (
<div>
</div>
)
}
export default MentorInternPage
@@ -1,11 +0,0 @@
import React from 'react'
const MentorProjectsPage = () => {
return (
<div>
</div>
)
}
export default MentorProjectsPage
@@ -0,0 +1,26 @@
import type { Project } from '../../../lib/mockData';
import ProjectTimeline from '../intern/components/ProjectTimeline';
import HeroProject from '../intern/components/Hero-project';
import MentorTaskCard from './components/MentorTaskCard';
export default function MentorForumTab({ project }: { project: Project }) {
return (
<div>
{/* Hero banner */}
<HeroProject title={project.name} description={project.description} />
{/* Timeline + Task list */}
<div className="flex gap-7 items-start">
<div className="w-1/5 bg-white rounded-xl p-5 shadow-md shrink-0">
<ProjectTimeline tasks={project.tasks} />
</div>
<div className="w-4/5">
{project.tasks.map((task, idx) => (
<MentorTaskCard key={idx} task={task} />
))}
</div>
</div>
</div>
);
}
@@ -0,0 +1,45 @@
import type { Project, Intern } from '../../../lib/mockData';
export default function InternsTab({ project }: { project: Project }) {
return (
<div>
{/* Mentor */}
<div className="mb-8">
<h3 className=" font-bold text-base text-[#1a1a1a] mb-3.5">
Mentor
</h3>
<div className="border-b border-[#eee] pb-4">
<div className="flex items-center gap-3">
<span className=" text-sm text-[#333]">
{project.mentor}
</span>
</div>
</div>
</div>
{/* Members */}
<div>
<h3 className="font-bold text-base text-[#1a1a1a] mb-3.5">
Members
</h3>
{project.interns.map((intern: Intern, idx: number) => (
<div
key={idx}
className={`flex items-center justify-between py-3 ${
idx < project.interns.length - 1 ? 'border-b border-[#f0f0f0]' : ''
}`}
>
<div className="flex items-center gap-3">
<span className=" text-sm text-[#333]">
{intern.email}
</span>
</div>
<span className=" text-[13px] text-[#555] font-semibold">
{intern.role}
</span>
</div>
))}
</div>
</div>
);
}
@@ -0,0 +1,83 @@
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { mockProjects } from '../../../lib/mockData';
import { ChevronLeft } from 'lucide-react';
import MentorForumTab from './ProjectForumTab';
import InternsTab from './ProjectsInternTab';
import TaskTab from './TaskTab';
// ─── ProjectDetailPage ─────────────────────────────────────────────────────────
export default function ProjectDetailPage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState<'forum' | 'participants' | 'Task'>('forum');
const handleBack = () => {
if (window.history.length > 1) {
navigate(-1);
} else {
navigate('/intern/projects');
}
};
const project = mockProjects.find((p) => p.id === Number(id));
if (!project) {
return (
<div className="p-10 text-[#888]">
Project tidak ditemukan.
</div>
);
}
const tabClass = (tab: string) =>
`pr-0 py-2.5 mr-7 text-sm bg-transparent border-0 cursor-pointer transition-colors duration-150 border-b-2 ${activeTab === tab
? 'font-bold text-[#C0392B] border-[#C0392B]'
: 'font-medium text-[#666] border-transparent'
}`;
return (
<div className="bg-gray-50 min-h-screen">
<div className="px-24 py-10">
{/* Back Button */}
<button
onClick={handleBack}
className="flex items-center gap-2 mb-6 text-sm font-medium text-[#666] hover:text-[#C0392B] transition-colors duration-150 group"
>
<span className='flex flex-row gap-2'>
<ChevronLeft width={20} height={20} className="transition-transform duration-150 group-hover:-translate-x-1" />
Kembali ke Daftar Project
</span>
</button>
{/* Tabs */}
<div className="flex mb-4 border-b-1 border-gray-300">
<button className={tabClass('forum')} onClick={() => setActiveTab('forum')}>
Forum
</button>
<button className={tabClass('participants')} onClick={() => setActiveTab('participants')}>
Participants
</button>
<button className={tabClass('Task')} onClick={() => setActiveTab('Task')}>
Tasks
</button>
</div>
{/* Tab content */}
{(() => {
switch (activeTab) {
case 'forum':
return <MentorForumTab project={project} />;
case 'participants':
return <InternsTab project={project} />;
case 'Task':
return <TaskTab project={project} />; // Tambahkan komponen TaskTab di sini
default:
return null;
}
})()}
</div>
</div>
);
}
+79
View File
@@ -0,0 +1,79 @@
import type { Project, Task } from '../../../lib/mockData';
import { ClipboardList, Pencil, Trash2 } from 'lucide-react';
export default function TaskTab({ project }: { project: Project }) {
const handleAddTask = () => {
console.log('Trigger modal / halaman tambah tugas');
};
const handleEditTask = (taskTitle: string) => {
console.log('Edit task:', taskTitle);
};
const handleDeleteTask = (taskTitle: string) => {
console.log('Delete task:', taskTitle);
};
return (
<div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 min-h-[400px]">
{/* Header Section */}
<div className="flex justify-between items-center mb-6">
<h2 className="text-3xl font-bold text-slate-800 tracking-tight">Tasks</h2>
<button
onClick={handleAddTask}
className="bg-[#C0392B] hover:bg-[#A93226] text-white px-6 py-2.5 rounded-full font-bold text-sm transition-colors shadow-sm"
>
Add Task
</button>
</div>
<hr className="border-gray-200/80 mb-4" />
{/* Task List Section */}
<div className="flex flex-col">
{project.tasks && project.tasks.length > 0 ? (
project.tasks.map((task: Task, idx: number) => (
<div
key={idx}
className={`flex items-center justify-between py-4 group ${
idx < project.tasks.length - 1 ? 'border-b border-gray-100' : ''
}`}
>
{/* Task Title & Icon */}
<div className="flex items-center gap-3 min-w-0">
<ClipboardList size={22} className="text-slate-600 shrink-0" />
<span className="text-base text-slate-700 font-medium truncate">
{task.title}
</span>
</div>
{/* Action Buttons (Edit & Delete) */}
<div className="flex items-center gap-4 text-slate-600 shrink-0">
<button
onClick={() => handleEditTask(task.title)}
className="hover:text-blue-600 transition-colors p-1"
title="Edit Task"
>
<Pencil size={20} />
</button>
<button
onClick={() => handleDeleteTask(task.title)}
className="hover:text-red-600 transition-colors p-1"
title="Delete Task"
>
<Trash2 size={20} />
</button>
</div>
</div>
))
) : (
<div className="text-center py-10 text-gray-400 text-sm">
Belum ada tugas yang dibuat untuk proyek ini.
</div>
)}
</div>
</div>
);
}
@@ -0,0 +1,25 @@
import { Bell } from "lucide-react"
import humiclogo from "../../../../assets/logo.png";
const Header = () => {
return (
<header className='sticky top-0 flex w-full justify-between items-center py-4 px-12 bg-white shadow-md z-10'>
<div className=''>
<img
src={humiclogo}
alt="Humic Logo"
className="w-[130px] cursor-pointer"
/>
</div>
<div className="flex flex-row gap-4 items-center">
<Bell className="w-6 h-6 hover:text-red-800"/>
<div className="bg-blue-500 rounded-full w-8 h-8">
</div>
</div>
</header>
)
}
export default Header
@@ -0,0 +1,39 @@
import { useNavigate } from 'react-router-dom'
import { mockProjects } from '../../../../lib/mockData'
import { Plus } from 'lucide-react';
const HomeMentorProject = () => {
const navigate = useNavigate();
return (
<div className='flex flex-col'>
<div className='flex flex-row justify-between mb-3 px-5 items-center'>
<h2 className="text-lg font-semibold text-gray-800 mb-4">Projects</h2>
<button className='bg-gray-300 px-2 py-1 rounded-xl font-bold hover:bg-gray-400'>
<span className='text-sm flex flex-row items-center gap-3'>
<Plus className='w-3 h-3'/>
Add
</span>
</button>
</div>
<div className="flex flex-col gap-3">
{mockProjects.map((project, index) => {
return (
<div
key={index}
onClick={() => navigate(`/intern/projects/${project.id}`)}
className="flex items-center gap-4 border border-gray-200 rounded-lg px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors duration-150"
>
<div className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 bg-gray-300`}>
</div>
<div>
<p className="text-sm font-semibold text-gray-800">{project.name}</p>
</div>
</div>
)
})}
</div>
</div>
)
}
export default HomeMentorProject
@@ -0,0 +1,63 @@
import { Trash2, UsersRound } from 'lucide-react'
import type { Intern } from '../../../../lib/mockData'
import { useNavigate } from 'react-router-dom'
interface ProjectCardProps {
id: number | string
name: string
interns: Intern[]
thumbnailUrl?: string
}
export default function MentorProjectCard({
id,
name,
interns,
thumbnailUrl
}: ProjectCardProps) {
const navigate = useNavigate();
const handleCardClick = () => {
navigate(`/admin/projects/${id}`)
};
const handleDeleteClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
console.log('Delete project dengan id:', id)
}
return (
<div
onClick={handleCardClick}
className="flex flex-col bg-white rounded-xl overflow-hidden shadow-sm border border-gray-100 w-[180px] min-h-[270px]hover:bg-gray-50 transition-colors">
{/* Thumbnail */}
<div className="h-[150px] bg-gradient-to-tr from-red-700 to-red-800 overflow-hidden shrink-0">
{thumbnailUrl && (
<img src={thumbnailUrl} alt={name} className="w-full h-full object-cover" />
)}
</div>
{/* Content*/}
<div className="p-3 flex flex-col gap-2 flex-1 justify-between">
<div className="flex flex-col gap-1">
<p className="text-sm font-semibold text-gray-800 leading-tight line-clamp-2">
{name}
</p>
</div>
{/* Interns Count */}
<div className="flex items-center gap-2 mt-auto">
<UsersRound className='text-gray-400 w-4 h-4' />
<span className="text-xs text-gray-500 truncate">
{interns.length}/{interns.length}
</span>
</div>
</div>
{/* Footer */}
<div className="border-t border-gray-300 text-gray-400 flex justify-end p-2 bg-gray-50/50">
<button onClick={handleDeleteClick} className="hover:text-red-600 transition-colors">
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
)
}
@@ -0,0 +1,34 @@
import type { Task } from "../../../../lib/mockData";
const formatDate = (date: Date) =>
new Date(date).toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric' });
export default function MentorTaskCard({ task }: { task: Task }) {
return (
<div className="border border-[#eee] rounded-[10px] px-5 py-4 mb-3 bg-white shadow-[0_1px_4px_rgba(0,0,0,0.05)] flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<div className=" text-[11px] text-[#888] mb-1">
Deadline : {formatDate(task.deadline.date)} at {task.deadline.time}
</div>
<div className=" font-bold text-[15px] text-[#1a1a1a] mb-1">
{task.title}
</div>
<div className=" text-[13px] text-[#666]">
{task.description}
</div>
</div>
<div className="shrink-0">
<span
className={`inline-block font-bold text-xs px-2.5 py-[3px] rounded-md tracking-[0.5px] border ${
task.isSubmitted
? "bg-[#27AE60] text-white border-[#27AE60]"
: "bg-white text-[#1a1a1a] border-gray-400"
}`}
>
{task.submittedCount}/{task.totalInterns}
</span>
</div>
</div>
);
}