diff --git a/src/App.tsx b/src/App.tsx index a6b06fc..dc7cd2e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,7 +35,7 @@ import SertificatePage from "./Pages/Internify/intern/SertificatePage"; import ProjectDetailPage from "./Pages/Internify/intern/ProjectDetailPage"; import HomeMentorPage from "./Pages/Internify/mentor/HomeMentorPage"; import MentorProjectsPage from "./Pages/Internify/mentor/BaseProjectsPage"; -import MentorCertificatePage from "./Pages/Internify/mentor/CertificatePage"; +import MentorCertificatePage from "./Pages/Internify/mentor/CertificateListPage"; import MentorInternPage from "./Pages/Internify/mentor/InternsPage"; import MentorDetailProject from "./Pages/Internify/mentor/ProjectsPage"; import InternifyLogin from "./Pages/Internify/LoginInternify" @@ -48,6 +48,8 @@ import MentorProjectsDetailPage from "./Pages/Internify/mentor/MentorProjectDeta import ViewInternSubmission from "./Pages/Internify/mentor/ViewInternSubmission"; import SettingsPage from "./Pages/Internify/mentor/MentorSettingsPage"; import ProtectedRoutes from "./Pages/utils/ProtectedRoute"; +import CertificateDetail from "./Pages/Internify/mentor/CertificateDetail"; +import CertificateResult from "./Pages/Internify/mentor/CertificateGenerate"; function App() { return ( @@ -109,6 +111,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/Pages/internify/LoginInternify.tsx b/src/Pages/internify/LoginInternify.tsx index 16bed3a..a4c0fb9 100644 --- a/src/Pages/internify/LoginInternify.tsx +++ b/src/Pages/internify/LoginInternify.tsx @@ -60,8 +60,6 @@ export default function InternifyLogin() { return (
-
-
diff --git a/src/Pages/internify/intern/ForumTab.tsx b/src/Pages/internify/intern/ForumTab.tsx index ace03e0..0441557 100644 --- a/src/Pages/internify/intern/ForumTab.tsx +++ b/src/Pages/internify/intern/ForumTab.tsx @@ -10,7 +10,6 @@ export default function ForumTab({ project }: { project: Project }) { const { tasks, loading, error } = useProjectTasks(slug ?? ""); return (
- {/* Hero banner */} {/* Loading */} diff --git a/src/Pages/internify/intern/ProjectDetailPage.tsx b/src/Pages/internify/intern/ProjectDetailPage.tsx index 5249e58..d27c2c0 100644 --- a/src/Pages/internify/intern/ProjectDetailPage.tsx +++ b/src/Pages/internify/intern/ProjectDetailPage.tsx @@ -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

Memuat project...

; if (error) return

{error}

; if (!project) return

Project tidak ditemukan.

; diff --git a/src/Pages/internify/mentor/CertificatePage.tsx b/src/Pages/internify/mentor/CertificatePage.tsx index 92a5396..45c66d1 100644 --- a/src/Pages/internify/mentor/CertificatePage.tsx +++ b/src/Pages/internify/mentor/CertificatePage.tsx @@ -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" }, diff --git a/src/Pages/internify/mentor/ProjectsInternTab.tsx b/src/Pages/internify/mentor/ProjectsInternTab.tsx index 3c4426d..006cd8e 100644 --- a/src/Pages/internify/mentor/ProjectsInternTab.tsx +++ b/src/Pages/internify/mentor/ProjectsInternTab.tsx @@ -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 (
@@ -62,7 +73,11 @@ export default function InternsTab({ project }: { project: ProjectDetail }) { {member.position}
-
diff --git a/src/Pages/internify/mentor/components/CreateProjectDialog.tsx b/src/Pages/internify/mentor/components/CreateProjectDialog.tsx index 91d565a..602b0cd 100644 --- a/src/Pages/internify/mentor/components/CreateProjectDialog.tsx +++ b/src/Pages/internify/mentor/components/CreateProjectDialog.tsx @@ -11,16 +11,11 @@ interface CreateProjectModalProps { export default function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps) { const dialogRef = useRef(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([ - "alex.dev@gmail.com", - "sarah.engineer@gmail.com" - ]); + const [invitedEmails, setInvitedEmails] = useState([]); 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" /> @@ -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" /> diff --git a/src/Pages/internify/mentor/components/CreateTaskDialog.tsx b/src/Pages/internify/mentor/components/CreateTaskDialog.tsx index 34f12b0..d3da25b 100644 --- a/src/Pages/internify/mentor/components/CreateTaskDialog.tsx +++ b/src/Pages/internify/mentor/components/CreateTaskDialog.tsx @@ -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]" />
@@ -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]" />
diff --git a/src/Pages/internify/mentor/components/EditSubmissionTask.tsx b/src/Pages/internify/mentor/components/EditSubmissionTask.tsx index a306d37..985c898 100644 --- a/src/Pages/internify/mentor/components/EditSubmissionTask.tsx +++ b/src/Pages/internify/mentor/components/EditSubmissionTask.tsx @@ -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; diff --git a/src/Pages/internify/mentor/components/MentorProjectCard.tsx b/src/Pages/internify/mentor/components/MentorProjectCard.tsx index 1a6c657..b850c12 100644 --- a/src/Pages/internify/mentor/components/MentorProjectCard.tsx +++ b/src/Pages/internify/mentor/components/MentorProjectCard.tsx @@ -20,13 +20,14 @@ export default function MentorProjectCard(project: Project) { const Icon = getDynamicIcon(project.project_icon); const handleDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); } return (
{/* Thumbnail */} -
+
diff --git a/src/hooks/useProjects.ts b/src/hooks/useProjects.ts index c23951c..2cb267c 100644 --- a/src/hooks/useProjects.ts +++ b/src/hooks/useProjects.ts @@ -21,7 +21,8 @@ import { updateProject, archiveProject, assignMember, - getProjectMembers + getProjectMembers, + removeMember } from "../services/ProjectService"; // GET /project-api/get @@ -227,4 +228,24 @@ export const useAssignMember = () => { }; return { assign, loading, error }; +}; + +export const useRemoveMember = () => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const remove = async (payload: { id_project: string | number, id_user: number }) => { + setLoading(true); + setError(null); + try { + return await removeMember(payload); + } catch { + setError("Gagal remove member."); + return null; + } finally { + setLoading(false); + } + }; + + return { remove, loading, error }; }; \ No newline at end of file diff --git a/src/services/ProjectService.ts b/src/services/ProjectService.ts index a5a9b07..5f5e2e6 100644 --- a/src/services/ProjectService.ts +++ b/src/services/ProjectService.ts @@ -15,7 +15,7 @@ import type { // POST /project-api/add export const createProject = async ( payload: CreateProjectPayload -): Promise => { +): Promise => { const res = await api.post("/project-api/add", payload); return res.data.data; }; @@ -23,7 +23,7 @@ export const createProject = async ( // GET /project-api/get export const getProjects = async ( status?: "active" | "completed" | "archived" -): Promise => { +): Promise => { const res = await api.get("/project-api/get", { params: status ? { status } : undefined, }); @@ -115,4 +115,14 @@ export const assignMember = async ( ): Promise => { const res = await api.post("/project-api/assign-member", payload); return res.data.data; +}; + +export const removeMember = async ( + payload: { + id_project: string | number; + id_user: number; + } +): Promise => { + const res = await api.post("/project-api/remove-member", { id_project: payload.id_project, id_user: payload.id_user }); + return res.data.data; }; \ No newline at end of file diff --git a/src/types/project.types.ts b/src/types/project.types.ts index df5a146..ad8e362 100644 --- a/src/types/project.types.ts +++ b/src/types/project.types.ts @@ -16,6 +16,7 @@ export interface Project { id: number; slug: string; project_icon: string; + background_color: string; project_name: string; description: string; start_date: string;