diff --git a/src/App.tsx b/src/App.tsx index c47d62b..35c8ea7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -45,6 +45,8 @@ 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"; +import ViewInternSubmission from "./Pages/Internify/mentor/ViewInternSubmission"; +import SettingsPage from "./Pages/Internify/mentor/MentorSettingsPage"; import ProtectedRoutes from "./Pages/utils/ProtectedRoute"; function App() { @@ -86,29 +88,30 @@ function App() { } /> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - + }> + } /> + } /> + } /> + } /> + } /> + } /> + }/> + } /> + } /> + } /> + ); diff --git a/src/Layout/Header-internify.tsx b/src/Layout/Header-internify.tsx index 976913b..f17c057 100644 --- a/src/Layout/Header-internify.tsx +++ b/src/Layout/Header-internify.tsx @@ -7,7 +7,6 @@ export default function Header({ toggleSidebar }: { toggleSidebar: () => void }) return (
- {/* Tombol Burger Menu: Hanya muncul di layar < md */} -
diff --git a/src/Pages/internify/Breadcumbs.tsx b/src/Pages/internify/Breadcumbs.tsx index d2a28e6..8691960 100644 --- a/src/Pages/internify/Breadcumbs.tsx +++ b/src/Pages/internify/Breadcumbs.tsx @@ -35,7 +35,7 @@ const breadcrumbRoutes = [ { 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/certificates', breadcrumb: 'Certificates' }, { path: '/mentor/intern', breadcrumb: 'List Intern' }, { path: '/mentor/faq', breadcrumb: 'FAQ' }, { path: '/mentor/notifications', breadcrumb: 'Notifications' }, diff --git a/src/Pages/internify/intern/TaskSubmission.tsx b/src/Pages/internify/intern/TaskSubmission.tsx index 737e25f..9bd5b76 100644 --- a/src/Pages/internify/intern/TaskSubmission.tsx +++ b/src/Pages/internify/intern/TaskSubmission.tsx @@ -8,12 +8,12 @@ const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-'); export default function TaskSubmission() { const [searchParams] = useSearchParams(); - const { slug, taskSlug } = useParams<{ slug: string; taskSlug: string }>(); + const { slug, TaskSlug } = useParams<{ slug: string; TaskSlug: string }>(); const submissionType = (searchParams.get("type") ?? "file") as "file" | "link"; const project = mockProjects.find((p) => toSlug(p.name) === slug); - const task = project?.tasks.find((t) => toSlug(t.title) === taskSlug); + const task = project?.tasks.find((t) => toSlug(t.title) === TaskSlug); const handleFileSubmit = (files: File[]) => { const fileNames = files.map(f => f.name).join(', '); console.log(`Submitting Files for Project ${slug}:`, fileNames); @@ -28,47 +28,43 @@ export default function TaskSubmission() { return (
{/* Header Card */} -
+
-
+

- {task?.title ?? "Submit Task"} + {task?.title}

+ + Due {task?.deadline.label} at {task?.deadline.time} +
- - - {submissionType === "file" ? "📄 File Upload" : "🔗 Link Submission"} -
{/* Task Description Card */} -
-

Task Description

-

- {task?.description ?? "Kerjakan dan kirimkan tugas sesuai dengan instruksi yang diberikan."} +

+

Task Description

+

+ {task?.description}

{/* Submit Area Card */} -
-
+
+
-

Submit Task

+

Save Changes

{/* Render form sesuai tipe dari query param */} {submissionType === "file" ? ( - + ) : ( - + )} {/* Info Box */} -
+

Once submitted, your work will be locked for mentor review. You can request a re-submission if needed. diff --git a/src/Pages/internify/intern/components/SubmissionStatusTable.tsx b/src/Pages/internify/intern/components/SubmissionStatusTable.tsx new file mode 100644 index 0000000..b5fa1e9 --- /dev/null +++ b/src/Pages/internify/intern/components/SubmissionStatusTable.tsx @@ -0,0 +1,145 @@ +import { useState } from "react"; +import { FileText, FileUp, Link } from "lucide-react"; + +interface SubmitStatusTableProps { + type: "file" | "link"; + files?: File[]; + link?: string; + submittedAt?: Date; + deadline?: Date; + onEdit: () => void; + onDelete: () => void; +} + +function getTimeEarly(submittedAt: Date, deadline: Date): string { + const diffMs = deadline.getTime() - submittedAt.getTime(); + if (diffMs <= 0) return "Submitted after deadline"; + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const days = Math.floor(diffHours / 24); + const hours = diffHours % 24; + const parts = []; + if (days > 0) parts.push(`${days} day${days > 1 ? "s" : ""}`); + if (hours > 0) parts.push(`${hours} hour${hours > 1 ? "s" : ""}`); + return `Assignment was submitted ${parts.join(" ")} early`; +} + +export default function SubmitStatusTable({ + type, + files = [], + link = "", + submittedAt = new Date(), + deadline, + onEdit, + onDelete, +}: SubmitStatusTableProps) { + const [confirmDelete, setConfirmDelete] = useState(false); + + const timeLabel = deadline + ? getTimeEarly(submittedAt, deadline) + : "Assignment was submitted early"; + + const rows = [ + { label: "Submission status", value: "Submitted for grading" }, + { label: "Grading status", value: "Not graded" }, + { label: "Time Remaining", value: timeLabel }, + ]; + + return ( +

+ {/* Header */} +
+
+ {confirmDelete ? ( + + Are you sure you want to remove your submission? + + ) : ( + <> + + Submit Status + + )} +
+ + {confirmDelete ? ( + /* Confirm state */ +
+ + +
+ ) : ( + /* Default state */ +
+ + +
+ )} +
+ + {/* Rows */} +
+ {rows.map((row) => ( +
+
+ {row.label} +
+
{row.value}
+
+ ))} + + {/* File / Link row */} +
+
+ {type === "file" ? "File submission" : "Link submission"} +
+
+ {type === "file" ? ( + files.map((f, i) => ( +
+ + {f.name} +
+ )) + ) : ( + + + {link} + + )} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/Pages/internify/intern/components/TaskForm-file.tsx b/src/Pages/internify/intern/components/TaskForm-file.tsx index f9f6809..c7a4e1e 100644 --- a/src/Pages/internify/intern/components/TaskForm-file.tsx +++ b/src/Pages/internify/intern/components/TaskForm-file.tsx @@ -1,13 +1,14 @@ import { useState, useRef } from "react"; -import { FileText, UploadCloud, X, FileUp } from "lucide-react"; +import { FileText, UploadCloud, X } from "lucide-react"; import { LuSendHorizontal } from "react-icons/lu"; +import SubmitStatusTable from "./SubmissionStatusTable"; interface TaskFormFileProps { - taskId: string | undefined; onSubmitSuccess: (files: File[]) => void; + deadline?: Date; } -export default function TaskFormFile({ taskId, onSubmitSuccess }: TaskFormFileProps) { +export default function TaskFormFile({ onSubmitSuccess, deadline }: TaskFormFileProps) { const [files, setFiles] = useState([]); const [isDragActive, setIsDragActive] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false); @@ -88,77 +89,19 @@ export default function TaskFormFile({ taskId, onSubmitSuccess }: TaskFormFilePr } }; - if (isSubmitted) { - return ( -
- {/* Header Action */} -
-
- -

Submit Status

-
-
- - -
-
- - {/* Status Table */} -
-
-
- Submission status -
-
- Submitted for grading -
-
- -
-
- Grading status -
-
- Not graded -
-
- -
-
- Time Remaining -
-
- Assignment was submitted [waktu submitted] -
-
- -
-
- File submission -
-
- {files.map((f, i) => ( -
- - {f.name} -
- ))} -
-
-
-
- ); - } +// ganti if (isSubmitted) { return (...) } +if (isSubmitted) { + return ( + + ); +} return (
@@ -177,7 +120,7 @@ export default function TaskFormFile({ taskId, onSubmitSuccess }: TaskFormFilePr onDragLeave={handleDrag} onDrop={handleDrop} onClick={() => fileInputRef.current?.click()} - className={`w-full min-h-[120px] border border-dashed rounded-xl p-4 bg-white cursor-pointer transition-all ${isDragActive ? "border-[#B30000] bg-red-50/40" : "border-gray-200 hover:border-gray-300" + className={`w-full min-h-[120px] border border-dashed rounded-xl p-4 bg-box-secondary cursor-pointer transition-all ${isDragActive ? "border-[#B30000] bg-red-50/40" : "border-foreground hover:border-gray-500" }`} > {files.length > 0 ? ( diff --git a/src/Pages/internify/intern/components/TaskForm-link.tsx b/src/Pages/internify/intern/components/TaskForm-link.tsx index 9fa00c8..478133f 100644 --- a/src/Pages/internify/intern/components/TaskForm-link.tsx +++ b/src/Pages/internify/intern/components/TaskForm-link.tsx @@ -1,20 +1,21 @@ import { useState } from "react"; import { Link, FileText } from "lucide-react"; import { LuSendHorizontal } from "react-icons/lu"; +import SubmitStatusTable from "./SubmissionStatusTable"; interface TaskFormLinkProps { - taskId: string | undefined; onSubmitSuccess: (link: string) => void; + deadline?: Date; } -export default function TaskFormLink({ taskId, onSubmitSuccess }: TaskFormLinkProps) { +export default function TaskFormLink({ onSubmitSuccess, deadline }: TaskFormLinkProps) { const [submissionLink, setSubmissionLink] = useState(""); const [isSubmitted, setIsSubmitted] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!submissionLink.trim()) { - alert("Silakan masukkan link tugas Anda terlebih dahulu."); + // alert("Silakan masukkan link tugas Anda terlebih dahulu."); return; } setIsSubmitted(true); // ini hilang @@ -33,62 +34,17 @@ export default function TaskFormLink({ taskId, onSubmitSuccess }: TaskFormLinkPr }; if (isSubmitted) { - return ( -
- {/* Header */} -
-
- -

Submit Status

-
-
- - -
-
- - {/* Table */} -
-
-
Submission status
-
Submitted for grading
-
-
-
Grading status
-
pc: Not graded
-
-
-
Time Remaining
-
pc: Assignment was submitted 2 days 15 hours early
-
-
-
Link submission
- -
-
-
- ); - } + return ( + + ); +} return ( diff --git a/src/Pages/internify/layout.tsx b/src/Pages/internify/layout.tsx index 5da88f8..0379c1c 100644 --- a/src/Pages/internify/layout.tsx +++ b/src/Pages/internify/layout.tsx @@ -11,7 +11,7 @@ export const InternLayout = () => { const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen); const closeSidebar = () => setIsSidebarOpen(false); return ( -
+
@@ -36,7 +36,7 @@ export const MentorLayout = () => { const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen); const closeSidebar = () => setIsSidebarOpen(false); return ( -
+
diff --git a/src/Pages/internify/mentor/CertificateDetail.tsx b/src/Pages/internify/mentor/CertificateDetail.tsx new file mode 100644 index 0000000..475cd02 --- /dev/null +++ b/src/Pages/internify/mentor/CertificateDetail.tsx @@ -0,0 +1,361 @@ +// src/pages/mentor/CertificateDetail.tsx +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { + CalendarDays, + CheckCircle2, + AlertCircle, + Eye, + ChevronLeft, + ChevronRight, + Sparkles, + ArrowLeft, +} from "lucide-react"; + +// ─── Types ──────────────────────────────────────────────────────────────────── +type InternStatus = "ELIGIBLE" | "INCOMPLETE"; + +interface Intern { + id: number; + name: string; + role: string; + initials: string; + taskProgress: number; + taskTotal: number; + status: InternStatus; +} + +// ─── Mock Data ──────────────────────────────────────────────────────────────── +const mockProject = { + title: "Edge Computing Framework", + period: "Dec 2023 — Mar 2024", + status: "COMPLETED" as const, + scope: + "Advanced research and implementation of low-latency computing nodes at the network edge, focusing on real-time data processing for autonomous systems.", + totalInterns: 21, + eligibleInterns: 18, +}; + +const mockInterns: Intern[] = [ + { id: 1, name: "Marcus Rodriguez", role: "Software Engineer Intern", initials: "MR", taskProgress: 10, taskTotal: 10, status: "ELIGIBLE" }, + { id: 2, name: "Priya Sharma", role: "Frontend Developer Intern", initials: "PS", taskProgress: 8, taskTotal: 10, status: "INCOMPLETE" }, + { id: 3, name: "James Smith", role: "QA Engineering Intern", initials: "JS", taskProgress: 10, taskTotal: 10, status: "ELIGIBLE" }, + { id: 4, name: "Nina Castillo", role: "Mobile Development Intern", initials: "NC", taskProgress: 9, taskTotal: 10, status: "ELIGIBLE" }, + { id: 5, name: "Derek Lim", role: "DevOps & Cloud Intern", initials: "DL", taskProgress: 7, taskTotal: 10, status: "INCOMPLETE" }, + { id: 6, name: "Alex Johnson", role: "Backend Engineering Intern", initials: "AJ", taskProgress: 10, taskTotal: 10, status: "ELIGIBLE" }, +]; + +const TOTAL_ENTRIES = 1284; +const PER_PAGE = 3; + +// ─── Avatar ─────────────────────────────────────────────────────────────────── +const avatarColors: Record = { + MR: "bg-blue-100 text-blue-700", + PS: "bg-purple-100 text-purple-700", + JS: "bg-green-100 text-green-700", + NC: "bg-pink-100 text-pink-700", + DL: "bg-teal-100 text-teal-700", + AJ: "bg-orange-100 text-orange-700", +}; + +function Avatar({ initials }: { initials: string }) { + const color = avatarColors[initials] ?? "bg-gray-100 text-gray-500"; + return ( +
+ {initials} +
+ ); +} + +// ─── Progress Bar ───────────────────────────────────────────────────────────── +function ProgressBar({ value, total }: { value: number; total: number }) { + const pct = Math.round((value / total) * 100); + const isComplete = value === total; + return ( +
+
+
+
+ + {value}/{total} + +
+ ); +} + +// ─── Status Badge ───────────────────────────────────────────────────────────── +function StatusBadge({ status }: { status: InternStatus }) { + const isEligible = status === "ELIGIBLE"; + return ( +
+ {isEligible + ? + : } + {status} +
+ ); +} + +// ─── Main Page ──────────────────────────────────────────────────────────────── +export default function CertificateDetail() { + const navigate = useNavigate(); + + // Selected interns (checked) + const initialSelected = new Set( + mockInterns.filter((i) => i.status === "ELIGIBLE").map((i) => i.id) + ); + const [selected, setSelected] = useState>(initialSelected); + const [page, setPage] = useState(1); + + const totalPages = Math.ceil(mockInterns.length / PER_PAGE); + const paginated = mockInterns.slice((page - 1) * PER_PAGE, page * PER_PAGE); + + const eligibleIds = mockInterns.filter((i) => i.status === "ELIGIBLE").map((i) => i.id); + const allEligibleSelected = eligibleIds.every((id) => selected.has(id)); + + const toggleSelectAll = () => { + if (allEligibleSelected) { + setSelected(new Set()); + } else { + setSelected(new Set(eligibleIds)); + } + }; + + const toggleOne = (id: number, status: InternStatus) => { + if (status === "INCOMPLETE") return; // tidak bisa dipilih + setSelected((prev) => { + const next = new Set(prev); + next.has(id) ? next.delete(id) : next.add(id); + return next; + }); + }; + + return ( +
+ {/* Breadcrumb */} +
navigate("/mentor/certificates/all")} + > + Certificates › +
+ + {/* Page Header */} +
+

Certificates

+

+ Administrate, issue, and track professional certifications for all interns. +

+
+ + {/* ── Two-column layout ── */} +
+ + {/* ── LEFT: Project Info ── */} +
+ {/* Status badge */} +
+ + COMPLETED +
+ + {/* Title */} +

+ {mockProject.title} +

+ + {/* Period */} +
+ + {mockProject.period} +
+ + {/* Project Scope */} +
+

+ Project Scope +

+

+ {mockProject.scope} +

+
+ + {/* Stats: Total & Eligible */} +
+
+

{mockProject.totalInterns}

+

Total Interns

+
+
+

{mockProject.eligibleInterns}

+

Eligible

+
+
+
+ + {/* ── RIGHT: Intern Roster ── */} +
+ + {/* Roster Header */} +
+
+

Intern Roster

+

+ Confirm completion status before final certificate generation. +

+
+ {/* Select All button */} + +
+ + {/* Column Headers */} +
+
+ Intern Name + Task Progress + Status + Action +
+ + {/* Rows */} +
+ {paginated.map((intern) => { + const isChecked = selected.has(intern.id); + const isIncomplete = intern.status === "INCOMPLETE"; + return ( +
+ {/* Checkbox */} + + + {/* Name + role */} +
+ +
+

{intern.name}

+

{intern.role}

+
+
+ + {/* Task Progress */} +
+ +
+ + {/* Status */} +
+ +
+ + {/* Action */} +
+ {isIncomplete ? ( + + ) : ( + + )} +
+
+ ); + })} +
+ + {/* Pagination Footer */} +
+

+ Showing {(page - 1) * PER_PAGE + 1} to {Math.min(page * PER_PAGE, mockInterns.length)} of {TOTAL_ENTRIES.toLocaleString()} entries +

+
+ + {Array.from({ length: totalPages }, (_, i) => i + 1).map((n) => ( + + ))} + +
+
+
+
+ + {/* ── Bottom Action Bar ── */} +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/Pages/internify/mentor/CertificateGenerate.tsx b/src/Pages/internify/mentor/CertificateGenerate.tsx new file mode 100644 index 0000000..1769de3 --- /dev/null +++ b/src/Pages/internify/mentor/CertificateGenerate.tsx @@ -0,0 +1,240 @@ +// src/pages/mentor/CertificateResult.tsx +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { + CheckCircle2, + FolderArchive, + Search, + Eye, + Download, + Share2, + ChevronLeft, + ChevronRight, +} from "lucide-react"; + +// ─── Types ──────────────────────────────────────────────────────────────────── +interface InternCert { + id: number; + name: string; + role: string; + initials: string; + credentialId: string; +} + +// ─── Mock Data ──────────────────────────────────────────────────────────────── +const mockCerts: InternCert[] = [ + { id: 1, name: "Alex Johnson", role: "Backend Engineering", initials: "AJ", credentialId: "CERT-2023-0045" }, + { id: 2, name: "Sarah Lin", role: "UI/UX Design Specialist",initials: "SL", credentialId: "CERT-2023-0046" }, + { id: 3, name: "Marcus Kane", role: "Data Analytics", initials: "MK", credentialId: "CERT-2023-0047" }, + { id: 4, name: "Priya Sharma", role: "Frontend Engineering", initials: "PS", credentialId: "CERT-2023-0048" }, + { id: 5, name: "Derek Lim", role: "DevOps & Cloud", initials: "DL", credentialId: "CERT-2023-0049" }, + { id: 6, name: "Nina Castillo", role: "Mobile Development", initials: "NC", credentialId: "CERT-2023-0050" }, +]; + +const TOTAL_CERTS = 18; +const PER_PAGE = 3; + +// ─── Avatar ─────────────────────────────────────────────────────────────────── +const avatarColors: Record = { + AJ: "bg-blue-100 text-blue-700", + SL: "bg-purple-100 text-purple-700", + MK: "bg-orange-100 text-orange-700", + PS: "bg-teal-100 text-teal-700", + DL: "bg-green-100 text-green-700", + NC: "bg-pink-100 text-pink-700", +}; + +function Avatar({ initials }: { initials: string }) { + const color = avatarColors[initials] ?? "bg-gray-100 text-gray-600"; + return ( +
+ {initials} +
+ ); +} + +// ─── Main Page ──────────────────────────────────────────────────────────────── +export default function CertificateResult() { + const navigate = useNavigate(); + const [search, setSearch] = useState(""); + const [page, setPage] = useState(1); + + // Filter + paginate + const filtered = mockCerts.filter( + (c) => + c.name.toLowerCase().includes(search.toLowerCase()) || + c.role.toLowerCase().includes(search.toLowerCase()) + ); + const totalPages = Math.ceil(filtered.length / PER_PAGE); + const paginated = filtered.slice((page - 1) * PER_PAGE, page * PER_PAGE); + + const handleSearch = (e: React.ChangeEvent) => { + setSearch(e.target.value); + setPage(1); + }; + + return ( +
+ {/* Breadcrumb */} +
navigate("/mentor/certificates/all")}> + Certificates › +
+ + {/* Page Header */} +
+

Certificates

+

+ Administrate, issue, and track professional certifications for all interns. +

+
+ + {/* ── Success Hero ── */} +
+ {/* Icon circle */} +
+ +
+ + {/* Headline */} +

+ {TOTAL_CERTS} Certificates Successfully Generated! +

+ + {/* Subtext */} +

+ The validation engine has processed all engineering credentials. You can now + publish them to the public registry or download the batch for offline distribution. +

+ + {/* Download CTA */} + +
+ + {/* ── Bottom Section: Table + Preview Card ── */} +
+ + {/* Recipient Directory Table */} +
+ + {/* Table Header */} +
+

Recipient Directory

+ {/* Search */} +
+ + +
+
+ + {/* Column Headers */} +
+ Intern Name + Credential ID + Actions +
+ + {/* Rows */} +
+ {paginated.length === 0 ? ( +
+ No interns match your search. +
+ ) : ( + paginated.map((cert) => ( +
+ {/* Name + role */} +
+ +
+

{cert.name}

+

{cert.role}

+
+
+ + {/* Credential ID */} +

{cert.credentialId}

+ + {/* Actions */} +
+ + + +
+
+ )) + )} +
+ + {/* Pagination Footer */} +
+

+ Showing {filtered.length === 0 ? 0 : (page - 1) * PER_PAGE + 1} to{" "} + {Math.min(page * PER_PAGE, filtered.length)} of {TOTAL_CERTS} interns +

+
+ + {Array.from({ length: totalPages }, (_, i) => i + 1).map((n) => ( + + ))} + +
+
+
+ + {/* Preview Card (placeholder — gambar belum diimplementasi) */} +
+
+

+ Institutional Excellence +

+

+ Certified via Mentor Portal v2.4 +

+
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/Pages/internify/mentor/CertificateListPage.tsx b/src/Pages/internify/mentor/CertificateListPage.tsx new file mode 100644 index 0000000..54420bd --- /dev/null +++ b/src/Pages/internify/mentor/CertificateListPage.tsx @@ -0,0 +1,149 @@ +// src/pages/mentor/CertificateList.tsx +import { useNavigate } from "react-router-dom"; +import {CalendarDays} from "lucide-react"; + +// ─── Types ─────────────────────────────────────────────────────────────────── +type CertStatus = "COMPLETED" | "PENDING CERTS"; + +interface Project { + id: number; + title: string; + period: string; + internCount: number; + status: CertStatus; +} + +// ─── Mock Data ──────────────────────────────────────────────────────────────── +const mockProjects: Project[] = [ + { + id: 1, + title: "Autonomous Navigation Systems", + period: "Jan 2024 - Apr 2024", + internCount: 14, + status: "COMPLETED", + }, + { + id: 2, + title: "Full-Stack Fintech Solution", + period: "Feb 2024 - June 2024", + internCount: 8, + status: "PENDING CERTS", + }, + { + id: 3, + title: "User Experience Audit 2024", + period: "Mar 2024 - May 2024", + internCount: 5, + status: "PENDING CERTS", + }, + { + id: 4, + title: "Edge Computing Framework", + period: "Dec 2023 - Mar 2024", + internCount: 21, + status: "COMPLETED", + }, +]; + +// ─── Sub Components ─────────────────────────────────────────────────────────── + +/** Badge status: COMPLETED (hijau) vs PENDING CERTS (kuning) */ +function StatusBadge({ status }: { status: CertStatus }) { + const isCompleted = status === "COMPLETED"; + return ( + + {status} + + ); +} + +/** Satu card project */ +function ProjectCard({ + project, + onClick, +}: { + project: Project; + onClick: () => void; +}) { + return ( +
+ {/* Top: icon + status */} +
+ +
+ + {/* Title + period */} +
+

+ {project.title} +

+
+ + {project.period} +
+
+ +
+ + {/* Intern count + avatars */} +
+
+

Interns

+

+ {project.internCount} {project.internCount === 1 ? "intern" : "Interns"} +

+
+
+
+ ); +} + +// ─── Main Page ──────────────────────────────────────────────────────────────── +export default function CertificateList() { + const navigate = useNavigate(); + + const handleCardClick = (id: number) => { + navigate(`/mentor/certificates/${id}`); + }; + + return ( +
+ {/* Breadcrumb */} +
+ Certificates › +
+ + {/* Page Header */} +
+

Certificates

+

+ Administrate, issue, and track professional certifications for all interns. +

+
+ + {/* Section: List Projects */} +
+

List Projects

+ +
+ {mockProjects.map((project) => ( + handleCardClick(project.id)} + /> + ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/Pages/internify/mentor/CertificatePage.tsx b/src/Pages/internify/mentor/CertificatePage.tsx index c899c97..a93cb7e 100644 --- a/src/Pages/internify/mentor/CertificatePage.tsx +++ b/src/Pages/internify/mentor/CertificatePage.tsx @@ -58,10 +58,9 @@ const MentorCertificatePage = () => {
{/* Main Grid Content */} -
- +
{/* Kiri: Form Issue New Certificate (4 Kolom) */} -
+

Issue New Certificate

@@ -143,7 +142,7 @@ const MentorCertificatePage = () => {
{/* Kanan: Issued Certificates History (8 Kolom) */} -
+
{/* Header Table */}
diff --git a/src/Pages/internify/mentor/InternsPage.tsx b/src/Pages/internify/mentor/InternsPage.tsx index 6fbddc5..b80fbd8 100644 --- a/src/Pages/internify/mentor/InternsPage.tsx +++ b/src/Pages/internify/mentor/InternsPage.tsx @@ -41,8 +41,7 @@ export default function InternsManagement() { {/* Filter Row & Stats Grid */}
{/* Left Control Inputs */} -
- +
{/* Search Box Input */}
@@ -101,7 +100,7 @@ export default function InternsManagement() {
- diff --git a/src/Pages/internify/mentor/MentorProjectDetailPage.tsx b/src/Pages/internify/mentor/MentorProjectDetailPage.tsx index 064044e..bedd9b9 100644 --- a/src/Pages/internify/mentor/MentorProjectDetailPage.tsx +++ b/src/Pages/internify/mentor/MentorProjectDetailPage.tsx @@ -1,19 +1,21 @@ -import { useParams } from "react-router-dom"; +import { useParams, useNavigate } from "react-router-dom"; import { useState } from "react"; import { mockProjects } from "../../../lib/mockProjects"; import { Calendar, Edit, SlidersHorizontal, Search } from "lucide-react"; import EditSubmissionModal from "./components/EditSubmissionTask"; + const toSlug = (name: string) => name.toLowerCase().replace(/\s+/g, '-'); export default function MentorProjectsDetailPage() { + const navigate = useNavigate(); const { slug, taskSlug } = useParams<{ slug: string; taskSlug: string }>(); const targetedProject = mockProjects.find((p) => toSlug(p.name) === slug); - + if (!targetedProject) { return
Project tidak ditemukan.
; } - + const targetedTask = targetedProject.tasks.find((t) => toSlug(t.title) === taskSlug); if (!targetedTask) { return
Tugas tidak ditemukan.
; @@ -36,7 +38,7 @@ export default function MentorProjectsDetailPage() {
{/* Kotak Informasi Detail & Deadline */} -
+

Description

@@ -94,7 +96,7 @@ export default function MentorProjectsDetailPage() { {/* Kolom Nama & Profil */} -

+
{internProfile.name} @@ -124,7 +126,15 @@ export default function MentorProjectsDetailPage() { {/* Kolom Tombol Aksi */} - diff --git a/src/Pages/internify/mentor/MentorSettingsPage.tsx b/src/Pages/internify/mentor/MentorSettingsPage.tsx new file mode 100644 index 0000000..4a8f4de --- /dev/null +++ b/src/Pages/internify/mentor/MentorSettingsPage.tsx @@ -0,0 +1,183 @@ +import { useState, useRef } from "react"; + +const mockProfileData = { + fullName: "JonathanKristina", + email: "JonathanKristina@gmail.com", + bio: "UIUX holic" +}; + +export default function MentorSettingsPage() { + const [formData, setFormData] = useState(mockProfileData); + const [photoPreview, setPhotoPreview] = useState(null); + const fileInputRef = useRef(null); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSaveChanges = (e: React.FormEvent) => { + e.preventDefault(); + alert("Changes saved successfully!"); + console.log("Saved Data:", formData); + }; + + const handlePhotoUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => setPhotoPreview(reader.result as string); + reader.readAsDataURL(file); + } + }; + + const handleRemovePhoto = () => { + setPhotoPreview(null); + if (fileInputRef.current) fileInputRef.current.value = ""; + }; + + const initialLetter = formData.fullName ? formData.fullName.charAt(0).toUpperCase() : "U"; + + return ( +
+ {/* Header Title */} +
+

Settings

+

+ Manage and edit your profile and account information. +

+
+ {/* White Card Container */} +
+ + {/* Card Header */} +
+

Profile Information

+

+ Update your photo and personal details. +

+
+ + {/* Card Body & Form */} + + + {/* Profile Photo Row */} +
+ {/* Avatar dengan badge edit */} +
+
+ {photoPreview ? ( + Profile + ) : ( + {initialLetter} + )} +
+ {/* Badge edit icon */} + +
+ + {/* Info & Action Buttons */} +
+

Profile Photo

+

+ JPG, GIF or PNG. Recommended size 800x800px. Max size of 800K. +

+
+ + +
+
+ + {/* Hidden file input */} + +
+ +
+ + {/* Grid Inputs */} +
+
+ + +
+
+ + +
+
+ + {/* Professional Bio */} +
+ +