feat: add modals for managing interns, creating projects, and tasks

- Implemented AddInternsDialog for inviting and managing interns.
- Created CreateProjectDialog for launching new projects with detailed forms.
- Added CreateTaskDialog for task creation with deadline and submission type options.
- Enhanced Header component with navigation functionality.
- Updated MentorProjectCard to navigate correctly to mentor projects.
- Refactored SidebarMentor for improved navigation and mobile responsiveness.
- Adjusted Internify login logic to streamline user role navigation.
- Added CSS styles for consistent layout and design.
- Introduced mock data for interns to facilitate development and testing.
This commit is contained in:
Kemas M. Aryadary Rasyad
2026-06-06 23:47:22 +07:00
parent e59e63a21e
commit 46cd26f4d2
33 changed files with 1886 additions and 332 deletions
+39 -39
View File
@@ -1,51 +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
import { mockProjects } from '../../../lib/mockData';
import MentorProjectCard from './components/MentorProjectCard';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import CreateProjectModal from './components/CreateProjectDialog';
const MentorProjectsPage = () => {
const navigate = useNavigate()
export default function MentorProjectsPage(){
const [isModalOpen, setIsModalOpen] = useState(false);
const handleJoinProject = () => {
// Navigasi ke halaman tujuan, silakan ganti '/path-tujuan' sesuai rute kamu
navigate('/path-tujuan')
}
const openDialog = () => {
setIsModalOpen(true);
};
const closeDialog = () => {
setIsModalOpen(false);
};
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 className="container">
<div className="flex justify-between items-center mb-6">
<div className="flex flex-col">
<h1 className="page-title">List Projects</h1>
<p className="page-title-desc">Create, manage, and monitor ongoing projects and assignments.</p>
</div>
<div>
{/* Tombol pemicu buka modal */}
<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" />
<span>Add</span>
</button>
</div>
</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
{isModalOpen && (
<CreateProjectModal
isOpen={isModalOpen}
onClose={closeDialog}
/>
)}
</div>
);
};
+240 -5
View File
@@ -1,11 +1,246 @@
import React from 'react'
import React, { useState } from 'react';
import {
PlusCircle,
UploadCloud,
Eye,
Download,
Trash2,
ChevronLeft,
ChevronRight,
DownloadCloud
} from 'lucide-react';
import { LuHistory, LuSendHorizontal } from 'react-icons/lu';
// Mock Data untuk Tabel Riwayat Sertifikat
const initialHistoryData = [
{ id: 1, name: "Jonathan Kristina", email: "JonathanKristina@gmail.com", project: "Internify Project", date: "24 Oct 2024", initials: "JK" },
{ id: 2, name: "Ahmad Faisal", email: "ahmad.faisal@gmail.com", project: "Web Dev Fundamentals", date: "15 Sep 2024", initials: "AF" },
{ id: 3, name: "Siti Rahma", email: "siti.rahma@gmail.com", project: "UI/UX Essentials", date: "10 Aug 2024", initials: "SR" },
{ id: 4, name: "David Beckham", email: "david.b@gmail.com", project: "Engineering Intern 101", date: "05 Aug 2024", initials: "DB" },
];
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");
const [expiryDate, setExpiryDate] = useState("");
// State Data Tabel
const [historyData, setHistoryData] = useState(initialHistoryData);
const handleIssueCertificate = (e: React.FormEvent) => {
e.preventDefault();
const payload = {
intern: selectedIntern,
project: selectedProject,
issueDate,
expiryDate
};
console.log("Issuing Certificate:", payload);
alert("Certificate Issued Successfully!");
};
const handleDeleteHistory = (id: number) => {
if(confirm("Are you sure you want to delete this certificate log?")) {
setHistoryData(historyData.filter(item => item.id !== id));
}
};
return (
<div>
<div className="px-6 md:px-14 py-8 md:py-12 bg-gray-50 min-h-screen">
{/* Breadcrumb & Header Title */}
<div className="mb-8">
<p className="text-xs font-semibold text-[#B30000] mb-1 flex items-center gap-1 cursor-pointer">
Certificates <span className="text-gray-400 font-normal">&gt;</span>
</p>
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 tracking-tight">Certificates</h1>
<p className="text-sm md:text-base text-gray-500 mt-1">
Administrate, issue, and track professional certifications for all interns.
</p>
</div>
{/* Main Grid Content */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
{/* Kiri: Form Issue New Certificate (4 Kolom) */}
<div className="lg:col-span-5 xl:col-span-4 bg-white border border-gray-100 rounded-2xl p-6 md:p-8 shadow-sm">
<div className="flex items-center gap-3 mb-6">
<PlusCircle className="w-6 h-6 text-[#B30000]" />
<h2 className="text-xl font-bold text-gray-900 tracking-tight">Issue New Certificate</h2>
</div>
<form onSubmit={handleIssueCertificate} className="space-y-5">
{/* Select Intern */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Select Intern</label>
<select
value={selectedIntern}
onChange={(e) => setSelectedIntern(e.target.value)}
className="w-full px-3 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium"
>
<option value="JonathanKristina@gmail.com">JonathanKristina@gmail.com</option>
<option value="ahmad.faisal@gmail.com">ahmad.faisal@gmail.com</option>
<option value="siti.rahma@gmail.com">siti.rahma@gmail.com</option>
</select>
</div>
{/* Select Project / Course */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Select Project / Course</label>
<select
value={selectedProject}
onChange={(e) => setSelectedProject(e.target.value)}
className="w-full px-3 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium"
>
<option value="Internify Project (Web Development)">Internify Project (Web Development)</option>
<option value="Web Dev Fundamentals">Web Dev Fundamentals</option>
<option value="UI/UX Essentials">UI/UX Essentials</option>
</select>
</div>
{/* Certificate Image Upload Area */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Certificate Image</label>
<div className="border-2 border-dashed border-red-200/60 bg-red-50/10 rounded-xl p-6 text-center flex flex-col items-center justify-center cursor-pointer hover:bg-red-50/20 transition-colors group">
<div className="w-10 h-10 rounded-full bg-red-50 flex items-center justify-center mb-2 group-hover:scale-105 transition-transform">
<UploadCloud className="w-5 h-5 text-[#B30000]" />
</div>
<p className="text-xs font-bold text-gray-800">Upload Certificate Image</p>
<p className="text-[10px] text-gray-400 mt-0.5">PNG, JPG up to 5MB</p>
</div>
</div>
{/* Date Grid */}
<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">Issue Date</label>
<input
type="date"
required
value={issueDate}
onChange={(e) => setIssueDate(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-red-600 focus:ring-1 focus:ring-red-600"
/>
</div>
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-500 tracking-wide">Expiry (Optional)</label>
<input
type="date"
value={expiryDate}
onChange={(e) => setExpiryDate(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-red-600 focus:ring-1 focus:ring-red-600"
/>
</div>
</div>
{/* Submit Button */}
<button
type="submit"
className="w-full py-3 mt-4 bg-[#B30000] hover:bg-[#990000] text-white text-sm font-bold rounded-xl transition-colors shadow-sm flex items-center justify-center gap-2"
>
<LuSendHorizontal className="w-4 h-4" />
Issue Certificate
</button>
</form>
</div>
{/* Kanan: Issued Certificates History (8 Kolom) */}
<div className="lg:col-span-7 lg:col-span-8 bg-white border border-gray-100 rounded-2xl shadow-sm overflow-hidden flex flex-col justify-between">
<div>
{/* Header Table */}
<div className="flex items-center justify-between px-6 py-5 border-b border-gray-50">
<div className="flex items-center gap-2.5">
<LuHistory className="w-5 h-5 text-red-600 stroke-[2.5]" />
<h2 className="text-lg font-bold text-gray-900 tracking-tight">Issued Certificates History</h2>
</div>
<button className="text-xs font-bold text-[#B30000] hover:text-[#990000] flex items-center gap-1.5 bg-red-50/50 px-3 py-1.5 rounded-lg border border-red-100 transition-colors">
<span>Export CSV</span>
<DownloadCloud className="w-3.5 h-3.5" />
</button>
</div>
{/* Responsive Table Wrapper */}
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-gray-50/70 border-b border-gray-100">
<th className="px-6 py-3.5 text-[10.5px] font-bold tracking-wider text-gray-400 uppercase">Intern Name</th>
<th className="px-6 py-3.5 text-[10.5px] font-bold tracking-wider text-gray-400 uppercase">Project</th>
<th className="px-6 py-3.5 text-[10.5px] font-bold tracking-wider text-gray-400 uppercase">Issue Date</th>
<th className="px-6 py-3.5 text-[10.5px] font-bold tracking-wider text-gray-400 uppercase text-center">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50">
{historyData.map((row) => (
<tr key={row.id} className="hover:bg-gray-50/40 transition-colors">
{/* Kolom Nama Intern + Avatar Ringkas */}
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-gray-100 border border-gray-200/60 flex items-center justify-center text-xs font-bold text-gray-500 flex-shrink-0">
{row.initials}
</div>
<div>
<p className="text-sm font-bold text-gray-900 leading-tight">{row.name}</p>
<p className="text-[11px] text-gray-400 mt-0.5">{row.email}</p>
</div>
</div>
</td>
{/* Kolom Nama Proyek */}
<td className="px-6 py-4">
<p className="text-sm text-gray-600 font-medium">{row.project}</p>
</td>
{/* Kolom Tanggal Terbit */}
<td className="px-6 py-4">
<p className="text-sm text-gray-500 font-medium">{row.date}</p>
</td>
{/* Kolom Deretan Aksi */}
<td className="px-6 py-4">
<div className="flex items-center justify-center gap-1">
<button className="p-1.5 text-gray-400 hover:text-gray-700 hover:bg-gray-100 rounded-md transition-colors" title="View Certificate">
<Eye className="w-4 h-4" />
</button>
<button className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors" title="Download">
<Download className="w-4 h-4" />
</button>
<button
onClick={() => handleDeleteHistory(row.id)}
className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors"
title="Delete Log"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Bagian Footer Tabel & Paginasi */}
<div className="px-6 py-4 border-t border-gray-100 bg-gray-50/30 flex flex-col sm:flex-row gap-3 items-center justify-between text-xs font-medium text-gray-500">
<p>Showing 1 to {historyData.length} of 1,284 entries</p>
<div className="flex items-center gap-1">
<button className="p-1 border border-gray-200 rounded-md bg-white text-gray-400 hover:bg-gray-50 disabled:opacity-50">
<ChevronLeft className="w-4 h-4" />
</button>
<button className="w-7 h-7 flex items-center justify-center rounded-md bg-[#B30000] text-white font-bold shadow-sm">1</button>
<button className="w-7 h-7 flex items-center justify-center rounded-md bg-white border border-gray-200 text-gray-600 hover:bg-gray-50">2</button>
<button className="w-7 h-7 flex items-center justify-center rounded-md bg-white border border-gray-200 text-gray-600 hover:bg-gray-50">3</button>
<button className="p-1 border border-gray-200 rounded-md bg-white text-gray-400 hover:bg-gray-50">
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
</div>
)
);
}
export default MentorCertificatePage
export default MentorCertificatePage;
+1 -1
View File
@@ -4,7 +4,7 @@ import HomeMentorProject from './components/HomeMentorProject'
const HomeMentorPage = () => {
return (
<div className="flex-1 px-14 py-10 bg-gray-50">
<div className="container flex-1 bg-gray-50">
<HomeHeader />
<div className="flex flex-col gap-5">
<div className="">
+195 -4
View File
@@ -1,5 +1,196 @@
export default function MentorInternPage() {
return(
<div></div>
)
import { useState, useMemo } from "react";
import { Search, SlidersHorizontal, Plus, Trash2 } from "lucide-react";
import { mockInternsData } from "../../../lib/mockData";
export default function InternsManagement() {
// State Filter & Search
const [searchEmail, setSearchEmail] = useState("");
const [selectedProject, setSelectedProject] = useState("All Projects");
// Handler Aksi Kursor/Tombol
const handleAddAssignment = (id: number) => {
alert(`Assign/Add action clicked for intern ID: ${id}`);
};
const handleDeleteIntern = (id: number) => {
alert(`Deleted intern ID: ${id}`);
};
// Logic Filtering Data menggunakan useMemo
const filteredInterns = useMemo(() => {
return mockInternsData.filter((intern) => {
const matchesEmail = intern.email.toLowerCase().includes(searchEmail.toLowerCase());
const matchesProject =
selectedProject === "All Projects" ||
intern.projectName.toLowerCase() === selectedProject.toLowerCase();
return matchesEmail && matchesProject;
});
}, [searchEmail, selectedProject]);
return (
<div className="w-full px-16 py-12 font-sans">
{/* Header Title */}
<div className="mb-6">
<h1 className="page-title">Interns Management</h1>
<p className="page-title-desc">
Manage role-based access and project assignments for active interns.
</p>
</div>
{/* Filter Row & Stats Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 items-center mb-6">
{/* Left Control Inputs */}
<div className="lg:col-span-2 flex flex-col sm:flex-row items-center gap-3">
{/* Search Box Input */}
<div className="relative w-full sm:max-w-xs">
<span className="absolute inset-y-0 left-3 flex items-center text-gray-400">
<Search className="w-4 h-4" />
</span>
<input
type="text"
placeholder="Filter by email"
value={searchEmail}
onChange={(e) => setSearchEmail(e.target.value)}
className="w-full pl-9 pr-4 py-2 bg-gray-100 border border-transparent rounded-lg text-sm placeholder-gray-400 text-gray-800 focus:outline-none focus:bg-white focus:border-gray-200 font-medium"
/>
</div>
{/* Select Project Dropdown */}
<div className="relative w-full sm:w-48">
<select
value={selectedProject}
onChange={(e) => setSelectedProject(e.target.value)}
className="w-full pl-3 pr-8 py-2 bg-gray-100 border border-transparent rounded-lg text-sm text-gray-700 font-medium appearance-none focus:outline-none focus:bg-white focus:border-gray-200"
>
<option value="All Projects">All Projects</option>
<option value="Internify Project">Internify Project</option>
<option value="MBG Project">MBG Project</option>
<option value="Unassigned">Unassigned</option>
</select>
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-500">
<svg className="fill-current h-4 w-4" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />
</svg>
</div>
</div>
{/* Icon Sliders / Filter Button */}
<button className="p-2 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition-colors hidden sm:block">
<SlidersHorizontal className="w-4 h-4" />
</button>
</div>
{/* Right Info Box Stats */}
<div className="bg-white border border-gray-100 rounded-xl px-6 py-2.5 flex items-center justify-around text-center shadow-[0_2px_10px_rgba(0,0,0,0.01)] w-full lg:max-w-sm ml-auto">
<div>
<span className="block text-xl font-bold text-red-600 leading-none">124</span>
<span className="text-[10px] font-bold text-gray-400 tracking-wider uppercase mt-1 block">
TOTAL INTERNS
</span>
</div>
<div className="h-8 w-px bg-gray-100 mx-2"></div>
<div>
<span className="block text-xl font-bold text-green-600 leading-none">92</span>
<span className="text-[10px] font-bold text-gray-400 tracking-wider uppercase mt-1 block">
ACTIVE ASSIGNMENTS
</span>
</div>
</div>
</div>
{/* Main Table Container Block */}
<div className="w-full bg-white rounded-xl border border-gray-100 shadow-[0_4px_20px_rgba(0,0,0,0.01)] overflow-hidden">
{/* Red Table Header Block */}
<div className="bg-[#B30000] text-white px-6 py-3.5 flex items-center text-xs font-bold uppercase tracking-wider">
<div className="w-1/2">Intern Details</div>
<div className="w-1/3">Current Project</div>
<div className="w-1/6 text-right pr-4">Actions</div>
</div>
{/* Table Body Iteration Rows */}
<div className="divide-y divide-gray-100">
{filteredInterns.length > 0 ? (
filteredInterns.map((intern, index) => {
const isUnassigned = intern.projectName === "Unassigned";
return (
<div key={`${intern.id}-${index}`} className="px-6 py-4 flex items-center hover:bg-gray-50/60 transition-colors">
{/* Column 1: Intern Details (Avatar + Name & Email) */}
<div className="w-1/2 flex items-center gap-3">
{intern.avatar ? (
<img
src={intern.avatar}
alt={intern.name}
className="w-10 h-10 rounded-full object-cover border border-gray-100 shadow-sm"
/>
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-teal-400 to-emerald-500 flex items-center justify-center border border-gray-100 shadow-sm text-white text-xs font-bold">
{intern.name.charAt(0)}
</div>
)}
<div>
<h4 className="text-sm font-bold text-gray-900 leading-tight">{intern.name}</h4>
<p className="text-xs text-gray-400 font-medium mt-0.5">{intern.email}</p>
</div>
</div>
{/* Column 2: Current Project Title & Role Accent */}
<div className="w-1/3">
<h5 className={`text-sm font-semibold leading-tight ${isUnassigned ? 'text-gray-500 italic' : 'text-gray-900'}`}>
{intern.projectName}
</h5>
<p className={`text-xs font-bold mt-0.5 ${isUnassigned ? 'text-red-500' : 'text-red-600'}`}>
{intern.role}
</p>
</div>
{/* Column 3: Action Buttons Block Right aligned */}
<div className="w-1/6 flex items-center justify-end gap-3.5 pr-2">
<button
onClick={() => handleAddAssignment(intern.id)}
className="p-1.5 bg-[#B30000] hover:bg-[#990000] text-white rounded-lg transition-colors shadow-sm"
>
<Plus className="w-4 h-4 stroke-[3]" />
</button>
<button
onClick={() => handleDeleteIntern(intern.id)}
className="p-1.5 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-lg transition-colors"
>
<Trash2 className="w-4 h-4 stroke-[2]" />
</button>
</div>
</div>
);
})
) : (
<div className="text-center py-12 text-sm text-gray-400 font-medium">
No interns found matching the filter criteria.
</div>
)}
</div>
{/* Red Table Pagination Bar Footer */}
<div className="bg-[#B30000] text-white px-6 py-3.5 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 text-xs font-medium">
<div>
Showing <span className="font-bold">{filteredInterns.length}</span> of 124 interns
</div>
<div className="flex items-center gap-2 self-end sm:self-auto">
<button className="px-3 py-1.5 bg-black/20 hover:bg-black/30 rounded-md font-semibold opacity-50 cursor-not-allowed transition-colors">
Previous
</button>
<button className="px-4 py-1.5 bg-white text-gray-900 hover:bg-gray-100 rounded-md font-bold shadow-sm transition-colors">
Next
</button>
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,109 @@
import { useRef, useEffect, useState } from "react";
import { X, Search, Plus, Trash2 } from "lucide-react";
interface ManageInternsModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function ManageInternsModal({ isOpen, onClose }: ManageInternsModalProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
const [searchQuery, setSearchQuery] = useState("Jonathankristin@gmail.com");
// Data Intern Terundang (Simulasi list atas yang ada ikon sampah)
const [invitedList, setInvitedList] = useState([
{ id: 1, email: "Jonathankristina@gmail.com", initials: "JK" }
]);
// Data Rekomendasi/Hasil Pencarian (Simulasi list bawah yang ada ikon plus)
const [searchResult, setSearchResult] = useState([
{ id: 2, email: "Jonathankristin@gmail.com", initials: "JK" }
]);
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();
};
dialog.addEventListener("cancel", handleCancel);
return () => {
dialog.removeEventListener("cancel", handleCancel);
document.body.style.overflow = "unset";
};
}, [isOpen, onClose]);
return (
<dialog
ref={dialogRef}
className="fixed inset-0 m-auto flex flex-col w-[calc(100%-2rem)] max-w-[540px] max-h-[calc(100vh-4rem)] rounded-2xl border border-gray-100 bg-white p-6 shadow-2xl backdrop:bg-black/40 backdrop:backdrop-blur-sm animate-in fade-in zoom-in-95 duration-200"
>
{/* 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..."
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">
<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}
</div>
<span className="text-sm font-medium text-gray-600">{intern.email}</span>
</div>
<button
onClick={() => setInvitedList(invitedList.filter(i => i.id !== intern.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" />
</button>
</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}
</div>
<span className="text-sm font-medium text-gray-600">{intern.email}</span>
</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>
))}
</div>
</dialog>
);
}
@@ -0,0 +1,299 @@
import { useRef, useEffect, useState } from "react";
import { X, Calendar } from "lucide-react";
import {
LuCode,
LuAlbum,
LuCloud,
LuSmartphone,
LuSettings,
LuUsers,
LuClipboardList,
LuPenTool,
LuLightbulb,
LuShield,
LuMail,
LuSendHorizontal
} from "react-icons/lu";
interface CreateProjectModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
// State Form Management
const [selectedIcon, setSelectedIcon] = useState("code");
const [projectName, setProjectName] = useState("");
const [projectDescription, setProjectDescription] = useState("");
const [emailInput, setEmailInput] = useState("");
const [invitedEmails, setInvitedEmails] = useState<string[]>([
"alex.dev@gmail.com",
"sarah.engineer@gmail.com"
]);
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
// Daftar Icon Pilihan di baris atas
const projectIcons = [
{ id: "code", icon: LuCode },
{ id: "chart", icon: LuAlbum },
{ id: "cloud", icon: LuCloud },
{ id: "mobile", icon: LuSmartphone },
{ id: "settings", icon: LuSettings },
{ id: "users", icon: LuUsers },
{ id: "task", icon: LuClipboardList },
{ id: "design", icon: LuPenTool },
{ id: "idea", icon: LuLightbulb },
{ id: "secure", icon: LuShield },
];
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();
};
dialog.addEventListener("cancel", handleCancel);
return () => {
dialog.removeEventListener("cancel", handleCancel);
document.body.style.overflow = "unset";
};
}, [isOpen, onClose]);
// Fungsi menambah email undangan baru
const handleAddEmail = (e: React.FormEvent) => {
e.preventDefault();
if (emailInput.trim() && !invitedEmails.includes(emailInput.trim())) {
setInvitedEmails([...invitedEmails, emailInput.trim()]);
setEmailInput("");
}
};
// Fungsi menghapus email undangan
const handleRemoveEmail = (emailToRemove: string) => {
setInvitedEmails(invitedEmails.filter(email => email !== emailToRemove));
};
// Handler Submit Utama
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const payload = {
icon: selectedIcon,
name: projectName,
description: projectDescription,
emails: invitedEmails,
startDate,
endDate
};
console.log("Launching Project:", payload);
alert("Project Launched Successfully!");
onClose();
};
return (
<dialog
ref={dialogRef}
className="fixed inset-0 m-auto flex flex-col w-[calc(100%-2rem)] max-w-[620px] max-h-[calc(100vh-4rem)] rounded-2xl border border-gray-100 bg-white p-0 shadow-2xl backdrop:bg-black/40 backdrop:backdrop-blur-sm animate-in fade-in zoom-in-95 duration-200"
>
{/* Header Modal */}
<div className="flex items-start justify-between px-6 md:px-8 py-4 md:py-5 border-b border-gray-100">
<div>
<h2 className="text-xl font-bold text-gray-900 tracking-tight">Create New Project</h2>
<p className="text-xs text-gray-400 font-medium mt-0.5">
Launch a new internship assignment or trackable milestone.
</p>
</div>
<button
onClick={onClose}
className="rounded-lg p-1.5 text-gray-400 hover:bg-gray-50 hover:text-gray-600 transition-colors mt-1"
>
<X className="w-5 h-5 stroke-[2.5]" />
</button>
</div>
{/* Body Form Modal */}
<form onSubmit={handleSubmit} className="p-6 md:p-8 space-y-4 md:space-y-5 overflow-y-auto flex-1">
{/* Section: Select Project Icon */}
<div className="space-y-2">
<label className="text-xs font-bold text-gray-700 tracking-wide">
Select Project Icon
</label>
<div className="flex flex-wrap gap-2">
{projectIcons.map((item) => {
const IconComponent = item.icon;
const isSelected = selectedIcon === item.id;
return (
<button
key={item.id}
type="button"
onClick={() => setSelectedIcon(item.id)}
className={`w-10 h-10 flex items-center justify-center rounded-lg border text-lg transition-all ${isSelected
? "border-[#B30000] bg-red-50/50 text-[#B30000] ring-2 ring-[#B30000]/20 font-bold"
: "border-gray-200 bg-gray-50 text-gray-500 hover:bg-gray-100"
}`}
>
<IconComponent className={isSelected ? "stroke-[2.5]" : "stroke-[2]"} />
</button>
);
})}
</div>
</div>
{/* Section: Project Name */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">
Project Name
</label>
<input
type="text"
required
placeholder="e.g., Q3 Infrastructure Upgrade"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
className="w-full px-4 py-2.5 bg-white border border-gray-200 rounded-lg text-sm placeholder-gray-400 text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium"
/>
</div>
{/* Section: Project Description */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">
Project Description
</label>
<textarea
rows={4}
placeholder="Outline the project goals, deliverables, and technical requirements..."
value={projectDescription}
onChange={(e) => setProjectDescription(e.target.value)}
className="w-full px-4 py-2.5 bg-white border border-gray-200 rounded-lg text-sm placeholder-gray-400 text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium resize-none leading-relaxed"
/>
</div>
{/* Section: Invite via Gmail */}
<div className="space-y-2">
<label className="text-xs font-bold text-gray-700 tracking-wide">
Invite via Gmail
</label>
<div className="relative flex items-center">
<span className="absolute left-3.5 text-gray-400">
<LuMail className="w-4 h-4 stroke-[2.5]" />
</span>
<input
type="email"
placeholder="intern.name@gmail.com"
value={emailInput}
onChange={(e) => setEmailInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAddEmail(e);
}
}}
className="w-full pl-10 pr-20 py-2.5 bg-white border border-gray-200 rounded-lg text-sm placeholder-gray-400 text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium"
/>
<button
type="button"
onClick={handleAddEmail}
className="absolute right-2 px-3 py-1 bg-gray-100 hover:bg-gray-200 text-gray-800 text-[11px] font-bold rounded-md transition-colors tracking-wider uppercase"
>
ADD
</button>
</div>
{/* Badges List Email */}
<div className="flex flex-wrap gap-1.5 pt-1">
{invitedEmails.map((email) => (
<div
key={email}
className="flex items-center gap-1 bg-gray-100 text-gray-600 px-2.5 py-1 rounded-full text-xs font-medium border border-gray-200/60"
>
<span>{email}</span>
<button
type="button"
onClick={() => handleRemoveEmail(email)}
className="text-gray-400 hover:text-gray-600 rounded-full p-0.5"
>
<X className="w-3 h-3 stroke-[3]" />
</button>
</div>
))}
</div>
</div>
{/* Section: Dates Grid (Start Date & End Date) */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Start Date */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">
Start Date
</label>
<div className="relative">
<input
type="date"
required
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="w-full pl-4 pr-10 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium appearance-none"
/>
<span className="absolute right-3.5 top-3 text-gray-400 pointer-events-none">
<Calendar className="w-4 h-4" />
</span>
</div>
</div>
{/* End Date */}
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">
End Date
</label>
<div className="relative">
<input
type="date"
required
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className="w-full pl-4 pr-10 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium appearance-none"
/>
<span className="absolute right-3.5 top-3 text-gray-400 pointer-events-none">
<Calendar className="w-4 h-4" />
</span>
</div>
</div>
</div>
{/* Action Row Buttons */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 pt-6">
<button
type="button"
onClick={onClose}
className="w-full sm:w-[150px] py-2.5 border border-gray-200 rounded-xl text-sm font-semibold text-gray-500 hover:text-gray-700 hover:bg-gray-50 transition-colors"
>
Cancel
</button>
<button
type="submit"
className="w-full sm:w-[240px] py-2.5 bg-[#B30000] hover:bg-[#990000] text-white text-sm font-bold rounded-xl transition-colors shadow-md flex items-center justify-center gap-2 tracking-wide"
>
Launch Project
<LuSendHorizontal className="w-4 h-4 stroke-[2.5]" />
</button>
</div>
</form>
</dialog>
);
}
@@ -0,0 +1,190 @@
import { useRef, useEffect, useState } from "react";
import { X, Calendar, Clock, FileText, Link } from "lucide-react";
interface CreateTaskModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function CreateTaskModal({ isOpen, onClose }: CreateTaskModalProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
// 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");
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();
};
dialog.addEventListener("cancel", handleCancel);
return () => {
dialog.removeEventListener("cancel", handleCancel);
document.body.style.overflow = "unset";
};
}, [isOpen, onClose]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const payload = { taskTitle, description, deadlineDate, specificTime, submissionType };
console.log("Saving Task:", payload);
alert("Task Saved Successfully!");
onClose();
};
return (
<dialog
ref={dialogRef}
className="fixed inset-0 m-auto flex flex-col w-[calc(100%-2rem)] max-w-[480px] max-h-[calc(100vh-4rem)] rounded-2xl border border-gray-100 bg-white p-0 shadow-2xl backdrop:bg-black/40 backdrop:backdrop-blur-sm animate-in fade-in zoom-in-95 duration-200"
>
{/* Header Modal */}
<div className="flex items-start justify-between px-6 py-5 border-b border-gray-50">
<div>
<h2 className="text-xl 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>
</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="p-6 space-y-4 overflow-y-auto flex-1">
{/* Task Title */}
<div className="space-y-1.5">
<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-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]"
/>
</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"
/>
</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-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]"
/>
<Calendar className="absolute right-3 top-2.5 w-4 h-4 text-gray-400 pointer-events-none" />
</div>
</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-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]"
/>
<Clock className="absolute right-3 top-2.5 w-4 h-4 text-gray-400 pointer-events-none" />
</div>
</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" />}
</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="px-5 py-2 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="px-5 py-2 bg-[#B30000] hover:bg-[#990000] text-white text-xs font-bold rounded-xl transition-colors shadow-md"
>
Save Task
</button>
</div>
</form>
</dialog>
);
}
@@ -1,25 +1,33 @@
import { Bell } from "lucide-react"
import { Bell, Menu } from "lucide-react"
import humiclogo from "../../../../assets/logo.png";
import { useNavigate } from "react-router-dom";
const Header = () => {
export default function Header({ toggleSidebar }: { toggleSidebar: () => void }) {
const nav = useNavigate();
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"
/>
<header className='sticky top-0 flex w-full justify-between items-center py-4 px-12 bg-white border-b border-gray-300 z-10'>
<div className='flex items-center gap-3'>
{/* Tombol Burger Menu: Hanya muncul di layar < md */}
<button
onClick={toggleSidebar}
className="p-1 text-gray-600 hover:bg-gray-100 rounded-md md:hidden"
>
<Menu className="w-6 h-6" />
</button>
<button onClick={()=>nav('/intern')}>
<img src={humiclogo} alt="Humic Logo" className="w-[130px] cursor-pointer" />
</button>
</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 className="flex flex-row gap-6 items-center">
<button onClick={()=>nav("notifications")}>
<Bell className="w-6 h-6 hover:text-red-800"/>
</button>
<div className="bg-blue-500 rounded-xl w-9 h-9">
</div>
</div>
</header>
)
}
export default Header
}
@@ -17,7 +17,7 @@ export default function MentorProjectCard({
}: ProjectCardProps) {
const navigate = useNavigate();
const handleCardClick = () => {
navigate(`/admin/projects/${id}`)
navigate(`/mentor/projects/${id}`)
};
const handleDeleteClick = (e: React.MouseEvent<HTMLButtonElement>) => {
@@ -1,69 +1,91 @@
import { useNavigate } from 'react-router-dom';
import { Home, FolderKanban, Award, Settings, HelpCircle, LogOut, ChevronRight } from 'lucide-react';
import { useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { Home, FolderKanban, Award, Settings, HelpCircle, LogOut, ChevronRight, X } from 'lucide-react';
const navItems = [
const navItems = [
// belum kutambahin
{ label: "Home", icon: Home, path: "" },
{ label: "List Projects", icon: FolderKanban, path: "" },
{ label: "Interns", icon: FolderKanban, path: "" },
{ label: "Certificates", icon: Award, path: "" },
{ label: "Home", icon: Home, path: "/mentor" },
{ label: "List Projects", icon: FolderKanban, path: "/mentor/projects" },
{ label: "Interns", icon: FolderKanban, path: "/mentor/intern" },
{ label: "Certificates", icon: Award, path: "/mentor/certificates" },
];
const prefItems = [
{ label: "Settings", icon: Settings, path: "/home-intern/settings" },
{ label: "FAQ", icon: HelpCircle, path: "/home-intern/faq" },
{ label: "Logout", icon: LogOut, path: "/home-intern/logout", danger: true },
{ label: "Settings", icon: Settings, path: "/mentor/settings" },
{ label: "FAQ", icon: HelpCircle, path: "/mentor/faq" },
{ label: "Logout", icon: LogOut, path: "/login-internify", danger: true },
];
function SidebarIntern() {
interface SidebarProps {
isOpen: boolean;
closeSidebar: () => void;
}
export default function SidebarMentor({ isOpen, closeSidebar }: SidebarProps) {
const navigate = useNavigate();
const [active, setActive] = useState("/home-intern");
const location = useLocation();
const handleNav = (path: string) => {
setActive(path);
navigate(path);
closeSidebar();
};
return (
<aside className="w-[240px] bg-white border-r border-gray-100 flex flex-col py-5 px-3 gap-1 sticky top-[73px] h-[calc(100vh-73px)] overflow-y-auto flex-shrink-0">
{/* Brand */}
<div className="flex items-center gap-2.5 px-2 pb-4">
<div className="w-9 h-9 rounded-[10px] bg-red-600 flex items-center justify-center flex-shrink-0">
</div>
<div>
<p className="text-sm font-bold text-gray-900 leading-tight">Internify</p>
<p className="text-[11px] text-gray-400">LMS Portal</p>
<aside
className={`
/* Struktur Dasar Layout */
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
/* Kondisi Layar Desktop (>= md) */
md:sticky md:top-[73px] md:h-[calc(100vh-73px)] md:w-[250px] md:translate-x-0 md:z-8
/* Kondisi Layar Mobile (< md) dan Logika Aktif-nya */
fixed top-0 left-0 h-screen w-[270px] z-40 pt-5
${isOpen ? "translate-x-0" : "-translate-x-full"}
`}
>
{/* Brand & Tombol Close untuk Mobile */}
<div className="flex items-center justify-between px-2 pb-4">
<div className="flex items-center gap-2.5">
<div className="w-9 h-9 rounded-[10px] bg-red-600 flex items-center justify-center flex-shrink-0"></div>
<div>
<p className="text-sm font-bold text-gray-900 leading-tight">Internify</p>
<p className="text-[11px] text-gray-400">LMS Portal</p>
</div>
</div>
<button
onClick={closeSidebar}
className="p-1 text-gray-500 hover:bg-gray-100 rounded-md md:hidden"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="h-px bg-gray-100 my-1" />
<div className="h-px bg-gray-300 my-1" />
{/* Main nav */}
<nav className="flex flex-col gap-0.5">
{navItems.map(({ label, icon: Icon, path }) => {
const isActive = active === path;
const isActive = location.pathname === path;
return (
<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
${isActive
${isActive
? "bg-red-50 text-red-600 font-semibold border-l-4 border-red-600"
: "text-gray-500 hover:bg-red-50 hover:text-red-600"
}`}
>
<Icon className="w-[18px] h-[18px] flex-shrink-0" />
<span>{label}</span>
{isActive && (
<ChevronRight className="w-3.5 h-3.5 ml-auto opacity-60" />
)}
{isActive && <ChevronRight className="w-3.5 h-3.5 ml-auto opacity-60" />}
</button>
);
})}
</nav>
<div className="h-px bg-gray-100 my-1" />
<div className="h-px bg-gray-200 my-1" />
{/* Preferences */}
<p className="text-[10px] font-semibold tracking-widest text-gray-400 px-3 py-1.5">
@@ -71,7 +93,7 @@ function SidebarIntern() {
</p>
<nav className="flex flex-col gap-0.5">
{prefItems.map(({ label, icon: Icon, path, danger }) => {
const isActive = active === path;
const isActive = location.pathname === path;
return (
<button
key={path}
@@ -94,6 +116,3 @@ function SidebarIntern() {
</aside>
);
}
export default SidebarIntern