Files
termanager2/resources/views/assignments/pdf-viewer.blade.php

353 lines
11 KiB
PHP
Raw Permalink 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="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
<title>Territorio {{ $assignment->territorio?->numero }}</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf_viewer.min.css" integrity="sha512-kMgaLfnBSAM0MFgr8fMDCMr2SYGQiMIFRbkBxRfFEqDqw/0hNh2GpcjYKjR0z4VoVVhYx1VlJdvfO1HCkhpg==" crossorigin="anonymous" referrerpolicy="no-referrer">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f1f5f9;
display: flex;
flex-direction: column;
overflow: hidden;
}
header {
background: #fff;
border-bottom: 1px solid #e2e8f0;
padding: 10px 16px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 16px;
z-index: 10;
}
.logo {
font-size: 15px;
font-weight: 700;
color: #4f46e5;
letter-spacing: -.3px;
flex: none;
}
.info {
display: flex;
flex-wrap: wrap;
gap: 4px 12px;
flex: 1;
}
.chip {
display: inline-flex;
align-items: center;
gap: 4px;
background: #f1f5f9;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 3px 8px;
font-size: 12px;
color: #374151;
}
.chip .label {
font-size: 10px;
color: #94a3b8;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .4px;
}
.chip .value {
font-weight: 600;
color: #1e293b;
}
.open-btn {
display: inline-flex;
align-items: center;
gap: 5px;
background: #4f46e5;
color: #fff;
text-decoration: none;
font-size: 12px;
font-weight: 600;
padding: 6px 12px;
border-radius: 6px;
flex: none;
transition: background .15s;
}
.open-btn:hover { background: #4338ca; }
/* Toolbar */
.pdf-toolbar {
background: #1e293b;
color: #fff;
padding: 6px 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
font-size: 13px;
flex: none;
z-index: 10;
}
.pdf-toolbar button {
background: rgba(255,255,255,.1);
border: none;
color: #fff;
width: 32px;
height: 32px;
border-radius: 6px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: background .15s;
font-size: 16px;
}
.pdf-toolbar button:hover { background: rgba(255,255,255,.2); }
.pdf-toolbar button:disabled { opacity: .3; cursor: default; }
.page-info {
font-variant-numeric: tabular-nums;
min-width: 80px;
text-align: center;
}
/* PDF viewport */
.pdf-viewport {
flex: 1 1 0;
min-height: 0;
overflow-y: auto;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
background: #64748b;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 0;
gap: 12px;
}
.pdf-viewport canvas {
display: block;
max-width: 100%;
height: auto;
box-shadow: 0 2px 16px rgba(0,0,0,.25);
background: #fff;
}
/* Loading spinner */
.pdf-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
padding: 40px;
color: #fff;
font-size: 14px;
}
.spinner {
width: 36px;
height: 36px;
border: 3px solid rgba(255,255,255,.2);
border-top-color: #fff;
border-radius: 50%;
animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.pdf-error {
display: none;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 40px;
text-align: center;
color: #fff;
}
.pdf-error a {
background: #4f46e5;
color: #fff;
text-decoration: none;
font-weight: 600;
font-size: 14px;
padding: 10px 20px;
border-radius: 8px;
}
@media (max-width: 480px) {
header { padding: 8px 10px; }
.chip { font-size: 11px; padding: 2px 6px; }
.info { gap: 3px 8px; }
.pdf-toolbar { padding: 4px 8px; gap: 8px; }
.pdf-toolbar button { width: 28px; height: 28px; }
}
</style>
</head>
<body>
<header>
<span class="logo">TerManager2</span>
<div class="info">
<div class="chip">
<span class="label">Territorio</span>
<span class="value"> {{ $assignment->territorio?->numero }}</span>
</div>
<div class="chip">
<span class="label">Assegnato a</span>
<span class="value">{{ $assignment->proclamatore?->nome_completo ?? '—' }}</span>
</div>
<div class="chip">
<span class="label">Data</span>
<span class="value">{{ $assignment->assigned_at->format('d/m/Y') }}</span>
</div>
</div>
@if($showDownload)
<a href="{{ $pdfUrl }}" download class="open-btn">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5m0 0l5-5m-5 5V4"/></svg>
Scarica PDF
</a>
@endif
</header>
<div class="pdf-toolbar">
<button id="prevPage" title="Pagina precedente" disabled></button>
<span class="page-info" id="pageInfo"></span>
<button id="nextPage" title="Pagina successiva" disabled></button>
<button id="zoomOut" title="Riduci"></button>
<button id="zoomIn" title="Ingrandisci">+</button>
</div>
<div class="pdf-viewport" id="pdfViewport">
<div class="pdf-loading" id="pdfLoading">
<div class="spinner"></div>
Caricamento PDF…
</div>
<div class="pdf-error" id="pdfError">
<svg width="48" height="48" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"/></svg>
<p>Impossibile caricare il PDF.</p>
@if($showDownload)
<a href="{{ $pdfUrl }}" download>Scarica PDF</a>
@endif
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.min.mjs" type="module"></script>
<script type="module">
import * as pdfjsLib from 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.min.mjs';
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.worker.min.mjs';
const url = @json($pdfUrl);
const viewport = document.getElementById('pdfViewport');
const loading = document.getElementById('pdfLoading');
const error = document.getElementById('pdfError');
const pageInfo = document.getElementById('pageInfo');
const prevBtn = document.getElementById('prevPage');
const nextBtn = document.getElementById('nextPage');
const zoomIn = document.getElementById('zoomIn');
const zoomOut = document.getElementById('zoomOut');
let pdfDoc = null;
let scale = 1.5;
let rendering = false;
const canvases = [];
// Determine initial scale based on screen width
if (window.innerWidth <= 480) {
scale = 1.0;
} else if (window.innerWidth <= 768) {
scale = 1.2;
}
async function renderPage(pageNum) {
const page = await pdfDoc.getPage(pageNum);
const vp = page.getViewport({ scale });
// Reuse or create canvas
let canvas = canvases[pageNum - 1];
if (!canvas) {
canvas = document.createElement('canvas');
canvases[pageNum - 1] = canvas;
}
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
canvas.width = Math.floor(vp.width * dpr);
canvas.height = Math.floor(vp.height * dpr);
canvas.style.width = Math.floor(vp.width) + 'px';
canvas.style.height = Math.floor(vp.height) + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
await page.render({ canvasContext: ctx, viewport: vp }).promise;
return canvas;
}
async function renderAllPages() {
if (rendering) return;
rendering = true;
// Clear viewport
viewport.querySelectorAll('canvas').forEach(c => c.remove());
for (let i = 1; i <= pdfDoc.numPages; i++) {
const canvas = await renderPage(i);
viewport.appendChild(canvas);
}
pageInfo.textContent = pdfDoc.numPages + (pdfDoc.numPages === 1 ? ' pagina' : ' pagine');
rendering = false;
}
async function init() {
try {
pdfDoc = await pdfjsLib.getDocument({ url, withCredentials: false }).promise;
loading.style.display = 'none';
prevBtn.disabled = true;
nextBtn.disabled = true;
// Render all pages as continuous scroll
await renderAllPages();
zoomIn.disabled = false;
zoomOut.disabled = false;
} catch (err) {
console.error('PDF load error:', err);
loading.style.display = 'none';
error.style.display = 'flex';
}
}
zoomIn.addEventListener('click', () => {
scale = Math.min(scale + 0.25, 4);
canvases.length = 0;
renderAllPages();
});
zoomOut.addEventListener('click', () => {
scale = Math.max(scale - 0.25, 0.5);
canvases.length = 0;
renderAllPages();
});
init();
</script>
</body>
</html>