feat: implement project management dashboard and certification module for mentors and interns

This commit is contained in:
Kemas M. Aryadary Rasyad
2026-06-26 22:53:19 +07:00
parent 60aba4180e
commit 6ec9620f1f
13 changed files with 73 additions and 29 deletions
-2
View File
@@ -60,8 +60,6 @@ export default function InternifyLogin() {
return (
<div className="min-h-screen bg-background flex flex-col justify-between items-center py-8 px-4 font-sans selection:bg-red-foreground">
<div className="hidden md:block"></div>
<div className="w-full max-w-[440px] flex flex-col items-center">
<div className="text-center mb-6">
<div className="flex items-center justify-center gap-2 font-bold text-2xl tracking-tight text-black">
-1
View File
@@ -10,7 +10,6 @@ export default function ForumTab({ project }: { project: Project }) {
const { tasks, loading, error } = useProjectTasks(slug ?? "");
return (
<div>
{/* Hero banner */}
<HeroProject title={project.project_name} description={project.description} />
{/* Loading */}
@@ -8,8 +8,6 @@ export default function ProjectDetailPage() {
const { slug } = useParams<{ slug: string }>();
const { project, loading, error } = useProjectDetail(slug ?? "");
const [activeTab, setActiveTab] = useState<'forum' | 'participants'>('forum');
// Fallback sederhana
if (loading) return <p className="p-10 text-gray-400">Memuat project...</p>;
if (error) return <p className="p-10 text-red-500">{error}</p>;
if (!project) return <p className="p-10 text-gray-400">Project tidak ditemukan.</p>;
@@ -11,7 +11,6 @@ import {
} from 'lucide-react';
import { LuHistory, LuSendHorizontal } from 'react-icons/lu';
// Mock Data untuk Tabel Riwayat Sertifikat
const initialHistoryData = [
{ id: 1, name: "Jonathan Kristina", email: "JonathanKristina@gmail.com", project: "Internify Project", date: "24 Oct 2024", initials: "JK" },
{ id: 2, name: "Ahmad Faisal", email: "ahmad.faisal@gmail.com", project: "Web Dev Fundamentals", date: "15 Sep 2024", initials: "AF" },
@@ -2,10 +2,21 @@ import { useState } from 'react';
import { Plus } from 'lucide-react';
import ManageInternsModal from './components/AddInternsDialog';
import type { ProjectDetail, ProjectMember } from '../../../types/project.types';
import { useProjectInterns } from '../../../hooks/useProjects';
import { useRemoveMember } from '../../../hooks/useProjects';
export default function InternsTab({ project }: { project: ProjectDetail }) {
const [isModalOpen, setIsModalOpen] = useState(false);
const { remove, loading } = useRemoveMember();
const handleRemove = async (id_user: number) => {
if (window.confirm('Are you sure you want to remove this member?')) {
const res = await remove({ id_project: project.id, id_user });
if (res !== null) {
alert('Member removed successfully');
window.location.reload();
}
}
};
return (
<div>
@@ -62,7 +73,11 @@ export default function InternsTab({ project }: { project: ProjectDetail }) {
{member.position}
</span>
<div>
<button className='flex items-center gap-1 text-[14px] bg-red-600 text-white p-1 rounded-xl'>
<button
onClick={() => handleRemove(member.id)}
disabled={loading}
className='flex items-center gap-1 text-[14px] bg-red-600 text-white p-1 rounded-xl disabled:opacity-50 hover:bg-red-700 transition-colors'
>
<Plus className='w-4 h-4'/>Delete
</button>
</div>
@@ -11,16 +11,11 @@ interface CreateProjectModalProps {
export default function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
// State Form Management
const [selectedIcon, setSelectedIcon] = useState(PROJECT_ICON_CODES[0]);
const [projectName, setProjectName] = useState("");
const [projectDescription, setProjectDescription] = useState("");
const [emailInput, setEmailInput] = useState("");
const [invitedEmails, setInvitedEmails] = useState<string[]>([
"alex.dev@gmail.com",
"sarah.engineer@gmail.com"
]);
const [invitedEmails, setInvitedEmails] = useState<string[]>([]);
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const { create, loading, error } = useCreateProject();
@@ -54,7 +49,6 @@ export default function CreateProjectModal({ isOpen, onClose }: CreateProjectMod
};
}, [isOpen, onClose]);
// Fungsi menambah email undangan baru
const handleAddEmail = (e: React.FormEvent) => {
e.preventDefault();
if (emailInput.trim() && !invitedEmails.includes(emailInput.trim())) {
@@ -63,7 +57,6 @@ export default function CreateProjectModal({ isOpen, onClose }: CreateProjectMod
}
};
// Fungsi menghapus email undangan
const handleRemoveEmail = (emailToRemove: string) => {
setInvitedEmails(invitedEmails.filter(email => email !== emailToRemove));
};
@@ -71,14 +64,19 @@ export default function CreateProjectModal({ isOpen, onClose }: CreateProjectMod
// Handler Submit Utama
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = await create({
const payload: any = {
project_icon: selectedIcon,
project_name: projectName,
description: projectDescription,
member_emails: invitedEmails,
start_date: startDate,
end_date: endDate,
});
};
if (invitedEmails.length > 0) {
payload.member_emails = invitedEmails;
}
const result = await create(payload);
if (result) {
onClose();
}
@@ -226,7 +224,7 @@ export default function CreateProjectModal({ isOpen, onClose }: CreateProjectMod
required
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="w-full pl-4 pr-10 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium appearance-none"
className="w-full px-3 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium appearance-none"
/>
<span className="absolute right-3.5 top-3 text-gray-400 pointer-events-none">
</span>
@@ -244,7 +242,7 @@ export default function CreateProjectModal({ isOpen, onClose }: CreateProjectMod
required
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className="w-full pl-4 pr-10 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium appearance-none"
className="w-full px-3 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium appearance-none"
/>
<span className="absolute right-3.5 top-3 text-gray-400 pointer-events-none">
</span>
@@ -80,7 +80,7 @@ export default function CreateTaskModal({ isOpen, onClose, projectId, onSuccess
required
value={deadlineDate}
onChange={(e) => setDeadlineDate(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
className="w-full px-3 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
</div>
<div className="space-y-1.5">
<label className="text-xs font-bold text-gray-700 tracking-wide">Specific Time</label>
@@ -89,7 +89,7 @@ export default function CreateTaskModal({ isOpen, onClose, projectId, onSuccess
required
value={specificTime}
onChange={(e) => setSpecificTime(e.target.value)}
className="w-full pl-3 pr-9 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
className="w-full px-3 py-3 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-800 focus:outline-none focus:border-[#B30000] focus:ring-1 focus:ring-[#B30000]" />
</div>
</div>
<div className="space-y-1.5">
@@ -1,6 +1,6 @@
import { useRef, useEffect, useState } from "react";
import { X } from "lucide-react";
import { LuSave, LuCalendar } from "react-icons/lu";
import { LuSave } from "react-icons/lu";
interface EditTaskModalProps {
isOpen: boolean;
@@ -20,13 +20,14 @@ export default function MentorProjectCard(project: Project) {
const Icon = getDynamicIcon(project.project_icon);
const handleDeleteClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
}
return (
<div
onClick={handleCardClick}
className="flex flex-col bg-white rounded-xl overflow-hidden shadow-sm border border-gray-100 w-[180px] min-h-[270px]hover:bg-gray-50 transition-colors">
{/* Thumbnail */}
<div className="h-[150px] bg-gradient-to-tr from-red-700 to-red-800 overflow-hidden shrink-0 flex items-center justify-center">
<div style={{ backgroundColor: project.background_color ?? '#dc2626' }} className="h-[150px] overflow-hidden shrink-0 flex items-center justify-center">
<Icon className="w-12 h-12 text-white opacity-80" />
</div>