satupeta-main/app/(modules)/admin/mapset-upload/_components/step-2-pdf-viewer.tsx

145 lines
5.4 KiB
TypeScript
Raw Normal View History

2026-01-28 05:48:46 +00:00
// app/admin/upload/_components/step-2-pdf-viewer.tsx
"use client";
2026-01-29 04:33:40 +00:00
import { useRef } from "react";
2026-01-28 05:48:46 +00:00
import { usePdfViewer } from "../_hooks/use-pdf-viewer";
import { Button } from "@/shared/components/ui/button";
2026-01-29 04:33:40 +00:00
import { Checkbox } from "@/shared/components/ui/checkbox";
import { Loader2, CheckSquare, Square } from "lucide-react";
2026-01-28 05:48:46 +00:00
import { motion } from "framer-motion";
export default function StepPdfViewer() {
2026-01-29 04:33:40 +00:00
const {
pages,
loading,
localSelectedPages,
toggleSelectPage,
handleProcessPdf,
// Fitur Baru dari Hook
handleSelectAll,
isAllSelected,
MAX_SELECT
} = usePdfViewer();
// 🔥 REF UNTUK SCROLLING
const pageRefs = useRef<{ [key: number]: HTMLDivElement | null }>({});
// Fungsi Helper Scroll
const scrollToPage = (pageNum: number) => {
const element = pageRefs.current[pageNum];
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "start" });
}
};
const handleSidebarClick = (pageNum: number) => {
toggleSelectPage(pageNum);
// Auto scroll hanya jika kita mencentang (opsional: bisa juga scroll saat uncheck jika mau)
// Disini saya buat scroll terjadi setiap kali klik baris
scrollToPage(pageNum);
};
2026-01-28 05:48:46 +00:00
return (
<div className="flex h-full gap-4">
{/* Sidebar Kiri */}
<div className="w-64 border-r pr-4 overflow-y-auto">
<h2 className="font-semibold mb-4">Pilih Halaman</h2>
2026-01-29 04:33:40 +00:00
<div className="mb-3 pb-3 border-b">
<Button
variant="outline"
size="sm"
className="w-full flex justify-between"
onClick={handleSelectAll}
disabled={loading || pages.length === 0}
>
<span className="flex items-center gap-2">
{isAllSelected ? <CheckSquare className="w-4 h-4"/> : <Square className="w-4 h-4"/>}
{isAllSelected ? "Batalkan Semua" : "Pilih Semua"}
</span>
</Button>
<p className="text-xs text-slate-400 mt-2 text-center">
Maksimal {MAX_SELECT} halaman.
</p>
</div>
<div className="space-y-2 overflow-y-auto flex-1 pr-2">
2026-01-28 05:48:46 +00:00
{pages.map((p) => (
<div
key={p.pageNum}
2026-01-29 04:33:40 +00:00
className={`flex items-center space-x-3 p-2 rounded cursor-pointer border transition-colors ${
localSelectedPages.includes(p.pageNum)
? "bg-blue-50 border-blue-200"
: "hover:bg-slate-50 border-transparent"
2026-01-28 05:48:46 +00:00
}`}
2026-01-29 04:33:40 +00:00
onClick={() => handleSidebarClick(p.pageNum)}
2026-01-28 05:48:46 +00:00
>
<Checkbox
checked={localSelectedPages.includes(p.pageNum)}
2026-01-29 04:33:40 +00:00
// onCheckedChange sudah dihandle oleh onClick parent div agar area klik lebih luas
className="pointer-events-none"
2026-01-28 05:48:46 +00:00
/>
2026-01-29 04:33:40 +00:00
<span className={`text-sm ${localSelectedPages.includes(p.pageNum) ? "text-blue-700 font-medium" : "text-slate-600"}`}>
Halaman {p.pageNum}
</span>
2026-01-28 05:48:46 +00:00
</div>
))}
</div>
<div className="mt-4 pt-4 border-t sticky bottom-0 bg-white">
<Button
className="w-full"
onClick={handleProcessPdf}
disabled={loading || localSelectedPages.length === 0}
>
{loading ? <Loader2 className="animate-spin mr-2" /> : null}
Proses Halaman
</Button>
</div>
</div>
{/* Main Content (Preview Images) */}
<div className="flex-1 overflow-y-auto bg-gray-100 p-4 rounded-lg">
{loading && pages.length === 0 && (
<div className="flex justify-center items-center h-full">
<Loader2 className="animate-spin h-8 w-8 text-blue-500" />
<span className="ml-2">Merender PDF...</span>
</div>
)}
2026-01-29 04:33:40 +00:00
<div className="space-y-8">
2026-01-28 05:48:46 +00:00
{pages.map((p) => (
<motion.div
key={p.pageNum}
2026-01-29 04:33:40 +00:00
// 🔥 SET REF DI SINI
ref={(el) => { pageRefs.current[p.pageNum] = el; }}
2026-01-28 05:48:46 +00:00
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
2026-01-29 04:33:40 +00:00
transition={{ delay: 0.05 * (p.pageNum > 10 ? 10 : p.pageNum) }} // Optimasi delay list panjang
// Styling highlight jika terpilih
className={`bg-white p-2 shadow-sm rounded-lg border-2 transition-all duration-300 ${
localSelectedPages.includes(p.pageNum)
? "border-blue-500 ring-4 ring-blue-500/10"
: "border-transparent hover:border-slate-300"
}`}
onClick={() => toggleSelectPage(p.pageNum)} // Klik gambar juga bisa toggle select
2026-01-28 05:48:46 +00:00
>
2026-01-29 04:33:40 +00:00
<div className="relative">
{/* Badge Nomor Halaman di atas gambar */}
<div className="absolute top-2 left-2 bg-slate-800/80 text-white text-xs px-2 py-1 rounded backdrop-blur-sm z-10">
Halaman. {p.pageNum}
</div>
<img
src={p.imageUrl}
alt={`Page ${p.pageNum}`}
className="w-full h-auto rounded border border-slate-100"
loading="lazy"
/>
</div>
2026-01-28 05:48:46 +00:00
</motion.div>
))}
</div>
</div>
</div>
);
}