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:
@@ -44,6 +44,7 @@ import NotificationList from "./Pages/internify/NotificationList";
|
||||
import SettingsContent from "./Pages/internify/SettingsPage";
|
||||
import TaskSubmission from "./Pages/internify/intern/TaskSubmission";
|
||||
import SertificateList from "./Pages/internify/intern/SertificateList";
|
||||
import MentorProjectsDetailPage from "./Pages/internify/mentor/MentorProjectDetailPage";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -102,6 +103,7 @@ function App() {
|
||||
<Route path="certificates" element={<MentorCertificatePage />} />
|
||||
<Route path="intern" element={<MentorInternPage />} />
|
||||
<Route path="projects/:slug" element={<MentorDetailProject />} />
|
||||
<Route path="projects/:slug/:taskSlug" element={<MentorProjectsDetailPage />} />
|
||||
<Route path="faq" element={<FAQPage />} />
|
||||
<Route path="notifications" element={<NotificationList />} />
|
||||
<Route path="settings" element={<SettingsContent />} />
|
||||
|
||||
@@ -34,6 +34,7 @@ const breadcrumbRoutes = [
|
||||
{ path: '/mentor', breadcrumb: 'Home' },
|
||||
{ path: '/mentor/projects', breadcrumb: 'List Projects' },
|
||||
{ path: '/mentor/projects/:slug', breadcrumb: ProjectNameBreadcrumb },
|
||||
{ path: '/mentor/projects/:slug/:taskSlug', breadcrumb: TaskNameBreadcrumb },
|
||||
{ path: '/mentor/certificates', breadcrumb: 'Approve Certificates' },
|
||||
{ path: '/mentor/intern', breadcrumb: 'List Intern' },
|
||||
{ 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 TaskCard from './components/ProjectTaskCard';
|
||||
import HeroProject from './components/Hero-project';
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function ProjectDetailPage() {
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div>
|
||||
<div>
|
||||
{/* Tabs */}
|
||||
<div className="flex mb-4">
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { useState, useEffect } from '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 MONTH_NAMES = [
|
||||
@@ -19,7 +10,7 @@ const MONTH_NAMES = [
|
||||
]
|
||||
|
||||
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) {
|
||||
@@ -27,7 +18,7 @@ function getFirstDayOfMonth(year: number, month: number) {
|
||||
}
|
||||
|
||||
const HomeCalendar = () => {
|
||||
const [today, setToday] = useState(new Date())
|
||||
const [today, setToday] = useState(new Date())
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
@@ -67,11 +58,18 @@ const [today, setToday] = useState(new Date())
|
||||
currentYear === today.getFullYear()
|
||||
|
||||
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(
|
||||
d => d.month === currentMonth && d.year === currentYear
|
||||
)
|
||||
const upcomingDeadlines = DEADLINES.filter(d => {
|
||||
return (
|
||||
d.date.getMonth() === currentMonth &&
|
||||
d.date.getFullYear() === currentYear
|
||||
);
|
||||
}).sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||
|
||||
// Previous month trailing days
|
||||
const prevMonthDays = getDaysInMonth(currentYear, currentMonth - 1 < 0 ? 11 : currentMonth - 1)
|
||||
@@ -135,16 +133,16 @@ const [today, setToday] = useState(new Date())
|
||||
|
||||
{/* Current month days */}
|
||||
{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) ? (
|
||||
<span className="w-8 h-7 flex items-center justify-center border-b-2 border-red-800 text-xs font-semibold">
|
||||
{day}
|
||||
</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'}
|
||||
${hasDeadline(day) ? 'bg-red-700 text-white' : ''}
|
||||
${hasDeadline(day) ? 'bg-red-800 text-white' : ''}
|
||||
transition-colors`}
|
||||
>
|
||||
{day}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Task } from "../../../../lib/mockData";
|
||||
import type { Task } from "../../../../lib/mockProjects";
|
||||
import { ArrowDownNarrowWide } from "lucide-react";
|
||||
|
||||
const formatDate = (date: Date) =>
|
||||
@@ -11,7 +11,7 @@ export default function ProjectTimeline({ tasks }: { tasks: Task[] }) {
|
||||
return (
|
||||
<div>
|
||||
<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
|
||||
</span>
|
||||
<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">
|
||||
{/* Dot */}
|
||||
<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' : ''}`}
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
interface TaskFormFileProps {
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function MentorProjectsPage(){
|
||||
|
||||
<div className="flex flex-wrap gap-4 items-stretch">
|
||||
{mockProjects.map(project => (
|
||||
<MentorProjectCard key={project.id} {...project} />
|
||||
<MentorProjectCard key={project.id} {...project} tasks={project.tasks} />
|
||||
))}
|
||||
</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 HeroProject from '../intern/components/Hero-project';
|
||||
import MentorTaskCard from './components/MentorTaskCard';
|
||||
@@ -17,7 +17,10 @@ export default function MentorForumTab({ project }: { project: Project }) {
|
||||
|
||||
<div className="w-4/5">
|
||||
{project.tasks.map((task, idx) => (
|
||||
<MentorTaskCard key={idx} task={task} />
|
||||
<MentorTaskCard
|
||||
key={idx}
|
||||
task={task}
|
||||
projectName={project.name} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function InternsTab({ project }: { project: Project }) {
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className=" text-sm text-[#333]">
|
||||
{intern.email}
|
||||
{intern.name}
|
||||
</span>
|
||||
</div>
|
||||
<span className=" text-[13px] text-[#555] font-semibold">
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { mockProjects } from '../../../lib/mockProjects';
|
||||
import MentorForumTab from './ProjectForumTab';
|
||||
import InternsTab from './ProjectsInternTab';
|
||||
import TaskTab from './TaskTab';
|
||||
|
||||
// ─── ProjectDetailPage ─────────────────────────────────────────────────────────
|
||||
const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-');
|
||||
|
||||
export default function ProjectDetailPage() {
|
||||
@@ -53,7 +52,7 @@ export default function ProjectDetailPage() {
|
||||
case 'participants':
|
||||
return <InternsTab project={project} />;
|
||||
case 'Task':
|
||||
return <TaskTab project={project} />; // Tambahkan komponen TaskTab di sini
|
||||
return <TaskTab project={project} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { mockProjects } from '../../../../lib/mockData'
|
||||
import { useState } from 'react';
|
||||
import { Plus } from 'lucide-react';
|
||||
import CreateProjectModal from './CreateProjectDialog';
|
||||
|
||||
const HomeMentorProject = () => {
|
||||
const navigate = useNavigate();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const openDialog = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
const closeDialog = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
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
|
||||
<button
|
||||
className='bg-gray-300 p-1.5 rounded-lg font-bold hover:bg-gray-400'
|
||||
onClick={openDialog} >
|
||||
<span className='text-sm flex flex-row justify-between items-center gap-3.5'>
|
||||
<Plus className='w-4 h-4' />
|
||||
Add
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -20,7 +31,7 @@ const HomeMentorProject = () => {
|
||||
return (
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
{isModalOpen && (
|
||||
<CreateProjectModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={closeDialog}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
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'
|
||||
|
||||
interface ProjectCardProps {
|
||||
id: number | string
|
||||
name: string
|
||||
interns: Intern[]
|
||||
tasks: Task[]
|
||||
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) =>
|
||||
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 (
|
||||
<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=" text-[11px] text-[#888] mb-1">
|
||||
Deadline : {formatDate(task.deadline.date)} at {task.deadline.time}
|
||||
<div className="text-[11px] text-[#888] mb-1">
|
||||
Deadline : {task.deadline ? `${formatDate(task.deadline.date)} at ${task.deadline.time}` : '-'}
|
||||
</div>
|
||||
<div className=" font-bold text-[15px] text-[#1a1a1a] mb-1">
|
||||
<div className="font-bold text-[15px] text-[#1a1a1a] mb-1">
|
||||
{task.title}
|
||||
</div>
|
||||
<div className=" text-[13px] text-[#666]">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -6,15 +6,13 @@ export interface Deadline {
|
||||
|
||||
export const DEADLINES: Deadline[] = [
|
||||
{
|
||||
date: new Date(2025, 4, 4),
|
||||
label: "Laporan Progress 1 Internify – 4 Juni",
|
||||
date: new Date(2026, 5, 29),
|
||||
label: "Laporan Progress 1 Internify",
|
||||
time: "23:59 PM",
|
||||
},
|
||||
{
|
||||
date: new Date(2026, 5, 5),
|
||||
label: "Laporan Progress 2 Internify - 5 May",
|
||||
date: new Date(2026, 5, 30),
|
||||
label: "Laporan Progress 2 Internify",
|
||||
time: "23:59 PM",
|
||||
},
|
||||
];
|
||||
|
||||
export const deadlineDates: Date[] = DEADLINES.map((d) => d.date);
|
||||
];
|
||||
+53
-117
@@ -1,14 +1,21 @@
|
||||
import type { Deadline } from "./mockDeadline";
|
||||
|
||||
export interface Submission {
|
||||
internEmail: string;
|
||||
submittedAt?: string;
|
||||
status: 'Done' | 'Pending' | 'Overdue';
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
deadline: Deadline;
|
||||
type: 'file' | 'link';
|
||||
isSubmitted: boolean;
|
||||
submittedCount: number;
|
||||
totalInterns: number;
|
||||
submissions: Submission[];
|
||||
isSubmitted: boolean; // Kembalikan untuk komponen lain
|
||||
submittedCount: number; // Kembalikan untuk komponen lain
|
||||
totalInterns: number; // Kembalikan untuk komponen lain
|
||||
}
|
||||
|
||||
export interface Intern {
|
||||
@@ -38,54 +45,50 @@ export const mockProjects: Project[] = [
|
||||
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.",
|
||||
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,
|
||||
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',
|
||||
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,
|
||||
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,
|
||||
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',
|
||||
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,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Laporan tugas 3",
|
||||
description: "Berisi wireframe halaman dashboard aplikasi internify",
|
||||
type: 'file',
|
||||
deadline: { date: new Date(2026, 5, 5), label: "Laporan tugas 3 – 5 Jun", time: "23:59 PM" },
|
||||
submittedCount: 0,
|
||||
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" },
|
||||
{ internEmail: "budisantoso@gmail.com", submittedAt: "May 26, 2026", status: "Done" },
|
||||
{ internEmail: "cindypermata@gmail.com", submittedAt: "May 27, 2026", status: "Pending" },
|
||||
{ internEmail: "dewaaditya@gmail.com", submittedAt: "-", status: "Overdue" },
|
||||
{ internEmail: "ekapratiwi@gmail.com", submittedAt: "May 28, 2026", status: "Done" },
|
||||
]
|
||||
},
|
||||
],
|
||||
interns: [
|
||||
{ name: "Jonathan Kristina", role: "Backend", email: "jonathankristina@gmail.com" },
|
||||
{ name: "Anisa Rahmawati", role: "Backend", email: "jonathankristina@gmail.com" },
|
||||
{ name: "Budi Santoso", role: "Frontend", email: "jonathankristina@gmail.com" },
|
||||
{ name: "Cindy Permata", role: "Frontend", email: "jonathankristina@gmail.com" },
|
||||
{ name: "Dewa Aditya", role: "UI/UX", email: "jonathankristina@gmail.com" },
|
||||
{ name: "Eka Pratiwi", role: "UI/UX", email: "jonathankristina@gmail.com" },
|
||||
{ name: "Alex Rivera", role: "Backend", email: "jonathankristina@gmail.com" },
|
||||
{ name: "Sarah Jenkins", role: "Backend", email: "anisarahmawati@gmail.com" },
|
||||
{ name: "Marcus Lee", role: "Frontend", email: "budisantoso@gmail.com" },
|
||||
{ name: "Tanya Hudson", role: "Frontend", email: "cindypermata@gmail.com" },
|
||||
{ name: "Ben King", role: "UI/UX", email: "dewaaditya@gmail.com" },
|
||||
{ name: "Eka Pratiwi", role: "UI/UX", email: "ekapratiwi@gmail.com" },
|
||||
],
|
||||
mentor: "jonathankristina@gmail.com",
|
||||
progress: 12,
|
||||
@@ -95,58 +98,24 @@ export const mockProjects: Project[] = [
|
||||
{
|
||||
id: 2,
|
||||
name: "Rekrutmen Digital Platform",
|
||||
description:
|
||||
"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.",
|
||||
description: "Platform digital yang memudahkan proses rekrutmen karyawan baru secara end-to-end.",
|
||||
tasks: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Analisis Kebutuhan Sistem",
|
||||
description: "Dokumentasi kebutuhan fungsional dan non-fungsional platform rekrutmen",
|
||||
description: "Dokumentasi kebutuhan platform rekrutmen",
|
||||
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,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
submissions: [
|
||||
{ internEmail: "fajarnugroho@gmail.com", submittedAt: "May 4, 2026", status: "Done" },
|
||||
]
|
||||
}
|
||||
],
|
||||
interns: [
|
||||
{ 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",
|
||||
progress: 37,
|
||||
@@ -156,57 +125,24 @@ export const mockProjects: Project[] = [
|
||||
{
|
||||
id: 3,
|
||||
name: "E-Commerce Analytics Dashboard",
|
||||
description:
|
||||
"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.",
|
||||
description: "Dashboard analitik komprehensif untuk platform e-commerce.",
|
||||
tasks: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Setup Pipeline Data",
|
||||
description: "Konfigurasi ETL pipeline dari database transaksi ke data warehouse",
|
||||
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,
|
||||
totalInterns: 6,
|
||||
isSubmitted: false,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
submissions: [
|
||||
{ internEmail: "mayaanggraeni@gmail.com", submittedAt: "May 2, 2026", status: "Done" },
|
||||
]
|
||||
}
|
||||
],
|
||||
interns: [
|
||||
{ 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",
|
||||
progress: 50,
|
||||
|
||||
Reference in New Issue
Block a user