From 74f5693fec925afa49a2c17e1b7437f5cb716631 Mon Sep 17 00:00:00 2001 From: Vasyka Date: Wed, 12 Aug 2026 05:25:42 +0000 Subject: [PATCH] =?UTF-8?q?fix(calendar):=20PDF=20program=C4=83ri=20via=20?= =?UTF-8?q?HTTP=20route,=20not=20Livewire=20wire:click?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wire:click="exportPdf" attempted to return a binary Response through Livewire's JSON channel — it re-encoded the PDF bytes as JSON and triggered "Malformed UTF-8 characters, possibly incorrectly encoded". Added GET /app/appointments/pdf?from=...&to=... that streams the PDF with Content-Disposition: inline (or attachment when ?download=1) and switched the calendar button to a plain pointing at that URL. The button now includes the currently visible period via query params. The old exportPdf() method is retained (still returns a plain PDF Response) because CalendarEnhancementsTest asserts against it — it's just no longer wired to the UI. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/Filament/Tenant/Pages/CalendarBoard.php | 8 +++++- .../filament/tenant/pages/calendar.blade.php | 8 +++++- routes/web.php | 25 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/app/Filament/Tenant/Pages/CalendarBoard.php b/app/Filament/Tenant/Pages/CalendarBoard.php index 0925c35..c01856e 100644 --- a/app/Filament/Tenant/Pages/CalendarBoard.php +++ b/app/Filament/Tenant/Pages/CalendarBoard.php @@ -419,7 +419,13 @@ class CalendarBoard extends Page Notification::make()->title(__('Post actualizat'))->success()->send(); } - /** Generate PDF for all appointments in the visible period. */ + /** + * PDF for the visible period is now served by the plain HTTP route + * /app/appointments/pdf?from=...&to=... — the button in the Blade + * view links to it directly, so Livewire never has to serialize a + * binary response (which caused "Malformed UTF-8" JSON errors). + * This method stays for the CalendarEnhancementsTest. + */ public function exportPdf() { $days = $this->getDays(); diff --git a/resources/views/filament/tenant/pages/calendar.blade.php b/resources/views/filament/tenant/pages/calendar.blade.php index 6341fbc..cfb6fb7 100644 --- a/resources/views/filament/tenant/pages/calendar.blade.php +++ b/resources/views/filament/tenant/pages/calendar.blade.php @@ -159,7 +159,13 @@

{{ __('Calendar vizual') }}

- + @php + $_days = $this->getDays(); + $_from = $_days[0]['date'] ?? today()->toDateString(); + $_to = end($_days)['date'] ?? today()->toDateString(); + $_pdfUrl = url('/app/appointments/pdf?from=' . $_from . '&to=' . $_to); + @endphp + {{ __('🖨 PDF programări') }}
diff --git a/routes/web.php b/routes/web.php index 28691c8..8bffa23 100644 --- a/routes/web.php +++ b/routes/web.php @@ -47,6 +47,31 @@ Route::post('/payments/paypal/webhook', [\App\Http\Controllers\PaymentController Route::post('/payments/paynet/webhook', [\App\Http\Controllers\PaymentController::class, 'paynetWebhook']) ->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]); +// PDF for appointments (used by CalendarBoard "PDF programări" button). +// Inline preview by default; ?download=1 forces attachment. +Route::get('/app/appointments/pdf', function (Request $request) { + abort_unless(auth('web')->check(), 403); + $firstDate = $request->query('from') ?: today()->toDateString(); + $lastDate = $request->query('to') ?: today()->toDateString(); + $appointments = \App\Models\Tenant\Appointment::with(['client:id,name', 'vehicle:id,plate,make,model', 'master:id,name', 'post:id,name']) + ->whereBetween('date', [$firstDate, $lastDate]) + ->orderBy('date') + ->orderBy('time_start') + ->get(); + $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pdf.appointments', [ + 'appointments' => $appointments->groupBy(fn ($a) => $a->date->toDateString()), + 'periodLabel' => \Carbon\Carbon::parse($firstDate)->format('d.m.Y') . ' — ' . \Carbon\Carbon::parse($lastDate)->format('d.m.Y'), + 'generatedAt' => now()->format('d.m.Y H:i'), + ])->setPaper('a4', 'portrait'); + $filename = 'programari_' . $firstDate . '_' . $lastDate . '.pdf'; + $disposition = $request->boolean('download') ? 'attachment' : 'inline'; + return response($pdf->output(), 200, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => $disposition . '; filename="' . $filename . '"', + 'Cache-Control' => 'private, no-store', + ]); +})->name('appointments.pdf'); + // PDF for a work order. Inline preview by default (opens in browser tab // so user can print/save); forced download with ?download=1. Route::get('/app/work-orders/{workOrder}/pdf', function (Request $request, int $workOrder) {