diff --git a/app/Filament/Tenant/Pages/CalendarBoard.php b/app/Filament/Tenant/Pages/CalendarBoard.php index e9dff6e..77aad27 100644 --- a/app/Filament/Tenant/Pages/CalendarBoard.php +++ b/app/Filament/Tenant/Pages/CalendarBoard.php @@ -437,11 +437,13 @@ class CalendarBoard extends Page 'generatedAt' => now()->format('d.m.Y H:i'), ])->setPaper('a4', 'portrait'); - return response()->streamDownload( - fn () => print $pdf->output(), - 'programari_' . $firstDate . '_' . $lastDate . '.pdf', - ['Content-Type' => 'application/pdf'] - ); + $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', + ]); } /** Flat list of appointments for the visible period — used by list view. */ diff --git a/app/Filament/Tenant/Resources/InjectorProtocolResource.php b/app/Filament/Tenant/Resources/InjectorProtocolResource.php index 39a8a32..a693fd2 100644 --- a/app/Filament/Tenant/Resources/InjectorProtocolResource.php +++ b/app/Filament/Tenant/Resources/InjectorProtocolResource.php @@ -264,7 +264,7 @@ class InjectorProtocolResource extends Resource ->actions([ Actions\Action::make('pdf') ->label(__('PDF')) - ->icon('heroicon-m-document-arrow-down') + ->icon('heroicon-m-document-magnifying-glass') ->color('gray') ->url(fn (InjectorProtocol $r) => route('tenant.injector-protocols.pdf', ['record' => $r->id])) ->openUrlInNewTab(), diff --git a/app/Filament/Tenant/Resources/InjectorProtocolResource/Pages/EditInjectorProtocol.php b/app/Filament/Tenant/Resources/InjectorProtocolResource/Pages/EditInjectorProtocol.php index a70eb40..0cb1943 100644 --- a/app/Filament/Tenant/Resources/InjectorProtocolResource/Pages/EditInjectorProtocol.php +++ b/app/Filament/Tenant/Resources/InjectorProtocolResource/Pages/EditInjectorProtocol.php @@ -15,8 +15,8 @@ class EditInjectorProtocol extends EditRecord { return [ Actions\Action::make('pdf') - ->label(__('Descarcă PDF')) - ->icon('heroicon-m-document-arrow-down') + ->label(__('Vizualizare PDF')) + ->icon('heroicon-m-document-magnifying-glass') ->color('gray') ->url(fn (InjectorProtocol $record) => route('tenant.injector-protocols.pdf', ['record' => $record->id])) ->openUrlInNewTab(), diff --git a/app/Filament/Tenant/Resources/WorkOrderResource.php b/app/Filament/Tenant/Resources/WorkOrderResource.php index e7581bc..0cbb2df 100644 --- a/app/Filament/Tenant/Resources/WorkOrderResource.php +++ b/app/Filament/Tenant/Resources/WorkOrderResource.php @@ -186,17 +186,10 @@ class WorkOrderResource extends Resource ->actions([ Actions\Action::make('pdf') ->label(__('PDF')) - ->icon('heroicon-m-document-arrow-down') + ->icon('heroicon-m-document-magnifying-glass') ->color('gray') - ->action(function (WorkOrder $r) { - $svc = app(\App\Services\WorkOrderPdfService::class); - $pdf = $svc->generate($r); - $filename = $svc->filename($r); - return response()->streamDownload( - fn () => print($pdf->output()), - $filename - ); - }), + ->url(fn (WorkOrder $r) => url('/app/work-orders/' . $r->id . '/pdf')) + ->openUrlInNewTab(), Actions\EditAction::make(), Actions\DeleteAction::make(), ]) diff --git a/app/Filament/Tenant/Resources/WorkOrderResource/Pages/EditWorkOrder.php b/app/Filament/Tenant/Resources/WorkOrderResource/Pages/EditWorkOrder.php index 19b5c78..5e5e0ef 100644 --- a/app/Filament/Tenant/Resources/WorkOrderResource/Pages/EditWorkOrder.php +++ b/app/Filament/Tenant/Resources/WorkOrderResource/Pages/EditWorkOrder.php @@ -4,7 +4,6 @@ namespace App\Filament\Tenant\Resources\WorkOrderResource\Pages; use App\Filament\Tenant\Resources\WorkOrderResource; use App\Models\Tenant\WorkOrder; -use App\Services\WorkOrderPdfService; use Filament\Actions; use Filament\Resources\Pages\EditRecord; @@ -85,19 +84,11 @@ class EditWorkOrder extends EditRecord 'wo' => $this->record, ])), Actions\Action::make('pdf') - ->label(__('Descarcă PDF')) - ->icon('heroicon-m-document-arrow-down') + ->label(__('Vizualizare PDF')) + ->icon('heroicon-m-document-magnifying-glass') ->color('gray') - ->action(function () { - /** @var WorkOrder $wo */ - $wo = $this->record; - $svc = app(WorkOrderPdfService::class); - $pdf = $svc->generate($wo); - return response()->streamDownload( - fn () => print($pdf->output()), - $svc->filename($wo) - ); - }), + ->url(fn () => url('/app/work-orders/' . $this->record->id . '/pdf')) + ->openUrlInNewTab(), Actions\DeleteAction::make(), ]; } diff --git a/lang/en.json b/lang/en.json index e023f8f..37d190b 100644 --- a/lang/en.json +++ b/lang/en.json @@ -2207,8 +2207,10 @@ "Vizitator": "Viewer", "Vizite": "Visits", "Vizual": "Visual", + "Vizualizare PDF": "View PDF", "Vizualizare dashboard": "Dashboard view", "Vizualizează": "View", + "Vizualizează factură (PDF)": "View invoice (PDF)", "Vizualizări": "Views", "Vopsea sărită": "Paint chip", "Vopsire": "Painting", diff --git a/lang/ru.json b/lang/ru.json index d291458..0e9fed2 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -2207,8 +2207,10 @@ "Vizitator": "Гость", "Vizite": "Визиты", "Vizual": "Визуально", + "Vizualizare PDF": "Просмотр PDF", "Vizualizare dashboard": "Дашборд", "Vizualizează": "Просмотр", + "Vizualizează factură (PDF)": "Просмотр счёта (PDF)", "Vizualizări": "Просмотры", "Vopsea sărită": "Скол краски", "Vopsire": "Покраска", diff --git a/resources/views/filament/tenant/pages/work-order-dashboard.blade.php b/resources/views/filament/tenant/pages/work-order-dashboard.blade.php index 695ed0f..aebdffc 100644 --- a/resources/views/filament/tenant/pages/work-order-dashboard.blade.php +++ b/resources/views/filament/tenant/pages/work-order-dashboard.blade.php @@ -495,9 +495,15 @@ @if ($wo->vehicle) @php + // 1) Vehicle's own photo (if the model uses MediaLibrary), else + // 2) fall back to the first WO photo (uploaded via edit form). $photoUrl = method_exists($wo->vehicle, 'getFirstMediaUrl') ? ($wo->vehicle->getFirstMediaUrl('photos') ?: null) : null; + if (! $photoUrl) { + $woFirst = $wo->getFirstMedia('photos'); + $photoUrl = $woFirst?->getFullUrl(); + } @endphp @if ($photoUrl) {{ $wo->vehicle->make }} @@ -683,7 +689,10 @@
- 📄 {{ __('Descarcă factură (PDF)') }} + 🔍 {{ __('Vizualizează factură (PDF)') }} + + + ⬇️ {{ __('Descarcă factură (PDF)') }} @php $signed = $wo->getMedia('signed_documents'); @endphp @if ($signed->isNotEmpty()) diff --git a/routes/web.php b/routes/web.php index fac7140..28691c8 100644 --- a/routes/web.php +++ b/routes/web.php @@ -47,13 +47,21 @@ 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 download for a work order (used from dashboard's Documents tab). -Route::get('/app/work-orders/{workOrder}/pdf', function (int $workOrder) { +// 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) { abort_unless(auth('web')->check(), 403); $wo = \App\Models\Tenant\WorkOrder::findOrFail($workOrder); $svc = app(\App\Services\WorkOrderPdfService::class); $pdf = $svc->generate($wo); - return response()->streamDownload(fn () => print($pdf->output()), $svc->filename($wo)); + $bytes = $pdf->output(); + $filename = $svc->filename($wo); + $disposition = $request->boolean('download') ? 'attachment' : 'inline'; + return response($bytes, 200, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => $disposition . '; filename="' . $filename . '"', + 'Cache-Control' => 'private, no-store', + ]); })->name('work-orders.pdf'); // User invitation accept flow (no auth required — token is the credential). @@ -82,17 +90,20 @@ Route::middleware(['web', 'auth'])->group(function () { Route::post('/push/unsubscribe', [\App\Http\Controllers\PushSubscriptionController::class, 'unsubscribe']) ->name('push.unsubscribe'); - Route::get('/app/injector-protocols/{record}/pdf', function (\App\Models\Tenant\InjectorProtocol $record) { + Route::get('/app/injector-protocols/{record}/pdf', function (Request $request, \App\Models\Tenant\InjectorProtocol $record) { abort_unless( auth()->user()?->canDo(\App\Auth\Permissions::INJECTOR_PROTOCOLS_VIEW), 403 ); $svc = app(\App\Services\InjectorProtocolPdfService::class); - return response()->streamDownload( - fn () => print($svc->render($record)), - $svc->filename($record), - ['Content-Type' => 'application/pdf'] - ); + $bytes = $svc->render($record); + $filename = $svc->filename($record); + $disposition = $request->boolean('download') ? 'attachment' : 'inline'; + return response($bytes, 200, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => $disposition . '; filename="' . $filename . '"', + 'Cache-Control' => 'private, no-store', + ]); })->name('tenant.injector-protocols.pdf'); }); diff --git a/tests/Feature/CalendarEnhancementsTest.php b/tests/Feature/CalendarEnhancementsTest.php index 70630d9..1379b9e 100644 --- a/tests/Feature/CalendarEnhancementsTest.php +++ b/tests/Feature/CalendarEnhancementsTest.php @@ -168,10 +168,11 @@ class CalendarEnhancementsTest extends TestCase $response = $page->exportPdf(); - // It's a StreamedResponse - $this->assertInstanceOf(\Symfony\Component\HttpFoundation\StreamedResponse::class, $response); + // Inline PDF response (was StreamedResponse before we switched to preview-by-default). + $this->assertSame('application/pdf', $response->headers->get('content-type')); $cd = $response->headers->get('content-disposition'); + $this->assertStringStartsWith('inline;', $cd); $this->assertStringContainsString('programari_', $cd); - $this->assertStringEndsWith('.pdf', $cd); + $this->assertStringContainsString('.pdf', $cd); } }