feat(i18n): wrap 1103 hardcoded UI strings across Filament with __()

- Convert static $modelLabel/$pluralModelLabel/$title to getter methods
- Wrap ->label()/->placeholder()/->helperText()/->description()/->title()/->body() args
- Wrap Section::make()/Fieldset::make()/Notification::make()->title() args
- Fix RelationManagers::getTitle() signature to match parent (Model, string)
- Fix Pages::getTitle() to instance method (BasePage::getTitle is non-static)
- Extend lang/ru.json + lang/en.json with 700+ common terms; identity fallback for the rest
- Remove duplicate getters in 5 resources that had manual getModelLabel already

All 306 tests pass. Missing translations fall back to the RO key so the UI never breaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 05:04:24 +00:00
parent 78ff8d4b43
commit f7fc69077b
83 changed files with 4212 additions and 1251 deletions
@@ -48,8 +48,8 @@ class ActivityResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('created_at')->label('Când')->dateTime('d.m.Y H:i')->sortable(),
Tables\Columns\TextColumn::make('description')->label('Acțiune')->badge()
Tables\Columns\TextColumn::make('created_at')->label(__('Când'))->dateTime('d.m.Y H:i')->sortable(),
Tables\Columns\TextColumn::make('description')->label(__('Acțiune'))->badge()
->colors([
'success' => ['creat'],
'info' => ['modificat'],
@@ -57,12 +57,12 @@ class ActivityResource extends Resource
'warning' => ['restaurat'],
]),
Tables\Columns\TextColumn::make('subject_type')
->label('Tip')
->label(__('Tip'))
->formatStateUsing(fn ($s) => $s ? class_basename($s) : '—'),
Tables\Columns\TextColumn::make('subject_id')->label('ID')->placeholder('—'),
Tables\Columns\TextColumn::make('causer.name')->label('De către')->placeholder('Sistem'),
Tables\Columns\TextColumn::make('subject_id')->label(__('ID'))->placeholder('—'),
Tables\Columns\TextColumn::make('causer.name')->label(__('De către'))->placeholder(__('Sistem')),
Tables\Columns\TextColumn::make('attribute_changes')
->label('Detalii')
->label(__('Detalii'))
->formatStateUsing(function ($state) {
if (! $state) return '—';
$arr = is_string($state) ? json_decode($state, true) : $state;
@@ -75,10 +75,10 @@ class ActivityResource extends Resource
])
->filters([
Tables\Filters\SelectFilter::make('description')
->label('Acțiune')
->label(__('Acțiune'))
->options(['creat' => 'creat', 'modificat' => 'modificat', 'șters' => 'șters']),
Tables\Filters\Filter::make('today')
->label('Astăzi')
->label(__('Astăzi'))
->query(fn ($q) => $q->whereDate('created_at', today())),
])
->defaultSort('created_at', 'desc')
@@ -34,25 +34,30 @@ class AppointmentResource extends Resource
protected static ?string $modelLabel = 'programare';
protected static ?string $pluralModelLabel = 'programări';
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('programări');
}
protected static ?int $navigationSort = 7;
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Când & unde')
Schemas\Components\Section::make(__('Când & unde'))
->columns(3)
->schema([
Forms\Components\DatePicker::make('date')->label('Data')->default(today())->required(),
Forms\Components\TimePicker::make('time_start')->label('De la')->required()->seconds(false),
Forms\Components\TimePicker::make('time_end')->label('Până la')->required()->seconds(false),
Forms\Components\DatePicker::make('date')->label(__('Data'))->default(today())->required(),
Forms\Components\TimePicker::make('time_start')->label(__('De la'))->required()->seconds(false),
Forms\Components\TimePicker::make('time_end')->label(__('Până la'))->required()->seconds(false),
Forms\Components\Select::make('post_id')
->label('Pod')
->label(__('Pod'))
->options(fn () => Post::where('is_active', true)->orderBy('sort_order')->pluck('name', 'id'))
->searchable(),
Forms\Components\Select::make('master_id')
->label('Maistru / Mecanic')
->label(__('Maistru / Mecanic'))
->options(fn () => User::pluck('name', 'id'))
->searchable(),
Forms\Components\Select::make('status')
@@ -60,23 +65,23 @@ class AppointmentResource extends Resource
->default('scheduled')
->required(),
]),
Schemas\Components\Section::make('Client & Auto')
Schemas\Components\Section::make(__('Client & Auto'))
->columns(2)
->schema([
Forms\Components\Select::make('client_id')
->label('Client')
->label(__('Client'))
->options(fn () => Client::pluck('name', 'id'))
->searchable()
->live(),
Forms\Components\Select::make('vehicle_id')
->label('Auto')
->label(__('Auto'))
->options(fn (Schemas\Components\Utilities\Get $get) => $get('client_id')
? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id')
: [])
->searchable(),
]),
Forms\Components\TextInput::make('title')->label('Subiect')->required()->maxLength(160),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
Forms\Components\TextInput::make('title')->label(__('Subiect'))->required()->maxLength(160),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]);
}
@@ -84,14 +89,14 @@ class AppointmentResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('date')->label('Data')->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('time_start')->label('De la')->time('H:i'),
Tables\Columns\TextColumn::make('time_end')->label('Până la')->time('H:i'),
Tables\Columns\TextColumn::make('post.name')->label('Pod')->placeholder('—'),
Tables\Columns\TextColumn::make('title')->label('Subiect')->searchable()->limit(40),
Tables\Columns\TextColumn::make('client.name')->label('Client')->placeholder('—'),
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'),
Tables\Columns\TextColumn::make('master.name')->label('Maistru')->placeholder('—'),
Tables\Columns\TextColumn::make('date')->label(__('Data'))->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('time_start')->label(__('De la'))->time('H:i'),
Tables\Columns\TextColumn::make('time_end')->label(__('Până la'))->time('H:i'),
Tables\Columns\TextColumn::make('post.name')->label(__('Pod'))->placeholder('—'),
Tables\Columns\TextColumn::make('title')->label(__('Subiect'))->searchable()->limit(40),
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->placeholder('—'),
Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($state) => Appointment::STATUSES[$state] ?? $state)
->badge()
@@ -104,14 +109,14 @@ class AppointmentResource extends Resource
])
->filters([
Tables\Filters\Filter::make('today')
->label('Astăzi')
->label(__('Astăzi'))
->query(fn ($q) => $q->whereDate('date', today())),
Tables\Filters\Filter::make('upcoming')
->label('Viitoare')
->label(__('Viitoare'))
->query(fn ($q) => $q->where('date', '>=', today())),
Tables\Filters\SelectFilter::make('status')->options(Appointment::STATUSES),
Tables\Filters\SelectFilter::make('post_id')
->label('Pod')
->label(__('Pod'))
->options(fn () => Post::pluck('name', 'id')),
])
->actions([
@@ -32,10 +32,6 @@ class BodyshopJobResource extends Resource
return __('nav.group.Tinichigerie');
}
protected static ?string $modelLabel = 'lucrare caroserie';
protected static ?string $pluralModelLabel = 'lucrări caroserie';
protected static ?int $navigationSort = 80;
public static function getNavigationBadge(): ?string
@@ -48,50 +44,50 @@ class BodyshopJobResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Lucrare')
Schemas\Components\Section::make(__('Lucrare'))
->columns(3)
->schema([
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('Generat automat'),
Forms\Components\Select::make('type')->label('Tip')->options(BodyshopJob::TYPES)->default('body_repair')->required(),
Forms\Components\Select::make('status')->label('Status')->options(BodyshopJob::STATUSES)->default('estimate')->required(),
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false)->placeholder(__('Generat automat')),
Forms\Components\Select::make('type')->label(__('Tip'))->options(BodyshopJob::TYPES)->default('body_repair')->required(),
Forms\Components\Select::make('status')->label(__('Status'))->options(BodyshopJob::STATUSES)->default('estimate')->required(),
Forms\Components\Select::make('client_id')
->label('Client')
->label(__('Client'))
->options(fn () => Client::pluck('name', 'id'))
->searchable()->live(),
Forms\Components\Select::make('vehicle_id')
->label('Auto')
->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(),
Forms\Components\TextInput::make('estimate_amount')->label('Deviz')->numeric()->default(0),
Forms\Components\TextInput::make('approved_amount')->label('Aprobat')->numeric()->default(0),
Forms\Components\TextInput::make('estimate_amount')->label(__('Deviz'))->numeric()->default(0),
Forms\Components\TextInput::make('approved_amount')->label(__('Aprobat'))->numeric()->default(0),
]),
Schemas\Components\Section::make('Asigurare')
Schemas\Components\Section::make(__('Asigurare'))
->collapsible()
->columns(3)
->schema([
Forms\Components\Toggle::make('is_insurance')->label('Caz de asigurare')->live()->columnSpanFull(),
Forms\Components\TextInput::make('insurer')->label('Asigurător')
Forms\Components\Toggle::make('is_insurance')->label(__('Caz de asigurare'))->live()->columnSpanFull(),
Forms\Components\TextInput::make('insurer')->label(__('Asigurător'))
->visible(fn (Get $get) => $get('is_insurance')),
Forms\Components\TextInput::make('policy_no')->label('Nr. poliță')
Forms\Components\TextInput::make('policy_no')->label(__('Nr. poliță'))
->visible(fn (Get $get) => $get('is_insurance')),
Forms\Components\TextInput::make('claim_no')->label('Nr. dosar daună')
Forms\Components\TextInput::make('claim_no')->label(__('Nr. dosar daună'))
->visible(fn (Get $get) => $get('is_insurance')),
Forms\Components\Select::make('insurance_status')->label('Status dosar')
Forms\Components\Select::make('insurance_status')->label(__('Status dosar'))
->options(BodyshopJob::INSURANCE_STATUSES)
->visible(fn (Get $get) => $get('is_insurance')),
]),
Schemas\Components\Section::make('Foto înainte / după')
Schemas\Components\Section::make(__('Foto înainte / după'))
->columns(2)
->schema([
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos_before')
->label('Înainte')->collection('photos_before')->multiple()->image()->reorderable()->maxFiles(20),
->label(__('Înainte'))->collection('photos_before')->multiple()->image()->reorderable()->maxFiles(20),
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos_after')
->label('După')->collection('photos_after')->multiple()->image()->reorderable()->maxFiles(20),
->label(__('După'))->collection('photos_after')->multiple()->image()->reorderable()->maxFiles(20),
]),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]);
}
@@ -99,15 +95,15 @@ class BodyshopJobResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable()->placeholder('—'),
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'),
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable()->placeholder('—'),
Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
Tables\Columns\TextColumn::make('type')
->formatStateUsing(fn ($s) => BodyshopJob::TYPES[$s] ?? $s)
->badge()->color('info'),
Tables\Columns\IconColumn::make('is_insurance')->label('Asig.')->boolean()->toggleable(),
Tables\Columns\TextColumn::make('damage_points_count')->counts('damagePoints')->label('Daune')->alignRight(),
Tables\Columns\TextColumn::make('approved_amount')->label('Aprobat')->money('MDL')->alignRight(),
Tables\Columns\IconColumn::make('is_insurance')->label(__('Asig.'))->boolean()->toggleable(),
Tables\Columns\TextColumn::make('damage_points_count')->counts('damagePoints')->label(__('Daune'))->alignRight(),
Tables\Columns\TextColumn::make('approved_amount')->label(__('Aprobat'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => BodyshopJob::STATUSES[$s] ?? $s)
->badge()
@@ -121,14 +117,14 @@ class BodyshopJobResource extends Resource
->filters([
Tables\Filters\SelectFilter::make('type')->options(BodyshopJob::TYPES),
Tables\Filters\SelectFilter::make('status')->options(BodyshopJob::STATUSES),
Tables\Filters\TernaryFilter::make('is_insurance')->label('Caz asigurare'),
Tables\Filters\TernaryFilter::make('is_insurance')->label(__('Caz asigurare')),
])
->actions([
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Nicio lucrare de caroserie')
->emptyStateDescription('Înregistrează lucrări de tinichigerie, vopsitorie, PDR, detailing, ceramică, PPF sau polish. Hartă daune, dosar asigurare și arhivă foto înainte/după.')
->emptyStateHeading(__('Nicio lucrare de caroserie'))
->emptyStateDescription(__('Înregistrează lucrări de tinichigerie, vopsitorie, PDR, detailing, ceramică, PPF sau polish. Hartă daune, dosar asigurare și arhivă foto înainte/după.'))
->emptyStateIcon('heroicon-o-paint-brush')
->defaultSort('created_at', 'desc');
}
@@ -14,26 +14,31 @@ class DamagePointsRelationManager extends RelationManager
{
protected static string $relationship = 'damagePoints';
protected static ?string $title = 'Hartă daune';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Hartă daune');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\Select::make('zone')
->label('Zonă')
->label(__('Zonă'))
->options(array_combine(DamagePoint::ZONES, DamagePoint::ZONES))
->searchable()
->required(),
Forms\Components\Select::make('kind')
->label('Tip daună')
->label(__('Tip daună'))
->options(array_combine(DamagePoint::KINDS, DamagePoint::KINDS))
->required(),
Forms\Components\Select::make('severity')
->label('Gravitate')
->label(__('Gravitate'))
->options(DamagePoint::SEVERITIES)
->default('minor')
->required(),
Forms\Components\Textarea::make('notes')->label('Observații')->rows(2)->columnSpanFull(),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->rows(2)->columnSpanFull(),
]);
}
@@ -42,10 +47,10 @@ class DamagePointsRelationManager extends RelationManager
return $table
->recordTitleAttribute('zone')
->columns([
Tables\Columns\TextColumn::make('zone')->label('Zonă')->badge()->color('gray'),
Tables\Columns\TextColumn::make('kind')->label('Tip'),
Tables\Columns\TextColumn::make('zone')->label(__('Zonă'))->badge()->color('gray'),
Tables\Columns\TextColumn::make('kind')->label(__('Tip')),
Tables\Columns\TextColumn::make('severity')
->label('Gravitate')
->label(__('Gravitate'))
->formatStateUsing(fn ($s) => DamagePoint::SEVERITIES[$s] ?? $s)
->badge()
->colors(['gray' => ['minor'], 'warning' => ['medium'], 'danger' => ['severe']]),
@@ -53,7 +58,7 @@ class DamagePointsRelationManager extends RelationManager
])
->headerActions([Actions\CreateAction::make()])
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
->emptyStateHeading('Nicio daună marcată')
->emptyStateDescription('Adaugă punctele de daună pe zone (capotă, ușă, aripă) cu tip și gravitate — formează harta de daune a mașinii.');
->emptyStateHeading(__('Nicio daună marcată'))
->emptyStateDescription(__('Adaugă punctele de daună pe zone (capotă, ușă, aripă) cu tip și gravitate — formează harta de daune a mașinii.'));
}
}
+11 -11
View File
@@ -38,26 +38,26 @@ class CallResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Apel')
Schemas\Components\Section::make(__('Apel'))
->columns(2)
->schema([
Forms\Components\DateTimePicker::make('called_at')->label('Data & ora')->default(now())->required(),
Forms\Components\DateTimePicker::make('called_at')->label(__('Data & ora'))->default(now())->required(),
Forms\Components\Select::make('direction')
->options(Call::DIRECTIONS)
->default('incoming')
->required(),
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->required()->maxLength(40),
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->required()->maxLength(40),
Forms\Components\Select::make('status')
->options(Call::STATUSES)
->default('answered')
->required(),
Forms\Components\TextInput::make('duration_sec')->label('Durată (sec)')->numeric()->default(0),
Forms\Components\TextInput::make('duration_sec')->label(__('Durată (sec)'))->numeric()->default(0),
Forms\Components\Select::make('client_id')
->label('Client')
->label(__('Client'))
->options(fn () => Client::pluck('name', 'id'))
->searchable(),
]),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]);
}
@@ -65,7 +65,7 @@ class CallResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('called_at')->label('Data')->dateTime('d.m.Y H:i')->sortable(),
Tables\Columns\TextColumn::make('called_at')->label(__('Data'))->dateTime('d.m.Y H:i')->sortable(),
Tables\Columns\TextColumn::make('direction')
->formatStateUsing(fn ($s) => Call::DIRECTIONS[$s] ?? $s)
->badge()
@@ -75,8 +75,8 @@ class CallResource extends Resource
'danger' => ['missed'],
]),
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(),
Tables\Columns\TextColumn::make('client.name')->label('Client')->placeholder('—'),
Tables\Columns\TextColumn::make('duration_formatted')->label('Durată')->state(fn (Call $r) => $r->duration_formatted),
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->placeholder('—'),
Tables\Columns\TextColumn::make('duration_formatted')->label(__('Durată'))->state(fn (Call $r) => $r->duration_formatted),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => Call::STATUSES[$s] ?? $s)
->badge(),
@@ -84,10 +84,10 @@ class CallResource extends Resource
->filters([
Tables\Filters\SelectFilter::make('direction')->options(Call::DIRECTIONS),
Tables\Filters\Filter::make('today')
->label('Astăzi')
->label(__('Astăzi'))
->query(fn ($q) => $q->whereDate('called_at', today())),
Tables\Filters\Filter::make('missed')
->label('Pierdute')
->label(__('Pierdute'))
->query(fn ($q) => $q->where('direction', 'missed')->orWhere('status', 'missed')),
])
->actions([
@@ -25,7 +25,12 @@ class ClientResource extends Resource
protected static ?string $modelLabel = 'client';
protected static ?string $pluralModelLabel = 'clienți';
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('clienți');
}
protected static ?int $navigationSort = 10;
@@ -47,18 +52,18 @@ class ClientResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Date generale')
Schemas\Components\Section::make(__('Date generale'))
->columns(2)
->schema([
Forms\Components\Select::make('type')
->label('Tip')
->label(__('Tip'))
->options(['individual' => 'Persoană fizică', 'company' => 'Persoană juridică'])
->default('individual')
->required()
->live(),
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(120),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(120),
Forms\Components\TextInput::make('company_name')
->label('Denumire companie')
->label(__('Denumire companie'))
->visible(fn (Schemas\Components\Utilities\Get $get) => $get('type') === 'company')
->maxLength(160),
Forms\Components\Select::make('status')
@@ -69,40 +74,40 @@ class ClientResource extends Resource
->default('active')
->required(),
Forms\Components\Toggle::make('is_vip')
->label('Client VIP')
->helperText('Activează coeficienții de preț VIP pe fișele acestui client.'),
->label(__('Client VIP'))
->helperText(__('Activează coeficienții de preț VIP pe fișele acestui client.')),
]),
Schemas\Components\Section::make('Contacte')
Schemas\Components\Section::make(__('Contacte'))
->columns(2)
->schema([
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->required()->maxLength(40),
Forms\Components\TextInput::make('phone_alt')->label('Telefon alternativ')->tel()->maxLength(40),
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->required()->maxLength(40),
Forms\Components\TextInput::make('phone_alt')->label(__('Telefon alternativ'))->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->email()->maxLength(120),
Forms\Components\TextInput::make('telegram')->maxLength(60),
Forms\Components\TextInput::make('telegram_chat_id')
->label('Telegram chat ID')
->label(__('Telegram chat ID'))
->disabled()
->dehydrated(false)
->placeholder('Se completează automat când clientul scrie la bot')
->placeholder(__('Se completează automat când clientul scrie la bot'))
->helperText(fn ($record) => $record?->telegram_chat_id
? '✅ Telegram legat — notificările vor merge prin bot'
: null),
Forms\Components\TextInput::make('whatsapp')->maxLength(60),
Forms\Components\TextInput::make('viber')->maxLength(60),
]),
Schemas\Components\Section::make('Marketing')
Schemas\Components\Section::make(__('Marketing'))
->columns(2)
->schema([
Forms\Components\TextInput::make('source')->label('Sursă')->maxLength(60),
Forms\Components\TextInput::make('marketing_channel')->label('Canal marketing')->maxLength(60),
Forms\Components\TextInput::make('source')->label(__('Sursă'))->maxLength(60),
Forms\Components\TextInput::make('marketing_channel')->label(__('Canal marketing'))->maxLength(60),
]),
Schemas\Components\Section::make('Financiar')
Schemas\Components\Section::make(__('Financiar'))
->columns(2)
->schema([
Forms\Components\TextInput::make('balance')->label('Sold')->numeric()->default(0),
Forms\Components\TextInput::make('discount_pct')->label('Discount %')->numeric()->default(0),
Forms\Components\TextInput::make('balance')->label(__('Sold'))->numeric()->default(0),
Forms\Components\TextInput::make('discount_pct')->label(__('Discount %'))->numeric()->default(0),
]),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(3),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(3),
]);
}
@@ -113,7 +118,7 @@ class ClientResource extends Resource
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('phone')->searchable()->copyable(),
Tables\Columns\TextColumn::make('email')->searchable()->toggleable(),
Tables\Columns\TextColumn::make('vehicles_count')->counts('vehicles')->label('Mașini'),
Tables\Columns\TextColumn::make('vehicles_count')->counts('vehicles')->label(__('Mașini')),
Tables\Columns\TextColumn::make('status')
->badge()
->colors([
@@ -136,8 +141,8 @@ class ClientResource extends Resource
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Niciun client încă')
->emptyStateDescription('Adaugă primul tău client manual sau importă din CSV. Toate mașinile, fișele și plățile se vor lega automat de el.')
->emptyStateHeading(__('Niciun client încă'))
->emptyStateDescription(__('Adaugă primul tău client manual sau importă din CSV. Toate mașinile, fișele și plățile se vor lega automat de el.'))
->emptyStateIcon('heroicon-o-users')
->defaultSort('created_at', 'desc');
}
@@ -17,16 +17,16 @@ class ListClients extends ListRecords
{
return [
Actions\Action::make('export')
->label('Export CSV')
->label(__('Export CSV'))
->icon('heroicon-m-arrow-down-tray')
->color('gray')
->action(fn () => app(CsvImportExport::class)->exportClients()),
Actions\Action::make('import')
->label('Import CSV')
->label(__('Import CSV'))
->icon('heroicon-m-arrow-up-tray')
->color('gray')
->modalHeading('Import clienți din CSV')
->modalDescription('CSV cu header: ' . implode(', ', CsvImportExport::CLIENT_COLUMNS) . '. Deduplicare după telefon.')
->modalHeading(__('Import clienți din CSV'))
->modalDescription(__('CSV cu header: ' . implode(', ', CsvImportExport::CLIENT_COLUMNS) . '. Deduplicare după telefon.'))
->schema([
Forms\Components\FileUpload::make('file')
->required()
+13 -13
View File
@@ -46,22 +46,22 @@ class DealResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Detalii')
Schemas\Components\Section::make(__('Detalii'))
->columns(2)
->schema([
Forms\Components\Select::make('client_id')
->label('Client')
->label(__('Client'))
->options(fn () => Client::pluck('name', 'id'))
->searchable()
->required(),
Forms\Components\Select::make('vehicle_id')
->label('Auto')
->label(__('Auto'))
->options(fn (Schemas\Components\Utilities\Get $get) => $get('client_id')
? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id')
: [])
->searchable(),
Forms\Components\TextInput::make('name')->label('Subiect')->required()->maxLength(160),
Forms\Components\TextInput::make('price')->label('Valoare')->numeric()->default(0),
Forms\Components\TextInput::make('name')->label(__('Subiect'))->required()->maxLength(160),
Forms\Components\TextInput::make('price')->label(__('Valoare'))->numeric()->default(0),
Forms\Components\Select::make('stage')
->options(Deal::STAGES)
->default('new')
@@ -70,11 +70,11 @@ class DealResource extends Resource
->options(Lead::SOURCES)
->searchable(),
Forms\Components\Select::make('assigned_to')
->label('Responsabil')
->label(__('Responsabil'))
->options(fn () => User::pluck('name', 'id'))
->searchable(),
]),
Forms\Components\Textarea::make('note')->label('Notițe')->columnSpanFull()->rows(3),
Forms\Components\Textarea::make('note')->label(__('Notițe'))->columnSpanFull()->rows(3),
]);
}
@@ -83,9 +83,9 @@ class DealResource extends Resource
return $table
->columns([
Tables\Columns\TextColumn::make('id')->label('#')->sortable(),
Tables\Columns\TextColumn::make('name')->label('Subiect')->searchable()->limit(40),
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable(),
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'),
Tables\Columns\TextColumn::make('name')->label(__('Subiect'))->searchable()->limit(40),
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
Tables\Columns\TextColumn::make('stage')
->formatStateUsing(fn ($state) => Deal::STAGES[$state] ?? $state)
->badge()
@@ -97,14 +97,14 @@ class DealResource extends Resource
'danger' => ['lost'],
]),
Tables\Columns\TextColumn::make('price')->money('MDL')->sortable(),
Tables\Columns\TextColumn::make('source')->label('Sursă')->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->placeholder('—'),
Tables\Columns\TextColumn::make('assignedTo.name')->label('Responsabil')->placeholder('—'),
Tables\Columns\TextColumn::make('source')->label(__('Sursă'))->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->placeholder('—'),
Tables\Columns\TextColumn::make('assignedTo.name')->label(__('Responsabil'))->placeholder('—'),
Tables\Columns\TextColumn::make('created_at')->date()->sortable(),
])
->filters([
Tables\Filters\SelectFilter::make('stage')->options(Deal::STAGES),
Tables\Filters\SelectFilter::make('assigned_to')
->label('Responsabil')
->label(__('Responsabil'))
->options(fn () => User::pluck('name', 'id')),
])
->actions([
@@ -31,41 +31,46 @@ class EmployeeProfileResource extends Resource
protected static ?string $modelLabel = 'angajat';
protected static ?string $pluralModelLabel = 'angajați';
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('angajați');
}
protected static ?int $navigationSort = 52;
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Profil')
Schemas\Components\Section::make(__('Profil'))
->columns(2)
->schema([
Forms\Components\Select::make('user_id')
->label('Utilizator')
->label(__('Utilizator'))
->options(fn () => User::orderBy('name')->pluck('name', 'id'))
->searchable()
->required()
->unique(ignoreRecord: true),
Forms\Components\TextInput::make('position')->label('Funcție')->maxLength(120),
Forms\Components\DatePicker::make('hire_date')->label('Angajat din'),
Forms\Components\TextInput::make('position')->label(__('Funcție'))->maxLength(120),
Forms\Components\DatePicker::make('hire_date')->label(__('Angajat din')),
]),
Schemas\Components\Section::make('Salariu & comisioane')
Schemas\Components\Section::make(__('Salariu & comisioane'))
->columns(3)
->schema([
Forms\Components\TextInput::make('base_salary')->label('Salariu bază')->numeric()->default(0),
Forms\Components\TextInput::make('base_salary')->label(__('Salariu bază'))->numeric()->default(0),
Forms\Components\TextInput::make('works_pct')
->label('% manopere')
->label(__('% manopere'))
->numeric()->default(0)
->suffix('%')
->helperText('% din venitul manoperelor finalizate.'),
->helperText(__('% din venitul manoperelor finalizate.')),
Forms\Components\TextInput::make('parts_pct')
->label('% marja piese')
->label(__('% marja piese'))
->numeric()->default(0)
->suffix('%')
->helperText('% din marja (sell-buy) pieselor montate.'),
->helperText(__('% din marja (sell-buy) pieselor montate.')),
]),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]);
}
@@ -73,12 +78,12 @@ class EmployeeProfileResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('user.name')->label('Nume')->searchable()->sortable(),
Tables\Columns\TextColumn::make('position')->label('Funcție')->placeholder('—'),
Tables\Columns\TextColumn::make('user.name')->label(__('Nume'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('position')->label(__('Funcție'))->placeholder('—'),
Tables\Columns\TextColumn::make('base_salary')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('works_pct')->label('% Manopere')
Tables\Columns\TextColumn::make('works_pct')->label(__('% Manopere'))
->formatStateUsing(fn ($s) => $s . '%')->alignRight(),
Tables\Columns\TextColumn::make('parts_pct')->label('% Piese')
Tables\Columns\TextColumn::make('parts_pct')->label(__('% Piese'))
->formatStateUsing(fn ($s) => $s . '%')->alignRight(),
Tables\Columns\TextColumn::make('hire_date')->date('d.m.Y')->placeholder('—'),
])
@@ -29,7 +29,12 @@ class ExpenseResource extends Resource
return __('nav.group.Finanțe');
}
protected static ?string $modelLabel = 'cheltuială';
protected static ?string $modelLabel = null;
public static function getModelLabel(): string
{
return __('cheltuială');
}
protected static ?string $pluralModelLabel = 'cheltuieli';
@@ -48,27 +53,27 @@ class ExpenseResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Cheltuială')
Schemas\Components\Section::make(__('Cheltuială'))
->columns(2)
->schema([
Forms\Components\DatePicker::make('paid_at')->label('Data')->default(today())->required(),
Forms\Components\DatePicker::make('paid_at')->label(__('Data'))->default(today())->required(),
Forms\Components\Select::make('category')
->options(Expense::CATEGORIES)
->default('other')
->required(),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->maxLength(160)->columnSpanFull(),
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(),
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->maxLength(160)->columnSpanFull(),
Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
Forms\Components\Select::make('method')
->options(Expense::METHODS)
->default('cash')
->required(),
Forms\Components\Select::make('supplier_id')
->label('Furnizor (opțional)')
->label(__('Furnizor (opțional)'))
->options(fn () => Supplier::pluck('name', 'id'))
->searchable(),
Forms\Components\TextInput::make('reference')->label('Ref.')->maxLength(64),
Forms\Components\TextInput::make('reference')->label(__('Ref.'))->maxLength(64),
]),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]);
}
@@ -76,22 +81,22 @@ class ExpenseResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('paid_at')->label('Data')->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('paid_at')->label(__('Data'))->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('category')
->formatStateUsing(fn ($s) => Expense::CATEGORIES[$s] ?? $s)
->badge(),
Tables\Columns\TextColumn::make('name')->searchable()->wrap(),
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->placeholder('—')->toggleable(),
Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->placeholder('—')->toggleable(),
Tables\Columns\TextColumn::make('method')
->formatStateUsing(fn ($s) => Expense::METHODS[$s] ?? $s),
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable()
->color('danger')
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label('Total')),
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label(__('Total'))),
])
->filters([
Tables\Filters\SelectFilter::make('category')->options(Expense::CATEGORIES),
Tables\Filters\Filter::make('this_month')
->label('Luna curentă')
->label(__('Luna curentă'))
->query(fn ($q) => $q->whereMonth('paid_at', now()->month)->whereYear('paid_at', now()->year)),
])
->actions([
+25 -20
View File
@@ -29,7 +29,12 @@ class LaborResource extends Resource
return __('nav.group.Service');
}
protected static ?string $modelLabel = 'normă';
protected static ?string $modelLabel = null;
public static function getModelLabel(): string
{
return __('normă');
}
protected static ?string $pluralModelLabel = 'norme-ore';
@@ -38,31 +43,31 @@ class LaborResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Manoperă')
Schemas\Components\Section::make(__('Manoperă'))
->columns(2)
->schema([
Forms\Components\Select::make('category')
->label('Categorie')
->label(__('Categorie'))
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES))
->required()
->searchable(),
Forms\Components\TextInput::make('code')->label('Cod')->maxLength(32),
Forms\Components\TextInput::make('name_ro')->label('Nume (RO)')->required()->maxLength(160),
Forms\Components\TextInput::make('name_ru')->label('Nume (RU)')->maxLength(160),
Forms\Components\TextInput::make('code')->label(__('Cod'))->maxLength(32),
Forms\Components\TextInput::make('name_ro')->label(__('Nume (RO)'))->required()->maxLength(160),
Forms\Components\TextInput::make('name_ru')->label(__('Nume (RU)'))->maxLength(160),
Forms\Components\Select::make('pricing_mode')
->label('Mod tarifare')
->label(__('Mod tarifare'))
->options(Labor::PRICING_MODES)
->default('hourly')
->live()
->required(),
Forms\Components\TextInput::make('hours')->label('Ore (normă)')->numeric()->default(1)
Forms\Components\TextInput::make('hours')->label(__('Ore (normă)'))->numeric()->default(1)
->visible(fn (Schemas\Components\Utilities\Get $get) => $get('pricing_mode') !== 'fixed'),
Forms\Components\TextInput::make('fixed_price')->label('Preț fix (MDL)')->numeric()->default(0)
Forms\Components\TextInput::make('fixed_price')->label(__('Preț fix (MDL)'))->numeric()->default(0)
->visible(fn (Schemas\Components\Utilities\Get $get) => $get('pricing_mode') === 'fixed'),
Forms\Components\TextInput::make('price')->label('Preț orientativ (MDL)')->numeric()->default(0),
Forms\Components\Toggle::make('is_active')->label('Activă')->default(true),
Forms\Components\TextInput::make('price')->label(__('Preț orientativ (MDL)'))->numeric()->default(0),
Forms\Components\Toggle::make('is_active')->label(__('Activă'))->default(true),
]),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]);
}
@@ -70,23 +75,23 @@ class LaborResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('category')->label('Categorie')->badge()->sortable(),
Tables\Columns\TextColumn::make('name_ro')->label('Manoperă')->searchable()->sortable(),
Tables\Columns\TextColumn::make('category')->label(__('Categorie'))->badge()->sortable(),
Tables\Columns\TextColumn::make('name_ro')->label(__('Manoperă'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('pricing_mode')
->label('Tarifare')
->label(__('Tarifare'))
->formatStateUsing(fn ($s) => $s === 'fixed' ? 'Fix' : 'Pe oră')
->badge()
->color(fn ($s) => $s === 'fixed' ? 'info' : 'gray'),
Tables\Columns\TextColumn::make('hours')->label('Ore')->numeric(decimalPlaces: 2)->alignRight(),
Tables\Columns\TextColumn::make('fixed_price')->label('Preț fix')->money('MDL')->alignRight()
Tables\Columns\TextColumn::make('hours')->label(__('Ore'))->numeric(decimalPlaces: 2)->alignRight(),
Tables\Columns\TextColumn::make('fixed_price')->label(__('Preț fix'))->money('MDL')->alignRight()
->placeholder('—')->toggleable(),
Tables\Columns\TextColumn::make('laborParts_count')->counts('laborParts')->label('Piese impl.')->alignRight()->toggleable(),
Tables\Columns\IconColumn::make('is_active')->label('Activă')->boolean(),
Tables\Columns\TextColumn::make('laborParts_count')->counts('laborParts')->label(__('Piese impl.'))->alignRight()->toggleable(),
Tables\Columns\IconColumn::make('is_active')->label(__('Activă'))->boolean(),
])
->filters([
Tables\Filters\SelectFilter::make('category')
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES)),
Tables\Filters\TernaryFilter::make('is_active')->label('Doar active'),
Tables\Filters\TernaryFilter::make('is_active')->label(__('Doar active')),
])
->actions([
Actions\EditAction::make(),
@@ -15,13 +15,18 @@ class DefaultPartsRelationManager extends RelationManager
{
protected static string $relationship = 'laborParts';
protected static ?string $title = 'Piese implicite';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Piese implicite');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\Select::make('part_id')
->label('Piesă')
->label(__('Piesă'))
->options(fn () => Part::where('is_active', true)
->get()
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])
@@ -34,8 +39,8 @@ class DefaultPartsRelationManager extends RelationManager
$set('unit', $p->unit);
}
}),
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(),
Forms\Components\TextInput::make('unit')->label('UM')->default('buc')->maxLength(16),
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)->required(),
Forms\Components\TextInput::make('unit')->label(__('UM'))->default('buc')->maxLength(16),
]);
}
@@ -44,14 +49,14 @@ class DefaultPartsRelationManager extends RelationManager
return $table
->recordTitleAttribute('part.name')
->columns([
Tables\Columns\TextColumn::make('part.name')->label('Piesă')->wrap(),
Tables\Columns\TextColumn::make('part.article')->label('Cod')->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(),
Tables\Columns\TextColumn::make('unit')->label('UM'),
Tables\Columns\TextColumn::make('part.name')->label(__('Piesă'))->wrap(),
Tables\Columns\TextColumn::make('part.article')->label(__('Cod'))->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
Tables\Columns\TextColumn::make('unit')->label(__('UM')),
])
->headerActions([Actions\CreateAction::make()])
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
->emptyStateHeading('Nicio piesă implicită')
->emptyStateDescription('Adaugă piesele care se montează de obicei la această manoperă — se adaugă automat în fișă când selectezi manopera.');
->emptyStateHeading(__('Nicio piesă implicită'))
->emptyStateDescription(__('Adaugă piesele care se montează de obicei la această manoperă — se adaugă automat în fișă când selectezi manopera.'));
}
}
+17 -17
View File
@@ -52,25 +52,25 @@ class LeadResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Contact')
Schemas\Components\Section::make(__('Contact'))
->columns(2)
->schema([
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(120),
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->required()->maxLength(40),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(120),
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->required()->maxLength(40),
Forms\Components\TextInput::make('email')->email()->maxLength(120),
Forms\Components\Select::make('status')
->options(Lead::STATUSES)
->default('new')
->required(),
]),
Schemas\Components\Section::make('Auto')
Schemas\Components\Section::make(__('Auto'))
->columns(2)
->schema([
Forms\Components\TextInput::make('car')->label('Marca')->maxLength(60),
Forms\Components\TextInput::make('car')->label(__('Marca'))->maxLength(60),
Forms\Components\TextInput::make('model')->maxLength(60),
]),
Forms\Components\Textarea::make('message')->label('Mesaj client')->columnSpanFull()->rows(3),
Schemas\Components\Section::make('Sursă & Atribuire')
Forms\Components\Textarea::make('message')->label(__('Mesaj client'))->columnSpanFull()->rows(3),
Schemas\Components\Section::make(__('Sursă & Atribuire'))
->columns(2)
->schema([
Forms\Components\Select::make('source')
@@ -78,12 +78,12 @@ class LeadResource extends Resource
->searchable()
->default('manual'),
Forms\Components\Select::make('assigned_to')
->label('Responsabil')
->label(__('Responsabil'))
->options(fn () => User::pluck('name', 'id'))
->searchable(),
Forms\Components\TextInput::make('budget')->label('Buget')->numeric(),
Forms\Components\TextInput::make('budget')->label(__('Buget'))->numeric(),
]),
Schemas\Components\Section::make('Marketing (UTM)')
Schemas\Components\Section::make(__('Marketing (UTM)'))
->collapsed()
->columns(2)
->schema([
@@ -93,7 +93,7 @@ class LeadResource extends Resource
Forms\Components\TextInput::make('utm_term'),
Forms\Components\TextInput::make('utm_content'),
]),
Forms\Components\Textarea::make('notes')->label('Notițe interne')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Notițe interne'))->columnSpanFull()->rows(2),
]);
}
@@ -101,11 +101,11 @@ class LeadResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('created_at')->label('Data')->dateTime('d.m.Y H:i')->sortable(),
Tables\Columns\TextColumn::make('created_at')->label(__('Data'))->dateTime('d.m.Y H:i')->sortable(),
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(),
Tables\Columns\TextColumn::make('car')->label('Auto')->formatStateUsing(fn ($state, $record) => trim($state . ' ' . ($record->model ?? ''))),
Tables\Columns\TextColumn::make('source')->label('Sursă')->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->badge(),
Tables\Columns\TextColumn::make('car')->label(__('Auto'))->formatStateUsing(fn ($state, $record) => trim($state . ' ' . ($record->model ?? ''))),
Tables\Columns\TextColumn::make('source')->label(__('Sursă'))->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->badge(),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($state) => Lead::STATUSES[$state] ?? $state)
->badge()
@@ -116,7 +116,7 @@ class LeadResource extends Resource
'success' => ['converted'],
'danger' => ['lost'],
]),
Tables\Columns\TextColumn::make('assignedTo.name')->label('Responsabil')->placeholder('—'),
Tables\Columns\TextColumn::make('assignedTo.name')->label(__('Responsabil'))->placeholder('—'),
Tables\Columns\TextColumn::make('budget')->money('MDL')->placeholder('—'),
])
->filters([
@@ -125,7 +125,7 @@ class LeadResource extends Resource
])
->actions([
Actions\Action::make('convert')
->label('Convertește')
->label(__('Convertește'))
->icon('heroicon-m-arrow-right-circle')
->color('success')
->visible(fn (Lead $r) => $r->status !== 'converted')
@@ -141,7 +141,7 @@ class LeadResource extends Resource
Actions\DeleteAction::make(),
])
->emptyStateHeading('Nicio cerere primită')
->emptyStateDescription('Aici apar cererile clienților potențiali. Convertește-le în deal-uri sau direct în programări de la butonul „Convertește".')
->emptyStateDescription(__('Aici apar cererile clienților potențiali. Convertește-le în deal-uri sau direct în programări de la butonul „Convertește".'))
->emptyStateIcon('heroicon-o-inbox-arrow-down')
->defaultSort('created_at', 'desc');
}
@@ -30,31 +30,36 @@ class MarketingChannelResource extends Resource
protected static ?string $modelLabel = 'canal';
protected static ?string $pluralModelLabel = 'canale marketing';
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('canale marketing');
}
protected static ?int $navigationSort = 61;
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Identificare')
Schemas\Components\Section::make(__('Identificare'))
->columns(3)
->schema([
Forms\Components\TextInput::make('name')->label('Nume canal')->required()->maxLength(120),
Forms\Components\TextInput::make('icon')->label('Iconiță (emoji)')->maxLength(8)->placeholder('🔍 / 📘 / 📸'),
Forms\Components\TextInput::make('name')->label(__('Nume canal'))->required()->maxLength(120),
Forms\Components\TextInput::make('icon')->label(__('Iconiță (emoji)'))->maxLength(8)->placeholder('🔍 / 📘 / 📸'),
Forms\Components\ColorPicker::make('color'),
]),
Schemas\Components\Section::make('Buget & rezultate (luna curentă)')
Schemas\Components\Section::make(__('Buget & rezultate (luna curentă)'))
->columns(3)
->schema([
Forms\Components\TextInput::make('budget_monthly')->label('Buget')->numeric()->default(0),
Forms\Components\TextInput::make('spent_monthly')->label('Cheltuit')->numeric()->default(0),
Forms\Components\TextInput::make('revenue')->label('Venit generat')->numeric()->default(0),
Forms\Components\TextInput::make('leads_count')->label('Lead-uri')->numeric()->default(0),
Forms\Components\TextInput::make('converted_count')->label('Convertite')->numeric()->default(0),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
Forms\Components\TextInput::make('budget_monthly')->label(__('Buget'))->numeric()->default(0),
Forms\Components\TextInput::make('spent_monthly')->label(__('Cheltuit'))->numeric()->default(0),
Forms\Components\TextInput::make('revenue')->label(__('Venit generat'))->numeric()->default(0),
Forms\Components\TextInput::make('leads_count')->label(__('Lead-uri'))->numeric()->default(0),
Forms\Components\TextInput::make('converted_count')->label(__('Convertite'))->numeric()->default(0),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]);
}
@@ -65,26 +70,26 @@ class MarketingChannelResource extends Resource
Tables\Columns\ColorColumn::make('color')->label(''),
Tables\Columns\TextColumn::make('icon')->label('')->width(40),
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('budget_monthly')->label('Buget')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('spent_monthly')->label('Cheltuit')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('leads_count')->label('Lead-uri')->alignRight(),
Tables\Columns\TextColumn::make('converted_count')->label('Convertite')->alignRight(),
Tables\Columns\TextColumn::make('revenue')->label('Venit')->money('MDL')->alignRight()->color('success'),
Tables\Columns\TextColumn::make('budget_monthly')->label(__('Buget'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('spent_monthly')->label(__('Cheltuit'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('leads_count')->label(__('Lead-uri'))->alignRight(),
Tables\Columns\TextColumn::make('converted_count')->label(__('Convertite'))->alignRight(),
Tables\Columns\TextColumn::make('revenue')->label(__('Venit'))->money('MDL')->alignRight()->color('success'),
Tables\Columns\TextColumn::make('roi')
->label('ROI')
->label(__('ROI'))
->state(fn (MarketingChannel $r) => $r->roi)
->formatStateUsing(fn ($s) => $s . '%')
->color(fn ($s) => $s >= 0 ? 'success' : 'danger')
->alignRight(),
Tables\Columns\TextColumn::make('cost_per_lead')
->label('Cost/lead')
->label(__('Cost/lead'))
->state(fn (MarketingChannel $r) => $r->cost_per_lead)
->money('MDL')
->alignRight(),
Tables\Columns\IconColumn::make('is_active')->boolean(),
])
->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Active'),
Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
])
->actions([
Actions\EditAction::make(),
@@ -31,20 +31,30 @@ class MarkupRuleResource extends Resource
return __('nav.group.Depozit');
}
protected static ?string $modelLabel = 'regulă';
protected static ?string $modelLabel = null;
protected static ?string $pluralModelLabel = 'reguli markup';
public static function getModelLabel(): string
{
return __('regulă');
}
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('reguli markup');
}
protected static ?int $navigationSort = 44;
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Regulă')
Schemas\Components\Section::make(__('Regulă'))
->columns(2)
->schema([
Forms\Components\Select::make('type')
->label('Tip')
->label(__('Tip'))
->options(MarkupRule::TYPES)
->default('category')
->required()
@@ -58,25 +68,25 @@ class MarkupRuleResource extends Resource
->searchable()
->required(fn (Get $get) => in_array($get('type'), ['category', 'brand'], true)),
Forms\Components\TextInput::make('range_from')
->label('De la (preț achiziție)')
->label(__('De la (preț achiziție)'))
->numeric()
->visible(fn (Get $get) => $get('type') === 'range'),
Forms\Components\TextInput::make('range_to')
->label('Până la (gol = ∞)')
->label(__('Până la (gol = ∞)'))
->numeric()
->visible(fn (Get $get) => $get('type') === 'range'),
Forms\Components\TextInput::make('markup_pct')
->label('Markup %')
->label(__('Markup %'))
->numeric()
->required()
->suffix('%')
->helperText('Ex 30 → preț vânzare = preț achiziție × 1.30'),
->helperText(__('Ex 30 → preț vânzare = preț achiziție × 1.30')),
Forms\Components\TextInput::make('priority')
->label('Prioritate')
->label(__('Prioritate'))
->numeric()
->default(100)
->helperText('Mai mic = aplicat primul.'),
Forms\Components\Toggle::make('is_active')->label('Activă')->default(true),
->helperText(__('Mai mic = aplicat primul.')),
Forms\Components\Toggle::make('is_active')->label(__('Activă'))->default(true),
]),
]);
}
@@ -89,11 +99,11 @@ class MarkupRuleResource extends Resource
Tables\Columns\TextColumn::make('type')
->formatStateUsing(fn ($s) => MarkupRule::TYPES[$s] ?? $s)
->badge(),
Tables\Columns\TextColumn::make('key')->label('Cheie')->placeholder('—'),
Tables\Columns\TextColumn::make('range_from')->label('De la')->placeholder('—'),
Tables\Columns\TextColumn::make('range_to')->label('Până la')->placeholder('∞'),
Tables\Columns\TextColumn::make('key')->label(__('Cheie'))->placeholder('—'),
Tables\Columns\TextColumn::make('range_from')->label(__('De la'))->placeholder('—'),
Tables\Columns\TextColumn::make('range_to')->label(__('Până la'))->placeholder('∞'),
Tables\Columns\TextColumn::make('markup_pct')
->label('Markup')
->label(__('Markup'))
->formatStateUsing(fn ($s) => '+' . $s . '%')
->color('success')
->weight('bold'),
@@ -104,11 +114,11 @@ class MarkupRuleResource extends Resource
])
->headerActions([
Actions\Action::make('apply_all')
->label('Aplică toate regulile la stoc')
->label(__('Aplică toate regulile la stoc'))
->icon('heroicon-m-bolt')
->color('warning')
->requiresConfirmation()
->modalDescription('Va recalcula sell_price pentru TOATE piesele active. Continui?')
->modalDescription(__('Va recalcula sell_price pentru TOATE piesele active. Continui?'))
->action(function () {
$count = 0;
Part::where('is_active', true)->where('buy_price', '>', 0)->chunk(100, function ($parts) use (&$count) {
@@ -47,34 +47,34 @@ class MasterResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Date personale')
Schemas\Components\Section::make(__('Date personale'))
->columns(2)
->schema([
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(120),
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->label('Email')->email()->maxLength(120),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(120),
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->label(__('Email'))->email()->maxLength(120),
Forms\Components\Select::make('status')
->options(['active' => 'Activ', 'inactive' => 'Inactiv', 'blocked' => 'Blocat'])
->default('active')
->required(),
]),
Schemas\Components\Section::make('Profesie')
Schemas\Components\Section::make(__('Profesie'))
->columns(2)
->schema([
Forms\Components\TextInput::make('specialization')
->label('Specializare')
->placeholder('Motor / Frâne / Electrică ...')
->label(__('Specializare'))
->placeholder(__('Motor / Frâne / Electrică ...'))
->maxLength(120),
Forms\Components\ColorPicker::make('color')->label('Culoare în calendar'),
Forms\Components\TextInput::make('hourly_rate')->label('Tarif/oră')->numeric(),
Forms\Components\ColorPicker::make('color')->label(__('Culoare în calendar')),
Forms\Components\TextInput::make('hourly_rate')->label(__('Tarif/oră'))->numeric(),
Forms\Components\Hidden::make('role')->default('mechanic'),
]),
Schemas\Components\Section::make('Acces în aplicație (opțional)')
Schemas\Components\Section::make(__('Acces în aplicație (opțional)'))
->columns(1)
->collapsed()
->schema([
Forms\Components\TextInput::make('password')
->label('Parolă (lasă gol pentru a nu schimba)')
->label(__('Parolă (lasă gol pentru a nu schimba)'))
->password()
->minLength(6)
->dehydrated(fn ($state) => filled($state))
@@ -89,9 +89,9 @@ class MasterResource extends Resource
->columns([
Tables\Columns\ColorColumn::make('color')->label(''),
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('specialization')->label('Specializare')->placeholder('—'),
Tables\Columns\TextColumn::make('specialization')->label(__('Specializare'))->placeholder('—'),
Tables\Columns\TextColumn::make('phone')->copyable()->placeholder('—'),
Tables\Columns\TextColumn::make('hourly_rate')->label('Tarif/h')->money('MDL')->alignRight()->placeholder('—'),
Tables\Columns\TextColumn::make('hourly_rate')->label(__('Tarif/h'))->money('MDL')->alignRight()->placeholder('—'),
Tables\Columns\TextColumn::make('status')
->badge()
->colors([
@@ -10,5 +10,5 @@ class ListMasters extends ListRecords
{
protected static string $resource = MasterResource::class;
protected function getHeaderActions(): array { return [Actions\CreateAction::make()->label('Nou tehnician')]; }
protected function getHeaderActions(): array { return [Actions\CreateAction::make()->label(__('Nou tehnician'))]; }
}
@@ -37,25 +37,25 @@ class MessageTemplateResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Identificare')
Schemas\Components\Section::make(__('Identificare'))
->columns(2)
->schema([
Forms\Components\TextInput::make('name')->label('Nume template')->required()->maxLength(120),
Forms\Components\TextInput::make('name')->label(__('Nume template'))->required()->maxLength(120),
Forms\Components\Select::make('channel')
->options(MessageTemplate::CHANNELS)
->default('telegram')
->required(),
Forms\Components\TextInput::make('subject')->label('Subiect (email)')->maxLength(160)->columnSpanFull(),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
Forms\Components\TextInput::make('subject')->label(__('Subiect (email)'))->maxLength(160)->columnSpanFull(),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]),
Schemas\Components\Section::make('Conținut')
Schemas\Components\Section::make(__('Conținut'))
->columns(1)
->schema([
Forms\Components\Textarea::make('body')
->label('Mesaj')
->label(__('Mesaj'))
->required()
->rows(6)
->helperText('Variabile disponibile: {name}, {car}, {date}, {time}, {amount}, {service}, {mileage}'),
->helperText(__('Variabile disponibile: {name}, {car}, {date}, {time}, {amount}, {service}, {mileage}')),
]),
]);
}
@@ -68,7 +68,7 @@ class MessageTemplateResource extends Resource
Tables\Columns\TextColumn::make('channel')
->formatStateUsing(fn ($s) => MessageTemplate::CHANNELS[$s] ?? $s)
->badge(),
Tables\Columns\TextColumn::make('body')->label('Preview')->limit(60),
Tables\Columns\TextColumn::make('body')->label(__('Preview'))->limit(60),
Tables\Columns\IconColumn::make('is_active')->boolean(),
])
->filters([
@@ -30,9 +30,19 @@ class OnlineOrderResource extends Resource
return __('nav.group.Magazin');
}
protected static ?string $modelLabel = 'comandă';
protected static ?string $modelLabel = null;
protected static ?string $pluralModelLabel = 'comenzi online';
public static function getModelLabel(): string
{
return __('comandă');
}
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('comenzi online');
}
protected static ?int $navigationSort = 50;
@@ -50,18 +60,18 @@ class OnlineOrderResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Comandă')
Schemas\Components\Section::make(__('Comandă'))
->columns(3)
->schema([
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false),
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false),
Forms\Components\Select::make('status')->options(OnlineOrder::STATUSES)->required(),
Forms\Components\Select::make('delivery_method')->label('Livrare')->options(OnlineOrder::DELIVERY)->required(),
Forms\Components\TextInput::make('customer_name')->label('Client')->required(),
Forms\Components\TextInput::make('customer_phone')->label('Telefon')->required(),
Forms\Components\TextInput::make('customer_email')->label('Email'),
Forms\Components\TextInput::make('address')->label('Adresă')->columnSpan(2),
Forms\Components\TextInput::make('delivery_fee')->label('Taxă livrare')->numeric(),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Select::make('delivery_method')->label(__('Livrare'))->options(OnlineOrder::DELIVERY)->required(),
Forms\Components\TextInput::make('customer_name')->label(__('Client'))->required(),
Forms\Components\TextInput::make('customer_phone')->label(__('Telefon'))->required(),
Forms\Components\TextInput::make('customer_email')->label(__('Email')),
Forms\Components\TextInput::make('address')->label(__('Adresă'))->columnSpan(2),
Forms\Components\TextInput::make('delivery_fee')->label(__('Taxă livrare'))->numeric(),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]),
]);
}
@@ -70,12 +80,12 @@ class OnlineOrderResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
Tables\Columns\TextColumn::make('created_at')->label('Data')->dateTime('d.m.Y H:i')->sortable(),
Tables\Columns\TextColumn::make('customer_name')->label('Client')->searchable(),
Tables\Columns\TextColumn::make('customer_phone')->label('Telefon')->copyable(),
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('created_at')->label(__('Data'))->dateTime('d.m.Y H:i')->sortable(),
Tables\Columns\TextColumn::make('customer_name')->label(__('Client'))->searchable(),
Tables\Columns\TextColumn::make('customer_phone')->label(__('Telefon'))->copyable(),
Tables\Columns\TextColumn::make('delivery_method')
->label('Livrare')
->label(__('Livrare'))
->formatStateUsing(fn ($s) => OnlineOrder::DELIVERY[$s] ?? $s),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => OnlineOrder::STATUSES[$s] ?? $s)
@@ -94,12 +104,12 @@ class OnlineOrderResource extends Resource
])
->actions([
Actions\Action::make('fulfill')
->label('Onorează (scade stoc)')
->label(__('Onorează (scade stoc)'))
->icon('heroicon-m-check-badge')
->color('success')
->visible(fn (OnlineOrder $r) => ! in_array($r->status, ['delivered', 'cancelled'], true))
->requiresConfirmation()
->modalDescription('Scade din stoc piesele legate de catalog (FIFO) și marchează comanda confirmată.')
->modalDescription(__('Scade din stoc piesele legate de catalog (FIFO) și marchează comanda confirmată.'))
->action(function (OnlineOrder $r) {
$svc = app(\App\Services\Warehouse\WarehouseService::class);
$issued = 0; $skipped = 0;
@@ -10,19 +10,24 @@ class ItemsRelationManager extends RelationManager
{
protected static string $relationship = 'items';
protected static ?string $title = 'Produse';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Produse');
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name')->label('Piesă')->wrap(),
Tables\Columns\TextColumn::make('article')->label('Cod')->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(),
Tables\Columns\TextColumn::make('price')->label('Preț')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('name')->label(__('Piesă'))->wrap(),
Tables\Columns\TextColumn::make('article')->label(__('Cod'))->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
Tables\Columns\TextColumn::make('price')->label(__('Preț'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
Tables\Columns\IconColumn::make('fulfilled')->label('Onorat')->boolean(),
Tables\Columns\IconColumn::make('fulfilled')->label(__('Onorat'))->boolean(),
]);
}
}
+52 -47
View File
@@ -30,7 +30,12 @@ class PartResource extends Resource
return __('nav.group.Depozit');
}
protected static ?string $modelLabel = 'piesă';
protected static ?string $modelLabel = null;
public static function getModelLabel(): string
{
return __('piesă');
}
protected static ?string $pluralModelLabel = 'piese';
@@ -66,50 +71,50 @@ class PartResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Identificare')
Schemas\Components\Section::make(__('Identificare'))
->columns(3)
->schema([
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpan(3)->maxLength(200),
Forms\Components\TextInput::make('article')->label('Cod articol')->maxLength(64),
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpan(3)->maxLength(200),
Forms\Components\TextInput::make('article')->label(__('Cod articol'))->maxLength(64),
Forms\Components\TextInput::make('brand')->maxLength(64),
Forms\Components\Select::make('category')
->label('Categorie')
->label(__('Categorie'))
->options(array_combine(Part::CATEGORIES, Part::CATEGORIES))
->searchable(),
Forms\Components\TextInput::make('barcode')->label('Cod bare')->maxLength(64),
Forms\Components\TextInput::make('location')->label('Locație rack/bin')->maxLength(64),
Forms\Components\TextInput::make('barcode')->label(__('Cod bare'))->maxLength(64),
Forms\Components\TextInput::make('location')->label(__('Locație rack/bin'))->maxLength(64),
]),
Schemas\Components\Section::make('Stoc')
Schemas\Components\Section::make(__('Stoc'))
->columns(4)
->schema([
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(0)->required(),
Forms\Components\TextInput::make('unit')->label('UM')->default('buc')->maxLength(16),
Forms\Components\TextInput::make('min_qty')->label('Minim')->numeric()->default(0),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(0)->required(),
Forms\Components\TextInput::make('unit')->label(__('UM'))->default('buc')->maxLength(16),
Forms\Components\TextInput::make('min_qty')->label(__('Minim'))->numeric()->default(0),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
Forms\Components\Toggle::make('is_published')
->label('Publicat în magazin')
->helperText('Apare în magazinul online public.')
->label(__('Publicat în magazin'))
->helperText(__('Apare în magazinul online public.'))
->default(false),
]),
Schemas\Components\Section::make('Prețuri')
Schemas\Components\Section::make(__('Prețuri'))
->columns(2)
->schema([
Forms\Components\TextInput::make('buy_price')->label('Preț achiziție')->numeric()->default(0),
Forms\Components\TextInput::make('sell_price')->label('Preț vânzare')->numeric()->default(0),
Forms\Components\TextInput::make('buy_price')->label(__('Preț achiziție'))->numeric()->default(0),
Forms\Components\TextInput::make('sell_price')->label(__('Preț vânzare'))->numeric()->default(0),
]),
Schemas\Components\Section::make('Furnizor preferat')
Schemas\Components\Section::make(__('Furnizor preferat'))
->columns(1)
->schema([
Forms\Components\Select::make('preferred_supplier_id')
->label('Furnizor')
->label(__('Furnizor'))
->options(fn () => Supplier::pluck('name', 'id'))
->searchable(),
]),
Schemas\Components\Section::make('Imagine')
Schemas\Components\Section::make(__('Imagine'))
->collapsible()
->schema([
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('image')
->label('Foto piesă')
->label(__('Foto piesă'))
->collection('image')
->multiple()
->reorderable()
@@ -118,9 +123,9 @@ class PartResource extends Resource
->maxFiles(8)
->maxSize(2048)
->columnSpanFull()
->helperText('Galerie de până la 8 imagini. Prima e afișată în catalog. Max 2 MB / imagine.'),
->helperText(__('Galerie de până la 8 imagini. Prima e afișată în catalog. Max 2 MB / imagine.')),
]),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]);
}
@@ -134,40 +139,40 @@ class PartResource extends Resource
->circular()
->size(32),
Tables\Columns\TextColumn::make('name')->searchable()->sortable()->wrap(),
Tables\Columns\TextColumn::make('article')->label('Cod')->searchable()->copyable()->placeholder('—'),
Tables\Columns\TextColumn::make('article')->label(__('Cod'))->searchable()->copyable()->placeholder('—'),
Tables\Columns\TextColumn::make('brand')->placeholder('—'),
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('qty')
->label('Stoc')
->label(__('Stoc'))
->numeric(decimalPlaces: 2)
->alignRight()
->color(fn ($state, $record) => $record->qty <= 0 ? 'danger' : ($record->qty <= $record->min_qty ? 'warning' : null))
->weight(fn ($state, $record) => $record->qty <= $record->min_qty ? 'bold' : null),
Tables\Columns\TextColumn::make('qty_reserved')
->label('Rezervat')
->label(__('Rezervat'))
->numeric(decimalPlaces: 2)
->alignRight()
->color(fn ($state) => (float) $state > 0 ? 'info' : null)
->toggleable(),
Tables\Columns\TextColumn::make('unit')->label('UM'),
Tables\Columns\TextColumn::make('location')->label('Loc.')->placeholder('—'),
Tables\Columns\TextColumn::make('sell_price')->label('Preț vz.')->money('MDL')->alignRight(),
Tables\Columns\IconColumn::make('is_published')->label('Magazin')->boolean()->toggleable(),
Tables\Columns\TextColumn::make('preferredSupplier.name')->label('Furnizor')->placeholder('—')->toggleable(),
Tables\Columns\TextColumn::make('unit')->label(__('UM')),
Tables\Columns\TextColumn::make('location')->label(__('Loc.'))->placeholder('—'),
Tables\Columns\TextColumn::make('sell_price')->label(__('Preț vz.'))->money('MDL')->alignRight(),
Tables\Columns\IconColumn::make('is_published')->label(__('Magazin'))->boolean()->toggleable(),
Tables\Columns\TextColumn::make('preferredSupplier.name')->label(__('Furnizor'))->placeholder('—')->toggleable(),
])
->filters([
Tables\Filters\SelectFilter::make('category')
->options(array_combine(Part::CATEGORIES, Part::CATEGORIES)),
Tables\Filters\Filter::make('low_stock')
->label('Stoc minim')
->label(__('Stoc minim'))
->query(fn ($q) => $q->whereColumn('qty', '<=', 'min_qty')),
Tables\Filters\Filter::make('out_of_stock')
->label('Lipsă')
->label(__('Lipsă'))
->query(fn ($q) => $q->where('qty', '<=', 0)),
])
->actions([
Actions\Action::make('qr')
->label('QR')
->label(__('QR'))
->icon('heroicon-m-qr-code')
->color('gray')
->modalHeading(fn (Part $r) => 'QR pentru ' . $r->name)
@@ -187,7 +192,7 @@ class PartResource extends Resource
]);
}),
Actions\Action::make('ai_price')
->label('AI: preț recomandat')
->label(__('AI: preț recomandat'))
->icon('heroicon-m-sparkles')
->color('primary')
->modalHeading(fn (Part $r) => "AI: preț pentru {$r->name}")
@@ -199,20 +204,20 @@ class PartResource extends Resource
return view('filament.tenant.ai-reply', ['reply' => $reply, 'meta' => $meta]);
}),
Actions\Action::make('receive')
->label('Recepție')
->label(__('Recepție'))
->icon('heroicon-m-arrow-down-tray')
->color('success')
->schema([
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->required()->minValue(0.001),
Forms\Components\TextInput::make('buy_price')->label('Preț unitar')->numeric()->required(),
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->required()->minValue(0.001),
Forms\Components\TextInput::make('buy_price')->label(__('Preț unitar'))->numeric()->required(),
Forms\Components\Select::make('supplier_id')
->label('Furnizor')
->label(__('Furnizor'))
->options(fn () => \App\Models\Tenant\Supplier::pluck('name', 'id')),
Forms\Components\Select::make('warehouse_id')
->label('Depozit')
->label(__('Depozit'))
->options(fn () => \App\Models\Tenant\Warehouse::where('is_active', true)->pluck('name', 'id'))
->default(fn () => \App\Models\Tenant\Warehouse::where('is_default', true)->value('id')),
Forms\Components\TextInput::make('batch_ref')->label('Ref. lot/factură')->maxLength(64),
Forms\Components\TextInput::make('batch_ref')->label(__('Ref. lot/factură'))->maxLength(64),
])
->action(function (Part $record, array $data) {
$warehouse = $data['warehouse_id']
@@ -230,7 +235,7 @@ class PartResource extends Resource
batchRef: $data['batch_ref'] ?? null,
);
\Filament\Notifications\Notification::make()
->title('Stoc adăugat')
->title(__('Stoc adăugat'))
->success()
->send();
}),
@@ -239,7 +244,7 @@ class PartResource extends Resource
])
->bulkActions([
Actions\BulkAction::make('print_labels')
->label('Tipărește etichete QR')
->label(__('Tipărește etichete QR'))
->icon('heroicon-m-printer')
->color('gray')
->action(function ($records) {
@@ -248,20 +253,20 @@ class PartResource extends Resource
})
->deselectRecordsAfterCompletion(),
Actions\BulkAction::make('publish')
->label('Publică în magazin')
->label(__('Publică în magazin'))
->icon('heroicon-m-globe-alt')
->color('success')
->action(fn ($records) => collect($records)->each->update(['is_published' => true]))
->deselectRecordsAfterCompletion(),
Actions\BulkAction::make('unpublish')
->label('Scoate din magazin')
->label(__('Scoate din magazin'))
->icon('heroicon-m-eye-slash')
->color('gray')
->action(fn ($records) => collect($records)->each->update(['is_published' => false]))
->deselectRecordsAfterCompletion(),
])
->emptyStateHeading('Depozit gol')
->emptyStateDescription('Adaugă piese manual, sau folosește Achiziții ca să le adaugi prin recepție de la furnizor (cu prețuri și stoc auto). Procentaj poate seta automat prețul de vânzare.')
->emptyStateHeading(__('Depozit gol'))
->emptyStateDescription(__('Adaugă piese manual, sau folosește Achiziții ca să le adaugi prin recepție de la furnizor (cu prețuri și stoc auto). Procentaj poate seta automat prețul de vânzare.'))
->emptyStateIcon('heroicon-o-cube')
->defaultSort('name');
}
@@ -10,36 +10,41 @@ class BatchesRelationManager extends RelationManager
{
protected static string $relationship = 'batches';
protected static ?string $title = 'Loturi (FIFO)';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Loturi (FIFO)');
}
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('received_at')
->label('Recepție')
->label(__('Recepție'))
->dateTime('d.m.Y H:i')
->sortable(),
Tables\Columns\TextColumn::make('warehouse.code')->label('Depozit')->placeholder('—'),
Tables\Columns\TextColumn::make('batch_ref')->label('Ref.')->placeholder('—'),
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->placeholder('—'),
Tables\Columns\TextColumn::make('warehouse.code')->label(__('Depozit'))->placeholder('—'),
Tables\Columns\TextColumn::make('batch_ref')->label(__('Ref.'))->placeholder('—'),
Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->placeholder('—'),
Tables\Columns\TextColumn::make('qty_in')
->label('Intrat')
->label(__('Intrat'))
->numeric(decimalPlaces: 2)
->alignRight(),
Tables\Columns\TextColumn::make('qty_remaining')
->label('Rămas')
->label(__('Rămas'))
->numeric(decimalPlaces: 2)
->alignRight()
->weight('bold')
->color(fn ($state) => (float) $state <= 0 ? 'gray' : 'success'),
Tables\Columns\TextColumn::make('buy_price')
->label('Preț unit.')
->label(__('Preț unit.'))
->money('MDL')
->alignRight(),
])
->defaultSort('received_at')
->emptyStateHeading('Niciun lot înregistrat')
->emptyStateDescription('Apasă „Recepție" pe lista de piese pentru a înregistra prima intrare în depozit.');
->emptyStateHeading(__('Niciun lot înregistrat'))
->emptyStateDescription(__('Apasă „Recepție" pe lista de piese pentru a înregistra prima intrare în depozit.'));
}
}
@@ -13,13 +13,18 @@ class CrossRefsRelationManager extends RelationManager
{
protected static string $relationship = 'crossRefs';
protected static ?string $title = 'Coduri cross (OEM/echivalente)';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Coduri cross (OEM/echivalente)');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\TextInput::make('cross_article')->label('Cod echivalent')->required()->maxLength(64),
Forms\Components\TextInput::make('brand')->label('Brand')->maxLength(64),
Forms\Components\TextInput::make('cross_article')->label(__('Cod echivalent'))->required()->maxLength(64),
Forms\Components\TextInput::make('brand')->label(__('Brand'))->maxLength(64),
]);
}
@@ -28,12 +33,12 @@ class CrossRefsRelationManager extends RelationManager
return $table
->recordTitleAttribute('cross_article')
->columns([
Tables\Columns\TextColumn::make('cross_article')->label('Cod')->searchable(),
Tables\Columns\TextColumn::make('cross_article')->label(__('Cod'))->searchable(),
Tables\Columns\TextColumn::make('brand')->placeholder('—'),
])
->headerActions([Actions\CreateAction::make()])
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
->emptyStateHeading('Niciun cod cross')
->emptyStateDescription('Adaugă coduri echivalente OEM/aftermarket ca să fie găsite în căutarea din magazin.');
->emptyStateHeading(__('Niciun cod cross'))
->emptyStateDescription(__('Adaugă coduri echivalente OEM/aftermarket ca să fie găsite în căutarea din magazin.'));
}
}
@@ -10,26 +10,31 @@ class PriceHistoryRelationManager extends RelationManager
{
protected static string $relationship = 'priceHistory';
protected static ?string $title = 'Istoric prețuri furnizori';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Istoric prețuri furnizori');
}
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('observed_at')
->label('Data')
->label(__('Data'))
->dateTime('d.m.Y H:i')
->sortable(),
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->searchable(),
Tables\Columns\TextColumn::make('purchase.number')->label('PO')->placeholder('—'),
Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->searchable(),
Tables\Columns\TextColumn::make('purchase.number')->label(__('PO'))->placeholder('—'),
Tables\Columns\TextColumn::make('price')
->money('MDL')
->alignRight()
->sortable(),
Tables\Columns\TextColumn::make('currency')->label('Val.'),
Tables\Columns\TextColumn::make('currency')->label(__('Val.')),
])
->defaultSort('observed_at', 'desc')
->emptyStateHeading('Niciun preț înregistrat')
->emptyStateDescription('Prețurile se înregistrează automat la fiecare recepție de PO.');
->emptyStateHeading(__('Niciun preț înregistrat'))
->emptyStateDescription(__('Prețurile se înregistrează automat la fiecare recepție de PO.'));
}
}
@@ -30,9 +30,19 @@ class PaymentResource extends Resource
return __('nav.group.Finanțe');
}
protected static ?string $modelLabel = 'plată';
protected static ?string $modelLabel = null;
protected static ?string $pluralModelLabel = 'plăți';
public static function getModelLabel(): string
{
return __('plată');
}
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('plăți');
}
protected static ?int $navigationSort = 50;
@@ -54,28 +64,28 @@ class PaymentResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Plată')
Schemas\Components\Section::make(__('Plată'))
->columns(2)
->schema([
Forms\Components\DatePicker::make('paid_at')->label('Data')->default(today())->required(),
Forms\Components\DatePicker::make('paid_at')->label(__('Data'))->default(today())->required(),
Forms\Components\Select::make('method')
->options(Payment::METHODS)
->default('cash')
->required(),
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(),
Forms\Components\TextInput::make('reference')->label('Referință (chitanță / tranzacție)')->maxLength(64),
Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
Forms\Components\TextInput::make('reference')->label(__('Referință (chitanță / tranzacție)'))->maxLength(64),
Forms\Components\Select::make('client_id')
->label('Client')
->label(__('Client'))
->options(fn () => Client::pluck('name', 'id'))
->searchable(),
Forms\Components\Select::make('work_order_id')
->label('Fișă lucru')
->label(__('Fișă lucru'))
->options(fn () => WorkOrder::orderBy('id', 'desc')->limit(50)
->get()->mapWithKeys(fn ($w) => [$w->id => "{$w->number}" . ($w->client?->name ?? '?')])
->toArray())
->searchable()
->helperText('Plata stabilește automat starea fișei (unpaid/partial/paid)'),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
->helperText(__('Plata stabilește automat starea fișei (unpaid/partial/paid)')),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]),
]);
}
@@ -84,22 +94,22 @@ class PaymentResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('paid_at')->label('Data')->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable(),
Tables\Columns\TextColumn::make('workOrder.number')->label('Fișă')->placeholder('—'),
Tables\Columns\TextColumn::make('paid_at')->label(__('Data'))->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
Tables\Columns\TextColumn::make('workOrder.number')->label(__('Fișă'))->placeholder('—'),
Tables\Columns\TextColumn::make('method')
->formatStateUsing(fn ($s) => Payment::METHODS[$s] ?? $s)
->badge(),
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable()->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label('Total')),
Tables\Columns\TextColumn::make('reference')->label('Ref.')->placeholder('—')->toggleable(),
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable()->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label(__('Total'))),
Tables\Columns\TextColumn::make('reference')->label(__('Ref.'))->placeholder('—')->toggleable(),
])
->filters([
Tables\Filters\SelectFilter::make('method')->options(Payment::METHODS),
Tables\Filters\Filter::make('today')
->label('Astăzi')
->label(__('Astăzi'))
->query(fn ($q) => $q->whereDate('paid_at', today())),
Tables\Filters\Filter::make('this_month')
->label('Luna curentă')
->label(__('Luna curentă'))
->query(fn ($q) => $q->whereMonth('paid_at', now()->month)->whereYear('paid_at', now()->year)),
])
->actions([
@@ -31,7 +31,12 @@ class PayrollAdjustmentResource extends Resource
protected static ?string $modelLabel = 'ajustare';
protected static ?string $pluralModelLabel = 'bonusuri/avansuri';
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('bonusuri/avansuri');
}
protected static ?int $navigationSort = 54;
@@ -48,11 +53,11 @@ class PayrollAdjustmentResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Detalii')
Schemas\Components\Section::make(__('Detalii'))
->columns(2)
->schema([
Forms\Components\Select::make('user_id')
->label('Utilizator')
->label(__('Utilizator'))
->options(fn () => User::orderBy('name')->pluck('name', 'id'))
->searchable()
->required(),
@@ -60,14 +65,14 @@ class PayrollAdjustmentResource extends Resource
->options(PayrollAdjustment::TYPES)
->default('bonus')
->required(),
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(),
Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
Forms\Components\TextInput::make('period')
->label('Perioadă (YYYY-MM)')
->label(__('Perioadă (YYYY-MM)'))
->placeholder(now()->format('Y-m'))
->regex('/^\d{4}-\d{2}$/')
->required(),
Forms\Components\DatePicker::make('date')->default(today())->required(),
Forms\Components\TextInput::make('reason')->label('Motiv')->maxLength(160),
Forms\Components\TextInput::make('reason')->label(__('Motiv'))->maxLength(160),
]),
]);
}
@@ -77,7 +82,7 @@ class PayrollAdjustmentResource extends Resource
return $table
->columns([
Tables\Columns\TextColumn::make('date')->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('user.name')->label('Utilizator')->searchable(),
Tables\Columns\TextColumn::make('user.name')->label(__('Utilizator'))->searchable(),
Tables\Columns\TextColumn::make('type')
->formatStateUsing(fn ($s) => PayrollAdjustment::TYPES[$s] ?? $s)
->badge()
@@ -89,12 +94,12 @@ class PayrollAdjustmentResource extends Resource
Tables\Columns\TextColumn::make('period'),
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('reason')->limit(40),
Tables\Columns\IconColumn::make('applied')->boolean()->label('Aplicat'),
Tables\Columns\IconColumn::make('applied')->boolean()->label(__('Aplicat')),
])
->filters([
Tables\Filters\SelectFilter::make('type')->options(PayrollAdjustment::TYPES),
Tables\Filters\SelectFilter::make('user_id')
->label('Utilizator')
->label(__('Utilizator'))
->options(fn () => User::pluck('name', 'id')),
])
->actions([
@@ -50,40 +50,40 @@ class PayrollRunResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Detalii')
Schemas\Components\Section::make(__('Detalii'))
->columns(2)
->schema([
Forms\Components\Select::make('user_id')
->label('Utilizator')
->label(__('Utilizator'))
->options(fn () => User::orderBy('name')->pluck('name', 'id'))
->searchable()
->required(),
Forms\Components\TextInput::make('period')
->label('Perioada (YYYY-MM)')
->label(__('Perioada (YYYY-MM)'))
->placeholder(now()->format('Y-m'))
->required()
->regex('/^\d{4}-\d{2}$/'),
]),
Schemas\Components\Section::make('Calcul')
Schemas\Components\Section::make(__('Calcul'))
->columns(3)
->schema([
Forms\Components\TextInput::make('base')->label('Bază')->numeric()->default(0),
Forms\Components\TextInput::make('works_revenue')->label('Venit manopere')->numeric()->disabled(),
Forms\Components\TextInput::make('works_pct_amount')->label('Comision manopere')->numeric()->disabled(),
Forms\Components\TextInput::make('parts_margin')->label('Marja piese')->numeric()->disabled(),
Forms\Components\TextInput::make('parts_pct_amount')->label('Comision piese')->numeric()->disabled(),
Forms\Components\TextInput::make('base')->label(__('Bază'))->numeric()->default(0),
Forms\Components\TextInput::make('works_revenue')->label(__('Venit manopere'))->numeric()->disabled(),
Forms\Components\TextInput::make('works_pct_amount')->label(__('Comision manopere'))->numeric()->disabled(),
Forms\Components\TextInput::make('parts_margin')->label(__('Marja piese'))->numeric()->disabled(),
Forms\Components\TextInput::make('parts_pct_amount')->label(__('Comision piese'))->numeric()->disabled(),
Forms\Components\TextInput::make('bonus')->numeric()->default(0),
Forms\Components\TextInput::make('fines')->label('Penalizări')->numeric()->default(0),
Forms\Components\TextInput::make('advance')->label('Avans')->numeric()->default(0),
Forms\Components\TextInput::make('total')->label('Total net')->numeric()->disabled(),
Forms\Components\TextInput::make('fines')->label(__('Penalizări'))->numeric()->default(0),
Forms\Components\TextInput::make('advance')->label(__('Avans'))->numeric()->default(0),
Forms\Components\TextInput::make('total')->label(__('Total net'))->numeric()->disabled(),
]),
Schemas\Components\Section::make('Plată')
Schemas\Components\Section::make(__('Plată'))
->columns(2)
->schema([
Forms\Components\Toggle::make('paid')->label('Achitat'),
Forms\Components\DatePicker::make('paid_at')->label('Data plății'),
Forms\Components\Toggle::make('paid')->label(__('Achitat')),
Forms\Components\DatePicker::make('paid_at')->label(__('Data plății')),
]),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]);
}
@@ -91,21 +91,21 @@ class PayrollRunResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('period')->label('Perioadă')->sortable(),
Tables\Columns\TextColumn::make('user.name')->label('Utilizator')->searchable(),
Tables\Columns\TextColumn::make('period')->label(__('Perioadă'))->sortable(),
Tables\Columns\TextColumn::make('user.name')->label(__('Utilizator'))->searchable(),
Tables\Columns\TextColumn::make('base')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('works_pct_amount')->label('% manopere')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('parts_pct_amount')->label('% piese')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('works_pct_amount')->label(__('% manopere'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('parts_pct_amount')->label(__('% piese'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('bonus')->money('MDL')->alignRight()->color('success'),
Tables\Columns\TextColumn::make('fines')->money('MDL')->alignRight()->color('danger'),
Tables\Columns\TextColumn::make('advance')->money('MDL')->alignRight()->color('warning'),
Tables\Columns\TextColumn::make('total')->label('Total')->money('MDL')->alignRight()->weight('bold')
Tables\Columns\TextColumn::make('total')->label(__('Total'))->money('MDL')->alignRight()->weight('bold')
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')),
Tables\Columns\IconColumn::make('paid')->boolean(),
])
->headerActions([
Actions\Action::make('compute_all')
->label('Calculează luna curentă')
->label(__('Calculează luna curentă'))
->icon('heroicon-m-calculator')
->color('primary')
->action(function () {
@@ -122,13 +122,13 @@ class PayrollRunResource extends Resource
])
->filters([
Tables\Filters\SelectFilter::make('period')
->label('Perioadă')
->label(__('Perioadă'))
->options(fn () => PayrollRun::distinct()->pluck('period', 'period')->toArray()),
Tables\Filters\TernaryFilter::make('paid')->label('Achitat'),
Tables\Filters\TernaryFilter::make('paid')->label(__('Achitat')),
])
->actions([
Actions\Action::make('recompute')
->label('Recalculează')
->label(__('Recalculează'))
->icon('heroicon-m-arrow-path')
->action(fn (PayrollRun $r) => app(PayrollCalculator::class)->compute($r->user_id, $r->period)),
Actions\EditAction::make(),
+21 -16
View File
@@ -32,7 +32,12 @@ class PostResource extends Resource
protected static ?string $modelLabel = 'pod';
protected static ?string $pluralModelLabel = 'posturi de lucru';
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('posturi de lucru');
}
protected static ?int $navigationSort = 76;
@@ -44,37 +49,37 @@ class PostResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Pod / Spațiu lucru')
Schemas\Components\Section::make(__('Pod / Spațiu lucru'))
->columns(2)
->schema([
Forms\Components\TextInput::make('name')
->label('Nume')
->label(__('Nume'))
->required()
->maxLength(80)
->placeholder('Ex: Pod 1, Curte 1, Atelier electric'),
->placeholder(__('Ex: Pod 1, Curte 1, Atelier electric')),
Forms\Components\ColorPicker::make('color')
->default('#3b82f6'),
Forms\Components\TextInput::make('hours_per_day')
->label('Ore disponibile / zi')
->label(__('Ore disponibile / zi'))
->numeric()
->step(0.5)
->default(10)
->helperText('Capacitatea zilnică în ore'),
->helperText(__('Capacitatea zilnică în ore')),
Forms\Components\Select::make('default_master_id')
->label('Mecanic implicit')
->label(__('Mecanic implicit'))
->options(fn () => User::where('status', 'active')->pluck('name', 'id'))
->searchable()
->placeholder('Niciun mecanic implicit')
->helperText('Va fi pre-completat când creezi o programare pentru acest pod'),
->placeholder(__('Niciun mecanic implicit'))
->helperText(__('Va fi pre-completat când creezi o programare pentru acest pod')),
Forms\Components\TextInput::make('description')
->label('Descriere')
->label(__('Descriere'))
->maxLength(255)
->placeholder('Ex: cu lift, fără lift, doar diagnoză...')
->placeholder(__('Ex: cu lift, fără lift, doar diagnoză...'))
->columnSpanFull(),
Forms\Components\TextInput::make('sort_order')
->numeric()
->default(100),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]),
]);
}
@@ -85,11 +90,11 @@ class PostResource extends Resource
->columns([
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\ColorColumn::make('color'),
Tables\Columns\TextColumn::make('hours_per_day')->label('Ore/zi')->sortable(),
Tables\Columns\TextColumn::make('defaultMaster.name')->label('Mecanic implicit')->placeholder('—'),
Tables\Columns\TextColumn::make('hours_per_day')->label(__('Ore/zi'))->sortable(),
Tables\Columns\TextColumn::make('defaultMaster.name')->label(__('Mecanic implicit'))->placeholder('—'),
Tables\Columns\TextColumn::make('description')->placeholder('—')->limit(40)->toggleable(),
Tables\Columns\TextColumn::make('appointments_count')->counts('appointments')->label('Programări')->badge(),
Tables\Columns\ToggleColumn::make('is_active')->label('Activ'),
Tables\Columns\TextColumn::make('appointments_count')->counts('appointments')->label(__('Programări'))->badge(),
Tables\Columns\ToggleColumn::make('is_active')->label(__('Activ')),
])
->actions([
Actions\EditAction::make(),
@@ -28,57 +28,53 @@ class PricingCoefficientResource extends Resource
return __('nav.group.Depozit');
}
protected static ?string $modelLabel = 'coeficient';
protected static ?string $pluralModelLabel = 'coeficienți preț';
protected static ?int $navigationSort = 46;
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Coeficient')
Schemas\Components\Section::make(__('Coeficient'))
->columns(2)
->schema([
Forms\Components\TextInput::make('name')->label('Denumire')->required()
->placeholder('ex: Mașină veche, Client VIP, Express')->columnSpanFull(),
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()
->placeholder(__('ex: Mașină veche, Client VIP, Express'))->columnSpanFull(),
Forms\Components\TextInput::make('multiplier')
->label('Multiplicator')
->label(__('Multiplicator'))
->numeric()
->required()
->default(1.10)
->helperText('1.15 = +15% peste prețul de bază. 0.95 = -5%.'),
Forms\Components\TextInput::make('priority')->label('Prioritate')->numeric()->default(100),
->helperText(__('1.15 = +15% peste prețul de bază. 0.95 = -5%.')),
Forms\Components\TextInput::make('priority')->label(__('Prioritate'))->numeric()->default(100),
Forms\Components\Toggle::make('stackable')
->label('Cumulabil')
->label(__('Cumulabil'))
->default(true)
->helperText('Cumulabil = se înmulțește cu alți coeficienți. Necumulabil = doar cel mai mare necumulabil se aplică.'),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
->helperText(__('Cumulabil = se înmulțește cu alți coeficienți. Necumulabil = doar cel mai mare necumulabil se aplică.')),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]),
Schemas\Components\Section::make('Condiții (toate trebuie îndeplinite)')
->description('Lasă gol = se aplică mereu. Combină condițiile pentru a ținti situații specifice.')
Schemas\Components\Section::make(__('Condiții (toate trebuie îndeplinite)'))
->description(__('Lasă gol = se aplică mereu. Combină condițiile pentru a ținti situații specifice.'))
->columns(2)
->schema([
Forms\Components\CheckboxList::make('conditions.classes')
->label('Clase auto')
->label(__('Clase auto'))
->options(PricingCoefficient::VEHICLE_CLASSES)
->columns(2)
->columnSpanFull(),
Forms\Components\CheckboxList::make('conditions.body_types')
->label('Caroserie')
->label(__('Caroserie'))
->options(\App\Models\Tenant\Vehicle::BODY_TYPES)
->columns(3)
->columnSpanFull(),
Forms\Components\CheckboxList::make('conditions.transmissions')
->label('Cutie de viteze')
->label(__('Cutie de viteze'))
->options(\App\Models\Tenant\Vehicle::TRANSMISSION_TYPES)
->columns(3)
->columnSpanFull(),
Forms\Components\TextInput::make('conditions.age_min')->label('Vârstă min (ani)')->numeric(),
Forms\Components\TextInput::make('conditions.age_max')->label('Vârstă max (ani)')->numeric(),
Forms\Components\Toggle::make('conditions.client_vip')->label('Doar clienți VIP'),
Forms\Components\TextInput::make('conditions.age_min')->label(__('Vârstă min (ani)'))->numeric(),
Forms\Components\TextInput::make('conditions.age_max')->label(__('Vârstă max (ani)'))->numeric(),
Forms\Components\Toggle::make('conditions.client_vip')->label(__('Doar clienți VIP')),
Forms\Components\CheckboxList::make('conditions.urgency')
->label('Urgență')
->label(__('Urgență'))
->options(PricingCoefficient::URGENCY)
->columns(3)
->columnSpanFull(),
@@ -90,25 +86,25 @@ class PricingCoefficientResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('priority')->label('Prio')->sortable()->alignRight(),
Tables\Columns\TextColumn::make('priority')->label(__('Prio'))->sortable()->alignRight(),
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('multiplier')
->label('Multiplicator')
->label(__('Multiplicator'))
->formatStateUsing(fn ($s) => '×' . rtrim(rtrim(number_format((float) $s, 3), '0'), '.'))
->alignRight()
->color(fn ($s) => (float) $s >= 1 ? 'success' : 'warning'),
Tables\Columns\IconColumn::make('stackable')->label('Cumul.')->boolean(),
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(),
Tables\Columns\IconColumn::make('stackable')->label(__('Cumul.'))->boolean(),
Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
])
->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Active'),
Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
])
->actions([
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Niciun coeficient')
->emptyStateDescription('Adaugă reguli care ajustează prețul în funcție de vârsta mașinii, clasă (SUV, comercial, hibrid), client VIP sau urgență. Se aplică peste markup-ul de bază pe fișele de lucru.')
->emptyStateHeading(__('Niciun coeficient'))
->emptyStateDescription(__('Adaugă reguli care ajustează prețul în funcție de vârsta mașinii, clasă (SUV, comercial, hibrid), client VIP sau urgență. Se aplică peste markup-ul de bază pe fișele de lucru.'))
->emptyStateIcon('heroicon-o-adjustments-horizontal')
->defaultSort('priority');
}
@@ -32,26 +32,36 @@ class PurchaseResource extends Resource
return __('nav.group.Depozit');
}
protected static ?string $modelLabel = 'achiziție';
protected static ?string $modelLabel = null;
protected static ?string $pluralModelLabel = 'achiziții';
public static function getModelLabel(): string
{
return __('achiziție');
}
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('achiziții');
}
protected static ?int $navigationSort = 43;
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Antet')
Schemas\Components\Section::make(__('Antet'))
->columns(3)
->schema([
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('Generat automat'),
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false)->placeholder(__('Generat automat')),
Forms\Components\Select::make('supplier_id')
->label('Furnizor')
->label(__('Furnizor'))
->options(fn () => Supplier::where('is_active', true)->pluck('name', 'id'))
->searchable()
->required(),
Forms\Components\Select::make('warehouse_id')
->label('Depozit țintă')
->label(__('Depozit țintă'))
->options(fn () => Warehouse::where('is_active', true)->pluck('name', 'id'))
->default(fn () => Warehouse::where('is_default', true)->value('id'))
->required(),
@@ -59,12 +69,12 @@ class PurchaseResource extends Resource
->options(Purchase::STATUSES)
->default('draft')
->required(),
Forms\Components\DatePicker::make('order_date')->label('Data comandă')->default(today())->required(),
Forms\Components\DatePicker::make('expected_at')->label('Așteptată'),
Forms\Components\DatePicker::make('received_at')->label('Recepționată'),
Forms\Components\DatePicker::make('paid_at')->label('Plătită')->columnSpanFull(),
Forms\Components\DatePicker::make('order_date')->label(__('Data comandă'))->default(today())->required(),
Forms\Components\DatePicker::make('expected_at')->label(__('Așteptată')),
Forms\Components\DatePicker::make('received_at')->label(__('Recepționată')),
Forms\Components\DatePicker::make('paid_at')->label(__('Plătită'))->columnSpanFull(),
]),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]);
}
@@ -72,11 +82,11 @@ class PurchaseResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->searchable(),
Tables\Columns\TextColumn::make('order_date')->label('Comandată')->date('d.m.Y'),
Tables\Columns\TextColumn::make('expected_at')->label('Așteptată')->date('d.m.Y')->placeholder('—'),
Tables\Columns\TextColumn::make('received_at')->label('Recepționată')->date('d.m.Y')->placeholder('—'),
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->searchable(),
Tables\Columns\TextColumn::make('order_date')->label(__('Comandată'))->date('d.m.Y'),
Tables\Columns\TextColumn::make('expected_at')->label(__('Așteptată'))->date('d.m.Y')->placeholder('—'),
Tables\Columns\TextColumn::make('received_at')->label(__('Recepționată'))->date('d.m.Y')->placeholder('—'),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => Purchase::STATUSES[$s] ?? $s)
->badge()
@@ -88,7 +98,7 @@ class PurchaseResource extends Resource
'danger' => ['cancelled'],
]),
Tables\Columns\TextColumn::make('received_progress')
->label('Progres')
->label(__('Progres'))
->state(function (Purchase $r) {
$items = $r->items;
$ord = (float) $items->sum('qty');
@@ -101,27 +111,27 @@ class PurchaseResource extends Resource
->filters([
Tables\Filters\SelectFilter::make('status')->options(Purchase::STATUSES),
Tables\Filters\SelectFilter::make('supplier_id')
->label('Furnizor')
->label(__('Furnizor'))
->options(fn () => Supplier::pluck('name', 'id')),
])
->actions([
Actions\Action::make('receive_all')
->label('Recepție totală')
->label(__('Recepție totală'))
->icon('heroicon-m-check-circle')
->color('success')
->visible(fn (Purchase $r) => ! in_array($r->status, ['received', 'cancelled', 'draft'], true))
->requiresConfirmation()
->modalDescription('Se vor crea batch-uri pentru toate restanțele rămase în depozitul țintă.')
->modalDescription(__('Se vor crea batch-uri pentru toate restanțele rămase în depozitul țintă.'))
->action(function (Purchase $r) {
try {
$r->receiveAllRemaining();
Notification::make()
->title('Recepție completă — batch-uri create')
->title(__('Recepție completă — batch-uri create'))
->success()
->send();
} catch (\Throwable $e) {
Notification::make()
->title('Eroare')
->title(__('Eroare'))
->body($e->getMessage())
->danger()
->send();
@@ -21,14 +21,14 @@ class ListPurchases extends ListRecords
{
return [
Actions\Action::make('ocr')
->label('Import factură (OCR)')
->label(__('Import factură (OCR)'))
->icon('heroicon-m-document-arrow-up')
->color('gray')
->modalHeading('Import factură via OCR')
->modalDescription('Încarcă o poză cu factura. AI-ul extrage furnizorul, data și liniile. Verifici și salvezi.')
->modalHeading(__('Import factură via OCR'))
->modalDescription(__('Încarcă o poză cu factura. AI-ul extrage furnizorul, data și liniile. Verifici și salvezi.'))
->schema([
Forms\Components\FileUpload::make('invoice')
->label('Foto factură')
->label(__('Foto factură'))
->image()
->disk('local')
->directory('ocr-imports')
@@ -41,7 +41,7 @@ class ListPurchases extends ListRecords
if (! ($result['ok'] ?? false)) {
Notification::make()
->title('OCR eșuat')
->title(__('OCR eșuat'))
->body($result['error'] ?? 'Eroare necunoscută.')
->danger()->send();
@unlink($abs);
@@ -83,7 +83,7 @@ class ListPurchases extends ListRecords
@unlink($abs);
Notification::make()
->title('Factură importată')
->title(__('Factură importată'))
->body(sprintf('%d linii, total %.2f. Verifică și ajustează înainte de a confirma.',
count($payload['items']), (float) $purchase->total))
->success()->send();
@@ -18,13 +18,18 @@ class ItemsRelationManager extends RelationManager
{
protected static string $relationship = 'items';
protected static ?string $title = 'Articole';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Articole');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\Select::make('part_id')
->label('Piesă din catalog')
->label(__('Piesă din catalog'))
->options(fn () => Part::where('is_active', true)
->get()
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])
@@ -40,11 +45,11 @@ class ItemsRelationManager extends RelationManager
}
})
->columnSpanFull(),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(),
Forms\Components\TextInput::make('article')->label('Cod articol'),
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(),
Forms\Components\TextInput::make('unit')->label('UM')->default('buc'),
Forms\Components\TextInput::make('buy_price')->label('Preț achiziție')->numeric()->required(),
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
Forms\Components\TextInput::make('article')->label(__('Cod articol')),
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)->required(),
Forms\Components\TextInput::make('unit')->label(__('UM'))->default('buc'),
Forms\Components\TextInput::make('buy_price')->label(__('Preț achiziție'))->numeric()->required(),
]);
}
@@ -55,13 +60,13 @@ class ItemsRelationManager extends RelationManager
->columns([
Tables\Columns\TextColumn::make('name')->wrap(),
Tables\Columns\TextColumn::make('article')->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label('Comandat')->alignRight(),
Tables\Columns\TextColumn::make('qty')->label(__('Comandat'))->alignRight(),
Tables\Columns\TextColumn::make('qty_received')
->label('Recepționat')
->label(__('Recepționat'))
->alignRight()
->color(fn ($state, $record) => $record->isFullyReceived() ? 'success' : ((float) $state > 0 ? 'warning' : 'gray'))
->formatStateUsing(fn ($state, $record) => sprintf('%.2f / %.2f', (float) $state, (float) $record->qty)),
Tables\Columns\TextColumn::make('unit')->label('UM'),
Tables\Columns\TextColumn::make('unit')->label(__('UM')),
Tables\Columns\TextColumn::make('buy_price')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
])
@@ -70,22 +75,22 @@ class ItemsRelationManager extends RelationManager
])
->actions([
Actions\Action::make('receive_item')
->label('Recepționează')
->label(__('Recepționează'))
->icon('heroicon-m-arrow-down-tray')
->color('success')
->visible(fn (PurchaseItem $r) => ! $r->isFullyReceived())
->schema([
Forms\Components\Placeholder::make('outstanding')
->label('Restanță')
->label(__('Restanță'))
->content(fn (PurchaseItem $r) => sprintf('%.2f %s', $r->outstanding(), $r->unit ?? 'buc')),
Forms\Components\TextInput::make('qty')
->label('Cantitate recepționată')
->label(__('Cantitate recepționată'))
->numeric()
->required()
->minValue(0.001)
->default(fn (PurchaseItem $r) => $r->outstanding()),
Forms\Components\Select::make('warehouse_id')
->label('Depozit țintă')
->label(__('Depozit țintă'))
->options(fn () => Warehouse::where('is_active', true)->pluck('name', 'id'))
->default(fn (PurchaseItem $r) => $r->purchase?->warehouse_id
?? Warehouse::where('is_default', true)->value('id'))
@@ -96,12 +101,12 @@ class ItemsRelationManager extends RelationManager
try {
$r->purchase->receiveItem($r, (float) $data['qty'], $wh);
Notification::make()
->title('Recepționat — batch creat')
->title(__('Recepționat — batch creat'))
->success()
->send();
} catch (\Throwable $e) {
Notification::make()
->title('Eroare la recepție')
->title(__('Eroare la recepție'))
->body($e->getMessage())
->danger()
->send();
+10 -10
View File
@@ -43,16 +43,16 @@ class RoleResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Rol')
Schemas\Components\Section::make(__('Rol'))
->columns(2)
->schema([
Forms\Components\TextInput::make('name')->label('Slug')->required()->maxLength(64)
Forms\Components\TextInput::make('name')->label(__('Slug'))->required()->maxLength(64)
->disabled(fn ($record) => $record && in_array($record->name, array_keys(Permissions::roleMatrix()), true))
->helperText('Rolurile sistem (owner/admin/etc.) au numele blocat'),
->helperText(__('Rolurile sistem (owner/admin/etc.) au numele blocat')),
Forms\Components\TextInput::make('guard_name')->default('web')->disabled(),
]),
Schemas\Components\Section::make('Drepturi')
->description('Bifează ce poate face acest rol. Modificările au efect imediat.')
Schemas\Components\Section::make(__('Drepturi'))
->description(__('Bifează ce poate face acest rol. Modificările au efect imediat.'))
->schema(self::permissionFields())
->columns(1),
]);
@@ -88,25 +88,25 @@ class RoleResource extends Resource
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->label('Rol')
->label(__('Rol'))
->formatStateUsing(fn ($state) => Permissions::roleLabels()[$state] ?? $state)
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('name')
->label('Slug')
->label(__('Slug'))
->copyable()
->color('gray'),
Tables\Columns\TextColumn::make('permissions_count')
->counts('permissions')
->label('Drepturi')
->label(__('Drepturi'))
->badge(),
Tables\Columns\TextColumn::make('users_count')
->counts('users')
->label('Utilizatori')
->label(__('Utilizatori'))
->badge(),
])
->actions([
Actions\EditAction::make()->label('Editează drepturi'),
Actions\EditAction::make()->label(__('Editează drepturi')),
Actions\DeleteAction::make()
->hidden(fn ($record) => in_array($record->name, array_keys(Permissions::roleMatrix()), true)),
])
@@ -30,9 +30,19 @@ class ServiceTemplateResource extends Resource
return __('nav.group.Service');
}
protected static ?string $modelLabel = 'șablon';
protected static ?string $modelLabel = null;
protected static ?string $pluralModelLabel = 'șabloane servicii';
public static function getModelLabel(): string
{
return __('șablon');
}
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('șabloane servicii');
}
protected static ?int $navigationSort = 33;
@@ -42,14 +52,14 @@ class ServiceTemplateResource extends Resource
Schemas\Components\Section::make()
->columns(2)
->schema([
Forms\Components\TextInput::make('name')->label('Denumire')->required()
->placeholder('ex: Revizie completă 15.000 km')->columnSpanFull(),
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()
->placeholder(__('ex: Revizie completă 15.000 km'))->columnSpanFull(),
Forms\Components\Select::make('category')
->label('Categorie')
->label(__('Categorie'))
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES))
->searchable(),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]),
]);
}
@@ -60,18 +70,18 @@ class ServiceTemplateResource extends Resource
->columns([
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('items_count')->counts('items')->label('Linii')->alignRight(),
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(),
Tables\Columns\TextColumn::make('items_count')->counts('items')->label(__('Linii'))->alignRight(),
Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
])
->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Active'),
Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
])
->actions([
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Niciun șablon')
->emptyStateDescription('Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet") și aplică-l pe o fișă cu un click.')
->emptyStateHeading(__('Niciun șablon'))
->emptyStateDescription(__('Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet") și aplică-l pe o fișă cu un click.'))
->emptyStateIcon('heroicon-o-clipboard-document-list')
->defaultSort('name');
}
@@ -18,19 +18,24 @@ class ItemsRelationManager extends RelationManager
{
protected static string $relationship = 'items';
protected static ?string $title = 'Conținut șablon';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Conținut șablon');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\Select::make('kind')
->label('Tip')
->label(__('Tip'))
->options(ServiceTemplateItem::KINDS)
->default('labor')
->live()
->required(),
Forms\Components\Select::make('labor_id')
->label('Manoperă')
->label(__('Manoperă'))
->options(fn () => Labor::where('is_active', true)->pluck('name_ro', 'id'))
->searchable()
->visible(fn (Get $get) => $get('kind') === 'labor')
@@ -42,7 +47,7 @@ class ItemsRelationManager extends RelationManager
}
}),
Forms\Components\Select::make('part_id')
->label('Piesă')
->label(__('Piesă'))
->options(fn () => Part::where('is_active', true)
->get()->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])->toArray())
->searchable()
@@ -51,10 +56,10 @@ class ItemsRelationManager extends RelationManager
->afterStateUpdated(function ($state, Set $set) {
if ($state && $p = Part::find($state)) $set('name', $p->name);
}),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(),
Forms\Components\TextInput::make('hours')->label('Ore')->numeric()
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
Forms\Components\TextInput::make('hours')->label(__('Ore'))->numeric()
->visible(fn (Get $get) => $get('kind') === 'labor'),
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)
->visible(fn (Get $get) => $get('kind') === 'part'),
]);
}
@@ -65,13 +70,13 @@ class ItemsRelationManager extends RelationManager
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('kind')
->label('Tip')
->label(__('Tip'))
->formatStateUsing(fn ($s) => ServiceTemplateItem::KINDS[$s] ?? $s)
->badge()
->color(fn ($s) => $s === 'labor' ? 'info' : 'gray'),
Tables\Columns\TextColumn::make('name')->wrap(),
Tables\Columns\TextColumn::make('hours')->label('Ore')->placeholder('—')->alignRight(),
Tables\Columns\TextColumn::make('qty')->label('Cant.')->placeholder('—')->alignRight(),
Tables\Columns\TextColumn::make('hours')->label(__('Ore'))->placeholder('—')->alignRight(),
Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->placeholder('—')->alignRight(),
])
->headerActions([Actions\CreateAction::make()])
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()]);
@@ -32,24 +32,20 @@ class ShopCustomerResource extends Resource
return __('nav.group.Magazin');
}
protected static ?string $modelLabel = 'client magazin';
protected static ?string $pluralModelLabel = 'clienți magazin';
protected static ?int $navigationSort = 52;
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make()->columns(2)->schema([
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(160),
Forms\Components\TextInput::make('phone')->label('Telefon')->required()->maxLength(40),
Forms\Components\TextInput::make('email')->label('Email')->email()->maxLength(160),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(160),
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->required()->maxLength(40),
Forms\Components\TextInput::make('email')->label(__('Email'))->email()->maxLength(160),
Forms\Components\Select::make('client_id')
->label('Client legat (CRM)')
->label(__('Client legat (CRM)'))
->options(fn () => \App\Models\Tenant\Client::pluck('name', 'id'))
->searchable()
->helperText('Legătura cu fișa CRM (opțional). Auto-matched la înregistrare după telefon.'),
->helperText(__('Legătura cu fișa CRM (opțional). Auto-matched la înregistrare după telefon.')),
]),
]);
}
@@ -61,19 +57,19 @@ class ShopCustomerResource extends Resource
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(),
Tables\Columns\TextColumn::make('email')->placeholder('—')->copyable()->toggleable(),
Tables\Columns\TextColumn::make('client.name')->label('Client CRM')->placeholder('—')->toggleable(),
Tables\Columns\TextColumn::make('orders_count')->counts('orders')->label('Comenzi')->alignRight(),
Tables\Columns\TextColumn::make('last_login_at')->label('Ultim login')->since()->placeholder('Niciodată'),
Tables\Columns\TextColumn::make('created_at')->label('Înregistrat')->date('d.m.Y')->toggleable(),
Tables\Columns\TextColumn::make('client.name')->label(__('Client CRM'))->placeholder('—')->toggleable(),
Tables\Columns\TextColumn::make('orders_count')->counts('orders')->label(__('Comenzi'))->alignRight(),
Tables\Columns\TextColumn::make('last_login_at')->label(__('Ultim login'))->since()->placeholder(__('Niciodată')),
Tables\Columns\TextColumn::make('created_at')->label(__('Înregistrat'))->date('d.m.Y')->toggleable(),
])
->actions([
Actions\Action::make('reset_password')
->label('Trimite reset parolă')
->label(__('Trimite reset parolă'))
->icon('heroicon-m-key')
->color('warning')
->visible(fn (ShopCustomer $r) => ! empty($r->email))
->requiresConfirmation()
->modalDescription('Trimite emailul standard de resetare a parolei către clientul magazinului.')
->modalDescription(__('Trimite emailul standard de resetare a parolei către clientul magazinului.'))
->action(function (ShopCustomer $r) {
$status = Password::broker('shop_customers')->sendResetLink(['email' => $r->email]);
Notification::make()
@@ -86,8 +82,8 @@ class ShopCustomerResource extends Resource
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Niciun client magazin')
->emptyStateDescription('Aici apar clienții care și-au creat cont în magazinul online (/shop/register).')
->emptyStateHeading(__('Niciun client magazin'))
->emptyStateDescription(__('Aici apar clienții care și-au creat cont în magazinul online (/shop/register).'))
->emptyStateIcon('heroicon-o-user-circle')
->defaultSort('created_at', 'desc');
}
@@ -11,15 +11,20 @@ class OrdersRelationManager extends RelationManager
{
protected static string $relationship = 'orders';
protected static ?string $title = 'Comenzi';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Comenzi');
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('number')
->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.'),
Tables\Columns\TextColumn::make('created_at')->label('Data')->dateTime('d.m.Y H:i'),
Tables\Columns\TextColumn::make('number')->label(__('Nr.')),
Tables\Columns\TextColumn::make('created_at')->label(__('Data'))->dateTime('d.m.Y H:i'),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => OnlineOrder::STATUSES[$s] ?? $s)
->badge()
@@ -33,6 +38,6 @@ class OrdersRelationManager extends RelationManager
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
])
->defaultSort('created_at', 'desc')
->emptyStateHeading('Nicio comandă încă');
->emptyStateHeading(__('Nicio comandă încă'));
}
}
@@ -30,10 +30,6 @@ class SubcontractJobResource extends Resource
return __('nav.group.Subcontractare');
}
protected static ?string $modelLabel = 'lucrare terți';
protected static ?string $pluralModelLabel = 'lucrări terți';
protected static ?int $navigationSort = 71;
public static function getNavigationBadge(): ?string
@@ -45,43 +41,43 @@ class SubcontractJobResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Lucrare')
Schemas\Components\Section::make(__('Lucrare'))
->columns(2)
->schema([
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('Generat automat'),
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false)->placeholder(__('Generat automat')),
Forms\Components\Select::make('status')->options(SubcontractJob::STATUSES)->default('sent')->required(),
Forms\Components\Select::make('subcontractor_id')
->label('Subcontractor')
->label(__('Subcontractor'))
->options(fn () => Subcontractor::where('is_active', true)->pluck('name', 'id'))
->searchable(),
Forms\Components\Select::make('work_order_id')
->label('Fișă asociată')
->label(__('Fișă asociată'))
->options(fn () => WorkOrder::whereNotIn('status', ['done', 'cancelled'])
->get()->mapWithKeys(fn ($w) => [$w->id => "#{$w->number} · " . ($w->vehicle?->plate ?? '')])->toArray())
->searchable(),
Forms\Components\Select::make('category')
->label('Categorie')
->label(__('Categorie'))
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
->searchable(),
Forms\Components\Textarea::make('description')->label('Descriere')->rows(2)->columnSpanFull(),
Forms\Components\Textarea::make('description')->label(__('Descriere'))->rows(2)->columnSpanFull(),
]),
Schemas\Components\Section::make('Cost & marjă')
Schemas\Components\Section::make(__('Cost & marjă'))
->columns(3)
->schema([
Forms\Components\TextInput::make('cost')->label('Cost (de la terț)')->numeric()->default(0)->required(),
Forms\Components\TextInput::make('markup_pct')->label('Markup %')->numeric()->default(0)
->helperText('> 0 calculează automat prețul client.'),
Forms\Components\TextInput::make('client_price')->label('Preț client')->numeric()->default(0)
->helperText('Setat manual dacă markup = 0.'),
Forms\Components\Toggle::make('paid_to_sub')->label('Plătit către terț'),
Forms\Components\TextInput::make('cost')->label(__('Cost (de la terț)'))->numeric()->default(0)->required(),
Forms\Components\TextInput::make('markup_pct')->label(__('Markup %'))->numeric()->default(0)
->helperText(__('> 0 calculează automat prețul client.')),
Forms\Components\TextInput::make('client_price')->label(__('Preț client'))->numeric()->default(0)
->helperText(__('Setat manual dacă markup = 0.')),
Forms\Components\Toggle::make('paid_to_sub')->label(__('Plătit către terț')),
]),
Schemas\Components\Section::make('Termene')
Schemas\Components\Section::make(__('Termene'))
->columns(3)
->schema([
Forms\Components\DatePicker::make('sent_at')->label('Trimis')->default(today()),
Forms\Components\DatePicker::make('eta')->label('ETA'),
Forms\Components\DatePicker::make('returned_at')->label('Returnat'),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\DatePicker::make('sent_at')->label(__('Trimis'))->default(today()),
Forms\Components\DatePicker::make('eta')->label(__('ETA')),
Forms\Components\DatePicker::make('returned_at')->label(__('Returnat')),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]),
]);
}
@@ -90,14 +86,14 @@ class SubcontractJobResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
Tables\Columns\TextColumn::make('subcontractor.name')->label('Terț')->placeholder('—'),
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('subcontractor.name')->label(__('Terț'))->placeholder('—'),
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('workOrder.number')->label('Fișă')->placeholder('—'),
Tables\Columns\TextColumn::make('cost')->label('Cost')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('client_price')->label('Preț client')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('workOrder.number')->label(__('Fișă'))->placeholder('—'),
Tables\Columns\TextColumn::make('cost')->label(__('Cost'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('client_price')->label(__('Preț client'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('margin')
->label('Marjă')
->label(__('Marjă'))
->state(fn (SubcontractJob $r) => $r->margin())
->money('MDL')
->alignRight()
@@ -110,20 +106,20 @@ class SubcontractJobResource extends Resource
'success' => ['done', 'returned'],
'danger' => ['cancelled'],
]),
Tables\Columns\IconColumn::make('paid_to_sub')->label('Plătit terț')->boolean()->toggleable(),
Tables\Columns\IconColumn::make('paid_to_sub')->label(__('Plătit terț'))->boolean()->toggleable(),
])
->filters([
Tables\Filters\SelectFilter::make('status')->options(SubcontractJob::STATUSES),
Tables\Filters\SelectFilter::make('subcontractor_id')
->label('Subcontractor')
->label(__('Subcontractor'))
->options(fn () => Subcontractor::pluck('name', 'id')),
])
->actions([
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Nicio lucrare la terți')
->emptyStateDescription('Înregistrează lucrările trimise la ateliere externe (turbo, cutii, vopsitorie). Costul terțului + markup intră automat în totalul fișei asociate.')
->emptyStateHeading(__('Nicio lucrare la terți'))
->emptyStateDescription(__('Înregistrează lucrările trimise la ateliere externe (turbo, cutii, vopsitorie). Costul terțului + markup intră automat în totalul fișei asociate.'))
->emptyStateIcon('heroicon-o-arrow-top-right-on-square')
->defaultSort('created_at', 'desc');
}
@@ -38,19 +38,19 @@ class SubcontractorResource extends Resource
{
return $schema->components([
Schemas\Components\Section::make()->columns(2)->schema([
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(160),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(160),
Forms\Components\Select::make('specialty')
->label('Specialitate')
->label(__('Specialitate'))
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
->searchable(),
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->maxLength(40),
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->email()->maxLength(120),
Forms\Components\Select::make('rating')
->label('Rating')
->label(__('Rating'))
->options([1 => '★', 2 => '★★', 3 => '★★★', 4 => '★★★★', 5 => '★★★★★'])
->default(3),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]),
]);
}
@@ -63,18 +63,18 @@ class SubcontractorResource extends Resource
Tables\Columns\TextColumn::make('specialty')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('phone')->copyable()->placeholder('—'),
Tables\Columns\TextColumn::make('rating')->formatStateUsing(fn ($s) => str_repeat('★', (int) $s)),
Tables\Columns\TextColumn::make('jobs_count')->counts('jobs')->label('Lucrări')->alignRight(),
Tables\Columns\TextColumn::make('jobs_count')->counts('jobs')->label(__('Lucrări'))->alignRight(),
Tables\Columns\IconColumn::make('is_active')->boolean(),
])
->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Activi'),
Tables\Filters\TernaryFilter::make('is_active')->label(__('Activi')),
])
->actions([
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Niciun subcontractor')
->emptyStateDescription('Adaugă atelierele terțe la care trimiți lucrări (turbo, cutii, vopsitorie, PDR) și urmărește costul + marja.')
->emptyStateHeading(__('Niciun subcontractor'))
->emptyStateDescription(__('Adaugă atelierele terțe la care trimiți lucrări (turbo, cutii, vopsitorie, PDR) și urmărește costul + marja.'))
->emptyStateIcon('heroicon-o-user-group')
->defaultSort('name');
}
@@ -37,30 +37,30 @@ class SupplierResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Identificare')
Schemas\Components\Section::make(__('Identificare'))
->columns(2)
->schema([
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(160),
Forms\Components\TextInput::make('contact_name')->label('Persoană contact')->maxLength(120),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(160),
Forms\Components\TextInput::make('contact_name')->label(__('Persoană contact'))->maxLength(120),
Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->email()->maxLength(120),
Forms\Components\TextInput::make('website')->url()->maxLength(160),
]),
Schemas\Components\Section::make('Comercial')
Schemas\Components\Section::make(__('Comercial'))
->columns(3)
->schema([
Forms\Components\TextInput::make('pay_terms')->label('Termeni plată')->placeholder('Net 30 / Avans')->maxLength(60),
Forms\Components\TextInput::make('delivery_days')->label('Zile livrare')->numeric()->default(0),
Forms\Components\TextInput::make('pay_terms')->label(__('Termeni plată'))->placeholder(__('Net 30 / Avans'))->maxLength(60),
Forms\Components\TextInput::make('delivery_days')->label(__('Zile livrare'))->numeric()->default(0),
Forms\Components\Select::make('rating')
->label('Rating')
->label(__('Rating'))
->options([1 => '★', 2 => '★★', 3 => '★★★', 4 => '★★★★', 5 => '★★★★★'])
->default(3)
->required(),
Forms\Components\TextInput::make('discount_pct')->label('Discount %')->numeric()->default(0),
Forms\Components\TagsInput::make('categories')->label('Categorii')->placeholder('Frâne, Ulei, ...')->columnSpan(2),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
Forms\Components\TextInput::make('discount_pct')->label(__('Discount %'))->numeric()->default(0),
Forms\Components\TagsInput::make('categories')->label(__('Categorii'))->placeholder(__('Frâne, Ulei, ...'))->columnSpan(2),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]);
}
@@ -69,41 +69,41 @@ class SupplierResource extends Resource
return $table
->columns([
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('contact_name')->label('Contact')->placeholder('—'),
Tables\Columns\TextColumn::make('contact_name')->label(__('Contact'))->placeholder('—'),
Tables\Columns\TextColumn::make('phone')->copyable()->placeholder('—'),
Tables\Columns\TextColumn::make('rating')
->label('Rating')
->label(__('Rating'))
->formatStateUsing(fn ($s) => str_repeat('★', (int) $s)),
Tables\Columns\TextColumn::make('on_time_pct')
->label('La timp 90d')
->label(__('La timp 90d'))
->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->onTimeRate($r))
->formatStateUsing(fn ($s) => $s === null ? '—' : "{$s}%")
->color(fn ($s) => $s === null ? 'gray' : ($s >= 90 ? 'success' : ($s >= 70 ? 'warning' : 'danger')))
->alignRight()
->toggleable(),
Tables\Columns\TextColumn::make('avg_delivery_days')
->label('Avg zile')
->label(__('Avg zile'))
->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->avgDeliveryDays($r))
->formatStateUsing(fn ($s) => $s === null ? '—' : (string) $s)
->alignRight()
->toggleable(),
Tables\Columns\TextColumn::make('spend_90d')
->label('Cheltuit 90d')
->label(__('Cheltuit 90d'))
->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->spend($r))
->money('MDL')
->alignRight()
->toggleable(),
Tables\Columns\TextColumn::make('delivery_days')->label('Livrare (zile)')->alignRight()->toggleable(),
Tables\Columns\TextColumn::make('discount_pct')->label('Discount')
Tables\Columns\TextColumn::make('delivery_days')->label(__('Livrare (zile)'))->alignRight()->toggleable(),
Tables\Columns\TextColumn::make('discount_pct')->label(__('Discount'))
->formatStateUsing(fn ($s) => $s . '%')->alignRight()->toggleable(),
Tables\Columns\IconColumn::make('is_active')->boolean(),
])
->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Activi'),
Tables\Filters\TernaryFilter::make('is_active')->label(__('Activi')),
])
->actions([
Actions\Action::make('rate')
->label('Rerating')
->label(__('Rerating'))
->icon('heroicon-m-arrow-path')
->color('gray')
->action(function (Supplier $r) {
@@ -111,8 +111,8 @@ class SupplierResource extends Resource
->computedRating($r);
if ($score === null) {
\Filament\Notifications\Notification::make()
->title('Date insuficiente')
->body('Necesită cel puțin 2 recepții complete cu data așteptată setată.')
->title(__('Date insuficiente'))
->body(__('Necesită cel puțin 2 recepții complete cu data așteptată setată.'))
->warning()
->send();
return;
@@ -32,10 +32,6 @@ class TireSetResource extends Resource
return __('nav.group.Anvelope');
}
protected static ?string $modelLabel = 'set anvelope';
protected static ?string $pluralModelLabel = 'seturi anvelope';
protected static ?int $navigationSort = 60;
public static function getNavigationBadge(): ?string
@@ -52,53 +48,53 @@ class TireSetResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Proprietar')
Schemas\Components\Section::make(__('Proprietar'))
->columns(2)
->schema([
Forms\Components\Select::make('client_id')
->label('Client')
->label(__('Client'))
->options(fn () => Client::pluck('name', 'id'))
->searchable()
->live()
->required(),
Forms\Components\Select::make('vehicle_id')
->label('Auto')
->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(),
Forms\Components\TextInput::make('label')->label('Etichetă')->placeholder('ex: Iarnă Michelin'),
Forms\Components\Select::make('season')->label('Sezon')->options(TireSet::SEASONS)->default('winter')->required(),
Forms\Components\TextInput::make('label')->label(__('Etichetă'))->placeholder(__('ex: Iarnă Michelin')),
Forms\Components\Select::make('season')->label(__('Sezon'))->options(TireSet::SEASONS)->default('winter')->required(),
]),
Schemas\Components\Section::make('Specificații')
Schemas\Components\Section::make(__('Specificații'))
->columns(3)
->schema([
Forms\Components\TextInput::make('width')->label('Lățime')->numeric()->placeholder('205'),
Forms\Components\TextInput::make('profile')->label('Profil')->numeric()->placeholder('55'),
Forms\Components\TextInput::make('diameter')->label('Diametru R')->numeric()->placeholder('16'),
Forms\Components\TextInput::make('width')->label(__('Lățime'))->numeric()->placeholder('205'),
Forms\Components\TextInput::make('profile')->label(__('Profil'))->numeric()->placeholder('55'),
Forms\Components\TextInput::make('diameter')->label(__('Diametru R'))->numeric()->placeholder('16'),
Forms\Components\TextInput::make('brand')->maxLength(64),
Forms\Components\TextInput::make('model')->maxLength(64),
Forms\Components\TextInput::make('dot_year')->label('DOT')->maxLength(8)->placeholder('3621'),
Forms\Components\Toggle::make('has_rims')->label('Cu jante'),
Forms\Components\Select::make('rim_type')->label('Tip jante')->options(['steel' => 'Tablă', 'alloy' => 'Aliaj']),
Forms\Components\Select::make('condition')->label('Stare')->options(TireSet::CONDITIONS),
Forms\Components\TextInput::make('dot_year')->label(__('DOT'))->maxLength(8)->placeholder('3621'),
Forms\Components\Toggle::make('has_rims')->label(__('Cu jante')),
Forms\Components\Select::make('rim_type')->label(__('Tip jante'))->options(['steel' => 'Tablă', 'alloy' => 'Aliaj']),
Forms\Components\Select::make('condition')->label(__('Stare'))->options(TireSet::CONDITIONS),
]),
Schemas\Components\Section::make('Uzură (mm) per poziție')
Schemas\Components\Section::make(__('Uzură (mm) per poziție'))
->columns(4)
->schema([
Forms\Components\TextInput::make('tread.fl')->label('Față-Stânga')->numeric(),
Forms\Components\TextInput::make('tread.fr')->label('Față-Dreapta')->numeric(),
Forms\Components\TextInput::make('tread.rl')->label('Spate-Stânga')->numeric(),
Forms\Components\TextInput::make('tread.rr')->label('Spate-Dreapta')->numeric(),
Forms\Components\TextInput::make('tread.fl')->label(__('Față-Stânga'))->numeric(),
Forms\Components\TextInput::make('tread.fr')->label(__('Față-Dreapta'))->numeric(),
Forms\Components\TextInput::make('tread.rl')->label(__('Spate-Stânga'))->numeric(),
Forms\Components\TextInput::make('tread.rr')->label(__('Spate-Dreapta'))->numeric(),
]),
Schemas\Components\Section::make('TPMS & foto')
Schemas\Components\Section::make(__('TPMS & foto'))
->columns(2)
->schema([
Forms\Components\Toggle::make('tpms')->label('Senzori TPMS'),
Forms\Components\TextInput::make('notes')->label('Observații'),
Forms\Components\Toggle::make('tpms')->label(__('Senzori TPMS')),
Forms\Components\TextInput::make('notes')->label(__('Observații')),
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos')
->label('Fotografii')
->label(__('Fotografii'))
->collection('photos')
->multiple()
->image()
@@ -112,23 +108,23 @@ class TireSetResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable()->sortable(),
Tables\Columns\TextColumn::make('label')->label('Etichetă')->placeholder('—'),
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('label')->label(__('Etichetă'))->placeholder('—'),
Tables\Columns\TextColumn::make('size')
->label('Dimensiune')
->label(__('Dimensiune'))
->state(fn (TireSet $r) => $r->sizeLabel()),
Tables\Columns\TextColumn::make('season')
->label('Sezon')
->label(__('Sezon'))
->formatStateUsing(fn ($s) => TireSet::SEASONS[$s] ?? $s)
->badge()
->colors(['warning' => ['summer'], 'info' => ['winter'], 'gray' => ['allseason']]),
Tables\Columns\TextColumn::make('tread_min')->label('Uzură min')
Tables\Columns\TextColumn::make('tread_min')->label(__('Uzură min'))
->formatStateUsing(fn ($s) => $s ? $s . ' mm' : '—')
->color(fn ($s) => $s !== null && (float) $s < 3 ? 'danger' : null)
->alignRight(),
Tables\Columns\IconColumn::make('tpms')->label('TPMS')->boolean()->toggleable(),
Tables\Columns\IconColumn::make('tpms')->label(__('TPMS'))->boolean()->toggleable(),
Tables\Columns\TextColumn::make('storage_status')
->label('Depozit')
->label(__('Depozit'))
->state(fn (TireSet $r) => $r->isStored() ? ($r->currentStorage()?->location ?? 'da') : '—')
->badge()
->color(fn ($state) => $state === '—' ? 'gray' : 'success'),
@@ -136,19 +132,19 @@ class TireSetResource extends Resource
->filters([
Tables\Filters\SelectFilter::make('season')->options(TireSet::SEASONS),
Tables\Filters\Filter::make('stored')
->label('În depozit')
->label(__('În depozit'))
->query(fn ($q) => $q->whereHas('storage', fn ($s) => $s->where('status', 'stored'))),
])
->actions([
Actions\Action::make('check_in')
->label('Check-in depozit')
->label(__('Check-in depozit'))
->icon('heroicon-m-arrow-down-on-square')
->color('success')
->visible(fn (TireSet $r) => ! $r->isStored())
->schema([
Forms\Components\TextInput::make('location')->label('Locație (raft)')->required()->placeholder('A1-03'),
Forms\Components\TextInput::make('season_label')->label('Perioadă')->placeholder('Iarnă 2025-2026'),
Forms\Components\TextInput::make('fee')->label('Taxă depozitare')->numeric()->default(0),
Forms\Components\TextInput::make('location')->label(__('Locație (raft)'))->required()->placeholder(__('A1-03')),
Forms\Components\TextInput::make('season_label')->label(__('Perioadă'))->placeholder(__('Iarnă 2025-2026')),
Forms\Components\TextInput::make('fee')->label(__('Taxă depozitare'))->numeric()->default(0),
])
->action(function (TireSet $r, array $data) {
\App\Models\Tenant\TireStorage::create([
@@ -159,27 +155,27 @@ class TireSetResource extends Resource
'status' => 'stored',
'checked_in_at' => now(),
]);
\Filament\Notifications\Notification::make()->title('Set primit în depozit')->success()->send();
\Filament\Notifications\Notification::make()->title(__('Set primit în depozit'))->success()->send();
}),
Actions\Action::make('check_out')
->label('Eliberează')
->label(__('Eliberează'))
->icon('heroicon-m-arrow-up-on-square')
->color('warning')
->visible(fn (TireSet $r) => $r->isStored())
->requiresConfirmation()
->modalDescription('Marchează setul ca ridicat de client.')
->modalDescription(__('Marchează setul ca ridicat de client.'))
->action(function (TireSet $r) {
$storage = $r->currentStorage();
if ($storage) {
$storage->update(['status' => 'retrieved', 'checked_out_at' => now()]);
}
\Filament\Notifications\Notification::make()->title('Set eliberat din depozit')->success()->send();
\Filament\Notifications\Notification::make()->title(__('Set eliberat din depozit'))->success()->send();
}),
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Niciun set de anvelope')
->emptyStateDescription('Înregistrează seturile de anvelope ale clienților și gestionează depozitarea sezonieră (tire hotel). Urmărește uzura, TPMS și locația în depozit.')
->emptyStateHeading(__('Niciun set de anvelope'))
->emptyStateDescription(__('Înregistrează seturile de anvelope ale clienților și gestionează depozitarea sezonieră (tire hotel). Urmărește uzura, TPMS și locația în depozit.'))
->emptyStateIcon('heroicon-o-lifebuoy')
->defaultSort('created_at', 'desc');
}
@@ -11,25 +11,30 @@ class StorageRelationManager extends RelationManager
{
protected static string $relationship = 'storage';
protected static ?string $title = 'Istoric depozitare';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Istoric depozitare');
}
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('season_label')->label('Perioadă')->placeholder('—'),
Tables\Columns\TextColumn::make('location')->label('Locație')->placeholder('—'),
Tables\Columns\TextColumn::make('checked_in_at')->label('Primit')->dateTime('d.m.Y'),
Tables\Columns\TextColumn::make('checked_out_at')->label('Ridicat')->dateTime('d.m.Y')->placeholder('—'),
Tables\Columns\TextColumn::make('season_label')->label(__('Perioadă'))->placeholder('—'),
Tables\Columns\TextColumn::make('location')->label(__('Locație'))->placeholder('—'),
Tables\Columns\TextColumn::make('checked_in_at')->label(__('Primit'))->dateTime('d.m.Y'),
Tables\Columns\TextColumn::make('checked_out_at')->label(__('Ridicat'))->dateTime('d.m.Y')->placeholder('—'),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => TireStorage::STATUSES[$s] ?? $s)
->badge()
->colors(['success' => ['stored'], 'gray' => ['retrieved']]),
Tables\Columns\TextColumn::make('fee')->money('MDL')->alignRight(),
Tables\Columns\IconColumn::make('paid')->label('Plătit')->boolean(),
Tables\Columns\IconColumn::make('paid')->label(__('Plătit'))->boolean(),
])
->defaultSort('checked_in_at', 'desc')
->emptyStateHeading('Niciun istoric')
->emptyStateDescription('Folosește „Check-in depozit" pe set pentru a înregistra prima depozitare.');
->emptyStateHeading(__('Niciun istoric'))
->emptyStateDescription(__('Folosește „Check-in depozit" pe set pentru a înregistra prima depozitare.'));
}
}
+26 -26
View File
@@ -54,39 +54,39 @@ class UserResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Identitate')
Schemas\Components\Section::make(__('Identitate'))
->columns(2)
->schema([
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(120),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(120),
Forms\Components\TextInput::make('email')->email()->required()->maxLength(120),
Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
Forms\Components\Select::make('locale')
->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English'])
->default('ro'),
]),
Schemas\Components\Section::make('Acces')
Schemas\Components\Section::make(__('Acces'))
->columns(2)
->schema([
Forms\Components\Select::make('role')
->label('Rol primar')
->label(__('Rol primar'))
->options(\App\Auth\Permissions::roleLabels())
->required()
->default('mechanic')
->helperText('Rolul principal — sincronizat automat cu drepturile RBAC.'),
->helperText(__('Rolul principal — sincronizat automat cu drepturile RBAC.')),
Forms\Components\Select::make('status')
->options(['active' => 'Activ', 'inactive' => 'Inactiv', 'blocked' => 'Blocat'])
->default('active')
->required(),
Forms\Components\TextInput::make('password')
->label('Parolă')
->label(__('Parolă'))
->password()
->required(fn (string $context) => $context === 'create')
->dehydrated(fn ($state) => filled($state))
->dehydrateStateUsing(fn ($state) => Hash::make($state))
->minLength(6)
->helperText('La editare lasă gol pentru a păstra parola actuală.'),
->helperText(__('La editare lasă gol pentru a păstra parola actuală.')),
Forms\Components\Select::make('roles_picked')
->label('Roluri suplimentare')
->label(__('Roluri suplimentare'))
->multiple()
->options(\App\Auth\Permissions::roleLabels())
->afterStateHydrated(function ($component, $record) {
@@ -94,35 +94,35 @@ class UserResource extends Resource
})
->dehydrated(false)
->columnSpanFull()
->helperText('Roluri suplimentare peste rolul primar — drepturile se cumulează.'),
->helperText(__('Roluri suplimentare peste rolul primar — drepturile se cumulează.')),
]),
Schemas\Components\Section::make('Salariu & marjă')
->description('Configurează procentele pentru calcul salariu. Marja internă (nu TVA) se scade din prețul de manoperă pentru a determina baza salariului.')
Schemas\Components\Section::make(__('Salariu & marjă'))
->description(__('Configurează procentele pentru calcul salariu. Marja internă (nu TVA) se scade din prețul de manoperă pentru a determina baza salariului.'))
->columns(2)
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false)
->schema([
Forms\Components\TextInput::make('hourly_rate')
->label('Tarif orar (MDL)')
->label(__('Tarif orar (MDL)'))
->numeric()
->step(0.01)
->placeholder('Ex: 100'),
->placeholder(__('Ex: 100')),
Forms\Components\TextInput::make('internal_margin_pct')
->label('Marjă internă (%)')
->label(__('Marjă internă (%)'))
->numeric()
->step(0.01)
->minValue(0)
->maxValue(90)
->placeholder('Ex: 20 pentru +20%')
->helperText('Doar la manopere (proprii + subcontract). Baza salariu = preț client × (1 marjă/100). Lasă gol pentru a folosi valoarea implicită a companiei.'),
->placeholder(__('Ex: 20 pentru +20%'))
->helperText(__('Doar la manopere (proprii + subcontract). Baza salariu = preț client × (1 marjă/100). Lasă gol pentru a folosi valoarea implicită a companiei.')),
]),
Schemas\Components\Section::make('Securitate')
Schemas\Components\Section::make(__('Securitate'))
->columns(2)
->schema([
Forms\Components\Placeholder::make('mfa_status')
->label('Autentificare 2FA')
->label(__('Autentificare 2FA'))
->content(fn ($record) => $record && $record->hasTwoFactorEnabled() ? '✓ Activat (TOTP)' : '✗ Dezactivat'),
Forms\Components\Placeholder::make('last_login')
->label('Ultima autentificare')
->label(__('Ultima autentificare'))
->content(fn ($record) => $record?->last_login_at?->diffForHumans() ?? '—'),
]),
]);
@@ -139,7 +139,7 @@ class UserResource extends Resource
->formatStateUsing(fn ($state) => \App\Auth\Permissions::roleLabels()[$state] ?? $state)
->badge(),
Tables\Columns\IconColumn::make('app_authentication_secret')
->label('2FA')
->label(__('2FA'))
->boolean()
->getStateUsing(fn ($record) => $record->hasTwoFactorEnabled())
->trueIcon('heroicon-o-shield-check')
@@ -147,14 +147,14 @@ class UserResource extends Resource
->falseIcon('heroicon-o-shield-exclamation')
->falseColor('warning'),
Tables\Columns\TextColumn::make('active_sessions')
->label('Sesiuni')
->label(__('Sesiuni'))
->getStateUsing(fn ($record) => \Illuminate\Support\Facades\DB::table('sessions')->where('user_id', $record->id)->count())
->badge()
->color(fn ($state) => $state > 0 ? 'success' : 'gray')
->toggleable(),
Tables\Columns\TextColumn::make('permission_overrides_count')
->counts('permissionOverrides')
->label('Excepții')
->label(__('Excepții'))
->badge()
->color('warning')
->toggleable(isToggledHiddenByDefault: true),
@@ -180,7 +180,7 @@ class UserResource extends Resource
->actions([
Actions\EditAction::make(),
Actions\Action::make('force_logout')
->label('Force logout')
->label(__('Force logout'))
->icon('heroicon-o-arrow-right-on-rectangle')
->color('warning')
->visible(fn ($record) => \Illuminate\Support\Facades\DB::table('sessions')->where('user_id', $record->id)->exists())
@@ -191,16 +191,16 @@ class UserResource extends Resource
\Filament\Notifications\Notification::make()->title("$n sesiuni revoke-uite")->success()->send();
}),
Actions\Action::make('reset_2fa')
->label('Resetează 2FA')
->label(__('Resetează 2FA'))
->icon('heroicon-o-shield-exclamation')
->color('warning')
->visible(fn ($record) => $record && $record->hasTwoFactorEnabled())
->requiresConfirmation()
->modalDescription('Dezactivează 2FA pentru acest utilizator. Va trebui să re-configureze TOTP la următoarea autentificare.')
->modalDescription(__('Dezactivează 2FA pentru acest utilizator. Va trebui să re-configureze TOTP la următoarea autentificare.'))
->action(function ($record) {
$record->saveAppAuthenticationSecret(null);
$record->saveAppAuthenticationRecoveryCodes(null);
\Filament\Notifications\Notification::make()->title('2FA resetat')->success()->send();
\Filament\Notifications\Notification::make()->title(__('2FA resetat'))->success()->send();
}),
Actions\DeleteAction::make(),
])
@@ -16,18 +16,23 @@ class PermissionOverridesRelationManager extends RelationManager
{
protected static string $relationship = 'permissionOverrides';
protected static ?string $title = 'Excepții drepturi';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Excepții drepturi');
}
protected static string|\BackedEnum|null $icon = 'heroicon-o-shield-exclamation';
public function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Excepție')
Schemas\Components\Section::make(__('Excepție'))
->columns(2)
->schema([
Forms\Components\Select::make('permission_id')
->label('Drept')
->label(__('Drept'))
->required()
->searchable()
->options(fn () => Permission::orderBy('name')->pluck('name', 'id'))
@@ -37,12 +42,12 @@ class PermissionOverridesRelationManager extends RelationManager
->options(['grant' => 'GRANT — adaugă dreptul', 'deny' => 'DENY — interzice dreptul'])
->default('grant'),
Forms\Components\DatePicker::make('expires_at')
->label('Expiră la (opțional)')
->label(__('Expiră la (opțional)'))
->minDate(now()),
Forms\Components\Textarea::make('reason')
->label('Motiv')
->label(__('Motiv'))
->columnSpanFull()
->placeholder('Ex: lockdown temporar; acces pentru audit; etc.')
->placeholder(__('Ex: lockdown temporar; acces pentru audit; etc.'))
->rows(2),
]),
]);
@@ -54,7 +59,7 @@ class PermissionOverridesRelationManager extends RelationManager
->recordTitleAttribute('mode')
->columns([
Tables\Columns\TextColumn::make('permission.name')
->label('Drept')
->label(__('Drept'))
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('mode')
@@ -65,12 +70,12 @@ class PermissionOverridesRelationManager extends RelationManager
->limit(40)
->placeholder('—'),
Tables\Columns\TextColumn::make('expires_at')
->label('Expiră')
->label(__('Expiră'))
->date()
->placeholder('niciodată')
->placeholder(__('niciodată'))
->color(fn ($record) => $record?->isExpired() ? 'danger' : 'gray'),
Tables\Columns\TextColumn::make('grantedBy.name')
->label('Acordat de')
->label(__('Acordat de'))
->placeholder('—')
->toggleable(),
])
@@ -24,9 +24,19 @@ class VehicleResource extends Resource
return __('nav.label.Automobile');
}
protected static ?string $modelLabel = 'mașină';
protected static ?string $modelLabel = null;
protected static ?string $pluralModelLabel = 'mașini';
public static function getModelLabel(): string
{
return __('mașină');
}
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('mașini');
}
protected static ?int $navigationSort = 20;
@@ -51,21 +61,21 @@ class VehicleResource extends Resource
public static function form(Schema $schema): Schema
{
return $schema->components([
Schemas\Components\Section::make('Identificare')
Schemas\Components\Section::make(__('Identificare'))
->columns(2)
->schema([
Forms\Components\Select::make('client_id')
->label('Proprietar')
->label(__('Proprietar'))
->options(fn () => Client::pluck('name', 'id'))
->searchable()
->required(),
Forms\Components\TextInput::make('plate')->label('Nr. înmatriculare')->maxLength(16),
Forms\Components\TextInput::make('make')->label('Marca')->required()->maxLength(60),
Forms\Components\TextInput::make('plate')->label(__('Nr. înmatriculare'))->maxLength(16),
Forms\Components\TextInput::make('make')->label(__('Marca'))->required()->maxLength(60),
Forms\Components\TextInput::make('model')->required()->maxLength(60),
Forms\Components\TextInput::make('year')->numeric()->minValue(1950)->maxValue(2100),
Forms\Components\TextInput::make('vin')->maxLength(32),
]),
Schemas\Components\Section::make('Tehnice')
Schemas\Components\Section::make(__('Tehnice'))
->columns(2)
->schema([
Forms\Components\TextInput::make('engine')->maxLength(60),
@@ -76,13 +86,13 @@ class VehicleResource extends Resource
'EV' => 'Electric', 'GPL' => 'GPL', 'GNC' => 'GNC',
]),
Forms\Components\Select::make('vehicle_class')
->label('Clasă (pentru pricing)')
->label(__('Clasă (pentru pricing)'))
->options(\App\Models\Tenant\PricingCoefficient::VEHICLE_CLASSES)
->helperText('Folosită de coeficienții de preț. Hibrid/EV se deduc și din combustibil.'),
Forms\Components\TextInput::make('mileage')->label('Kilometraj')->numeric()->default(0),
->helperText(__('Folosită de coeficienții de preț. Hibrid/EV se deduc și din combustibil.')),
Forms\Components\TextInput::make('mileage')->label(__('Kilometraj'))->numeric()->default(0),
Forms\Components\TextInput::make('color')->maxLength(40),
]),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(3),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(3),
]);
}
@@ -90,18 +100,18 @@ class VehicleResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('plate')->label('Nr.')->searchable(),
Tables\Columns\TextColumn::make('plate')->label(__('Nr.'))->searchable(),
Tables\Columns\TextColumn::make('make')->sortable(),
Tables\Columns\TextColumn::make('model'),
Tables\Columns\TextColumn::make('year'),
Tables\Columns\TextColumn::make('client.name')->label('Proprietar')->searchable(),
Tables\Columns\TextColumn::make('client.name')->label(__('Proprietar'))->searchable(),
Tables\Columns\TextColumn::make('vin')->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('mileage')->label('Km')->numeric(),
Tables\Columns\TextColumn::make('mileage')->label(__('Km'))->numeric(),
Tables\Columns\TextColumn::make('created_at')->date()->sortable(),
])
->actions([
Actions\Action::make('decode_vin')
->label('Decode VIN')
->label(__('Decode VIN'))
->icon('heroicon-m-cpu-chip')
->color('gray')
->visible(fn (\App\Models\Tenant\Vehicle $r) => ! empty($r->vin) && strlen($r->vin) === 17)
@@ -113,11 +123,11 @@ class VehicleResource extends Resource
return view('filament.tenant.vin-decode', ['info' => $info, 'vehicle' => $r]);
}),
Actions\Action::make('ai_recommend')
->label('AI: recomandări')
->label(__('AI: recomandări'))
->icon('heroicon-m-sparkles')
->color('primary')
->visible(fn (\App\Models\Tenant\Vehicle $r) => ! empty($r->vin))
->modalHeading('Recomandări AI')
->modalHeading(__('Recomandări AI'))
->modalSubmitAction(false)
->modalCancelActionLabel('Închide')
->modalContent(function (\App\Models\Tenant\Vehicle $r) {
@@ -128,8 +138,8 @@ class VehicleResource extends Resource
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Nicio mașină încă')
->emptyStateDescription('Adaugă mașini manual sau importă din CSV. Folosește VIN-căutare pentru decoder rapid și completare automată brand/model/an.')
->emptyStateHeading(__('Nicio mașină încă'))
->emptyStateDescription(__('Adaugă mașini manual sau importă din CSV. Folosește VIN-căutare pentru decoder rapid și completare automată brand/model/an.'))
->emptyStateIcon('heroicon-o-truck')
->defaultSort('created_at', 'desc');
}
@@ -17,16 +17,16 @@ class ListVehicles extends ListRecords
{
return [
Actions\Action::make('export')
->label('Export CSV')
->label(__('Export CSV'))
->icon('heroicon-m-arrow-down-tray')
->color('gray')
->action(fn () => app(CsvImportExport::class)->exportVehicles()),
Actions\Action::make('import')
->label('Import CSV')
->label(__('Import CSV'))
->icon('heroicon-m-arrow-up-tray')
->color('gray')
->modalHeading('Import mașini din CSV')
->modalDescription('CSV cu header: ' . implode(', ', CsvImportExport::VEHICLE_COLUMNS) . '. Coloana client_phone trebuie să existe deja la clienți.')
->modalHeading(__('Import mașini din CSV'))
->modalDescription(__('CSV cu header: ' . implode(', ', CsvImportExport::VEHICLE_COLUMNS) . '. Coloana client_phone trebuie să existe deja la clienți.'))
->schema([
Forms\Components\FileUpload::make('file')
->required()
@@ -38,11 +38,11 @@ class WarehouseResource extends Resource
{
return $schema->components([
Schemas\Components\Section::make()->columns(2)->schema([
Forms\Components\TextInput::make('code')->label('Cod')->required()->maxLength(32),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->maxLength(120),
Forms\Components\TextInput::make('address')->label('Adresă')->columnSpanFull()->maxLength(200),
Forms\Components\Toggle::make('is_default')->label('Depozit implicit'),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
Forms\Components\TextInput::make('code')->label(__('Cod'))->required()->maxLength(32),
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->maxLength(120),
Forms\Components\TextInput::make('address')->label(__('Adresă'))->columnSpanFull()->maxLength(200),
Forms\Components\Toggle::make('is_default')->label(__('Depozit implicit')),
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]),
]);
}
@@ -54,19 +54,19 @@ class WarehouseResource extends Resource
Tables\Columns\TextColumn::make('code')->searchable()->sortable(),
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('address')->placeholder('—')->toggleable(),
Tables\Columns\IconColumn::make('is_default')->label('Implicit')->boolean(),
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(),
Tables\Columns\IconColumn::make('is_default')->label(__('Implicit'))->boolean(),
Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
Tables\Columns\TextColumn::make('batches_count')
->counts('batches')
->label('Loturi')
->label(__('Loturi'))
->alignRight(),
])
->actions([
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Niciun depozit')
->emptyStateDescription('Un depozit implicit a fost creat la migrare. Adaugă altele dacă ai locații fizice separate (sucursală, hală, mobil).')
->emptyStateHeading(__('Niciun depozit'))
->emptyStateDescription(__('Un depozit implicit a fost creat la migrare. Adaugă altele dacă ai locații fizice separate (sucursală, hală, mobil).'))
->emptyStateIcon('heroicon-o-building-storefront')
->defaultSort('code');
}
@@ -34,9 +34,19 @@ class WorkOrderResource extends Resource
return __('nav.group.Service');
}
protected static ?string $modelLabel = 'fișă';
protected static ?string $modelLabel = null;
protected static ?string $pluralModelLabel = 'fișe lucru';
public static function getModelLabel(): string
{
return __('fișă');
}
protected static ?string $pluralModelLabel = null;
public static function getPluralModelLabel(): string
{
return __('fișe lucru');
}
protected static ?int $navigationSort = 30;
@@ -63,76 +73,76 @@ class WorkOrderResource extends Resource
{
return $schema->components([
// Antet compact — o singură secțiune densă cu doar esențialul, mereu deschisă.
Schemas\Components\Section::make('Antet fișă')
->description('Informația de bază — click pe titlul secțiunilor de mai jos pentru detalii.')
Schemas\Components\Section::make(__('Antet fișă'))
->description(__('Informația de bază — click pe titlul secțiunilor de mai jos pentru detalii.'))
->compact()
->columns(4)
->schema([
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('auto'),
Forms\Components\DatePicker::make('opened_at')->label('Deschis')->default(today())->required(),
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false)->placeholder('auto'),
Forms\Components\DatePicker::make('opened_at')->label(__('Deschis'))->default(today())->required(),
Forms\Components\Select::make('status')->options(WorkOrder::STATUSES)->default('new')->required(),
Forms\Components\Select::make('urgency')->label('Urgență')
Forms\Components\Select::make('urgency')->label(__('Urgență'))
->options(\App\Models\Tenant\PricingCoefficient::URGENCY)->default('normal')->required(),
Forms\Components\Select::make('client_id')->label('Client')
Forms\Components\Select::make('client_id')->label(__('Client'))
->options(fn () => Client::pluck('name', 'id'))
->searchable()->live()->required()->columnSpan(2),
Forms\Components\Select::make('vehicle_id')->label('Auto')
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),
Forms\Components\Select::make('master_id')->label('Maistru')
Forms\Components\Select::make('master_id')->label(__('Maistru'))
->options(fn () => User::where('status', 'active')->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(),
Forms\Components\TextInput::make('mileage_in')->label(__('Km intrare'))->numeric(),
Forms\Components\TextInput::make('mileage_out')->label(__('Km ieșire'))->numeric(),
]),
// Toate secțiunile de detaliu — collapsible și collapsed by default
Schemas\Components\Section::make('Diagnostic')
Schemas\Components\Section::make(__('Diagnostic'))
->collapsible()->collapsed()->compact()
->schema([
Forms\Components\Textarea::make('complaint')->label('Plângere client')->rows(2)->columnSpanFull(),
Forms\Components\Textarea::make('diagnosis')->label('Diagnostic')->rows(3)->columnSpanFull(),
Forms\Components\Textarea::make('recommendations')->label('Recomandări')->rows(2)->columnSpanFull(),
Forms\Components\Textarea::make('complaint')->label(__('Plângere client'))->rows(2)->columnSpanFull(),
Forms\Components\Textarea::make('diagnosis')->label(__('Diagnostic'))->rows(3)->columnSpanFull(),
Forms\Components\Textarea::make('recommendations')->label(__('Recomandări'))->rows(2)->columnSpanFull(),
]),
Schemas\Components\Section::make('Foto')
Schemas\Components\Section::make(__('Foto'))
->collapsible()->collapsed()->compact()
->schema([
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos')
->label('Fotografii')->collection('photos')
->label(__('Fotografii'))->collection('photos')
->multiple()->reorderable()->image()->imageEditor()->maxFiles(20)
->columnSpanFull(),
]),
Schemas\Components\Section::make('Tracking & ETA')
Schemas\Components\Section::make(__('Tracking & ETA'))
->columns(3)->collapsible()->collapsed()->compact()
->schema([
Forms\Components\DateTimePicker::make('eta_at')->label('Gata estimat (ETA)')->seconds(false),
Forms\Components\TextInput::make('tracking_token')->label('Token public')
Forms\Components\DateTimePicker::make('eta_at')->label(__('Gata estimat (ETA)'))->seconds(false),
Forms\Components\TextInput::make('tracking_token')->label(__('Token public'))
->disabled()->dehydrated(false)->columnSpan(2)
->helperText(fn (?WorkOrder $record) => $record?->tracking_token
? 'Link client: ' . $record->trackingUrl()
: 'Se generează la salvare'),
]),
Schemas\Components\Section::make('Plată & total')
Schemas\Components\Section::make(__('Plată & total'))
->columns(3)->collapsible()->collapsed()->compact()
->schema([
Forms\Components\Select::make('pay_status')->options(WorkOrder::PAY_STATUSES)->default('unpaid')->required(),
Forms\Components\TextInput::make('discount_pct')->label('Discount %')->numeric()->default(0),
Forms\Components\TextInput::make('discount_pct')->label(__('Discount %'))->numeric()->default(0),
Forms\Components\Toggle::make('apply_margin')
->label('Aplică marjă internă')
->label(__('Aplică marjă internă'))
->default(true)
->helperText('On = din prețul manoperelor se scade marja pentru salariu. Off = manopere la cost (salariu pe Total integral).')
->helperText(__('On = din prețul manoperelor se scade marja pentru salariu. Off = manopere la cost (salariu pe Total integral).'))
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false),
Forms\Components\TextInput::make('override_margin_pct')->label('Marjă internă (%) — override')
Forms\Components\TextInput::make('override_margin_pct')->label(__('Marjă internă (%) — override'))
->numeric()->step(0.01)->minValue(0)->maxValue(90)
->placeholder('Ex: 25')
->helperText('Doar pentru cazuri speciale. Lasă gol → folosește marja mecanicului.')
->placeholder(__('Ex: 25'))
->helperText(__('Doar pentru cazuri speciale. Lasă gol → folosește marja mecanicului.'))
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false),
Forms\Components\TextInput::make('total')->label('Total')->numeric()->disabled()->dehydrated(false),
Forms\Components\Toggle::make('approved')->label('Aprobat de client'),
Forms\Components\DatePicker::make('closed_at')->label('Închis'),
Forms\Components\TextInput::make('total')->label(__('Total'))->numeric()->disabled()->dehydrated(false),
Forms\Components\Toggle::make('approved')->label(__('Aprobat de client')),
Forms\Components\DatePicker::make('closed_at')->label(__('Închis')),
]),
]);
}
@@ -141,11 +151,11 @@ class WorkOrderResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
Tables\Columns\TextColumn::make('opened_at')->label('Deschis')->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable(),
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'),
Tables\Columns\TextColumn::make('master.name')->label('Maistru')->placeholder('—'),
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('opened_at')->label(__('Deschis'))->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($state) => WorkOrder::STATUSES[$state] ?? $state)
->badge()
@@ -170,12 +180,12 @@ class WorkOrderResource extends Resource
Tables\Filters\SelectFilter::make('status')->options(WorkOrder::STATUSES),
Tables\Filters\SelectFilter::make('pay_status')->options(WorkOrder::PAY_STATUSES),
Tables\Filters\SelectFilter::make('master_id')
->label('Maistru')
->label(__('Maistru'))
->options(fn () => User::pluck('name', 'id')),
])
->actions([
Actions\Action::make('pdf')
->label('PDF')
->label(__('PDF'))
->icon('heroicon-m-document-arrow-down')
->color('gray')
->action(function (WorkOrder $r) {
@@ -190,8 +200,8 @@ class WorkOrderResource extends Resource
Actions\EditAction::make(),
Actions\DeleteAction::make(),
])
->emptyStateHeading('Nicio fișă de lucru')
->emptyStateDescription('Crează prima fișă pentru o mașină existentă. Adaugă manopere, piese, plăți — totalul se calculează automat.')
->emptyStateHeading(__('Nicio fișă de lucru'))
->emptyStateDescription(__('Crează prima fișă pentru o mașină existentă. Adaugă manopere, piese, plăți — totalul se calculează automat.'))
->emptyStateIcon('heroicon-o-wrench-screwdriver')
->defaultSort('opened_at', 'desc');
}
@@ -33,12 +33,12 @@ class EditWorkOrder extends EditRecord
{
return [
Actions\Action::make('apply_template')
->label('Aplică șablon')
->label(__('Aplică șablon'))
->icon('heroicon-m-clipboard-document-list')
->color('gray')
->schema([
\Filament\Forms\Components\Select::make('template_id')
->label('Șablon serviciu')
->label(__('Șablon serviciu'))
->options(fn () => \App\Models\Tenant\ServiceTemplate::where('is_active', true)->pluck('name', 'id'))
->searchable()
->required(),
@@ -53,11 +53,11 @@ class EditWorkOrder extends EditRecord
->success()->send();
}),
Actions\Action::make('ai_diagnose')
->label('AI: sugerează diagnostic')
->label(__('AI: sugerează diagnostic'))
->icon('heroicon-m-sparkles')
->color('primary')
->visible(fn () => ! empty($this->record->complaint))
->modalHeading('Diagnostic AI bazat pe plângerea clientului')
->modalHeading(__('Diagnostic AI bazat pe plângerea clientului'))
->modalSubmitAction(false)
->modalCancelActionLabel('Închide')
->modalContent(function () {
@@ -66,7 +66,7 @@ class EditWorkOrder extends EditRecord
return view('filament.tenant.ai-reply', ['reply' => $reply, 'meta' => $meta]);
}),
Actions\Action::make('tracking')
->label('Link client (QR)')
->label(__('Link client (QR)'))
->icon('heroicon-m-qr-code')
->color('primary')
->modalHeading(fn () => 'Tracking client — WO #' . $this->record->number)
@@ -76,7 +76,7 @@ class EditWorkOrder extends EditRecord
'wo' => $this->record,
])),
Actions\Action::make('pdf')
->label('Descarcă PDF')
->label(__('Descarcă PDF'))
->icon('heroicon-m-document-arrow-down')
->color('gray')
->action(function () {
@@ -19,13 +19,18 @@ class PartsRelationManager extends RelationManager
{
protected static string $relationship = 'parts';
protected static ?string $title = 'Piese';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Piese');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\Select::make('part_id')
->label('Din catalog (lasă gol pentru text liber)')
->label(__('Din catalog (lasă gol pentru text liber)'))
->options(fn () => Part::where('is_active', true)
->get()
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}] " : '') . "(stoc: {$p->qty})"])
@@ -43,20 +48,20 @@ class PartsRelationManager extends RelationManager
}
})
->columnSpanFull(),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(),
Forms\Components\TextInput::make('article')->label('Cod articol')->maxLength(64),
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
Forms\Components\TextInput::make('article')->label(__('Cod articol'))->maxLength(64),
Forms\Components\TextInput::make('brand')->maxLength(64),
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(),
Forms\Components\TextInput::make('unit')->label('UM')->maxLength(16)->default('buc'),
Forms\Components\TextInput::make('buy_price')->label('Preț achiziție')->numeric()->default(0),
Forms\Components\TextInput::make('sell_price')->label('Preț vânzare')->numeric()->required(),
Forms\Components\TextInput::make('discount_pct')->label('Discount %')->numeric()->default(0),
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)->required(),
Forms\Components\TextInput::make('unit')->label(__('UM'))->maxLength(16)->default('buc'),
Forms\Components\TextInput::make('buy_price')->label(__('Preț achiziție'))->numeric()->default(0),
Forms\Components\TextInput::make('sell_price')->label(__('Preț vânzare'))->numeric()->required(),
Forms\Components\TextInput::make('discount_pct')->label(__('Discount %'))->numeric()->default(0),
Forms\Components\Select::make('status')
->options(WorkOrderPart::STATUSES)
->default('needed')
->required()
->helperText('La trecere pe „Montată" se scade automat din stoc.'),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
->helperText(__('La trecere pe „Montată" se scade automat din stoc.')),
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]);
}
@@ -65,11 +70,11 @@ class PartsRelationManager extends RelationManager
return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name')->label('Piesă')->wrap(),
Tables\Columns\TextColumn::make('article')->label('Cod')->placeholder('—'),
Tables\Columns\TextColumn::make('name')->label(__('Piesă'))->wrap(),
Tables\Columns\TextColumn::make('article')->label(__('Cod'))->placeholder('—'),
Tables\Columns\TextColumn::make('brand')->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(),
Tables\Columns\TextColumn::make('sell_price')->label('Preț')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
Tables\Columns\TextColumn::make('sell_price')->label(__('Preț'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => WorkOrderPart::STATUSES[$s] ?? $s)
@@ -86,11 +91,11 @@ class PartsRelationManager extends RelationManager
])
->actions([
Actions\Action::make('smart_price')
->label('Preț inteligent')
->label(__('Preț inteligent'))
->icon('heroicon-m-sparkles')
->color('primary')
->visible(fn (WorkOrderPart $r) => (bool) $r->part_id)
->modalHeading('Preț contextual')
->modalHeading(__('Preț contextual'))
->modalSubmitActionLabel('Aplică prețul')
->modalContent(function (WorkOrderPart $r) {
$wo = $r->workOrder;
@@ -112,7 +117,7 @@ class PartsRelationManager extends RelationManager
->success()->send();
}),
Actions\Action::make('issue_now')
->label('Eliberează')
->label(__('Eliberează'))
->icon('heroicon-m-arrow-up-on-square')
->color('warning')
->visible(fn (WorkOrderPart $r) => $r->part_id
@@ -128,7 +133,7 @@ class PartsRelationManager extends RelationManager
->success()->send();
}),
Actions\Action::make('return_part')
->label('Restituire')
->label(__('Restituire'))
->icon('heroicon-m-arrow-uturn-left')
->color('gray')
->visible(fn (WorkOrderPart $r) => $r->part_id
@@ -137,12 +142,12 @@ class PartsRelationManager extends RelationManager
->exists())
->schema([
Forms\Components\TextInput::make('qty')
->label('Cantitate restituită')
->label(__('Cantitate restituită'))
->numeric()
->required()
->minValue(0.001)
->default(fn (WorkOrderPart $r) => (float) $r->qty),
Forms\Components\Textarea::make('notes')->rows(2)->label('Observații'),
Forms\Components\Textarea::make('notes')->rows(2)->label(__('Observații')),
])
->action(function (WorkOrderPart $r, array $data) {
$batch = app(WarehouseService::class)->returnPart(
@@ -14,19 +14,24 @@ class PaymentsRelationManager extends RelationManager
{
protected static string $relationship = 'payments';
protected static ?string $title = 'Plăți';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Plăți');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\DatePicker::make('paid_at')->label('Data')->default(today())->required(),
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(),
Forms\Components\DatePicker::make('paid_at')->label(__('Data'))->default(today())->required(),
Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
Forms\Components\Select::make('method')
->options(Payment::METHODS)
->default('cash')
->required(),
Forms\Components\TextInput::make('reference')->label('Referință')->maxLength(64),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
Forms\Components\TextInput::make('reference')->label(__('Referință'))->maxLength(64),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]);
}
@@ -35,9 +40,9 @@ class PaymentsRelationManager extends RelationManager
return $table
->recordTitleAttribute('amount')
->columns([
Tables\Columns\TextColumn::make('paid_at')->label('Data')->date('d.m.Y'),
Tables\Columns\TextColumn::make('paid_at')->label(__('Data'))->date('d.m.Y'),
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label('Plătit')),
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label(__('Plătit'))),
Tables\Columns\TextColumn::make('method')
->formatStateUsing(fn ($s) => Payment::METHODS[$s] ?? $s)
->badge(),
@@ -15,27 +15,32 @@ class SubcontractJobsRelationManager extends RelationManager
{
protected static string $relationship = 'subcontractJobs';
protected static ?string $title = 'Lucrări la terți';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Lucrări la terți');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\Select::make('subcontractor_id')
->label('Subcontractor')
->label(__('Subcontractor'))
->options(fn () => Subcontractor::where('is_active', true)->pluck('name', 'id'))
->searchable()
->columnSpanFull(),
Forms\Components\Select::make('category')
->label('Categorie')
->label(__('Categorie'))
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
->searchable(),
Forms\Components\Select::make('status')->options(SubcontractJob::STATUSES)->default('sent')->required(),
Forms\Components\Textarea::make('description')->label('Descriere')->rows(2)->columnSpanFull(),
Forms\Components\TextInput::make('cost')->label('Cost (terț)')->numeric()->default(0)->required(),
Forms\Components\TextInput::make('markup_pct')->label('Markup %')->numeric()->default(0),
Forms\Components\TextInput::make('client_price')->label('Preț client')->numeric()->default(0)
->helperText('Folosit dacă markup = 0.'),
Forms\Components\DatePicker::make('eta')->label('ETA'),
Forms\Components\Textarea::make('description')->label(__('Descriere'))->rows(2)->columnSpanFull(),
Forms\Components\TextInput::make('cost')->label(__('Cost (terț)'))->numeric()->default(0)->required(),
Forms\Components\TextInput::make('markup_pct')->label(__('Markup %'))->numeric()->default(0),
Forms\Components\TextInput::make('client_price')->label(__('Preț client'))->numeric()->default(0)
->helperText(__('Folosit dacă markup = 0.')),
Forms\Components\DatePicker::make('eta')->label(__('ETA')),
]);
}
@@ -44,13 +49,13 @@ class SubcontractJobsRelationManager extends RelationManager
return $table
->recordTitleAttribute('number')
->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.'),
Tables\Columns\TextColumn::make('subcontractor.name')->label('Terț')->placeholder('—'),
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('cost')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('client_price')->label('Preț client')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('client_price')->label(__('Preț client'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('margin')
->label('Marjă')
->label(__('Marjă'))
->state(fn (SubcontractJob $r) => $r->margin())
->money('MDL')->alignRight()
->color(fn ($s) => (float) $s > 0 ? 'success' : ((float) $s < 0 ? 'danger' : 'gray')),
@@ -17,13 +17,18 @@ class WorksRelationManager extends RelationManager
{
protected static string $relationship = 'works';
protected static ?string $title = 'Manopere';
protected static ?string $title = null;
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return __('Manopere');
}
public function form(Schema $schema): Schema
{
return $schema->components([
Forms\Components\Select::make('labor_id')
->label('Catalog manoperă')
->label(__('Catalog manoperă'))
->options(fn () => Labor::where('is_active', true)
->get()
->mapWithKeys(fn ($l) => [$l->id => "[{$l->category}] {$l->name_ro} ({$l->hours}h)"])
@@ -38,18 +43,18 @@ class WorksRelationManager extends RelationManager
}
})
->columnSpanFull(),
Forms\Components\TextInput::make('name')->label('Nume')->required()->columnSpanFull(),
Forms\Components\TextInput::make('hours')->label('Ore')->numeric()->default(1)->required(),
Forms\Components\TextInput::make('price_per_hour')->label('Preț/h')->numeric()->required(),
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->columnSpanFull(),
Forms\Components\TextInput::make('hours')->label(__('Ore'))->numeric()->default(1)->required(),
Forms\Components\TextInput::make('price_per_hour')->label(__('Preț/h'))->numeric()->required(),
Forms\Components\Select::make('master_id')
->label('Maistru')
->label(__('Maistru'))
->options(fn () => User::pluck('name', 'id'))
->searchable(),
Forms\Components\Select::make('status')
->options(WorkOrderWork::STATUSES)
->default('todo')
->required(),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]);
}
@@ -68,11 +73,11 @@ class WorksRelationManager extends RelationManager
return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name')->label('Manoperă')->wrap(),
Tables\Columns\TextColumn::make('hours')->label('Ore')->numeric(decimalPlaces: 2)->alignRight(),
Tables\Columns\TextColumn::make('price_per_hour')->label('Preț/h')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('name')->label(__('Manoperă'))->wrap(),
Tables\Columns\TextColumn::make('hours')->label(__('Ore'))->numeric(decimalPlaces: 2)->alignRight(),
Tables\Columns\TextColumn::make('price_per_hour')->label(__('Preț/h'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('total')
->label('Total')
->label(__('Total'))
->money('MDL')
->alignRight()
->description(function ($record) {
@@ -82,7 +87,7 @@ class WorksRelationManager extends RelationManager
if ((float) $record->applied_margin_pct === 0.0) return null;
return 'Bază salariu: ' . number_format((float) $record->salary_base, 2) . ' MDL · marjă ' . rtrim(rtrim(number_format((float) $record->applied_margin_pct, 2), '0'), '.') . '%';
}),
Tables\Columns\TextColumn::make('master.name')->label('Maistru')->placeholder('—'),
Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => WorkOrderWork::STATUSES[$s] ?? $s)
->badge()