Update all hooks (Projects, Tasks, Certificates), implement backend for Projects. HomePage on Mentors

This commit is contained in:
Kemas M. Aryadary Rasyad
2026-06-26 10:59:35 +07:00
parent 2e162bfc1e
commit 53b270921d
34 changed files with 895 additions and 625 deletions
+22 -10
View File
@@ -1,11 +1,13 @@
import { mockProjects } from '../../../lib/mockProjects';
import MentorProjectCard from './components/MentorProjectCard';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import CreateProjectModal from './components/CreateProjectDialog';
import { useProjects } from '../../../hooks/useProjects';
export default function MentorProjectsPage(){
export default function MentorProjectsPage() {
const [isModalOpen, setIsModalOpen] = useState(false);
const { projects, loading, error } = useProjects("active");
const openDialog = () => {
setIsModalOpen(true);
@@ -15,8 +17,18 @@ export default function MentorProjectsPage(){
setIsModalOpen(false);
};
if (loading) return (
<div className="flex flex-col gap-3">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-16 bg-gray-100 rounded-lg animate-pulse" />
))}
</div>
);
if (error) return <p className="text-danger text-md">{error}</p>;
return (
<div className="">
<div>
<div className="flex justify-between items-center mb-6">
<div className="flex flex-col">
<h1 className="page-title">List Projects</h1>
@@ -24,8 +36,8 @@ export default function MentorProjectsPage(){
</div>
<div>
{/* Tombol pemicu buka modal */}
<button
onClick={openDialog}
<button
onClick={openDialog}
className="flex items-center gap-2 px-4 py-1 bg-[#C0392B] text-white rounded-lg hover:bg-[#A93226] transition-colors duration-150"
>
<Plus className="w-4 h-4" />
@@ -35,15 +47,15 @@ export default function MentorProjectsPage(){
</div>
<div className="flex flex-wrap gap-4 items-stretch">
{mockProjects.map(project => (
<MentorProjectCard key={project.id} {...project} tasks={project.tasks} />
{projects.map(project => (
<MentorProjectCard key={project.id} {...project} />
))}
</div>
{isModalOpen && (
<CreateProjectModal
isOpen={isModalOpen}
onClose={closeDialog}
<CreateProjectModal
isOpen={isModalOpen}
onClose={closeDialog}
/>
)}
</div>
@@ -20,7 +20,6 @@ const initialHistoryData = [
];
const MentorCertificatePage = () => {
// State Form Management
const [selectedIntern, setSelectedIntern] = useState("JonathanKristina@gmail.com");
const [selectedProject, setSelectedProject] = useState("Internify Project (Web Development)");
const [issueDate, setIssueDate] = useState("2024-10-24");
@@ -8,7 +8,7 @@ const HomeMentorPage = () => {
<HomeHeader />
<div className="flex flex-col gap-5">
<div className="">
<div className="bg-white border border-gray-200 rounded-xl p-6">
<div className="bg-white border border-box-border rounded-xl p-6">
<HomeMentorProject />
</div>
+17 -13
View File
@@ -1,27 +1,31 @@
import type { Project } from '../../../lib/mockProjects';
import { useParams } from 'react-router-dom';
import ProjectTimeline from '../intern/components/ProjectTimeline';
import HeroProject from '../intern/components/Hero-project';
import MentorTaskCard from './components/MentorTaskCard';
import { useProjectTasks } from '../../../hooks/useTasks';
import type { Project } from '../../../types/project.types';
export default function MentorForumTab({ project }: { project: Project }) {
const { slug } = useParams<{ slug: string }>();
const { tasks, loading, error } = useProjectTasks(slug ?? "");
return (
<div>
{/* Hero banner */}
<HeroProject title={project.name} description={project.description} />
<HeroProject title={project.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 className="flex flex-col gap-7 md:flex-row md:items-start">
<div className="w-full md:w-1/5 bg-white rounded-xl p-5 border border-box-border shrink-0">
<ProjectTimeline tasks={tasks} />
</div>
<div className="w-4/5">
{project.tasks.map((task, idx) => (
<MentorTaskCard
key={idx}
task={task}
projectName={project.name} />
))}
<div className="w-full border-t border-box-border md:border-t-0 pt-4 md:pt-0 md:w-4/5">
{tasks.length === 0 ? (
<p className="text-sm text-gray-400">Belum ada task.</p>
) : (
tasks.map((task) => (
<MentorTaskCard key={task.id} task={task} />
))
)}
</div>
</div>
</div>
@@ -1,9 +1,9 @@
import { useState } from 'react';
import type { Project, Intern } from '../../../lib/mockProjects';
import { Plus } from 'lucide-react';
import ManageInternsModal from './components/AddInternsDialog';
import type { ProjectDetail, ProjectMember } from '../../../types/project.types';
export default function InternsTab({ project }: { project: Project }) {
export default function InternsTab({ project }: { project: ProjectDetail }) {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
@@ -15,8 +15,17 @@ export default function InternsTab({ project }: { project: Project }) {
</h3>
<div className="border-b border-gray-300 pb-4">
<div className="flex items-center gap-3">
{project.admin.profile_picture ? (
<img
src={project.admin.profile_picture}
alt={project.admin.full_name}
className="w-8 h-8 rounded-full object-cover"
/>
) : (
<div className="w-8 h-8 rounded-full bg-gray-200" />
)}
<span className=" text-sm text-[#333]">
{project.mentor}
{project.admin.full_name}
</span>
</div>
</div>
@@ -37,19 +46,19 @@ export default function InternsTab({ project }: { project: Project }) {
</span>
</button>
</div>
{project.interns.map((intern: Intern, idx: number) => (
{project.members.map((member: ProjectMember, idx) => (
<div
key={idx}
className={`flex items-center justify-between py-3 ${idx < project.interns.length - 1 ? 'border-b border-gray-300' : ''
className={`flex items-center justify-between py-3 ${idx < project.members.length - 1 ? 'border-b border-gray-300' : ''
}`}
>
<div className="flex items-center gap-3">
<span className=" text-sm text-[#333]">
{intern.name}
{member.full_name}
</span>
</div>
<span className=" text-[13px] text-[#555] font-semibold">
{intern.role}
{member.position}
</span>
<div>
<button className='flex items-center gap-1 text-[14px] bg-red-600 text-white p-1 rounded-xl'>
@@ -61,7 +70,9 @@ export default function InternsTab({ project }: { project: Project }) {
</div>
{isModalOpen && <ManageInternsModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
onClose={() => setIsModalOpen(false)}
projectId={project.id}
initialMembers={project.members}
/>}
</div>
);
+5 -4
View File
@@ -1,17 +1,18 @@
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { mockProjects } from '../../../lib/mockProjects';
import { useProjectDetail } from '../../../hooks/useProjects';
import MentorForumTab from './ProjectForumTab';
import InternsTab from './ProjectsInternTab';
import TaskTab from './TaskTab';
const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-');
export default function ProjectDetailPage() {
const { slug } = useParams<{ slug: string }>();
const [activeTab, setActiveTab] = useState<'forum' | 'participants' | 'Task'>('forum');
const { project, loading, error } = useProjectDetail(slug ?? "");
const project = mockProjects.find((p) => toSlug(p.name) === slug);
if (loading) return <p className="p-10 text-gray-400">Memuat project...</p>;
if (error) return <p className="p-10 text-red-500">{error}</p>;
if (!project) return <p className="p-10 text-gray-400">Project tidak ditemukan.</p>
if (!project) {
return (
+37 -32
View File
@@ -1,28 +1,36 @@
import type { Project, Task } from '../../../lib/mockData';
import { useProjectTasks, useCreateTask, useDeleteTask } from '../../../hooks/useTasks';
import type { ProjectDetail } from '../../../types/project.types';
import type { ProjectTask } from '../../../types/task.types';
import { ClipboardList, Pencil, Trash2 } from 'lucide-react';
import { useState } from 'react';
import CreateTaskModal from './components/CreateTaskDialog';
import EditTaskModal from './components/EditTaskDialog';
export default function TaskTab({ project }: { project: Project }) {
export default function TaskTab({ project }: { project: ProjectDetail }) {
const { tasks, loading, error, refetch } = useProjectTasks(String(project.id));
const { remove } = useDeleteTask();
const [isModalOpen, setIsModalOpen] = useState(false);
const [editModalOpen, setEditModalOpen] = useState(false);
const [selectedTask, setSelectedTask] = useState<ProjectTask | null>(null);
const handleEditTask = (taskTitle: string) => {
console.log('Edit task:', taskTitle);
const handleDeleteTask = async (taskId: number) => {
const ok = await remove(taskId, String(project.id));
if (ok) refetch();
};
const handleDeleteTask = (taskTitle: string) => {
console.log('Delete task:', taskTitle);
};
if (loading) return (
<div className="flex flex-col gap-3 p-6">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-12 bg-gray-100 rounded-lg animate-pulse" />
))}
</div>
);
if (error) return <p className="text-red-500 text-sm p-6">{error}</p>;
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={() => setIsModalOpen(true)}
className="bg-[#C0392B] hover:bg-[#A93226] text-white px-6 py-2.5 rounded-full font-bold text-sm transition-colors shadow-sm"
@@ -30,39 +38,31 @@ export default function TaskTab({ project }: { project: Project }) {
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) => (
{tasks.length > 0 ? (
tasks.map((task, idx) => (
<div
key={idx}
className={`flex items-center justify-between py-4 group ${idx < project.tasks.length - 1 ? 'border-b border-gray-100' : ''
}`}
key={task.id}
className={`flex items-center justify-between py-4 group ${
idx < 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>
<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={() => setEditModalOpen(true)}
onClick={() => { setSelectedTask(task); setEditModalOpen(true); }}
className="hover:text-blue-600 transition-colors p-1"
title="Edit Task"
>
<Pencil size={20} />
</button>
<button
onClick={() => handleDeleteTask(task.title)}
onClick={() => handleDeleteTask(task.id)}
className="hover:text-red-600 transition-colors p-1"
title="Delete Task"
>
<Trash2 size={20} />
</button>
@@ -75,18 +75,23 @@ export default function TaskTab({ project }: { project: Project }) {
</div>
)}
</div>
{isModalOpen &&
{isModalOpen && (
<CreateTaskModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
projectId={String(project.id)}
onSuccess={() => { setIsModalOpen(false); refetch(); }}
/>
}
{editModalOpen &&
)}
{editModalOpen && selectedTask && (
<EditTaskModal
isOpen={editModalOpen}
onClose={() => setEditModalOpen(false)}
onClose={() => { setEditModalOpen(false); setSelectedTask(null); }}
task={selectedTask}
onSuccess={() => { setEditModalOpen(false); setSelectedTask(null); refetch(); }}
/>
}
)}
</div>
);
}
@@ -1,28 +1,34 @@
// ManageInternsModal.tsx
import { useRef, useEffect, useState } from "react";
import { X, Search, Plus, Trash2 } from "lucide-react";
import { Search, Plus, Trash2 } from "lucide-react";
import { useMahasiswa } from "../../../../hooks/useInterns";
import { useAssignMember } from '../../../../hooks/useProjects';
import type { ProjectMember } from '../../../../types/project.types';
import type { Mahasiswa } from '../../../../types/project.types';
interface ManageInternsModalProps {
isOpen: boolean;
onClose: () => void;
projectId: number;
initialMembers: ProjectMember[];
}
export default function ManageInternsModal({ isOpen, onClose }: ManageInternsModalProps) {
export default function ManageInternsModal({ isOpen, onClose, projectId, initialMembers }: ManageInternsModalProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
const [searchQuery, setSearchQuery] = useState("Jonathankristin@gmail.com");
const [searchQuery, setSearchQuery] = useState("");
const [invitedList, setInvitedList] = useState<ProjectMember[]>(initialMembers);
const [invitedList, setInvitedList] = useState([
{ id: 1, email: "Jonathankristina@gmail.com", initials: "JK" }
]);
const { mahasiswa, loading: loadingMahasiswa } = useMahasiswa();
const { assign, loading: assigning } = useAssignMember();
// Data Rekomendasi/Hasil Pencarian (Simulasi list bawah yang ada ikon plus)
const [searchResult, setSearchResult] = useState([
{ id: 2, email: "Jonathankristin@gmail.com", initials: "JK" }
]);
// Sync saat modal dibuka
useEffect(() => {
if (isOpen) setInvitedList(initialMembers);
}, [isOpen, initialMembers]);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (isOpen) {
dialog.showModal();
document.body.style.overflow = "hidden";
@@ -30,12 +36,7 @@ export default function ManageInternsModal({ isOpen, onClose }: ManageInternsMod
dialog.close();
document.body.style.overflow = "unset";
}
const handleCancel = (e: Event) => {
e.preventDefault();
onClose();
};
const handleCancel = (e: Event) => { e.preventDefault(); onClose(); };
dialog.addEventListener("cancel", handleCancel);
return () => {
dialog.removeEventListener("cancel", handleCancel);
@@ -43,38 +44,66 @@ export default function ManageInternsModal({ isOpen, onClose }: ManageInternsMod
};
}, [isOpen, onClose]);
// Filter mahasiswa yang belum jadi member & sesuai search
const invitedIds = new Set(invitedList.map(m => m.id));
const searchResult = mahasiswa.filter(m =>
!invitedIds.has(m.id) &&
(m.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
`${m.nama_depan} ${m.nama_belakang}`.toLowerCase().includes(searchQuery.toLowerCase()))
);
const handleAssign = async (intern: Mahasiswa) => {
const result = await assign({ id_project: projectId, id_user: intern.id });
if (result) {
setInvitedList(prev => [...prev, {
id: intern.id,
id_user: intern.id,
full_name: `${intern.nama_depan} ${intern.nama_belakang}`,
email: intern.email,
avatar: "",
kelompok_peminatan: intern.jurusan,
professional_bio: "",
position: intern.role,
} as ProjectMember]);
}
};
const getInitials = (m: Mahasiswa) =>
`${m.nama_depan[0]}${m.nama_belakang[0]}`.toUpperCase();
return (
<dialog
ref={dialogRef}
className="custom-dialog"
>
<dialog ref={dialogRef} className="custom-dialog">
<div className="px-3 py-5">
{/* Search Input Area */}
<div className="relative flex items-center mb-4">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search by email..."
placeholder="Cari nama atau email..."
className="w-full pl-4 pr-10 py-3 bg-white border border-gray-200 rounded-xl text-sm placeholder-gray-400 text-gray-700 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium shadow-sm"
/>
<Search className="absolute right-3.5 w-4 h-4 text-gray-400" />
</div>
{/* List Container (Scrollable jika data banyak) */}
<div className="overflow-y-auto space-y-3 pr-1 flex-1">
{/* List 1: Intern Yang Sudah Tergabung / Ditambahkan */}
{invitedList.map((intern) => (
<div key={intern.id} className="flex items-center justify-between p-2 rounded-xl hover:bg-gray-50 transition-colors">
{/* Sudah jadi member */}
{invitedList.map((member) => (
<div key={member.id} className="flex items-center justify-between p-2 rounded-xl hover:bg-gray-50 transition-colors">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-full bg-gray-200 border border-gray-300 flex items-center justify-center text-xs font-bold text-gray-500">
{intern.initials}
{member.avatar ? (
<img src={member.avatar} alt={member.full_name} className="w-9 h-9 rounded-full object-cover" />
) : (
<div className="w-9 h-9 rounded-full bg-gray-200 border border-gray-300 flex items-center justify-center text-xs font-bold text-gray-500">
{member.full_name.slice(0, 2).toUpperCase()}
</div>
)}
<div className="flex flex-col">
<span className="text-sm font-medium text-gray-700">{member.full_name}</span>
<span className="text-xs text-gray-400">{member.email}</span>
</div>
<span className="text-sm font-medium text-gray-600">{intern.email}</span>
</div>
<button
onClick={() => setInvitedList(invitedList.filter(i => i.id !== intern.id))}
onClick={() => setInvitedList(prev => prev.filter(i => i.id !== member.id))}
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
>
<Trash2 className="w-4 h-4" />
@@ -82,27 +111,35 @@ export default function ManageInternsModal({ isOpen, onClose }: ManageInternsMod
</div>
))}
{/* List 2: Hasil Pencarian / Belum Ditambahkan */}
{searchResult.map((intern) => (
<div key={intern.id} className="flex items-center justify-between p-2 rounded-xl hover:bg-gray-50 transition-colors">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-full bg-gray-200 border border-gray-300 flex items-center justify-center text-xs font-bold text-gray-500">
{intern.initials}
{/* Hasil pencarian — belum jadi member */}
{loadingMahasiswa ? (
<p className="text-sm text-gray-400 text-center py-2">Memuat...</p>
) : (
searchResult.map((intern) => (
<div key={intern.id} className="flex items-center justify-between p-2 rounded-xl hover:bg-gray-50 transition-colors">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-full bg-gray-200 border border-gray-300 flex items-center justify-center text-xs font-bold text-gray-500">
{getInitials(intern)}
</div>
<div className="flex flex-col">
<span className="text-sm font-medium text-gray-700">{intern.nama_depan} {intern.nama_belakang}</span>
<span className="text-xs text-gray-400">{intern.email}</span>
</div>
</div>
<span className="text-sm font-medium text-gray-600">{intern.email}</span>
<button
onClick={() => handleAssign(intern)}
disabled={assigning}
className="p-2 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-lg transition-colors disabled:opacity-50"
>
<Plus className="w-5 h-5" />
</button>
</div>
<button
onClick={() => {
setInvitedList([...invitedList, intern]);
setSearchResult(searchResult.filter(i => i.id !== intern.id));
}}
className="p-2 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-lg transition-colors"
>
<Plus className="w-5 h-5" />
</button>
</div>
))}
))
)}
{!loadingMahasiswa && searchQuery && searchResult.length === 0 && (
<p className="text-sm text-gray-400 text-center py-2">Tidak ada mahasiswa ditemukan.</p>
)}
</div>
</div>
</dialog>
@@ -1,187 +1,112 @@
import { useRef, useEffect, useState } from "react";
import { X, FileText, Link } from "lucide-react";
import { useCreateTask } from '../../../../hooks/useTasks';
import type { SubmissionType } from '../../../../types/task.types';
interface CreateTaskModalProps {
isOpen: boolean;
onClose: () => void;
projectId: string;
onSuccess: () => void;
}
export default function CreateTaskModal({ isOpen, onClose }: CreateTaskModalProps) {
export default function CreateTaskModal({ isOpen, onClose, projectId, onSuccess }: CreateTaskModalProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
const { create, loading } = useCreateTask(projectId);
// Form States
const [taskTitle, setTaskTitle] = useState("");
const [description, setDescription] = useState("");
const [deadlineDate, setDeadlineDate] = useState("");
const [specificTime, setSpecificTime] = useState("");
const [submissionType, setSubmissionType] = useState<"file" | "url">("file");
const [submissionType, setSubmissionType] = useState<SubmissionType>("file_upload");
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (isOpen) {
dialog.showModal();
document.body.style.overflow = "hidden";
} else {
dialog.close();
document.body.style.overflow = "unset";
}
const handleCancel = (e: Event) => {
e.preventDefault();
onClose();
};
if (isOpen) { dialog.showModal(); document.body.style.overflow = "hidden"; }
else { dialog.close(); document.body.style.overflow = "unset"; }
const handleCancel = (e: Event) => { e.preventDefault(); onClose(); };
dialog.addEventListener("cancel", handleCancel);
return () => {
dialog.removeEventListener("cancel", handleCancel);
document.body.style.overflow = "unset";
};
return () => { dialog.removeEventListener("cancel", handleCancel); document.body.style.overflow = "unset"; };
}, [isOpen, onClose]);
const handleSubmit = (e: React.FormEvent) => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const payload = { taskTitle, description, deadlineDate, specificTime, submissionType };
console.log("Saving Task:", payload);
alert("Task Saved Successfully!");
onClose();
const result = await create({
title: taskTitle,
description,
deadline_date: deadlineDate,
specific_time: specificTime,
submission_type: submissionType,
});
if (result) onSuccess();
};
// JSX sama persis, hanya ganti handleSubmit dan tambah disabled pada button submit:
return (
<dialog
ref={dialogRef}
className="custom-dialog p-[1.5rem]"
>
{/* Header Modal */}
<dialog ref={dialogRef} className="custom-dialog p-[1.5rem]">
<div className="dialog-header">
<div>
<h2 className="text-[26px] font-bold text-[#B30000] tracking-tight">Create New Task</h2>
<p className="text-xs text-gray-400 font-medium mt-0.5">
Fill in the details for the next intern assignment.
</p>
<p className="text-xs text-gray-400 font-medium mt-0.5">Fill in the details for the next intern assignment.</p>
</div>
<button onClick={onClose} className="rounded-lg p-1.5 text-gray-400 hover:bg-gray-50 hover:text-gray-600 transition-colors">
<X className="w-5 h-5 stroke-[2.5]" />
</button>
</div>
{/* Body Form Modal (Scrollable jika layar pendek) */}
<form onSubmit={handleSubmit} className="px-6 space-y-4 overflow-y-auto flex-1">
{/* Task Title */}
<div className="">
<div>
<label className="text-xs font-bold text-gray-700 tracking-wide">Task Title</label>
<input
type="text"
required
placeholder="e.g., Design High-Fidelity Prototypes"
value={taskTitle}
<input type="text" required placeholder="e.g., Design High-Fidelity Prototypes" value={taskTitle}
onChange={(e) => setTaskTitle(e.target.value)}
className="w-full px-3 py-4 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]"
/>
className="w-full px-3 py-4 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
</div>
{/* Description */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Description</label>
<textarea
rows={4}
placeholder="Outline the requirements and objectives..."
value={description}
<textarea rows={4} placeholder="Outline the requirements and objectives..." value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000] resize-none leading-relaxed"
/>
className="w-full px-3 py-2 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000] resize-none leading-relaxed" />
</div>
{/* Deadline Grid (Date & Time) */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Deadline Date</label>
<div className="relative">
<input
type="date"
required
value={deadlineDate}
onChange={(e) => setDeadlineDate(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]"
/>
</div>
<input type="date" required value={deadlineDate} onChange={(e) => setDeadlineDate(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
</div>
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Specific Time (Jam)</label>
<div className="relative">
<input
type="time"
required
value={specificTime}
onChange={(e) => setSpecificTime(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]"
/>
</div>
<label className="text-xs font-bold text-gray-700 tracking-wide">Specific Time</label>
<input type="time" required value={specificTime} onChange={(e) => setSpecificTime(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
</div>
</div>
{/* Submission Type Card Radio */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Submission Type</label>
<div className="grid grid-cols-2 gap-3">
{/* Opsi 1: File Upload */}
<div
onClick={() => setSubmissionType("file")}
className={`flex items-center gap-3 px-4 py-3 rounded-xl border cursor-pointer transition-all ${
submissionType === "file"
? "border-[#B30000] bg-[#B30000] text-white font-semibold"
: "border-gray-200 bg-white text-gray-600 hover:bg-gray-50"
}`}
>
<div className={`w-3.5 h-3.5 rounded-full border flex items-center justify-center ${submissionType === "file" ? "border-white bg-white" : "border-gray-300"}`}>
{submissionType === "file" && <div className="w-1.5 h-1.5 bg-[#B30000] rounded-full" />}
{(["file_upload", "url_link"] as SubmissionType[]).map((type) => (
<div key={type} onClick={() => setSubmissionType(type)}
className={`flex items-center gap-3 px-4 py-3 rounded-xl border cursor-pointer transition-all ${
submissionType === type ? "border-[#B30000] bg-[#B30000] text-white font-semibold" : "border-gray-200 bg-white text-gray-600 hover:bg-gray-50"
}`}>
<div className={`w-3.5 h-3.5 rounded-full border flex items-center justify-center ${submissionType === type ? "border-white bg-white" : "border-gray-300"}`}>
{submissionType === type && <div className="w-1.5 h-1.5 bg-[#B30000] rounded-full" />}
</div>
{type === "file_upload" ? <FileText className="w-4 h-4" /> : <Link className="w-4 h-4" />}
<span className="text-xs">{type === "file_upload" ? "File Upload" : "URL Link"}</span>
</div>
<FileText className="w-4 h-4" />
<span className="text-xs">File Upload</span>
</div>
{/* Opsi 2: URL Link */}
<div
onClick={() => setSubmissionType("url")}
className={`flex items-center gap-3 px-4 py-3 rounded-xl border cursor-pointer transition-all ${
submissionType === "url"
? "border-[#B30000] bg-[#B30000] text-white font-semibold"
: "border-gray-200 bg-white text-gray-600 hover:bg-gray-50"
}`}
>
<div className={`w-3.5 h-3.5 rounded-full border flex items-center justify-center ${submissionType === "url" ? "border-white bg-white" : "border-gray-300"}`}>
{submissionType === "url" && <div className="w-1.5 h-1.5 bg-[#B30000] rounded-full" />}
</div>
<Link className="w-4 h-4" />
<span className="text-xs">URL Link</span>
</div>
))}
</div>
</div>
<div className="h-px bg-gray-100 my-2" />
{/* Action Buttons */}
<div className="flex items-center justify-end gap-3 pt-2">
<button
type="button"
onClick={onClose}
className="p-3 border border-gray-200 rounded-xl text-xs font-semibold text-gray-500 hover:text-gray-700 hover:bg-gray-50 transition-colors"
>
<button type="button" onClick={onClose}
className="p-3 border border-gray-200 rounded-xl text-xs font-semibold text-gray-500 hover:text-gray-700 hover:bg-gray-50 transition-colors">
Cancel
</button>
<button
type="submit"
className="p-3 bg-[#B30000] hover:bg-[#990000] text-white text-xs font-bold rounded-xl transition-colors shadow-md"
>
Save Task
<button type="submit" disabled={loading}
className="p-3 bg-[#B30000] hover:bg-[#990000] text-white text-xs font-bold rounded-xl transition-colors shadow-md disabled:opacity-50">
{loading ? "Saving..." : "Save Task"}
</button>
</div>
</form>
</dialog>
);
@@ -1,187 +1,118 @@
import { useRef, useEffect, useState } from "react";
import { X, FileText, Link } from "lucide-react";
import { useUpdateTask } from '../../../../hooks/useTasks';
import type { ProjectTask, SubmissionType } from '../../../../types/task.types';
interface EditTaskModalProps {
isOpen: boolean;
onClose: () => void;
task: ProjectTask;
onSuccess: () => void;
}
export default function EditTaskModal({ isOpen, onClose }: EditTaskModalProps) {
export default function EditTaskModal({ isOpen, onClose, task, onSuccess }: EditTaskModalProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
const { update, loading } = useUpdateTask();
// Form States
const [taskTitle, setTaskTitle] = useState("");
const [description, setDescription] = useState("");
const [deadlineDate, setDeadlineDate] = useState("");
const [specificTime, setSpecificTime] = useState("");
const [submissionType, setSubmissionType] = useState<"file" | "url">("file");
const [taskTitle, setTaskTitle] = useState(task.title);
const [description, setDescription] = useState(task.description);
const [deadlineDate, setDeadlineDate] = useState(task.deadline_at.split("T")[0]);
const [specificTime, setSpecificTime] = useState(task.deadline_at.split("T")[1]?.slice(0, 5) ?? "");
const [submissionType, setSubmissionType] = useState<SubmissionType>(task.submission_type);
// Reset tiap task berubah
useEffect(() => {
setTaskTitle(task.title);
setDescription(task.description);
setDeadlineDate(task.deadline_at.split("T")[0]);
setSpecificTime(task.deadline_at.split("T")[1]?.slice(0, 5) ?? "");
setSubmissionType(task.submission_type);
}, [task]);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (isOpen) {
dialog.showModal();
document.body.style.overflow = "hidden";
} else {
dialog.close();
document.body.style.overflow = "unset";
}
const handleCancel = (e: Event) => {
e.preventDefault();
onClose();
};
if (isOpen) { dialog.showModal(); document.body.style.overflow = "hidden"; }
else { dialog.close(); document.body.style.overflow = "unset"; }
const handleCancel = (e: Event) => { e.preventDefault(); onClose(); };
dialog.addEventListener("cancel", handleCancel);
return () => {
dialog.removeEventListener("cancel", handleCancel);
document.body.style.overflow = "unset";
};
return () => { dialog.removeEventListener("cancel", handleCancel); document.body.style.overflow = "unset"; };
}, [isOpen, onClose]);
const handleSubmit = (e: React.FormEvent) => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const payload = { taskTitle, description, deadlineDate, specificTime, submissionType };
console.log("Saving Task:", payload);
alert("Task Saved Successfully!");
onClose();
const result = await update(task.id, {
title: taskTitle,
description,
deadline_date: deadlineDate,
specific_time: specificTime,
submission_type: submissionType,
}, String(task.id_project));
if (result) onSuccess();
};
return (
<dialog
ref={dialogRef}
className="custom-dialog p-[1.5rem]"
>
{/* Header Modal */}
<dialog ref={dialogRef} className="custom-dialog p-[1.5rem]">
<div className="dialog-header">
<div>
<h2 className="text-[26px] font-bold text-[#B30000] tracking-tight">Edut Task</h2>
<p className="text-xs text-gray-400 font-medium mt-0.5">
Fill in the details for the next intern assignment.
</p>
<h2 className="text-[26px] font-bold text-[#B30000] tracking-tight">Edit Task</h2>
<p className="text-xs text-gray-400 font-medium mt-0.5">Update the details for this assignment.</p>
</div>
<button onClick={onClose} className="rounded-lg p-1.5 text-gray-400 hover:bg-gray-50 hover:text-gray-600 transition-colors">
<X className="w-5 h-5 stroke-[2.5]" />
</button>
</div>
{/* Body Form Modal (Scrollable jika layar pendek) */}
<form onSubmit={handleSubmit} className="px-6 space-y-4 overflow-y-auto flex-1">
{/* Task Title */}
<div className="">
<div>
<label className="text-xs font-bold text-gray-700 tracking-wide">Task Title</label>
<input
type="text"
required
placeholder="e.g., Design High-Fidelity Prototypes"
value={taskTitle}
onChange={(e) => setTaskTitle(e.target.value)}
className="w-full px-3 py-4 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]"
/>
<input type="text" required value={taskTitle} onChange={(e) => setTaskTitle(e.target.value)}
className="w-full px-3 py-4 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
</div>
{/* Description */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Description</label>
<textarea
rows={4}
placeholder="Outline the requirements and objectives..."
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000] resize-none leading-relaxed"
/>
<textarea rows={4} value={description} onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000] resize-none leading-relaxed" />
</div>
{/* Deadline Grid (Date & Time) */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Deadline Date</label>
<div className="relative">
<input
type="date"
required
value={deadlineDate}
onChange={(e) => setDeadlineDate(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]"
/>
</div>
<input type="date" required value={deadlineDate} onChange={(e) => setDeadlineDate(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
</div>
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Specific Time (Jam)</label>
<div className="relative">
<input
type="time"
required
value={specificTime}
onChange={(e) => setSpecificTime(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]"
/>
</div>
<label className="text-xs font-bold text-gray-700 tracking-wide">Specific Time</label>
<input type="time" required value={specificTime} onChange={(e) => setSpecificTime(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
</div>
</div>
{/* Submission Type Card Radio */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Submission Type</label>
<div className="grid grid-cols-2 gap-3">
{/* Opsi 1: File Upload */}
<div
onClick={() => setSubmissionType("file")}
className={`flex items-center gap-3 px-4 py-3 rounded-xl border cursor-pointer transition-all ${
submissionType === "file"
? "border-[#B30000] bg-[#B30000] text-white font-semibold"
: "border-gray-200 bg-white text-gray-600 hover:bg-gray-50"
}`}
>
<div className={`w-3.5 h-3.5 rounded-full border flex items-center justify-center ${submissionType === "file" ? "border-white bg-white" : "border-gray-300"}`}>
{submissionType === "file" && <div className="w-1.5 h-1.5 bg-[#B30000] rounded-full" />}
{(["file_upload", "url_link"] as SubmissionType[]).map((type) => (
<div key={type} onClick={() => setSubmissionType(type)}
className={`flex items-center gap-3 px-4 py-3 rounded-xl border cursor-pointer transition-all ${
submissionType === type ? "border-[#B30000] bg-[#B30000] text-white font-semibold" : "border-gray-200 bg-white text-gray-600 hover:bg-gray-50"
}`}>
<div className={`w-3.5 h-3.5 rounded-full border flex items-center justify-center ${submissionType === type ? "border-white bg-white" : "border-gray-300"}`}>
{submissionType === type && <div className="w-1.5 h-1.5 bg-[#B30000] rounded-full" />}
</div>
{type === "file_upload" ? <FileText className="w-4 h-4" /> : <Link className="w-4 h-4" />}
<span className="text-xs">{type === "file_upload" ? "File Upload" : "URL Link"}</span>
</div>
<FileText className="w-4 h-4" />
<span className="text-xs">File Upload</span>
</div>
{/* Opsi 2: URL Link */}
<div
onClick={() => setSubmissionType("url")}
className={`flex items-center gap-3 px-4 py-3 rounded-xl border cursor-pointer transition-all ${
submissionType === "url"
? "border-[#B30000] bg-[#B30000] text-white font-semibold"
: "border-gray-200 bg-white text-gray-600 hover:bg-gray-50"
}`}
>
<div className={`w-3.5 h-3.5 rounded-full border flex items-center justify-center ${submissionType === "url" ? "border-white bg-white" : "border-gray-300"}`}>
{submissionType === "url" && <div className="w-1.5 h-1.5 bg-[#B30000] rounded-full" />}
</div>
<Link className="w-4 h-4" />
<span className="text-xs">URL Link</span>
</div>
))}
</div>
</div>
<div className="h-px bg-gray-100 my-2" />
{/* Action Buttons */}
<div className="flex items-center justify-end gap-3 pt-2">
<button
type="button"
onClick={onClose}
className="p-3 border border-gray-200 rounded-xl text-xs font-semibold text-gray-500 hover:text-gray-700 hover:bg-gray-50 transition-colors"
>
<button type="button" onClick={onClose}
className="p-3 border border-gray-200 rounded-xl text-xs font-semibold text-gray-500 hover:text-gray-700 hover:bg-gray-50 transition-colors">
Cancel
</button>
<button
type="submit"
className="p-3 bg-[#B30000] hover:bg-[#990000] text-white text-xs font-bold rounded-xl transition-colors shadow-md"
>
Save Task
<button type="submit" disabled={loading}
className="p-3 bg-[#B30000] hover:bg-[#990000] text-white text-xs font-bold rounded-xl transition-colors shadow-md disabled:opacity-50">
{loading ? "Saving..." : "Save Task"}
</button>
</div>
</form>
</dialog>
);
@@ -3,10 +3,12 @@ import { mockProjects } from '../../../../lib/mockData'
import { useState } from 'react';
import { Plus } from 'lucide-react';
import CreateProjectModal from './CreateProjectDialog';
import { useProjects } from '../../../../hooks/useProjects';
const HomeMentorProject = () => {
const navigate = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const { projects, loading, error } = useProjects();
const openDialog = () => {
setIsModalOpen(true);
};
@@ -17,7 +19,7 @@ const HomeMentorProject = () => {
<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
<button
className='bg-gray-300 p-1.5 rounded-lg font-bold hover:bg-gray-400'
onClick={openDialog} >
<span className='text-sm flex flex-row justify-between items-center gap-3.5'>
@@ -26,22 +28,22 @@ const HomeMentorProject = () => {
</span>
</button>
</div>
<div className="flex flex-col gap-3">
{mockProjects.map((project, index) => {
return (
<div
key={index}
onClick={() => navigate(`/intern/projects/${project.name}`)}
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 className="flex flex-col max-h-[210px] overflow-y-auto gap-3 pr-1">
{loading && <p className="text-sm text-gray-400 px-5">Memuat...</p>}
{error && <p className="text-sm text-red-500 px-5">{error}</p>}
{projects.map((project) => (
<div
key={project.id}
onClick={() => navigate(`/mentor/projects/${project.slug}`)}
className="flex items-center gap-4 border border-box-border 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.project_name}</p>
</div>
</div>
))}
</div>
{isModalOpen && (
<CreateProjectModal
@@ -1,48 +1,40 @@
import { Trash2, UsersRound } from 'lucide-react'
import type { Intern, Task } from '../../../../lib/mockProjects'
import { useNavigate } from 'react-router-dom'
import * as LucideIcons from 'lucide-react';
import type { LucideProps } from 'lucide-react';
import type { Project } from '../../../../types/project.types';
interface ProjectCardProps {
id: number | string
name: string
interns: Intern[]
tasks: Task[]
thumbnailUrl?: string
}
const getDynamicIcon = (iconName: string) => {
const formatted = iconName.charAt(0).toUpperCase() + iconName.slice(1);
const Icon = (LucideIcons as unknown as Record<string, React.ComponentType<LucideProps>>)[formatted];
return Icon ?? LucideIcons.FolderOpen;
};
const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-');
export default function MentorProjectCard({
id,
name,
interns,
thumbnailUrl
}: ProjectCardProps) {
export default function MentorProjectCard(project: Project) {
const navigate = useNavigate();
const handleCardClick = () => {
navigate(`/mentor/projects/${toSlug(name)}`)
navigate(`/mentor/projects/${toSlug(project.slug)}`)
};
const Icon = getDynamicIcon(project.project_icon);
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 className="h-[150px] bg-gradient-to-tr from-red-700 to-red-800 overflow-hidden shrink-0 flex items-center justify-center">
<Icon className="w-12 h-12 text-white opacity-80" />
</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}
{project.project_name}
</p>
</div>
@@ -50,7 +42,7 @@ export default function MentorProjectCard({
<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}
{project.total_members}/{project.total_members}
</span>
</div>
</div>
@@ -1,28 +1,21 @@
import type { Task } from "../../../../lib/mockProjects";
import { useNavigate } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import type { ProjectTask } from "../../../../types/task.types";
const formatDate = (date: Date) =>
const formatDate = (date: string) =>
new Date(date).toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric' });
const formatTime = (dateStr: string) =>
new Date(dateStr).toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-');
interface MentorTaskCardProps {
task: Task;
projectName: string;
}
export default function MentorTaskCard({ task, projectName }: MentorTaskCardProps) {
export default function MentorTaskCard({ task }: {task: ProjectTask}) {
const navigate = useNavigate();
const { slug } = useParams<{ slug: string }>();
const handleCardClick = () => {
// Pastikan jika projectName belum ter-render, proses navigasi tidak bikin crash
if (!projectName || !task?.title) return;
const projectSlug = toSlug(projectName);
const taskSlug = toSlug(task.title);
// Rute mutlak menuju target kamu: /mentor/projects/:slug/:taskSlug
navigate(`/mentor/projects/${projectSlug}/${taskSlug}`);
navigate(`/intern/projects/${slug}/${task.slug}?type=${task.submission_type}`)
};
return (
@@ -32,7 +25,7 @@ export default function MentorTaskCard({ task, projectName }: MentorTaskCardProp
>
<div className="flex-1 min-w-0">
<div className="text-[11px] text-[#888] mb-1">
Deadline : {task.deadline ? `${formatDate(task.deadline.date)} at ${task.deadline.time}` : '-'}
Deadline : {task.deadline_at ? `${formatDate(task.deadline_at)} at ${formatTime(task.deadline_at)}` : '-'}
</div>
<div className="font-bold text-[15px] text-[#1a1a1a] mb-1">
{task.title}
@@ -1,5 +1,5 @@
import { useNavigate, useLocation } from 'react-router-dom';
import { Home, FolderKanban, Award, Settings, HelpCircle, LogOut, ChevronRight, X } from 'lucide-react';
import { Home, FolderKanban, Award, Settings, HelpCircle, LogOut, X } from 'lucide-react';
const navItems = [
// belum kutambahin
@@ -39,7 +39,7 @@ export default function SidebarMentor({ isOpen, closeSidebar }: SidebarProps) {
return (
<aside
className={`
bg-white border-r border-gray-300 flex flex-col py-5 px-3 gap-1 overflow-y-auto flex-shrink-0 transition-transform duration-300 ease-in-out
bg-white border-r border-gray-300 flex flex-col py-5 px-1 gap-1 overflow-y-auto flex-shrink-0 transition-transform duration-300 ease-in-out
md:sticky md:top-[50px] md:h-[calc(100vh-73px)] md:w-[250px] md:translate-x-0 md:z-8
fixed top-0 left-0 h-[calc(100vh-50px)] w-[270px] z-40 pt-5
${isOpen ? "translate-x-0" : "-translate-x-full"}
@@ -71,10 +71,10 @@ export default function SidebarMentor({ isOpen, closeSidebar }: SidebarProps) {
<button
key={path}
onClick={() => handleNav(path)}
className={`flex items-center gap-2.5 w-full px-3 py-2 rounded-lg text-[13.5px] font-medium text-left transition-colors
className={`flex items-center gap-2.5 w-full px-3 py-2 text-[13.5px] rounded-lg font-medium text-left transition-colors
${isActive
? "bg-base-foreground text-base font-semibold border-l-4 border-base"
: "text-font hover:bg-base-foreground hover:text-base"
? "bg-red-foreground text-red font-semibold border-l-6 border-red"
: "text-font hover:bg-red-foreground"
}`}
>
<Icon className="w-[18px] h-[18px] flex-shrink-0" />
@@ -99,10 +99,10 @@ export default function SidebarMentor({ isOpen, closeSidebar }: SidebarProps) {
onClick={() => isLogout ? handleLogout() : handleNav(path)}
className={`flex items-center gap-2.5 w-full px-3 py-2 rounded-lg text-[13.5px] font-medium text-left transition-colors
${isActive
? "bg-red-50 text-red-600 font-semibold"
? "bg-red-50 text-red-600 font-semibold border-l-6 border-red "
: danger
? "text-font hover:bg-base-foreground hover:text-base"
: "text-font hover:bg-base-foreground hover:text-base"
? "text-font hover:bg-base-foreground"
: "text-font hover:bg-red-foreground"
}`}
>
<Icon className="w-[18px] h-[18px] flex-shrink-0" />