diff --git a/src/Pages/internify/LoginInternify.tsx b/src/Pages/internify/LoginInternify.tsx
index 56d417a..e4be35c 100644
--- a/src/Pages/internify/LoginInternify.tsx
+++ b/src/Pages/internify/LoginInternify.tsx
@@ -46,7 +46,6 @@ export default function InternifyLogin() {
document.cookie = `token=${token}; path=/; SameSite=Strict`;
const decoded = decodeJWT(token);
- console.log(decoded)
const role = decoded?.role;
if (role === "intern") {
navigate("/intern");
diff --git a/src/Pages/internify/NotificationList.tsx b/src/Pages/internify/NotificationList.tsx
index abd781a..e9e396f 100644
--- a/src/Pages/internify/NotificationList.tsx
+++ b/src/Pages/internify/NotificationList.tsx
@@ -1,7 +1,9 @@
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
-import { Check, Download } from "lucide-react";
+import { RiCheckDoubleLine } from "react-icons/ri";
+import { Check } from "lucide-react";
import { LuFileSpreadsheet } from "react-icons/lu";
+import { MdOutlineEmail } from "react-icons/md";
import { io } from "socket.io-client";
import * as NotificationService from "../../services/NotificationService";
import type { UINotification, BackendNotification } from "../../types/notification.types";
@@ -22,8 +24,9 @@ export default function NotificationList() {
const loadNotifications = async () => {
try {
const data = await NotificationService.getNotifications();
- const unread = data.filter((n) => !n.is_read);
- const mapped = unread.map(NotificationService.mapNotificationToUI);
+ // const unread = data.filter((n) => !n.is_read);
+ // const mapped = unread.map(NotificationService.mapNotificationToUI);
+ const mapped = data.map(NotificationService.mapNotificationToUI);
setNotifications(mapped);
} catch (err) {
console.error("Failed to load notifications:", err);
@@ -35,7 +38,7 @@ export default function NotificationList() {
const token = getCookieToken();
if (!token) return;
- const socketUrl = import.meta.env.VITE_API_BASE_URL || "http://localhost:9000";
+ const socketUrl = import.meta.env.VITE_API_BASE_URL;
const socket = io(socketUrl, {
auth: { token },
transports: ["websocket"]
@@ -47,7 +50,6 @@ export default function NotificationList() {
socket.on("notification", (notif: BackendNotification) => {
console.log("New realtime notification received:", notif);
- if (notif.is_read) return;
const mapped = NotificationService.mapNotificationToUI(notif);
setNotifications(prev => {
if (prev.some(p => p.id === mapped.id)) {
@@ -69,7 +71,7 @@ export default function NotificationList() {
const handleMarkAllRead = async () => {
try {
const res = await NotificationService.markAllAsRead();
- setNotifications([]);
+ setNotifications(prev => prev.map(n => ({ ...n, isNew: false })));
customToast.success("Berhasil", res.message);
} catch (err: any) {
const msg = err?.response?.data?.message ?? "Gagal menandai semua notifikasi.";
@@ -80,7 +82,9 @@ export default function NotificationList() {
const handleReadMessage = async (id: number) => {
try {
const res = await NotificationService.markAsRead(id);
- setNotifications(prev => prev.filter(n => n.id !== id));
+ setNotifications(prev =>
+ prev.map(n => (n.id === id ? { ...n, isNew: false } : n))
+ );
customToast.success("Ditandai dibaca", res.message);
} catch (err: any) {
const msg = err?.response?.data?.message ?? "Gagal menandai notifikasi.";
@@ -100,9 +104,9 @@ export default function NotificationList() {
@@ -115,72 +119,136 @@ export default function NotificationList() {
)}
-
- {/* Render Group TODAY */}
-
- {notifications.filter(n => n.group === "TODAY").map((item) => (
-
- {/* Icon Container */}
-
- {item.type === 'new-task' && }
- {item.type === 'achievement' && }
-
-
- {/* Content text */}
-
-
- {item.isNew && (
-
- NEW
-
- )}
-
{item.title}
-
-
- {/* Custom description coloring logic */}
-
- {item.description}
-
-
- {/* Conditional Actions */}
- {item.type === 'new-task' && item.link && (
-
-
-
+ {/* SECTION TODAY */}
+ {notifications.filter((n) => n.group === "TODAY").length > 0 && (
+
+ {notifications
+ .filter((n) => n.group === "TODAY")
+ .map((item) => (
+
+ {/* Icon Container */}
+
+ {item.type === "new-task" ? (
+
+ ) : item.type === "achievement" ? (
+
+ ) : (
+
+ )}
- )}
- {item.type === 'achievement' && (
-
- )}
-
+ {/* Content Text */}
+
+
+ {item.isNew && (
+
+ NEW
+
+ )}
+
+ {item.title}
+
+
- {/* Time */}
-
- {item.time}
-
-
- ))}
-
+
+ {item.description}
+
+
+ {/* Actions */}
+
+ {item.link && (
+
+ )}
+ {item.isNew && (
+
+ )}
+
+
+
+ {/* Time */}
+
+ {item.time}
+
+
+ ))}
+
+ )}
+
+ {/* SECTION YESTERDAY / SEBELUMNYA */}
+ {notifications.filter((n) => n.group !== "TODAY").length > 0 && (
+
+ {/* Subheader Title */}
+
+ YESTERDAY
+
+
+ {notifications
+ .filter((n) => n.group !== "TODAY")
+ .map((item) => (
+
+ {/* Icon Container Gray */}
+
+ {item.type === "new-task" ? (
+
+ ) : item.type === "achievement" ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Content Text */}
+
+
+ {item.title}
+
+
+ {item.description}
+
+
+ {/* Action Button untuk Yesterday jika ada */}
+ {item.link && (
+
+
+
+ )}
+
+
+ {/* Time */}
+
+ {item.time}
+
+
+ ))}
+
+ )}
);
diff --git a/src/Pages/internify/intern/components/ProjectTaskCard.tsx b/src/Pages/internify/intern/components/ProjectTaskCard.tsx
index ef10285..c9060f3 100644
--- a/src/Pages/internify/intern/components/ProjectTaskCard.tsx
+++ b/src/Pages/internify/intern/components/ProjectTaskCard.tsx
@@ -53,12 +53,10 @@ export default function TaskCard({ task }: { task: InternTaskItem }) {
Tipe Submission : {task.submission_type === 'file_upload' ? "File" : "Link"}
-
-
-
+
- {statusLabels[task.submission_status]}
+ className={`font-semibold text-xs px-3.5 py-[5px] whitespace-nowrap`}>
+ {statusLabels[displayStatus]}
diff --git a/src/Pages/internify/mentor/MentorProjectDetailPage.tsx b/src/Pages/internify/mentor/MentorProjectDetailPage.tsx
index db9d123..f3b8ce0 100644
--- a/src/Pages/internify/mentor/MentorProjectDetailPage.tsx
+++ b/src/Pages/internify/mentor/MentorProjectDetailPage.tsx
@@ -118,7 +118,11 @@ export default function MentorProjectsDetailPage() {
{/* Kolom Tanggal Kumpul */}
{sub.submitted_at
- ? new Date(sub.submitted_at).toLocaleDateString("id-ID")
+ ? new Date(sub.submitted_at).toLocaleString("id-ID", {
+ day: "numeric",
+ month: "long",
+ year: "numeric",
+ })
: "-"}
|
@@ -126,12 +130,12 @@ export default function MentorProjectsDetailPage() {
{sub.display_status === "done" && (
- Done
+ Done
)}
{sub.display_status === "pending" && (
- Pending
+ Pending
)}
{sub.display_status === "overdue" && (
diff --git a/src/Pages/internify/mentor/components/CreateTaskDialog.tsx b/src/Pages/internify/mentor/components/CreateTaskDialog.tsx
index ad70a34..2e0113d 100644
--- a/src/Pages/internify/mentor/components/CreateTaskDialog.tsx
+++ b/src/Pages/internify/mentor/components/CreateTaskDialog.tsx
@@ -17,6 +17,7 @@ export default function CreateTaskModal({ isOpen, onClose, projectId, onSuccess
const [taskTitle, setTaskTitle] = useState("");
const [description, setDescription] = useState("");
const [deadlineDate, setDeadlineDate] = useState("");
+ const [dateError, setDateError] = useState("");
const [specificTime, setSpecificTime] = useState("");
const [submissionType, setSubmissionType] = useState("file_upload");
@@ -24,32 +25,43 @@ export default function CreateTaskModal({ isOpen, onClose, projectId, onSuccess
const dialog = dialogRef.current;
if (!dialog) return;
- if (isOpen) {
- dialog.showModal();
+ if (isOpen) {
+ dialog.showModal();
document.body.style.overflow = "hidden";
}
- else {
- dialog.close();
+ else {
+ dialog.close();
document.body.style.overflow = "unset";
}
-
+
const handleCancel = (e: Event) => {
e.preventDefault();
};
-
+
dialog.addEventListener("cancel", handleCancel);
-
+
return () => {
dialog.removeEventListener(
"cancel", handleCancel
- );
- document.body.style.overflow = "unset";
+ );
+ document.body.style.overflow = "unset";
};
}, [isOpen, onClose]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
+
+ setDateError("");
+
+ if (deadlineDate) {
+ const today = new Date().toISOString().split("T")[0];
+ if (deadlineDate < today) {
+ setDateError("Deadline date cannot be in the past");
+ return;
+ }
+ }
+
const result = await create({
title: taskTitle,
description,
@@ -104,8 +116,11 @@ export default function CreateTaskModal({ isOpen, onClose, projectId, onSuccess
type="date"
required
value={deadlineDate}
- onChange={(e) => setDeadlineDate(e.target.value)}
+ onChange={(e) => { setDeadlineDate(e.target.value); setDateError(""); }}
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]" />
+ {dateError && (
+ {dateError}
+ )}
diff --git a/src/Pages/internify/mentor/components/StatusSubmissionTable.tsx b/src/Pages/internify/mentor/components/StatusSubmissionTable.tsx
index 59df0cf..13c6da8 100644
--- a/src/Pages/internify/mentor/components/StatusSubmissionTable.tsx
+++ b/src/Pages/internify/mentor/components/StatusSubmissionTable.tsx
@@ -37,7 +37,7 @@ interface StatusTableProps {
}
export function StatusTable({ status, submission }: StatusTableProps) {
- const hasSubmission = status === "submitted" || status === "done";
+ const hasSubmission = status === "submitted" || status == "overdue";
const rows: { label: string; value: React.ReactNode }[] = [
{
diff --git a/src/Pages/utils/showToast.tsx b/src/Pages/utils/showToast.tsx
index 330ade1..bbb023b 100644
--- a/src/Pages/utils/showToast.tsx
+++ b/src/Pages/utils/showToast.tsx
@@ -1,7 +1,7 @@
import toast, { type Toast } from 'react-hot-toast';
import { FiX } from 'react-icons/fi';
import { FaCheck } from "react-icons/fa";
-import { IoAlertOutline } from "react-icons/io5";
+import { IoAlertCircleOutline } from "react-icons/io5";
function ToastCard({ t, type, title, description }: {
t: Toast;
@@ -19,7 +19,7 @@ function ToastCard({ t, type, title, description }: {
>
- {isSuccess ? : }
+ {isSuccess ? : }
|