Add multilingual support to labor categories (RO/RU/EN)
- Migration: adds name_ro, name_ru, name_en columns; copies name→name_ro; seeds RU and EN translations for all 11 default categories - LaborCategory: label() returns localized name, optionsForSelect() keyed by canonical name (stored in labors.category) with localized label - LaborCategoryResource: 3-column form with RO/RU/EN inputs; table shows all three columns side by side - LaborResource: category select and filter use optionsForSelect() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,13 +42,31 @@ class LaborCategoryResource extends Resource
|
|||||||
public static function form(Schema $schema): Schema
|
public static function form(Schema $schema): Schema
|
||||||
{
|
{
|
||||||
return $schema->components([
|
return $schema->components([
|
||||||
Forms\Components\TextInput::make('name')
|
Forms\Components\Section::make(__('Denumire'))
|
||||||
->label(__('Denumire'))
|
->columns(3)
|
||||||
->required()
|
->schema([
|
||||||
->maxLength(80)
|
Forms\Components\TextInput::make('name_ro')
|
||||||
->columnSpanFull(),
|
->label(__('Română (RO)'))
|
||||||
|
->required()
|
||||||
|
->maxLength(80)
|
||||||
|
->live(onBlur: true)
|
||||||
|
->afterStateUpdated(function ($state, callable $set, $get) {
|
||||||
|
// Auto-fill canonical name from name_ro
|
||||||
|
if (! $get('name')) {
|
||||||
|
$set('name', $state);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Forms\Components\TextInput::make('name_ru')
|
||||||
|
->label(__('Rusă (RU)'))
|
||||||
|
->maxLength(80),
|
||||||
|
Forms\Components\TextInput::make('name_en')
|
||||||
|
->label(__('Engleză (EN)'))
|
||||||
|
->maxLength(80),
|
||||||
|
]),
|
||||||
|
Forms\Components\Hidden::make('name')
|
||||||
|
->dehydrateStateUsing(fn ($state, $get) => $state ?: $get('name_ro')),
|
||||||
Forms\Components\TextInput::make('sort_order')
|
Forms\Components\TextInput::make('sort_order')
|
||||||
->label(__('Ordine'))
|
->label(__('Ordine afișare'))
|
||||||
->numeric()
|
->numeric()
|
||||||
->default(0),
|
->default(0),
|
||||||
]);
|
]);
|
||||||
@@ -63,15 +81,16 @@ class LaborCategoryResource extends Resource
|
|||||||
->sortable()
|
->sortable()
|
||||||
->alignRight()
|
->alignRight()
|
||||||
->width(50),
|
->width(50),
|
||||||
Tables\Columns\TextColumn::make('name')
|
Tables\Columns\TextColumn::make('name_ro')
|
||||||
->label(__('Categorie'))
|
->label('RO')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('updated_at')
|
Tables\Columns\TextColumn::make('name_ru')
|
||||||
->label(__('Modificat'))
|
->label('RU')
|
||||||
->dateTime('d.m.Y')
|
->placeholder('—'),
|
||||||
->sortable()
|
Tables\Columns\TextColumn::make('name_en')
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->label('EN')
|
||||||
|
->placeholder('—'),
|
||||||
])
|
])
|
||||||
->defaultSort('sort_order')
|
->defaultSort('sort_order')
|
||||||
->reorderable('sort_order')
|
->reorderable('sort_order')
|
||||||
@@ -80,7 +99,6 @@ class LaborCategoryResource extends Resource
|
|||||||
Actions\DeleteAction::make(),
|
Actions\DeleteAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([]);
|
->bulkActions([]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class LaborResource extends Resource
|
|||||||
->schema([
|
->schema([
|
||||||
Forms\Components\Select::make('category')
|
Forms\Components\Select::make('category')
|
||||||
->label(__('Categorie'))
|
->label(__('Categorie'))
|
||||||
->options(fn () => \App\Models\Tenant\LaborCategory::orderBy('sort_order')->orderBy('name')->pluck('name', 'name'))
|
->options(fn () => \App\Models\Tenant\LaborCategory::optionsForSelect())
|
||||||
->required()
|
->required()
|
||||||
->searchable(),
|
->searchable(),
|
||||||
Forms\Components\TextInput::make('code')->label(__('Cod'))->maxLength(32),
|
Forms\Components\TextInput::make('code')->label(__('Cod'))->maxLength(32),
|
||||||
@@ -100,7 +100,7 @@ class LaborResource extends Resource
|
|||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
Tables\Filters\SelectFilter::make('category')
|
Tables\Filters\SelectFilter::make('category')
|
||||||
->options(fn () => \App\Models\Tenant\LaborCategory::orderBy('sort_order')->orderBy('name')->pluck('name', 'name')),
|
->options(fn () => \App\Models\Tenant\LaborCategory::optionsForSelect()),
|
||||||
Tables\Filters\TernaryFilter::make('is_active')->label(__('Doar active')),
|
Tables\Filters\TernaryFilter::make('is_active')->label(__('Doar active')),
|
||||||
])
|
])
|
||||||
->actions([
|
->actions([
|
||||||
|
|||||||
@@ -9,7 +9,25 @@ class LaborCategory extends Model
|
|||||||
{
|
{
|
||||||
use BelongsToTenant;
|
use BelongsToTenant;
|
||||||
|
|
||||||
protected $fillable = ['company_id', 'name', 'sort_order'];
|
protected $fillable = ['company_id', 'name', 'name_ro', 'name_ru', 'name_en', 'sort_order'];
|
||||||
|
|
||||||
protected $casts = ['sort_order' => 'integer'];
|
protected $casts = ['sort_order' => 'integer'];
|
||||||
|
|
||||||
|
/** Localized display label — falls back through ru→en→name_ro→name. */
|
||||||
|
public function label(?string $locale = null): string
|
||||||
|
{
|
||||||
|
$locale = $locale ?: app()->getLocale();
|
||||||
|
return $this->{"name_{$locale}"}
|
||||||
|
?: $this->name_ro
|
||||||
|
?: $this->name
|
||||||
|
?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Options array keyed by canonical name (stored in labors.category). */
|
||||||
|
public static function optionsForSelect(): array
|
||||||
|
{
|
||||||
|
return static::orderBy('sort_order')->orderBy('name')->get()
|
||||||
|
->mapWithKeys(fn ($c) => [$c->name => $c->label()])
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('labor_categories', function (Blueprint $table) {
|
||||||
|
$table->string('name_ro')->nullable()->after('name');
|
||||||
|
$table->string('name_ru')->nullable()->after('name_ro');
|
||||||
|
$table->string('name_en')->nullable()->after('name_ru');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy existing name → name_ro
|
||||||
|
DB::table('labor_categories')->update(['name_ro' => DB::raw('`name`')]);
|
||||||
|
|
||||||
|
// Seed Russian and English for the default categories
|
||||||
|
$translations = [
|
||||||
|
'Motor' => ['ru' => 'Двигатель', 'en' => 'Engine'],
|
||||||
|
'Frâne' => ['ru' => 'Тормоза', 'en' => 'Brakes'],
|
||||||
|
'Suspensie' => ['ru' => 'Подвеска', 'en' => 'Suspension'],
|
||||||
|
'Anvelope' => ['ru' => 'Шины', 'en' => 'Tires'],
|
||||||
|
'ITP' => ['ru' => 'Техосмотр', 'en' => 'Inspection'],
|
||||||
|
'Cutie viteze' => ['ru' => 'Коробка передач', 'en' => 'Gearbox'],
|
||||||
|
'Caroserie' => ['ru' => 'Кузов', 'en' => 'Bodywork'],
|
||||||
|
'Electrică' => ['ru' => 'Электрика', 'en' => 'Electrical'],
|
||||||
|
'Climatizare' => ['ru' => 'Климат', 'en' => 'AC / Climate'],
|
||||||
|
'Eșapament' => ['ru' => 'Выхлоп', 'en' => 'Exhaust'],
|
||||||
|
'Altele' => ['ru' => 'Прочее', 'en' => 'Other'],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($translations as $name => $t) {
|
||||||
|
DB::table('labor_categories')
|
||||||
|
->where('name', $name)
|
||||||
|
->update(['name_ru' => $t['ru'], 'name_en' => $t['en']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('labor_categories', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['name_ro', 'name_ru', 'name_en']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user