feat: Refactor mentor components and implement project management features
- Removed unused imports and interfaces in HomeProjectList component. - Added ArrowDownNarrowWide icon to ProjectTimeline component for visual enhancement. - Updated layout imports to reflect new component structure. - Created BaseProjectsPage for mentor to manage projects with navigation. - Introduced MentorCertificatePage as a placeholder for future certificate functionality. - Deleted obsolete HomeMentorPage and replaced it with HomePage for mentor dashboard. - Added InternsPage and updated MentorInternPage for better intern management. - Implemented ProjectForumTab and ProjectsInternTab for project details and intern listings. - Developed TaskTab for task management within projects. - Created SidebarMentor for navigation in mentor dashboard. - Enhanced login functionality with user role-based navigation. - Updated mockData to include task submission counts and certificate data structure. - Added new assets for certificate representation.
This commit is contained in:
+25
-14
@@ -29,15 +29,19 @@ import EditFaq from "./Pages/EditFAQ";
|
||||
import EditFeedback from "./Pages/EditFeedback";
|
||||
import EditLowongan from "./Pages/EditLowongan";
|
||||
import HomeInternPage from "./Pages/internify/intern/HomeInternPage";
|
||||
import {InternLayout, MentorLayout} from "./Pages/internify/layout";
|
||||
import { InternLayout, MentorLayout } from "./Pages/internify/layout";
|
||||
import ProjectsPage from "./Pages/internify/intern/ProjectsPage";
|
||||
import SertificatePage from "./Pages/internify/intern/SertificatePage";
|
||||
import ProjectDetailPage from "./Pages/internify/intern/ProjectDetailPage";
|
||||
import HomeMentorPage from "./Pages/internify/mentor/HomeMentorPage";
|
||||
import MentorProjectsPage from "./Pages/internify/mentor/MentorProjectsPage";
|
||||
import MentorCertificatePage from "./Pages/internify/mentor/MentorCertificatePage";
|
||||
import MentorInternPage from "./Pages/internify/mentor/MentorInternPage";
|
||||
import MentorDetailProject from "./Pages/internify/mentor/MentorDetailProject";
|
||||
import HomeMentorPage from "./Pages/internify/mentor/HomePage";
|
||||
import MentorProjectsPage from "./Pages/internify/mentor/BaseProjectsPage";
|
||||
import MentorCertificatePage from "./Pages/internify/mentor/CertificatePage";
|
||||
import MentorInternPage from "./Pages/internify/mentor/InternsPage";
|
||||
import MentorDetailProject from "./Pages/internify/mentor/ProjectsPage";
|
||||
import InternifyLogin from "./Pages/internify/page"
|
||||
import FAQPage from "./Pages/internify/FAQpage";
|
||||
import NotificationList from "./Pages/internify/NotificationList";
|
||||
import SettingsContent from "./Pages/internify/SettingsPage";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -62,7 +66,7 @@ function App() {
|
||||
<Route path="/add-product-humic" element={<AddProductHumic />} />
|
||||
<Route path="/edit-product-humic/:id" element={<EditProductHumic />} />
|
||||
<Route path="/kontent-aktif/edit/:id" element={<EditKontenAdmin />} />
|
||||
<Route path="/internships-details-admin/:id"element={<InternshipDetailsAdmin />}/>
|
||||
<Route path="/internships-details-admin/:id" element={<InternshipDetailsAdmin />} />
|
||||
<Route path="/partnership-admin" element={<PartnershipsList />} />
|
||||
<Route path="/add-partnership" element={<AddPartnershipAdmin />} />
|
||||
<Route path="/edit-partnership/:id" element={<EditPartnerships />} />
|
||||
@@ -77,20 +81,27 @@ function App() {
|
||||
|
||||
</Route>
|
||||
|
||||
<Route path="/login-internify" element={<InternifyLogin />} />
|
||||
{/* // Endpoints for intern-specific pages */}
|
||||
<Route path="/intern" element={<InternLayout />}>
|
||||
<Route index element={<HomeInternPage />} />
|
||||
<Route path="projects" element={<ProjectsPage />} />
|
||||
<Route path="certificates" element={<SertificatePage />} />
|
||||
<Route path="projects/:id" element={<ProjectDetailPage/>}/>
|
||||
<Route path="projects/:id" element={<ProjectDetailPage />} />
|
||||
<Route path="faq" element={<FAQPage />} />
|
||||
<Route path="notifications" element={<NotificationList />} />
|
||||
<Route path="settings" element={<SettingsContent />} />
|
||||
</Route>
|
||||
|
||||
<Route path="mentor-intern" element={<MentorLayout/>}>
|
||||
<Route index element={<HomeMentorPage/>}/>
|
||||
<Route path="projects" element={<MentorProjectsPage/>} />
|
||||
<Route path="certificates" element={<MentorCertificatePage/>} />
|
||||
<Route path="intern" element={<MentorInternPage/>} />
|
||||
<Route path="projects/:id" element={<MentorDetailProject />}/>
|
||||
<Route path="/mentor" element={<MentorLayout />}>
|
||||
<Route index element={<HomeMentorPage />} />
|
||||
<Route path="projects" element={<MentorProjectsPage />} />
|
||||
<Route path="certificates" element={<MentorCertificatePage />} />
|
||||
<Route path="intern" element={<MentorInternPage />} />
|
||||
<Route path="projects/:id" element={<MentorDetailProject />} />
|
||||
<Route path="faq" element={<FAQPage />} />
|
||||
<Route path="notifications" element={<NotificationList />} />
|
||||
<Route path="settings" element={<SettingsContent />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
|
||||
+12
-12
@@ -1,5 +1,6 @@
|
||||
import { Briefcase, Globe, Users } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FaInstagram, FaLinkedin, FaRegEnvelope } from "react-icons/fa";
|
||||
|
||||
const footerLinks = {
|
||||
Internship: [
|
||||
@@ -16,9 +17,9 @@ const footerLinks = {
|
||||
};
|
||||
|
||||
const socials = [
|
||||
{ label: "Website", icon: Globe, href: "https://placeholder.com" },
|
||||
{ label: "Team", icon: Users, href: "https://placeholder.com" },
|
||||
{ label: "Work", icon: Briefcase, href: "https://placeholder.com" },
|
||||
{ label: "Instagram", icon: FaInstagram, href: "https://placeholder.com" },
|
||||
{ label: "LinkedIn", icon: FaLinkedin, href: "https://placeholder.com" },
|
||||
{ label: "Email", icon: FaRegEnvelope, href: "https://placeholder.com" },
|
||||
];
|
||||
|
||||
|
||||
@@ -26,13 +27,12 @@ export function FooterIntern() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<footer className="bg-white border-t border-gray-100 px-14 py-8 flex gap-12 flex-wrap">
|
||||
<footer className="bg-white border-t border-gray-300 px-14 py-8 flex gap-12 flex-wrap">
|
||||
{/* Brand info */}
|
||||
<div className="flex flex-col gap-2 flex-shrink-0 w-52">
|
||||
<p className="text-sm font-bold text-gray-900">Internify Logo</p>
|
||||
<address className="text-[12.5px] text-gray-400 leading-relaxed not-italic">
|
||||
Gedung Kultubai Selatan, Blok F<br />
|
||||
Jl. Telekomunikasi, Terusan Buah Batu Bandung<br />
|
||||
<div className="flex flex-col gap-2 flex-shrink-0 w-70">
|
||||
<address className="text-xs text-gray-800 leading-relaxed not-italic">
|
||||
Gedung Kultubai Selatan, Blok F
|
||||
Jl. Telekomunikasi, Terusan Buah Batu Bandung
|
||||
Jawa Barat, Indonesia. 40257
|
||||
</address>
|
||||
<div className="flex gap-2 mt-1">
|
||||
@@ -43,7 +43,7 @@ export function FooterIntern() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={label}
|
||||
className="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center text-gray-400 hover:bg-red-50 hover:text-red-600 transition-colors"
|
||||
className="w-10 h-10 border rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-red-50 hover:text-red-600 transition-colors"
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
</a>
|
||||
@@ -56,12 +56,12 @@ export function FooterIntern() {
|
||||
{Object.entries(footerLinks).map(([title, links]) => (
|
||||
<div key={title} className="flex flex-col gap-2.5 min-w-[110px]">
|
||||
<p className="text-[13px] font-bold text-gray-900">{title}</p>
|
||||
<ul className="flex flex-col gap-2">
|
||||
<ul className="flex flex-col gap-0.5">
|
||||
{links.map(({ label, path }) => (
|
||||
<li key={label}>
|
||||
<button
|
||||
onClick={() => navigate(path)}
|
||||
className="text-[12.5px] text-gray-400 hover:text-red-600 transition-colors text-left bg-transparent border-none p-0 cursor-pointer"
|
||||
className="text-[12.5px] text-gray-600 hover:text-red-800 transition-colors text-left bg-transparent border-none p-0 cursor-pointer"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
@@ -109,6 +109,16 @@ const Navbar = () => {
|
||||
>
|
||||
Internship
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate("/login-internify");
|
||||
setOpenMenu(false);
|
||||
}}
|
||||
className="block w-full text-left hover:text-blue-600 transition"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
+91
-227
@@ -1,234 +1,98 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Home, FolderKanban, Award, Settings, HelpCircle, LogOut, User, ChevronRight } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
const SidebarAdmin = () => {
|
||||
const navItems = [
|
||||
{ label: "Home", icon: Home, path: "/admin" },
|
||||
{ label: "List Projects", icon: FolderKanban, path: "/admin/projects" },
|
||||
{ label: "Interns", icon: User, path: "/admin/intern" },
|
||||
{ label: "Certificates", icon: Award, path: "/admin/certificates" },
|
||||
];
|
||||
|
||||
const prefItems = [
|
||||
{ label: "Settings", icon: Settings, path: "/admin/settings" },
|
||||
{ label: "FAQ", icon: HelpCircle, path: "/admin/faq" },
|
||||
{ label: "Logout", icon: LogOut, path: "/admin/logout", danger: true },
|
||||
];
|
||||
|
||||
function SidebarIntern() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [active, setActive] = useState("/admin");
|
||||
|
||||
const handleNav = (path: string) => {
|
||||
setActive(path);
|
||||
navigate(path);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-[#EDF2F7] w-[300px] min-h-[87vh] flex flex-col justify-between p-4">
|
||||
<div className="container-sidebar">
|
||||
<h2 className="font-semibold text-[#2C5282] text-[20px]">Menu</h2>
|
||||
<div className="list-of-sidebar flex flex-col p-7 gap-4">
|
||||
<div
|
||||
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||
location.pathname === "/dashboard"
|
||||
? "bg-[#D3DFEA] font-bold"
|
||||
: "hover:bg-[#D3DFEA] "
|
||||
<aside className="w-[279px] 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-px bg-gray-100 my-1" />
|
||||
|
||||
{/* Main nav */}
|
||||
<nav className="flex flex-col gap-0.5">
|
||||
{navItems.map(({ label, icon: Icon, path }) => {
|
||||
const isActive = active === 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
|
||||
? "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"
|
||||
}`}
|
||||
onClick={() => navigate("/dashboard")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
className="size-8 text-[#2C5282]"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 3 L5 8 A4 4 0 0 0 3 11 L3 15 A4 4 0 0 0 7 19 L17 19 A4 4 0 0 0 21 15 L21 11 A4 4 0 0 0 19 8 L12 3 Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 13 v4"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<h2 className="text-[#2C5282]">Dashboard</h2>
|
||||
</div>
|
||||
<div
|
||||
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||
location.pathname === "/product-list"
|
||||
? "bg-[#D3DFEA] font-bold"
|
||||
: "hover:bg-[#D3DFEA] "
|
||||
}`}
|
||||
onClick={() => navigate("/product-list")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-8 text-[#2C5282]"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"
|
||||
/>
|
||||
</svg>
|
||||
<h2 className="text-[#2C5282]">Project</h2>
|
||||
</div>
|
||||
<div
|
||||
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||
location.pathname === "/lowongan-list"
|
||||
? "bg-[#D3DFEA] font-bold"
|
||||
: "hover:bg-[#D3DFEA]"
|
||||
}`}
|
||||
onClick={() => navigate("/lowongan-list")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-8 text-[#2C5282]"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 10.5v6m3-3H9m4.06-7.19-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<h2 className="text-[#2C5282]">Lowongan</h2>
|
||||
</div>
|
||||
<div
|
||||
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||
location.pathname === "/partnership-admin"
|
||||
? "bg-[#D3DFEA] font-bold"
|
||||
: "hover:bg-[#D3DFEA]"
|
||||
}`}
|
||||
onClick={() => navigate("/partnership-admin")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-8 text-[#2C5282]"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<h2 className="text-[#2C5282]">Partnership</h2>
|
||||
</div>
|
||||
<div
|
||||
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||
location.pathname === "/internships-list"
|
||||
? "bg-[#D3DFEA] font-bold"
|
||||
: "hover:bg-[#D3DFEA]"
|
||||
}`}
|
||||
onClick={() => navigate("/internships-list")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-8 text-[#2C5282]"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M18 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0ZM3 19.235v-.11a6.375 6.375 0 0 1 12.75 0v.109A12.318 12.318 0 0 1 9.374 21c-2.331 0-4.512-.645-6.374-1.766Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<h2 className="text-[#2C5282]">Pelamar</h2>
|
||||
</div>
|
||||
<div
|
||||
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||
location.pathname === "/feedbacks-list"
|
||||
? "bg-[#D3DFEA] font-bold"
|
||||
: "hover:bg-[#D3DFEA]"
|
||||
}`}
|
||||
onClick={() => navigate("/feedback-list")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-8 text-[#2C5282]"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-5l-3 3-3-3H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z"
|
||||
/>
|
||||
<circle cx="8" cy="10" r="1.2" fill="currentColor" />
|
||||
<circle cx="12" cy="10" r="1.2" fill="currentColor" />
|
||||
<circle cx="16" cy="10" r="1.2" fill="currentColor" />
|
||||
</svg>
|
||||
|
||||
<h2 className="text-[#2C5282]">Feedback</h2>
|
||||
</div>
|
||||
<div
|
||||
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||
location.pathname === "/faq-list"
|
||||
? "bg-[#D3DFEA] font-bold"
|
||||
: "hover:bg-[#D3DFEA]"
|
||||
}`}
|
||||
onClick={() => navigate("/faq-list")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-8 text-[#2C5282]"
|
||||
>
|
||||
{/* Balon chat */}
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-5l-3 3-3-3H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z"
|
||||
/>
|
||||
{/* Tanda tanya */}
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.75 9.5a2.25 2.25 0 1 1 4.5 0c0 1.25-.75 1.75-1.5 1.85s-1 1-1 1.75"
|
||||
/>
|
||||
<circle cx="12" cy="16" r="0.6" fill="currentColor" />
|
||||
</svg>
|
||||
|
||||
<h2 className="text-[#2C5282]">FAQ</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="logout flex flex-row items-center cursor-pointer gap-4 p-3 rounded-2xl hover:bg-[#F8D2D2] hover:font-bold mx-7"
|
||||
onClick={() => {
|
||||
document.cookie =
|
||||
"token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;";
|
||||
navigate("/");
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-8 text-[#C3423F]"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15m-3 0-3-3m0 0 3-3m-3 3H15"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<h2 className="text-[#C3423F] text-[16px] font-semibold">Keluar</h2>
|
||||
</div>
|
||||
</div>
|
||||
<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" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
})}
|
||||
</nav>
|
||||
|
||||
export default SidebarAdmin;
|
||||
<div className="h-px bg-gray-100 my-1" />
|
||||
|
||||
{/* Preferences */}
|
||||
<p className="text-[10px] font-semibold tracking-widest text-gray-400 px-3 py-1.5">
|
||||
PREFERENCES
|
||||
</p>
|
||||
<nav className="flex flex-col gap-0.5">
|
||||
{prefItems.map(({ label, icon: Icon, path, danger }) => {
|
||||
const isActive = active === 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
|
||||
? "bg-red-50 text-red-600 font-semibold"
|
||||
: danger
|
||||
? "text-gray-400 hover:bg-red-50 hover:text-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>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default SidebarIntern
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
|
||||
const mockFAQData = [
|
||||
{
|
||||
id: 1,
|
||||
question: "How do I generate internship certificates?",
|
||||
answer: "Once an intern has completed all required project milestones and their final evaluation is approved by a manager, go to the 'Manage Certificates' tab. Select the intern's name, click 'Generate Certificate', and the system will automatically populate the official Internify template with their specific details."
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
question: "What are the technical requirements for interns?",
|
||||
answer: "Internify is a web-based LMS. Interns simply need a modern web browser (Chrome, Firefox, or Safari) and a stable internet connection. Some engineering modules may require local development environments, which are detailed in the specific project onboarding documentation."
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
question: "How often is the project dashboard updated?",
|
||||
answer: "The dashboard updates in real-time as interns submit tasks or participate in the forum. Weekly progress reports are automatically generated every Friday at 5:00 PM EST and can be exported as PDF files from the Dashboard view."
|
||||
}
|
||||
];
|
||||
|
||||
export default function FAQPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl px-6 py-6 font-sans">
|
||||
{/* Breadcrumb */}
|
||||
<div className="flex items-center text-xs text-gray-400 font-medium mb-1">
|
||||
<span className="cursor-pointer hover:underline" onClick={() => navigate("/")}>Home</span>
|
||||
<ChevronRight className="w-3.5 h-3.5 mx-1" />
|
||||
<span className="text-[#B30000] font-semibold">FAQ</span>
|
||||
</div>
|
||||
|
||||
{/* Header Title */}
|
||||
<div className="mb-10">
|
||||
<h1 className="text-3xl font-bold text-gray-900 tracking-tight">FAQ</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
Quick answers to common questions about the Internify LMS platform.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* FAQ Lists */}
|
||||
<div className="space-y-8 max-w-4xl">
|
||||
{mockFAQData.map((faq) => (
|
||||
<div key={faq.id} className="relative pl-6 group">
|
||||
|
||||
{/* Indikator Garis/Kotak Kecil Merah di Sisi Kiri sesuai gambar */}
|
||||
<div className="absolute left-0 top-1.5 w-1 h-3.5 bg-[#B30000] rounded-sm transition-transform duration-200 group-hover:scale-y-125"></div>
|
||||
|
||||
{/* Item Question */}
|
||||
<h2 className="text-[17px] font-bold text-[#8A0000] tracking-tight leading-snug mb-2">
|
||||
{faq.question}
|
||||
</h2>
|
||||
|
||||
{/* Item Answer */}
|
||||
<p className="text-sm text-gray-500 font-medium leading-relaxed tracking-normal">
|
||||
{faq.answer}
|
||||
</p>
|
||||
|
||||
{/* Divider Line tipis di bawah setiap item, kecuali item terakhir */}
|
||||
{faq.id !== mockFAQData.length && (
|
||||
<div className="border-b border-gray-100 pt-8"></div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ChevronRight, Check, Download, Info, Award } from "lucide-react";
|
||||
import { LuFileSpreadsheet } from "react-icons/lu"; // Alternatif icon task dari react-icons
|
||||
|
||||
const initialNotifications = [
|
||||
{
|
||||
id: 1,
|
||||
type: "new-task",
|
||||
title: "New Task Assigned",
|
||||
time: "Just now",
|
||||
isNew: true,
|
||||
group: "TODAY",
|
||||
description: "Mentor Sarah Johnson has pushed a new task: User Persona Analysis"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: "achievement",
|
||||
title: "Certificate Ready",
|
||||
time: "2 hours ago",
|
||||
isNew: false,
|
||||
group: "TODAY",
|
||||
description: "Your certificate for UI Engineering Phase 1 is now available for download!"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: "system",
|
||||
title: "System Maintenance",
|
||||
time: "2 days ago",
|
||||
isNew: false,
|
||||
group: "YESTERDAY",
|
||||
description: "Internify will undergo scheduled maintenance this Sunday from 02:00 AM to 04:00 AM GMT+7."
|
||||
}
|
||||
];
|
||||
|
||||
export default function NotificationList() {
|
||||
const navigate = useNavigate();
|
||||
const [notifications, setNotifications] = useState(initialNotifications);
|
||||
|
||||
const handleMarkAllRead = () => {
|
||||
setNotifications(prev => prev.map(n => ({ ...n, isNew: false })));
|
||||
alert("All notifications marked as read!");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl px-6 py-6 font-sans">
|
||||
{/* Breadcrumb */}
|
||||
<div className="flex items-center text-xs text-gray-400 font-medium mb-1">
|
||||
<span className="cursor-pointer hover:underline" onClick={() => navigate("/")}>Home</span>
|
||||
<ChevronRight className="w-3.5 h-3.5 mx-1" />
|
||||
<span className="text-[#B30000] font-semibold">Notification</span>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 tracking-tight">Notifications</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
Keep track of your interns' progress and system updates.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleMarkAllRead}
|
||||
className="flex items-center gap-1.5 self-start sm:self-center px-4 py-2 border border-gray-200 rounded-lg text-xs font-semibold text-gray-700 bg-white hover:bg-gray-50 transition-colors shadow-sm"
|
||||
>
|
||||
<Check className="w-4 h-4 text-green-600 stroke-[3]" />
|
||||
Mark all as read
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* List Group Section */}
|
||||
<div className="space-y-6">
|
||||
|
||||
{/* Render Group TODAY */}
|
||||
<div className="space-y-3">
|
||||
{notifications.filter(n => n.group === "TODAY").map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`relative flex items-start gap-4 p-5 bg-white border border-gray-100 rounded-xl shadow-[0_2px_10px_rgba(0,0,0,0.01)] ${
|
||||
item.type === 'new-task' ? 'border-l-4 border-l-[#B30000]' : ''
|
||||
}`}
|
||||
>
|
||||
{/* Icon Container */}
|
||||
<div className={`p-2.5 rounded-xl ${
|
||||
item.type === 'new-task' ? 'bg-red-50 text-red-600' : 'bg-gray-100 text-gray-600'
|
||||
}`}>
|
||||
{item.type === 'new-task' && <LuFileSpreadsheet className="w-5 h-5" />}
|
||||
{item.type === 'achievement' && <Check className="w-5 h-5 stroke-[2.5]" />}
|
||||
</div>
|
||||
|
||||
{/* Content text */}
|
||||
<div className="flex-grow space-y-1">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{item.isNew && (
|
||||
<span className="bg-[#B30000] text-white font-extrabold text-[9px] px-2 py-0.5 rounded uppercase tracking-wider">
|
||||
NEW
|
||||
</span>
|
||||
)}
|
||||
<h3 className="text-sm font-bold text-gray-900">{item.title}</h3>
|
||||
</div>
|
||||
|
||||
{/* Custom description coloring logic */}
|
||||
<p className="text-xs text-gray-500 font-medium leading-relaxed max-w-2xl">
|
||||
{item.type === 'new-task' ? (
|
||||
<>Mentor <span className="font-semibold text-gray-900">Sarah Johnson</span> has pushed a new task: <span className="italic font-semibold text-gray-800">User Persona Analysis</span></>
|
||||
) : item.type === 'achievement' ? (
|
||||
<>Your certificate for <span className="font-semibold text-gray-900">UI Engineering Phase 1</span> is now available for download!</>
|
||||
) : item.description}
|
||||
</p>
|
||||
|
||||
{/* Conditional Actions */}
|
||||
{item.type === 'new-task' && (
|
||||
<div className="flex items-center gap-2 pt-3">
|
||||
<button className="px-4 py-1.5 bg-[#B30000] hover:bg-[#990000] text-white text-xs font-bold rounded-lg shadow-sm transition-colors">
|
||||
View Task
|
||||
</button>
|
||||
<button className="px-4 py-1.5 border border-gray-200 text-gray-500 hover:text-gray-700 hover:bg-gray-50 text-xs font-semibold rounded-lg transition-colors">
|
||||
Later
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.type === 'achievement' && (
|
||||
<button className="flex items-center gap-1 text-xs font-bold text-[#B30000] hover:underline pt-2">
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
Download Certificate
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Time */}
|
||||
<span className="text-[11px] text-gray-400 font-medium whitespace-nowrap self-start md:absolute md:top-5 md:right-5">
|
||||
{item.time}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Header Section YESTERDAY */}
|
||||
<div className="space-y-3 pt-2">
|
||||
<h2 className="text-[10px] font-bold tracking-widest text-gray-400 uppercase">
|
||||
YESTERDAY
|
||||
</h2>
|
||||
|
||||
{notifications.filter(n => n.group === "YESTERDAY").map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="relative flex items-start gap-4 p-5 bg-gray-50 border border-gray-100 rounded-xl"
|
||||
>
|
||||
<div className="p-2.5 bg-gray-200 text-gray-500 rounded-xl">
|
||||
<Info className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-grow space-y-1">
|
||||
<h3 className="text-sm font-bold text-gray-700">{item.title}</h3>
|
||||
<p className="text-xs text-gray-500 font-medium leading-relaxed max-w-2xl">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-[11px] text-gray-400 font-medium whitespace-nowrap md:absolute md:top-5 md:right-5">
|
||||
{item.time}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ChevronRight } from "lucide-react"; // dari lucide-react
|
||||
|
||||
// Mock Data Profile awal
|
||||
const mockProfileData = {
|
||||
fullName: "JonathanKristina",
|
||||
email: "JonathanKristina@gmail.com",
|
||||
bio: "UIUX holic"
|
||||
};
|
||||
|
||||
export default function SettingsContent() {
|
||||
const navigate = useNavigate();
|
||||
const [formData, setFormData] = useState(mockProfileData);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSaveChanges = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// Simulasi penyimpanan data sukses
|
||||
alert("Changes saved successfully!");
|
||||
console.log("Saved Data:", formData);
|
||||
};
|
||||
|
||||
// Mendapatkan inisial huruf pertama untuk avatar profile gambar
|
||||
const initialLetter = formData.fullName ? formData.fullName.charAt(0).toUpperCase() : "U";
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-5xl px-6 py-6 font-sans">
|
||||
{/* Breadcrumb */}
|
||||
<div className="flex items-center text-xs text-gray-400 font-medium mb-1">
|
||||
<span className="cursor-pointer hover:underline" onClick={() => navigate("/")}>Home</span>
|
||||
<ChevronRight className="w-3.5 h-3.5 mx-1" />
|
||||
<span className="text-[#B30000] font-semibold">Settings</span>
|
||||
</div>
|
||||
|
||||
{/* Header Title */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-gray-900 tracking-tight">Settings</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
Manage and edit your profile and account information.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* White Card Container */}
|
||||
<div className="w-full bg-white rounded-xl border border-gray-100 shadow-[0_2px_12px_rgba(0,0,0,0.01)] overflow-hidden">
|
||||
|
||||
{/* Card Header */}
|
||||
<div className="p-6 border-b border-gray-100">
|
||||
<h2 className="text-base font-bold text-gray-900">Profile Information</h2>
|
||||
<p className="text-xs text-gray-400 mt-0.5">
|
||||
Update your photo and personal details.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Card Body & Form */}
|
||||
<form onSubmit={handleSaveChanges} className="p-6 space-y-6">
|
||||
|
||||
{/* Profile Photo Display Row */}
|
||||
<div className="flex items-center gap-4 py-2">
|
||||
<div className="w-16 h-16 bg-gray-100 border border-gray-200 rounded-xl flex items-center justify-center shadow-sm">
|
||||
<span className="text-2xl font-black text-gray-900 tracking-tighter">
|
||||
{initialLetter}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xs font-bold text-gray-900">Profile Photo</h3>
|
||||
<p className="text-sm text-gray-600 mt-0.5">{formData.fullName}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border-t border-dashed border-gray-200" />
|
||||
|
||||
{/* Grid Inputs (Full Name & Email Address) */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
{/* Field: Full Name */}
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold tracking-wider text-gray-400 uppercase block">
|
||||
Full Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="fullName"
|
||||
value={formData.fullName}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 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 transition-colors bg-white font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Field: Email Address */}
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold tracking-wider text-gray-400 uppercase block">
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 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 transition-colors bg-white font-medium"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Field: Professional Bio */}
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold tracking-wider text-gray-400 uppercase block">
|
||||
Professional Bio
|
||||
</label>
|
||||
<textarea
|
||||
name="bio"
|
||||
rows={4}
|
||||
value={formData.bio}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 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 transition-colors bg-white font-medium resize-none"
|
||||
/>
|
||||
<p className="text-[11px] text-gray-400 font-normal pt-0.5">
|
||||
Brief description for your profile. URLs and @mentions are allowed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Form Action Footer Row */}
|
||||
<div className="flex justify-end pt-4">
|
||||
<button
|
||||
type="submit"
|
||||
className="px-6 py-2 bg-[#B30000] hover:bg-[#990000] text-white font-bold text-xs rounded-lg shadow-sm transition-colors uppercase tracking-wider"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import HomeHeader from "./components/HomeHeader";
|
||||
import HomeProjectList from "./components/HomeProjectList";
|
||||
import HomeCalendar from "./components/HomeCalendar";
|
||||
import TaskTimeline from "./components/TaskTImeline";
|
||||
import TaskTimeline from "./components/TaskTimeline";
|
||||
|
||||
// PR: tambahin breadcrumb
|
||||
export default function HomeInternPage() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { mockProjects } from '../../../lib/mockData';
|
||||
import ForumTab from './ProjectForum';
|
||||
import ParticipantsTab from './ProjectParticipants';
|
||||
import ForumTab from './ForumTab';
|
||||
import ParticipantsTab from './ParticipantTab';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
|
||||
// ─── ProjectDetailPage ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
import { certificateData } from "../../../lib/mockData";
|
||||
import CertificateAvailable from "./components/CertificateAvailabe";
|
||||
import CertificateNotAvailable from "./components/CertificateNotAvailable";
|
||||
|
||||
const SertificatePage = () => {
|
||||
const data = certificateData; // ganti isReceived di object atas
|
||||
|
||||
return (
|
||||
<div className="px-15 py-10">
|
||||
<div className="px-15 py-10 bg-white min-h-screen">
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex flex-col gap-1">
|
||||
|
||||
<h1 className="text-4xl font-bold mb-2">Certificates</h1>
|
||||
<p className="font-light text-gray-500">View and download internship certificates</p>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
|
||||
{/* Content */}
|
||||
<div>
|
||||
{data.isReceived ? (
|
||||
<CertificateAvailable data={data} />
|
||||
) : (
|
||||
<CertificateNotAvailable
|
||||
progress={data.projectProgress}
|
||||
remainingTasks={data.remainingTasks}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SertificatePage
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { useState } from "react";
|
||||
import { Download, History , Link, Award, Printer } from "lucide-react";
|
||||
import { type CertificateData, type CertificateHistory } from "../../../../lib/mockData";
|
||||
import certificateImg from "../../../../assets/certificate.png";
|
||||
|
||||
export default function CertificateAvailable({ data }: { data: CertificateData }) {
|
||||
const { certificate, history } = data;
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopyLink = () => {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row gap-6 items-start">
|
||||
{/* LEFT: Certificate Preview + Actions */}
|
||||
<div className="w-full lg:w-4/6 flex flex-col gap-4">
|
||||
{/* Certificate Card */}
|
||||
<div className="flex rounded-xl p-10 mb-3 border border-gray-300 shadow-md items-center justify-center">
|
||||
<img
|
||||
src={certificateImg}
|
||||
alt="Certificate"
|
||||
className="rounded-xl w-[550px] h-[350px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Program Summary */}
|
||||
<div className="grid grid-cols-2 gap-5">
|
||||
<div className="bg-white rounded-2xl border border-gray-200 shadow-sm p-5">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<h3 className="font-semibold text-gray-800 text-sm">Ringkasan Program</h3>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ label: "Nama Program", value: certificate.programName },
|
||||
{ label: "Durasi", value: certificate.duration },
|
||||
{
|
||||
label: "Nilai Akhir",
|
||||
value: (
|
||||
<span className="text-red-600 font-bold">
|
||||
{certificate.finalScore}/100 ({certificate.grade})
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "Status",
|
||||
value: (
|
||||
<span className="bg-green-100 text-green-700 text-xs font-bold px-2 py-0.5 rounded-full">
|
||||
{certificate.status}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
].map((row) => (
|
||||
<div
|
||||
key={row.label}
|
||||
className="flex justify-between items-center text-sm border-b border-gray-50 pb-2 last:border-0"
|
||||
>
|
||||
<span className="text-gray-500">{row.label}</span>
|
||||
<span className="text-gray-800 font-medium">{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-2xl border border-gray-200 shadow-sm p-5">
|
||||
<div className="flex flex-col gap-3">
|
||||
|
||||
{/* Unduh Sertifikat */}
|
||||
<button className="flex items-center justify-center gap-2 w-full bg-red-700 hover:bg-red-800 active:scale-95 transition-all text-white font-semibold py-3.5 rounded-xl text-sm">
|
||||
<Download className="w-4 h-4" />
|
||||
Unduh Sertifikat (PDF)
|
||||
</button>
|
||||
|
||||
{/* Bagikan ke LinkedIn */}
|
||||
<button className="flex items-center justify-center gap-2 w-full bg-blue-600 hover:bg-blue-700 active:scale-95 transition-all text-white font-semibold py-3.5 rounded-xl text-sm">
|
||||
Bagikan ke LinkedIn
|
||||
</button>
|
||||
|
||||
{/* Cetak & Salin Link */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button className="flex flex-col items-center justify-center gap-1.5 border border-gray-200 hover:bg-gray-50 active:scale-95 transition-all text-red-700 font-medium py-3.5 rounded-xl text-sm">
|
||||
<Printer className="w-5 h-5" />
|
||||
Cetak
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCopyLink}
|
||||
className="flex flex-col items-center justify-center gap-1.5 border border-gray-200 hover:bg-gray-50 active:scale-95 transition-all text-red-700 font-medium py-3.5 rounded-xl text-sm"
|
||||
>
|
||||
<Link className="w-5 h-5" />
|
||||
{copied ? "Copied!" : "Salin Link"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT: Certificate History */}
|
||||
<div className="w-full lg:w-2/6 flex-shrink-0">
|
||||
<div className="bg-white rounded-2xl border border-gray-200 shadow-sm p-5">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<History className="w-5 h-5 text-red-700" />
|
||||
<h3 className="font-semibold text-gray-800 text-lg">Certificate History</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{history.map((item: CertificateHistory) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="flex flex-col gap-3 p-3 bg-white border border-gray-200 shadow-sm rounded-xl hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
>
|
||||
<div className="w-full flex justify-center">
|
||||
<img
|
||||
src={certificateImg}
|
||||
alt="Certificate"
|
||||
className="rounded-xl h-[200px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<p className="text-xs">{item.month}</p>
|
||||
<p className="text-sm font-semibold text-gray-800 leading-tight">{item.title}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Grade: {item.grade}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="w-full my-3 flex justify-end">
|
||||
<button className="text-sm text-red-500 font-semibold hover:underline">
|
||||
See more
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Lock, Clock } from "lucide-react";
|
||||
|
||||
export default function CertificateNotAvailable({ progress, remainingTasks }: { progress: number; remainingTasks: number }) {
|
||||
return (
|
||||
<div className="bg-white w-full rounded-2xl border border-gray-100 shadow-sm p-20 flex flex-col items-center text-center max-w-3/5 mx-auto z-10">
|
||||
{/* Icon */}
|
||||
<div className="relative mb-6">
|
||||
<div className=" w-33 h-33 border-2 border-dashed border-gray-600 rounded-xl flex justify-center items-center">
|
||||
|
||||
<div className="w-20 h-20 flex items-center rounded-lg justify-center bg-gray-300">
|
||||
<Lock className="w-10 h-10 text-red-700" />
|
||||
</div>
|
||||
<div className="absolute -bottom-2 -right-2 w-6 h-6 bg-amber-500 rounded- flex items-center justify-center shadow-md">
|
||||
<Clock className="w-4 h-4 text-yellow-800" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-3">Sertifikat Belum Tersedia</h2>
|
||||
<p className="text-gray-500 text-sm leading-relaxed mb-6">
|
||||
Anda harus menyelesaikan semua tugas proyek sebelum dapat mengunduh sertifikat kelulusan.
|
||||
Silakan periksa kembali progres tugas Anda di menu Proyek.
|
||||
</p>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-sm font-semibold text-gray-700">Progres Proyek</span>
|
||||
<span className="text-sm font-bold text-red-700">{progress}%</span>
|
||||
</div>
|
||||
<div className="w-full h-2.5 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-red-700 rounded-full transition-all duration-700"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mt-2 text-left">
|
||||
Selesaikan {remainingTasks} tugas lagi untuk membuka akses.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ const HomeCalendar = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className=" px-13 py-5">
|
||||
<div className=" px-13 py-5 border border-gray-200 rounded-2xl">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-base font-semibold text-gray-800">
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import { Code2, ChessKing, Beef } from 'lucide-react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { mockProjects } from '../../../../lib/mockData'
|
||||
|
||||
interface Project {
|
||||
name: string
|
||||
role: string
|
||||
logo: React.ElementType
|
||||
iconBg: string
|
||||
iconColor: string
|
||||
}
|
||||
|
||||
const HomeProjectList = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Task } from "../../../../lib/mockData";
|
||||
import { ArrowDownNarrowWide } from "lucide-react";
|
||||
|
||||
const formatDate = (date: Date) =>
|
||||
new Date(date).toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric' });
|
||||
@@ -9,10 +10,11 @@ export default function ProjectTimeline({ tasks }: { tasks: Task[] }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 mb-4">
|
||||
<div className="flex items-center gap-1.5 mb-4 justify-between">
|
||||
<span className="font-semibold text-sm text-[#1a1a1a]">
|
||||
Timeline
|
||||
</span>
|
||||
<ArrowDownNarrowWide className="w-5 h-5 text-gray-500"/>
|
||||
</div>
|
||||
|
||||
<ul className="relative pl-4">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
import Header from './Header';
|
||||
import Header from './mentor/components/Header';
|
||||
import SidebarIntern from './intern/components/SidebarIntern';
|
||||
import SidebarAdmin from '../../Layout/SidebarAdmin';
|
||||
import FooterIntern from './intern/components/FooterIntern';
|
||||
import FooterIntern from '../../Layout/Footer-Internify';
|
||||
|
||||
export const InternLayout = () => {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { mockProjects } from '../../../lib/mockData'
|
||||
import MentorProjectCard from './components/MentorProjectCard'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { useNavigate } from 'react-router-dom' // Import useNavigate
|
||||
|
||||
const MentorProjectsPage = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleJoinProject = () => {
|
||||
// Navigasi ke halaman tujuan, silakan ganti '/path-tujuan' sesuai rute kamu
|
||||
navigate('/path-tujuan')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-14 py-15 bg-gray-50 min-h-screen">
|
||||
{/* Breadcrumb simulasi sesuai gambar */}
|
||||
<div className="text-xs text-gray-500 mb-2">
|
||||
<span className="text-red-600 font-medium">Home</span> > <span>List Projects</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1 mb-6">
|
||||
<h1 className="text-4xl font-bold text-gray-900">List Projects</h1>
|
||||
<p className="text-lg text-gray-500">Create, manage, and monitor ongoing projects and assignments.</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-4 items-stretch">
|
||||
{/* Render List Project */}
|
||||
{mockProjects.map(project => (
|
||||
<MentorProjectCard key={project.id} {...project} />
|
||||
))}
|
||||
|
||||
{/* Tombol "Join New Project" dengan Border Putus-putus */}
|
||||
<button
|
||||
onClick={handleJoinProject}
|
||||
className="w-[180px] min-h-[230px] border-2 border-dashed border-gray-400 rounded-xl flex flex-col items-center justify-center gap-3 p-4 bg-white hover:bg-gray-100 hover:border-red-600 transition-colors group"
|
||||
>
|
||||
{/* Lingkaran Plus */}
|
||||
<div className="w-12 h-12 rounded-full border border-red-600 flex items-center justify-center text-red-600 group-hover:bg-red-50 transition-colors">
|
||||
<Plus size={24} />
|
||||
</div>
|
||||
{/* Teks Deskripsi */}
|
||||
<span className="text-sm font-semibold text-red-600 text-center leading-tight">
|
||||
Join New Project
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MentorProjectsPage
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
const HomeMentorPage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeMentorPage
|
||||
@@ -0,0 +1,24 @@
|
||||
import HomeHeader from '../intern/components/HomeHeader'
|
||||
import HomeCalendar from '../intern/components/HomeCalendar'
|
||||
import HomeMentorProject from './components/HomeMentorProject'
|
||||
|
||||
const HomeMentorPage = () => {
|
||||
return (
|
||||
<div className="flex-1 px-14 py-10 bg-gray-50">
|
||||
<HomeHeader />
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<HomeMentorProject />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="bg-white rounded-xl">
|
||||
<HomeCalendar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeMentorPage
|
||||
@@ -0,0 +1,5 @@
|
||||
export default function MentorInternPage() {
|
||||
return(
|
||||
<div></div>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
const MentorDetailProject = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MentorDetailProject
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
const MentorInternPage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MentorInternPage
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
const MentorProjectsPage = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MentorProjectsPage
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Project } from '../../../lib/mockData';
|
||||
import ProjectTimeline from '../intern/components/ProjectTimeline';
|
||||
import HeroProject from '../intern/components/Hero-project';
|
||||
import MentorTaskCard from './components/MentorTaskCard';
|
||||
|
||||
export default function MentorForumTab({ project }: { project: Project }) {
|
||||
return (
|
||||
<div>
|
||||
{/* Hero banner */}
|
||||
<HeroProject title={project.name} description={project.description} />
|
||||
|
||||
{/* Timeline + Task list */}
|
||||
<div className="flex gap-7 items-start">
|
||||
<div className="w-1/5 bg-white rounded-xl p-5 shadow-md shrink-0">
|
||||
<ProjectTimeline tasks={project.tasks} />
|
||||
</div>
|
||||
|
||||
<div className="w-4/5">
|
||||
{project.tasks.map((task, idx) => (
|
||||
<MentorTaskCard key={idx} task={task} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Project, Intern } from '../../../lib/mockData';
|
||||
|
||||
export default function InternsTab({ project }: { project: Project }) {
|
||||
return (
|
||||
<div>
|
||||
{/* Mentor */}
|
||||
<div className="mb-8">
|
||||
<h3 className=" font-bold text-base text-[#1a1a1a] mb-3.5">
|
||||
Mentor
|
||||
</h3>
|
||||
<div className="border-b border-[#eee] pb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className=" text-sm text-[#333]">
|
||||
{project.mentor}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Members */}
|
||||
<div>
|
||||
<h3 className="font-bold text-base text-[#1a1a1a] mb-3.5">
|
||||
Members
|
||||
</h3>
|
||||
{project.interns.map((intern: Intern, idx: number) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`flex items-center justify-between py-3 ${
|
||||
idx < project.interns.length - 1 ? 'border-b border-[#f0f0f0]' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className=" text-sm text-[#333]">
|
||||
{intern.email}
|
||||
</span>
|
||||
</div>
|
||||
<span className=" text-[13px] text-[#555] font-semibold">
|
||||
{intern.role}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { mockProjects } from '../../../lib/mockData';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import MentorForumTab from './ProjectForumTab';
|
||||
import InternsTab from './ProjectsInternTab';
|
||||
import TaskTab from './TaskTab';
|
||||
|
||||
// ─── ProjectDetailPage ─────────────────────────────────────────────────────────
|
||||
export default function ProjectDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [activeTab, setActiveTab] = useState<'forum' | 'participants' | 'Task'>('forum');
|
||||
|
||||
const handleBack = () => {
|
||||
if (window.history.length > 1) {
|
||||
navigate(-1);
|
||||
} else {
|
||||
navigate('/intern/projects');
|
||||
}
|
||||
};
|
||||
|
||||
const project = mockProjects.find((p) => p.id === Number(id));
|
||||
|
||||
if (!project) {
|
||||
return (
|
||||
<div className="p-10 text-[#888]">
|
||||
Project tidak ditemukan.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const tabClass = (tab: string) =>
|
||||
`pr-0 py-2.5 mr-7 text-sm bg-transparent border-0 cursor-pointer transition-colors duration-150 border-b-2 ${activeTab === tab
|
||||
? 'font-bold text-[#C0392B] border-[#C0392B]'
|
||||
: 'font-medium text-[#666] border-transparent'
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 min-h-screen">
|
||||
<div className="px-24 py-10">
|
||||
|
||||
{/* Back Button */}
|
||||
<button
|
||||
onClick={handleBack}
|
||||
className="flex items-center gap-2 mb-6 text-sm font-medium text-[#666] hover:text-[#C0392B] transition-colors duration-150 group"
|
||||
>
|
||||
<span className='flex flex-row gap-2'>
|
||||
<ChevronLeft width={20} height={20} className="transition-transform duration-150 group-hover:-translate-x-1" />
|
||||
Kembali ke Daftar Project
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex mb-4 border-b-1 border-gray-300">
|
||||
<button className={tabClass('forum')} onClick={() => setActiveTab('forum')}>
|
||||
Forum
|
||||
</button>
|
||||
<button className={tabClass('participants')} onClick={() => setActiveTab('participants')}>
|
||||
Participants
|
||||
</button>
|
||||
<button className={tabClass('Task')} onClick={() => setActiveTab('Task')}>
|
||||
Tasks
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tab content */}
|
||||
{(() => {
|
||||
switch (activeTab) {
|
||||
case 'forum':
|
||||
return <MentorForumTab project={project} />;
|
||||
case 'participants':
|
||||
return <InternsTab project={project} />;
|
||||
case 'Task':
|
||||
return <TaskTab project={project} />; // Tambahkan komponen TaskTab di sini
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { Project, Task } from '../../../lib/mockData';
|
||||
import { ClipboardList, Pencil, Trash2 } from 'lucide-react';
|
||||
|
||||
export default function TaskTab({ project }: { project: Project }) {
|
||||
|
||||
const handleAddTask = () => {
|
||||
console.log('Trigger modal / halaman tambah tugas');
|
||||
};
|
||||
|
||||
const handleEditTask = (taskTitle: string) => {
|
||||
console.log('Edit task:', taskTitle);
|
||||
};
|
||||
|
||||
const handleDeleteTask = (taskTitle: string) => {
|
||||
console.log('Delete task:', taskTitle);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 min-h-[400px]">
|
||||
{/* Header Section */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-3xl font-bold text-slate-800 tracking-tight">Tasks</h2>
|
||||
|
||||
<button
|
||||
onClick={handleAddTask}
|
||||
className="bg-[#C0392B] hover:bg-[#A93226] text-white px-6 py-2.5 rounded-full font-bold text-sm transition-colors shadow-sm"
|
||||
>
|
||||
Add Task
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr className="border-gray-200/80 mb-4" />
|
||||
|
||||
{/* Task List Section */}
|
||||
<div className="flex flex-col">
|
||||
{project.tasks && project.tasks.length > 0 ? (
|
||||
project.tasks.map((task: Task, idx: number) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`flex items-center justify-between py-4 group ${
|
||||
idx < project.tasks.length - 1 ? 'border-b border-gray-100' : ''
|
||||
}`}
|
||||
>
|
||||
{/* Task Title & Icon */}
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<ClipboardList size={22} className="text-slate-600 shrink-0" />
|
||||
<span className="text-base text-slate-700 font-medium truncate">
|
||||
{task.title}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons (Edit & Delete) */}
|
||||
<div className="flex items-center gap-4 text-slate-600 shrink-0">
|
||||
<button
|
||||
onClick={() => handleEditTask(task.title)}
|
||||
className="hover:text-blue-600 transition-colors p-1"
|
||||
title="Edit Task"
|
||||
>
|
||||
<Pencil size={20} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteTask(task.title)}
|
||||
className="hover:text-red-600 transition-colors p-1"
|
||||
title="Delete Task"
|
||||
>
|
||||
<Trash2 size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-10 text-gray-400 text-sm">
|
||||
Belum ada tugas yang dibuat untuk proyek ini.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Bell } from "lucide-react"
|
||||
import humiclogo from "../../assets/logo.png";
|
||||
import humiclogo from "../../../../assets/logo.png";
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { mockProjects } from '../../../../lib/mockData'
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
const HomeMentorProject = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex flex-row justify-between mb-3 px-5 items-center'>
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-4">Projects</h2>
|
||||
<button className='bg-gray-300 px-2 py-1 rounded-xl font-bold hover:bg-gray-400'>
|
||||
<span className='text-sm flex flex-row items-center gap-3'>
|
||||
<Plus className='w-3 h-3'/>
|
||||
Add
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{mockProjects.map((project, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => navigate(`/intern/projects/${project.id}`)}
|
||||
className="flex items-center gap-4 border border-gray-200 rounded-lg px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors duration-150"
|
||||
>
|
||||
<div className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 bg-gray-300`}>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-gray-800">{project.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeMentorProject
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Trash2, UsersRound } from 'lucide-react'
|
||||
import type { Intern } from '../../../../lib/mockData'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
interface ProjectCardProps {
|
||||
id: number | string
|
||||
name: string
|
||||
interns: Intern[]
|
||||
thumbnailUrl?: string
|
||||
}
|
||||
|
||||
export default function MentorProjectCard({
|
||||
id,
|
||||
name,
|
||||
interns,
|
||||
thumbnailUrl
|
||||
}: ProjectCardProps) {
|
||||
const navigate = useNavigate();
|
||||
const handleCardClick = () => {
|
||||
navigate(`/admin/projects/${id}`)
|
||||
};
|
||||
|
||||
const handleDeleteClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
console.log('Delete project dengan id:', id)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onClick={handleCardClick}
|
||||
className="flex flex-col bg-white rounded-xl overflow-hidden shadow-sm border border-gray-100 w-[180px] min-h-[270px]hover:bg-gray-50 transition-colors">
|
||||
{/* Thumbnail */}
|
||||
<div className="h-[150px] bg-gradient-to-tr from-red-700 to-red-800 overflow-hidden shrink-0">
|
||||
{thumbnailUrl && (
|
||||
<img src={thumbnailUrl} alt={name} className="w-full h-full object-cover" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content*/}
|
||||
<div className="p-3 flex flex-col gap-2 flex-1 justify-between">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-sm font-semibold text-gray-800 leading-tight line-clamp-2">
|
||||
{name}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Interns Count */}
|
||||
<div className="flex items-center gap-2 mt-auto">
|
||||
<UsersRound className='text-gray-400 w-4 h-4' />
|
||||
<span className="text-xs text-gray-500 truncate">
|
||||
{interns.length}/{interns.length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t border-gray-300 text-gray-400 flex justify-end p-2 bg-gray-50/50">
|
||||
<button onClick={handleDeleteClick} className="hover:text-red-600 transition-colors">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Task } from "../../../../lib/mockData";
|
||||
|
||||
const formatDate = (date: Date) =>
|
||||
new Date(date).toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric' });
|
||||
|
||||
export default function MentorTaskCard({ task }: { task: Task }) {
|
||||
return (
|
||||
<div className="border border-[#eee] rounded-[10px] px-5 py-4 mb-3 bg-white shadow-[0_1px_4px_rgba(0,0,0,0.05)] flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className=" text-[11px] text-[#888] mb-1">
|
||||
Deadline : {formatDate(task.deadline.date)} at {task.deadline.time}
|
||||
</div>
|
||||
<div className=" font-bold text-[15px] text-[#1a1a1a] mb-1">
|
||||
{task.title}
|
||||
</div>
|
||||
<div className=" text-[13px] text-[#666]">
|
||||
{task.description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="shrink-0">
|
||||
<span
|
||||
className={`inline-block font-bold text-xs px-2.5 py-[3px] rounded-md tracking-[0.5px] border ${
|
||||
task.isSubmitted
|
||||
? "bg-[#27AE60] text-white border-[#27AE60]"
|
||||
: "bg-white text-[#1a1a1a] border-gray-400"
|
||||
}`}
|
||||
>
|
||||
{task.submittedCount}/{task.totalInterns}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { useState } from "react";
|
||||
import logo from "../../assets/logo.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Mail, Lock, Eye, EyeOff } from 'lucide-react';
|
||||
|
||||
|
||||
// Data user dummy untuk keperluan simulasi navigasi frontend
|
||||
const DataUsers = [
|
||||
{ "Name": "John Doe", "Email": "john@email.com", "Password": "password123", "role": "intern" },
|
||||
{ "Name": "Jane Smith", "Email": "jane@email.com", "Password": "password123", "role": "mentor" },
|
||||
];
|
||||
|
||||
export default function InternifyLogin() {
|
||||
const navigate = useNavigate();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const handleSignIn = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
||||
if (!email || !password) {
|
||||
setError("Email dan password wajib diisi.");
|
||||
return;
|
||||
}
|
||||
|
||||
const user = DataUsers.find(
|
||||
(u) => u.Email.toLowerCase() === email.toLowerCase() && u.Password === password
|
||||
);
|
||||
|
||||
if (user) {
|
||||
// 3. Gunakan navigate() sesuai dengan role masing-masing
|
||||
if (user.role === "admin") {
|
||||
navigate("/admin");
|
||||
} else if (user.role === "intern") {
|
||||
navigate("/intern");
|
||||
} else if (user.role === "mentor") {
|
||||
navigate("/mentor");
|
||||
} else {
|
||||
setError("Role user tidak dikenali.");
|
||||
}
|
||||
} else {
|
||||
setError("Email atau password salah.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#F9F9F9] flex flex-col justify-between items-center py-8 px-4 font-sans selection:bg-red-200">
|
||||
<div className="hidden md:block"></div>
|
||||
|
||||
{/* Main Container */}
|
||||
<div className="w-full max-w-[440px] flex flex-col items-center">
|
||||
{/* Logo Section */}
|
||||
<div className="text-center mb-6">
|
||||
<div className="flex items-center justify-center gap-2 font-bold text-2xl tracking-tight text-black">
|
||||
<img src={logo} alt="Logo" className="w-32 h-20" />
|
||||
</div>
|
||||
<p className="text-[10px] tracking-widest text-yellow-950 font-semibold mt-1 uppercase">
|
||||
HUMIC LMS PORTAL
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Card Login */}
|
||||
<div className="max-w-md bg-white rounded-2xl border border-gray-100 p-8 md:p-12">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-1">Welcome back</h2>
|
||||
<p className="text-sm text-gray-500 mb-6">Access your dashboard with your credentials.</p>
|
||||
|
||||
<form onSubmit={handleSignIn} className="space-y-4">
|
||||
{/* Input Email */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-medium text-gray-700">Email address</label>
|
||||
<div className="relative">
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
|
||||
<Mail className="w-4 h-4 ml-1" />
|
||||
</span>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="name@company.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2.5 bg-white border border-gray-200 rounded-lg text-sm placeholder-gray-400 focus:outline-none focus:border-red-500 focus:ring-1 focus:ring-red-500 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input Password */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-medium text-gray-700">Password</label>
|
||||
<div className="relative">
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
|
||||
<Lock className="w-4 h-4 ml-1" />
|
||||
</span>
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="••••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full pl-10 pr-10 py-2.5 bg-white border border-gray-200 rounded-lg text-sm placeholder-gray-400 focus:outline-none focus:border-red-500 focus:ring-1 focus:ring-red-500 transition-colors"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
{showPassword ? (
|
||||
<Eye className="w-4 h-4" />
|
||||
) : (
|
||||
<EyeOff className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
{error && (
|
||||
<p className="text-xs text-red-600 bg-red-50 p-2 rounded-md font-medium">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Button Sign In */}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-[#B30000] hover:bg-[#990000] text-white font-medium text-sm py-2.5 rounded-lg transition-colors shadow-sm mt-2"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Forgot Password */}
|
||||
<div className="text-center mt-6">
|
||||
<a href="#" className="text-xs font-semibold text-red-600 hover:underline">
|
||||
Forgot Password?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Divider & Support */}
|
||||
<div className="mt-1">
|
||||
<div className="relative flex py-2 items-center">
|
||||
<div className="flex-grow border-t border-gray-100"></div>
|
||||
<span className="flex-shrink mx-4 text-[10px] font-bold tracking-wider text-gray-400 uppercase">
|
||||
SUPPORT
|
||||
</span>
|
||||
<div className="flex-grow border-t border-gray-100"></div>
|
||||
</div>
|
||||
|
||||
<button className="w-full mt-2 flex items-center justify-center gap-2 border border-gray-200 rounded-full py-1.5 text-xs text-gray-600 hover:bg-gray-50 transition-colors font-medium">
|
||||
<svg className="w-3.5 h-3.5 text-gray-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Frequently Asked Questions
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Section */}
|
||||
<div className="text-center text-[11px] text-gray-400 space-y-1">
|
||||
<p>© 2024 Internify. CoE Humic Engineering.</p>
|
||||
<div className="space-x-2">
|
||||
<a href="#" className="hover:underline">Privacy Policy</a>
|
||||
<span>•</span>
|
||||
<a href="#" className="hover:underline">Terms of Service</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
+57
-11
@@ -24,6 +24,8 @@ export interface Task {
|
||||
description: string;
|
||||
deadline: Deadline;
|
||||
isSubmitted: boolean;
|
||||
submittedCount: number;
|
||||
totalInterns: number;
|
||||
}
|
||||
|
||||
export interface Intern {
|
||||
@@ -57,24 +59,32 @@ export const mockProjects: Project[] = [
|
||||
title: "Laporan tugas",
|
||||
description: "Berisi design ulux, dan user flow dari aplikasi internify",
|
||||
deadline: { date: new Date(2026, 4, 1), label: "Laporan tugas – 1 Mei", time: "23:59 PM" },
|
||||
submittedCount: 6,
|
||||
totalInterns: 6,
|
||||
isSubmitted: true,
|
||||
},
|
||||
{
|
||||
title: "Laporan tugas 1",
|
||||
description: "Berisi design ulux, dan user flow dari aplikasi internify",
|
||||
deadline: { date: new Date(2026, 4, 14), label: "Laporan tugas 1 – 14 Mei", time: "23:59 PM" },
|
||||
submittedCount: 2,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
{
|
||||
title: "Laporan tugas 2",
|
||||
description: "Berisi design ulux, dan user flow dari aplikasi internify",
|
||||
deadline: { date: new Date(2026, 4, 28), label: "Laporan tugas 2 – 28 Mei", time: "23:59 PM" },
|
||||
submittedCount: 3,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
{
|
||||
title: "Laporan tugas 3",
|
||||
description: "Berisi wireframe halaman dashboard aplikasi internify",
|
||||
deadline: { date: new Date(2026, 5, 5), label: "Laporan tugas 3 – 5 Jun", time: "23:59 PM" },
|
||||
submittedCount: 0,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
],
|
||||
@@ -101,24 +111,32 @@ export const mockProjects: Project[] = [
|
||||
title: "Analisis Kebutuhan Sistem",
|
||||
description: "Dokumentasi kebutuhan fungsional dan non-fungsional platform rekrutmen",
|
||||
deadline: { date: new Date(2026, 4, 5), label: "Analisis Kebutuhan – 5 Mei", time: "23:59 PM" },
|
||||
submittedCount: 6,
|
||||
totalInterns: 6,
|
||||
isSubmitted: true,
|
||||
},
|
||||
{
|
||||
title: "Desain Database Schema",
|
||||
description: "Rancangan struktur database untuk kandidat, lowongan, dan proses seleksi",
|
||||
deadline: { date: new Date(2026, 4, 10), label: "Desain Database – 10 Mei", time: "23:59 PM" },
|
||||
submittedCount: 6,
|
||||
totalInterns: 6,
|
||||
isSubmitted: true,
|
||||
},
|
||||
{
|
||||
title: "Implementasi Auth Module",
|
||||
description: "Pengembangan sistem autentikasi dan otorisasi multi-role",
|
||||
deadline: { date: new Date(2026, 4, 18), label: "Auth Module – 18 Mei", time: "23:59 PM" },
|
||||
submittedCount: 3,
|
||||
totalInterns: 6,
|
||||
isSubmitted: true,
|
||||
},
|
||||
{
|
||||
title: "Modul Posting Lowongan",
|
||||
description: "Fitur buat, edit, dan publish lowongan pekerjaan",
|
||||
deadline: { date: new Date(2026, 4, 25), label: "Posting Lowongan – 25 Mei", time: "23:59 PM" },
|
||||
submittedCount: 0,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
],
|
||||
@@ -146,25 +164,33 @@ export const mockProjects: Project[] = [
|
||||
title: "Setup Pipeline Data",
|
||||
description: "Konfigurasi ETL pipeline dari database transaksi ke data warehouse",
|
||||
deadline: { date: new Date(2026, 4, 3), label: "Setup Pipeline – 3 Mei", time: "23:59 PM" },
|
||||
submittedCount: 6,
|
||||
totalInterns: 6,
|
||||
isSubmitted: true,
|
||||
},
|
||||
{
|
||||
title: "Desain Komponen Chart",
|
||||
description: "Pembuatan reusable chart component untuk berbagai tipe visualisasi data",
|
||||
deadline: { date: new Date(2026, 4, 12), label: "Komponen Chart – 12 Mei", time: "23:59 PM" },
|
||||
submittedCount: 6,
|
||||
totalInterns: 6,
|
||||
isSubmitted: true,
|
||||
},
|
||||
{
|
||||
title: "Dashboard Overview Page",
|
||||
description: "Halaman utama dengan ringkasan KPI dan metric utama bisnis",
|
||||
deadline: { date: new Date(2026, 4, 20), label: "Overview Page – 20 Mei", time: "23:59 PM" },
|
||||
isSubmitted: true,
|
||||
submittedCount: 0,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
{
|
||||
title: "Laporan Penjualan",
|
||||
description: "Modul analitik penjualan dengan filter periode dan kategori produk",
|
||||
deadline: { date: new Date(2026, 4, 28), label: "Laporan Penjualan – 28 Mei", time: "23:59 PM" },
|
||||
isSubmitted: true,
|
||||
submittedCount: 0,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
],
|
||||
interns: [
|
||||
@@ -182,8 +208,34 @@ export const mockProjects: Project[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const certificateData = {
|
||||
isReceived: true, // ubah ke false untuk tampilan "belum tersedia"
|
||||
export interface Certificate {
|
||||
id: string;
|
||||
issuedDate: string;
|
||||
recipientName: string;
|
||||
programName: string;
|
||||
duration: string;
|
||||
finalScore: number;
|
||||
grade: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface CertificateHistory {
|
||||
id: number;
|
||||
month: string;
|
||||
title: string;
|
||||
grade: string;
|
||||
}
|
||||
|
||||
export interface CertificateData {
|
||||
isReceived: boolean;
|
||||
projectProgress: number;
|
||||
remainingTasks: number;
|
||||
certificate: Certificate;
|
||||
history: CertificateHistory[];
|
||||
}
|
||||
|
||||
export const certificateData: CertificateData = {
|
||||
isReceived: false, // ubah ke false untuk tampilan "belum tersedia"
|
||||
projectProgress: 65,
|
||||
remainingTasks: 3,
|
||||
certificate: {
|
||||
@@ -208,12 +260,6 @@ export const certificateData = {
|
||||
month: "Maret 2023",
|
||||
title: "UI/UX Design Essentials",
|
||||
grade: "A",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
month: "Januari 2023",
|
||||
title: "Data Analytics Bootcamp",
|
||||
grade: "B+",
|
||||
},
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user