Toast Implementation; fix add interns dialogue: penghapusan UI delete pada dialog komponen; penambahan params pada generate sertificate; perubahan tipe state untuk submission (user bisa open file submission)
This commit is contained in:
@@ -5,6 +5,7 @@ import type { ProjectDetail } from "../../../../types/project.types";
|
||||
import type { Certificate } from "../../../../types/certificate.types";
|
||||
import { downloadCertificatePdf, downloadCertificateImage } from "../../../utils/Certificates";
|
||||
import { generateCertificate, resolveImageUrl } from "../../../utils/SertificateGenerator";
|
||||
import { customToast } from "../../../utils/showToast";
|
||||
|
||||
interface CertificateAvailableProps {
|
||||
project: ProjectDetail | null;
|
||||
@@ -19,29 +20,6 @@ export default function CertificateAvailable({ project, certificate, templateUrl
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const [loadingPreview, setLoadingPreview] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!templateUrl) return;
|
||||
const verifyUrl = `${window.location.origin}/verify-certificate/${certificate.uuid}`;
|
||||
|
||||
generateCertificate(
|
||||
resolveImageUrl(templateUrl),
|
||||
certificate.user.full_name,
|
||||
certificate.project.project_name,
|
||||
certificate.certificate_no,
|
||||
verifyUrl,
|
||||
"20 Juni 2024 - 20 September 2024") //placeholder karena saya butuh data durasi certificate
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
setPreviewUrl(url);
|
||||
})
|
||||
.finally(() => setLoadingPreview(false));
|
||||
|
||||
// Cleanup supaya tidak memory leak
|
||||
return () => {
|
||||
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
||||
};
|
||||
}, [templateUrl, certificate.user.full_name]);
|
||||
|
||||
const duration = project
|
||||
? (() => {
|
||||
const start = new Date(project.start_date);
|
||||
@@ -56,10 +34,37 @@ export default function CertificateAvailable({ project, certificate, templateUrl
|
||||
})()
|
||||
: "-";
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!templateUrl) return;
|
||||
const verifyUrl = `${window.location.origin}/verify-certificate/${certificate.uuid}`;
|
||||
|
||||
generateCertificate(
|
||||
resolveImageUrl(templateUrl),
|
||||
certificate.user.full_name,
|
||||
certificate.project.project_name,
|
||||
certificate.certificate_no,
|
||||
verifyUrl,
|
||||
duration)
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
setPreviewUrl(url);
|
||||
})
|
||||
.finally(() => setLoadingPreview(false));
|
||||
|
||||
return () => {
|
||||
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
||||
};
|
||||
}, [templateUrl, certificate.user.full_name]);
|
||||
|
||||
|
||||
const handleDownloadImage = async () => {
|
||||
setDownloadingImg(true);
|
||||
try {
|
||||
await downloadCertificateImage(certificate, templateUrl, "png");
|
||||
customToast.success("Sertifikat berhasil diunduh!");
|
||||
} catch {
|
||||
customToast.error("Gagal mengunduh sertifikat!");
|
||||
} finally {
|
||||
setDownloadingImg(false);
|
||||
}
|
||||
@@ -69,6 +74,9 @@ export default function CertificateAvailable({ project, certificate, templateUrl
|
||||
setDownloadingPdf(true);
|
||||
try {
|
||||
await downloadCertificatePdf(certificate, templateUrl);
|
||||
customToast.success("Sertifikat berhasil diunduh!");
|
||||
} catch {
|
||||
customToast.error("Gagal mengunduh sertifikat!");
|
||||
} finally {
|
||||
setDownloadingPdf(false);
|
||||
}
|
||||
@@ -147,9 +155,13 @@ export default function CertificateAvailable({ project, certificate, templateUrl
|
||||
<button
|
||||
// onClick={handleCopyLink}
|
||||
className="flex flex-col items-center justify-center gap-1.5 border border-card-outline hover:bg-gray-50 active:scale-95 transition-all text-red-700 font-medium py-3.5 rounded-xl text-sm"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(`${window.location.origin}/verify-certificate/${certificate.uuid}`);
|
||||
customToast.success("Link berhasil disalin!");
|
||||
}}
|
||||
>
|
||||
<Link className="w-5 h-5" />
|
||||
Salin Link
|
||||
Salin Link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { InternTaskItem } from "../../../../types/project.types";
|
||||
import type { ProjectTask } from "../../../../types/task.types";
|
||||
|
||||
const formatDate = (dateStr: string) =>
|
||||
new Date(dateStr).toLocaleDateString('id-ID', { day: 'numeric', month: 'short' });
|
||||
|
||||
export default function ProjectTimeline({ tasks }: { tasks: InternTaskItem[] }) {
|
||||
export default function ProjectTimeline({ tasks }: { tasks: ProjectTask[] }) {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return (
|
||||
|
||||
@@ -98,10 +98,8 @@ export default function SidebarIntern({ isOpen, closeSidebar }: SidebarProps) {
|
||||
onClick={() => isLogout ? handleLogout() : handleNav(path)}
|
||||
className={`flex items-center gap-2.5 w-full px-3 py-2 rounded-lg text-[13.5px] font-medium text-left transition-colors
|
||||
${isActive
|
||||
? "bg-red-50 text-red-600 font-semibold"
|
||||
: danger
|
||||
? "text-font hover:bg-base-foreground"
|
||||
: "text-font hover:bg-red-foreground"
|
||||
? "bg-red-foreground text-red font-semibold border-l-4 border-base"
|
||||
: "text-font hover:bg-red-foreground"
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-[18px] h-[18px] flex-shrink-0" />
|
||||
|
||||
Reference in New Issue
Block a user