2026-01-28 05:48:46 +00:00
|
|
|
// "use client";
|
|
|
|
|
|
|
|
|
|
// import { useState, useEffect } from "react";
|
|
|
|
|
// import { useUploadContext } from "../_context/upload-context";
|
|
|
|
|
// // Sesuaikan import ini dengan lokasi service API Anda yang sebenarnya
|
|
|
|
|
// import uploadApi from "@/shared/services/map-upload";
|
|
|
|
|
// import { toast } from "sonner";
|
|
|
|
|
|
|
|
|
|
// // --- 1. Helper Load PDF.js via CDN (Sama seperti di use-upload.ts) ---
|
|
|
|
|
// const loadPdfJs = async () => {
|
|
|
|
|
// return new Promise<any>((resolve, reject) => {
|
|
|
|
|
// // Cek jika global variable sudah ada
|
|
|
|
|
// if ((window as any).pdfjsLib) {
|
|
|
|
|
// resolve((window as any).pdfjsLib);
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// const script = document.createElement("script");
|
|
|
|
|
// // Gunakan versi yang sama agar konsisten
|
|
|
|
|
// script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
|
|
|
|
|
// script.async = true;
|
|
|
|
|
|
|
|
|
|
// script.onload = () => {
|
|
|
|
|
// const pdfjsLib = (window as any).pdfjsLib;
|
|
|
|
|
// // Set worker
|
|
|
|
|
// pdfjsLib.GlobalWorkerOptions.workerSrc =
|
|
|
|
|
// "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
|
|
|
|
|
// resolve(pdfjsLib);
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
// script.onerror = (err) => reject(err);
|
|
|
|
|
// document.body.appendChild(script);
|
|
|
|
|
// });
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
// export function usePdfViewer() {
|
|
|
|
|
// const { state, setState, goToStep } = useUploadContext();
|
|
|
|
|
// const [pages, setPages] = useState<{ pageNum: number; imageUrl: string }[]>([]);
|
|
|
|
|
// const [loading, setLoading] = useState(false);
|
|
|
|
|
// const [localSelectedPages, setLocalSelectedPages] = useState<number[]>([]);
|
|
|
|
|
|
|
|
|
|
// // Load PDF saat component mount atau file berubah
|
|
|
|
|
// useEffect(() => {
|
|
|
|
|
// if (state.file && state.step === "PDF_VIEWER") {
|
|
|
|
|
// renderPdfPages(state.file);
|
|
|
|
|
// }
|
|
|
|
|
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
// }, [state.file, state.step]);
|
|
|
|
|
|
|
|
|
|
// const renderPdfPages = async (pdfFile: File) => {
|
|
|
|
|
// setLoading(true);
|
|
|
|
|
// setPages([]); // Reset halaman lama
|
|
|
|
|
|
|
|
|
|
// try {
|
|
|
|
|
// // 1. Load Library dari CDN
|
|
|
|
|
// const pdfjsLib = await loadPdfJs();
|
|
|
|
|
|
|
|
|
|
// // 2. Baca File
|
|
|
|
|
// const arrayBuffer = await pdfFile.arrayBuffer();
|
|
|
|
|
// const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
|
|
|
|
// const totalPages = pdf.numPages;
|
|
|
|
|
// const pageImages = [];
|
|
|
|
|
|
|
|
|
|
// // 3. Render Setiap Halaman ke Canvas -> Image URL
|
|
|
|
|
// for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
|
|
|
|
|
// const page = await pdf.getPage(pageNum);
|
|
|
|
|
// const viewport = page.getViewport({ scale: 1 }); // Scale 1 cukup untuk thumbnail
|
|
|
|
|
|
|
|
|
|
// const canvas = document.createElement("canvas");
|
|
|
|
|
// const ctx = canvas.getContext("2d");
|
|
|
|
|
|
|
|
|
|
// if (ctx) {
|
|
|
|
|
// canvas.height = viewport.height;
|
|
|
|
|
// canvas.width = viewport.width;
|
|
|
|
|
|
|
|
|
|
// await page.render({
|
|
|
|
|
// canvasContext: ctx,
|
|
|
|
|
// viewport: viewport,
|
|
|
|
|
// }).promise;
|
|
|
|
|
|
|
|
|
|
// pageImages.push({
|
|
|
|
|
// pageNum,
|
|
|
|
|
// imageUrl: canvas.toDataURL("image/jpeg", 0.8), // Gunakan JPEG kompresi agar ringan
|
|
|
|
|
// });
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// setPages(pageImages);
|
|
|
|
|
// } catch (err) {
|
|
|
|
|
// console.error("PDF Error:", err);
|
|
|
|
|
// toast.error("Gagal memuat halaman PDF. Pastikan file tidak korup.");
|
|
|
|
|
// } finally {
|
|
|
|
|
// setLoading(false);
|
|
|
|
|
// }
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
// const toggleSelectPage = (pageNum: number) => {
|
|
|
|
|
// setLocalSelectedPages((prev) => {
|
|
|
|
|
// if (prev.includes(pageNum)) {
|
|
|
|
|
// return prev.filter((p) => p !== pageNum);
|
|
|
|
|
// } else {
|
|
|
|
|
// if (prev.length >= 20) {
|
|
|
|
|
// toast.warning("Maksimal pilih 20 halaman.");
|
|
|
|
|
// return prev;
|
|
|
|
|
// }
|
|
|
|
|
// return [...prev, pageNum];
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
// const handleProcessPdf = async () => {
|
|
|
|
|
// if (localSelectedPages.length === 0) {
|
|
|
|
|
// toast.warning("Pilih minimal 1 halaman.");
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// setLoading(true);
|
|
|
|
|
// try {
|
|
|
|
|
// // Panggil API dengan halaman yang DIPILIH SAJA
|
|
|
|
|
// const res = await uploadApi.uploadFile(
|
|
|
|
|
// state.file!,
|
|
|
|
|
// localSelectedPages,
|
|
|
|
|
// null,
|
|
|
|
|
// state.fileDesc
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
// setState(prev => ({ ...prev, result: res }));
|
|
|
|
|
|
|
|
|
|
// // Routing Logic setelah upload PDF selesai
|
|
|
|
|
// if (!res.tables) {
|
|
|
|
|
// goToStep("VALIDATE");
|
|
|
|
|
// } else if (res.tables.length > 1) {
|
|
|
|
|
// goToStep("TABLE_PICKER");
|
|
|
|
|
// toast.success("Beberapa tabel terdeteksi. Silakan pilih tabel.");
|
|
|
|
|
// } else {
|
|
|
|
|
// goToStep("TABLE_PICKER"); // Atau langsung validate tergantung kebutuhan
|
|
|
|
|
// }
|
|
|
|
|
// } catch (err: any) {
|
|
|
|
|
// toast.error(err.message || "Gagal memproses halaman PDF");
|
|
|
|
|
// } finally {
|
|
|
|
|
// setLoading(false);
|
|
|
|
|
// }
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
// // 🔥 PENTING: Wajib me-return object ini agar tidak "undefined" di component
|
|
|
|
|
// return {
|
|
|
|
|
// pages,
|
|
|
|
|
// loading,
|
|
|
|
|
// localSelectedPages,
|
|
|
|
|
// toggleSelectPage,
|
|
|
|
|
// handleProcessPdf,
|
|
|
|
|
// };
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
|
import { useUploadContext } from "../_context/upload-context";
|
|
|
|
|
// Pastikan path ini sesuai dengan lokasi service API upload Anda
|
|
|
|
|
import uploadApi from "@/shared/services/map-upload";
|
|
|
|
|
import { toast } from "sonner";
|
|
|
|
|
|
|
|
|
|
// --- HELPER: Load PDF.js via CDN (Bypassing Webpack agar tidak Error) ---
|
|
|
|
|
const loadPdfJs = async () => {
|
|
|
|
|
return new Promise<any>((resolve, reject) => {
|
|
|
|
|
// 1. Cek jika library sudah ada di window
|
|
|
|
|
if ((window as any).pdfjsLib) {
|
|
|
|
|
resolve((window as any).pdfjsLib);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Inject Script jika belum ada
|
|
|
|
|
const script = document.createElement("script");
|
|
|
|
|
script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
|
|
|
|
|
script.async = true;
|
|
|
|
|
|
|
|
|
|
script.onload = () => {
|
|
|
|
|
const pdfjsLib = (window as any).pdfjsLib;
|
|
|
|
|
// Set worker source ke versi yang sama
|
|
|
|
|
pdfjsLib.GlobalWorkerOptions.workerSrc =
|
|
|
|
|
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
|
|
|
|
|
resolve(pdfjsLib);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
script.onerror = (err) => reject(err);
|
|
|
|
|
document.body.appendChild(script);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function usePdfViewer() {
|
|
|
|
|
const { state, setState, goToStep } = useUploadContext();
|
|
|
|
|
const [pages, setPages] = useState<{ pageNum: number; imageUrl: string }[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [localSelectedPages, setLocalSelectedPages] = useState<number[]>([]);
|
|
|
|
|
|
2026-01-29 04:33:40 +00:00
|
|
|
const MAX_SELECT = 20;
|
|
|
|
|
|
2026-01-28 05:48:46 +00:00
|
|
|
// Efek untuk memuat halaman PDF saat komponen dipasang
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (state.file && state.step === "PDF_VIEWER") {
|
|
|
|
|
renderPdfPages(state.file);
|
|
|
|
|
}
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [state.file, state.step]);
|
|
|
|
|
|
|
|
|
|
// --- FUNGSI RENDER PDF KE GAMBAR ---
|
|
|
|
|
const renderPdfPages = async (pdfFile: File) => {
|
|
|
|
|
setLoading(true);
|
2026-01-29 04:33:40 +00:00
|
|
|
setPages([]);
|
2026-01-28 05:48:46 +00:00
|
|
|
try {
|
|
|
|
|
const pdfjsLib = await loadPdfJs();
|
|
|
|
|
const arrayBuffer = await pdfFile.arrayBuffer();
|
|
|
|
|
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
|
|
|
|
const totalPages = pdf.numPages;
|
|
|
|
|
const pageImages = [];
|
|
|
|
|
|
|
|
|
|
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
|
|
|
|
|
const page = await pdf.getPage(pageNum);
|
2026-01-29 04:33:40 +00:00
|
|
|
const viewport = page.getViewport({ scale: 1 });
|
2026-01-28 05:48:46 +00:00
|
|
|
const canvas = document.createElement("canvas");
|
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
|
if (ctx) {
|
|
|
|
|
canvas.height = viewport.height;
|
|
|
|
|
canvas.width = viewport.width;
|
2026-01-29 04:33:40 +00:00
|
|
|
await page.render({ canvasContext: ctx, viewport }).promise;
|
2026-01-28 05:48:46 +00:00
|
|
|
pageImages.push({
|
|
|
|
|
pageNum,
|
2026-01-29 04:33:40 +00:00
|
|
|
imageUrl: canvas.toDataURL("image/jpeg", 0.7),
|
2026-01-28 05:48:46 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setPages(pageImages);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("PDF Render Error:", err);
|
|
|
|
|
toast.error("Gagal memuat visualisasi PDF.");
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- FUNGSI PILIH HALAMAN ---
|
|
|
|
|
const toggleSelectPage = (pageNum: number) => {
|
|
|
|
|
setLocalSelectedPages((prev) => {
|
|
|
|
|
if (prev.includes(pageNum)) {
|
|
|
|
|
return prev.filter((p) => p !== pageNum);
|
|
|
|
|
} else {
|
2026-01-29 04:33:40 +00:00
|
|
|
if (prev.length >= MAX_SELECT) {
|
|
|
|
|
toast.warning(`Maksimal ${MAX_SELECT} halaman yang dapat dipilih.`);
|
2026-01-28 05:48:46 +00:00
|
|
|
return prev;
|
|
|
|
|
}
|
|
|
|
|
return [...prev, pageNum];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-29 04:33:40 +00:00
|
|
|
// 🔥 SELECT ALL
|
|
|
|
|
const isAllSelected = pages.length > 0 && localSelectedPages.length === Math.min(pages.length, MAX_SELECT);
|
|
|
|
|
|
|
|
|
|
const handleSelectAll = () => {
|
|
|
|
|
if (isAllSelected) {
|
|
|
|
|
// Jika sudah terpilih semua (atau max), batalkan semua
|
|
|
|
|
setLocalSelectedPages([]);
|
|
|
|
|
} else {
|
|
|
|
|
// Pilih semua (tapi batasi sesuai MAX_SELECT)
|
|
|
|
|
const allPageNums = pages.map(p => p.pageNum).slice(0, MAX_SELECT);
|
|
|
|
|
setLocalSelectedPages(allPageNums);
|
|
|
|
|
|
|
|
|
|
if (pages.length > MAX_SELECT) {
|
|
|
|
|
toast.info(`Otomatis memilih ${MAX_SELECT} halaman pertama (Batas Maksimum).`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-28 05:48:46 +00:00
|
|
|
// --- LOGIKA PROSES UPLOAD & ROUTING YANG ANDA MINTA ---
|
|
|
|
|
const handleProcessPdf = async () => {
|
|
|
|
|
if (localSelectedPages.length === 0) {
|
|
|
|
|
toast.warning("Harap pilih minimal 1 halaman untuk diproses.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-10 01:56:22 +00:00
|
|
|
|
2026-01-28 05:48:46 +00:00
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const res = await uploadApi.uploadFile(
|
|
|
|
|
state.file!,
|
2026-02-10 01:56:22 +00:00
|
|
|
localSelectedPages, // Kirim array halaman: [1, 2, 5]
|
|
|
|
|
null, // sheet null karena ini PDF
|
2026-01-28 05:48:46 +00:00
|
|
|
state.fileDesc
|
|
|
|
|
);
|
2026-02-10 01:56:22 +00:00
|
|
|
|
2026-01-28 05:48:46 +00:00
|
|
|
setState(prev => ({ ...prev, result: res }));
|
2026-02-10 01:56:22 +00:00
|
|
|
|
|
|
|
|
if (res.data.tables && res.data.tables.length > 0) {
|
2026-01-28 05:48:46 +00:00
|
|
|
goToStep("TABLE_PICKER");
|
2026-02-10 01:56:22 +00:00
|
|
|
toast.success(`Ditemukan ${res.data.tables.length} tabel. Silakan pilih tabel.`);
|
|
|
|
|
} else if (!res.data.tables) {
|
2026-01-28 05:48:46 +00:00
|
|
|
goToStep("VALIDATE");
|
|
|
|
|
toast.info("Tabel tidak terdeteksi spesifik, lanjut ke validasi.");
|
2026-02-10 01:56:22 +00:00
|
|
|
} else {
|
|
|
|
|
toast.warning(res.message);
|
2026-01-28 05:48:46 +00:00
|
|
|
}
|
2026-02-10 01:56:22 +00:00
|
|
|
|
2026-01-28 05:48:46 +00:00
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
toast.error(err.message || "Gagal memproses halaman PDF terpilih.");
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 🔥 Return wajib agar tidak error destructuring undefined
|
|
|
|
|
return {
|
|
|
|
|
pages,
|
|
|
|
|
loading,
|
|
|
|
|
localSelectedPages,
|
|
|
|
|
toggleSelectPage,
|
|
|
|
|
handleProcessPdf,
|
2026-01-29 04:33:40 +00:00
|
|
|
handleSelectAll,
|
|
|
|
|
isAllSelected,
|
|
|
|
|
MAX_SELECT
|
2026-01-28 05:48:46 +00:00
|
|
|
};
|
|
|
|
|
}
|