feat: implement project management dashboard and certification module for mentors and interns
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user