feat: add react-hot-toast for notifications, enhance project detail page, and update task handling
- Added `react-hot-toast` dependency for improved notification handling. - Introduced `MentorProjectsDetailPage` for detailed project and task views. - Updated routing to include task-specific paths. - Enhanced breadcrumb navigation to reflect new project structure. - Refactored task handling in `MentorTaskCard` and `ProjectTimeline` components. - Updated mock data to include detailed task submissions and deadlines. - Improved UI elements for better user experience in project management.
This commit is contained in:
Generated
+27
-1
@@ -18,6 +18,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-froala-wysiwyg": "^4.5.2",
|
"react-froala-wysiwyg": "^4.5.2",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
|
"react-hot-toast": "^2.6.0",
|
||||||
"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",
|
||||||
@@ -2808,7 +2809,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/d3-array": {
|
"node_modules/d3-array": {
|
||||||
@@ -3537,6 +3537,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/goober": {
|
||||||
|
"version": "2.1.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz",
|
||||||
|
"integrity": "sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"csstype": "^3.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
@@ -4702,6 +4711,23 @@
|
|||||||
"react": ">=16.4.1"
|
"react": ">=16.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hot-toast": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"goober": "^2.1.16"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16",
|
||||||
|
"react-dom": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-icons": {
|
"node_modules/react-icons": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-froala-wysiwyg": "^4.5.2",
|
"react-froala-wysiwyg": "^4.5.2",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
|
"react-hot-toast": "^2.6.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import NotificationList from "./Pages/internify/NotificationList";
|
|||||||
import SettingsContent from "./Pages/internify/SettingsPage";
|
import SettingsContent from "./Pages/internify/SettingsPage";
|
||||||
import TaskSubmission from "./Pages/internify/intern/TaskSubmission";
|
import TaskSubmission from "./Pages/internify/intern/TaskSubmission";
|
||||||
import SertificateList from "./Pages/internify/intern/SertificateList";
|
import SertificateList from "./Pages/internify/intern/SertificateList";
|
||||||
|
import MentorProjectsDetailPage from "./Pages/internify/mentor/MentorProjectDetailPage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -102,6 +103,7 @@ function App() {
|
|||||||
<Route path="certificates" element={<MentorCertificatePage />} />
|
<Route path="certificates" element={<MentorCertificatePage />} />
|
||||||
<Route path="intern" element={<MentorInternPage />} />
|
<Route path="intern" element={<MentorInternPage />} />
|
||||||
<Route path="projects/:slug" element={<MentorDetailProject />} />
|
<Route path="projects/:slug" element={<MentorDetailProject />} />
|
||||||
|
<Route path="projects/:slug/:taskSlug" element={<MentorProjectsDetailPage />} />
|
||||||
<Route path="faq" element={<FAQPage />} />
|
<Route path="faq" element={<FAQPage />} />
|
||||||
<Route path="notifications" element={<NotificationList />} />
|
<Route path="notifications" element={<NotificationList />} />
|
||||||
<Route path="settings" element={<SettingsContent />} />
|
<Route path="settings" element={<SettingsContent />} />
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const breadcrumbRoutes = [
|
|||||||
{ path: '/mentor', breadcrumb: 'Home' },
|
{ path: '/mentor', breadcrumb: 'Home' },
|
||||||
{ path: '/mentor/projects', breadcrumb: 'List Projects' },
|
{ path: '/mentor/projects', breadcrumb: 'List Projects' },
|
||||||
{ path: '/mentor/projects/:slug', breadcrumb: ProjectNameBreadcrumb },
|
{ path: '/mentor/projects/:slug', breadcrumb: ProjectNameBreadcrumb },
|
||||||
|
{ path: '/mentor/projects/:slug/:taskSlug', breadcrumb: TaskNameBreadcrumb },
|
||||||
{ path: '/mentor/certificates', breadcrumb: 'Approve Certificates' },
|
{ path: '/mentor/certificates', breadcrumb: 'Approve Certificates' },
|
||||||
{ path: '/mentor/intern', breadcrumb: 'List Intern' },
|
{ path: '/mentor/intern', breadcrumb: 'List Intern' },
|
||||||
{ path: '/mentor/faq', breadcrumb: 'FAQ' },
|
{ path: '/mentor/faq', breadcrumb: 'FAQ' },
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Project } from '../../../lib/mockData';
|
import type { Project } from '../../../lib/mockProjects';
|
||||||
import ProjectTimeline from './components/ProjectTimeline';
|
import ProjectTimeline from './components/ProjectTimeline';
|
||||||
import TaskCard from './components/ProjectTaskCard';
|
import TaskCard from './components/ProjectTaskCard';
|
||||||
import HeroProject from './components/Hero-project';
|
import HeroProject from './components/Hero-project';
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function ProjectDetailPage() {
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div>
|
||||||
<div>
|
<div>
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="flex mb-4">
|
<div className="flex mb-4">
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||||
|
import { DEADLINES } from '../../../../lib/mockDeadline'
|
||||||
|
|
||||||
interface Deadline {
|
|
||||||
date: number
|
|
||||||
month: number
|
|
||||||
year: number
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEADLINES: Deadline[] = [
|
|
||||||
{ date: 30, month: 6, year: 2026, label: 'Tenggat Proyek Internify Internify - 4 May' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const DAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
const DAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
||||||
const MONTH_NAMES = [
|
const MONTH_NAMES = [
|
||||||
@@ -19,7 +10,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) {
|
||||||
@@ -27,7 +18,7 @@ function getFirstDayOfMonth(year: number, month: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HomeCalendar = () => {
|
const HomeCalendar = () => {
|
||||||
const [today, setToday] = useState(new Date())
|
const [today, setToday] = useState(new Date())
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
@@ -67,11 +58,18 @@ const [today, setToday] = useState(new Date())
|
|||||||
currentYear === today.getFullYear()
|
currentYear === today.getFullYear()
|
||||||
|
|
||||||
const hasDeadline = (day: number) =>
|
const hasDeadline = (day: number) =>
|
||||||
DEADLINES.some(d => d.date === day && d.month - 1 === currentMonth && d.year === currentYear)
|
DEADLINES.some(d =>
|
||||||
|
d.date.getDate() === day &&
|
||||||
|
d.date.getMonth() === currentMonth &&
|
||||||
|
d.date.getFullYear() === currentYear
|
||||||
|
)
|
||||||
|
|
||||||
const upcomingDeadlines = DEADLINES.filter(
|
const upcomingDeadlines = DEADLINES.filter(d => {
|
||||||
d => d.month === currentMonth && d.year === currentYear
|
return (
|
||||||
)
|
d.date.getMonth() === currentMonth &&
|
||||||
|
d.date.getFullYear() === currentYear
|
||||||
|
);
|
||||||
|
}).sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||||
|
|
||||||
// Previous month trailing days
|
// Previous month trailing days
|
||||||
const prevMonthDays = getDaysInMonth(currentYear, currentMonth - 1 < 0 ? 11 : currentMonth - 1)
|
const prevMonthDays = getDaysInMonth(currentYear, currentMonth - 1 < 0 ? 11 : currentMonth - 1)
|
||||||
@@ -135,16 +133,16 @@ const [today, setToday] = useState(new Date())
|
|||||||
|
|
||||||
{/* Current month days */}
|
{/* Current month days */}
|
||||||
{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-0.5">
|
||||||
{isToday(day) ? (
|
{isToday(day) ? (
|
||||||
<span className="w-8 h-7 flex items-center justify-center border-b-2 border-red-800 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>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className={`w-full h-7 flex items-center justify-center text-xs rounded
|
className={`w-full h-7 flex items-center justify-center text-xs
|
||||||
${isBold(day) ? 'font-bold text-gray-800' : 'text-gray-700'}
|
${isBold(day) ? 'font-bold text-gray-800' : 'text-gray-700'}
|
||||||
${hasDeadline(day) ? 'bg-red-700 text-white' : ''}
|
${hasDeadline(day) ? 'bg-red-800 text-white' : ''}
|
||||||
transition-colors`}
|
transition-colors`}
|
||||||
>
|
>
|
||||||
{day}
|
{day}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Task } from "../../../../lib/mockData";
|
import type { Task } from "../../../../lib/mockProjects";
|
||||||
import { ArrowDownNarrowWide } from "lucide-react";
|
import { ArrowDownNarrowWide } from "lucide-react";
|
||||||
|
|
||||||
const formatDate = (date: Date) =>
|
const formatDate = (date: Date) =>
|
||||||
@@ -11,7 +11,7 @@ export default function ProjectTimeline({ tasks }: { tasks: Task[] }) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-1.5 mb-4 justify-between">
|
<div className="flex items-center gap-1.5 mb-4 justify-between">
|
||||||
<span className="font-semibold text-sm text-[#1a1a1a]">
|
<span className="font-semibold text-md text-[#1a1a1a]">
|
||||||
Timeline
|
Timeline
|
||||||
</span>
|
</span>
|
||||||
<ArrowDownNarrowWide className="w-5 h-5 text-gray-500"/>
|
<ArrowDownNarrowWide className="w-5 h-5 text-gray-500"/>
|
||||||
@@ -29,7 +29,7 @@ export default function ProjectTimeline({ tasks }: { tasks: Task[] }) {
|
|||||||
<li key={idx} className="relative mb-6 list-none">
|
<li key={idx} className="relative mb-6 list-none">
|
||||||
{/* Dot */}
|
{/* Dot */}
|
||||||
<div
|
<div
|
||||||
className={`absolute -left-4 top-0.5 w-2.5 h-2.5 rounded-full border-2 border-white ${active ? 'bg-[#C0392B]' : 'bg-[#ccc]'
|
className={`absolute -left-5 top-0.5 w-3 h-3 rounded-full border-2 border-white ${active ? 'bg-[#C0392B]' : 'bg-[#ccc]'
|
||||||
} ${isToday ? 'shadow-md' : ''}`}
|
} ${isToday ? 'shadow-md' : ''}`}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { FileText, UploadCloud, Trash2, X, FileUp } from "lucide-react";
|
import { FileText, UploadCloud, X, FileUp } from "lucide-react";
|
||||||
import { LuSendHorizontal } from "react-icons/lu";
|
import { LuSendHorizontal } from "react-icons/lu";
|
||||||
|
|
||||||
interface TaskFormFileProps {
|
interface TaskFormFileProps {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function MentorProjectsPage(){
|
|||||||
|
|
||||||
<div className="flex flex-wrap gap-4 items-stretch">
|
<div className="flex flex-wrap gap-4 items-stretch">
|
||||||
{mockProjects.map(project => (
|
{mockProjects.map(project => (
|
||||||
<MentorProjectCard key={project.id} {...project} />
|
<MentorProjectCard key={project.id} {...project} tasks={project.tasks} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { mockProjects } from "../../../lib/mockProjects";
|
||||||
|
import { Calendar, Edit, SlidersHorizontal, Search } from "lucide-react";
|
||||||
|
|
||||||
|
const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-');
|
||||||
|
|
||||||
|
// Helper untuk mendapatkan inisial nama avatar
|
||||||
|
const getInitials = (name: string) => {
|
||||||
|
return name.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function MentorProjectsDetailPage() {
|
||||||
|
const { slug, taskSlug } = useParams<{ slug: string; taskSlug: string }>();
|
||||||
|
|
||||||
|
const targetedProject = mockProjects.find((p) => toSlug(p.name) === slug);
|
||||||
|
if (!targetedProject) {
|
||||||
|
return <div className="p-10 text-[#888]">Project tidak ditemukan.</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetedTask = targetedProject.tasks.find((t) => toSlug(t.title) === taskSlug);
|
||||||
|
if (!targetedTask) {
|
||||||
|
return <div className="p-10 text-[#888]">Tugas tidak ditemukan.</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
|
||||||
|
{/* Header Utama */}
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h1 className="text-3xl font-bold text-[#1a1a1a]">{targetedTask.title}</h1>
|
||||||
|
<button className="flex items-center gap-2 border border-gray-400 text-gray-700 px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-50 transition-all">
|
||||||
|
<Edit size={16} />
|
||||||
|
Edit Task
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Kotak Informasi Detail & Deadline */}
|
||||||
|
<div className="border border-gray-300 rounded-xl p-6 bg-white flex flex-col md:flex-row gap-6 mb-8 shadow-sm">
|
||||||
|
<div className="flex-1 md:border-r border-gray-200 md:pr-6">
|
||||||
|
<h2 className="text-xs font-sans text-gray-400 tracking-wider uppercase mb-2">Description</h2>
|
||||||
|
<p className="text-sm text-gray-700 leading-relaxed whitespace-pre-line">
|
||||||
|
{targetedTask.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-120 flex flex-col justify-start">
|
||||||
|
<h2 className="text-xs font-sans text-gray-400 tracking-wider uppercase mb-2">Deadline</h2>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="text-red-600 mt-0.5">
|
||||||
|
<Calendar size={20} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-base font-bold text-gray-900">{targetedTask.deadline.label}</div>
|
||||||
|
<div className="text-xs text-gray-400">at {targetedTask.deadline.time}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Panel Tabel Intern Submissions */}
|
||||||
|
<div className="border border-gray-300 rounded-xl bg-white overflow-hidden shadow-sm">
|
||||||
|
{/* Header Tabel Kontrol */}
|
||||||
|
<div className="p-5 flex justify-between items-center border-b border-gray-200">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<h2 className="text-xl font-bold text-gray-900">Intern Submissions</h2>
|
||||||
|
<span className="bg-gray-100 text-gray-500 font-semibold text-xs px-2.5 py-0.5 rounded-md border border-gray-200">
|
||||||
|
{targetedTask.submissions?.length || 0} total
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4 text-gray-500">
|
||||||
|
<button className="hover:text-gray-800"><SlidersHorizontal size={18} /></button>
|
||||||
|
<button className="hover:text-gray-800"><Search size={18} /></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Isi Tabel */}
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-gray-50 border-b border-gray-200 text-xs font-bold text-gray-400 tracking-wider uppercase">
|
||||||
|
<th className="px-6 py-4">Intern Name</th>
|
||||||
|
<th className="px-6 py-4">Submitted Date</th>
|
||||||
|
<th className="px-6 py-4">Status</th>
|
||||||
|
<th className="px-6 py-4 text-right">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
{targetedTask.submissions?.map((sub, idx) => {
|
||||||
|
// Cari relasi profil lengkap intern lewat email
|
||||||
|
const internProfile = targetedProject.interns.find(i => i.email === sub.internEmail);
|
||||||
|
if (!internProfile) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={idx} className="hover:bg-gray-50/50 transition-colors">
|
||||||
|
{/* Kolom Nama & Profil */}
|
||||||
|
<td className="px-6 py-4 flex items-center gap-3">
|
||||||
|
<div className="w-9 h-9 rounded-full bg-gray-200 text-gray-500 font-semibold text-xs flex items-center justify-center border border-gray-300">
|
||||||
|
{getInitials(internProfile.name)}
|
||||||
|
</div>
|
||||||
|
<span className="font-semibold text-sm text-gray-800">{internProfile.name}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Kolom Tanggal Kumpul */}
|
||||||
|
<td className="px-6 py-4 text-sm text-gray-500">
|
||||||
|
{sub.submittedAt || "-"}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Kolom Badge Status */}
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
{sub.status === "Done" && (
|
||||||
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-semibold rounded-full bg-green-50 text-green-700 border border-green-200">
|
||||||
|
<span className="w-1.5 h-1.5 bg-green-500 rounded-full"></span> Done
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{sub.status === "Pending" && (
|
||||||
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-semibold rounded-full bg-amber-50 text-amber-700 border border-amber-200">
|
||||||
|
<span className="w-1.5 h-1.5 bg-amber-500 rounded-full"></span> Pending
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{sub.status === "Overdue" && (
|
||||||
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-semibold rounded-full bg-red-700 text-white border">
|
||||||
|
<span className="w-1.5 h-1.5 bg-white rounded-full"></span> Overdue
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Kolom Tombol Aksi */}
|
||||||
|
<td className="px-6 py-4 text-right">
|
||||||
|
<button className="text-sm font-bold text-red-800 hover:text-red-900 transition-colors inline-flex items-center gap-1">
|
||||||
|
View Submission <span className="text-xs">→</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Project } from '../../../lib/mockData';
|
import type { Project } from '../../../lib/mockProjects';
|
||||||
import ProjectTimeline from '../intern/components/ProjectTimeline';
|
import ProjectTimeline from '../intern/components/ProjectTimeline';
|
||||||
import HeroProject from '../intern/components/Hero-project';
|
import HeroProject from '../intern/components/Hero-project';
|
||||||
import MentorTaskCard from './components/MentorTaskCard';
|
import MentorTaskCard from './components/MentorTaskCard';
|
||||||
@@ -17,7 +17,10 @@ export default function MentorForumTab({ project }: { project: Project }) {
|
|||||||
|
|
||||||
<div className="w-4/5">
|
<div className="w-4/5">
|
||||||
{project.tasks.map((task, idx) => (
|
{project.tasks.map((task, idx) => (
|
||||||
<MentorTaskCard key={idx} task={task} />
|
<MentorTaskCard
|
||||||
|
key={idx}
|
||||||
|
task={task}
|
||||||
|
projectName={project.name} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function InternsTab({ project }: { project: Project }) {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className=" text-sm text-[#333]">
|
<span className=" text-sm text-[#333]">
|
||||||
{intern.email}
|
{intern.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className=" text-[13px] text-[#555] font-semibold">
|
<span className=" text-[13px] text-[#555] font-semibold">
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { mockProjects } from '../../../lib/mockProjects';
|
import { mockProjects } from '../../../lib/mockProjects';
|
||||||
import MentorForumTab from './ProjectForumTab';
|
import MentorForumTab from './ProjectForumTab';
|
||||||
import InternsTab from './ProjectsInternTab';
|
import InternsTab from './ProjectsInternTab';
|
||||||
import TaskTab from './TaskTab';
|
import TaskTab from './TaskTab';
|
||||||
|
|
||||||
// ─── ProjectDetailPage ─────────────────────────────────────────────────────────
|
|
||||||
const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-');
|
const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-');
|
||||||
|
|
||||||
export default function ProjectDetailPage() {
|
export default function ProjectDetailPage() {
|
||||||
@@ -53,7 +52,7 @@ export default function ProjectDetailPage() {
|
|||||||
case 'participants':
|
case 'participants':
|
||||||
return <InternsTab project={project} />;
|
return <InternsTab project={project} />;
|
||||||
case 'Task':
|
case 'Task':
|
||||||
return <TaskTab project={project} />; // Tambahkan komponen TaskTab di sini
|
return <TaskTab project={project} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { mockProjects } from '../../../../lib/mockData'
|
import { mockProjects } from '../../../../lib/mockData'
|
||||||
|
import { useState } from 'react';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
|
import CreateProjectModal from './CreateProjectDialog';
|
||||||
|
|
||||||
const HomeMentorProject = () => {
|
const HomeMentorProject = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const openDialog = () => {
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
const closeDialog = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
<div className='flex flex-row justify-between mb-3 px-5 items-center'>
|
<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>
|
<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'>
|
<button
|
||||||
<span className='text-sm flex flex-row items-center gap-3'>
|
className='bg-gray-300 p-1.5 rounded-lg font-bold hover:bg-gray-400'
|
||||||
<Plus className='w-3 h-3'/>
|
onClick={openDialog} >
|
||||||
Add
|
<span className='text-sm flex flex-row justify-between items-center gap-3.5'>
|
||||||
|
<Plus className='w-4 h-4' />
|
||||||
|
Add
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,7 +31,7 @@ const HomeMentorProject = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => navigate(`/intern/projects/${project.id}`)}
|
onClick={() => navigate(`/intern/projects/${project.name}`)}
|
||||||
className="flex items-center gap-4 border border-gray-200 rounded-lg px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors duration-150"
|
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 className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 bg-gray-300`}>
|
||||||
@@ -32,6 +43,12 @@ const HomeMentorProject = () => {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{isModalOpen && (
|
||||||
|
<CreateProjectModal
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={closeDialog}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Trash2, UsersRound } from 'lucide-react'
|
import { Trash2, UsersRound } from 'lucide-react'
|
||||||
import type { Intern } from '../../../../lib/mockProjects'
|
import type { Intern, Task } from '../../../../lib/mockProjects'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
interface ProjectCardProps {
|
interface ProjectCardProps {
|
||||||
id: number | string
|
id: number | string
|
||||||
name: string
|
name: string
|
||||||
interns: Intern[]
|
interns: Intern[]
|
||||||
|
tasks: Task[]
|
||||||
thumbnailUrl?: string
|
thumbnailUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,46 @@
|
|||||||
import type { Task } from "../../../../lib/mockData";
|
import type { Task } from "../../../../lib/mockProjects";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const formatDate = (date: Date) =>
|
const formatDate = (date: Date) =>
|
||||||
new Date(date).toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric' });
|
new Date(date).toLocaleDateString('id-ID', { day: 'numeric', month: 'short', year: 'numeric' });
|
||||||
|
|
||||||
export default function MentorTaskCard({ task }: { task: Task }) {
|
const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-');
|
||||||
|
|
||||||
|
interface MentorTaskCardProps {
|
||||||
|
task: Task;
|
||||||
|
projectName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MentorTaskCard({ task, projectName }: MentorTaskCardProps) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleCardClick = () => {
|
||||||
|
// Pastikan jika projectName belum ter-render, proses navigasi tidak bikin crash
|
||||||
|
if (!projectName || !task?.title) return;
|
||||||
|
|
||||||
|
const projectSlug = toSlug(projectName);
|
||||||
|
const taskSlug = toSlug(task.title);
|
||||||
|
|
||||||
|
// Rute mutlak menuju target kamu: /mentor/projects/:slug/:taskSlug
|
||||||
|
navigate(`/mentor/projects/${projectSlug}/${taskSlug}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
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="border border-[#eee] rounded-[10px] px-5 py-4 mb-3 bg-white shadow-lg flex items-start justify-between gap-3 cursor-pointer hover:border-gray-300 transition-colors"
|
||||||
|
onClick={handleCardClick}
|
||||||
|
>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className=" text-[11px] text-[#888] mb-1">
|
<div className="text-[11px] text-[#888] mb-1">
|
||||||
Deadline : {formatDate(task.deadline.date)} at {task.deadline.time}
|
Deadline : {task.deadline ? `${formatDate(task.deadline.date)} at ${task.deadline.time}` : '-'}
|
||||||
</div>
|
</div>
|
||||||
<div className=" font-bold text-[15px] text-[#1a1a1a] mb-1">
|
<div className="font-bold text-[15px] text-[#1a1a1a] mb-1">
|
||||||
{task.title}
|
{task.title}
|
||||||
</div>
|
</div>
|
||||||
<div className=" text-[13px] text-[#666]">
|
<div className="text-[13px] text-[#666]">
|
||||||
{task.description}
|
{task.description}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6,15 +6,13 @@ export interface Deadline {
|
|||||||
|
|
||||||
export const DEADLINES: Deadline[] = [
|
export const DEADLINES: Deadline[] = [
|
||||||
{
|
{
|
||||||
date: new Date(2025, 4, 4),
|
date: new Date(2026, 5, 29),
|
||||||
label: "Laporan Progress 1 Internify – 4 Juni",
|
label: "Laporan Progress 1 Internify",
|
||||||
time: "23:59 PM",
|
time: "23:59 PM",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
date: new Date(2026, 5, 5),
|
date: new Date(2026, 5, 30),
|
||||||
label: "Laporan Progress 2 Internify - 5 May",
|
label: "Laporan Progress 2 Internify",
|
||||||
time: "23:59 PM",
|
time: "23:59 PM",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const deadlineDates: Date[] = DEADLINES.map((d) => d.date);
|
|
||||||
+53
-117
@@ -1,14 +1,21 @@
|
|||||||
import type { Deadline } from "./mockDeadline";
|
import type { Deadline } from "./mockDeadline";
|
||||||
|
|
||||||
|
export interface Submission {
|
||||||
|
internEmail: string;
|
||||||
|
submittedAt?: string;
|
||||||
|
status: 'Done' | 'Pending' | 'Overdue';
|
||||||
|
}
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
deadline: Deadline;
|
deadline: Deadline;
|
||||||
type: 'file' | 'link';
|
type: 'file' | 'link';
|
||||||
isSubmitted: boolean;
|
submissions: Submission[];
|
||||||
submittedCount: number;
|
isSubmitted: boolean; // Kembalikan untuk komponen lain
|
||||||
totalInterns: number;
|
submittedCount: number; // Kembalikan untuk komponen lain
|
||||||
|
totalInterns: number; // Kembalikan untuk komponen lain
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Intern {
|
export interface Intern {
|
||||||
@@ -38,54 +45,50 @@ export const mockProjects: Project[] = [
|
|||||||
description:
|
description:
|
||||||
"Internify merupakan website Human Capital (HUMC) yang dirancang sebagai platform pendaftaran dan pengelolaan program magang secara digital. Sistem ini dibuat untuk membantu proses rekrutmen peserta magang mulai dari pendaftaran, seleksi, hingga monitoring aktivitas peserta selama program berlangsung.\n\nFokus utama dari pengembangan Internify adalah pembuatan Learning Management System (LMS) yang digunakan untuk mendukung kegiatan pembelajaran dan pengelolaan peserta magang. LMS pada Internify menyediakan fitur seperti akses materi pembelajaran, pengumpulan tugas, monitoring progress peserta, absensi, sertifikat, hingga komunikasi antara mentor dan peserta magang.",
|
"Internify merupakan website Human Capital (HUMC) yang dirancang sebagai platform pendaftaran dan pengelolaan program magang secara digital. Sistem ini dibuat untuk membantu proses rekrutmen peserta magang mulai dari pendaftaran, seleksi, hingga monitoring aktivitas peserta selama program berlangsung.\n\nFokus utama dari pengembangan Internify adalah pembuatan Learning Management System (LMS) yang digunakan untuk mendukung kegiatan pembelajaran dan pengelolaan peserta magang. LMS pada Internify menyediakan fitur seperti akses materi pembelajaran, pengumpulan tugas, monitoring progress peserta, absensi, sertifikat, hingga komunikasi antara mentor dan peserta magang.",
|
||||||
tasks: [
|
tasks: [
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "Laporan tugas",
|
|
||||||
description: "Berisi design ulux, dan user flow dari aplikasi internify",
|
|
||||||
type: 'link',
|
|
||||||
deadline: { date: new Date(2026, 4, 1), label: "Laporan tugas – 1 Mei", time: "23:59 PM" },
|
|
||||||
submittedCount: 6,
|
|
||||||
totalInterns: 6,
|
|
||||||
isSubmitted: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: "Laporan tugas 1",
|
title: "Laporan tugas 1",
|
||||||
description: "Berisi design ulux, dan user flow dari aplikasi internify",
|
description: "Berisi design uiux, dan user flow dari aplikasi internify.",
|
||||||
type: 'file',
|
type: 'file',
|
||||||
deadline: { date: new Date(2026, 4, 14), label: "Laporan tugas 1 – 14 Mei", time: "23:59 PM" },
|
deadline: { date: new Date(2026, 4, 14), label: "May 14, 2026", time: "23:59 PM" },
|
||||||
|
isSubmitted: false,
|
||||||
submittedCount: 2,
|
submittedCount: 2,
|
||||||
totalInterns: 6,
|
totalInterns: 6,
|
||||||
isSubmitted: false,
|
submissions: [
|
||||||
|
{ internEmail: "jonathankristina@gmail.com", submittedAt: "May 12, 2026", status: "Done" },
|
||||||
|
{ internEmail: "anisarahmawati@gmail.com", submittedAt: "May 13, 2026", status: "Done" },
|
||||||
|
{ internEmail: "budisantoso@gmail.com", submittedAt: "-", status: "Pending" },
|
||||||
|
{ internEmail: "cindypermata@gmail.com", submittedAt: "-", status: "Pending" },
|
||||||
|
{ internEmail: "dewaaditya@gmail.com", submittedAt: "-", status: "Pending" },
|
||||||
|
{ internEmail: "ekapratiwi@gmail.com", submittedAt: "-", status: "Pending" },
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: "Laporan tugas 2",
|
title: "Laporan tugas 2",
|
||||||
description: "Berisi design ulux, dan user flow dari aplikasi internify",
|
description: "Berisi design uiux, dan user flow dari aplikasi internify. Pastikan semua link Figma dapat diakses oleh mentor dan mencakup setidaknya 15 screen utama beserta prototype interaktifnya.",
|
||||||
type: 'link',
|
type: 'link',
|
||||||
deadline: { date: new Date(2026, 4, 28), label: "Laporan tugas 2 – 28 Mei", time: "23:59 PM" },
|
deadline: { date: new Date(2026, 4, 28), label: "May 28, 2026", time: "23:59 PM" },
|
||||||
|
isSubmitted: false,
|
||||||
submittedCount: 3,
|
submittedCount: 3,
|
||||||
totalInterns: 6,
|
totalInterns: 6,
|
||||||
isSubmitted: false,
|
submissions: [
|
||||||
},
|
{ internEmail: "jonathankristina@gmail.com", submittedAt: "May 25, 2026", status: "Done" },
|
||||||
{
|
{ internEmail: "anisarahmawati@gmail.com", submittedAt: "May 26, 2026", status: "Pending" },
|
||||||
id: 4,
|
{ internEmail: "budisantoso@gmail.com", submittedAt: "May 26, 2026", status: "Done" },
|
||||||
title: "Laporan tugas 3",
|
{ internEmail: "cindypermata@gmail.com", submittedAt: "May 27, 2026", status: "Pending" },
|
||||||
description: "Berisi wireframe halaman dashboard aplikasi internify",
|
{ internEmail: "dewaaditya@gmail.com", submittedAt: "-", status: "Overdue" },
|
||||||
type: 'file',
|
{ internEmail: "ekapratiwi@gmail.com", submittedAt: "May 28, 2026", status: "Done" },
|
||||||
deadline: { date: new Date(2026, 5, 5), label: "Laporan tugas 3 – 5 Jun", time: "23:59 PM" },
|
]
|
||||||
submittedCount: 0,
|
|
||||||
totalInterns: 6,
|
|
||||||
isSubmitted: false,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
interns: [
|
interns: [
|
||||||
{ name: "Jonathan Kristina", role: "Backend", email: "jonathankristina@gmail.com" },
|
{ name: "Alex Rivera", role: "Backend", email: "jonathankristina@gmail.com" },
|
||||||
{ name: "Anisa Rahmawati", role: "Backend", email: "jonathankristina@gmail.com" },
|
{ name: "Sarah Jenkins", role: "Backend", email: "anisarahmawati@gmail.com" },
|
||||||
{ name: "Budi Santoso", role: "Frontend", email: "jonathankristina@gmail.com" },
|
{ name: "Marcus Lee", role: "Frontend", email: "budisantoso@gmail.com" },
|
||||||
{ name: "Cindy Permata", role: "Frontend", email: "jonathankristina@gmail.com" },
|
{ name: "Tanya Hudson", role: "Frontend", email: "cindypermata@gmail.com" },
|
||||||
{ name: "Dewa Aditya", role: "UI/UX", email: "jonathankristina@gmail.com" },
|
{ name: "Ben King", role: "UI/UX", email: "dewaaditya@gmail.com" },
|
||||||
{ name: "Eka Pratiwi", role: "UI/UX", email: "jonathankristina@gmail.com" },
|
{ name: "Eka Pratiwi", role: "UI/UX", email: "ekapratiwi@gmail.com" },
|
||||||
],
|
],
|
||||||
mentor: "jonathankristina@gmail.com",
|
mentor: "jonathankristina@gmail.com",
|
||||||
progress: 12,
|
progress: 12,
|
||||||
@@ -95,58 +98,24 @@ export const mockProjects: Project[] = [
|
|||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Rekrutmen Digital Platform",
|
name: "Rekrutmen Digital Platform",
|
||||||
description:
|
description: "Platform digital yang memudahkan proses rekrutmen karyawan baru secara end-to-end.",
|
||||||
"Platform digital yang memudahkan proses rekrutmen karyawan baru secara end-to-end. Mulai dari posting lowongan, screening CV otomatis, penjadwalan wawancara, hingga onboarding karyawan baru.\n\nSistem ini mengintegrasikan berbagai modul HR seperti manajemen data kandidat, penilaian kompetensi berbasis AI, dan dashboard analytics untuk tim HR dalam mengambil keputusan rekrutmen yang lebih efektif dan efisien.",
|
|
||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "Analisis Kebutuhan Sistem",
|
title: "Analisis Kebutuhan Sistem",
|
||||||
description: "Dokumentasi kebutuhan fungsional dan non-fungsional platform rekrutmen",
|
description: "Dokumentasi kebutuhan platform rekrutmen",
|
||||||
type: 'file',
|
type: 'file',
|
||||||
deadline: { date: new Date(2026, 4, 5), label: "Analisis Kebutuhan – 5 Mei", time: "23:59 PM" },
|
deadline: { date: new Date(2026, 4, 5), label: "May 5, 2026", time: "23:59 PM" },
|
||||||
|
isSubmitted: false,
|
||||||
submittedCount: 6,
|
submittedCount: 6,
|
||||||
totalInterns: 6,
|
totalInterns: 6,
|
||||||
isSubmitted: false,
|
submissions: [
|
||||||
},
|
{ internEmail: "fajarnugroho@gmail.com", submittedAt: "May 4, 2026", status: "Done" },
|
||||||
{
|
]
|
||||||
id: 2,
|
}
|
||||||
title: "Desain Database Schema",
|
|
||||||
description: "Rancangan struktur database untuk kandidat, lowongan, dan proses seleksi",
|
|
||||||
type: 'link',
|
|
||||||
deadline: { date: new Date(2026, 4, 10), label: "Desain Database – 10 Mei", time: "23:59 PM" },
|
|
||||||
submittedCount: 6,
|
|
||||||
totalInterns: 6,
|
|
||||||
isSubmitted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Implementasi Auth Module",
|
|
||||||
description: "Pengembangan sistem autentikasi dan otorisasi multi-role",
|
|
||||||
type: 'file',
|
|
||||||
deadline: { date: new Date(2026, 4, 18), label: "Auth Module – 18 Mei", time: "23:59 PM" },
|
|
||||||
submittedCount: 3,
|
|
||||||
totalInterns: 6,
|
|
||||||
isSubmitted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "Modul Posting Lowongan",
|
|
||||||
description: "Fitur buat, edit, dan publish lowongan pekerjaan",
|
|
||||||
type: 'link',
|
|
||||||
deadline: { date: new Date(2026, 4, 25), label: "Posting Lowongan – 25 Mei", time: "23:59 PM" },
|
|
||||||
submittedCount: 0,
|
|
||||||
totalInterns: 6,
|
|
||||||
isSubmitted: false,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
interns: [
|
interns: [
|
||||||
{ name: "Fajar Nugroho", role: "Backend", email: "fajarnugroho@gmail.com" },
|
{ name: "Fajar Nugroho", role: "Backend", email: "fajarnugroho@gmail.com" },
|
||||||
{ name: "Gita Melinda", role: "Backend", email: "gitamelinda@gmail.com" },
|
|
||||||
{ name: "Hendra Wijaya", role: "Frontend", email: "hendrawijaya@gmail.com" },
|
|
||||||
{ name: "Indira Sari", role: "Frontend", email: "indirasari@gmail.com" },
|
|
||||||
{ name: "Joko Purnomo", role: "UI/UX", email: "jokopurnomo@gmail.com" },
|
|
||||||
{ name: "Kartika Dewi", role: "QA", email: "kartikadewi@gmail.com" },
|
|
||||||
{ name: "Lukman Hakim", role: "QA", email: "lukmanhakim@gmail.com" },
|
|
||||||
],
|
],
|
||||||
mentor: "seniormHR@company.com",
|
mentor: "seniormHR@company.com",
|
||||||
progress: 37,
|
progress: 37,
|
||||||
@@ -156,57 +125,24 @@ export const mockProjects: Project[] = [
|
|||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name: "E-Commerce Analytics Dashboard",
|
name: "E-Commerce Analytics Dashboard",
|
||||||
description:
|
description: "Dashboard analitik komprehensif untuk platform e-commerce.",
|
||||||
"Dashboard analitik komprehensif untuk platform e-commerce yang menyajikan insight real-time tentang performa penjualan, perilaku pelanggan, dan efektivitas kampanye marketing.\n\nProyek ini mencakup pengembangan pipeline data dari berbagai sumber transaksi, visualisasi interaktif menggunakan chart library modern, serta fitur export laporan dalam berbagai format untuk kebutuhan manajemen bisnis.",
|
|
||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "Setup Pipeline Data",
|
title: "Setup Pipeline Data",
|
||||||
description: "Konfigurasi ETL pipeline dari database transaksi ke data warehouse",
|
description: "Konfigurasi ETL pipeline dari database transaksi ke data warehouse",
|
||||||
type: 'link',
|
type: 'link',
|
||||||
deadline: { date: new Date(2026, 4, 3), label: "Setup Pipeline – 3 Mei", time: "23:59 PM" },
|
deadline: { date: new Date(2026, 4, 3), label: "May 3, 2026", time: "23:59 PM" },
|
||||||
|
isSubmitted: false,
|
||||||
submittedCount: 6,
|
submittedCount: 6,
|
||||||
totalInterns: 6,
|
totalInterns: 6,
|
||||||
isSubmitted: false,
|
submissions: [
|
||||||
},
|
{ internEmail: "mayaanggraeni@gmail.com", submittedAt: "May 2, 2026", status: "Done" },
|
||||||
{
|
]
|
||||||
id: 2,
|
}
|
||||||
title: "Desain Komponen Chart",
|
|
||||||
description: "Pembuatan reusable chart component untuk berbagai tipe visualisasi data",
|
|
||||||
type: 'file',
|
|
||||||
deadline: { date: new Date(2026, 4, 12), label: "Komponen Chart – 12 Mei", time: "23:59 PM" },
|
|
||||||
submittedCount: 6,
|
|
||||||
totalInterns: 6,
|
|
||||||
isSubmitted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Dashboard Overview Page",
|
|
||||||
description: "Halaman utama dengan ringkasan KPI dan metric utama bisnis",
|
|
||||||
type: 'link',
|
|
||||||
deadline: { date: new Date(2026, 4, 20), label: "Overview Page – 20 Mei", time: "23:59 PM" },
|
|
||||||
submittedCount: 0,
|
|
||||||
totalInterns: 6,
|
|
||||||
isSubmitted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "Laporan Penjualan",
|
|
||||||
description: "Modul analitik penjualan dengan filter periode dan kategori produk",
|
|
||||||
type: 'file',
|
|
||||||
deadline: { date: new Date(2026, 4, 28), label: "Laporan Penjualan – 28 Mei", time: "23:59 PM" },
|
|
||||||
submittedCount: 0,
|
|
||||||
totalInterns: 6,
|
|
||||||
isSubmitted: false,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
interns: [
|
interns: [
|
||||||
{ name: "Maya Anggraeni", role: "Backend", email: "mayaanggraeni@gmail.com" },
|
{ name: "Maya Anggraeni", role: "Backend", email: "mayaanggraeni@gmail.com" },
|
||||||
{ name: "Nando Pratama", role: "Backend", email: "nandopratama@gmail.com" },
|
|
||||||
{ name: "Olivia Susanto", role: "Frontend", email: "oliviasusanto@gmail.com" },
|
|
||||||
{ name: "Pandu Kusuma", role: "Frontend", email: "pandukukusuma@gmail.com" },
|
|
||||||
{ name: "Qori Ramadhan", role: "Data Analyst", email: "qoriramadhan@gmail.com" },
|
|
||||||
{ name: "Rini Handayani", role: "UI/UX", email: "rinihandayani@gmail.com" },
|
|
||||||
],
|
],
|
||||||
mentor: "leadanalyst@company.com",
|
mentor: "leadanalyst@company.com",
|
||||||
progress: 50,
|
progress: 50,
|
||||||
|
|||||||
Reference in New Issue
Block a user