feat: add breadcrumbs component and integrate with layout; update dependencies and refactor various pages
This commit is contained in:
Generated
+12
-1
@@ -21,7 +21,8 @@
|
|||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
"recharts": "^3.4.1",
|
"recharts": "^3.4.1",
|
||||||
"tailwindcss": "^4.1.6"
|
"tailwindcss": "^4.1.6",
|
||||||
|
"use-react-router-breadcrumbs": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.25.0",
|
"@eslint/js": "^9.25.0",
|
||||||
@@ -5160,6 +5161,16 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-react-router-breadcrumbs": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-react-router-breadcrumbs/-/use-react-router-breadcrumbs-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-Zbcy0KvWt1JePFcUHJAnTr7Z+AeO9WxmPs6A5Q/xqOVoi8edPKzpqHF87WB2opXwie/QjCxrEyTB7kFg7fgXvQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-router-dom": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/use-sync-external-store": {
|
"node_modules/use-sync-external-store": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -23,7 +23,8 @@
|
|||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
"recharts": "^3.4.1",
|
"recharts": "^3.4.1",
|
||||||
"tailwindcss": "^4.1.6"
|
"tailwindcss": "^4.1.6",
|
||||||
|
"use-react-router-breadcrumbs": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.25.0",
|
"@eslint/js": "^9.25.0",
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Link, useLocation } from "react-router-dom";
|
||||||
|
import useBreadcrumbs from "use-react-router-breadcrumbs";
|
||||||
|
import { ChevronRight } from "lucide-react";
|
||||||
|
|
||||||
|
const breadcrumbRoutes = [
|
||||||
|
// --- AREA INTERN ---
|
||||||
|
{ path: '/intern', breadcrumb: 'Home' },
|
||||||
|
{ path: '/intern/projects', breadcrumb: 'Projects' },
|
||||||
|
{ path: '/intern/projects/:id', breadcrumb: 'Detail Project' }, // Otomatis handle ID dinamis
|
||||||
|
{ path: '/intern/certificates', breadcrumb: 'Certificates' },
|
||||||
|
{ path: '/intern/tasks', breadcrumb: 'Tasks' },
|
||||||
|
{ path: '/intern/tasks/:id', breadcrumb: 'Detail Task' },
|
||||||
|
{ path: '/intern/tasks/:id/submit', breadcrumb: 'Submit Task' },
|
||||||
|
{ path: '/intern/faq', breadcrumb: 'FAQ' },
|
||||||
|
{ path: '/intern/notifications', breadcrumb: 'Notifications' },
|
||||||
|
{ path: '/intern/settings', breadcrumb: 'Settings' },
|
||||||
|
|
||||||
|
// --- AREA MENTOR / ADMIN ---
|
||||||
|
{ path: '/mentor', breadcrumb: 'Home' },
|
||||||
|
{ path: '/mentor/projects', breadcrumb: 'Managed Projects' },
|
||||||
|
{ path: '/mentor/projects/:id', breadcrumb: 'Project Overview' },
|
||||||
|
{ path: '/mentor/certificates', breadcrumb: 'Approve Certificates' },
|
||||||
|
{ path: '/mentor/intern', breadcrumb: 'List Intern' },
|
||||||
|
{ path: '/mentor/faq', breadcrumb: 'FAQ' },
|
||||||
|
{ path: '/mentor/notifications', breadcrumb: 'Notifications' },
|
||||||
|
{ path: '/mentor/settings', breadcrumb: 'Settings' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Breadcrumbs(){
|
||||||
|
const breadcrumbs = useBreadcrumbs(breadcrumbRoutes);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const fixbreadcumbs = breadcrumbs.filter(({match}) => match.pathname !== '/');
|
||||||
|
return (
|
||||||
|
<nav className="text-[12px] mb-3">
|
||||||
|
{fixbreadcumbs.map(({ match, breadcrumb }) => {
|
||||||
|
const isLast = match.pathname === location.pathname;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span key={match.pathname} className="inline-flex items-center">
|
||||||
|
{isLast ? (
|
||||||
|
<span className="font-bold text-red-800">
|
||||||
|
{breadcrumb}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link to={match.pathname} className="text-gray-800">
|
||||||
|
{breadcrumb}
|
||||||
|
</Link>
|
||||||
|
{/* Karakter pemisah antar breadcrumb */}
|
||||||
|
<ChevronRight className="w-4 h-4 mx-1.5"/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -18,9 +18,9 @@ const mockFAQData = [
|
|||||||
|
|
||||||
export default function FAQPage() {
|
export default function FAQPage() {
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div>
|
||||||
{/* Header Title */}
|
{/* Header Title */}
|
||||||
<div className="mb-10">
|
<div className="mb-10 ">
|
||||||
<h1 className="page-title">FAQ</h1>
|
<h1 className="page-title">FAQ</h1>
|
||||||
<p className="page-title-desc">
|
<p className="page-title-desc">
|
||||||
Quick answers to common questions about the Internify LMS platform.
|
Quick answers to common questions about the Internify LMS platform.
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default function NotificationList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
|
||||||
<div>
|
<div>
|
||||||
@@ -50,13 +50,13 @@ export default function NotificationList() {
|
|||||||
Keep track of your interns' progress and system updates.
|
Keep track of your interns' progress and system updates.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
{/* <button
|
||||||
onClick={handleMarkAllRead}
|
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"
|
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]" />
|
<Check className="w-4 h-4 text-green-600 stroke-[3]" />
|
||||||
Mark all as read
|
Mark all as read
|
||||||
</button>
|
</button> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* List Group Section */}
|
{/* List Group Section */}
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ export default function SettingsContent() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Mendapatkan inisial huruf pertama untuk avatar profile gambar
|
// Mendapatkan inisial huruf pertama untuk avatar profile gambar
|
||||||
const initialLetter = formData.fullName ? formData.fullName.charAt(0).toUpperCase() : "U";
|
const initialLetter = formData.fullName ? formData.fullName.charAt(1).toUpperCase() : "U";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="">
|
||||||
|
|
||||||
{/* Header Title */}
|
{/* Header Title */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import HomeProjectList from "./components/HomeProjectList";
|
|||||||
import HomeCalendar from "./components/HomeCalendar";
|
import HomeCalendar from "./components/HomeCalendar";
|
||||||
import TaskTimeline from "./components/TaskTimeline";
|
import TaskTimeline from "./components/TaskTimeline";
|
||||||
|
|
||||||
// PR: tambahin breadcrumb
|
|
||||||
export default function HomeInternPage() {
|
export default function HomeInternPage() {
|
||||||
return (
|
return (
|
||||||
<div className="container flex-1">
|
<div className="flex-1">
|
||||||
<HomeHeader />
|
<HomeHeader />
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<div className="grid grid-cols-2 gap-5">
|
<div className="grid grid-cols-2 gap-5">
|
||||||
|
|||||||
@@ -36,20 +36,8 @@ export default function ProjectDetailPage() {
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="">
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
{/* 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 */}
|
{/* Tabs */}
|
||||||
<div className="flex mb-4">
|
<div className="flex mb-4">
|
||||||
<button className={tabClass('forum')} onClick={() => setActiveTab('forum')}>
|
<button className={tabClass('forum')} onClick={() => setActiveTab('forum')}>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { mockProjects } from '../../../lib/mockData'
|
|||||||
|
|
||||||
export default function ProjectsPage() {
|
export default function ProjectsPage() {
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="">
|
||||||
<div className="flex flex-col gap-1 mb-6">
|
<div className="flex flex-col gap-1 mb-6">
|
||||||
<span className="page-title">List Projects</span>
|
<span className="page-title">List Projects</span>
|
||||||
<h1 className="page-title-desc">View assigned projects and track their progress.</h1>
|
<h1 className="page-title-desc">View assigned projects and track their progress.</h1>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const SertificatePage = () => {
|
|||||||
const data = certificateData; // ganti isReceived di object atas
|
const data = certificateData; // ganti isReceived di object atas
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-12 flex flex-col gap-1">
|
<div className="mb-12 flex flex-col gap-1">
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function TaskSubmission() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Header Card */}
|
{/* Header Card */}
|
||||||
<div className="w-full bg-white rounded-2xl border border-gray-100 p-6 shadow-[0_4px_20px_rgba(0,0,0,0.01)]">
|
<div className="w-full bg-white rounded-2xl border border-gray-100 p-6 shadow-[0_4px_20px_rgba(0,0,0,0.01)]">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Lock, Clock } from "lucide-react";
|
|||||||
|
|
||||||
export default function CertificateNotAvailable({ progress, remainingTasks }: { progress: number; remainingTasks: number }) {
|
export default function CertificateNotAvailable({ progress, remainingTasks }: { progress: number; remainingTasks: number }) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white w-full rounded-2xl border border-gray-100 shadow-sm py-16 p-23 flex flex-col items-center text-center max-w-3/5 mx-auto z-10">
|
<div className="bg-white w-full rounded-2xl border border-gray-100 shadow-sm py-16 p-23 flex flex-col items-center text-center md:max-w-3/5 mx-auto z-10">
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
<div className="relative mb-6">
|
<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-33 h-33 border-2 border-dashed border-gray-600 rounded-xl flex justify-center items-center">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||||
|
|
||||||
interface Deadline {
|
interface Deadline {
|
||||||
@@ -9,8 +9,7 @@ interface Deadline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEADLINES: Deadline[] = [
|
const DEADLINES: Deadline[] = [
|
||||||
{ date: 4, month: 4, year: 2025, label: 'Laporan Progress 1 Internify– 4 May' },
|
{ date: 30, month: 6, year: 2026, label: 'Tenggat Proyek Internify Internify - 4 May' },
|
||||||
{ date: 5, month: 4, year: 2025, label: 'Laporan Progress 1 Internify– 4 May' },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const DAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
const DAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
||||||
@@ -20,7 +19,7 @@ const MONTH_NAMES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
function getDaysInMonth(year: number, month: number) {
|
function getDaysInMonth(year: number, month: number) {
|
||||||
return new Date(year, month + 1, 0).getDate()
|
return new Date(year, month+1, 0).getDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFirstDayOfMonth(year: number, month: number) {
|
function getFirstDayOfMonth(year: number, month: number) {
|
||||||
@@ -28,9 +27,18 @@ function getFirstDayOfMonth(year: number, month: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HomeCalendar = () => {
|
const HomeCalendar = () => {
|
||||||
const today = new Date()
|
const [today, setToday] = useState(new Date())
|
||||||
const [currentYear, setCurrentYear] = useState(2025)
|
|
||||||
const [currentMonth, setCurrentMonth] = useState(4) // May = 4 (0-indexed)
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setToday(new Date())
|
||||||
|
}, 60 * 1000) // update tiap 1 menit
|
||||||
|
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const [currentYear, setCurrentYear] = useState(today.getFullYear())
|
||||||
|
const [currentMonth, setCurrentMonth] = useState(today.getMonth())
|
||||||
|
|
||||||
const daysInMonth = getDaysInMonth(currentYear, currentMonth)
|
const daysInMonth = getDaysInMonth(currentYear, currentMonth)
|
||||||
const firstDay = getFirstDayOfMonth(currentYear, currentMonth)
|
const firstDay = getFirstDayOfMonth(currentYear, currentMonth)
|
||||||
@@ -59,7 +67,7 @@ const HomeCalendar = () => {
|
|||||||
currentYear === today.getFullYear()
|
currentYear === today.getFullYear()
|
||||||
|
|
||||||
const hasDeadline = (day: number) =>
|
const hasDeadline = (day: number) =>
|
||||||
DEADLINES.some(d => d.date === day && d.month === currentMonth && d.year === currentYear)
|
DEADLINES.some(d => d.date === day && d.month - 1 === currentMonth && d.year === currentYear)
|
||||||
|
|
||||||
const upcomingDeadlines = DEADLINES.filter(
|
const upcomingDeadlines = DEADLINES.filter(
|
||||||
d => d.month === currentMonth && d.year === currentYear
|
d => d.month === currentMonth && d.year === currentYear
|
||||||
@@ -129,7 +137,7 @@ const HomeCalendar = () => {
|
|||||||
{currentDays.map(day => (
|
{currentDays.map(day => (
|
||||||
<div key={day} className="flex justify-center items-center py-1 px-4">
|
<div key={day} className="flex justify-center items-center py-1 px-4">
|
||||||
{isToday(day) ? (
|
{isToday(day) ? (
|
||||||
<span className="w-8 h-7 flex items-center justify-center rounded bg-red-800 text-white text-xs font-semibold">
|
<span className="w-8 h-7 flex items-center justify-center border-b-2 border-red-800 text-xs font-semibold">
|
||||||
{day}
|
{day}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -171,7 +179,7 @@ const HomeCalendar = () => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-gray-400">No upcoming deadlines this month.</p>
|
<p className="text-[11px] text-gray-400">No upcoming deadlines this month.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,39 +1,17 @@
|
|||||||
interface Task {
|
import type { TaskTimelineProps } from "../../../../lib/mockDeadline"
|
||||||
tanggal: string
|
import { defaultTasks } from "../../../../lib/mockDeadline"
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TaskTimelineProps {
|
|
||||||
tasks?: Task[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultTasks: Task[] = [
|
|
||||||
{
|
|
||||||
tanggal: 'MAY 14, 2026 AT 23:59 PM',
|
|
||||||
title: 'Laporan tugas 1',
|
|
||||||
description: 'Berisi design uiux, dan user flow dari aplikasi internify',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tanggal: 'MAY 15, 2026 AT 23:59 PM',
|
|
||||||
title: 'Laporan tugas 1',
|
|
||||||
description: 'Berisi design uiux, dan user flow dari aplikasi internify',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tanggal: 'MAY 16, 2026 AT 23:59 PM',
|
|
||||||
title: 'Laporan tugas 1',
|
|
||||||
description: 'Berisi design uiux, dan user flow dari aplikasi internify',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const TaskTimeline = ({ tasks = defaultTasks }: TaskTimelineProps) => {
|
const TaskTimeline = ({ tasks = defaultTasks }: TaskTimelineProps) => {
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
|
|
||||||
const isOverdue = (tanggal: string) => {
|
const isOverdue = (tanggal: string) => {
|
||||||
const deadline = new Date(tanggal)
|
const deadline = new Date(tanggal)
|
||||||
return deadline < today
|
return deadline < today
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const upcomingIndex = tasks.findIndex(
|
||||||
|
task => new Date(task.tanggal) >= today
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold text-gray-800 mb-5">Task Timeline</h2>
|
<h2 className="text-base font-semibold text-gray-800 mb-5">Task Timeline</h2>
|
||||||
@@ -45,6 +23,7 @@ const TaskTimeline = ({ tasks = defaultTasks }: TaskTimelineProps) => {
|
|||||||
<ul className="flex flex-col gap-5">
|
<ul className="flex flex-col gap-5">
|
||||||
{tasks.map((task, index) => {
|
{tasks.map((task, index) => {
|
||||||
const overdue = isOverdue(task.tanggal)
|
const overdue = isOverdue(task.tanggal)
|
||||||
|
const isRedDot = overdue || index === upcomingIndex
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={index} className="flex gap-4 pl-0">
|
<li key={index} className="flex gap-4 pl-0">
|
||||||
@@ -52,9 +31,9 @@ const TaskTimeline = ({ tasks = defaultTasks }: TaskTimelineProps) => {
|
|||||||
<div className="relative flex-shrink-0 mt-1">
|
<div className="relative flex-shrink-0 mt-1">
|
||||||
<span
|
<span
|
||||||
className={`block w-3 h-3 rounded-full border-2 z-10 relative
|
className={`block w-3 h-3 rounded-full border-2 z-10 relative
|
||||||
${overdue
|
${isRedDot
|
||||||
? 'bg-red-500 border-red-500'
|
? 'bg-red-800 '
|
||||||
: 'bg-white border-gray-300'
|
: 'bg-gray-400'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Header from './mentor/components/Header';
|
|||||||
import SidebarIntern from './intern/components/SidebarIntern';
|
import SidebarIntern from './intern/components/SidebarIntern';
|
||||||
import SidebarMentor from './mentor/components/SidebarMentor';
|
import SidebarMentor from './mentor/components/SidebarMentor';
|
||||||
import FooterIntern from '../../Layout/Footer-Internify';
|
import FooterIntern from '../../Layout/Footer-Internify';
|
||||||
|
import Breadcrumbs from "./Breadcumbs";
|
||||||
|
|
||||||
export const InternLayout = () => {
|
export const InternLayout = () => {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
@@ -20,7 +21,8 @@ export const InternLayout = () => {
|
|||||||
onClick={closeSidebar}
|
onClick={closeSidebar}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<main className="flex-1 flex flex-col">
|
<main className="container">
|
||||||
|
<Breadcrumbs/>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +46,8 @@ export const MentorLayout = () => {
|
|||||||
onClick={closeSidebar}
|
onClick={closeSidebar}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<main className="flex-1 flex flex-col min-w-0">
|
<main className="container">
|
||||||
|
<Breadcrumbs/>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default function MentorProjectsPage(){
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="">
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h1 className="page-title">List Projects</h1>
|
<h1 className="page-title">List Projects</h1>
|
||||||
|
|||||||
@@ -48,12 +48,9 @@ const MentorCertificatePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-6 md:px-14 py-8 md:py-12 bg-gray-50 min-h-screen">
|
<div>
|
||||||
{/* Breadcrumb & Header Title */}
|
{/* Title */}
|
||||||
<div className="mb-8">
|
<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">></span>
|
|
||||||
</p>
|
|
||||||
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 tracking-tight">Certificates</h1>
|
<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">
|
<p className="text-sm md:text-base text-gray-500 mt-1">
|
||||||
Administrate, issue, and track professional certifications for all interns.
|
Administrate, issue, and track professional certifications for all interns.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import HomeMentorProject from './components/HomeMentorProject'
|
|||||||
|
|
||||||
const HomeMentorPage = () => {
|
const HomeMentorPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="container flex-1 bg-gray-50">
|
<div className="flex-1">
|
||||||
<HomeHeader />
|
<HomeHeader />
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<div className="">
|
<div className="">
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function InternsManagement() {
|
|||||||
}, [searchEmail, selectedProject]);
|
}, [searchEmail, selectedProject]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full px-16 py-12 font-sans">
|
<div className="">
|
||||||
{/* Header Title */}
|
{/* Header Title */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h1 className="page-title">Interns Management</h1>
|
<h1 className="page-title">Interns Management</h1>
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export default function CreateProjectModal({ isOpen, onClose }: CreateProjectMod
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSelectedIcon(item.id)}
|
onClick={() => setSelectedIcon(item.id)}
|
||||||
className={`w-10 h-10 flex items-center justify-center rounded-lg border text-lg transition-all ${isSelected
|
className={`w-8 h-8 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-[#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"
|
: "border-gray-200 bg-gray-50 text-gray-500 hover:bg-gray-100"
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { Mail, Lock, Eye, EyeOff } from 'lucide-react';
|
|||||||
|
|
||||||
// Data user dummy untuk keperluan simulasi navigasi frontend
|
// Data user dummy untuk keperluan simulasi navigasi frontend
|
||||||
const DataUsers = [
|
const DataUsers = [
|
||||||
{ "Name": "John Doe", "Email": "john@email.com", "Password": "password123", "role": "intern" },
|
{ "Email": "intern@email.com", "Password": "password123", "role": "intern" },
|
||||||
{ "Name": "Jane Smith", "Email": "jane@email.com", "Password": "password123", "role": "mentor" },
|
{ "Email": "mentor@email.com", "Password": "password123", "role": "mentor" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function InternifyLogin() {
|
export default function InternifyLogin() {
|
||||||
@@ -44,12 +44,10 @@ export default function InternifyLogin() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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="min-h-screen bg-gray-100 flex flex-col justify-between items-center py-8 px-4 font-sans selection:bg-red-200">
|
||||||
<div className="hidden md:block"></div>
|
<div className="hidden md:block"></div>
|
||||||
|
|
||||||
{/* Main Container */}
|
|
||||||
<div className="w-full max-w-[440px] flex flex-col items-center">
|
<div className="w-full max-w-[440px] flex flex-col items-center">
|
||||||
{/* Logo Section */}
|
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<div className="flex items-center justify-center gap-2 font-bold text-2xl tracking-tight text-black">
|
<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" />
|
<img src={logo} alt="Logo" className="w-32 h-20" />
|
||||||
@@ -59,7 +57,6 @@ export default function InternifyLogin() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Card Login */}
|
|
||||||
<div className="max-w-md bg-white rounded-2xl border border-gray-100 p-8 md:p-12">
|
<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>
|
<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>
|
<p className="text-sm text-gray-500 mb-6">Access your dashboard with your credentials.</p>
|
||||||
@@ -82,7 +79,6 @@ export default function InternifyLogin() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Input Password */}
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-xs font-medium text-gray-700">Password</label>
|
<label className="text-xs font-medium text-gray-700">Password</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -110,14 +106,12 @@ export default function InternifyLogin() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Error Message */}
|
|
||||||
{error && (
|
{error && (
|
||||||
<p className="text-xs text-red-600 bg-red-50 p-2 rounded-md font-medium">
|
<p className="text-xs text-red-600 bg-red-50 p-2 rounded-md font-medium">
|
||||||
{error}
|
{error}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Button Sign In */}
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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"
|
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"
|
||||||
@@ -153,12 +147,11 @@ export default function InternifyLogin() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer Section */}
|
|
||||||
<div className="text-center text-[11px] text-gray-400 space-y-1">
|
<div className="text-center text-[11px] text-gray-400 space-y-1">
|
||||||
<p>© 2024 Internify. CoE Humic Engineering.</p>
|
<p>© 2026 Internify. CoE Humic Engineering.</p>
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<a href="#" className="hover:underline">Privacy Policy</a>
|
<a href="#" className="hover:underline">Privacy Policy</a>
|
||||||
<span>•</span>
|
<span></span>
|
||||||
<a href="#" className="hover:underline">Terms of Service</a>
|
<a href="#" className="hover:underline">Terms of Service</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// App.tsx / router.tsx
|
// App.tsx / router.tsx
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
import HomeInternPage from "../internify/intern/HomeInternPage";
|
import HomeInternPage from "../internify/intern/HomeInternPage";
|
||||||
import InternLayout from "../internify/layout";
|
import {InternLayout} from "../internify/mentor/layout";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// App.tsx / router.tsx
|
||||||
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
|
import HomeMentorPage from "../internify/mentor/HomePage";
|
||||||
|
import {MentorLayout} from "../internify/mentor/layout";
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <MentorLayout />,
|
||||||
|
children: [
|
||||||
|
{ path: "home", element: <HomeMentorPage /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return <RouterProvider router={router} />;
|
||||||
|
}
|
||||||
+7
-13
@@ -35,19 +35,14 @@
|
|||||||
padding-inline: calc(var(--spacing) * 14);
|
padding-inline: calc(var(--spacing) * 14);
|
||||||
padding-block: calc(var(--spacing) * 12);
|
padding-block: calc(var(--spacing) * 12);
|
||||||
background-color: var(--color-gray-50);
|
background-color: var(--color-gray-50);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 13px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
background-color: #ffffff;
|
background-color: #F9F9F9;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||||
@@ -59,12 +54,12 @@
|
|||||||
line-height: var(--tw-leading, var(--text-3xl--line-height) /* calc(2.25 / 1.875) ≈ 1.2 */);
|
line-height: var(--tw-leading, var(--text-3xl--line-height) /* calc(2.25 / 1.875) ≈ 1.2 */);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--color-gray-900);
|
color: var(--color-gray-900);
|
||||||
--tw-tracking: var(--tracking-tight) /* -0.025em */;
|
--tw-tracking: var(--tracking-tight);
|
||||||
letter-spacing: var(--tracking-tight) /* -0.025em */;
|
letter-spacing: var(--tracking-tight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title-desc {
|
.page-title-desc {
|
||||||
font-size: var(--text-sm) /* 0.875rem = 14px */;
|
font-size: var(--text-sm);
|
||||||
color: var(--color-gray-500);
|
color: var(--color-gray-500);
|
||||||
margin-top: calc(var(--spacing) * 1)
|
margin-top: calc(var(--spacing) * 1)
|
||||||
}
|
}
|
||||||
@@ -115,7 +110,6 @@ dialog.custom-dialog::backdrop {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
/* Padding default untuk layar kecil (px-6 py-4) */
|
|
||||||
padding-left: 1.5rem; /* 24px */
|
padding-left: 1.5rem; /* 24px */
|
||||||
padding-right: 1.5rem; /* 24px */
|
padding-right: 1.5rem; /* 24px */
|
||||||
padding-top: 1rem; /* 16px */
|
padding-top: 1rem; /* 16px */
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
interface Task {
|
||||||
|
tanggal: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskTimelineProps {
|
||||||
|
tasks?: Task[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultTasks: Task[] = [
|
||||||
|
{
|
||||||
|
tanggal: '2026-05-14T23:59:00',
|
||||||
|
title: 'Laporan tugas 1',
|
||||||
|
description: 'Berisi design uiux, dan user flow dari aplikasi internify',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tanggal: '2026-05-15T23:59:00',
|
||||||
|
title: 'Laporan tugas 1',
|
||||||
|
description: 'Berisi design uiux, dan user flow dari aplikasi internify',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tanggal: '2026-06-20T21:00:00',
|
||||||
|
title: 'Laporan tugas 1',
|
||||||
|
description: 'Berisi design uiux, dan user flow dari aplikasi internify',
|
||||||
|
},
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user