5689898f33
- Updated CertificateForm to improve loading state UI with skeletons. - Enhanced CertificateResult with better loading indicators and structure. - Refactored ProjectForumTab to include a skeleton loader and improved error handling. - Introduced ProjectTaskTab for managing project tasks with modals for creating and editing tasks. - Improved ProjectsPage with a skeleton loader during project data fetching. - Refined AddInternsToProjects, ArchiveProjectDialog, CompleteProjectDialog, CreateProjectDialog, CreateTaskDialog, and EditTaskDialog components to handle modal states more effectively. - Updated EligibleInternTable to enhance loading states and formatting of dates. - Modified StatusSubmissionTable to simplify status display. - Improved certificate generation utility functions for better date formatting and layout management. - Removed deprecated certificate assets and added a new template for certificates. - Updated types for certificates and projects to include start and end dates.
99 lines
3.9 KiB
TypeScript
99 lines
3.9 KiB
TypeScript
import { useState } from "react";
|
|
import { CheckCircle2, FolderArchive, Loader2 } from "lucide-react";
|
|
import { useProjectCertificates } from "../../../hooks/useCertificates";
|
|
import { useProjectDetail } from "../../../hooks/useProjects";
|
|
import { useParams } from "react-router-dom";
|
|
import { downloadAllCertificatesZip } from "../../utils/Certificates";
|
|
import CertResultTable from "./components/CertResultTable";
|
|
|
|
export default function CertificateResult() {
|
|
const { slug } = useParams<{ slug: string }>();
|
|
const [zipping, setZipping] = useState(false);
|
|
const [zipProgress, setZipProgress] = useState<{ done: number; total: number } | null>(null);
|
|
|
|
const { project, loading: projectLoading, error: projectError } = useProjectDetail(slug ?? "");
|
|
const { certificates, loading: certLoading, error: certError } = useProjectCertificates(project?.id ?? 0);
|
|
|
|
const loading = projectLoading || certLoading;
|
|
const error = projectError || certError;
|
|
|
|
const handleDownloadZip = async () => {
|
|
if (!project || certificates.length === 0) return;
|
|
setZipping(true);
|
|
setZipProgress({ done: 0, total: certificates.length });
|
|
try {
|
|
await downloadAllCertificatesZip(
|
|
certificates,
|
|
project.certificate_template ?? "",
|
|
project.project_name,
|
|
(done, total) => setZipProgress({ done, total })
|
|
);
|
|
} catch (err) {
|
|
console.error("Gagal download ZIP:", err);
|
|
} finally {
|
|
setZipping(false);
|
|
setZipProgress(null);
|
|
}
|
|
};
|
|
|
|
if (loading) return (
|
|
<div className="flex flex-col gap-3 pb-14">
|
|
<div className="mb-4 flex flex-col gap-1">
|
|
<h1 className="page-title">Certificates</h1>
|
|
<p className="page-title-desc">View and download internship certificates</p>
|
|
</div>
|
|
<div className="h-[360px] bg-box-secondary rounded-xl animate-pulse" />
|
|
<div className="h-[150px] bg-box-secondary rounded-xl animate-pulse" />
|
|
</div>
|
|
);
|
|
if (error) return <div className="text-sm text-red-500 p-4">Gagal memuat data sertifikat.</div>;
|
|
|
|
return (
|
|
<div>
|
|
{/* Page Header */}
|
|
<div className="mb-8">
|
|
<h1 className="page-title">Certificates</h1>
|
|
<p className="page-title-desc">
|
|
Administrate, issue, and track professional certifications for all interns.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Success Hero */}
|
|
<div className="flex flex-col items-center text-center mb-8 px-4">
|
|
<div className="w-16 h-16 rounded-full bg-red flex items-center justify-center shadow-lg mb-5">
|
|
<CheckCircle2 className="w-8 h-8 text-white" strokeWidth={2.5} />
|
|
</div>
|
|
<h2 className="text-3xl md:text-4xl font-extrabold text-red mb-3 leading-tight">
|
|
{certificates.length} Certificates has been Generated!
|
|
</h2>
|
|
<p className="text-sm text-gray-500 max-w-md leading-relaxed mb-6">
|
|
The validation engine has processed all engineering credentials. You can now
|
|
publish them to the public registry or download the batch for offline distribution.
|
|
</p>
|
|
<button
|
|
onClick={handleDownloadZip}
|
|
disabled={zipping || certificates.length === 0}
|
|
className="flex items-center gap-2 px-6 py-2.5 border border-gray-200 rounded-xl text-sm font-bold text-red bg-white hover:bg-red-50 hover:border-red-200 shadow-sm transition-colors disabled:opacity-50"
|
|
>
|
|
{zipping ? (
|
|
<>
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
{zipProgress ? `Mengemas ${zipProgress.done}/${zipProgress.total}...` : "Menyiapkan..."}
|
|
</>
|
|
) : (
|
|
<>
|
|
<FolderArchive className="w-4 h-4" />
|
|
Download All (ZIP)
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Table */}
|
|
<CertResultTable
|
|
certificates={certificates}
|
|
templateUrl={project?.certificate_template ?? ""}
|
|
/>
|
|
</div>
|
|
);
|
|
} |