From 53b270921d1f9658fa2697a9822e0e03d4aa83de Mon Sep 17 00:00:00 2001 From: "Kemas M. Aryadary Rasyad" <112590882+aryadary0706@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:59:35 +0700 Subject: [PATCH 1/6] Update all hooks (Projects, Tasks, Certificates), implement backend for Projects. HomePage on Mentors --- src/Layout/Header-internify.tsx | 6 +- src/Pages/internify/LoginInternify.tsx | 14 +- src/Pages/internify/NotificationList.tsx | 269 +++++++++--------- src/Pages/internify/intern/ParticipantTab.tsx | 42 +-- .../internify/intern/ProjectDetailPage.tsx | 1 - .../intern/components/HomeCalendar.tsx | 28 +- .../intern/components/ProjectsCard.tsx | 2 +- .../intern/components/SidebarIntern.tsx | 2 +- src/Pages/internify/layout.tsx | 2 +- .../internify/mentor/BaseProjectsPage.tsx | 32 ++- .../internify/mentor/CertificatePage.tsx | 1 - src/Pages/internify/mentor/HomeMentorPage.tsx | 2 +- .../internify/mentor/ProjectForumTab.tsx | 30 +- .../internify/mentor/ProjectsInternTab.tsx | 27 +- src/Pages/internify/mentor/ProjectsPage.tsx | 9 +- src/Pages/internify/mentor/TaskTab.tsx | 69 ++--- .../mentor/components/AddInternsDialog.tsx | 137 +++++---- .../mentor/components/CreateTaskDialog.tsx | 173 ++++------- .../mentor/components/EditTaskDialog.tsx | 193 ++++--------- .../mentor/components/HomeMentorProject.tsx | 34 +-- .../mentor/components/MentorProjectCard.tsx | 38 +-- .../mentor/components/MentorTaskCard.tsx | 27 +- .../mentor/components/SidebarMentor.tsx | 16 +- src/hooks/useInterns.ts | 19 ++ src/hooks/useNotification.ts | 44 +++ src/hooks/useSertificates.ts | 138 +++++++++ src/hooks/useTasks.ts | 6 +- src/services/CertificateService.ts | 62 ++++ src/services/MahasiswaService.ts | 8 + src/services/NotificationService.ts | 20 ++ src/services/UserService.ts | 3 + src/services/UserServices.ts | 0 src/types/certificate.types.ts | 46 +++ src/types/project.types.ts | 20 ++ 34 files changed, 895 insertions(+), 625 deletions(-) create mode 100644 src/hooks/useInterns.ts create mode 100644 src/hooks/useNotification.ts create mode 100644 src/hooks/useSertificates.ts create mode 100644 src/services/CertificateService.ts create mode 100644 src/services/MahasiswaService.ts create mode 100644 src/services/NotificationService.ts create mode 100644 src/services/UserServices.ts create mode 100644 src/types/certificate.types.ts diff --git a/src/Layout/Header-internify.tsx b/src/Layout/Header-internify.tsx index 553e2f7..a095fcf 100644 --- a/src/Layout/Header-internify.tsx +++ b/src/Layout/Header-internify.tsx @@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom"; export default function Header({ toggleSidebar }: { toggleSidebar: () => void }) { const nav = useNavigate(); return ( -
+
-
+
diff --git a/src/Pages/internify/LoginInternify.tsx b/src/Pages/internify/LoginInternify.tsx index 8799e4d..f728d83 100644 --- a/src/Pages/internify/LoginInternify.tsx +++ b/src/Pages/internify/LoginInternify.tsx @@ -34,7 +34,7 @@ export default function InternifyLogin() { try { setLoading(true); const response = await api.post("/auth-api/login", { email, password }); - + console.log(response.data.data) const { token } = response.data.data; document.cookie = `token=${token}; path=/; SameSite=Strict`; @@ -42,7 +42,7 @@ export default function InternifyLogin() { const role = decoded?.role; if (role === "intern") { navigate("/intern"); - } else if (role === "admin") { + } else if (role === "mentor" || role === "admin") { navigate("/mentor"); } else { setError("Role user tidak dikenali."); @@ -59,7 +59,7 @@ export default function InternifyLogin() { }; return ( -
+
@@ -72,7 +72,7 @@ export default function InternifyLogin() {

-
+

Welcome back

Access your dashboard with your credentials.

@@ -147,14 +147,14 @@ export default function InternifyLogin() {
- + SUPPORT
- + +
+ )} + {type === "achievement" && !isOld && ( + + )} +
+ + {/* Time */} + + {getTimeLabel(item.created_at)} + +
+ ); }; return ( -
+
{/* Header */}
@@ -50,110 +124,43 @@ export default function NotificationList() { Keep track of your interns' progress and system updates.

- {/* */} +
- {/* List Group Section */} + {notifications.length === 0 && ( +

Belum ada notifikasi.

+ )} +
- - {/* Render Group TODAY */} -
- {notifications.filter(n => n.group === "TODAY").map((item) => ( -
- {/* Icon Container */} -
- {item.type === 'new-task' && } - {item.type === 'achievement' && } -
+ {/* TODAY */} + {todayNotifs.length > 0 && ( +
+

Today

+ {todayNotifs.map(n => renderNotifCard(n))} +
+ )} - {/* Content text */} -
-
- {item.isNew && ( - - NEW - - )} -

{item.title}

-
- - {/* Custom description coloring logic */} -

- {item.type === 'new-task' ? ( - <>Mentor Sarah Johnson has pushed a new task: User Persona Analysis - ) : item.type === 'achievement' ? ( - <>Your certificate for UI Engineering Phase 1 is now available for download! - ) : item.description} -

- - {/* Conditional Actions */} - {item.type === 'new-task' && ( -
- - -
- )} - - {item.type === 'achievement' && ( - - )} -
- - {/* Time */} - - {item.time} - -
- ))} -
- - {/* Header Section YESTERDAY */} -
-

- YESTERDAY -

- - {notifications.filter(n => n.group === "YESTERDAY").map((item) => ( -
-
- -
-
-

{item.title}

-

- {item.description} -

-
- - {item.time} - -
- ))} -
+ {/* YESTERDAY */} + {yesterdayNotifs.length > 0 && ( +
+

Yesterday

+ {yesterdayNotifs.map(n => renderNotifCard(n, true))} +
+ )} + {/* OLDER */} + {olderNotifs.length > 0 && ( +
+

Older

+ {olderNotifs.map(n => renderNotifCard(n, true))} +
+ )}
); diff --git a/src/Pages/internify/intern/ParticipantTab.tsx b/src/Pages/internify/intern/ParticipantTab.tsx index d851ac6..82bf71f 100644 --- a/src/Pages/internify/intern/ParticipantTab.tsx +++ b/src/Pages/internify/intern/ParticipantTab.tsx @@ -1,11 +1,6 @@ -import { useParams } from 'react-router-dom'; -import { } from '../../../hooks/useProjects'; -import type { Project } from '../../../types/project.types'; - -export default function ParticipantsTab({ project }: { project: Project }) { - const { slug } = useParams<{ slug: string }>(); - const { interns, loading, error } = useProjectInterns(slug ?? ""); +import type { ProjectDetail, ProjectMember } from '../../../types/project.types'; +export default function ParticipantsTab({ project }: { project: ProjectDetail }) { return (
{/* Mentor */} @@ -30,44 +25,29 @@ export default function ParticipantsTab({ project }: { project: Project }) { {/* Members */}

Members

- - {loading && ( -
- {[...Array(3)].map((_, i) => ( -
- ))} -
- )} - - {error &&

{error}

} - - {!loading && !error && interns.map((intern, idx) => ( + {project.members.map((member: ProjectMember, idx) => (
- {intern.avatar ? ( - {intern.full_name} + {member.avatar ? ( + {member.full_name} ) : (
)}
- {intern.full_name} - {intern.email} + {member.full_name} + {member.email}
- {intern.kelompok_peminatan} + {member.kelompok_peminatan}
))} - {!loading && !error && interns.length === 0 && ( + {project.members.length === 0 && (

Belum ada members.

)}
diff --git a/src/Pages/internify/intern/ProjectDetailPage.tsx b/src/Pages/internify/intern/ProjectDetailPage.tsx index 7b97d40..5249e58 100644 --- a/src/Pages/internify/intern/ProjectDetailPage.tsx +++ b/src/Pages/internify/intern/ProjectDetailPage.tsx @@ -1,6 +1,5 @@ import { useState } from 'react'; import { useParams } from 'react-router-dom'; -// import { useProjectDetail } from '../../../hooks/useProjectDetail'; import { useProjectDetail } from '../../../hooks/useProjects'; import ForumTab from './ForumTab'; import ParticipantsTab from './ParticipantTab'; diff --git a/src/Pages/internify/intern/components/HomeCalendar.tsx b/src/Pages/internify/intern/components/HomeCalendar.tsx index 5ac0110..7b34212 100644 --- a/src/Pages/internify/intern/components/HomeCalendar.tsx +++ b/src/Pages/internify/intern/components/HomeCalendar.tsx @@ -1,6 +1,8 @@ import { useState, useEffect } from 'react' import { ChevronLeft, ChevronRight } from 'lucide-react' import { useMyTasks } from '../../../../hooks/useProjects'; +//SEMENTARA USERSERVICES BELUM KUBUAT +import api from '../../../../lib/api'; const DAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] const MONTH_NAMES = [ @@ -16,19 +18,29 @@ function getFirstDayOfMonth(year: number, month: number) { return new Date(year, month, 1).getDay() } -const HomeCalendar = () => { +interface roleUser { + role: "mentor" | "intern" | "admin"; +} + +export default function HomeCalendar() { const [today, setToday] = useState(new Date()); + const [role, setRole] = useState<"mentor" | "intern" | "admin" | null>(null); const { tasks } = useMyTasks(); - const DEADLINES = tasks.map(task => ({ - date: new Date(task.deadline_at), - label: task.title, - })); + useEffect(() => { + api.get("/auth-api/me") + .then((res) => setRole(res.data.data.role)) + .catch((err) => console.error("Failed to fetch user:", err)); + }, []); + + const DEADLINES = role === "intern" + ? tasks.map(task => ({ date: new Date(task.deadline_at), label: task.title, })) + : []; useEffect(() => { const interval = setInterval(() => { setToday(new Date()) - }, 60 * 1000) // update tiap 1 menit + }, 60 * 1000) return () => clearInterval(interval) }, []) @@ -187,6 +199,4 @@ const HomeCalendar = () => {
) -} - -export default HomeCalendar \ No newline at end of file +} \ No newline at end of file diff --git a/src/Pages/internify/intern/components/ProjectsCard.tsx b/src/Pages/internify/intern/components/ProjectsCard.tsx index 2cbea7c..37684aa 100644 --- a/src/Pages/internify/intern/components/ProjectsCard.tsx +++ b/src/Pages/internify/intern/components/ProjectsCard.tsx @@ -14,7 +14,7 @@ const ProjectCard = (project: Project) => { const Icon = getDynamicIcon(project.project_icon); - const tasksDone = -1; + const tasksDone = 1; const tasksTotal = project.total_tasks; return ( diff --git a/src/Pages/internify/intern/components/SidebarIntern.tsx b/src/Pages/internify/intern/components/SidebarIntern.tsx index 6f1c2f6..135c8c7 100644 --- a/src/Pages/internify/intern/components/SidebarIntern.tsx +++ b/src/Pages/internify/intern/components/SidebarIntern.tsx @@ -48,7 +48,7 @@ export default function SidebarIntern({ isOpen, closeSidebar }: SidebarProps) { {/* Brand & Tombol Close untuk Mobile */}
-
+

Internify

LMS Portal

diff --git a/src/Pages/internify/layout.tsx b/src/Pages/internify/layout.tsx index 2c05d3e..8c147a5 100644 --- a/src/Pages/internify/layout.tsx +++ b/src/Pages/internify/layout.tsx @@ -47,7 +47,7 @@ export const MentorLayout = () => { /> )}
- {/* */} +
diff --git a/src/Pages/internify/mentor/BaseProjectsPage.tsx b/src/Pages/internify/mentor/BaseProjectsPage.tsx index 420d919..337f706 100644 --- a/src/Pages/internify/mentor/BaseProjectsPage.tsx +++ b/src/Pages/internify/mentor/BaseProjectsPage.tsx @@ -1,11 +1,13 @@ -import { mockProjects } from '../../../lib/mockProjects'; import MentorProjectCard from './components/MentorProjectCard'; import { Plus } from 'lucide-react'; import { useState } from 'react'; import CreateProjectModal from './components/CreateProjectDialog'; +import { useProjects } from '../../../hooks/useProjects'; -export default function MentorProjectsPage(){ + +export default function MentorProjectsPage() { const [isModalOpen, setIsModalOpen] = useState(false); + const { projects, loading, error } = useProjects("active"); const openDialog = () => { setIsModalOpen(true); @@ -15,8 +17,18 @@ export default function MentorProjectsPage(){ setIsModalOpen(false); }; + if (loading) return ( +
+ {[...Array(3)].map((_, i) => ( +
+ ))} +
+ ); + + if (error) return

{error}

; + return ( -
+

List Projects

@@ -24,8 +36,8 @@ export default function MentorProjectsPage(){
{/* Tombol pemicu buka modal */} -
- {mockProjects.map(project => ( - + {projects.map(project => ( + ))}
{isModalOpen && ( - )}
diff --git a/src/Pages/internify/mentor/CertificatePage.tsx b/src/Pages/internify/mentor/CertificatePage.tsx index a93cb7e..92a5396 100644 --- a/src/Pages/internify/mentor/CertificatePage.tsx +++ b/src/Pages/internify/mentor/CertificatePage.tsx @@ -20,7 +20,6 @@ const initialHistoryData = [ ]; const MentorCertificatePage = () => { - // State Form Management const [selectedIntern, setSelectedIntern] = useState("JonathanKristina@gmail.com"); const [selectedProject, setSelectedProject] = useState("Internify Project (Web Development)"); const [issueDate, setIssueDate] = useState("2024-10-24"); diff --git a/src/Pages/internify/mentor/HomeMentorPage.tsx b/src/Pages/internify/mentor/HomeMentorPage.tsx index 7e17b73..4d0b6a3 100644 --- a/src/Pages/internify/mentor/HomeMentorPage.tsx +++ b/src/Pages/internify/mentor/HomeMentorPage.tsx @@ -8,7 +8,7 @@ const HomeMentorPage = () => {
-
+
diff --git a/src/Pages/internify/mentor/ProjectForumTab.tsx b/src/Pages/internify/mentor/ProjectForumTab.tsx index ba73be3..90ac57b 100644 --- a/src/Pages/internify/mentor/ProjectForumTab.tsx +++ b/src/Pages/internify/mentor/ProjectForumTab.tsx @@ -1,27 +1,31 @@ -import type { Project } from '../../../lib/mockProjects'; +import { useParams } from 'react-router-dom'; import ProjectTimeline from '../intern/components/ProjectTimeline'; import HeroProject from '../intern/components/Hero-project'; import MentorTaskCard from './components/MentorTaskCard'; +import { useProjectTasks } from '../../../hooks/useTasks'; +import type { Project } from '../../../types/project.types'; export default function MentorForumTab({ project }: { project: Project }) { + const { slug } = useParams<{ slug: string }>(); + const { tasks, loading, error } = useProjectTasks(slug ?? ""); return (
{/* Hero banner */} - + {/* Timeline + Task list */} -
-
- +
+
+
- -
- {project.tasks.map((task, idx) => ( - - ))} +
+ {tasks.length === 0 ? ( +

Belum ada task.

+ ) : ( + tasks.map((task) => ( + + )) + )}
diff --git a/src/Pages/internify/mentor/ProjectsInternTab.tsx b/src/Pages/internify/mentor/ProjectsInternTab.tsx index 6d1a33c..7718670 100644 --- a/src/Pages/internify/mentor/ProjectsInternTab.tsx +++ b/src/Pages/internify/mentor/ProjectsInternTab.tsx @@ -1,9 +1,9 @@ import { useState } from 'react'; -import type { Project, Intern } from '../../../lib/mockProjects'; import { Plus } from 'lucide-react'; import ManageInternsModal from './components/AddInternsDialog'; +import type { ProjectDetail, ProjectMember } from '../../../types/project.types'; -export default function InternsTab({ project }: { project: Project }) { +export default function InternsTab({ project }: { project: ProjectDetail }) { const [isModalOpen, setIsModalOpen] = useState(false); return ( @@ -15,8 +15,17 @@ export default function InternsTab({ project }: { project: Project }) {
+ {project.admin.profile_picture ? ( + {project.admin.full_name} + ) : ( +
+ )} - {project.mentor} + {project.admin.full_name}
@@ -37,19 +46,19 @@ export default function InternsTab({ project }: { project: Project }) {
- {project.interns.map((intern: Intern, idx: number) => ( + {project.members.map((member: ProjectMember, idx) => (
- {intern.name} + {member.full_name}
- {intern.role} + {member.position}
{isModalOpen && setIsModalOpen(false)} + onClose={() => setIsModalOpen(false)} + projectId={project.id} + initialMembers={project.members} />}
); diff --git a/src/Pages/internify/mentor/ProjectsPage.tsx b/src/Pages/internify/mentor/ProjectsPage.tsx index aeef2f9..442fe44 100644 --- a/src/Pages/internify/mentor/ProjectsPage.tsx +++ b/src/Pages/internify/mentor/ProjectsPage.tsx @@ -1,17 +1,18 @@ import { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { mockProjects } from '../../../lib/mockProjects'; +import { useProjectDetail } from '../../../hooks/useProjects'; import MentorForumTab from './ProjectForumTab'; import InternsTab from './ProjectsInternTab'; import TaskTab from './TaskTab'; -const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-'); - export default function ProjectDetailPage() { const { slug } = useParams<{ slug: string }>(); const [activeTab, setActiveTab] = useState<'forum' | 'participants' | 'Task'>('forum'); + const { project, loading, error } = useProjectDetail(slug ?? ""); - const project = mockProjects.find((p) => toSlug(p.name) === slug); + if (loading) return

Memuat project...

; + if (error) return

{error}

; + if (!project) return

Project tidak ditemukan.

if (!project) { return ( diff --git a/src/Pages/internify/mentor/TaskTab.tsx b/src/Pages/internify/mentor/TaskTab.tsx index a17c6a0..2c6d73b 100644 --- a/src/Pages/internify/mentor/TaskTab.tsx +++ b/src/Pages/internify/mentor/TaskTab.tsx @@ -1,28 +1,36 @@ -import type { Project, Task } from '../../../lib/mockData'; +import { useProjectTasks, useCreateTask, useDeleteTask } from '../../../hooks/useTasks'; +import type { ProjectDetail } from '../../../types/project.types'; +import type { ProjectTask } from '../../../types/task.types'; import { ClipboardList, Pencil, Trash2 } from 'lucide-react'; import { useState } from 'react'; import CreateTaskModal from './components/CreateTaskDialog'; import EditTaskModal from './components/EditTaskDialog'; -export default function TaskTab({ project }: { project: Project }) { - +export default function TaskTab({ project }: { project: ProjectDetail }) { + const { tasks, loading, error, refetch } = useProjectTasks(String(project.id)); + const { remove } = useDeleteTask(); const [isModalOpen, setIsModalOpen] = useState(false); const [editModalOpen, setEditModalOpen] = useState(false); + const [selectedTask, setSelectedTask] = useState(null); - const handleEditTask = (taskTitle: string) => { - console.log('Edit task:', taskTitle); + const handleDeleteTask = async (taskId: number) => { + const ok = await remove(taskId, String(project.id)); + if (ok) refetch(); }; - const handleDeleteTask = (taskTitle: string) => { - console.log('Delete task:', taskTitle); - }; + if (loading) return ( +
+ {[...Array(3)].map((_, i) => ( +
+ ))} +
+ ); + if (error) return

{error}

; return (
- {/* Header Section */}

Tasks

-
-
- {/* Task List Section */}
- {project.tasks && project.tasks.length > 0 ? ( - project.tasks.map((task: Task, idx: number) => ( + {tasks.length > 0 ? ( + tasks.map((task, idx) => (
- {/* Task Title & Icon */}
- - {task.title} - + {task.title}
- - {/* Action Buttons (Edit & Delete) */}
@@ -75,18 +75,23 @@ export default function TaskTab({ project }: { project: Project }) {
)}
- {isModalOpen && + + {isModalOpen && ( setIsModalOpen(false)} + projectId={String(project.id)} + onSuccess={() => { setIsModalOpen(false); refetch(); }} /> - } - {editModalOpen && + )} + {editModalOpen && selectedTask && ( setEditModalOpen(false)} + onClose={() => { setEditModalOpen(false); setSelectedTask(null); }} + task={selectedTask} + onSuccess={() => { setEditModalOpen(false); setSelectedTask(null); refetch(); }} /> - } + )}
); } \ No newline at end of file diff --git a/src/Pages/internify/mentor/components/AddInternsDialog.tsx b/src/Pages/internify/mentor/components/AddInternsDialog.tsx index e7541f9..0b48c78 100644 --- a/src/Pages/internify/mentor/components/AddInternsDialog.tsx +++ b/src/Pages/internify/mentor/components/AddInternsDialog.tsx @@ -1,28 +1,34 @@ +// ManageInternsModal.tsx import { useRef, useEffect, useState } from "react"; -import { X, Search, Plus, Trash2 } from "lucide-react"; +import { Search, Plus, Trash2 } from "lucide-react"; +import { useMahasiswa } from "../../../../hooks/useInterns"; +import { useAssignMember } from '../../../../hooks/useProjects'; +import type { ProjectMember } from '../../../../types/project.types'; +import type { Mahasiswa } from '../../../../types/project.types'; interface ManageInternsModalProps { isOpen: boolean; onClose: () => void; + projectId: number; + initialMembers: ProjectMember[]; } -export default function ManageInternsModal({ isOpen, onClose }: ManageInternsModalProps) { +export default function ManageInternsModal({ isOpen, onClose, projectId, initialMembers }: ManageInternsModalProps) { const dialogRef = useRef(null); - const [searchQuery, setSearchQuery] = useState("Jonathankristin@gmail.com"); + const [searchQuery, setSearchQuery] = useState(""); + const [invitedList, setInvitedList] = useState(initialMembers); - const [invitedList, setInvitedList] = useState([ - { id: 1, email: "Jonathankristina@gmail.com", initials: "JK" } - ]); + const { mahasiswa, loading: loadingMahasiswa } = useMahasiswa(); + const { assign, loading: assigning } = useAssignMember(); - // Data Rekomendasi/Hasil Pencarian (Simulasi list bawah yang ada ikon plus) - const [searchResult, setSearchResult] = useState([ - { id: 2, email: "Jonathankristin@gmail.com", initials: "JK" } - ]); + // Sync saat modal dibuka + useEffect(() => { + if (isOpen) setInvitedList(initialMembers); + }, [isOpen, initialMembers]); useEffect(() => { const dialog = dialogRef.current; if (!dialog) return; - if (isOpen) { dialog.showModal(); document.body.style.overflow = "hidden"; @@ -30,12 +36,7 @@ export default function ManageInternsModal({ isOpen, onClose }: ManageInternsMod dialog.close(); document.body.style.overflow = "unset"; } - - const handleCancel = (e: Event) => { - e.preventDefault(); - onClose(); - }; - + const handleCancel = (e: Event) => { e.preventDefault(); onClose(); }; dialog.addEventListener("cancel", handleCancel); return () => { dialog.removeEventListener("cancel", handleCancel); @@ -43,38 +44,66 @@ export default function ManageInternsModal({ isOpen, onClose }: ManageInternsMod }; }, [isOpen, onClose]); + // Filter mahasiswa yang belum jadi member & sesuai search + const invitedIds = new Set(invitedList.map(m => m.id)); + const searchResult = mahasiswa.filter(m => + !invitedIds.has(m.id) && + (m.email.toLowerCase().includes(searchQuery.toLowerCase()) || + `${m.nama_depan} ${m.nama_belakang}`.toLowerCase().includes(searchQuery.toLowerCase())) + ); + + const handleAssign = async (intern: Mahasiswa) => { + const result = await assign({ id_project: projectId, id_user: intern.id }); + if (result) { + setInvitedList(prev => [...prev, { + id: intern.id, + id_user: intern.id, + full_name: `${intern.nama_depan} ${intern.nama_belakang}`, + email: intern.email, + avatar: "", + kelompok_peminatan: intern.jurusan, + professional_bio: "", + position: intern.role, + } as ProjectMember]); + } + }; + + const getInitials = (m: Mahasiswa) => + `${m.nama_depan[0]}${m.nama_belakang[0]}`.toUpperCase(); + return ( - +
- {/* Search Input Area */}
setSearchQuery(e.target.value)} - placeholder="Search by email..." + placeholder="Cari nama atau email..." className="w-full pl-4 pr-10 py-3 bg-white border border-gray-200 rounded-xl text-sm placeholder-gray-400 text-gray-700 focus:outline-none focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium shadow-sm" />
- {/* List Container (Scrollable jika data banyak) */}
- - {/* List 1: Intern Yang Sudah Tergabung / Ditambahkan */} - {invitedList.map((intern) => ( -
+ {/* Sudah jadi member */} + {invitedList.map((member) => ( +
-
- {intern.initials} + {member.avatar ? ( + {member.full_name} + ) : ( +
+ {member.full_name.slice(0, 2).toUpperCase()} +
+ )} +
+ {member.full_name} + {member.email}
- {intern.email}
))} - {/* List 2: Hasil Pencarian / Belum Ditambahkan */} - {searchResult.map((intern) => ( -
-
-
- {intern.initials} + {/* Hasil pencarian — belum jadi member */} + {loadingMahasiswa ? ( +

Memuat...

+ ) : ( + searchResult.map((intern) => ( +
+
+
+ {getInitials(intern)} +
+
+ {intern.nama_depan} {intern.nama_belakang} + {intern.email} +
- {intern.email} +
- -
- ))} + )) + )} + {!loadingMahasiswa && searchQuery && searchResult.length === 0 && ( +

Tidak ada mahasiswa ditemukan.

+ )}
diff --git a/src/Pages/internify/mentor/components/CreateTaskDialog.tsx b/src/Pages/internify/mentor/components/CreateTaskDialog.tsx index 41f2c7f..d022e04 100644 --- a/src/Pages/internify/mentor/components/CreateTaskDialog.tsx +++ b/src/Pages/internify/mentor/components/CreateTaskDialog.tsx @@ -1,187 +1,112 @@ import { useRef, useEffect, useState } from "react"; import { X, FileText, Link } from "lucide-react"; +import { useCreateTask } from '../../../../hooks/useTasks'; +import type { SubmissionType } from '../../../../types/task.types'; interface CreateTaskModalProps { isOpen: boolean; onClose: () => void; + projectId: string; + onSuccess: () => void; } -export default function CreateTaskModal({ isOpen, onClose }: CreateTaskModalProps) { +export default function CreateTaskModal({ isOpen, onClose, projectId, onSuccess }: CreateTaskModalProps) { const dialogRef = useRef(null); + const { create, loading } = useCreateTask(projectId); - // Form States const [taskTitle, setTaskTitle] = useState(""); const [description, setDescription] = useState(""); const [deadlineDate, setDeadlineDate] = useState(""); const [specificTime, setSpecificTime] = useState(""); - const [submissionType, setSubmissionType] = useState<"file" | "url">("file"); + const [submissionType, setSubmissionType] = useState("file_upload"); useEffect(() => { const dialog = dialogRef.current; if (!dialog) return; - - if (isOpen) { - dialog.showModal(); - document.body.style.overflow = "hidden"; - } else { - dialog.close(); - document.body.style.overflow = "unset"; - } - - const handleCancel = (e: Event) => { - e.preventDefault(); - onClose(); - }; - + if (isOpen) { dialog.showModal(); document.body.style.overflow = "hidden"; } + else { dialog.close(); document.body.style.overflow = "unset"; } + const handleCancel = (e: Event) => { e.preventDefault(); onClose(); }; dialog.addEventListener("cancel", handleCancel); - return () => { - dialog.removeEventListener("cancel", handleCancel); - document.body.style.overflow = "unset"; - }; + return () => { dialog.removeEventListener("cancel", handleCancel); document.body.style.overflow = "unset"; }; }, [isOpen, onClose]); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - const payload = { taskTitle, description, deadlineDate, specificTime, submissionType }; - console.log("Saving Task:", payload); - alert("Task Saved Successfully!"); - onClose(); + const result = await create({ + title: taskTitle, + description, + deadline_date: deadlineDate, + specific_time: specificTime, + submission_type: submissionType, + }); + if (result) onSuccess(); }; + // JSX sama persis, hanya ganti handleSubmit dan tambah disabled pada button submit: return ( - - {/* Header Modal */} +

Create New Task

-

- Fill in the details for the next intern assignment. -

+

Fill in the details for the next intern assignment.

- - {/* Body Form Modal (Scrollable jika layar pendek) */}
- - {/* Task Title */} -
+
- setTaskTitle(e.target.value)} - className="w-full px-3 py-4 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-4 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]" />
- - {/* Description */}
-