improve WO form: inline client/vehicle create, mechanic filter, category translation, PDF emoji fix

- client_id field: switched to relationship() with createOptionForm so new clients
  can be created inline (name, phone, email); search also matches by phone
- vehicle_id field: added createOptionForm (make, model, plate, year, VIN); newly
  created vehicle is automatically linked to the currently selected client
- master_id field: now shows only active users with role mechanic/master
- WorksRelationManager: labor catalog select marked optional; added createOptionForm
  so new catalog entries can be added without leaving the WO form
- SubcontractJobsRelationManager: category table column now translates value via __()
  and shows explicit label(__('Categorie'))
- PDF work-order: replaced 📞 / ✉ / ⚠ emojis with plain-text equivalents so
  DomPDF/DejaVu Sans renders them correctly instead of showing "????"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 19:56:40 +00:00
parent 2ca6ab58a1
commit 3c7e5fffbf
4 changed files with 46 additions and 9 deletions
@@ -84,16 +84,39 @@ class WorkOrderResource extends Resource
Forms\Components\Select::make('urgency')->label(__('Urgență'))
->options(\App\Support\I18n::opts(\App\Models\Tenant\PricingCoefficient::URGENCY))->default('normal')->required(),
Forms\Components\Select::make('client_id')->label(__('Client'))
->options(fn () => Client::pluck('name', 'id'))
->searchable()->live()->required()->columnSpan(2),
->relationship('client', 'name')
->getSearchResultsUsing(fn (string $query) =>
Client::where(fn ($q) => $q
->where('name', 'like', "%{$query}%")
->orWhere('phone', 'like', "%{$query}%"))
->limit(30)->pluck('name', 'id'))
->searchable()->live()->required()->columnSpan(2)
->createOptionForm([
Forms\Components\TextInput::make('name')->label(__('Nume'))->required(),
Forms\Components\TextInput::make('phone')->label(__('Telefon')),
Forms\Components\TextInput::make('email')->label(__('Email'))->email(),
]),
Forms\Components\Select::make('vehicle_id')->label(__('Auto'))
->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()
: [])
->searchable()->columnSpan(2),
->searchable()->columnSpan(2)
->createOptionForm([
Forms\Components\TextInput::make('make')->label(__('Marcă'))->required(),
Forms\Components\TextInput::make('model')->label(__('Model'))->required(),
Forms\Components\TextInput::make('plate')->label(__('Nr. înmatriculare'))->required(),
Forms\Components\TextInput::make('year')->label(__('An'))->numeric(),
Forms\Components\TextInput::make('vin')->label(__('VIN')),
])
->createOptionUsing(function (array $data, \Livewire\Component $livewire) {
$data['client_id'] = data_get((array) $livewire->data, 'client_id');
return Vehicle::create($data)->id;
}),
Forms\Components\Select::make('master_id')->label(__('Maistru'))
->options(fn () => User::where('status', 'active')->pluck('name', 'id'))
->options(fn () => User::where('status', 'active')
->whereIn('role', ['mechanic', 'master'])
->pluck('name', 'id'))
->searchable()->columnSpan(2),
Forms\Components\TextInput::make('mileage_in')->label(__('Km intrare'))->numeric(),
Forms\Components\TextInput::make('mileage_out')->label(__('Km ieșire'))->numeric(),
@@ -51,7 +51,9 @@ class SubcontractJobsRelationManager extends RelationManager
->columns([
Tables\Columns\TextColumn::make('number')->label(__('Nr.')),
Tables\Columns\TextColumn::make('subcontractor.name')->label(__('Terț'))->placeholder('—'),
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('category')->label(__('Categorie'))
->formatStateUsing(fn ($s) => $s ? __($s) : '—')
->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('cost')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('client_price')->label(__('Preț client'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('margin')
@@ -28,7 +28,7 @@ class WorksRelationManager extends RelationManager
{
return $schema->components([
Forms\Components\Select::make('labor_id')
->label(__('Catalog manoperă'))
->label(__('Catalog manoperă') . ' (' . __('opțional') . ')')
->options(fn () => Labor::where('is_active', true)
->get()
->mapWithKeys(fn ($l) => [$l->id => '[' . __($l->category) . '] ' . $l->label() . ' (' . $l->hours . __('h') . ')'])
@@ -42,6 +42,18 @@ class WorksRelationManager extends RelationManager
$set('price_per_hour', $labor->hours > 0 ? round($labor->price / max((float) $labor->hours, 1), 2) : 0);
}
})
->createOptionForm([
Forms\Components\Select::make('category')->label(__('Categorie'))
->options(\App\Support\I18n::opts(array_combine(Labor::CATEGORIES, Labor::CATEGORIES)))
->required(),
Forms\Components\TextInput::make('name_ro')->label(__('Denumire'))->required(),
Forms\Components\TextInput::make('hours')->label(__('Ore normate'))->numeric()->default(1)->required(),
Forms\Components\TextInput::make('price')->label(__('Preț total (MDL)'))->numeric()->default(0)->required(),
])
->createOptionUsing(function (array $data) {
$data['is_active'] = true;
return Labor::create($data)->id;
})
->columnSpanFull(),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->columnSpanFull(),
Forms\Components\TextInput::make('hours')->label(__('Ore'))->numeric()->default(1)->required(),
+3 -3
View File
@@ -126,10 +126,10 @@
<div class="brand-sub">{{ $company->city }}</div>
@endif
@if ($company->phone)
<div class="brand-sub">📞 {{ $company->phone }}</div>
<div class="brand-sub">Tel.: {{ $company->phone }}</div>
@endif
@if ($company->email)
<div class="brand-sub">✉ {{ $company->email }}</div>
<div class="brand-sub">Email: {{ $company->email }}</div>
@endif
</div>
<div class="col col-half">
@@ -268,7 +268,7 @@
@if ($wo->recommendations)
<div class="recommendations">
<h3>{{ __('⚠ Recomandări') }}</h3>
<h3>{{ __('Recomandări') }}</h3>
<div>{{ $wo->recommendations }}</div>
</div>
@endif