I am trying to create a pdf report using dom pdf as follows I have created a function for the export in controller
public function exportData(Request $request)
{
// Get current authenticated user
$authUser = User::where('id', Auth::user()->id)->with('profile')->first();
$userName = $authUser->profile->first_name . ' ' . $authUser->profile->middle_name . ' ' . $authUser->profile->last_name;
// Build query
$query = ActivityLog::with([
'causer.profile:id,user_id,title,first_name,middle_name,last_name',
'subject'
]);
// Apply search if exists
if ($request->filled('search')) {
$query->where(function($q) use ($request) {
$q->where('description', 'like', "%{$request->search}%")
->orWhereHas('causer.profile', function($q) use ($request) {
$q->where('first_name', 'like', "%{$request->search}%")
->orWhere('last_name', 'like', "%{$request->search}%");
});
});
}
// Get data
$logs = $query->get();
// Generate PDF
$pdf = Pdf::loadView('exports.activity-logs', [
'logs' => $logs,
'searchTerm' => $request->search,
'generatedBy' => $userName,
'generatedAt' => now()->format('Y-m-d H:i:s'),
'watermarkPath' => public_path('dist/back/img/logo-3-300x161.png') // Path your watermark image
]);
// Set PDF options
$pdf->setPaper('A4', 'landscape')
->setOption('margin-top', 15)
->setOption('margin-right', 15)
->setOption('margin-bottom', 15)
->setOption('margin-left', 15)
->setOption('enable_html5_parser', true)
->setOption('enable_remote', true);
return $pdf->download('activity_logs_export_'.now()->format('Ymd_His').'.pdf');
}
Then I have created a html template as follows
<!DOCTYPE html>
<html>
<head>
<title>Activity Logs Report</title>
<style nonce="{{ csp_nonce() }}">
/* Watermark styling */
.watermark {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
opacity: 0.1;
z-index: -1;
width: 60%;
}
/* Fixed header */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
text-align: center;
padding: 10px 0;
background-color: white;
border-bottom: 1px solid #dee2e6;
height: 60px;
}
.header-image {
height: 40px;
margin-bottom: 5px;
}
.header-text {
margin: 5px 0;
font-size: 14pt;
}
.search-term {
font-size: 9pt;
}
/* Fixed footer */
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
font-size: 8pt;
padding: 10px 0;
background-color: white;
border-top: 1px solid #dee2e6;
height: 30px;
}
.float-right {
text-align: right;
}
.float-left {
text-align: left;
}
.text-muted {
color: #949494;
}
/* Main content with safe margins */
.content {
margin-top: 70px;
margin-bottom: 40px;
padding: 0 15px;
}
/* Table styling */
.table {
width: 100%;
border-collapse: collapse;
margin-top: 0;
}
.table-bordered th,
.table-bordered td {
border: 1px solid #dee2e6;
padding: 0.3rem;
font-size: 9pt;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(0, 0, 0, 0.05);
}
/* Table row handling */
tr {
page-break-inside: avoid;
}
/* Repeat headers on each page */
thead {
display: table-header-group;
}
tbody {
display: table-row-group;
}
body {
font-family: 'Helvetica', 'Arial', sans-serif;
font-size: 10pt;
margin: 0;
padding: 0;
}
.footer-content {
display: flex;
justify-content: space-between;
padding: 0 15px;
}
</style>
</head>
<body>
<!-- Watermark -->
<img class="watermark" src="{{ $watermarkPath }}" alt="Watermark">
<!-- Header -->
<div class="header">
<img src="{{ $watermarkPath }}" class="header-image">
<h2 class="header-text">ACTIVITY LOGS REPORT</h2>
@if (!empty($searchTerm))
<div class="search-term">Filtered by: "{{ $searchTerm }}"</div>
@endif
</div>
<!-- Footer -->
<div class="footer">
<div class="footer-content">
<div class="-float-left text-muted">
<p>Generated by: {{ $generatedBy }}</p>
<p>Generated at: {{ $generatedAt }}</p>
</div>
<div class="float-right text-muted">
Page <span class="page-number"></span> of <span class="page-count"></span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="content">
@php
$rowsPerPage = 14; // 1 header + 13 data rows
$itemsPerPage = $rowsPerPage - 1; // 13 data rows per page
$totalItems = count($logs);
$currentRow = 0;
$pageNumber = 0;
@endphp
@while ($currentRow < $totalItems)
@php
$pageNumber++;
$startRow = $currentRow;
$endRow = min($currentRow + $itemsPerPage, $totalItems);
@endphp
<!-- Start new table for each page -->
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th>Performed By</th>
<th>Affected Resource</th>
<th>Subject</th>
<th>User Agent</th>
<th>Date/Time</th>
</tr>
</thead>
<tbody>
@for ($i = $startRow; $i < $endRow; $i++)
@php
$log = $logs[$i];
$rowNumber = $i + 1;
@endphp
<tr>
<td>{{ $rowNumber }}</td>
<td>{{ $log->description }}</td>
<td>
@if ($log->causer && $log->causer->profile)
{{ optional($log->causer->profile)->title }}
{{ optional($log->causer->profile)->first_name }}
{{ optional($log->causer->profile)->last_name }}
@else
System
@endif
</td>
<td>{{ $log->event }}</td>
<td>
@if ($log->subject)
{{ class_basename($log->subject_type) }} #{{ $log->subject_id }}
@else
N/A
@endif
</td>
<td>{{ $log->user_agent }}</td>
<td>{{ $log->created_at->format('Y-m-d H:i:s') }}</td>
</tr>
@endfor
</tbody>
</table>
@php $currentRow = $endRow; @endphp
<!-- Add page break except after last page -->
@if ($currentRow < $totalItems)
<div style="page-break-after: always;"></div>
@endif
@endwhile
</div>
<!-- Page numbers -->
<script type="text/php" nonce="{{ csp_nonce() }}">
if (isset($pdf)) {
$font = $fontMetrics->get_font('Helvetica', 'normal');
$size = 8;
$pageText = "Page " . $PAGE_NUM . " of " . $PAGE_COUNT;
$y = $pdf->get_height() - 20;
$x = $pdf->get_width() - 60;
$pdf->text($x, $y, $pageText, $font, $size);
}
</script>
</body>
</html>
Now My problem is that the tables generated are lacking some rows as it is jumping one number per page i.e the second page lacks no 14, third page lacks no 27 fourth page lacks no 40...etc
The table headers are also supposed to show in each page but that is only happening in the first page only
Lastly the footer should show the page numbers but it is not showing the calculated page number plus the total page count it's only showing the place holders page of
Renedering the template to html gives the following output
The supplied code is my attempt at solving the issue plus I have tried reseraching acrosss multiple questions but no solution is helping with my current issue
About page numbers if you are using type="text/php"
you need to activate isPhpEnabled
in the DomPDF configs. Checkout the docs here. You need to set this before Pdf::loadView
via options. Just be aware that this can cause severe security issues. NEVER ENABLE PHP FOR UNTRUSTED TEMPALTES! e.g. user uploaded template or data.
About missing records I guess the main issue is your messy logic. You are using 2 loops with overlapping counters. You can try a batch processing approach that is much cleaner, something like this:
<div class="content">
@php
$itemsPerPage = 13;
$totalItems = count($logs);
$pageNumber = 0;
@endphp
@while (!empty($logs))
@php
$pageNumber++;
$batch = array_splice($logs, 0, $itemsPerPage);
@endphp
<!-- Start new table for each page -->
<table class="table table-bordered table-striped">
<thead>
// headers
</thead>
<tbody>
@foreach($batch as $i => $log)
<tr>
<td>{{ $pageNumber * $itemsPerPage + $i + 1 }}</td>
// rest of the columns
</tr>
@endforeach
</tbody>
</table>
@if (!empty($logs))
<div style="page-break-after: always;"></div>
@else
@php break; @endphp
@endif
@endwhile
</div>
Edit: Changed while condition to !empty($logs)
to avoid infinite loops