diff --git a/src/Pages/internify/intern/components/ProjectsCard.tsx b/src/Pages/internify/intern/components/ProjectsCard.tsx
index aefac38..b7b15ea 100644
--- a/src/Pages/internify/intern/components/ProjectsCard.tsx
+++ b/src/Pages/internify/intern/components/ProjectsCard.tsx
@@ -12,7 +12,7 @@ const ProjectCard = (project: Project) => {
return (
navigate(`/intern/projects/${project.slug}`)}
+ onClick={() => navigate(`/internship/projects/${project.slug}`)}
className="w-[220px] min-h-[320px] flex flex-col bg-white rounded-xl overflow-hidden shadow-sm border border-gray-100 cursor-pointer hover:shadow-md transition-shadow duration-200"
>
{/* Thumbnail */}
diff --git a/src/Pages/internify/intern/components/SubmissionStatusTable.tsx b/src/Pages/internify/intern/components/SubmissionStatusTable.tsx
index 57d42e6..55522b8 100644
--- a/src/Pages/internify/intern/components/SubmissionStatusTable.tsx
+++ b/src/Pages/internify/intern/components/SubmissionStatusTable.tsx
@@ -15,13 +15,31 @@ interface SubmitStatusTableProps {
function getTimeEarly(submittedAt: Date, deadline: Date): string {
const diffMs = deadline.getTime() - submittedAt.getTime();
- if (diffMs <= 0) return `Submitted after deadline (${Math.abs(Math.floor(diffMs / (1000 * 60 * 60)))} hours late)`;
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
+
+ if (diffMs <= 0) {
+ const absHours = Math.abs(diffHours);
+ const days = Math.floor(absHours / 24);
+ const hours = absHours % 24;
+ const overdueParts = [] as string[];
+
+ if (days > 0) overdueParts.push(`${days} day${days > 1 ? "s" : ""}`);
+ if (hours > 0) overdueParts.push(`${hours} hour${hours > 1 ? "s" : ""}`);
+
+ if (overdueParts.length === 0) {
+ return "Submitted after deadline (0 hours late)";
+ }
+
+ return `Submitted after deadline (${overdueParts.join(" ")} late)`;
+ }
+
+ const parts = [] as string[];
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`;
}
diff --git a/src/Pages/internify/intern/components/TaskForm-file.tsx b/src/Pages/internify/intern/components/TaskForm-file.tsx
index a2d84aa..31ff80f 100644
--- a/src/Pages/internify/intern/components/TaskForm-file.tsx
+++ b/src/Pages/internify/intern/components/TaskForm-file.tsx
@@ -27,7 +27,14 @@ export default function TaskFormFile({ taskId, projectId, deadline, initialSubmi
selectedFiles.forEach((file) => {
const ext = file.name.split(".").pop()?.toLowerCase();
if (ext && allowedExtensions.includes(ext)) {
- if (file.size <= 5 * 1024 * 1024) validFiles.push(file);
+ if (file.size <= 5 * 1024 * 1024) {
+ validFiles.push(file);
+ } else {
+ customToast.error(
+ "File terlalu besar!",
+ `File ${file.name} melebihi batas ukuran 5MB.`
+ );
+ }
}
});
if (validFiles.length > 0) setFiles((prev) => [...prev, ...validFiles]);
diff --git a/src/Pages/internify/mentor/BaseProjectsPage.tsx b/src/Pages/internify/mentor/BaseProjectsPage.tsx
index b671553..cc39fe6 100644
--- a/src/Pages/internify/mentor/BaseProjectsPage.tsx
+++ b/src/Pages/internify/mentor/BaseProjectsPage.tsx
@@ -27,8 +27,8 @@ export default function MentorProjectsPage() {
if (loading) {
return (
- {[...Array(4)].map((_) => (
-
+ {[...Array(4)].map((_, index) => (
+
))}
diff --git a/src/Pages/internify/mentor/components/CertResultTable.tsx b/src/Pages/internify/mentor/components/CertResultTable.tsx
index 89fd47e..cf916b9 100644
--- a/src/Pages/internify/mentor/components/CertResultTable.tsx
+++ b/src/Pages/internify/mentor/components/CertResultTable.tsx
@@ -132,13 +132,6 @@ export default function CertResultTable({ certificates, templateUrl }: Certifica
:
}
-{/*
-
*/}
))
diff --git a/src/Pages/internify/mentor/components/EligibleInternTable.tsx b/src/Pages/internify/mentor/components/EligibleInternTable.tsx
index 686b414..85c373e 100644
--- a/src/Pages/internify/mentor/components/EligibleInternTable.tsx
+++ b/src/Pages/internify/mentor/components/EligibleInternTable.tsx
@@ -118,12 +118,14 @@ export default function EligibleInternTable({
id: intern.id_user,
id_project: project.id,
id_user: intern.id_user,
- certificate_no: "No.2025/SPI3/CERT/yyddmm/X",
+ certificate_no: "No.2025/SPI3/CERT/MMDDYY/XXX",
issued_at: new Date().toISOString().slice(0, 10),
uuid: "HANYA SEBATAS PREVIEW",
+ intern_position: "[Posisi Intern]",
user: {
id: intern.id_user,
full_name: intern.full_name,
+ intern_position: "[Posisi Intern]",
email: intern.email,
},
project: {
@@ -185,12 +187,12 @@ export default function EligibleInternTable({
const blob = await generateCertificate(
templateUrl,
cert.user.full_name,
- cert.project.project_name,
+ cert.intern_position,
cert.certificate_no,
duration,
cert.uuid,
);
- zip.file(`Sertifikat - ${cert.user.full_name}.png`, blob);
+ zip.file(`${cert.certificate_no}_${cert.user.full_name}_${cert.project.project_name}.png`, blob);
})
);
})(),
diff --git a/src/Pages/utils/Certificates.ts b/src/Pages/utils/Certificates.ts
index 29d1e08..4118550 100644
--- a/src/Pages/utils/Certificates.ts
+++ b/src/Pages/utils/Certificates.ts
@@ -25,7 +25,7 @@ export const previewCertificate = async (
const blob = await generateCertificate(
resolveFileUrl(templateUrl),
cert.user.full_name,
- cert.project.project_name,
+ cert.intern_position,
cert.certificate_no,
formatDateRange(cert.project.start_date, cert.project.end_date),
cert.uuid,
@@ -85,11 +85,11 @@ export const downloadCertificatePdf = async (
cert: Certificate,
templateUrl: string,
): Promise
=> {
- const fileName = `Sertifikat - ${cert.user.full_name} - ${cert.certificate_no}`;
+ const fileName = `${cert.certificate_no}_${cert.user.full_name}_${cert.project.project_name}`;
const blob = await generateCertificate(
resolveFileUrl(templateUrl),
cert.user.full_name,
- cert.project.project_name,
+ cert.intern_position,
cert.certificate_no,
formatDateRange(cert.project.start_date, cert.project.end_date),
cert.uuid,
@@ -125,11 +125,11 @@ export const downloadCertificateImage = async (
templateUrl: string,
format: "png" | "jpg" = "png",
): Promise => {
- const fileName = `Sertifikat - ${cert.user.full_name} - ${cert.certificate_no}`;
+ const fileName = `${cert.certificate_no}_${cert.user.full_name}_${cert.project.project_name}`;
const blob = await generateCertificate(
resolveFileUrl(templateUrl),
cert.user.full_name,
- cert.project.project_name,
+ cert.intern_position,
cert.certificate_no,
formatDateRange(cert.project.start_date, cert.project.end_date),
cert.uuid,
@@ -192,12 +192,12 @@ export const downloadAllCertificatesZip = async (
const blob = await generateCertificate(
resolveFileUrl(templateUrl),
cert.user.full_name,
- cert.project.project_name,
+ cert.intern_position,
cert.certificate_no,
formatDateRange(cert.project.start_date, cert.project.end_date),
cert.uuid,
);
- zip.file(`Sertifikat - ${cert.user.full_name}.png`, blob);
+ zip.file(`${cert.certificate_no}_${projectName}_${cert.user.full_name}.png`, blob);
onProgress?.(i + 1, total);
}),
);
@@ -205,7 +205,7 @@ export const downloadAllCertificatesZip = async (
const zipBlob = await zip.generateAsync({ type: "blob" });
const a = document.createElement("a");
a.href = URL.createObjectURL(zipBlob);
- a.download = `Sertifikat - ${projectName}.zip`;
+ a.download = `Sertifikat_${projectName}_${new Date().toISOString().slice(0, 10)}.zip`;
a.click();
URL.revokeObjectURL(a.href);
};
diff --git a/src/Pages/utils/SertificateGenerator.ts b/src/Pages/utils/SertificateGenerator.ts
index bf307fb..b6dbc20 100644
--- a/src/Pages/utils/SertificateGenerator.ts
+++ b/src/Pages/utils/SertificateGenerator.ts
@@ -23,13 +23,13 @@ async function ensureFontsLoaded(): Promise {
// LAYOUT DEFAULT SERTIFIKAT YANG AKAN DIGENERATE
const LAYOUT = {
- internName: { x: 0.54, y: 0.5, size: 130, font: "Great Vibes", color: "#800000" },
+ internName: { x: 0.54, y: 0.47, size: 130, font: "Great Vibes", color: "#800000" },
internNameSmall: { x: 0.82, y: 0.889, size: 47, font: "Great Vibes", color: "#090909" },
- projectName: { x: 0.815, y: 0.598, size: 46, font: "Grenze", color: "#800000" },
- duration: { x: 0.7, y: 0.645, size: 48, font: "Grenze", color: "#090909" },
+ userposition: { x: 0.855, y: 0.557, size: 46, font: "Grenze", color: "#800000" },
+ duration: { x: 0.7, y: 0.615, size: 48, font: "Grenze", color: "#090909" },
certNo: { x: 0.56, y: 0.305, size: 50, font: "Grenze", color: "#090909" },
certNoSmall: { x: 0.827, y: 0.92, size: 32, font: "Grenze", color: "#090909"},
- qr: { x: 0.77, y: 0.715, size: 0.103 },
+ qr: { x: 0.772, y: 0.715, size: 0.103 },
};
// HELPER: kurangi ukuran font 25% jika nama melebihi 22 karakter (termasuk spasi)
@@ -48,7 +48,7 @@ function fitNameText(name: string, baseSize: number): number {
export async function generateCertificate(
templateUrl: string,
internName: string,
- projectName: string,
+ userposition: string,
certificateNo: string,
ProjectDuration: string,
verifyUrl: string,
@@ -76,7 +76,7 @@ export async function generateCertificate(
text(LAYOUT.internName, internName);
text(LAYOUT.internNameSmall, internName);
- text(LAYOUT.projectName, projectName);
+ text(LAYOUT.userposition, userposition);
text(LAYOUT.duration, ProjectDuration);
text(LAYOUT.certNo, certificateNo);
text(LAYOUT.certNoSmall, certificateNo);
diff --git a/src/types/certificate.types.ts b/src/types/certificate.types.ts
index ff0bc8e..91ea3d8 100644
--- a/src/types/certificate.types.ts
+++ b/src/types/certificate.types.ts
@@ -2,6 +2,7 @@ export interface CertificateUser {
id: number;
full_name: string;
email: string;
+ intern_position: string;
}
export interface CertificateProject {
@@ -19,6 +20,7 @@ export interface Certificate {
id_user: number;
certificate_no: string;
issued_at: string;
+ intern_position: string;
uuid: string;
user: CertificateUser;
project: CertificateProject;
@@ -28,6 +30,7 @@ export interface Certificate {
export interface CertificateVerification {
uuid: string;
intern_name: string;
+ intern_position: string;
project_name: string;
created_at: string;
}
@@ -49,5 +52,6 @@ export interface CertificateVerifyResult {
uuid: string;
intern_name: string;
project: projectbysertif;
+ intern_position: string;
created_at: string;
}
\ No newline at end of file