322 lines
10 KiB
TypeScript
322 lines
10 KiB
TypeScript
|
|
// "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[]>([]);
|
||
|
|
|
||
|
|
// 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);
|
||
|
|
setPages([]); // Bersihkan halaman sebelumnya
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Load library dari CDN
|
||
|
|
const pdfjsLib = await loadPdfJs();
|
||
|
|
|
||
|
|
// Baca ArrayBuffer
|
||
|
|
const arrayBuffer = await pdfFile.arrayBuffer();
|
||
|
|
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||
|
|
const totalPages = pdf.numPages;
|
||
|
|
const pageImages = [];
|
||
|
|
|
||
|
|
// Render setiap halaman ke Canvas -> DataURL
|
||
|
|
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
|
||
|
|
const page = await pdf.getPage(pageNum);
|
||
|
|
const viewport = page.getViewport({ scale: 1 }); // Skala 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.7), // Kompresi JPEG biar ringan
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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 {
|
||
|
|
if (prev.length >= 20) {
|
||
|
|
toast.warning("Maksimal 20 halaman yang dapat dipilih.");
|
||
|
|
return prev;
|
||
|
|
}
|
||
|
|
return [...prev, pageNum];
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
// --- LOGIKA PROSES UPLOAD & ROUTING YANG ANDA MINTA ---
|
||
|
|
const handleProcessPdf = async () => {
|
||
|
|
if (localSelectedPages.length === 0) {
|
||
|
|
toast.warning("Harap pilih minimal 1 halaman untuk diproses.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setLoading(true);
|
||
|
|
try {
|
||
|
|
// 1. Kirim ke API dengan parameter 'pages' (localSelectedPages)
|
||
|
|
const res = await uploadApi.uploadFile(
|
||
|
|
state.file!,
|
||
|
|
localSelectedPages, // Kirim array halaman: [1, 2, 5]
|
||
|
|
null, // sheet null karena ini PDF
|
||
|
|
state.fileDesc
|
||
|
|
);
|
||
|
|
|
||
|
|
// 2. Simpan hasil response ke context global
|
||
|
|
setState(prev => ({ ...prev, result: res }));
|
||
|
|
|
||
|
|
// 3. Cek Logic Routing
|
||
|
|
if (res.data.tables && res.data.tables.length > 0) {
|
||
|
|
// Jika ada tabel terdeteksi -> Step 3 (Table Picker)
|
||
|
|
goToStep("TABLE_PICKER");
|
||
|
|
toast.success(`Ditemukan ${res.data.tables.length} tabel. Silakan pilih tabel.`);
|
||
|
|
} else if (!res.data.tables) {
|
||
|
|
// Jika TIDAK ada tabel (mungkin teks biasa/gambar) -> Step 4 (Validate/Preview Raw)
|
||
|
|
goToStep("VALIDATE");
|
||
|
|
toast.info("Tabel tidak terdeteksi spesifik, lanjut ke validasi.");
|
||
|
|
} else {
|
||
|
|
toast.warning(res.message);
|
||
|
|
}
|
||
|
|
|
||
|
|
} 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,
|
||
|
|
};
|
||
|
|
}
|