diff --git a/src/Pages/internify/NotificationList.tsx b/src/Pages/internify/NotificationList.tsx index 2941442..e3afa68 100644 --- a/src/Pages/internify/NotificationList.tsx +++ b/src/Pages/internify/NotificationList.tsx @@ -20,7 +20,8 @@ export default function NotificationList() { const loadNotifications = async () => { try { const data = await NotificationService.getNotifications(); - const mapped = data.map(NotificationService.mapNotificationToUI); + const unread = data.filter((n) => !n.is_read); + const mapped = unread.map(NotificationService.mapNotificationToUI); setNotifications(mapped); } catch (err) { console.error("Failed to load notifications:", err); @@ -44,6 +45,7 @@ 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)) { @@ -66,7 +68,7 @@ export default function NotificationList() { const handleMarkAllRead = async () => { try { await NotificationService.markAllAsRead(); - setNotifications(prev => prev.map(n => ({ ...n, isNew: false }))); + setNotifications([]); alert("All notifications marked as read!"); } catch (err) { console.error("Failed to mark all as read:", err); @@ -130,16 +132,16 @@ export default function NotificationList() {

{/* Conditional Actions */} - {item.type === 'new-task' && ( + {item.type === 'new-task' && item.link &&(
- + */}
)} @@ -148,7 +150,7 @@ export default function NotificationList() { className="flex items-center gap-1 text-xs font-bold text-[#B30000] hover:underline pt-2" onClick={() => navigate(`/certificates`)}> - Download Certificate + Ke Halaman )} diff --git a/src/Pages/internify/mentor/components/EligibleInternTable.tsx b/src/Pages/internify/mentor/components/EligibleInternTable.tsx index 6dc11f1..0a52fec 100644 --- a/src/Pages/internify/mentor/components/EligibleInternTable.tsx +++ b/src/Pages/internify/mentor/components/EligibleInternTable.tsx @@ -29,6 +29,20 @@ interface EligibleInternTableProps { const PER_PAGE = 5; +const BULAN_ID = [ + "Januari", "Februari", "Maret", "April", "Mei", "Juni", + "Juli", "Agustus", "September", "Oktober", "November", "Desember", +]; + +function formatDateIndo(dateStr: string): string { + const [year, month, day] = dateStr.split("-").map(Number); + return `${day} ${BULAN_ID[month - 1]} ${year}`; +} + +function formatDateRange(startDate: string, endDate: string): string { + return `${formatDateIndo(startDate)} - ${formatDateIndo(endDate)}`; +} + function StatusBadge({ status }: { status: boolean }) { return (
@@ -49,7 +63,6 @@ export default function EligibleInternTable({ // Durasi tanggal Proyek }: EligibleInternTableProps) { const navigate = useNavigate(); - // const { project } = useProjectDetail(String(projectId)); const { generate: SubmitCertificates } = useGenerateCertificates(); const initialSelected = new Set( @@ -99,6 +112,8 @@ export default function EligibleInternTable({ const zip = new JSZip(); const templateUrl = resolveImageUrl(project.certificate_template!); + const duration = formatDateRange(project.start_date, project.end_date); + await Promise.all( result.map(async (cert) => { const verifyUrl = `${window.location.origin}/verify-certificate/${cert.uuid}`; @@ -108,7 +123,7 @@ export default function EligibleInternTable({ cert.project.project_name, cert.certificate_no, verifyUrl, - "20 Juni 2024 - 20 September 2024" + duration ); zip.file(`Sertifikat - ${cert.user.full_name}.png`, blob); }) diff --git a/src/Pages/utils/SertificateGenerator.ts b/src/Pages/utils/SertificateGenerator.ts index ec6d782..d1a82f5 100644 --- a/src/Pages/utils/SertificateGenerator.ts +++ b/src/Pages/utils/SertificateGenerator.ts @@ -80,9 +80,9 @@ export async function generateCertificate( ctx.fillText(certificateNo, canvas.width * 0.825, canvas.height * 0.918); //Durasi Project — besar (Grenze) - ctx.font = `50px "Grenze", serif`; + ctx.font = `400 48px "Grenze", serif`; ctx.fillStyle = "#090909"; - ctx.fillText(ProjectDuration, canvas.width * 0.57, canvas.height * 0.715); + ctx.fillText("20 April - 20 Juni 2025", canvas.width * 0.68, canvas.height * 0.66); // QR code — kalau verifyUrl diisi const qrDataUrl = await QRCode.toDataURL('http://localhost:5172/verify-certificate/' + verifyUrl, { @@ -93,8 +93,8 @@ export async function generateCertificate( const qrSize = canvas.width * 0.103; ctx.drawImage( qrImg, - canvas.width * 0.77 - qrSize, - canvas.height * 0.715 - qrSize, + canvas.width * 0.8 - qrSize, + canvas.height * 0.75 - qrSize, qrSize, qrSize, ); diff --git a/src/Pages/utils/testlayout.html b/src/Pages/utils/testlayout.html index b411376..d9c2798 100644 --- a/src/Pages/utils/testlayout.html +++ b/src/Pages/utils/testlayout.html @@ -51,6 +51,10 @@ ctx.fillStyle = "#090909"; ctx.fillText("Internify Platform Dev", canvas.width * 0.57, canvas.height * 0.615); + ctx.font = `400 48px "Grenze", serif`; + ctx.fillStyle = "#090909"; + ctx.fillText("20 April - 20 Juni 2025", canvas.width * 0.68, canvas.height * 0.66); + // 4. Certificate no ctx.font = `50px Grenze`; ctx.fillText("CERT/20260609/PRJ2/USR5", canvas.width * 0.665, canvas.height * 0.305); diff --git a/src/services/NotificationService.ts b/src/services/NotificationService.ts index 6914699..a668dfd 100644 --- a/src/services/NotificationService.ts +++ b/src/services/NotificationService.ts @@ -90,6 +90,7 @@ export const mapNotificationToUI = (notif: BackendNotification): UINotification title: notif.title, time: formatRelativeTime(notif.created_at), isNew: !notif.is_read, + link: notif.link ?? "", group: mapGroup(notif.created_at), description: notif.message, }; diff --git a/src/types/notification.types.ts b/src/types/notification.types.ts index 6331ea7..d17aef9 100644 --- a/src/types/notification.types.ts +++ b/src/types/notification.types.ts @@ -6,6 +6,7 @@ export interface BackendNotification { message: string; is_read: boolean; created_at: string; + link: string | null; } export interface UINotification {