c9d400fd6e
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>
295 lines
15 KiB
PHP
295 lines
15 KiB
PHP
<?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'),
|
||
];
|
||
}
|
||
}
|