add pdf viewer
This commit is contained in:
parent
e7098a1354
commit
eba774013e
|
|
@ -31,14 +31,27 @@ export function useUploadController() {
|
|||
const ext = f.name.split(".").pop().toLowerCase();
|
||||
|
||||
if (ext === "pdf") {
|
||||
// try {
|
||||
// const reader = new FileReader();
|
||||
// reader.onload = async (e) => {
|
||||
// const typedArray = new Uint8Array(e.target.result);
|
||||
// const pdf = await pdfjsLib.getDocument({ data: typedArray }).promise;
|
||||
// // setPdfPageCount(pdf.numPages);
|
||||
// dispatch(setPdfPageCount(pdf.numPages));
|
||||
// console.log(`📄 PDF terdeteksi dengan ${pdf.numPages} halaman`);
|
||||
// };
|
||||
// reader.readAsArrayBuffer(f);
|
||||
// } catch (err) {
|
||||
// console.error("Gagal membaca PDF:", err);
|
||||
// }
|
||||
try {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const typedArray = new Uint8Array(e.target.result);
|
||||
const pdf = await pdfjsLib.getDocument({ data: typedArray }).promise;
|
||||
// setPdfPageCount(pdf.numPages);
|
||||
dispatch(setPdfPageCount(pdf.numPages));
|
||||
console.log(`📄 PDF terdeteksi dengan ${pdf.numPages} halaman`);
|
||||
navigate("/admin/upload/pdf"); // 👈 otomatis pindah ke viewer
|
||||
};
|
||||
reader.readAsArrayBuffer(f);
|
||||
} catch (err) {
|
||||
|
|
|
|||
92
src/pages/admin/upload/pdf_viewer/controller_pdf_viewer.jsx
Normal file
92
src/pages/admin/upload/pdf_viewer/controller_pdf_viewer.jsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useState } from "react";
|
||||
import * as pdfjsLib from "pdfjs-dist";
|
||||
import { setSelectedPages } from "../../../../store/slices/uploadSlice";
|
||||
import { uploadPdf } from "../service_admin_upload";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
"pdfjs-dist/build/pdf.worker.mjs",
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
export function usePdfViewerController() {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { file } = useSelector((state) => state.upload);
|
||||
|
||||
const [pages, setPages] = useState([]);
|
||||
const [selectedPages, setSelectedPagesLocal] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Render PDF menjadi gambar
|
||||
const loadPdfPages = async (pdfFile) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const typedArray = new Uint8Array(e.target.result);
|
||||
const pdf = await pdfjsLib.getDocument({ data: typedArray }).promise;
|
||||
const pageImages = [];
|
||||
const totalPages = pdf.numPages;
|
||||
|
||||
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
|
||||
const page = await pdf.getPage(pageNum);
|
||||
const viewport = page.getViewport({ scale: 1 });
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||
const imageUrl = canvas.toDataURL();
|
||||
pageImages.push({ pageNum, imageUrl });
|
||||
}
|
||||
|
||||
setPages(pageImages);
|
||||
setLoading(false);
|
||||
};
|
||||
reader.readAsArrayBuffer(pdfFile);
|
||||
} catch (err) {
|
||||
console.error("Gagal render PDF:", err);
|
||||
} finally {
|
||||
// setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle halaman yang dipilih
|
||||
const toggleSelectPage = (pageNum) => {
|
||||
let updated = [...selectedPages];
|
||||
if (updated.includes(pageNum)) {
|
||||
updated = updated.filter((p) => p !== pageNum);
|
||||
} else {
|
||||
if (updated.length >= 3) return;
|
||||
updated.push(pageNum);
|
||||
}
|
||||
setSelectedPagesLocal(updated);
|
||||
dispatch(setSelectedPages(updated.join(",")));
|
||||
};
|
||||
|
||||
const handleProcessPdf = async () => {
|
||||
if (selectedPages.length === 0) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await uploadPdf({ pages: selectedPages });
|
||||
console.log("PDF processed:", res);
|
||||
navigate("/admin/upload/validate");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
file,
|
||||
pages,
|
||||
loading,
|
||||
selectedPages,
|
||||
loadPdfPages,
|
||||
toggleSelectPage,
|
||||
handleProcessPdf,
|
||||
};
|
||||
}
|
||||
93
src/pages/admin/upload/pdf_viewer/views_admin_pdf_viewer.jsx
Normal file
93
src/pages/admin/upload/pdf_viewer/views_admin_pdf_viewer.jsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { usePdfViewerController } from "./controller_pdf_viewer";
|
||||
import LoadingOverlay from "../../../../components/LoadingOverlay";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export default function ViewsAdminPdfViewer() {
|
||||
const {
|
||||
file,
|
||||
pages,
|
||||
loading,
|
||||
selectedPages,
|
||||
toggleSelectPage,
|
||||
handleProcessPdf,
|
||||
loadPdfPages,
|
||||
} = usePdfViewerController();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (file) {
|
||||
loadPdfPages(file)
|
||||
}else{
|
||||
navigate("/admin/upload", { replace: true });
|
||||
};
|
||||
}, [file]);
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-106px)] bg-gray-100 overflow-hidden">
|
||||
{/* Sidebar kiri */}
|
||||
<div className="w-64 bg-white border-r border-gray-200 p-4 flex flex-col">
|
||||
<h2 className="text-lg font-semibold mb-4">Daftar Halaman</h2>
|
||||
<div className="flex-1 overflow-y-auto space-y-2">
|
||||
{pages.map((p) => (
|
||||
<label
|
||||
key={p.pageNum}
|
||||
className={`flex items-center gap-2 px-2 py-1 rounded cursor-pointer hover:bg-blue-50 transition ${
|
||||
selectedPages.includes(p.pageNum) ? "bg-blue-100" : ""
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedPages.includes(p.pageNum)}
|
||||
onChange={() => toggleSelectPage(p.pageNum)}
|
||||
/>
|
||||
<span>Halaman {p.pageNum}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 border-t pt-3 text-sm text-gray-600">
|
||||
<p>
|
||||
<span className="font-medium">Dipilih:</span>{" "}
|
||||
{selectedPages.length > 0
|
||||
? selectedPages.join(", ")
|
||||
: "Belum ada halaman"}
|
||||
</p>
|
||||
<p className="text-xs mt-1 text-gray-400">
|
||||
Maksimal 3 halaman yang dapat dipilih.
|
||||
</p>
|
||||
|
||||
<button
|
||||
onClick={handleProcessPdf}
|
||||
disabled={selectedPages.length === 0}
|
||||
className="mt-3 w-full py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 transition"
|
||||
>
|
||||
Proses Halaman
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Konten kanan (viewer) */}
|
||||
<div className="flex-1 relative overflow-y-auto">
|
||||
<LoadingOverlay show={loading} text="Merender PDF..." />
|
||||
<div className="p-6 space-y-8">
|
||||
{pages.map((p) => (
|
||||
<motion.div
|
||||
key={p.pageNum}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: p.pageNum * 0.05 }}
|
||||
className="border border-gray-300 rounded-lg bg-white shadow-sm overflow-hidden"
|
||||
>
|
||||
<img src={p.imageUrl} alt={`Halaman ${p.pageNum}`} className="w-full" />
|
||||
<p className="text-center text-sm text-gray-500 py-2 bg-gray-50">
|
||||
Halaman {p.pageNum}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -80,11 +80,11 @@ export default function ViewsAdminUploadStep1() {
|
|||
{/* {!file && (
|
||||
)} */}
|
||||
|
||||
{file && (
|
||||
{file && ext!= 'pdf' && (
|
||||
<div className="mt-6 border border-gray-200 bg-white rounded-xl p-6 shadow-sm">
|
||||
{/* Info File */}
|
||||
<div className="">
|
||||
<p className="text-gray-800 text-sm font-medium flex items-center gap-2">
|
||||
<p className="text-gray-800 text-sm font-medium flex items-center gap-2 mb-1">
|
||||
📎
|
||||
<span
|
||||
className={`${
|
||||
|
|
@ -92,6 +92,8 @@ export default function ViewsAdminUploadStep1() {
|
|||
? 'text-red-500'
|
||||
: file.name.endsWith('.csv')
|
||||
? 'text-green-500'
|
||||
: file.name.endsWith('.xlsx')
|
||||
? 'text-green-500'
|
||||
: file.name.endsWith('.zip')
|
||||
? 'text-yellow-500'
|
||||
: 'text-gray-500'
|
||||
|
|
@ -101,7 +103,7 @@ export default function ViewsAdminUploadStep1() {
|
|||
</span>
|
||||
</p>
|
||||
{ext === "pdf" && pdfPageCount && (
|
||||
<p className="text-gray-500 text-xs mt-1">
|
||||
<p className="text-gray-500 text-xs">
|
||||
File PDF <span className="font-semibold">{pdfPageCount}</span> halaman.
|
||||
</p>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ import AdminLayout from "../layouts/AdminLayout";
|
|||
import ViewsAdminHome from "../pages/admin/home/views_admin_home";
|
||||
import ViewsAdminUploadStep1 from "../pages/admin/upload/views_admin_upload";
|
||||
import ViewsAdminUploadValidate from "../pages/admin/upload/views_admin_validate_upload";
|
||||
import ViewsAdminPdfViewer from "../pages/admin/upload/pdf_viewer/views_admin_pdf_viewer";
|
||||
import ViewsAdminUploadSuccess from "../pages/admin/upload/views_admin_success_upload";
|
||||
import ViewsAdminPublikasi from "../pages/admin/publikasi/views_admin_publikasi";
|
||||
import ViewsAdminUploadRules from "../pages/admin/upload/rules/views_admin_rules_upload";
|
||||
|
|
@ -127,6 +128,7 @@ const router = createBrowserRouter(
|
|||
{ path: "home", element: <ViewsAdminHome /> },
|
||||
{ path: "upload", element: <ViewsAdminUploadStep1 /> },
|
||||
{ path: "upload/validate", element: <ViewsAdminUploadValidate /> },
|
||||
{ path: "upload/pdf", element: <ViewsAdminPdfViewer /> },
|
||||
{ path: "upload/success", element: <ViewsAdminUploadSuccess /> },
|
||||
{ path: "upload/rules", element: <ViewsAdminUploadRules /> },
|
||||
{ path: "publikasi", element: <ViewsAdminPublikasi /> },
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user