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:
@@ -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> > <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
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user