Files
autocrm/app/Filament/Tenant/Resources/InjectorProtocolResource.php
T
Vasyka c9d400fd6e feat(pdf): preview in Filament modal (iframe) instead of new tab
All "Vizualizare PDF" actions now open a wide (7xl) Filament modal
containing an iframe pointing at the inline PDF endpoint, with a
top-right "Descarcă" and "Deschide în tab nou" fallback.

- WorkOrder edit action + WO table row action
- InjectorProtocol edit action + protocols table row action
- Dashboard Docs tab preview button (Alpine.js overlay since it's
  a plain Blade view, not a Filament action)

The underlying /pdf routes stay unchanged (Content-Disposition: inline,
?download=1 for attachment) — the iframe just loads the same URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-06 05:47:07 +00:00

295 lines
15 KiB
PHP
Raw 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.
<?php
namespace App\Filament\Tenant\Resources;
use App\Auth\Permissions;
use App\Filament\Tenant\Resources\InjectorProtocolResource\Pages;
use App\Models\Tenant\Client;
use App\Models\Tenant\InjectorProtocol;
use App\Models\Tenant\User;
use App\Models\Tenant\Vehicle;
use App\Models\Tenant\WorkOrder;
use Filament\Actions;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Schemas;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Table;
class InjectorProtocolResource extends Resource
{
protected static ?string $model = InjectorProtocol::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-beaker';
public static function getNavigationLabel(): string
{
return __('nav.label.Protocoale forsunki');
}
public static function getNavigationGroup(): ?string
{
return __('nav.group.Service');
}
protected static ?int $navigationSort = 27;
public static function getModelLabel(): string
{
return __('protocol forsunki');
}
public static function getPluralModelLabel(): string
{
return __('protocoale forsunki');
}
public static function canViewAny(): bool
{
return auth()->user()?->canDo(Permissions::INJECTOR_PROTOCOLS_VIEW) ?? false;
}
public static function canCreate(): bool
{
return auth()->user()?->canDo(Permissions::INJECTOR_PROTOCOLS_CONCLUDE) ?? false;
}
public static function canEdit($record): bool
{
return auth()->user()?->canDo(Permissions::INJECTOR_PROTOCOLS_CONCLUDE) ?? false;
}
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make(__('Date auto & client'))
->columns(3)
->schema([
Forms\Components\TextInput::make('protocol_number')
->label(__('Nr. protocol'))
->disabled()
->dehydrated(false)
->placeholder(__('Generat automat')),
Forms\Components\Select::make('source_type')
->label(__('Sursă'))
->options(\App\Support\I18n::opts(InjectorProtocol::SOURCE_TYPES))
->default('client')
->required(),
Forms\Components\TextInput::make('source_name')
->label(__('Autoservis / Client'))
->required()
->maxLength(160),
Forms\Components\Select::make('client_id')
->label(__('Client CRM'))
->options(fn () => Client::pluck('name', 'id'))
->searchable()
->live()
->afterStateUpdated(function ($state, Set $set) {
if ($state && $c = Client::find($state)) {
$set('source_name', $c->name);
$set('phone', $c->phone);
}
}),
Forms\Components\Select::make('vehicle_id')
->label(__('Auto CRM'))
->options(fn (Get $get) => $get('client_id')
? Vehicle::where('client_id', $get('client_id'))
->get()->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray()
: Vehicle::get()->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray())
->searchable()
->live()
->afterStateUpdated(function ($state, Set $set) {
if ($state && $v = Vehicle::find($state)) {
$set('car_model', trim(($v->make ?? '') . ' ' . ($v->model ?? '')));
$set('plate_number', $v->plate);
$set('vin', $v->vin);
$set('year', $v->year);
$set('mileage', $v->mileage);
}
}),
Forms\Components\Select::make('work_order_id')
->label(__('Fișă lucru (opțional)'))
->options(fn () => WorkOrder::orderBy('id', 'desc')->limit(50)
->get()->mapWithKeys(fn ($w) => [$w->id => "{$w->number}" . ($w->client?->name ?? '?')])->toArray())
->searchable(),
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->maxLength(40),
Forms\Components\TextInput::make('plate_number')->label(__('Nr. înmatriculare'))->maxLength(32),
Forms\Components\TextInput::make('vin')->label(__('VIN'))->maxLength(17),
Forms\Components\TextInput::make('car_model')->label(__('Model auto'))->maxLength(120),
Forms\Components\TextInput::make('year')->label(__('An'))->numeric()->minValue(1950)->maxValue(2100),
Forms\Components\TextInput::make('mileage')->label(__('Kilometraj'))->numeric()->suffix(__('km')),
Forms\Components\TextInput::make('injector_brand')->label(__('Marca injectoarelor'))->maxLength(60),
Forms\Components\TextInput::make('injector_count')->label(__('Nr. injectoare'))
->numeric()->minValue(1)->maxValue(8)->default(8)->required(),
]),
Schemas\Components\Section::make(__('Cauza adresării'))
->columns(3)
->schema([
Forms\Components\Checkbox::make('reason_new_injectors')->label(__('Injectoare noi')),
Forms\Components\Checkbox::make('reason_engine_overhaul')->label(__('Reparație capitală motor')),
Forms\Components\Checkbox::make('reason_check')->label(__('Verificare / diagnostic')),
]),
Schemas\Components\Section::make(__('Măsurători mecanica injectoarelor'))
->description(__('8 forsunki × 6 măsurători (Înainte / După). Introducerea se face pe rând.'))
->schema([
Forms\Components\Repeater::make('rows')
->label('')
->relationship('rows')
->schema([
Forms\Components\TextInput::make('position')
->label(__('№'))
->numeric()->minValue(1)->maxValue(8)->required()
->disabled()->dehydrated(true),
Forms\Components\TextInput::make('resistance_before')->label(__('Rezistență ÎNAINTE'))->numeric()->step(0.01)->suffix('Ω'),
Forms\Components\TextInput::make('resistance_after')->label(__('DUPĂ'))->numeric()->step(0.01)->suffix('Ω'),
Forms\Components\TextInput::make('open_time_before')->label(__('Deschidere ÎNAINTE'))->numeric()->step(0.01)->suffix('ms'),
Forms\Components\TextInput::make('open_time_after')->label(__('DUPĂ'))->numeric()->step(0.01)->suffix('ms'),
Forms\Components\TextInput::make('close_time_before')->label(__('Închidere ÎNAINTE'))->numeric()->step(0.01)->suffix('ms'),
Forms\Components\TextInput::make('close_time_after')->label(__('DUPĂ'))->numeric()->step(0.01)->suffix('ms'),
Forms\Components\TextInput::make('close_time_kz_before')->label(__('Închidere KZ ÎNAINTE'))->numeric()->step(0.01)->suffix('ms'),
Forms\Components\TextInput::make('close_time_kz_after')->label(__('DUPĂ'))->numeric()->step(0.01)->suffix('ms'),
Forms\Components\TextInput::make('flow_before')->label(__('Debit ÎNAINTE'))->numeric()->step(0.01)->suffix(__('ml')),
Forms\Components\TextInput::make('flow_after')->label(__('DUPĂ'))->numeric()->step(0.01)->suffix(__('ml')),
Forms\Components\TextInput::make('tightness_before')->label(__('Etanșeitate ÎNAINTE'))->maxLength(20),
Forms\Components\TextInput::make('tightness_after')->label(__('DUPĂ'))->maxLength(20),
])
->columns(2)
->itemLabel(fn (array $state) => __('Forsunka') . ' #' . ($state['position'] ?? '?'))
->addable(false)
->deletable(false)
->reorderable(false)
->collapsed(fn ($record) => (bool) $record),
]),
Schemas\Components\Section::make(__('Заключение'))
->columns(3)
->schema([
Forms\Components\Checkbox::make('conclusion_ok')
->label(__('Injectoarele în normă, apte pentru exploatare')),
Forms\Components\Checkbox::make('conclusion_needs_cleaning')
->label(__('Necesită curățare repetată')),
Forms\Components\Checkbox::make('conclusion_needs_replacement')
->label(__('Recomandată înlocuire')),
Forms\Components\Textarea::make('master_comment')
->label(__('Comentariu / recomandări maistru'))
->rows(3)
->columnSpanFull(),
Forms\Components\Select::make('master_id')
->label(__('Maistru'))
->options(fn () => User::whereIn('role', ['mechanic', 'admin', 'owner'])
->pluck('name', 'id'))
->searchable(),
Forms\Components\TextInput::make('master_name_signed')
->label(__('Semnat de (manual)'))
->maxLength(120)
->helperText(__('Doar dacă maistrul nu e în listă.')),
Forms\Components\DatePicker::make('issue_date')
->label(__('Data emiterii'))
->default(today()),
Forms\Components\Checkbox::make('client_acknowledged')
->label(__('Client informat / a luat cunoștință'))
->columnSpanFull(),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('protocol_number')
->label(__('Nr. protocol'))
->searchable()
->sortable()
->copyable()
->weight('bold'),
Tables\Columns\TextColumn::make('issue_date')
->label(__('Data'))
->date('d.m.Y')
->sortable()
->placeholder('—'),
Tables\Columns\TextColumn::make('source_name')
->label(__('Autoservis / Client'))
->searchable()
->wrap(),
Tables\Columns\TextColumn::make('plate_number')
->label(__('Nr. auto'))
->searchable()
->placeholder('—'),
Tables\Columns\TextColumn::make('injector_brand')
->label(__('Marca'))
->placeholder('—')
->toggleable(),
Tables\Columns\TextColumn::make('injector_count')
->label(__('Injectoare'))
->alignCenter(),
Tables\Columns\IconColumn::make('conclusion_ok')
->label(__('OK'))
->boolean()
->toggleable(),
Tables\Columns\IconColumn::make('conclusion_needs_cleaning')
->label(__('Curățare'))
->boolean()
->toggleable(),
Tables\Columns\IconColumn::make('conclusion_needs_replacement')
->label(__('Înlocuire'))
->boolean()
->toggleable(),
Tables\Columns\TextColumn::make('master.name')
->label(__('Maistru'))
->placeholder('—')
->toggleable(),
Tables\Columns\TextColumn::make('created_at')
->label(__('Creat'))
->dateTime('d.m.Y H:i')
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\SelectFilter::make('source_type')
->label(__('Sursă'))
->options(\App\Support\I18n::opts(InjectorProtocol::SOURCE_TYPES)),
Tables\Filters\Filter::make('needs_action')
->label(__('Necesită acțiune'))
->query(fn ($q) => $q->where(fn ($q) => $q
->where('conclusion_needs_cleaning', true)
->orWhere('conclusion_needs_replacement', true))),
])
->actions([
Actions\Action::make('pdf')
->label(__('PDF'))
->icon('heroicon-m-document-magnifying-glass')
->color('gray')
->modalHeading(fn (InjectorProtocol $r) => __('Protocol') . ' ' . $r->protocol_number)
->modalSubmitAction(false)
->modalCancelActionLabel(__('Închide'))
->modalWidth('7xl')
->modalContent(fn (InjectorProtocol $r) => view('filament.tenant.pdf-preview', [
'pdfUrl' => route('tenant.injector-protocols.pdf', ['record' => $r->id]),
'downloadUrl' => route('tenant.injector-protocols.pdf', ['record' => $r->id, 'download' => 1]),
])),
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading(__('Niciun protocol'))
->emptyStateDescription(__('Creează primul protocol de diagnostic forsunki. Numărul se generează automat (PS-{tenant}-{an}-{seq}).'))
->emptyStateIcon('heroicon-o-beaker')
->defaultSort('created_at', 'desc');
}
public static function getPages(): array
{
return [
'index' => Pages\ListInjectorProtocols::route('/'),
'create' => Pages\CreateInjectorProtocol::route('/create'),
'edit' => Pages\EditInjectorProtocol::route('/{record}/edit'),
];
}
}