2026-02-02 15:16:09 +07:00

346 lines
8.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Preview Dokumen</title>
<style>
:root{
--bg:#f5f6f8;
--card:#fff;
--border:#e5e7eb;
--text:#111827;
--muted:#6b7280;
--primary:#2563eb;
--primary2:#1d4ed8;
}
*{ box-sizing:border-box; }
body{
margin:0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial;
background:var(--bg);
color:var(--text);
height: 100dvh;
}
/* ===== TOPBAR ===== */
.topbar{
position: sticky;
top:0;
z-index: 50;
min-height: 58px;
background: rgba(255,255,255,.96);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--border);
display:flex;
align-items:center;
justify-content:space-between;
padding: 0 12px;
gap: 8px;
}
.topbar-left,
.topbar-right{
display:flex;
align-items:center;
gap:8px;
white-space:nowrap;
flex-wrap:wrap;
}
.topbar-center{
display:flex;
align-items:center;
justify-content:center;
flex:1 1 auto;
pointer-events:none;
}
.topbar-logo{
height: 50px;
width:auto;
opacity:.95;
user-select:none;
pointer-events:none;
display:block;
}
.btn{
border:1px solid var(--border);
background:#fff;
padding:7px 10px;
border-radius:10px;
cursor:pointer;
font-size:14px;
line-height:1;
display:inline-flex;
align-items:center;
gap:6px;
text-decoration:none;
color:var(--text);
}
.btn:active{ transform: translateY(1px); }
.btn-primary{
background: var(--primary);
border-color: var(--primary);
color:#fff;
}
.btn-primary:hover{
background: var(--primary2);
border-color: var(--primary2);
color:#fff;
}
.badge{
font-size:13px;
padding:7px 10px;
border:1px solid var(--border);
background:#fff;
border-radius:999px;
white-space:nowrap;
color:var(--text);
}
/* responsive: kecilkan kontrol di layar sempit */
@media (max-width: 1000px){
.topbar-logo{ height: 28px; }
.btn{ font-size:12px; padding:6px 8px; border-radius:9px; }
.badge{ font-size:12px; padding:6px 8px; }
}
@media (max-width: 780px){
/* kalau sempit banget, sembunyikan teks "Dokumen" biar tidak tabrakan */
.doc-label{ display:none; }
.topbar{
padding: 8px 10px;
flex-wrap:wrap;
align-items:flex-start;
}
.topbar-center{
order:1;
flex-basis:100%;
justify-content:center;
}
.topbar-left{
order:2;
}
.topbar-right{
order:3;
width:100%;
justify-content:space-between;
}
}
/* ===== PDF AREA ===== */
#pdfWrap{
height: calc(100dvh - var(--topbar-height, 58px));
overflow-y: scroll;
overflow-x: auto;
padding: 14px;
scrollbar-gutter: stable both-edges;
}
#pdfPages{
display:flex !important;
flex-direction:column !important;
align-items:center !important;
gap:14px !important;
column-count:1 !important;
column-gap:0 !important;
}
.pageCanvas{
display:block !important;
background:var(--card);
border:1px solid var(--border);
border-radius:12px;
box-shadow:0 2px 10px rgba(0,0,0,.06);
max-width:100%;
height:auto;
}
.hint{
padding: 12px;
text-align:center;
color: var(--muted);
}
</style>
</head>
<body>
<div class="topbar">
<!-- LEFT -->
<div class="topbar-left">
<button class="btn" onclick="history.back()"><span class="doc-label">Kembali</span></button>
<span class="badge doc-label">Dokumen {{ $data->nama_dokumen ?? '-'}}</span>
</div>
<!-- CENTER LOGO -->
<div class="topbar-center">
<img src="{{ asset('logo/logo_rsabhk.png') }}" alt="Logo" class="topbar-logo">
</div>
<!-- RIGHT -->
<div class="topbar-right">
<button class="btn" id="zoomOut" title="Zoom Out"></button>
<span class="badge" id="zoomLabel">90%</span>
<button class="btn" id="zoomIn" title="Zoom In">+</button>
<button class="btn" id="resetZoom" title="Reset">Reset</button>
<a class="btn btn-primary"
href="/file-download/{{ $id }}"
title="Download PDF">
Unduh
</a>
<span class="badge" id="pageInfo">Page 0 / 0</span>
</div>
</div>
<div id="pdfWrap">
<div id="pdfPages" class="hint">Loading...</div>
</div>
<script type="module">
import * as pdfjsLib from "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/5.4.149/pdf.min.mjs";
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/5.4.149/pdf.worker.min.mjs";
const topbar = document.querySelector('.topbar');
const pdfWrap = document.getElementById('pdfWrap');
const pdfPages = document.getElementById('pdfPages');
const zoomLabel = document.getElementById('zoomLabel');
const pageInfo = document.getElementById('pageInfo');
const btnZoomIn = document.getElementById('zoomIn');
const btnZoomOut = document.getElementById('zoomOut');
const btnReset = document.getElementById('resetZoom');
let pdfDoc = null;
// default zoom nyaman
const defaultZoom = () => (window.innerWidth < 1366 ? 0.85 : 0.90);
let zoom = defaultZoom();
// batas lebar render maksimal
const MAX_DOC_WIDTH = 960; // kecilin jadi 900 kalau masih besar
function calcFitScale(page, baseZoom = 1.0) {
const padding = 56;
const wrapWidth = Math.max(320, pdfWrap.clientWidth - padding);
const targetWidth = Math.min(wrapWidth, MAX_DOC_WIDTH);
const vp1 = page.getViewport({ scale: 1 });
const fitScale = targetWidth / vp1.width;
return fitScale * baseZoom;
}
async function renderAllPages() {
if (!pdfDoc) return;
const prevScrollTop = pdfWrap.scrollTop;
pdfPages.innerHTML = '<div class="hint">Rendering...</div>';
const numPages = pdfDoc.numPages;
pageInfo.textContent = `Page 1 / ${numPages}`;
pdfPages.innerHTML = '';
for (let p = 1; p <= numPages; p++) {
const page = await pdfDoc.getPage(p);
const scale = calcFitScale(page, zoom);
const viewport = page.getViewport({ scale });
const canvas = document.createElement('canvas');
canvas.className = 'pageCanvas';
const ctx = canvas.getContext('2d', { alpha: false });
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
pdfPages.appendChild(canvas);
await page.render({ canvasContext: ctx, viewport }).promise;
}
zoomLabel.textContent = `${Math.round(zoom * 100)}%`;
pdfWrap.scrollTop = prevScrollTop;
}
async function loadPdf(url) {
pdfPages.innerHTML = '<div class="hint">Loading PDF...</div>';
const task = pdfjsLib.getDocument({
url,
withCredentials: true,
});
pdfDoc = await task.promise;
await renderAllPages();
// update page info based on scroll
pdfWrap.addEventListener('scroll', () => {
const canvases = pdfPages.querySelectorAll('canvas');
if (!canvases.length) return;
const wrapTop = pdfWrap.getBoundingClientRect().top;
let current = 1;
for (let i = 0; i < canvases.length; i++) {
const rect = canvases[i].getBoundingClientRect();
if (rect.top - wrapTop < 80) current = i + 1;
else break;
}
pageInfo.textContent = `Page ${current} / ${pdfDoc.numPages}`;
}, { passive: true });
}
// zoom handlers
btnZoomIn.addEventListener('click', async () => {
zoom = Math.min(1.6, +(zoom + 0.1).toFixed(2));
await renderAllPages();
});
btnZoomOut.addEventListener('click', async () => {
zoom = Math.max(0.6, +(zoom - 0.1).toFixed(2));
await renderAllPages();
});
btnReset.addEventListener('click', async () => {
zoom = defaultZoom();
await renderAllPages();
});
function syncTopbarHeight(){
if (!topbar) return;
const h = topbar.offsetHeight || 58;
document.documentElement.style.setProperty('--topbar-height', `${h}px`);
}
// rerender on resize (debounce)
let resizeTimer = null;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
syncTopbarHeight();
renderAllPages();
}, 200);
});
// load watermarked pdf endpoint
syncTopbarHeight();
loadPdf(`/file-preview/{{ $id }}`);
</script>
</body>
</html>