Generate Certificates: Adding QR with UUID of certificates to verify

This commit is contained in:
Kemas M. Aryadary Rasyad
2026-07-04 02:06:02 +07:00
parent 5b7ac30589
commit c4ca23e869
11 changed files with 280 additions and 131 deletions
@@ -7,7 +7,6 @@ import SertificateHistory from "./components/CertificateHistory";
// import { useClaimCertificate } from "../../../hooks/useCertificates";
import { useProjectDetail } from "../../../hooks/useProjects";
import { useMyCertificates } from "../../../hooks/useCertificates";
import type { Project } from "../../../types/project.types";
@@ -1,9 +1,10 @@
import { useState } from "react";
import { FileText, FileUp, Link } from "lucide-react";
import { resolveImageUrl } from "../../../utils/SertificateGenerator";
interface SubmitStatusTableProps {
type: "file" | "link";
filePath?: string | null; // ganti dari files: File[]
filePath?: string | null;
link?: string;
submittedAt?: Date;
deadline?: Date;
@@ -66,7 +67,7 @@ export default function SubmitStatusTable({
<div className="flex items-center gap-3">
<button
onClick={() => setConfirmDelete(false)}
className="text-xs font-medium px-4 py-1.5 rounded-lg bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors"
className="text-xs font-medium px-4 py-1.5 rounded-lg bg-card border border-card-outline text-gray-700 hover:bg-gray-50 transition-colors"
>
Cancel
</button>
@@ -106,7 +107,7 @@ export default function SubmitStatusTable({
key={row.label}
className="grid grid-cols-[180px_1fr] border-b border-box-border"
>
<div className="px-4 py-3 text-sm text-gray-500 border-r border-box-border">
<div className="px-4 py-3 text-sm text-font border-r border-box-border">
{row.label}
</div>
<div className="px-4 py-3 text-sm text-gray-800">{row.value}</div>
@@ -115,15 +116,20 @@ export default function SubmitStatusTable({
{/* File / Link row */}
<div className="grid grid-cols-[180px_1fr]">
<div className="px-4 py-3 text-sm text-gray-500 border-r border-box-border">
<div className="px-4 py-3 text-sm text-font border-r border-box-border">
{type === "file" ? "File submission" : "Link submission"}
</div>
<div className="px-4 py-3 flex flex-col gap-2">
{type === "file" ? (
<div className="flex items-center gap-2 text-sm text-gray-800">
<FileText className="w-4 h-4 text-red-700 stroke-[2.5]" />
{filePath ? filePath.split("/").pop() : "File submitted"}
</div>
<a
href={resolveImageUrl(filePath ?? "")}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-sm text-blue-600 hover:underline"
>
<FileText className="w-4 h-4 stroke-[2.5]" />
<span>{resolveImageUrl(filePath ?? "").split("/").pop()}</span>
</a>
) : (
<a href={link} target="_blank" rel="noopener noreferrer"
className="flex items-center gap-1.5 text-sm text-blue-600 hover:underline break-all">
@@ -65,7 +65,12 @@ export default function TaskFormFile({ taskId, projectId, deadline, initialSubmi
if (ok) { setFiles([]); setIsEditing(false); }
};
// Sudah ada submission dan tidak sedang edit -> tampilkan status
// Batal edit — kembali ke tampilan status tanpa kehilangan submission lama
const handleCancelEdit = () => {
setFiles([]);
setIsEditing(false);
};
if (submission && !isEditing) {
return (
<SubmitStatusTable
@@ -79,6 +84,8 @@ export default function TaskFormFile({ taskId, projectId, deadline, initialSubmi
);
}
const currentFileName = submission?.file_path?.split("/").pop();
return (
<form onSubmit={handleSubmit} className="space-y-4">
<input
@@ -97,6 +104,7 @@ export default function TaskFormFile({ taskId, projectId, deadline, initialSubmi
}`}
>
{files.length > 0 ? (
// File baru sudah dipilih — tampilkan itu (menggantikan file lama)
<div className="space-y-2">
{files.map((file, index) => (
<div key={index} className="flex items-center justify-between text-sm font-medium text-gray-800" onClick={(e) => e.stopPropagation()}>
@@ -110,6 +118,17 @@ export default function TaskFormFile({ taskId, projectId, deadline, initialSubmi
</div>
))}
</div>
) : isEditing && currentFileName ? (
// Mode edit, belum pilih file baru — tampilkan file submission lama sebagai referensi
<div className="flex flex-col items-center justify-center h-full space-y-2 text-center py-6">
<FileText className="w-6 h-6 text-gray-400" />
<p className="text-sm font-semibold text-gray-700">
File saat ini: <span className="text-red-700">{currentFileName}</span>
</p>
<p className="text-[11px] text-gray-400 font-medium pointer-events-none">
Klik atau drag & drop untuk mengganti file
</p>
</div>
) : (
<div className="flex flex-col items-center justify-center h-full space-y-2 pointer-events-none text-center py-6">
<UploadCloud className="w-8 h-8 text-gray-400 mx-auto stroke-[1.5]" />
@@ -118,13 +137,25 @@ export default function TaskFormFile({ taskId, projectId, deadline, initialSubmi
</div>
)}
</div>
<button
type="submit" disabled={loading}
className="w-full bg-[#B30000] hover:bg-[#990000] disabled:opacity-60 text-white font-bold py-3 rounded-xl transition-colors shadow-md flex items-center justify-center gap-2 tracking-wide cursor-pointer"
>
{loading ? "Mengirim..." : isEditing ? "Update Submission" : "Save Changes"}
<LuSendHorizontal className="w-4 h-4 stroke-[2.5]" />
</button>
<div className="flex gap-2">
{isEditing && (
<button
type="button"
onClick={handleCancelEdit}
className="w-full bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 font-bold py-3 rounded-xl transition-colors"
>
Batal
</button>
)}
<button
type="submit" disabled={loading}
className="w-full bg-[#B30000] hover:bg-[#990000] disabled:opacity-60 text-white font-bold py-3 rounded-xl transition-colors shadow-md flex items-center justify-center gap-2 tracking-wide cursor-pointer"
>
{loading ? "Mengirim..." : isEditing ? "Update Submission" : "Save Changes"}
<LuSendHorizontal className="w-4 h-4 stroke-[2.5]" />
</button>
</div>
</form>
);
}