Squashed commit of the following:
commit c993da3397b65488d919d1b929cbd4638754601b Author: ncoronel <ncoronel@quo.ar> Date: Tue Jul 28 09:54:10 2026 -0300 feat(tickets): update ticket metadata layout for improved readability and clarity commit 140fa4676d84bed788894e1d7994b26dccac7a34 Author: ncoronel <ncoronel@quo.ar> Date: Tue Jul 28 09:43:21 2026 -0300 feat(tickets): enhance PDF ticket download with dynamic filename and improved styling commit 8875c047b198776e61e8353c99cf4c7b00bc8fc9 Author: ncoronel <ncoronel@quo.ar> Date: Tue Jul 28 09:03:17 2026 -0300 feat(tickets): add PDF download functionality for tickets - Implemented a new endpoint to download tickets as a PDF. - Created DownloadTicketsPdfRequest for validating ticket IDs. - Added TicketPdfService to handle PDF generation and QR code embedding. - Developed a Blade view for rendering ticket details in PDF format. - Updated API routes to include the new PDF download route. - Added tests to ensure authenticated users can download their tickets and that users cannot download tickets belonging to others. - Updated composer.json and composer.lock to include necessary dependencies for PDF generation and QR code creation.
This commit is contained in:
213
resources/views/pdf/tickets.blade.php
Normal file
213
resources/views/pdf/tickets.blade.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<style>
|
||||
@page { margin: 0; }
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
background: #f4f7f5;
|
||||
color: #17211b;
|
||||
font-family: DejaVu Sans, sans-serif;
|
||||
font-size: 11px;
|
||||
margin: 0;
|
||||
}
|
||||
.ticket {
|
||||
background: #f4f7f5;
|
||||
min-height: 1120px;
|
||||
padding: 0 54px;
|
||||
position: relative;
|
||||
}
|
||||
.ticket + .ticket { page-break-before: always; }
|
||||
.accent { background: {{ $primaryColor }}; height: 8px; margin: 0 -54px; }
|
||||
.header {
|
||||
background: {{ $headerBackgroundColor }};
|
||||
color: {{ $headerTextColor }};
|
||||
height: 132px;
|
||||
margin: 0 -54px;
|
||||
padding: 32px 54px 0;
|
||||
}
|
||||
.header-table, .meta-table, .footer-table { border-collapse: collapse; width: 100%; }
|
||||
.brand-cell { vertical-align: middle; }
|
||||
.type-cell { text-align: right; vertical-align: middle; width: 190px; }
|
||||
.logo {
|
||||
height: 64px;
|
||||
max-width: 180px;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.tenant-name {
|
||||
display: inline-block;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
max-width: 340px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ticket-type {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
color: {{ $primaryColor }};
|
||||
display: inline-block;
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1.25px;
|
||||
padding: 8px 13px;
|
||||
}
|
||||
.content { padding-top: 36px; text-align: center; }
|
||||
.eyebrow {
|
||||
color: {{ $primaryColor }};
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1.4px;
|
||||
margin: 0 0 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.ticket-name {
|
||||
font-size: 27px;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
margin: 0 auto 23px;
|
||||
max-width: 570px;
|
||||
}
|
||||
.meta-card {
|
||||
background: #fff;
|
||||
border: 1px solid #dfe7e2;
|
||||
border-radius: 10px;
|
||||
margin: 0 auto 24px;
|
||||
padding: 15px 18px;
|
||||
text-align: left;
|
||||
width: 500px;
|
||||
}
|
||||
.meta-cell {
|
||||
border-right: 1px solid #e5ebe7;
|
||||
padding: 0 14px;
|
||||
vertical-align: top;
|
||||
width: 33.33%;
|
||||
}
|
||||
.meta-cell:first-child { padding-left: 0; }
|
||||
.meta-cell:last-child { border-right: 0; padding-right: 0; }
|
||||
.meta-label {
|
||||
color: #758078;
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.9px;
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.meta-value { color: #26322a; font-size: 12px; font-weight: bold; }
|
||||
.qr-card {
|
||||
background: #fff;
|
||||
border: 1px solid #dfe7e2;
|
||||
border-radius: 16px;
|
||||
display: inline-block;
|
||||
padding: 18px 22px 17px;
|
||||
}
|
||||
.qr-frame {
|
||||
border: 3px solid {{ $primaryColor }};
|
||||
border-radius: 14px;
|
||||
padding: 11px;
|
||||
}
|
||||
.qr { display: block; height: 276px; width: 276px; }
|
||||
.scan-label {
|
||||
color: #26322a;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin: 14px 0 3px;
|
||||
}
|
||||
.scan-help { color: #77827b; font-size: 9px; margin: 0; }
|
||||
.notice {
|
||||
background: #eaf1ed;
|
||||
border-left: 4px solid {{ $primaryColor }};
|
||||
border-radius: 6px;
|
||||
color: #445049;
|
||||
line-height: 1.45;
|
||||
margin: 22px auto 0;
|
||||
padding: 11px 14px;
|
||||
text-align: left;
|
||||
width: 500px;
|
||||
}
|
||||
.notice strong { color: #26322a; }
|
||||
.footer {
|
||||
border-top: 1px solid #dfe7e2;
|
||||
bottom: 32px;
|
||||
color: #7b857e;
|
||||
font-size: 8px;
|
||||
left: 54px;
|
||||
padding-top: 11px;
|
||||
position: absolute;
|
||||
right: 54px;
|
||||
}
|
||||
.footer-right { text-align: right; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@foreach ($tickets as $ticket)
|
||||
<section class="ticket">
|
||||
<div class="accent"></div>
|
||||
|
||||
<header class="header">
|
||||
<table class="header-table" role="presentation">
|
||||
<tr>
|
||||
<td class="brand-cell">
|
||||
@if ($logoDataUri)
|
||||
<img class="logo" src="{{ $logoDataUri }}" alt="Logo de {{ $tenant->nombre }}">
|
||||
@else
|
||||
<span class="tenant-name">{{ $tenant->nombre }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="type-cell">
|
||||
<span class="ticket-type">TICKET DIGITAL</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</header>
|
||||
|
||||
<main class="content">
|
||||
<p class="eyebrow">Tu acceso</p>
|
||||
<h1 class="ticket-name">{{ $ticket->name }}</h1>
|
||||
|
||||
<div class="meta-card">
|
||||
<table class="meta-table" role="presentation">
|
||||
<tr>
|
||||
<td class="meta-cell">
|
||||
<div class="meta-label">Número de ticket</div>
|
||||
<div class="meta-value">#{{ $ticket->id }}</div>
|
||||
</td>
|
||||
<td class="meta-cell">
|
||||
<div class="meta-label">Desde</div>
|
||||
<div class="meta-value">{{ $ticket->starts_at?->format('d/m/Y') ?? 'Sin fecha inicial' }}</div>
|
||||
</td>
|
||||
<td class="meta-cell">
|
||||
<div class="meta-label">Hasta</div>
|
||||
<div class="meta-value">{{ $ticket->expires_at?->format('d/m/Y') ?? 'Sin vencimiento' }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="qr-card">
|
||||
<div class="qr-frame">
|
||||
<img class="qr" src="{{ $qrCodes[$ticket->id] }}" alt="Código QR del ticket {{ $ticket->id }}">
|
||||
</div>
|
||||
<p class="scan-label">Escaneá este código al ingresar</p>
|
||||
<p class="scan-help">Aumentá el brillo de la pantalla para una lectura más rápida.</p>
|
||||
</div>
|
||||
|
||||
<div class="notice">
|
||||
<strong>Importante:</strong> este ticket es personal. No compartas el código QR con otras personas.
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<table class="footer-table" role="presentation">
|
||||
<tr>
|
||||
<td>{{ $tenant->nombre }}</td>
|
||||
<td class="footer-right">Conservá este ticket hasta finalizar tu ingreso.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</footer>
|
||||
</section>
|
||||
@endforeach
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user