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
+53 -48
View File
@@ -19,7 +19,12 @@ class PaymentSettings extends Page
return __('nav.label.Setări plăți'); return __('nav.label.Setări plăți');
} }
protected static ?string $title = 'Integrări de plată'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Integrări de plată');
}
protected static ?int $navigationSort = 90; protected static ?int $navigationSort = 90;
@@ -84,132 +89,132 @@ class PaymentSettings extends Page
{ {
return $schema return $schema
->components([ ->components([
Schemas\Components\Section::make('General') Schemas\Components\Section::make(__('General'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\Select::make('platform_currency') Forms\Components\Select::make('platform_currency')
->label('Monedă platformă') ->label(__('Monedă platformă'))
->options(['MDL' => 'MDL', 'EUR' => 'EUR', 'USD' => 'USD']) ->options(['MDL' => 'MDL', 'EUR' => 'EUR', 'USD' => 'USD'])
->required(), ->required(),
Forms\Components\TextInput::make('invoice_prefix') Forms\Components\TextInput::make('invoice_prefix')
->label('Prefix facturi')->maxLength(10)->default('INV'), ->label(__('Prefix facturi'))->maxLength(10)->default('INV'),
Forms\Components\Textarea::make('terms') Forms\Components\Textarea::make('terms')
->label('Termeni de plată')->rows(2)->columnSpanFull(), ->label(__('Termeni de plată'))->rows(2)->columnSpanFull(),
]), ]),
Schemas\Components\Section::make('Date legale (apar pe facturi)') Schemas\Components\Section::make(__('Date legale (apar pe facturi)'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('legal_name')->label('Denumire companie')->maxLength(160), Forms\Components\TextInput::make('legal_name')->label(__('Denumire companie'))->maxLength(160),
Forms\Components\TextInput::make('legal_idno')->label('IDNO / CUI')->maxLength(40), Forms\Components\TextInput::make('legal_idno')->label(__('IDNO / CUI'))->maxLength(40),
Forms\Components\TextInput::make('legal_address')->label('Adresă')->columnSpanFull()->maxLength(255), Forms\Components\TextInput::make('legal_address')->label(__('Adresă'))->columnSpanFull()->maxLength(255),
Forms\Components\TextInput::make('legal_phone')->label('Telefon')->tel()->maxLength(40), Forms\Components\TextInput::make('legal_phone')->label(__('Telefon'))->tel()->maxLength(40),
Forms\Components\TextInput::make('legal_email')->label('Email')->email()->maxLength(120), Forms\Components\TextInput::make('legal_email')->label(__('Email'))->email()->maxLength(120),
]), ]),
Schemas\Components\Section::make('💳 Stripe') Schemas\Components\Section::make(__('💳 Stripe'))
->description('Procesare carduri online. Funcționează în 30+ țări. Comision ~2.9% + 0.30€/tranzacție.') ->description(__('Procesare carduri online. Funcționează în 30+ țări. Comision ~2.9% + 0.30€/tranzacție.'))
->columns(3) ->columns(3)
->collapsible() ->collapsible()
->collapsed(fn (Get $get) => ! $get('stripe_enabled')) ->collapsed(fn (Get $get) => ! $get('stripe_enabled'))
->schema([ ->schema([
Forms\Components\Toggle::make('stripe_enabled')->label('Activează Stripe')->default(false)->live(), Forms\Components\Toggle::make('stripe_enabled')->label(__('Activează Stripe'))->default(false)->live(),
Forms\Components\Select::make('stripe_mode') Forms\Components\Select::make('stripe_mode')
->label('Mod') ->label(__('Mod'))
->options(['test' => '🧪 Test', 'live' => '🟢 Live']) ->options(['test' => '🧪 Test', 'live' => '🟢 Live'])
->default('test') ->default('test')
->required(fn (Get $get) => $get('stripe_enabled')) ->required(fn (Get $get) => $get('stripe_enabled'))
->visible(fn (Get $get) => $get('stripe_enabled')), ->visible(fn (Get $get) => $get('stripe_enabled')),
Forms\Components\TextInput::make('stripe_publishable') Forms\Components\TextInput::make('stripe_publishable')
->label('Publishable key')->placeholder('pk_test_...') ->label(__('Publishable key'))->placeholder('pk_test_...')
->visible(fn (Get $get) => $get('stripe_enabled')), ->visible(fn (Get $get) => $get('stripe_enabled')),
Forms\Components\TextInput::make('stripe_secret') Forms\Components\TextInput::make('stripe_secret')
->label('Secret key')->password()->revealable()->placeholder('sk_test_...') ->label(__('Secret key'))->password()->revealable()->placeholder('sk_test_...')
->visible(fn (Get $get) => $get('stripe_enabled')), ->visible(fn (Get $get) => $get('stripe_enabled')),
Forms\Components\TextInput::make('stripe_webhook') Forms\Components\TextInput::make('stripe_webhook')
->label('Webhook secret')->password()->revealable()->placeholder('whsec_...') ->label(__('Webhook secret'))->password()->revealable()->placeholder('whsec_...')
->columnSpanFull() ->columnSpanFull()
->helperText('Endpoint webhook: https://service.mir.md/payments/stripe/webhook') ->helperText(__('Endpoint webhook: https://service.mir.md/payments/stripe/webhook'))
->visible(fn (Get $get) => $get('stripe_enabled')), ->visible(fn (Get $get) => $get('stripe_enabled')),
]), ]),
Schemas\Components\Section::make('🅿️ PayPal') Schemas\Components\Section::make(__('🅿️ PayPal'))
->description('Plăți internaționale. Mai potrivit pentru clienți din afara MD.') ->description(__('Plăți internaționale. Mai potrivit pentru clienți din afara MD.'))
->columns(3) ->columns(3)
->collapsible() ->collapsible()
->collapsed(fn (Get $get) => ! $get('paypal_enabled')) ->collapsed(fn (Get $get) => ! $get('paypal_enabled'))
->schema([ ->schema([
Forms\Components\Toggle::make('paypal_enabled')->label('Activează PayPal')->default(false)->live(), Forms\Components\Toggle::make('paypal_enabled')->label(__('Activează PayPal'))->default(false)->live(),
Forms\Components\Select::make('paypal_mode') Forms\Components\Select::make('paypal_mode')
->label('Mod') ->label(__('Mod'))
->options(['sandbox' => '🧪 Sandbox', 'live' => '🟢 Live']) ->options(['sandbox' => '🧪 Sandbox', 'live' => '🟢 Live'])
->default('sandbox') ->default('sandbox')
->visible(fn (Get $get) => $get('paypal_enabled')), ->visible(fn (Get $get) => $get('paypal_enabled')),
Forms\Components\Placeholder::make('paypal_webhook_info') Forms\Components\Placeholder::make('paypal_webhook_info')
->label('Webhook') ->label(__('Webhook'))
->content('https://service.mir.md/payments/paypal/webhook') ->content('https://service.mir.md/payments/paypal/webhook')
->visible(fn (Get $get) => $get('paypal_enabled')), ->visible(fn (Get $get) => $get('paypal_enabled')),
Forms\Components\TextInput::make('paypal_client_id') Forms\Components\TextInput::make('paypal_client_id')
->label('Client ID')->placeholder('AYS...') ->label(__('Client ID'))->placeholder(__('AYS...'))
->visible(fn (Get $get) => $get('paypal_enabled')), ->visible(fn (Get $get) => $get('paypal_enabled')),
Forms\Components\TextInput::make('paypal_secret') Forms\Components\TextInput::make('paypal_secret')
->label('Secret')->password()->revealable()->placeholder('EJk...') ->label(__('Secret'))->password()->revealable()->placeholder(__('EJk...'))
->visible(fn (Get $get) => $get('paypal_enabled')), ->visible(fn (Get $get) => $get('paypal_enabled')),
]), ]),
Schemas\Components\Section::make('🇲🇩 Paynet (Moldova)') Schemas\Components\Section::make(__('🇲🇩 Paynet (Moldova)'))
->description('Procesator de plăți Moldova. Acceptă carduri MAIB, MICB, Victoriabank, OTP, MD Cash, Bitcoin, e-money. Comision negociabil ~1.8-2.5%.') ->description(__('Procesator de plăți Moldova. Acceptă carduri MAIB, MICB, Victoriabank, OTP, MD Cash, Bitcoin, e-money. Comision negociabil ~1.8-2.5%.'))
->columns(3) ->columns(3)
->collapsible() ->collapsible()
->collapsed(fn (Get $get) => ! $get('paynet_enabled')) ->collapsed(fn (Get $get) => ! $get('paynet_enabled'))
->schema([ ->schema([
Forms\Components\Toggle::make('paynet_enabled')->label('Activează Paynet')->default(false)->live(), Forms\Components\Toggle::make('paynet_enabled')->label(__('Activează Paynet'))->default(false)->live(),
Forms\Components\Select::make('paynet_mode') Forms\Components\Select::make('paynet_mode')
->label('Mod') ->label(__('Mod'))
->options(['test' => '🧪 Test', 'live' => '🟢 Live']) ->options(['test' => '🧪 Test', 'live' => '🟢 Live'])
->default('test') ->default('test')
->visible(fn (Get $get) => $get('paynet_enabled')), ->visible(fn (Get $get) => $get('paynet_enabled')),
Forms\Components\Placeholder::make('paynet_webhook_info') Forms\Components\Placeholder::make('paynet_webhook_info')
->label('Webhook (Notify URL)') ->label(__('Webhook (Notify URL)'))
->content('https://service.mir.md/payments/paynet/webhook') ->content('https://service.mir.md/payments/paynet/webhook')
->visible(fn (Get $get) => $get('paynet_enabled')), ->visible(fn (Get $get) => $get('paynet_enabled')),
Forms\Components\TextInput::make('paynet_merchant_code') Forms\Components\TextInput::make('paynet_merchant_code')
->label('Merchant Code') ->label(__('Merchant Code'))
->placeholder('AUTOCRM_001') ->placeholder(__('AUTOCRM_001'))
->visible(fn (Get $get) => $get('paynet_enabled')), ->visible(fn (Get $get) => $get('paynet_enabled')),
Forms\Components\TextInput::make('paynet_service_id') Forms\Components\TextInput::make('paynet_service_id')
->label('Service ID') ->label(__('Service ID'))
->numeric() ->numeric()
->placeholder('12345') ->placeholder('12345')
->visible(fn (Get $get) => $get('paynet_enabled')), ->visible(fn (Get $get) => $get('paynet_enabled')),
Forms\Components\TextInput::make('paynet_user') Forms\Components\TextInput::make('paynet_user')
->label('User API') ->label(__('User API'))
->visible(fn (Get $get) => $get('paynet_enabled')), ->visible(fn (Get $get) => $get('paynet_enabled')),
Forms\Components\TextInput::make('paynet_password') Forms\Components\TextInput::make('paynet_password')
->label('Parolă API')->password()->revealable() ->label(__('Parolă API'))->password()->revealable()
->visible(fn (Get $get) => $get('paynet_enabled')), ->visible(fn (Get $get) => $get('paynet_enabled')),
Forms\Components\TextInput::make('paynet_secret') Forms\Components\TextInput::make('paynet_secret')
->label('Secret semnătură')->password()->revealable() ->label(__('Secret semnătură'))->password()->revealable()
->columnSpanFull() ->columnSpanFull()
->helperText('Cheia HMAC pentru semnarea cererilor + verificarea webhook-urilor de la Paynet.') ->helperText(__('Cheia HMAC pentru semnarea cererilor + verificarea webhook-urilor de la Paynet.'))
->visible(fn (Get $get) => $get('paynet_enabled')), ->visible(fn (Get $get) => $get('paynet_enabled')),
]), ]),
Schemas\Components\Section::make('🏦 Transfer bancar (manual)') Schemas\Components\Section::make(__('🏦 Transfer bancar (manual)'))
->description('Datele apar pe facturi și pe pagina de plată a tenant-ului. Confirmarea o faci manual.') ->description(__('Datele apar pe facturi și pe pagina de plată a tenant-ului. Confirmarea o faci manual.'))
->columns(2) ->columns(2)
->collapsible() ->collapsible()
->schema([ ->schema([
Forms\Components\Toggle::make('bank_enabled')->label('Activează plata prin transfer')->default(true)->live(), Forms\Components\Toggle::make('bank_enabled')->label(__('Activează plata prin transfer'))->default(true)->live(),
Forms\Components\TextInput::make('bank_beneficiary') Forms\Components\TextInput::make('bank_beneficiary')
->label('Beneficiar')->placeholder('AutoCRM SRL') ->label(__('Beneficiar'))->placeholder(__('AutoCRM SRL'))
->visible(fn (Get $get) => $get('bank_enabled')), ->visible(fn (Get $get) => $get('bank_enabled')),
Forms\Components\TextInput::make('bank_iban') Forms\Components\TextInput::make('bank_iban')
->label('IBAN')->placeholder('MD00 AG00 0000 ...') ->label(__('IBAN'))->placeholder(__('MD00 AG00 0000 ...'))
->visible(fn (Get $get) => $get('bank_enabled')), ->visible(fn (Get $get) => $get('bank_enabled')),
Forms\Components\TextInput::make('bank_bic') Forms\Components\TextInput::make('bank_bic')
->label('BIC / SWIFT')->placeholder('AGRNMD2X') ->label(__('BIC / SWIFT'))->placeholder(__('AGRNMD2X'))
->visible(fn (Get $get) => $get('bank_enabled')), ->visible(fn (Get $get) => $get('bank_enabled')),
Forms\Components\TextInput::make('bank_name') Forms\Components\TextInput::make('bank_name')
->label('Banca')->placeholder('Moldova-Agroindbank') ->label(__('Banca'))->placeholder(__('Moldova-Agroindbank'))
->visible(fn (Get $get) => $get('bank_enabled')), ->visible(fn (Get $get) => $get('bank_enabled')),
Forms\Components\Textarea::make('bank_instructions') Forms\Components\Textarea::make('bank_instructions')
->label('Instrucțiuni adiționale')->rows(2)->columnSpanFull() ->label(__('Instrucțiuni adiționale'))->rows(2)->columnSpanFull()
->visible(fn (Get $get) => $get('bank_enabled')), ->visible(fn (Get $get) => $get('bank_enabled')),
]), ]),
]) ])
@@ -266,14 +271,14 @@ class PaymentSettings extends Page
'instructions' => $data['bank_instructions'] ?? null, 'instructions' => $data['bank_instructions'] ?? null,
]); ]);
Notification::make()->title('Setări plăți salvate')->success()->send(); Notification::make()->title(__('Setări plăți salvate'))->success()->send();
} }
protected function getFormActions(): array protected function getFormActions(): array
{ {
return [ return [
\Filament\Actions\Action::make('save') \Filament\Actions\Action::make('save')
->label('Salvează') ->label(__('Salvează'))
->submit('save'), ->submit('save'),
]; ];
} }
@@ -40,7 +40,7 @@ class CompanyResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identificare') Schemas\Components\Section::make(__('Identificare'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('slug') Forms\Components\TextInput::make('slug')
@@ -53,19 +53,19 @@ class CompanyResource extends Resource
) )
->dehydrateStateUsing(fn ($state) => strtolower((string) $state)) ->dehydrateStateUsing(fn ($state) => strtolower((string) $state))
->extraInputAttributes(['style' => 'text-transform: lowercase']) ->extraInputAttributes(['style' => 'text-transform: lowercase'])
->helperText('Subdomeniul: <slug>.service.mir.md'), ->helperText(__('Subdomeniul: <slug>.service.mir.md')),
Forms\Components\TextInput::make('name')->required()->maxLength(120), Forms\Components\TextInput::make('name')->required()->maxLength(120),
Forms\Components\TextInput::make('display_name')->maxLength(120), Forms\Components\TextInput::make('display_name')->maxLength(120),
Forms\Components\TextInput::make('city')->maxLength(60), Forms\Components\TextInput::make('city')->maxLength(60),
]), ]),
Schemas\Components\Section::make('Contact') Schemas\Components\Section::make(__('Contact'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('contact_name')->maxLength(120), Forms\Components\TextInput::make('contact_name')->maxLength(120),
Forms\Components\TextInput::make('phone')->tel()->maxLength(40), Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->email()->maxLength(120), Forms\Components\TextInput::make('email')->email()->maxLength(120),
]), ]),
Schemas\Components\Section::make('Abonament') Schemas\Components\Section::make(__('Abonament'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
@@ -79,31 +79,31 @@ class CompanyResource extends Resource
->default('trial') ->default('trial')
->required(), ->required(),
Forms\Components\Select::make('plan_id') Forms\Components\Select::make('plan_id')
->label('Plan') ->label(__('Plan'))
->options(fn () => Plan::pluck('name', 'id')) ->options(fn () => Plan::pluck('name', 'id'))
->searchable(), ->searchable(),
Forms\Components\DateTimePicker::make('trial_ends_at')->label('Trial expiră la'), Forms\Components\DateTimePicker::make('trial_ends_at')->label(__('Trial expiră la')),
Forms\Components\DateTimePicker::make('active_until')->label('Abonament până la'), Forms\Components\DateTimePicker::make('active_until')->label(__('Abonament până la')),
Forms\Components\Toggle::make('is_demo') Forms\Components\Toggle::make('is_demo')
->label('Tenant demo') ->label(__('Tenant demo'))
->helperText('Marchează acest tenant ca demo (datele pot fi resetate periodic).'), ->helperText(__('Marchează acest tenant ca demo (datele pot fi resetate periodic).')),
]), ]),
Schemas\Components\Section::make('Admin tenant (la creare)') Schemas\Components\Section::make(__('Admin tenant (la creare)'))
->columns(2) ->columns(2)
->visible(fn (string $operation) => $operation === 'create') ->visible(fn (string $operation) => $operation === 'create')
->schema([ ->schema([
Forms\Components\TextInput::make('admin_name') Forms\Components\TextInput::make('admin_name')
->label('Nume admin') ->label(__('Nume admin'))
->default('Administrator'), ->default('Administrator'),
Forms\Components\TextInput::make('admin_email') Forms\Components\TextInput::make('admin_email')
->label('Email admin') ->label(__('Email admin'))
->email() ->email()
->required(), ->required(),
Forms\Components\TextInput::make('admin_password') Forms\Components\TextInput::make('admin_password')
->label('Parolă (lasă gol pentru auto-generat)') ->label(__('Parolă (lasă gol pentru auto-generat)'))
->password() ->password()
->minLength(8) ->minLength(8)
->helperText('Dacă e gol, generăm 10 caractere random.'), ->helperText(__('Dacă e gol, generăm 10 caractere random.')),
]), ]),
]); ]);
} }
@@ -129,9 +129,9 @@ class CompanyResource extends Resource
'danger' => ['suspended', 'expired'], 'danger' => ['suspended', 'expired'],
'gray' => ['archived'], 'gray' => ['archived'],
]), ]),
Tables\Columns\TextColumn::make('plan.name')->label('Plan')->placeholder('—'), Tables\Columns\TextColumn::make('plan.name')->label(__('Plan'))->placeholder('—'),
Tables\Columns\TextColumn::make('city')->toggleable(), Tables\Columns\TextColumn::make('city')->toggleable(),
Tables\Columns\TextColumn::make('users_count')->counts('users')->label('Useri'), Tables\Columns\TextColumn::make('users_count')->counts('users')->label(__('Useri')),
Tables\Columns\TextColumn::make('created_at')->date()->sortable(), Tables\Columns\TextColumn::make('created_at')->date()->sortable(),
]) ])
->filters([ ->filters([
@@ -145,20 +145,20 @@ class CompanyResource extends Resource
Actions\ViewAction::make() Actions\ViewAction::make()
->url(fn (Company $record) => CompanyResource::getUrl('view', ['record' => $record])), ->url(fn (Company $record) => CompanyResource::getUrl('view', ['record' => $record])),
Actions\Action::make('open_tenant') Actions\Action::make('open_tenant')
->label('Deschide') ->label(__('Deschide'))
->icon('heroicon-m-arrow-top-right-on-square') ->icon('heroicon-m-arrow-top-right-on-square')
->color('primary') ->color('primary')
->url(fn (Company $record) => $record->url('/app')) ->url(fn (Company $record) => $record->url('/app'))
->openUrlInNewTab(), ->openUrlInNewTab(),
Actions\Action::make('suspend') Actions\Action::make('suspend')
->label('Suspendă') ->label(__('Suspendă'))
->icon('heroicon-m-no-symbol') ->icon('heroicon-m-no-symbol')
->color('danger') ->color('danger')
->visible(fn (Company $record) => in_array($record->status, ['active', 'trial'])) ->visible(fn (Company $record) => in_array($record->status, ['active', 'trial']))
->requiresConfirmation() ->requiresConfirmation()
->action(fn (Company $record) => app(\App\Services\CompanyProvisioner::class)->suspend($record)), ->action(fn (Company $record) => app(\App\Services\CompanyProvisioner::class)->suspend($record)),
Actions\Action::make('activate') Actions\Action::make('activate')
->label('Activează') ->label(__('Activează'))
->icon('heroicon-m-check-circle') ->icon('heroicon-m-check-circle')
->color('success') ->color('success')
->visible(fn (Company $record) => in_array($record->status, ['suspended', 'expired'])) ->visible(fn (Company $record) => in_array($record->status, ['suspended', 'expired']))
@@ -167,8 +167,8 @@ class CompanyResource extends Resource
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun tenant încă') ->emptyStateHeading(__('Niciun tenant încă'))
->emptyStateDescription('Adaugă primul client SaaS folosind butonul „Adaugă companie" — se va crea un tenant nou cu admin pe `<slug>.service.mir.md/app`.') ->emptyStateDescription(__('Adaugă primul client SaaS folosind butonul „Adaugă companie" — se va crea un tenant nou cu admin pe `<slug>.service.mir.md/app`.'))
->emptyStateIcon('heroicon-o-building-office-2') ->emptyStateIcon('heroicon-o-building-office-2')
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -110,13 +110,13 @@ class ViewCompany extends Page
{ {
return [ return [
Actions\Action::make('view_as') Actions\Action::make('view_as')
->label('Deschide tenantul') ->label(__('Deschide tenantul'))
->icon('heroicon-m-arrow-top-right-on-square') ->icon('heroicon-m-arrow-top-right-on-square')
->color('primary') ->color('primary')
->url(fn () => $this->record->url('/app')) ->url(fn () => $this->record->url('/app'))
->openUrlInNewTab(), ->openUrlInNewTab(),
Actions\Action::make('issue_invoice') Actions\Action::make('issue_invoice')
->label('Generează factură') ->label(__('Generează factură'))
->icon('heroicon-m-document-plus') ->icon('heroicon-m-document-plus')
->color('success') ->color('success')
->visible(fn () => (bool) $this->record->plan_id) ->visible(fn () => (bool) $this->record->plan_id)
@@ -147,7 +147,7 @@ class ViewCompany extends Page
$this->dispatch('$refresh'); $this->dispatch('$refresh');
}), }),
Actions\Action::make('upload_logo') Actions\Action::make('upload_logo')
->label('Logo') ->label(__('Logo'))
->icon('heroicon-m-photo') ->icon('heroicon-m-photo')
->color('gray') ->color('gray')
->schema([ ->schema([
@@ -166,29 +166,29 @@ class ViewCompany extends Page
$this->record->clearMediaCollection('logo'); $this->record->clearMediaCollection('logo');
$this->record->addMedia($abs)->preservingOriginal()->toMediaCollection('logo'); $this->record->addMedia($abs)->preservingOriginal()->toMediaCollection('logo');
@unlink($abs); @unlink($abs);
Notification::make()->title('Logo actualizat')->success()->send(); Notification::make()->title(__('Logo actualizat'))->success()->send();
} }
}), }),
Actions\Action::make('edit') Actions\Action::make('edit')
->label('Editează') ->label(__('Editează'))
->icon('heroicon-m-pencil') ->icon('heroicon-m-pencil')
->url(fn () => CompanyResource::getUrl('edit', ['record' => $this->record])), ->url(fn () => CompanyResource::getUrl('edit', ['record' => $this->record])),
Actions\Action::make('suspend') Actions\Action::make('suspend')
->label('Suspendă')->icon('heroicon-m-no-symbol')->color('danger') ->label(__('Suspendă'))->icon('heroicon-m-no-symbol')->color('danger')
->visible(fn () => in_array($this->record->status, ['active', 'trial'])) ->visible(fn () => in_array($this->record->status, ['active', 'trial']))
->requiresConfirmation() ->requiresConfirmation()
->action(function () { ->action(function () {
app(\App\Services\CompanyProvisioner::class)->suspend($this->record); app(\App\Services\CompanyProvisioner::class)->suspend($this->record);
Notification::make()->title('Tenant suspendat.')->success()->send(); Notification::make()->title(__('Tenant suspendat.'))->success()->send();
$this->dispatch('$refresh'); $this->dispatch('$refresh');
}), }),
Actions\Action::make('activate') Actions\Action::make('activate')
->label('Reactivează')->icon('heroicon-m-check-circle')->color('success') ->label(__('Reactivează'))->icon('heroicon-m-check-circle')->color('success')
->visible(fn () => in_array($this->record->status, ['suspended', 'expired'])) ->visible(fn () => in_array($this->record->status, ['suspended', 'expired']))
->requiresConfirmation() ->requiresConfirmation()
->action(function () { ->action(function () {
app(\App\Services\CompanyProvisioner::class)->reactivate($this->record); app(\App\Services\CompanyProvisioner::class)->reactivate($this->record);
Notification::make()->title('Tenant activat.')->success()->send(); Notification::make()->title(__('Tenant activat.'))->success()->send();
$this->dispatch('$refresh'); $this->dispatch('$refresh');
}), }),
]; ];
+27 -27
View File
@@ -46,7 +46,7 @@ class PlanResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identificare') Schemas\Components\Section::make(__('Identificare'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('slug') Forms\Components\TextInput::make('slug')
@@ -54,25 +54,25 @@ class PlanResource extends Resource
->dehydrateStateUsing(fn ($s) => strtolower((string) $s)) ->dehydrateStateUsing(fn ($s) => strtolower((string) $s))
->extraInputAttributes(['style' => 'text-transform:lowercase']), ->extraInputAttributes(['style' => 'text-transform:lowercase']),
Forms\Components\TextInput::make('name')->required()->maxLength(60), Forms\Components\TextInput::make('name')->required()->maxLength(60),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true), Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
Forms\Components\Toggle::make('is_public')->label('Public (afișat la signup)')->default(true), Forms\Components\Toggle::make('is_public')->label(__('Public (afișat la signup)'))->default(true),
Forms\Components\Toggle::make('is_demo') Forms\Components\Toggle::make('is_demo')
->label('Demo (pentru demonstrații sales)') ->label(__('Demo (pentru demonstrații sales)'))
->helperText('Plan ascuns public, folosit pentru tenant-i demo cu features deblocate.') ->helperText(__('Plan ascuns public, folosit pentru tenant-i demo cu features deblocate.'))
->default(false), ->default(false),
Forms\Components\TextInput::make('trial_days') Forms\Components\TextInput::make('trial_days')
->label('Perioadă trial (zile)') ->label(__('Perioadă trial (zile)'))
->numeric()->placeholder('null = fără trial') ->numeric()->placeholder(__('null = fără trial'))
->helperText('Numărul de zile gratis după ce un tenant alege acest plan.'), ->helperText(__('Numărul de zile gratis după ce un tenant alege acest plan.')),
]), ]),
Schemas\Components\Section::make('Preț') Schemas\Components\Section::make(__('Preț'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('price_monthly')->label('Preț lunar')->numeric()->required()->suffix(fn (Get $get) => $get('currency') ?? 'MDL'), Forms\Components\TextInput::make('price_monthly')->label(__('Preț lunar'))->numeric()->required()->suffix(fn (Get $get) => $get('currency') ?? 'MDL'),
Forms\Components\TextInput::make('price_yearly')->label('Preț anual')->numeric()->helperText('De obicei 10× preț lunar (2 luni gratis).')->suffix(fn (Get $get) => $get('currency') ?? 'MDL'), Forms\Components\TextInput::make('price_yearly')->label(__('Preț anual'))->numeric()->helperText(__('De obicei 10× preț lunar (2 luni gratis).'))->suffix(fn (Get $get) => $get('currency') ?? 'MDL'),
Forms\Components\Select::make('currency')->options(['MDL' => 'MDL', 'EUR' => 'EUR', 'USD' => 'USD'])->default('MDL'), Forms\Components\Select::make('currency')->options(['MDL' => 'MDL', 'EUR' => 'EUR', 'USD' => 'USD'])->default('MDL'),
]), ]),
Schemas\Components\Section::make('Funcționalități incluse') Schemas\Components\Section::make(__('Funcționalități incluse'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\CheckboxList::make('features') Forms\Components\CheckboxList::make('features')
@@ -80,15 +80,15 @@ class PlanResource extends Resource
->columns(2) ->columns(2)
->columnSpanFull(), ->columnSpanFull(),
]), ]),
Schemas\Components\Section::make('Limite') Schemas\Components\Section::make(__('Limite'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('limits.max_users')->label('Max useri')->numeric()->placeholder('∞'), Forms\Components\TextInput::make('limits.max_users')->label(__('Max useri'))->numeric()->placeholder('∞'),
Forms\Components\TextInput::make('limits.max_clients')->label('Max clienți')->numeric()->placeholder('∞'), Forms\Components\TextInput::make('limits.max_clients')->label(__('Max clienți'))->numeric()->placeholder('∞'),
Forms\Components\TextInput::make('limits.max_vehicles')->label('Max mașini')->numeric()->placeholder('∞'), Forms\Components\TextInput::make('limits.max_vehicles')->label(__('Max mașini'))->numeric()->placeholder('∞'),
Forms\Components\TextInput::make('limits.max_work_orders_month')->label('Max fișe/lună')->numeric()->placeholder('∞'), Forms\Components\TextInput::make('limits.max_work_orders_month')->label(__('Max fișe/lună'))->numeric()->placeholder('∞'),
Forms\Components\TextInput::make('limits.storage_mb')->label('Storage (MB)')->numeric()->placeholder('∞'), Forms\Components\TextInput::make('limits.storage_mb')->label(__('Storage (MB)'))->numeric()->placeholder('∞'),
Forms\Components\TextInput::make('limits.ai_messages_month')->label('Mesaje AI/lună')->numeric()->placeholder('∞'), Forms\Components\TextInput::make('limits.ai_messages_month')->label(__('Mesaje AI/lună'))->numeric()->placeholder('∞'),
]), ]),
]); ]);
} }
@@ -103,29 +103,29 @@ class PlanResource extends Resource
->description(fn ($record) => $record->is_demo ? '🎬 Demo' : ($record->trial_days ? "🎁 {$record->trial_days} zile trial" : null)), ->description(fn ($record) => $record->is_demo ? '🎬 Demo' : ($record->trial_days ? "🎁 {$record->trial_days} zile trial" : null)),
Tables\Columns\TextColumn::make('price_monthly') Tables\Columns\TextColumn::make('price_monthly')
->money(fn ($record) => $record->currency) ->money(fn ($record) => $record->currency)
->label('Lunar') ->label(__('Lunar'))
->sortable(), ->sortable(),
Tables\Columns\TextColumn::make('price_yearly') Tables\Columns\TextColumn::make('price_yearly')
->money(fn ($record) => $record->currency) ->money(fn ($record) => $record->currency)
->label('Anual'), ->label(__('Anual')),
Tables\Columns\TextColumn::make('companies_count') Tables\Columns\TextColumn::make('companies_count')
->counts('companies') ->counts('companies')
->label('Abonați') ->label(__('Abonați'))
->badge() ->badge()
->color('primary'), ->color('primary'),
Tables\Columns\TextColumn::make('features') Tables\Columns\TextColumn::make('features')
->label('Funcționalități') ->label(__('Funcționalități'))
->formatStateUsing(fn ($s) => is_array($s) ? count($s) . ' incluse' : '—') ->formatStateUsing(fn ($s) => is_array($s) ? count($s) . ' incluse' : '—')
->badge(), ->badge(),
Tables\Columns\IconColumn::make('is_active')->boolean()->label('Activ'), Tables\Columns\IconColumn::make('is_active')->boolean()->label(__('Activ')),
Tables\Columns\IconColumn::make('is_public')->boolean()->label('Public'), Tables\Columns\IconColumn::make('is_public')->boolean()->label(__('Public')),
]) ])
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun plan definit') ->emptyStateHeading(__('Niciun plan definit'))
->emptyStateDescription('Creează planuri (ex: Free, Basic, Pro) și atribuie-le companiilor.') ->emptyStateDescription(__('Creează planuri (ex: Free, Basic, Pro) și atribuie-le companiilor.'))
->defaultSort('price_monthly'); ->defaultSort('price_monthly');
} }
@@ -28,7 +28,12 @@ class SubscriptionResource extends Resource
return __('nav.label.Facturi & abonamente'); return __('nav.label.Facturi & abonamente');
} }
protected static ?string $modelLabel = 'factură'; protected static ?string $modelLabel = null;
public static function getModelLabel(): string
{
return __('factură');
}
protected static ?string $pluralModelLabel = 'facturi'; protected static ?string $pluralModelLabel = 'facturi';
@@ -45,15 +50,15 @@ class SubscriptionResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Companie & plan') Schemas\Components\Section::make(__('Companie & plan'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('company_id') Forms\Components\Select::make('company_id')
->label('Companie') ->label(__('Companie'))
->options(fn () => Company::orderBy('name')->pluck('name', 'id')) ->options(fn () => Company::orderBy('name')->pluck('name', 'id'))
->required()->searchable()->live(), ->required()->searchable()->live(),
Forms\Components\Select::make('plan_id') Forms\Components\Select::make('plan_id')
->label('Plan') ->label(__('Plan'))
->options(fn () => Plan::pluck('name', 'id')) ->options(fn () => Plan::pluck('name', 'id'))
->required()->searchable()->live() ->required()->searchable()->live()
->afterStateUpdated(function ($state, Set $set, Get $get) { ->afterStateUpdated(function ($state, Set $set, Get $get) {
@@ -80,7 +85,7 @@ class SubscriptionResource extends Resource
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
->options(Subscription::STATUSES)->default('pending')->required(), ->options(Subscription::STATUSES)->default('pending')->required(),
]), ]),
Schemas\Components\Section::make('Sumă') Schemas\Components\Section::make(__('Sumă'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('amount')->numeric()->required()->suffix(fn (Get $get) => $get('currency') ?? 'MDL'), Forms\Components\TextInput::make('amount')->numeric()->required()->suffix(fn (Get $get) => $get('currency') ?? 'MDL'),
@@ -88,19 +93,19 @@ class SubscriptionResource extends Resource
Forms\Components\Select::make('payment_method') Forms\Components\Select::make('payment_method')
->options(Subscription::PAYMENT_METHODS), ->options(Subscription::PAYMENT_METHODS),
]), ]),
Schemas\Components\Section::make('Perioadă') Schemas\Components\Section::make(__('Perioadă'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\DatePicker::make('period_start')->required()->default(today()), Forms\Components\DatePicker::make('period_start')->required()->default(today()),
Forms\Components\DatePicker::make('period_end')->required(), Forms\Components\DatePicker::make('period_end')->required(),
Forms\Components\DateTimePicker::make('due_at')->label('Scadent la'), Forms\Components\DateTimePicker::make('due_at')->label(__('Scadent la')),
Forms\Components\DateTimePicker::make('paid_at')->label('Plătit la'), Forms\Components\DateTimePicker::make('paid_at')->label(__('Plătit la')),
]), ]),
Schemas\Components\Section::make('Detalii') Schemas\Components\Section::make(__('Detalii'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('invoice_number')->label('Nr. factură')->placeholder('auto-generat')->maxLength(30), Forms\Components\TextInput::make('invoice_number')->label(__('Nr. factură'))->placeholder('auto-generat')->maxLength(30),
Forms\Components\TextInput::make('reference')->label('Referință (Stripe id, transfer)')->maxLength(100), Forms\Components\TextInput::make('reference')->label(__('Referință (Stripe id, transfer)'))->maxLength(100),
Forms\Components\Textarea::make('notes')->columnSpanFull()->rows(2), Forms\Components\Textarea::make('notes')->columnSpanFull()->rows(2),
]), ]),
]); ]);
@@ -111,15 +116,15 @@ class SubscriptionResource extends Resource
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('invoice_number') Tables\Columns\TextColumn::make('invoice_number')
->label('Factură') ->label(__('Factură'))
->placeholder(fn ($record) => '—') ->placeholder(fn ($record) => '—')
->copyable() ->copyable()
->searchable(), ->searchable(),
Tables\Columns\TextColumn::make('company.name') Tables\Columns\TextColumn::make('company.name')
->label('Companie') ->label(__('Companie'))
->searchable() ->searchable()
->url(fn ($record) => \App\Filament\Central\Resources\CompanyResource::getUrl('view', ['record' => $record->company_id])), ->url(fn ($record) => \App\Filament\Central\Resources\CompanyResource::getUrl('view', ['record' => $record->company_id])),
Tables\Columns\TextColumn::make('plan.name')->label('Plan')->placeholder('—'), Tables\Columns\TextColumn::make('plan.name')->label(__('Plan'))->placeholder('—'),
Tables\Columns\TextColumn::make('period') Tables\Columns\TextColumn::make('period')
->formatStateUsing(fn ($s) => Subscription::PERIODS[$s] ?? $s) ->formatStateUsing(fn ($s) => Subscription::PERIODS[$s] ?? $s)
->badge(), ->badge(),
@@ -137,8 +142,8 @@ class SubscriptionResource extends Resource
'cancelled', 'refunded' => 'gray', 'cancelled', 'refunded' => 'gray',
default => 'primary', default => 'primary',
}), }),
Tables\Columns\TextColumn::make('period_end')->label('Până la')->date(), Tables\Columns\TextColumn::make('period_end')->label(__('Până la'))->date(),
Tables\Columns\TextColumn::make('paid_at')->label('Plătit')->date()->placeholder('—'), Tables\Columns\TextColumn::make('paid_at')->label(__('Plătit'))->date()->placeholder('—'),
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('status')->options(Subscription::STATUSES), Tables\Filters\SelectFilter::make('status')->options(Subscription::STATUSES),
@@ -146,7 +151,7 @@ class SubscriptionResource extends Resource
]) ])
->actions([ ->actions([
Actions\Action::make('mark_paid') Actions\Action::make('mark_paid')
->label('Marchează plătit') ->label(__('Marchează plătit'))
->icon('heroicon-m-check-circle') ->icon('heroicon-m-check-circle')
->color('success') ->color('success')
->visible(fn ($record) => $record->status !== 'paid') ->visible(fn ($record) => $record->status !== 'paid')
@@ -163,8 +168,8 @@ class SubscriptionResource extends Resource
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Nicio factură generată') ->emptyStateHeading(__('Nicio factură generată'))
->emptyStateDescription('Crează manual prima factură sau folosește butonul „Generează factură" din pagina Companiei.') ->emptyStateDescription(__('Crează manual prima factură sau folosește butonul „Generează factură" din pagina Companiei.'))
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -36,24 +36,24 @@ class SuperAdminResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identificare') Schemas\Components\Section::make(__('Identificare'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('name')->required()->maxLength(120), Forms\Components\TextInput::make('name')->required()->maxLength(120),
Forms\Components\TextInput::make('email')->email()->required()->unique(ignoreRecord: true)->maxLength(120), Forms\Components\TextInput::make('email')->email()->required()->unique(ignoreRecord: true)->maxLength(120),
Forms\Components\TextInput::make('phone')->tel()->maxLength(40), Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true), Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]), ]),
Schemas\Components\Section::make('Permisiuni') Schemas\Components\Section::make(__('Permisiuni'))
->columns(1) ->columns(1)
->schema([ ->schema([
Forms\Components\Select::make('role') Forms\Components\Select::make('role')
->options(SuperAdmin::ROLES) ->options(SuperAdmin::ROLES)
->default('support') ->default('support')
->required() ->required()
->helperText('Owner = drepturi totale. Admin = aproape la fel. Sales = doar tenanți + planuri. Finance = facturi. Support = read-only.'), ->helperText(__('Owner = drepturi totale. Admin = aproape la fel. Sales = doar tenanți + planuri. Finance = facturi. Support = read-only.')),
]), ]),
Schemas\Components\Section::make('Parolă') Schemas\Components\Section::make(__('Parolă'))
->columns(1) ->columns(1)
->schema([ ->schema([
Forms\Components\TextInput::make('password') Forms\Components\TextInput::make('password')
@@ -65,7 +65,7 @@ class SuperAdminResource extends Resource
->minLength(8) ->minLength(8)
->helperText(fn (string $operation) => $operation === 'edit' ? 'Lasă gol ca să o păstrezi.' : 'Min 8 caractere.'), ->helperText(fn (string $operation) => $operation === 'edit' ? 'Lasă gol ca să o păstrezi.' : 'Min 8 caractere.'),
]), ]),
Forms\Components\Textarea::make('notes')->label('Note interne')->rows(2)->columnSpanFull(), Forms\Components\Textarea::make('notes')->label(__('Note interne'))->rows(2)->columnSpanFull(),
]); ]);
} }
@@ -92,25 +92,25 @@ class SuperAdminResource extends Resource
'sales' => 'info', 'sales' => 'info',
default => 'gray', default => 'gray',
}), }),
Tables\Columns\IconColumn::make('is_active')->boolean()->label('Activ'), Tables\Columns\IconColumn::make('is_active')->boolean()->label(__('Activ')),
Tables\Columns\IconColumn::make('app_authentication_secret') Tables\Columns\IconColumn::make('app_authentication_secret')
->label('2FA') ->label(__('2FA'))
->boolean() ->boolean()
->getStateUsing(fn ($record) => $record?->app_authentication_secret !== null) ->getStateUsing(fn ($record) => $record?->app_authentication_secret !== null)
->trueIcon('heroicon-o-lock-closed') ->trueIcon('heroicon-o-lock-closed')
->falseIcon('heroicon-o-lock-open') ->falseIcon('heroicon-o-lock-open')
->trueColor('success') ->trueColor('success')
->falseColor('warning'), ->falseColor('warning'),
Tables\Columns\TextColumn::make('last_login_at')->label('Ultim login')->dateTime()->placeholder('—'), Tables\Columns\TextColumn::make('last_login_at')->label(__('Ultim login'))->dateTime()->placeholder('—'),
Tables\Columns\TextColumn::make('created_at')->date(), Tables\Columns\TextColumn::make('created_at')->date(),
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('role')->options(SuperAdmin::ROLES), Tables\Filters\SelectFilter::make('role')->options(SuperAdmin::ROLES),
Tables\Filters\TernaryFilter::make('is_active')->label('Activ'), Tables\Filters\TernaryFilter::make('is_active')->label(__('Activ')),
]) ])
->actions([ ->actions([
Actions\Action::make('reset_password') Actions\Action::make('reset_password')
->label('Reset parolă') ->label(__('Reset parolă'))
->icon('heroicon-m-key') ->icon('heroicon-m-key')
->color('warning') ->color('warning')
->schema([ ->schema([
@@ -119,7 +119,7 @@ class SuperAdminResource extends Resource
]) ])
->action(function (SuperAdmin $record, array $data) { ->action(function (SuperAdmin $record, array $data) {
$record->update(['password' => Hash::make($data['new_password'])]); $record->update(['password' => Hash::make($data['new_password'])]);
Notification::make()->title('Parolă resetată.')->success()->send(); Notification::make()->title(__('Parolă resetată.'))->success()->send();
}), }),
Actions\Action::make('toggle_2fa') Actions\Action::make('toggle_2fa')
->label(fn (SuperAdmin $record) => $record->app_authentication_secret ? 'Dezactivează 2FA' : 'Forțează re-setare 2FA') ->label(fn (SuperAdmin $record) => $record->app_authentication_secret ? 'Dezactivează 2FA' : 'Forțează re-setare 2FA')
@@ -133,19 +133,19 @@ class SuperAdminResource extends Resource
'app_authentication_recovery_codes' => null, 'app_authentication_recovery_codes' => null,
'email_authentication_at' => null, 'email_authentication_at' => null,
])->saveQuietly(); ])->saveQuietly();
Notification::make()->title('2FA dezactivat.')->success()->send(); Notification::make()->title(__('2FA dezactivat.'))->success()->send();
}), }),
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make() Actions\DeleteAction::make()
->before(function (SuperAdmin $record) { ->before(function (SuperAdmin $record) {
if (auth('central')->id() === $record->id) { if (auth('central')->id() === $record->id) {
Notification::make()->title('Nu te poți șterge pe tine!')->danger()->send(); Notification::make()->title(__('Nu te poți șterge pe tine!'))->danger()->send();
return false; return false;
} }
}), }),
]) ])
->emptyStateHeading('Doar tu ești aici') ->emptyStateHeading(__('Doar tu ești aici'))
->emptyStateDescription('Adaugă echipa ta — colegi de la suport, sales, finance — fiecare cu rolul lui.') ->emptyStateDescription(__('Adaugă echipa ta — colegi de la suport, sales, finance — fiecare cu rolul lui.'))
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -17,7 +17,7 @@ class PendingPayments extends BaseWidget
public function table(Table $table): Table public function table(Table $table): Table
{ {
return $table return $table
->heading('Facturi pending / overdue') ->heading(__('Facturi pending / overdue'))
->query( ->query(
Subscription::query() Subscription::query()
->whereIn('status', ['pending', 'overdue']) ->whereIn('status', ['pending', 'overdue'])
@@ -27,10 +27,10 @@ class PendingPayments extends BaseWidget
) )
->columns([ ->columns([
Tables\Columns\TextColumn::make('invoice_number') Tables\Columns\TextColumn::make('invoice_number')
->label('Factură') ->label(__('Factură'))
->copyable() ->copyable()
->url(fn ($record) => SubscriptionResource::getUrl('edit', ['record' => $record])), ->url(fn ($record) => SubscriptionResource::getUrl('edit', ['record' => $record])),
Tables\Columns\TextColumn::make('company.name')->label('Companie'), Tables\Columns\TextColumn::make('company.name')->label(__('Companie')),
Tables\Columns\TextColumn::make('plan.name')->placeholder('—'), Tables\Columns\TextColumn::make('plan.name')->placeholder('—'),
Tables\Columns\TextColumn::make('amount')->money(fn ($record) => $record->currency)->weight('bold'), Tables\Columns\TextColumn::make('amount')->money(fn ($record) => $record->currency)->weight('bold'),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
@@ -38,11 +38,11 @@ class PendingPayments extends BaseWidget
->color(fn ($s) => $s === 'overdue' ? 'danger' : 'warning') ->color(fn ($s) => $s === 'overdue' ? 'danger' : 'warning')
->formatStateUsing(fn ($s) => Subscription::STATUSES[$s] ?? $s), ->formatStateUsing(fn ($s) => Subscription::STATUSES[$s] ?? $s),
Tables\Columns\TextColumn::make('due_at') Tables\Columns\TextColumn::make('due_at')
->label('Scadent') ->label(__('Scadent'))
->dateTime() ->dateTime()
->color(fn ($record) => $record->due_at && $record->due_at->isPast() ? 'danger' : null), ->color(fn ($record) => $record->due_at && $record->due_at->isPast() ? 'danger' : null),
]) ])
->emptyStateHeading('🎉 Toate facturile sunt plătite') ->emptyStateHeading(__('🎉 Toate facturile sunt plătite'))
->paginated(false); ->paginated(false);
} }
} }
@@ -39,7 +39,7 @@ class PlatformStats extends BaseWidget
->color('primary') ->color('primary')
->url(\App\Filament\Central\Resources\CompanyResource::getUrl('index')), ->url(\App\Filament\Central\Resources\CompanyResource::getUrl('index')),
Stat::make('Active', $active) Stat::make('Active', $active)
->description("✓ Pe abonament plătit") ->description(__("✓ Pe abonament plătit"))
->descriptionIcon('heroicon-m-check-circle') ->descriptionIcon('heroicon-m-check-circle')
->color('success'), ->color('success'),
Stat::make('În trial', $trial) Stat::make('În trial', $trial)
@@ -51,7 +51,7 @@ class PlatformStats extends BaseWidget
->descriptionIcon('heroicon-m-exclamation-triangle') ->descriptionIcon('heroicon-m-exclamation-triangle')
->color($expiring > 0 ? 'danger' : 'success'), ->color($expiring > 0 ? 'danger' : 'success'),
Stat::make('Venit luna curentă', number_format($mrr, 0, ',', ' ') . ' MDL') Stat::make('Venit luna curentă', number_format($mrr, 0, ',', ' ') . ' MDL')
->description('MRR realizat') ->description(__('MRR realizat'))
->descriptionIcon('heroicon-m-banknotes') ->descriptionIcon('heroicon-m-banknotes')
->color('success'), ->color('success'),
Stat::make('Facturi neplătite', $overdue) Stat::make('Facturi neplătite', $overdue)
@@ -17,7 +17,7 @@ class RecentTenants extends BaseWidget
public function table(Table $table): Table public function table(Table $table): Table
{ {
return $table return $table
->heading('Tenants recenți') ->heading(__('Tenants recenți'))
->query(Company::query()->latest()->limit(8)) ->query(Company::query()->latest()->limit(8))
->columns([ ->columns([
Tables\Columns\TextColumn::make('slug') Tables\Columns\TextColumn::make('slug')
@@ -33,8 +33,8 @@ class RecentTenants extends BaseWidget
default => 'gray', default => 'gray',
}), }),
Tables\Columns\TextColumn::make('plan.name')->placeholder('—'), Tables\Columns\TextColumn::make('plan.name')->placeholder('—'),
Tables\Columns\TextColumn::make('users_count')->counts('users')->label('Useri'), Tables\Columns\TextColumn::make('users_count')->counts('users')->label(__('Useri')),
Tables\Columns\TextColumn::make('created_at')->label('Creat')->since(), Tables\Columns\TextColumn::make('created_at')->label(__('Creat'))->since(),
]) ])
->paginated(false); ->paginated(false);
} }
+6 -1
View File
@@ -24,7 +24,12 @@ class AiAssistant extends Page
protected static ?int $navigationSort = 71; protected static ?int $navigationSort = 71;
protected static ?string $title = 'Asistent AI'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Asistent AI');
}
protected string $view = 'filament.tenant.pages.ai-assistant'; protected string $view = 'filament.tenant.pages.ai-assistant';
+11 -6
View File
@@ -23,7 +23,12 @@ class ApiTokens extends Page
protected static ?int $navigationSort = 95; protected static ?int $navigationSort = 95;
protected static ?string $title = 'API Tokens'; protected static ?string $title = null;
public function getTitle(): string
{
return __('API Tokens');
}
protected string $view = 'filament.tenant.pages.api-tokens'; protected string $view = 'filament.tenant.pages.api-tokens';
@@ -46,13 +51,13 @@ class ApiTokens extends Page
{ {
return [ return [
Actions\Action::make('create') Actions\Action::make('create')
->label('Generează token') ->label(__('Generează token'))
->icon('heroicon-m-plus') ->icon('heroicon-m-plus')
->schema([ ->schema([
Forms\Components\TextInput::make('name') Forms\Components\TextInput::make('name')
->label('Nume (descriere)') ->label(__('Nume (descriere)'))
->required() ->required()
->placeholder('ex: app-mobil, integrare-erp'), ->placeholder(__('ex: app-mobil, integrare-erp')),
]) ])
->action(function (array $data) { ->action(function (array $data) {
$u = auth()->user(); $u = auth()->user();
@@ -60,8 +65,8 @@ class ApiTokens extends Page
$token = $u->createToken($data['name']); $token = $u->createToken($data['name']);
$this->newToken = $token->plainTextToken; $this->newToken = $token->plainTextToken;
Notification::make() Notification::make()
->title('Token generat!') ->title(__('Token generat!'))
->body('Copiază token-ul ACUM — nu va mai fi afișat.') ->body(__('Copiază token-ul ACUM — nu va mai fi afișat.'))
->success() ->success()
->persistent() ->persistent()
->send(); ->send();
+6 -1
View File
@@ -24,7 +24,12 @@ class Backup extends Page
protected static ?int $navigationSort = 96; protected static ?int $navigationSort = 96;
protected static ?string $title = 'Backup & Export date'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Backup & Export date');
}
protected string $view = 'filament.tenant.pages.backup'; protected string $view = 'filament.tenant.pages.backup';
+13 -8
View File
@@ -27,7 +27,12 @@ class CalendarBoard extends Page
protected static ?int $navigationSort = 8; protected static ?int $navigationSort = 8;
protected static ?string $title = 'Calendar vizual'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Calendar vizual');
}
protected string $view = 'filament.tenant.pages.calendar'; protected string $view = 'filament.tenant.pages.calendar';
@@ -290,7 +295,7 @@ class CalendarBoard extends Page
} }
$a->date = $toDate; $a->date = $toDate;
$a->save(); $a->save();
Notification::make()->title('Programare mutată')->body($a->title . ' → ' . $toDate)->success()->send(); Notification::make()->title(__('Programare mutată'))->body($a->title . ' → ' . $toDate)->success()->send();
} }
public function openEvent(int $id): void public function openEvent(int $id): void
@@ -330,7 +335,7 @@ class CalendarBoard extends Page
{ {
Appointment::where('id', $id)->delete(); Appointment::where('id', $id)->delete();
$this->openEventId = null; $this->openEventId = null;
Notification::make()->title('Programare ștearsă')->success()->send(); Notification::make()->title(__('Programare ștearsă'))->success()->send();
} }
public function openNewForm(int $rowId = 0, string $date = ''): void public function openNewForm(int $rowId = 0, string $date = ''): void
@@ -372,7 +377,7 @@ class CalendarBoard extends Page
{ {
$name = trim($this->newPost['name'] ?? ''); $name = trim($this->newPost['name'] ?? '');
if ($name === '') { if ($name === '') {
Notification::make()->title('Numele este obligatoriu')->danger()->send(); Notification::make()->title(__('Numele este obligatoriu'))->danger()->send();
return; return;
} }
Post::create([ Post::create([
@@ -384,7 +389,7 @@ class CalendarBoard extends Page
'sort_order' => 100, 'sort_order' => 100,
]); ]);
$this->showNewPostForm = false; $this->showNewPostForm = false;
Notification::make()->title('Spațiu de lucru adăugat')->success()->send(); Notification::make()->title(__('Spațiu de lucru adăugat'))->success()->send();
} }
/** Inline rename + reassign default master from row label click. */ /** Inline rename + reassign default master from row label click. */
@@ -409,7 +414,7 @@ class CalendarBoard extends Page
'default_master_id' => $this->renamingPostMasterId ?: null, 'default_master_id' => $this->renamingPostMasterId ?: null,
]); ]);
$this->renamingPostId = null; $this->renamingPostId = null;
Notification::make()->title('Post actualizat')->success()->send(); Notification::make()->title(__('Post actualizat'))->success()->send();
} }
/** Generate PDF for all appointments in the visible period. */ /** Generate PDF for all appointments in the visible period. */
@@ -467,7 +472,7 @@ class CalendarBoard extends Page
{ {
$d = $this->newAppt; $d = $this->newAppt;
if (empty($d['title']) || empty($d['date'])) { if (empty($d['title']) || empty($d['date'])) {
Notification::make()->title('Subiect și data sunt obligatorii')->danger()->send(); Notification::make()->title(__('Subiect și data sunt obligatorii'))->danger()->send();
return; return;
} }
Appointment::create([ Appointment::create([
@@ -483,7 +488,7 @@ class CalendarBoard extends Page
'status' => 'scheduled', 'status' => 'scheduled',
]); ]);
$this->showNewForm = false; $this->showNewForm = false;
Notification::make()->title('Programare adăugată')->success()->send(); Notification::make()->title(__('Programare adăugată'))->success()->send();
} }
public function getMasterOptions(): array public function getMasterOptions(): array
@@ -27,7 +27,12 @@ class ExcelImportWizard extends Page
protected static ?int $navigationSort = 65; protected static ?int $navigationSort = 65;
protected static ?string $title = 'Import factură Excel/CSV'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Import factură Excel/CSV');
}
protected string $view = 'filament.tenant.pages.excel-import-wizard'; protected string $view = 'filament.tenant.pages.excel-import-wizard';
@@ -62,11 +67,11 @@ class ExcelImportWizard extends Page
public function goToStep2(): void public function goToStep2(): void
{ {
if (! $this->supplierId) { if (! $this->supplierId) {
Notification::make()->title('Selectează furnizorul')->danger()->send(); Notification::make()->title(__('Selectează furnizorul'))->danger()->send();
return; return;
} }
if (! $this->upload) { if (! $this->upload) {
Notification::make()->title('Încarcă fișierul Excel sau CSV')->danger()->send(); Notification::make()->title(__('Încarcă fișierul Excel sau CSV'))->danger()->send();
return; return;
} }
@@ -96,7 +101,7 @@ class ExcelImportWizard extends Page
$this->previewSummary = $result['summary']; $this->previewSummary = $result['summary'];
if (empty($this->previewRows)) { if (empty($this->previewRows)) {
Notification::make()->title('Nu am găsit linii valide — verifică maparea coloanelor')->warning()->send(); Notification::make()->title(__('Nu am găsit linii valide — verifică maparea coloanelor'))->warning()->send();
return; return;
} }
+6 -1
View File
@@ -26,7 +26,12 @@ class Finance extends Page
protected static ?int $navigationSort = 49; protected static ?int $navigationSort = 49;
protected static ?string $title = 'Finanțe'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Finanțe');
}
protected string $view = 'filament.tenant.pages.finance'; protected string $view = 'filament.tenant.pages.finance';
+7 -2
View File
@@ -22,7 +22,12 @@ class Integrations extends Page
protected static ?int $navigationSort = 63; protected static ?int $navigationSort = 63;
protected static ?string $title = 'Integrări externe'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Integrări externe');
}
protected string $view = 'filament.tenant.pages.integrations'; protected string $view = 'filament.tenant.pages.integrations';
@@ -54,7 +59,7 @@ class Integrations extends Page
'integrations' => $this->config, 'integrations' => $this->config,
]), ]),
]); ]);
Notification::make()->title('Integrări salvate')->success()->send(); Notification::make()->title(__('Integrări salvate'))->success()->send();
} }
public function integrations(): array public function integrations(): array
+11 -6
View File
@@ -28,7 +28,12 @@ class MechanicBoard extends Page
protected static ?int $navigationSort = 25; protected static ?int $navigationSort = 25;
protected static ?string $title = 'Atelierul meu'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Atelierul meu');
}
protected string $view = 'filament.tenant.pages.mechanic-board'; protected string $view = 'filament.tenant.pages.mechanic-board';
@@ -46,25 +51,25 @@ class MechanicBoard extends Page
return [ return [
[ [
'key' => 'in_work', 'key' => 'in_work',
'label' => 'În lucru', 'label' => __('În lucru'),
'color' => '#f59e0b', 'color' => '#f59e0b',
'items' => $all->where('status', 'in_work')->values(), 'items' => $all->where('status', 'in_work')->values(),
], ],
[ [
'key' => 'awaiting_parts', 'key' => 'awaiting_parts',
'label' => 'Așteaptă piese', 'label' => __('Așteaptă piese'),
'color' => '#8b5cf6', 'color' => '#8b5cf6',
'items' => $all->whereIn('status', ['awaiting_parts'])->values(), 'items' => $all->whereIn('status', ['awaiting_parts'])->values(),
], ],
[ [
'key' => 'ready', 'key' => 'ready',
'label' => 'Gata', 'label' => __('Gata'),
'color' => '#10b981', 'color' => '#10b981',
'items' => $all->where('status', 'ready')->values(), 'items' => $all->where('status', 'ready')->values(),
], ],
[ [
'key' => 'recent', 'key' => 'recent',
'label' => 'Recente / restul', 'label' => __('Recente / restul'),
'color' => '#64748b', 'color' => '#64748b',
'items' => $all->whereIn('status', ['done', 'approved', 'diagnosis']) 'items' => $all->whereIn('status', ['done', 'approved', 'diagnosis'])
->take(20) ->take(20)
@@ -119,7 +124,7 @@ class MechanicBoard extends Page
return; return;
} }
$work->block($this->blockReason, trim($this->blockNote) ?: null); $work->block($this->blockReason, trim($this->blockNote) ?: null);
Notification::make()->title('Lucrare blocată')->body($work->name . ' · ' . WorkOrderWork::BLOCK_REASONS[$this->blockReason])->warning()->send(); Notification::make()->title(__('Lucrare blocată'))->body($work->name . ' · ' . WorkOrderWork::BLOCK_REASONS[$this->blockReason])->warning()->send();
$this->blockingWorkId = null; $this->blockingWorkId = null;
} }
+6 -1
View File
@@ -28,7 +28,12 @@ class MechanicKpi extends Page
protected static ?int $navigationSort = 28; protected static ?int $navigationSort = 28;
protected static ?string $title = 'KPI mecanici'; protected static ?string $title = null;
public function getTitle(): string
{
return __('KPI mecanici');
}
protected string $view = 'filament.tenant.pages.mechanic-kpi'; protected string $view = 'filament.tenant.pages.mechanic-kpi';
+19 -14
View File
@@ -19,7 +19,12 @@ class Onboarding extends Page
protected static bool $shouldRegisterNavigation = false; protected static bool $shouldRegisterNavigation = false;
protected static ?string $title = 'Bun venit în AutoCRM!'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Bun venit în AutoCRM!');
}
protected string $view = 'filament.tenant.pages.onboarding'; protected string $view = 'filament.tenant.pages.onboarding';
@@ -54,18 +59,18 @@ class Onboarding extends Page
{ {
return $schema return $schema
->components([ ->components([
Schemas\Components\Section::make('Pas 1 — Datele afacerii') Schemas\Components\Section::make(__('Pas 1 — Datele afacerii'))
->visible(fn () => $this->step === 1) ->visible(fn () => $this->step === 1)
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('display_name') Forms\Components\TextInput::make('display_name')
->label('Denumire afișată')->required()->maxLength(120), ->label(__('Denumire afișată'))->required()->maxLength(120),
Forms\Components\TextInput::make('city') Forms\Components\TextInput::make('city')
->label('Oraș')->maxLength(60), ->label(__('Oraș'))->maxLength(60),
Forms\Components\TextInput::make('phone') Forms\Components\TextInput::make('phone')
->label('Telefon principal')->tel()->maxLength(40), ->label(__('Telefon principal'))->tel()->maxLength(40),
Forms\Components\Select::make('currency') Forms\Components\Select::make('currency')
->label('Monedă') ->label(__('Monedă'))
->options([ ->options([
'MDL' => 'MDL — Leu moldovenesc', 'MDL' => 'MDL — Leu moldovenesc',
'EUR' => 'EUR — Euro', 'EUR' => 'EUR — Euro',
@@ -78,28 +83,28 @@ class Onboarding extends Page
->required() ->required()
->searchable(), ->searchable(),
]), ]),
Schemas\Components\Section::make('Pas 2 — Brand & limbă') Schemas\Components\Section::make(__('Pas 2 — Brand & limbă'))
->visible(fn () => $this->step === 2) ->visible(fn () => $this->step === 2)
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('language') Forms\Components\Select::make('language')
->label('Limbă') ->label(__('Limbă'))
->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English']) ->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English'])
->required(), ->required(),
Forms\Components\ColorPicker::make('theme_color') Forms\Components\ColorPicker::make('theme_color')
->label('Culoare brand'), ->label(__('Culoare brand')),
Forms\Components\FileUpload::make('logo') Forms\Components\FileUpload::make('logo')
->label('Logo (opțional)') ->label(__('Logo (opțional)'))
->image()->imageEditor()->disk('public') ->image()->imageEditor()->disk('public')
->directory('tmp-uploads')->visibility('public') ->directory('tmp-uploads')->visibility('public')
->maxSize(2048), ->maxSize(2048),
]), ]),
Schemas\Components\Section::make('Pas 3 — Tarif & terminat') Schemas\Components\Section::make(__('Pas 3 — Tarif & terminat'))
->visible(fn () => $this->step === 3) ->visible(fn () => $this->step === 3)
->columns(1) ->columns(1)
->schema([ ->schema([
Forms\Components\TextInput::make('labor_rate') Forms\Components\TextInput::make('labor_rate')
->label('Tarif normo-oră (poți schimba oricând)') ->label(__('Tarif normo-oră (poți schimba oricând)'))
->numeric()->required(), ->numeric()->required(),
]), ]),
]) ])
@@ -147,8 +152,8 @@ class Onboarding extends Page
} }
Notification::make() Notification::make()
->title('🎉 Bun venit în AutoCRM!') ->title(__('🎉 Bun venit în AutoCRM!'))
->body('Setările au fost salvate. Hai să adăugăm primul client.') ->body(__('Setările au fost salvate. Hai să adăugăm primul client.'))
->success() ->success()
->send(); ->send();
+6 -1
View File
@@ -27,7 +27,12 @@ class PipelineBoard extends Page
protected static ?int $navigationSort = 5; protected static ?int $navigationSort = 5;
protected static ?string $title = 'Pipeline'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Pipeline');
}
protected string $view = 'filament.tenant.pages.pipeline-board'; protected string $view = 'filament.tenant.pages.pipeline-board';
@@ -23,7 +23,12 @@ class Recommendations extends Page
protected static ?int $navigationSort = 72; protected static ?int $navigationSort = 72;
protected static ?string $title = 'Recomandări — clienți de urmărit'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Recomandări — clienți de urmărit');
}
protected string $view = 'filament.tenant.pages.recommendations'; protected string $view = 'filament.tenant.pages.recommendations';
+6 -1
View File
@@ -30,7 +30,12 @@ class Reports extends Page
protected static ?int $navigationSort = 70; protected static ?int $navigationSort = 70;
protected static ?string $title = 'Rapoarte'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Rapoarte');
}
protected string $view = 'filament.tenant.pages.reports'; protected string $view = 'filament.tenant.pages.reports';
+6 -1
View File
@@ -33,7 +33,12 @@ class Scanner extends Page
protected static ?int $navigationSort = 39; protected static ?int $navigationSort = 39;
protected static ?string $title = 'Scaner cod QR / Bare'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Scaner cod QR / Bare');
}
protected string $view = 'filament.tenant.pages.scanner'; protected string $view = 'filament.tenant.pages.scanner';
+70 -65
View File
@@ -27,7 +27,12 @@ class Settings extends Page
protected static ?int $navigationSort = 90; protected static ?int $navigationSort = 90;
protected static ?string $title = 'Setări companie'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Setări companie');
}
protected string $view = 'filament.tenant.pages.settings'; protected string $view = 'filament.tenant.pages.settings';
@@ -81,23 +86,23 @@ class Settings extends Page
{ {
return $schema return $schema
->components([ ->components([
Schemas\Components\Section::make('Brand & contact') Schemas\Components\Section::make(__('Brand & contact'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('display_name')->label('Denumire afișată')->maxLength(120), Forms\Components\TextInput::make('display_name')->label(__('Denumire afișată'))->maxLength(120),
Forms\Components\TextInput::make('city')->label('Oraș')->maxLength(60), Forms\Components\TextInput::make('city')->label(__('Oraș'))->maxLength(60),
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\TextInput::make('email')->email()->maxLength(120),
]), ]),
Schemas\Components\Section::make('Localizare & monedă') Schemas\Components\Section::make(__('Localizare & monedă'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\Select::make('language') Forms\Components\Select::make('language')
->label('Limbă default') ->label(__('Limbă default'))
->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English']) ->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English'])
->required(), ->required(),
Forms\Components\Select::make('currency') Forms\Components\Select::make('currency')
->label('Monedă') ->label(__('Monedă'))
->options([ ->options([
'MDL' => 'MDL — Leu moldovenesc', 'MDL' => 'MDL — Leu moldovenesc',
'EUR' => 'EUR — Euro', 'EUR' => 'EUR — Euro',
@@ -108,133 +113,133 @@ class Settings extends Page
]) ])
->required() ->required()
->searchable(), ->searchable(),
Forms\Components\ColorPicker::make('theme_color')->label('Culoare brand'), Forms\Components\ColorPicker::make('theme_color')->label(__('Culoare brand')),
]), ]),
Schemas\Components\Section::make('Servicii & tarif') Schemas\Components\Section::make(__('Servicii & tarif'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('labor_rate')->label('Tarif normo-oră')->numeric()->required(), Forms\Components\TextInput::make('labor_rate')->label(__('Tarif normo-oră'))->numeric()->required(),
]), ]),
Schemas\Components\Section::make('Marjă internă (nu TVA)') Schemas\Components\Section::make(__('Marjă internă (nu TVA)'))
->description('Procent implicit aplicat la manopere. Se scade din prețul de manoperă pentru a determina baza salariului mecanicului. Vizibilă doar rolurilor cu drept „finance.view_internal_margin".') ->description(__('Procent implicit aplicat la manopere. Se scade din prețul de manoperă pentru a determina baza salariului mecanicului. Vizibilă doar rolurilor cu drept „finance.view_internal_margin".'))
->columns(2) ->columns(2)
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false) ->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false)
->schema([ ->schema([
Forms\Components\TextInput::make('default_internal_margin_pct') Forms\Components\TextInput::make('default_internal_margin_pct')
->label('% marjă implicit') ->label(__('% marjă implicit'))
->numeric() ->numeric()
->step(0.01) ->step(0.01)
->minValue(0) ->minValue(0)
->maxValue(90) ->maxValue(90)
->suffix('%') ->suffix('%')
->placeholder('Ex: 20') ->placeholder(__('Ex: 20'))
->helperText('Fallback când mecanicul nu are marja proprie setată. Lasă gol = 0%.'), ->helperText(__('Fallback când mecanicul nu are marja proprie setată. Lasă gol = 0%.')),
Forms\Components\Toggle::make('show_internal_margin_details') Forms\Components\Toggle::make('show_internal_margin_details')
->label('Afișează detalii marjă la procesele calculate') ->label(__('Afișează detalii marjă la procesele calculate'))
->helperText('On = arată „Bază salariu: X · marjă Y%" sub Total în tabelul Manopere. Off = doar Total (util când review-uiești o Fișă cu clientul de față).') ->helperText(__('On = arată „Bază salariu: X · marjă Y%" sub Total în tabelul Manopere. Off = doar Total (util când review-uiești o Fișă cu clientul de față).'))
->default(true), ->default(true),
]), ]),
Schemas\Components\Section::make('Liste configurabile') Schemas\Components\Section::make(__('Liste configurabile'))
->columns(1) ->columns(1)
->schema([ ->schema([
Forms\Components\Textarea::make('services') Forms\Components\Textarea::make('services')
->label('Servicii oferite (separate prin virgulă)') ->label(__('Servicii oferite (separate prin virgulă)'))
->rows(2), ->rows(2),
Forms\Components\Textarea::make('cars') Forms\Components\Textarea::make('cars')
->label('Mărci auto suportate (separate prin virgulă)') ->label(__('Mărci auto suportate (separate prin virgulă)'))
->rows(2), ->rows(2),
]), ]),
Schemas\Components\Section::make('Logo & favicon') Schemas\Components\Section::make(__('Logo & favicon'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\FileUpload::make('logo') Forms\Components\FileUpload::make('logo')
->label('Logo') ->label(__('Logo'))
->image() ->image()
->imageEditor() ->imageEditor()
->disk('public') ->disk('public')
->directory('tmp-uploads') ->directory('tmp-uploads')
->visibility('public') ->visibility('public')
->maxSize(2048) ->maxSize(2048)
->helperText('PNG/SVG, max 2 MB. Apare în sidebar.'), ->helperText(__('PNG/SVG, max 2 MB. Apare în sidebar.')),
Forms\Components\FileUpload::make('favicon') Forms\Components\FileUpload::make('favicon')
->label('Favicon') ->label(__('Favicon'))
->image() ->image()
->disk('public') ->disk('public')
->directory('tmp-uploads') ->directory('tmp-uploads')
->visibility('public') ->visibility('public')
->maxSize(512) ->maxSize(512)
->helperText('PNG/ICO, max 512 KB.'), ->helperText(__('PNG/ICO, max 512 KB.')),
]), ]),
Schemas\Components\Section::make('Notificări') Schemas\Components\Section::make(__('Notificări'))
->description('Activează / dezactivează notificările auto către clienți. Telegram are prioritate dacă clientul are cont legat.') ->description(__('Activează / dezactivează notificările auto către clienți. Telegram are prioritate dacă clientul are cont legat.'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Toggle::make('notify_wo_ready')->label('Mașina e gata de ridicat')->default(true), Forms\Components\Toggle::make('notify_wo_ready')->label(__('Mașina e gata de ridicat'))->default(true),
Forms\Components\Toggle::make('notify_payment')->label('Confirmare plată primită')->default(true), Forms\Components\Toggle::make('notify_payment')->label(__('Confirmare plată primită'))->default(true),
Forms\Components\Toggle::make('notify_appointment')->label('Programare confirmată')->default(true), Forms\Components\Toggle::make('notify_appointment')->label(__('Programare confirmată'))->default(true),
Forms\Components\Toggle::make('notify_reminder')->label('Reminder ITP / revizie')->default(true), Forms\Components\Toggle::make('notify_reminder')->label(__('Reminder ITP / revizie'))->default(true),
]), ]),
Schemas\Components\Section::make('Telegram bot') Schemas\Components\Section::make(__('Telegram bot'))
->description('Creează un bot la @BotFather, lipește token-ul aici și apasă „Setează webhook". Clienții îți scriu la bot, partajează telefonul, iar codul se leagă automat de fișa lor.') ->description(__('Creează un bot la @BotFather, lipește token-ul aici și apasă „Setează webhook". Clienții îți scriu la bot, partajează telefonul, iar codul se leagă automat de fișa lor.'))
->columns(1) ->columns(1)
->schema([ ->schema([
Forms\Components\TextInput::make('telegram_bot_token') Forms\Components\TextInput::make('telegram_bot_token')
->label('Bot token') ->label(__('Bot token'))
->password() ->password()
->revealable() ->revealable()
->placeholder('123456:ABC-XYZ...') ->placeholder(__('123456:ABC-XYZ...'))
->helperText(fn () => 'Webhook URL: ' . ->helperText(fn () => 'Webhook URL: ' .
app(\App\Services\Notifications\TelegramService::class) app(\App\Services\Notifications\TelegramService::class)
->webhookUrlFor(app(\App\Tenancy\TenantManager::class)->current())), ->webhookUrlFor(app(\App\Tenancy\TenantManager::class)->current())),
]), ]),
Schemas\Components\Section::make('Reminder service auto') Schemas\Components\Section::make(__('Reminder service auto'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('reminder_after_days') Forms\Components\TextInput::make('reminder_after_days')
->label('Trimite reminder după X zile fără vizită') ->label(__('Trimite reminder după X zile fără vizită'))
->numeric() ->numeric()
->minValue(30) ->minValue(30)
->default(365), ->default(365),
Forms\Components\TextInput::make('reminder_cooldown_days') Forms\Components\TextInput::make('reminder_cooldown_days')
->label('Nu re-trimite mai des de X zile') ->label(__('Nu re-trimite mai des de X zile'))
->numeric() ->numeric()
->minValue(7) ->minValue(7)
->default(30), ->default(30),
]), ]),
Schemas\Components\Section::make('Magazin online') Schemas\Components\Section::make(__('Magazin online'))
->description('Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat".') ->description(__('Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat".'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Toggle::make('shop_enabled')->label('Magazin activ')->columnSpanFull(), Forms\Components\Toggle::make('shop_enabled')->label(__('Magazin activ'))->columnSpanFull(),
Forms\Components\CheckboxList::make('shop_delivery_methods') Forms\Components\CheckboxList::make('shop_delivery_methods')
->label('Metode de livrare') ->label(__('Metode de livrare'))
->options(\App\Models\Tenant\OnlineOrder::DELIVERY) ->options(\App\Models\Tenant\OnlineOrder::DELIVERY)
->default(['pickup']) ->default(['pickup'])
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\TextInput::make('shop_delivery_fee')->label('Taxă livrare')->numeric()->default(0), Forms\Components\TextInput::make('shop_delivery_fee')->label(__('Taxă livrare'))->numeric()->default(0),
Forms\Components\TextInput::make('shop_free_delivery_over')->label('Livrare gratuită peste')->numeric()->default(0)->helperText('0 = dezactivat'), Forms\Components\TextInput::make('shop_free_delivery_over')->label(__('Livrare gratuită peste'))->numeric()->default(0)->helperText(__('0 = dezactivat')),
]), ]),
Schemas\Components\Section::make('Asistent AI') Schemas\Components\Section::make(__('Asistent AI'))
->description('Adaugă chei API ca să activezi asistentul. Cheile rămân la voi — nu sunt partajate.') ->description(__('Adaugă chei API ca să activezi asistentul. Cheile rămân la voi — nu sunt partajate.'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('ai_default_provider') Forms\Components\Select::make('ai_default_provider')
->label('Provider implicit') ->label(__('Provider implicit'))
->options(['claude' => 'Claude (Anthropic)', 'gpt' => 'ChatGPT (OpenAI)', 'gemini' => 'Gemini (Google)']) ->options(['claude' => 'Claude (Anthropic)', 'gpt' => 'ChatGPT (OpenAI)', 'gemini' => 'Gemini (Google)'])
->default('claude'), ->default('claude'),
Forms\Components\TextInput::make('ai_claude_key')->label('Claude API Key')->password()->revealable()->placeholder('sk-ant-...'), Forms\Components\TextInput::make('ai_claude_key')->label(__('Claude API Key'))->password()->revealable()->placeholder('sk-ant-...'),
Forms\Components\Select::make('ai_model_claude') Forms\Components\Select::make('ai_model_claude')
->label('Model Claude') ->label(__('Model Claude'))
->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['claude']) ->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['claude'])
->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['claude']), ->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['claude']),
Forms\Components\TextInput::make('ai_gpt_key')->label('OpenAI API Key')->password()->revealable()->placeholder('sk-proj-...'), Forms\Components\TextInput::make('ai_gpt_key')->label(__('OpenAI API Key'))->password()->revealable()->placeholder('sk-proj-...'),
Forms\Components\Select::make('ai_model_gpt') Forms\Components\Select::make('ai_model_gpt')
->label('Model OpenAI') ->label(__('Model OpenAI'))
->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['gpt']) ->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['gpt'])
->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['gpt']), ->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['gpt']),
Forms\Components\TextInput::make('ai_gemini_key')->label('Gemini API Key')->password()->revealable(), Forms\Components\TextInput::make('ai_gemini_key')->label(__('Gemini API Key'))->password()->revealable(),
Forms\Components\Select::make('ai_model_gemini') Forms\Components\Select::make('ai_model_gemini')
->label('Model Gemini') ->label(__('Model Gemini'))
->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['gemini']) ->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['gemini'])
->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['gemini']), ->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['gemini']),
]), ]),
@@ -247,7 +252,7 @@ class Settings extends Page
$data = $this->form->getState(); $data = $this->form->getState();
$company = app(TenantManager::class)->current(); $company = app(TenantManager::class)->current();
if (! $company) { if (! $company) {
Notification::make()->title('Tenant not resolved')->danger()->send(); Notification::make()->title(__('Tenant not resolved'))->danger()->send();
return; return;
} }
@@ -312,22 +317,22 @@ class Settings extends Page
} }
} }
Notification::make()->title('Setări salvate')->success()->send(); Notification::make()->title(__('Setări salvate'))->success()->send();
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Actions\Action::make('push_test') Actions\Action::make('push_test')
->label('Test notificare push') ->label(__('Test notificare push'))
->icon('heroicon-m-bell-alert') ->icon('heroicon-m-bell-alert')
->color('gray') ->color('gray')
->action(function () { ->action(function () {
$svc = app(\App\Services\Notifications\WebPushService::class); $svc = app(\App\Services\Notifications\WebPushService::class);
if (! $svc->configured()) { if (! $svc->configured()) {
Notification::make() Notification::make()
->title('Web Push neconfigurat') ->title(__('Web Push neconfigurat'))
->body('Rulează `php artisan push:vapid` și adaugă cheile în .env.') ->body(__('Rulează `php artisan push:vapid` și adaugă cheile în .env.'))
->warning()->send(); ->warning()->send();
return; return;
} }
@@ -344,7 +349,7 @@ class Settings extends Page
->send(); ->send();
}), }),
Actions\Action::make('telegram_test') Actions\Action::make('telegram_test')
->label('Testează bot Telegram') ->label(__('Testează bot Telegram'))
->icon('heroicon-m-bolt') ->icon('heroicon-m-bolt')
->color('gray') ->color('gray')
->action(function () { ->action(function () {
@@ -353,7 +358,7 @@ class Settings extends Page
$r = app(TelegramService::class)->getMe($company); $r = app(TelegramService::class)->getMe($company);
if (! ($r['ok'] ?? false)) { if (! ($r['ok'] ?? false)) {
Notification::make() Notification::make()
->title('Bot Telegram nu răspunde') ->title(__('Bot Telegram nu răspunde'))
->body($r['error'] ?? 'Verifică token-ul.') ->body($r['error'] ?? 'Verifică token-ul.')
->danger()->send(); ->danger()->send();
return; return;
@@ -364,24 +369,24 @@ class Settings extends Page
->success()->send(); ->success()->send();
}), }),
Actions\Action::make('telegram_webhook') Actions\Action::make('telegram_webhook')
->label('Setează webhook') ->label(__('Setează webhook'))
->icon('heroicon-m-link') ->icon('heroicon-m-link')
->color('primary') ->color('primary')
->requiresConfirmation() ->requiresConfirmation()
->modalDescription('Telegram va trimite mesajele primite la URL-ul webhook de mai jos.') ->modalDescription(__('Telegram va trimite mesajele primite la URL-ul webhook de mai jos.'))
->action(function () { ->action(function () {
$company = app(TenantManager::class)->current(); $company = app(TenantManager::class)->current();
if (! $company) return; if (! $company) return;
$r = app(TelegramService::class)->setWebhook($company); $r = app(TelegramService::class)->setWebhook($company);
if (! ($r['ok'] ?? false)) { if (! ($r['ok'] ?? false)) {
Notification::make() Notification::make()
->title('Webhook eșuat') ->title(__('Webhook eșuat'))
->body($r['error'] ?? json_encode($r['response'] ?? [])) ->body($r['error'] ?? json_encode($r['response'] ?? []))
->danger()->send(); ->danger()->send();
return; return;
} }
Notification::make() Notification::make()
->title('Webhook setat — botul e gata') ->title(__('Webhook setat — botul e gata'))
->success()->send(); ->success()->send();
}), }),
]; ];
+6 -1
View File
@@ -23,7 +23,12 @@ class VinSearch extends Page
protected static ?int $navigationSort = 41; protected static ?int $navigationSort = 41;
protected static ?string $title = 'VIN căutare & piese'; protected static ?string $title = null;
public function getTitle(): string
{
return __('VIN căutare & piese');
}
protected string $view = 'filament.tenant.pages.vin-search'; protected string $view = 'filament.tenant.pages.vin-search';
+6 -1
View File
@@ -23,7 +23,12 @@ class Workload extends Page
protected static ?int $navigationSort = 73; protected static ?int $navigationSort = 73;
protected static ?string $title = 'Încărcare service'; protected static ?string $title = null;
public function getTitle(): string
{
return __('Încărcare service');
}
protected string $view = 'filament.tenant.pages.workload'; protected string $view = 'filament.tenant.pages.workload';
@@ -48,8 +48,8 @@ class ActivityResource extends Resource
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('created_at')->label('Când')->dateTime('d.m.Y H:i')->sortable(), 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('description')->label(__('Acțiune'))->badge()
->colors([ ->colors([
'success' => ['creat'], 'success' => ['creat'],
'info' => ['modificat'], 'info' => ['modificat'],
@@ -57,12 +57,12 @@ class ActivityResource extends Resource
'warning' => ['restaurat'], 'warning' => ['restaurat'],
]), ]),
Tables\Columns\TextColumn::make('subject_type') Tables\Columns\TextColumn::make('subject_type')
->label('Tip') ->label(__('Tip'))
->formatStateUsing(fn ($s) => $s ? class_basename($s) : '—'), ->formatStateUsing(fn ($s) => $s ? class_basename($s) : '—'),
Tables\Columns\TextColumn::make('subject_id')->label('ID')->placeholder('—'), 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('causer.name')->label(__('De către'))->placeholder(__('Sistem')),
Tables\Columns\TextColumn::make('attribute_changes') Tables\Columns\TextColumn::make('attribute_changes')
->label('Detalii') ->label(__('Detalii'))
->formatStateUsing(function ($state) { ->formatStateUsing(function ($state) {
if (! $state) return '—'; if (! $state) return '—';
$arr = is_string($state) ? json_decode($state, true) : $state; $arr = is_string($state) ? json_decode($state, true) : $state;
@@ -75,10 +75,10 @@ class ActivityResource extends Resource
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('description') Tables\Filters\SelectFilter::make('description')
->label('Acțiune') ->label(__('Acțiune'))
->options(['creat' => 'creat', 'modificat' => 'modificat', 'șters' => 'șters']), ->options(['creat' => 'creat', 'modificat' => 'modificat', 'șters' => 'șters']),
Tables\Filters\Filter::make('today') Tables\Filters\Filter::make('today')
->label('Astăzi') ->label(__('Astăzi'))
->query(fn ($q) => $q->whereDate('created_at', today())), ->query(fn ($q) => $q->whereDate('created_at', today())),
]) ])
->defaultSort('created_at', 'desc') ->defaultSort('created_at', 'desc')
@@ -34,25 +34,30 @@ class AppointmentResource extends Resource
protected static ?string $modelLabel = 'programare'; 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; protected static ?int $navigationSort = 7;
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Când & unde') Schemas\Components\Section::make(__('Când & unde'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\DatePicker::make('date')->label('Data')->default(today())->required(), 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_start')->label(__('De la'))->required()->seconds(false),
Forms\Components\TimePicker::make('time_end')->label('Până la')->required()->seconds(false), Forms\Components\TimePicker::make('time_end')->label(__('Până la'))->required()->seconds(false),
Forms\Components\Select::make('post_id') Forms\Components\Select::make('post_id')
->label('Pod') ->label(__('Pod'))
->options(fn () => Post::where('is_active', true)->orderBy('sort_order')->pluck('name', 'id')) ->options(fn () => Post::where('is_active', true)->orderBy('sort_order')->pluck('name', 'id'))
->searchable(), ->searchable(),
Forms\Components\Select::make('master_id') Forms\Components\Select::make('master_id')
->label('Maistru / Mecanic') ->label(__('Maistru / Mecanic'))
->options(fn () => User::pluck('name', 'id')) ->options(fn () => User::pluck('name', 'id'))
->searchable(), ->searchable(),
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
@@ -60,23 +65,23 @@ class AppointmentResource extends Resource
->default('scheduled') ->default('scheduled')
->required(), ->required(),
]), ]),
Schemas\Components\Section::make('Client & Auto') Schemas\Components\Section::make(__('Client & Auto'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('client_id') Forms\Components\Select::make('client_id')
->label('Client') ->label(__('Client'))
->options(fn () => Client::pluck('name', 'id')) ->options(fn () => Client::pluck('name', 'id'))
->searchable() ->searchable()
->live(), ->live(),
Forms\Components\Select::make('vehicle_id') Forms\Components\Select::make('vehicle_id')
->label('Auto') ->label(__('Auto'))
->options(fn (Schemas\Components\Utilities\Get $get) => $get('client_id') ->options(fn (Schemas\Components\Utilities\Get $get) => $get('client_id')
? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id') ? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id')
: []) : [])
->searchable(), ->searchable(),
]), ]),
Forms\Components\TextInput::make('title')->label('Subiect')->required()->maxLength(160), Forms\Components\TextInput::make('title')->label(__('Subiect'))->required()->maxLength(160),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2), Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]); ]);
} }
@@ -84,14 +89,14 @@ class AppointmentResource extends Resource
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('date')->label('Data')->date('d.m.Y')->sortable(), 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_start')->label(__('De la'))->time('H:i'),
Tables\Columns\TextColumn::make('time_end')->label('Până 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('post.name')->label(__('Pod'))->placeholder('—'),
Tables\Columns\TextColumn::make('title')->label('Subiect')->searchable()->limit(40), Tables\Columns\TextColumn::make('title')->label(__('Subiect'))->searchable()->limit(40),
Tables\Columns\TextColumn::make('client.name')->label('Client')->placeholder('—'), Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->placeholder('—'),
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'), Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
Tables\Columns\TextColumn::make('master.name')->label('Maistru')->placeholder('—'), Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($state) => Appointment::STATUSES[$state] ?? $state) ->formatStateUsing(fn ($state) => Appointment::STATUSES[$state] ?? $state)
->badge() ->badge()
@@ -104,14 +109,14 @@ class AppointmentResource extends Resource
]) ])
->filters([ ->filters([
Tables\Filters\Filter::make('today') Tables\Filters\Filter::make('today')
->label('Astăzi') ->label(__('Astăzi'))
->query(fn ($q) => $q->whereDate('date', today())), ->query(fn ($q) => $q->whereDate('date', today())),
Tables\Filters\Filter::make('upcoming') Tables\Filters\Filter::make('upcoming')
->label('Viitoare') ->label(__('Viitoare'))
->query(fn ($q) => $q->where('date', '>=', today())), ->query(fn ($q) => $q->where('date', '>=', today())),
Tables\Filters\SelectFilter::make('status')->options(Appointment::STATUSES), Tables\Filters\SelectFilter::make('status')->options(Appointment::STATUSES),
Tables\Filters\SelectFilter::make('post_id') Tables\Filters\SelectFilter::make('post_id')
->label('Pod') ->label(__('Pod'))
->options(fn () => Post::pluck('name', 'id')), ->options(fn () => Post::pluck('name', 'id')),
]) ])
->actions([ ->actions([
@@ -32,10 +32,6 @@ class BodyshopJobResource extends Resource
return __('nav.group.Tinichigerie'); return __('nav.group.Tinichigerie');
} }
protected static ?string $modelLabel = 'lucrare caroserie';
protected static ?string $pluralModelLabel = 'lucrări caroserie';
protected static ?int $navigationSort = 80; protected static ?int $navigationSort = 80;
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
@@ -48,50 +44,50 @@ class BodyshopJobResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Lucrare') Schemas\Components\Section::make(__('Lucrare'))
->columns(3) ->columns(3)
->schema([ ->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('type')->label('Tip')->options(BodyshopJob::TYPES)->default('body_repair')->required(), 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('status')->label(__('Status'))->options(BodyshopJob::STATUSES)->default('estimate')->required(),
Forms\Components\Select::make('client_id') Forms\Components\Select::make('client_id')
->label('Client') ->label(__('Client'))
->options(fn () => Client::pluck('name', 'id')) ->options(fn () => Client::pluck('name', 'id'))
->searchable()->live(), ->searchable()->live(),
Forms\Components\Select::make('vehicle_id') Forms\Components\Select::make('vehicle_id')
->label('Auto') ->label(__('Auto'))
->options(fn (Get $get) => $get('client_id') ->options(fn (Get $get) => $get('client_id')
? Vehicle::where('client_id', $get('client_id'))->get() ? Vehicle::where('client_id', $get('client_id'))->get()
->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray() ->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray()
: []) : [])
->searchable(), ->searchable(),
Forms\Components\TextInput::make('estimate_amount')->label('Deviz')->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), Forms\Components\TextInput::make('approved_amount')->label(__('Aprobat'))->numeric()->default(0),
]), ]),
Schemas\Components\Section::make('Asigurare') Schemas\Components\Section::make(__('Asigurare'))
->collapsible() ->collapsible()
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\Toggle::make('is_insurance')->label('Caz de asigurare')->live()->columnSpanFull(), Forms\Components\Toggle::make('is_insurance')->label(__('Caz de asigurare'))->live()->columnSpanFull(),
Forms\Components\TextInput::make('insurer')->label('Asigurător') Forms\Components\TextInput::make('insurer')->label(__('Asigurător'))
->visible(fn (Get $get) => $get('is_insurance')), ->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')), ->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')), ->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) ->options(BodyshopJob::INSURANCE_STATUSES)
->visible(fn (Get $get) => $get('is_insurance')), ->visible(fn (Get $get) => $get('is_insurance')),
]), ]),
Schemas\Components\Section::make('Foto înainte / după') Schemas\Components\Section::make(__('Foto înainte / după'))
->columns(2) ->columns(2)
->schema([ ->schema([
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos_before') \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') \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 return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(), Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable()->placeholder('—'), Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable()->placeholder('—'),
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'), Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
Tables\Columns\TextColumn::make('type') Tables\Columns\TextColumn::make('type')
->formatStateUsing(fn ($s) => BodyshopJob::TYPES[$s] ?? $s) ->formatStateUsing(fn ($s) => BodyshopJob::TYPES[$s] ?? $s)
->badge()->color('info'), ->badge()->color('info'),
Tables\Columns\IconColumn::make('is_insurance')->label('Asig.')->boolean()->toggleable(), 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('damage_points_count')->counts('damagePoints')->label(__('Daune'))->alignRight(),
Tables\Columns\TextColumn::make('approved_amount')->label('Aprobat')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('approved_amount')->label(__('Aprobat'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => BodyshopJob::STATUSES[$s] ?? $s) ->formatStateUsing(fn ($s) => BodyshopJob::STATUSES[$s] ?? $s)
->badge() ->badge()
@@ -121,14 +117,14 @@ class BodyshopJobResource extends Resource
->filters([ ->filters([
Tables\Filters\SelectFilter::make('type')->options(BodyshopJob::TYPES), Tables\Filters\SelectFilter::make('type')->options(BodyshopJob::TYPES),
Tables\Filters\SelectFilter::make('status')->options(BodyshopJob::STATUSES), 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([
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Nicio lucrare de caroserie') ->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ă.') ->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') ->emptyStateIcon('heroicon-o-paint-brush')
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -14,26 +14,31 @@ class DamagePointsRelationManager extends RelationManager
{ {
protected static string $relationship = 'damagePoints'; 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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\Select::make('zone') Forms\Components\Select::make('zone')
->label('Zonă') ->label(__('Zonă'))
->options(array_combine(DamagePoint::ZONES, DamagePoint::ZONES)) ->options(array_combine(DamagePoint::ZONES, DamagePoint::ZONES))
->searchable() ->searchable()
->required(), ->required(),
Forms\Components\Select::make('kind') Forms\Components\Select::make('kind')
->label('Tip daună') ->label(__('Tip daună'))
->options(array_combine(DamagePoint::KINDS, DamagePoint::KINDS)) ->options(array_combine(DamagePoint::KINDS, DamagePoint::KINDS))
->required(), ->required(),
Forms\Components\Select::make('severity') Forms\Components\Select::make('severity')
->label('Gravitate') ->label(__('Gravitate'))
->options(DamagePoint::SEVERITIES) ->options(DamagePoint::SEVERITIES)
->default('minor') ->default('minor')
->required(), ->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 return $table
->recordTitleAttribute('zone') ->recordTitleAttribute('zone')
->columns([ ->columns([
Tables\Columns\TextColumn::make('zone')->label('Zonă')->badge()->color('gray'), Tables\Columns\TextColumn::make('zone')->label(__('Zonă'))->badge()->color('gray'),
Tables\Columns\TextColumn::make('kind')->label('Tip'), Tables\Columns\TextColumn::make('kind')->label(__('Tip')),
Tables\Columns\TextColumn::make('severity') Tables\Columns\TextColumn::make('severity')
->label('Gravitate') ->label(__('Gravitate'))
->formatStateUsing(fn ($s) => DamagePoint::SEVERITIES[$s] ?? $s) ->formatStateUsing(fn ($s) => DamagePoint::SEVERITIES[$s] ?? $s)
->badge() ->badge()
->colors(['gray' => ['minor'], 'warning' => ['medium'], 'danger' => ['severe']]), ->colors(['gray' => ['minor'], 'warning' => ['medium'], 'danger' => ['severe']]),
@@ -53,7 +58,7 @@ class DamagePointsRelationManager extends RelationManager
]) ])
->headerActions([Actions\CreateAction::make()]) ->headerActions([Actions\CreateAction::make()])
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()]) ->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
->emptyStateHeading('Nicio daună marcată') ->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.'); ->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 public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Apel') Schemas\Components\Section::make(__('Apel'))
->columns(2) ->columns(2)
->schema([ ->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') Forms\Components\Select::make('direction')
->options(Call::DIRECTIONS) ->options(Call::DIRECTIONS)
->default('incoming') ->default('incoming')
->required(), ->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') Forms\Components\Select::make('status')
->options(Call::STATUSES) ->options(Call::STATUSES)
->default('answered') ->default('answered')
->required(), ->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') Forms\Components\Select::make('client_id')
->label('Client') ->label(__('Client'))
->options(fn () => Client::pluck('name', 'id')) ->options(fn () => Client::pluck('name', 'id'))
->searchable(), ->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 return $table
->columns([ ->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') Tables\Columns\TextColumn::make('direction')
->formatStateUsing(fn ($s) => Call::DIRECTIONS[$s] ?? $s) ->formatStateUsing(fn ($s) => Call::DIRECTIONS[$s] ?? $s)
->badge() ->badge()
@@ -75,8 +75,8 @@ class CallResource extends Resource
'danger' => ['missed'], 'danger' => ['missed'],
]), ]),
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(), Tables\Columns\TextColumn::make('phone')->copyable()->searchable(),
Tables\Columns\TextColumn::make('client.name')->label('Client')->placeholder('—'), 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('duration_formatted')->label(__('Durată'))->state(fn (Call $r) => $r->duration_formatted),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => Call::STATUSES[$s] ?? $s) ->formatStateUsing(fn ($s) => Call::STATUSES[$s] ?? $s)
->badge(), ->badge(),
@@ -84,10 +84,10 @@ class CallResource extends Resource
->filters([ ->filters([
Tables\Filters\SelectFilter::make('direction')->options(Call::DIRECTIONS), Tables\Filters\SelectFilter::make('direction')->options(Call::DIRECTIONS),
Tables\Filters\Filter::make('today') Tables\Filters\Filter::make('today')
->label('Astăzi') ->label(__('Astăzi'))
->query(fn ($q) => $q->whereDate('called_at', today())), ->query(fn ($q) => $q->whereDate('called_at', today())),
Tables\Filters\Filter::make('missed') Tables\Filters\Filter::make('missed')
->label('Pierdute') ->label(__('Pierdute'))
->query(fn ($q) => $q->where('direction', 'missed')->orWhere('status', 'missed')), ->query(fn ($q) => $q->where('direction', 'missed')->orWhere('status', 'missed')),
]) ])
->actions([ ->actions([
@@ -25,7 +25,12 @@ class ClientResource extends Resource
protected static ?string $modelLabel = 'client'; 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; protected static ?int $navigationSort = 10;
@@ -47,18 +52,18 @@ class ClientResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Date generale') Schemas\Components\Section::make(__('Date generale'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('type') Forms\Components\Select::make('type')
->label('Tip') ->label(__('Tip'))
->options(['individual' => 'Persoană fizică', 'company' => 'Persoană juridică']) ->options(['individual' => 'Persoană fizică', 'company' => 'Persoană juridică'])
->default('individual') ->default('individual')
->required() ->required()
->live(), ->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') Forms\Components\TextInput::make('company_name')
->label('Denumire companie') ->label(__('Denumire companie'))
->visible(fn (Schemas\Components\Utilities\Get $get) => $get('type') === 'company') ->visible(fn (Schemas\Components\Utilities\Get $get) => $get('type') === 'company')
->maxLength(160), ->maxLength(160),
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
@@ -69,40 +74,40 @@ class ClientResource extends Resource
->default('active') ->default('active')
->required(), ->required(),
Forms\Components\Toggle::make('is_vip') Forms\Components\Toggle::make('is_vip')
->label('Client VIP') ->label(__('Client VIP'))
->helperText('Activează coeficienții de preț VIP pe fișele acestui client.'), ->helperText(__('Activează coeficienții de preț VIP pe fișele acestui client.')),
]), ]),
Schemas\Components\Section::make('Contacte') Schemas\Components\Section::make(__('Contacte'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->required()->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('phone_alt')->label(__('Telefon alternativ'))->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->email()->maxLength(120), Forms\Components\TextInput::make('email')->email()->maxLength(120),
Forms\Components\TextInput::make('telegram')->maxLength(60), Forms\Components\TextInput::make('telegram')->maxLength(60),
Forms\Components\TextInput::make('telegram_chat_id') Forms\Components\TextInput::make('telegram_chat_id')
->label('Telegram chat ID') ->label(__('Telegram chat ID'))
->disabled() ->disabled()
->dehydrated(false) ->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 ->helperText(fn ($record) => $record?->telegram_chat_id
? '✅ Telegram legat — notificările vor merge prin bot' ? '✅ Telegram legat — notificările vor merge prin bot'
: null), : null),
Forms\Components\TextInput::make('whatsapp')->maxLength(60), Forms\Components\TextInput::make('whatsapp')->maxLength(60),
Forms\Components\TextInput::make('viber')->maxLength(60), Forms\Components\TextInput::make('viber')->maxLength(60),
]), ]),
Schemas\Components\Section::make('Marketing') Schemas\Components\Section::make(__('Marketing'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('source')->label('Sursă')->maxLength(60), Forms\Components\TextInput::make('source')->label(__('Sursă'))->maxLength(60),
Forms\Components\TextInput::make('marketing_channel')->label('Canal marketing')->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) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('balance')->label('Sold')->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\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('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('phone')->searchable()->copyable(), Tables\Columns\TextColumn::make('phone')->searchable()->copyable(),
Tables\Columns\TextColumn::make('email')->searchable()->toggleable(), 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') Tables\Columns\TextColumn::make('status')
->badge() ->badge()
->colors([ ->colors([
@@ -136,8 +141,8 @@ class ClientResource extends Resource
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun client încă') ->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.') ->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') ->emptyStateIcon('heroicon-o-users')
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -17,16 +17,16 @@ class ListClients extends ListRecords
{ {
return [ return [
Actions\Action::make('export') Actions\Action::make('export')
->label('Export CSV') ->label(__('Export CSV'))
->icon('heroicon-m-arrow-down-tray') ->icon('heroicon-m-arrow-down-tray')
->color('gray') ->color('gray')
->action(fn () => app(CsvImportExport::class)->exportClients()), ->action(fn () => app(CsvImportExport::class)->exportClients()),
Actions\Action::make('import') Actions\Action::make('import')
->label('Import CSV') ->label(__('Import CSV'))
->icon('heroicon-m-arrow-up-tray') ->icon('heroicon-m-arrow-up-tray')
->color('gray') ->color('gray')
->modalHeading('Import clienți din CSV') ->modalHeading(__('Import clienți din CSV'))
->modalDescription('CSV cu header: ' . implode(', ', CsvImportExport::CLIENT_COLUMNS) . '. Deduplicare după telefon.') ->modalDescription(__('CSV cu header: ' . implode(', ', CsvImportExport::CLIENT_COLUMNS) . '. Deduplicare după telefon.'))
->schema([ ->schema([
Forms\Components\FileUpload::make('file') Forms\Components\FileUpload::make('file')
->required() ->required()
+13 -13
View File
@@ -46,22 +46,22 @@ class DealResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Detalii') Schemas\Components\Section::make(__('Detalii'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('client_id') Forms\Components\Select::make('client_id')
->label('Client') ->label(__('Client'))
->options(fn () => Client::pluck('name', 'id')) ->options(fn () => Client::pluck('name', 'id'))
->searchable() ->searchable()
->required(), ->required(),
Forms\Components\Select::make('vehicle_id') Forms\Components\Select::make('vehicle_id')
->label('Auto') ->label(__('Auto'))
->options(fn (Schemas\Components\Utilities\Get $get) => $get('client_id') ->options(fn (Schemas\Components\Utilities\Get $get) => $get('client_id')
? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id') ? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id')
: []) : [])
->searchable(), ->searchable(),
Forms\Components\TextInput::make('name')->label('Subiect')->required()->maxLength(160), Forms\Components\TextInput::make('name')->label(__('Subiect'))->required()->maxLength(160),
Forms\Components\TextInput::make('price')->label('Valoare')->numeric()->default(0), Forms\Components\TextInput::make('price')->label(__('Valoare'))->numeric()->default(0),
Forms\Components\Select::make('stage') Forms\Components\Select::make('stage')
->options(Deal::STAGES) ->options(Deal::STAGES)
->default('new') ->default('new')
@@ -70,11 +70,11 @@ class DealResource extends Resource
->options(Lead::SOURCES) ->options(Lead::SOURCES)
->searchable(), ->searchable(),
Forms\Components\Select::make('assigned_to') Forms\Components\Select::make('assigned_to')
->label('Responsabil') ->label(__('Responsabil'))
->options(fn () => User::pluck('name', 'id')) ->options(fn () => User::pluck('name', 'id'))
->searchable(), ->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 return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('id')->label('#')->sortable(), Tables\Columns\TextColumn::make('id')->label('#')->sortable(),
Tables\Columns\TextColumn::make('name')->label('Subiect')->searchable()->limit(40), Tables\Columns\TextColumn::make('name')->label(__('Subiect'))->searchable()->limit(40),
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable(), Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'), Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
Tables\Columns\TextColumn::make('stage') Tables\Columns\TextColumn::make('stage')
->formatStateUsing(fn ($state) => Deal::STAGES[$state] ?? $state) ->formatStateUsing(fn ($state) => Deal::STAGES[$state] ?? $state)
->badge() ->badge()
@@ -97,14 +97,14 @@ class DealResource extends Resource
'danger' => ['lost'], 'danger' => ['lost'],
]), ]),
Tables\Columns\TextColumn::make('price')->money('MDL')->sortable(), 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('source')->label(__('Sursă'))->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->placeholder('—'),
Tables\Columns\TextColumn::make('assignedTo.name')->label('Responsabil')->placeholder('—'), Tables\Columns\TextColumn::make('assignedTo.name')->label(__('Responsabil'))->placeholder('—'),
Tables\Columns\TextColumn::make('created_at')->date()->sortable(), Tables\Columns\TextColumn::make('created_at')->date()->sortable(),
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('stage')->options(Deal::STAGES), Tables\Filters\SelectFilter::make('stage')->options(Deal::STAGES),
Tables\Filters\SelectFilter::make('assigned_to') Tables\Filters\SelectFilter::make('assigned_to')
->label('Responsabil') ->label(__('Responsabil'))
->options(fn () => User::pluck('name', 'id')), ->options(fn () => User::pluck('name', 'id')),
]) ])
->actions([ ->actions([
@@ -31,41 +31,46 @@ class EmployeeProfileResource extends Resource
protected static ?string $modelLabel = 'angajat'; 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; protected static ?int $navigationSort = 52;
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Profil') Schemas\Components\Section::make(__('Profil'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('user_id') Forms\Components\Select::make('user_id')
->label('Utilizator') ->label(__('Utilizator'))
->options(fn () => User::orderBy('name')->pluck('name', 'id')) ->options(fn () => User::orderBy('name')->pluck('name', 'id'))
->searchable() ->searchable()
->required() ->required()
->unique(ignoreRecord: true), ->unique(ignoreRecord: true),
Forms\Components\TextInput::make('position')->label('Funcție')->maxLength(120), Forms\Components\TextInput::make('position')->label(__('Funcție'))->maxLength(120),
Forms\Components\DatePicker::make('hire_date')->label('Angajat din'), Forms\Components\DatePicker::make('hire_date')->label(__('Angajat din')),
]), ]),
Schemas\Components\Section::make('Salariu & comisioane') Schemas\Components\Section::make(__('Salariu & comisioane'))
->columns(3) ->columns(3)
->schema([ ->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') Forms\Components\TextInput::make('works_pct')
->label('% manopere') ->label(__('% manopere'))
->numeric()->default(0) ->numeric()->default(0)
->suffix('%') ->suffix('%')
->helperText('% din venitul manoperelor finalizate.'), ->helperText(__('% din venitul manoperelor finalizate.')),
Forms\Components\TextInput::make('parts_pct') Forms\Components\TextInput::make('parts_pct')
->label('% marja piese') ->label(__('% marja piese'))
->numeric()->default(0) ->numeric()->default(0)
->suffix('%') ->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 return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('user.name')->label('Nume')->searchable()->sortable(), Tables\Columns\TextColumn::make('user.name')->label(__('Nume'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('position')->label('Funcție')->placeholder('—'), Tables\Columns\TextColumn::make('position')->label(__('Funcție'))->placeholder('—'),
Tables\Columns\TextColumn::make('base_salary')->money('MDL')->alignRight(), 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(), ->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(), ->formatStateUsing(fn ($s) => $s . '%')->alignRight(),
Tables\Columns\TextColumn::make('hire_date')->date('d.m.Y')->placeholder('—'), Tables\Columns\TextColumn::make('hire_date')->date('d.m.Y')->placeholder('—'),
]) ])
@@ -29,7 +29,12 @@ class ExpenseResource extends Resource
return __('nav.group.Finanțe'); 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'; protected static ?string $pluralModelLabel = 'cheltuieli';
@@ -48,27 +53,27 @@ class ExpenseResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Cheltuială') Schemas\Components\Section::make(__('Cheltuială'))
->columns(2) ->columns(2)
->schema([ ->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') Forms\Components\Select::make('category')
->options(Expense::CATEGORIES) ->options(Expense::CATEGORIES)
->default('other') ->default('other')
->required(), ->required(),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->maxLength(160)->columnSpanFull(), Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->maxLength(160)->columnSpanFull(),
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(), Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
Forms\Components\Select::make('method') Forms\Components\Select::make('method')
->options(Expense::METHODS) ->options(Expense::METHODS)
->default('cash') ->default('cash')
->required(), ->required(),
Forms\Components\Select::make('supplier_id') Forms\Components\Select::make('supplier_id')
->label('Furnizor (opțional)') ->label(__('Furnizor (opțional)'))
->options(fn () => Supplier::pluck('name', 'id')) ->options(fn () => Supplier::pluck('name', 'id'))
->searchable(), ->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 return $table
->columns([ ->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') Tables\Columns\TextColumn::make('category')
->formatStateUsing(fn ($s) => Expense::CATEGORIES[$s] ?? $s) ->formatStateUsing(fn ($s) => Expense::CATEGORIES[$s] ?? $s)
->badge(), ->badge(),
Tables\Columns\TextColumn::make('name')->searchable()->wrap(), 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') Tables\Columns\TextColumn::make('method')
->formatStateUsing(fn ($s) => Expense::METHODS[$s] ?? $s), ->formatStateUsing(fn ($s) => Expense::METHODS[$s] ?? $s),
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable() Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable()
->color('danger') ->color('danger')
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label('Total')), ->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label(__('Total'))),
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('category')->options(Expense::CATEGORIES), Tables\Filters\SelectFilter::make('category')->options(Expense::CATEGORIES),
Tables\Filters\Filter::make('this_month') 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)), ->query(fn ($q) => $q->whereMonth('paid_at', now()->month)->whereYear('paid_at', now()->year)),
]) ])
->actions([ ->actions([
+25 -20
View File
@@ -29,7 +29,12 @@ class LaborResource extends Resource
return __('nav.group.Service'); 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'; protected static ?string $pluralModelLabel = 'norme-ore';
@@ -38,31 +43,31 @@ class LaborResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Manoperă') Schemas\Components\Section::make(__('Manoperă'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('category') Forms\Components\Select::make('category')
->label('Categorie') ->label(__('Categorie'))
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES)) ->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES))
->required() ->required()
->searchable(), ->searchable(),
Forms\Components\TextInput::make('code')->label('Cod')->maxLength(32), 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_ro')->label(__('Nume (RO)'))->required()->maxLength(160),
Forms\Components\TextInput::make('name_ru')->label('Nume (RU)')->maxLength(160), Forms\Components\TextInput::make('name_ru')->label(__('Nume (RU)'))->maxLength(160),
Forms\Components\Select::make('pricing_mode') Forms\Components\Select::make('pricing_mode')
->label('Mod tarifare') ->label(__('Mod tarifare'))
->options(Labor::PRICING_MODES) ->options(Labor::PRICING_MODES)
->default('hourly') ->default('hourly')
->live() ->live()
->required(), ->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'), ->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'), ->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\TextInput::make('price')->label(__('Preț orientativ (MDL)'))->numeric()->default(0),
Forms\Components\Toggle::make('is_active')->label('Activă')->default(true), 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 return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('category')->label('Categorie')->badge()->sortable(), Tables\Columns\TextColumn::make('category')->label(__('Categorie'))->badge()->sortable(),
Tables\Columns\TextColumn::make('name_ro')->label('Manoperă')->searchable()->sortable(), Tables\Columns\TextColumn::make('name_ro')->label(__('Manoperă'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('pricing_mode') Tables\Columns\TextColumn::make('pricing_mode')
->label('Tarifare') ->label(__('Tarifare'))
->formatStateUsing(fn ($s) => $s === 'fixed' ? 'Fix' : 'Pe oră') ->formatStateUsing(fn ($s) => $s === 'fixed' ? 'Fix' : 'Pe oră')
->badge() ->badge()
->color(fn ($s) => $s === 'fixed' ? 'info' : 'gray'), ->color(fn ($s) => $s === 'fixed' ? 'info' : 'gray'),
Tables\Columns\TextColumn::make('hours')->label('Ore')->numeric(decimalPlaces: 2)->alignRight(), 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('fixed_price')->label(__('Preț fix'))->money('MDL')->alignRight()
->placeholder('—')->toggleable(), ->placeholder('—')->toggleable(),
Tables\Columns\TextColumn::make('laborParts_count')->counts('laborParts')->label('Piese impl.')->alignRight()->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\IconColumn::make('is_active')->label(__('Activă'))->boolean(),
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('category') Tables\Filters\SelectFilter::make('category')
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES)), ->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([
Actions\EditAction::make(), Actions\EditAction::make(),
@@ -15,13 +15,18 @@ class DefaultPartsRelationManager extends RelationManager
{ {
protected static string $relationship = 'laborParts'; 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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\Select::make('part_id') Forms\Components\Select::make('part_id')
->label('Piesă') ->label(__('Piesă'))
->options(fn () => Part::where('is_active', true) ->options(fn () => Part::where('is_active', true)
->get() ->get()
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')]) ->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])
@@ -34,8 +39,8 @@ class DefaultPartsRelationManager extends RelationManager
$set('unit', $p->unit); $set('unit', $p->unit);
} }
}), }),
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(), 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('unit')->label(__('UM'))->default('buc')->maxLength(16),
]); ]);
} }
@@ -44,14 +49,14 @@ class DefaultPartsRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('part.name') ->recordTitleAttribute('part.name')
->columns([ ->columns([
Tables\Columns\TextColumn::make('part.name')->label('Piesă')->wrap(), Tables\Columns\TextColumn::make('part.name')->label(__('Piesă'))->wrap(),
Tables\Columns\TextColumn::make('part.article')->label('Cod')->placeholder('—'), Tables\Columns\TextColumn::make('part.article')->label(__('Cod'))->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(), Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
Tables\Columns\TextColumn::make('unit')->label('UM'), Tables\Columns\TextColumn::make('unit')->label(__('UM')),
]) ])
->headerActions([Actions\CreateAction::make()]) ->headerActions([Actions\CreateAction::make()])
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()]) ->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
->emptyStateHeading('Nicio piesă implicită') ->emptyStateHeading(__('Nicio piesă implicită'))
->emptyStateDescription('Adaugă piesele care se montează de obicei la această manoperă — se adaugă automat în fișă când selectezi manopera.'); ->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 public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Contact') Schemas\Components\Section::make(__('Contact'))
->columns(2) ->columns(2)
->schema([ ->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('phone')->label('Telefon')->tel()->required()->maxLength(40), Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->required()->maxLength(40),
Forms\Components\TextInput::make('email')->email()->maxLength(120), Forms\Components\TextInput::make('email')->email()->maxLength(120),
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
->options(Lead::STATUSES) ->options(Lead::STATUSES)
->default('new') ->default('new')
->required(), ->required(),
]), ]),
Schemas\Components\Section::make('Auto') Schemas\Components\Section::make(__('Auto'))
->columns(2) ->columns(2)
->schema([ ->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\TextInput::make('model')->maxLength(60),
]), ]),
Forms\Components\Textarea::make('message')->label('Mesaj client')->columnSpanFull()->rows(3), Forms\Components\Textarea::make('message')->label(__('Mesaj client'))->columnSpanFull()->rows(3),
Schemas\Components\Section::make('Sursă & Atribuire') Schemas\Components\Section::make(__('Sursă & Atribuire'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('source') Forms\Components\Select::make('source')
@@ -78,12 +78,12 @@ class LeadResource extends Resource
->searchable() ->searchable()
->default('manual'), ->default('manual'),
Forms\Components\Select::make('assigned_to') Forms\Components\Select::make('assigned_to')
->label('Responsabil') ->label(__('Responsabil'))
->options(fn () => User::pluck('name', 'id')) ->options(fn () => User::pluck('name', 'id'))
->searchable(), ->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() ->collapsed()
->columns(2) ->columns(2)
->schema([ ->schema([
@@ -93,7 +93,7 @@ class LeadResource extends Resource
Forms\Components\TextInput::make('utm_term'), Forms\Components\TextInput::make('utm_term'),
Forms\Components\TextInput::make('utm_content'), 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 return $table
->columns([ ->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('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(), 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('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('source')->label(__('Sursă'))->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->badge(),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($state) => Lead::STATUSES[$state] ?? $state) ->formatStateUsing(fn ($state) => Lead::STATUSES[$state] ?? $state)
->badge() ->badge()
@@ -116,7 +116,7 @@ class LeadResource extends Resource
'success' => ['converted'], 'success' => ['converted'],
'danger' => ['lost'], '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('—'), Tables\Columns\TextColumn::make('budget')->money('MDL')->placeholder('—'),
]) ])
->filters([ ->filters([
@@ -125,7 +125,7 @@ class LeadResource extends Resource
]) ])
->actions([ ->actions([
Actions\Action::make('convert') Actions\Action::make('convert')
->label('Convertește') ->label(__('Convertește'))
->icon('heroicon-m-arrow-right-circle') ->icon('heroicon-m-arrow-right-circle')
->color('success') ->color('success')
->visible(fn (Lead $r) => $r->status !== 'converted') ->visible(fn (Lead $r) => $r->status !== 'converted')
@@ -141,7 +141,7 @@ class LeadResource extends Resource
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Nicio cerere primită') ->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') ->emptyStateIcon('heroicon-o-inbox-arrow-down')
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -30,31 +30,36 @@ class MarketingChannelResource extends Resource
protected static ?string $modelLabel = 'canal'; 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; protected static ?int $navigationSort = 61;
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identificare') Schemas\Components\Section::make(__('Identificare'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('name')->label('Nume canal')->required()->maxLength(120), 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('icon')->label(__('Iconiță (emoji)'))->maxLength(8)->placeholder('🔍 / 📘 / 📸'),
Forms\Components\ColorPicker::make('color'), Forms\Components\ColorPicker::make('color'),
]), ]),
Schemas\Components\Section::make('Buget & rezultate (luna curentă)') Schemas\Components\Section::make(__('Buget & rezultate (luna curentă)'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('budget_monthly')->label('Buget')->numeric()->default(0), 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('spent_monthly')->label(__('Cheltuit'))->numeric()->default(0),
Forms\Components\TextInput::make('revenue')->label('Venit generat')->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('leads_count')->label(__('Lead-uri'))->numeric()->default(0),
Forms\Components\TextInput::make('converted_count')->label('Convertite')->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\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\ColorColumn::make('color')->label(''),
Tables\Columns\TextColumn::make('icon')->label('')->width(40), Tables\Columns\TextColumn::make('icon')->label('')->width(40),
Tables\Columns\TextColumn::make('name')->searchable()->sortable(), Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('budget_monthly')->label('Buget')->money('MDL')->alignRight(), 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('spent_monthly')->label(__('Cheltuit'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('leads_count')->label('Lead-uri')->alignRight(), Tables\Columns\TextColumn::make('leads_count')->label(__('Lead-uri'))->alignRight(),
Tables\Columns\TextColumn::make('converted_count')->label('Convertite')->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('revenue')->label(__('Venit'))->money('MDL')->alignRight()->color('success'),
Tables\Columns\TextColumn::make('roi') Tables\Columns\TextColumn::make('roi')
->label('ROI') ->label(__('ROI'))
->state(fn (MarketingChannel $r) => $r->roi) ->state(fn (MarketingChannel $r) => $r->roi)
->formatStateUsing(fn ($s) => $s . '%') ->formatStateUsing(fn ($s) => $s . '%')
->color(fn ($s) => $s >= 0 ? 'success' : 'danger') ->color(fn ($s) => $s >= 0 ? 'success' : 'danger')
->alignRight(), ->alignRight(),
Tables\Columns\TextColumn::make('cost_per_lead') Tables\Columns\TextColumn::make('cost_per_lead')
->label('Cost/lead') ->label(__('Cost/lead'))
->state(fn (MarketingChannel $r) => $r->cost_per_lead) ->state(fn (MarketingChannel $r) => $r->cost_per_lead)
->money('MDL') ->money('MDL')
->alignRight(), ->alignRight(),
Tables\Columns\IconColumn::make('is_active')->boolean(), Tables\Columns\IconColumn::make('is_active')->boolean(),
]) ])
->filters([ ->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Active'), Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
]) ])
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
@@ -31,20 +31,30 @@ class MarkupRuleResource extends Resource
return __('nav.group.Depozit'); 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; protected static ?int $navigationSort = 44;
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Regulă') Schemas\Components\Section::make(__('Regulă'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('type') Forms\Components\Select::make('type')
->label('Tip') ->label(__('Tip'))
->options(MarkupRule::TYPES) ->options(MarkupRule::TYPES)
->default('category') ->default('category')
->required() ->required()
@@ -58,25 +68,25 @@ class MarkupRuleResource extends Resource
->searchable() ->searchable()
->required(fn (Get $get) => in_array($get('type'), ['category', 'brand'], true)), ->required(fn (Get $get) => in_array($get('type'), ['category', 'brand'], true)),
Forms\Components\TextInput::make('range_from') Forms\Components\TextInput::make('range_from')
->label('De la (preț achiziție)') ->label(__('De la (preț achiziție)'))
->numeric() ->numeric()
->visible(fn (Get $get) => $get('type') === 'range'), ->visible(fn (Get $get) => $get('type') === 'range'),
Forms\Components\TextInput::make('range_to') Forms\Components\TextInput::make('range_to')
->label('Până la (gol = ∞)') ->label(__('Până la (gol = ∞)'))
->numeric() ->numeric()
->visible(fn (Get $get) => $get('type') === 'range'), ->visible(fn (Get $get) => $get('type') === 'range'),
Forms\Components\TextInput::make('markup_pct') Forms\Components\TextInput::make('markup_pct')
->label('Markup %') ->label(__('Markup %'))
->numeric() ->numeric()
->required() ->required()
->suffix('%') ->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') Forms\Components\TextInput::make('priority')
->label('Prioritate') ->label(__('Prioritate'))
->numeric() ->numeric()
->default(100) ->default(100)
->helperText('Mai mic = aplicat primul.'), ->helperText(__('Mai mic = aplicat primul.')),
Forms\Components\Toggle::make('is_active')->label('Activă')->default(true), Forms\Components\Toggle::make('is_active')->label(__('Activă'))->default(true),
]), ]),
]); ]);
} }
@@ -89,11 +99,11 @@ class MarkupRuleResource extends Resource
Tables\Columns\TextColumn::make('type') Tables\Columns\TextColumn::make('type')
->formatStateUsing(fn ($s) => MarkupRule::TYPES[$s] ?? $s) ->formatStateUsing(fn ($s) => MarkupRule::TYPES[$s] ?? $s)
->badge(), ->badge(),
Tables\Columns\TextColumn::make('key')->label('Cheie')->placeholder('—'), Tables\Columns\TextColumn::make('key')->label(__('Cheie'))->placeholder('—'),
Tables\Columns\TextColumn::make('range_from')->label('De la')->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('range_to')->label(__('Până la'))->placeholder('∞'),
Tables\Columns\TextColumn::make('markup_pct') Tables\Columns\TextColumn::make('markup_pct')
->label('Markup') ->label(__('Markup'))
->formatStateUsing(fn ($s) => '+' . $s . '%') ->formatStateUsing(fn ($s) => '+' . $s . '%')
->color('success') ->color('success')
->weight('bold'), ->weight('bold'),
@@ -104,11 +114,11 @@ class MarkupRuleResource extends Resource
]) ])
->headerActions([ ->headerActions([
Actions\Action::make('apply_all') Actions\Action::make('apply_all')
->label('Aplică toate regulile la stoc') ->label(__('Aplică toate regulile la stoc'))
->icon('heroicon-m-bolt') ->icon('heroicon-m-bolt')
->color('warning') ->color('warning')
->requiresConfirmation() ->requiresConfirmation()
->modalDescription('Va recalcula sell_price pentru TOATE piesele active. Continui?') ->modalDescription(__('Va recalcula sell_price pentru TOATE piesele active. Continui?'))
->action(function () { ->action(function () {
$count = 0; $count = 0;
Part::where('is_active', true)->where('buy_price', '>', 0)->chunk(100, function ($parts) use (&$count) { 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 public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Date personale') Schemas\Components\Section::make(__('Date personale'))
->columns(2) ->columns(2)
->schema([ ->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('phone')->label('Telefon')->tel()->maxLength(40), Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->label('Email')->email()->maxLength(120), Forms\Components\TextInput::make('email')->label(__('Email'))->email()->maxLength(120),
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
->options(['active' => 'Activ', 'inactive' => 'Inactiv', 'blocked' => 'Blocat']) ->options(['active' => 'Activ', 'inactive' => 'Inactiv', 'blocked' => 'Blocat'])
->default('active') ->default('active')
->required(), ->required(),
]), ]),
Schemas\Components\Section::make('Profesie') Schemas\Components\Section::make(__('Profesie'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('specialization') Forms\Components\TextInput::make('specialization')
->label('Specializare') ->label(__('Specializare'))
->placeholder('Motor / Frâne / Electrică ...') ->placeholder(__('Motor / Frâne / Electrică ...'))
->maxLength(120), ->maxLength(120),
Forms\Components\ColorPicker::make('color')->label('Culoare în calendar'), Forms\Components\ColorPicker::make('color')->label(__('Culoare în calendar')),
Forms\Components\TextInput::make('hourly_rate')->label('Tarif/oră')->numeric(), Forms\Components\TextInput::make('hourly_rate')->label(__('Tarif/oră'))->numeric(),
Forms\Components\Hidden::make('role')->default('mechanic'), 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) ->columns(1)
->collapsed() ->collapsed()
->schema([ ->schema([
Forms\Components\TextInput::make('password') Forms\Components\TextInput::make('password')
->label('Parolă (lasă gol pentru a nu schimba)') ->label(__('Parolă (lasă gol pentru a nu schimba)'))
->password() ->password()
->minLength(6) ->minLength(6)
->dehydrated(fn ($state) => filled($state)) ->dehydrated(fn ($state) => filled($state))
@@ -89,9 +89,9 @@ class MasterResource extends Resource
->columns([ ->columns([
Tables\Columns\ColorColumn::make('color')->label(''), Tables\Columns\ColorColumn::make('color')->label(''),
Tables\Columns\TextColumn::make('name')->searchable()->sortable(), 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('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') Tables\Columns\TextColumn::make('status')
->badge() ->badge()
->colors([ ->colors([
@@ -10,5 +10,5 @@ class ListMasters extends ListRecords
{ {
protected static string $resource = MasterResource::class; 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 public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identificare') Schemas\Components\Section::make(__('Identificare'))
->columns(2) ->columns(2)
->schema([ ->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') Forms\Components\Select::make('channel')
->options(MessageTemplate::CHANNELS) ->options(MessageTemplate::CHANNELS)
->default('telegram') ->default('telegram')
->required(), ->required(),
Forms\Components\TextInput::make('subject')->label('Subiect (email)')->maxLength(160)->columnSpanFull(), Forms\Components\TextInput::make('subject')->label(__('Subiect (email)'))->maxLength(160)->columnSpanFull(),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true), Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]), ]),
Schemas\Components\Section::make('Conținut') Schemas\Components\Section::make(__('Conținut'))
->columns(1) ->columns(1)
->schema([ ->schema([
Forms\Components\Textarea::make('body') Forms\Components\Textarea::make('body')
->label('Mesaj') ->label(__('Mesaj'))
->required() ->required()
->rows(6) ->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') Tables\Columns\TextColumn::make('channel')
->formatStateUsing(fn ($s) => MessageTemplate::CHANNELS[$s] ?? $s) ->formatStateUsing(fn ($s) => MessageTemplate::CHANNELS[$s] ?? $s)
->badge(), ->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(), Tables\Columns\IconColumn::make('is_active')->boolean(),
]) ])
->filters([ ->filters([
@@ -30,9 +30,19 @@ class OnlineOrderResource extends Resource
return __('nav.group.Magazin'); 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; protected static ?int $navigationSort = 50;
@@ -50,18 +60,18 @@ class OnlineOrderResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Comandă') Schemas\Components\Section::make(__('Comandă'))
->columns(3) ->columns(3)
->schema([ ->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('status')->options(OnlineOrder::STATUSES)->required(),
Forms\Components\Select::make('delivery_method')->label('Livrare')->options(OnlineOrder::DELIVERY)->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_name')->label(__('Client'))->required(),
Forms\Components\TextInput::make('customer_phone')->label('Telefon')->required(), Forms\Components\TextInput::make('customer_phone')->label(__('Telefon'))->required(),
Forms\Components\TextInput::make('customer_email')->label('Email'), Forms\Components\TextInput::make('customer_email')->label(__('Email')),
Forms\Components\TextInput::make('address')->label('Adresă')->columnSpan(2), Forms\Components\TextInput::make('address')->label(__('Adresă'))->columnSpan(2),
Forms\Components\TextInput::make('delivery_fee')->label('Taxă livrare')->numeric(), Forms\Components\TextInput::make('delivery_fee')->label(__('Taxă livrare'))->numeric(),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2), Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]), ]),
]); ]);
} }
@@ -70,12 +80,12 @@ class OnlineOrderResource extends Resource
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(), 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('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_name')->label(__('Client'))->searchable(),
Tables\Columns\TextColumn::make('customer_phone')->label('Telefon')->copyable(), Tables\Columns\TextColumn::make('customer_phone')->label(__('Telefon'))->copyable(),
Tables\Columns\TextColumn::make('delivery_method') Tables\Columns\TextColumn::make('delivery_method')
->label('Livrare') ->label(__('Livrare'))
->formatStateUsing(fn ($s) => OnlineOrder::DELIVERY[$s] ?? $s), ->formatStateUsing(fn ($s) => OnlineOrder::DELIVERY[$s] ?? $s),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => OnlineOrder::STATUSES[$s] ?? $s) ->formatStateUsing(fn ($s) => OnlineOrder::STATUSES[$s] ?? $s)
@@ -94,12 +104,12 @@ class OnlineOrderResource extends Resource
]) ])
->actions([ ->actions([
Actions\Action::make('fulfill') Actions\Action::make('fulfill')
->label('Onorează (scade stoc)') ->label(__('Onorează (scade stoc)'))
->icon('heroicon-m-check-badge') ->icon('heroicon-m-check-badge')
->color('success') ->color('success')
->visible(fn (OnlineOrder $r) => ! in_array($r->status, ['delivered', 'cancelled'], true)) ->visible(fn (OnlineOrder $r) => ! in_array($r->status, ['delivered', 'cancelled'], true))
->requiresConfirmation() ->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) { ->action(function (OnlineOrder $r) {
$svc = app(\App\Services\Warehouse\WarehouseService::class); $svc = app(\App\Services\Warehouse\WarehouseService::class);
$issued = 0; $skipped = 0; $issued = 0; $skipped = 0;
@@ -10,19 +10,24 @@ class ItemsRelationManager extends RelationManager
{ {
protected static string $relationship = 'items'; 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 public function table(Table $table): Table
{ {
return $table return $table
->recordTitleAttribute('name') ->recordTitleAttribute('name')
->columns([ ->columns([
Tables\Columns\TextColumn::make('name')->label('Piesă')->wrap(), Tables\Columns\TextColumn::make('name')->label(__('Piesă'))->wrap(),
Tables\Columns\TextColumn::make('article')->label('Cod')->placeholder('—'), Tables\Columns\TextColumn::make('article')->label(__('Cod'))->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(), Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
Tables\Columns\TextColumn::make('price')->label('Preț')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('price')->label(__('Preț'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('total')->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'); 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'; protected static ?string $pluralModelLabel = 'piese';
@@ -66,50 +71,50 @@ class PartResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identificare') Schemas\Components\Section::make(__('Identificare'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpan(3)->maxLength(200), 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('article')->label(__('Cod articol'))->maxLength(64),
Forms\Components\TextInput::make('brand')->maxLength(64), Forms\Components\TextInput::make('brand')->maxLength(64),
Forms\Components\Select::make('category') Forms\Components\Select::make('category')
->label('Categorie') ->label(__('Categorie'))
->options(array_combine(Part::CATEGORIES, Part::CATEGORIES)) ->options(array_combine(Part::CATEGORIES, Part::CATEGORIES))
->searchable(), ->searchable(),
Forms\Components\TextInput::make('barcode')->label('Cod bare')->maxLength(64), 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('location')->label(__('Locație rack/bin'))->maxLength(64),
]), ]),
Schemas\Components\Section::make('Stoc') Schemas\Components\Section::make(__('Stoc'))
->columns(4) ->columns(4)
->schema([ ->schema([
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(0)->required(), 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('unit')->label(__('UM'))->default('buc')->maxLength(16),
Forms\Components\TextInput::make('min_qty')->label('Minim')->numeric()->default(0), 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_active')->label(__('Activ'))->default(true),
Forms\Components\Toggle::make('is_published') Forms\Components\Toggle::make('is_published')
->label('Publicat în magazin') ->label(__('Publicat în magazin'))
->helperText('Apare în magazinul online public.') ->helperText(__('Apare în magazinul online public.'))
->default(false), ->default(false),
]), ]),
Schemas\Components\Section::make('Prețuri') Schemas\Components\Section::make(__('Prețuri'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('buy_price')->label('Preț achiziție')->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), 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) ->columns(1)
->schema([ ->schema([
Forms\Components\Select::make('preferred_supplier_id') Forms\Components\Select::make('preferred_supplier_id')
->label('Furnizor') ->label(__('Furnizor'))
->options(fn () => Supplier::pluck('name', 'id')) ->options(fn () => Supplier::pluck('name', 'id'))
->searchable(), ->searchable(),
]), ]),
Schemas\Components\Section::make('Imagine') Schemas\Components\Section::make(__('Imagine'))
->collapsible() ->collapsible()
->schema([ ->schema([
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('image') \Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('image')
->label('Foto piesă') ->label(__('Foto piesă'))
->collection('image') ->collection('image')
->multiple() ->multiple()
->reorderable() ->reorderable()
@@ -118,9 +123,9 @@ class PartResource extends Resource
->maxFiles(8) ->maxFiles(8)
->maxSize(2048) ->maxSize(2048)
->columnSpanFull() ->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() ->circular()
->size(32), ->size(32),
Tables\Columns\TextColumn::make('name')->searchable()->sortable()->wrap(), 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('brand')->placeholder('—'),
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'), Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('qty') Tables\Columns\TextColumn::make('qty')
->label('Stoc') ->label(__('Stoc'))
->numeric(decimalPlaces: 2) ->numeric(decimalPlaces: 2)
->alignRight() ->alignRight()
->color(fn ($state, $record) => $record->qty <= 0 ? 'danger' : ($record->qty <= $record->min_qty ? 'warning' : null)) ->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), ->weight(fn ($state, $record) => $record->qty <= $record->min_qty ? 'bold' : null),
Tables\Columns\TextColumn::make('qty_reserved') Tables\Columns\TextColumn::make('qty_reserved')
->label('Rezervat') ->label(__('Rezervat'))
->numeric(decimalPlaces: 2) ->numeric(decimalPlaces: 2)
->alignRight() ->alignRight()
->color(fn ($state) => (float) $state > 0 ? 'info' : null) ->color(fn ($state) => (float) $state > 0 ? 'info' : null)
->toggleable(), ->toggleable(),
Tables\Columns\TextColumn::make('unit')->label('UM'), Tables\Columns\TextColumn::make('unit')->label(__('UM')),
Tables\Columns\TextColumn::make('location')->label('Loc.')->placeholder('—'), Tables\Columns\TextColumn::make('location')->label(__('Loc.'))->placeholder('—'),
Tables\Columns\TextColumn::make('sell_price')->label('Preț vz.')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('sell_price')->label(__('Preț vz.'))->money('MDL')->alignRight(),
Tables\Columns\IconColumn::make('is_published')->label('Magazin')->boolean()->toggleable(), Tables\Columns\IconColumn::make('is_published')->label(__('Magazin'))->boolean()->toggleable(),
Tables\Columns\TextColumn::make('preferredSupplier.name')->label('Furnizor')->placeholder('—')->toggleable(), Tables\Columns\TextColumn::make('preferredSupplier.name')->label(__('Furnizor'))->placeholder('—')->toggleable(),
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('category') Tables\Filters\SelectFilter::make('category')
->options(array_combine(Part::CATEGORIES, Part::CATEGORIES)), ->options(array_combine(Part::CATEGORIES, Part::CATEGORIES)),
Tables\Filters\Filter::make('low_stock') Tables\Filters\Filter::make('low_stock')
->label('Stoc minim') ->label(__('Stoc minim'))
->query(fn ($q) => $q->whereColumn('qty', '<=', 'min_qty')), ->query(fn ($q) => $q->whereColumn('qty', '<=', 'min_qty')),
Tables\Filters\Filter::make('out_of_stock') Tables\Filters\Filter::make('out_of_stock')
->label('Lipsă') ->label(__('Lipsă'))
->query(fn ($q) => $q->where('qty', '<=', 0)), ->query(fn ($q) => $q->where('qty', '<=', 0)),
]) ])
->actions([ ->actions([
Actions\Action::make('qr') Actions\Action::make('qr')
->label('QR') ->label(__('QR'))
->icon('heroicon-m-qr-code') ->icon('heroicon-m-qr-code')
->color('gray') ->color('gray')
->modalHeading(fn (Part $r) => 'QR pentru ' . $r->name) ->modalHeading(fn (Part $r) => 'QR pentru ' . $r->name)
@@ -187,7 +192,7 @@ class PartResource extends Resource
]); ]);
}), }),
Actions\Action::make('ai_price') Actions\Action::make('ai_price')
->label('AI: preț recomandat') ->label(__('AI: preț recomandat'))
->icon('heroicon-m-sparkles') ->icon('heroicon-m-sparkles')
->color('primary') ->color('primary')
->modalHeading(fn (Part $r) => "AI: preț pentru {$r->name}") ->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]); return view('filament.tenant.ai-reply', ['reply' => $reply, 'meta' => $meta]);
}), }),
Actions\Action::make('receive') Actions\Action::make('receive')
->label('Recepție') ->label(__('Recepție'))
->icon('heroicon-m-arrow-down-tray') ->icon('heroicon-m-arrow-down-tray')
->color('success') ->color('success')
->schema([ ->schema([
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->required()->minValue(0.001), 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('buy_price')->label(__('Preț unitar'))->numeric()->required(),
Forms\Components\Select::make('supplier_id') Forms\Components\Select::make('supplier_id')
->label('Furnizor') ->label(__('Furnizor'))
->options(fn () => \App\Models\Tenant\Supplier::pluck('name', 'id')), ->options(fn () => \App\Models\Tenant\Supplier::pluck('name', 'id')),
Forms\Components\Select::make('warehouse_id') Forms\Components\Select::make('warehouse_id')
->label('Depozit') ->label(__('Depozit'))
->options(fn () => \App\Models\Tenant\Warehouse::where('is_active', true)->pluck('name', 'id')) ->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')), ->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) { ->action(function (Part $record, array $data) {
$warehouse = $data['warehouse_id'] $warehouse = $data['warehouse_id']
@@ -230,7 +235,7 @@ class PartResource extends Resource
batchRef: $data['batch_ref'] ?? null, batchRef: $data['batch_ref'] ?? null,
); );
\Filament\Notifications\Notification::make() \Filament\Notifications\Notification::make()
->title('Stoc adăugat') ->title(__('Stoc adăugat'))
->success() ->success()
->send(); ->send();
}), }),
@@ -239,7 +244,7 @@ class PartResource extends Resource
]) ])
->bulkActions([ ->bulkActions([
Actions\BulkAction::make('print_labels') Actions\BulkAction::make('print_labels')
->label('Tipărește etichete QR') ->label(__('Tipărește etichete QR'))
->icon('heroicon-m-printer') ->icon('heroicon-m-printer')
->color('gray') ->color('gray')
->action(function ($records) { ->action(function ($records) {
@@ -248,20 +253,20 @@ class PartResource extends Resource
}) })
->deselectRecordsAfterCompletion(), ->deselectRecordsAfterCompletion(),
Actions\BulkAction::make('publish') Actions\BulkAction::make('publish')
->label('Publică în magazin') ->label(__('Publică în magazin'))
->icon('heroicon-m-globe-alt') ->icon('heroicon-m-globe-alt')
->color('success') ->color('success')
->action(fn ($records) => collect($records)->each->update(['is_published' => true])) ->action(fn ($records) => collect($records)->each->update(['is_published' => true]))
->deselectRecordsAfterCompletion(), ->deselectRecordsAfterCompletion(),
Actions\BulkAction::make('unpublish') Actions\BulkAction::make('unpublish')
->label('Scoate din magazin') ->label(__('Scoate din magazin'))
->icon('heroicon-m-eye-slash') ->icon('heroicon-m-eye-slash')
->color('gray') ->color('gray')
->action(fn ($records) => collect($records)->each->update(['is_published' => false])) ->action(fn ($records) => collect($records)->each->update(['is_published' => false]))
->deselectRecordsAfterCompletion(), ->deselectRecordsAfterCompletion(),
]) ])
->emptyStateHeading('Depozit gol') ->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.') ->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') ->emptyStateIcon('heroicon-o-cube')
->defaultSort('name'); ->defaultSort('name');
} }
@@ -10,36 +10,41 @@ class BatchesRelationManager extends RelationManager
{ {
protected static string $relationship = 'batches'; 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 public function table(Table $table): Table
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('received_at') Tables\Columns\TextColumn::make('received_at')
->label('Recepție') ->label(__('Recepție'))
->dateTime('d.m.Y H:i') ->dateTime('d.m.Y H:i')
->sortable(), ->sortable(),
Tables\Columns\TextColumn::make('warehouse.code')->label('Depozit')->placeholder('—'), Tables\Columns\TextColumn::make('warehouse.code')->label(__('Depozit'))->placeholder('—'),
Tables\Columns\TextColumn::make('batch_ref')->label('Ref.')->placeholder('—'), Tables\Columns\TextColumn::make('batch_ref')->label(__('Ref.'))->placeholder('—'),
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->placeholder('—'), Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->placeholder('—'),
Tables\Columns\TextColumn::make('qty_in') Tables\Columns\TextColumn::make('qty_in')
->label('Intrat') ->label(__('Intrat'))
->numeric(decimalPlaces: 2) ->numeric(decimalPlaces: 2)
->alignRight(), ->alignRight(),
Tables\Columns\TextColumn::make('qty_remaining') Tables\Columns\TextColumn::make('qty_remaining')
->label('Rămas') ->label(__('Rămas'))
->numeric(decimalPlaces: 2) ->numeric(decimalPlaces: 2)
->alignRight() ->alignRight()
->weight('bold') ->weight('bold')
->color(fn ($state) => (float) $state <= 0 ? 'gray' : 'success'), ->color(fn ($state) => (float) $state <= 0 ? 'gray' : 'success'),
Tables\Columns\TextColumn::make('buy_price') Tables\Columns\TextColumn::make('buy_price')
->label('Preț unit.') ->label(__('Preț unit.'))
->money('MDL') ->money('MDL')
->alignRight(), ->alignRight(),
]) ])
->defaultSort('received_at') ->defaultSort('received_at')
->emptyStateHeading('Niciun lot înregistrat') ->emptyStateHeading(__('Niciun lot înregistrat'))
->emptyStateDescription('Apasă „Recepție" pe lista de piese pentru a înregistra prima intrare în depozit.'); ->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 $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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\TextInput::make('cross_article')->label('Cod echivalent')->required()->maxLength(64), 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('brand')->label(__('Brand'))->maxLength(64),
]); ]);
} }
@@ -28,12 +33,12 @@ class CrossRefsRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('cross_article') ->recordTitleAttribute('cross_article')
->columns([ ->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('—'), Tables\Columns\TextColumn::make('brand')->placeholder('—'),
]) ])
->headerActions([Actions\CreateAction::make()]) ->headerActions([Actions\CreateAction::make()])
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()]) ->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
->emptyStateHeading('Niciun cod cross') ->emptyStateHeading(__('Niciun cod cross'))
->emptyStateDescription('Adaugă coduri echivalente OEM/aftermarket ca să fie găsite în căutarea din magazin.'); ->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 $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 public function table(Table $table): Table
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('observed_at') Tables\Columns\TextColumn::make('observed_at')
->label('Data') ->label(__('Data'))
->dateTime('d.m.Y H:i') ->dateTime('d.m.Y H:i')
->sortable(), ->sortable(),
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->searchable(), Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->searchable(),
Tables\Columns\TextColumn::make('purchase.number')->label('PO')->placeholder('—'), Tables\Columns\TextColumn::make('purchase.number')->label(__('PO'))->placeholder('—'),
Tables\Columns\TextColumn::make('price') Tables\Columns\TextColumn::make('price')
->money('MDL') ->money('MDL')
->alignRight() ->alignRight()
->sortable(), ->sortable(),
Tables\Columns\TextColumn::make('currency')->label('Val.'), Tables\Columns\TextColumn::make('currency')->label(__('Val.')),
]) ])
->defaultSort('observed_at', 'desc') ->defaultSort('observed_at', 'desc')
->emptyStateHeading('Niciun preț înregistrat') ->emptyStateHeading(__('Niciun preț înregistrat'))
->emptyStateDescription('Prețurile se înregistrează automat la fiecare recepție de PO.'); ->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'); 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; protected static ?int $navigationSort = 50;
@@ -54,28 +64,28 @@ class PaymentResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Plată') Schemas\Components\Section::make(__('Plată'))
->columns(2) ->columns(2)
->schema([ ->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') Forms\Components\Select::make('method')
->options(Payment::METHODS) ->options(Payment::METHODS)
->default('cash') ->default('cash')
->required(), ->required(),
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->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('reference')->label(__('Referință (chitanță / tranzacție)'))->maxLength(64),
Forms\Components\Select::make('client_id') Forms\Components\Select::make('client_id')
->label('Client') ->label(__('Client'))
->options(fn () => Client::pluck('name', 'id')) ->options(fn () => Client::pluck('name', 'id'))
->searchable(), ->searchable(),
Forms\Components\Select::make('work_order_id') Forms\Components\Select::make('work_order_id')
->label('Fișă lucru') ->label(__('Fișă lucru'))
->options(fn () => WorkOrder::orderBy('id', 'desc')->limit(50) ->options(fn () => WorkOrder::orderBy('id', 'desc')->limit(50)
->get()->mapWithKeys(fn ($w) => [$w->id => "{$w->number}" . ($w->client?->name ?? '?')]) ->get()->mapWithKeys(fn ($w) => [$w->id => "{$w->number}" . ($w->client?->name ?? '?')])
->toArray()) ->toArray())
->searchable() ->searchable()
->helperText('Plata stabilește automat starea fișei (unpaid/partial/paid)'), ->helperText(__('Plata stabilește automat starea fișei (unpaid/partial/paid)')),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2), Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]), ]),
]); ]);
} }
@@ -84,22 +94,22 @@ class PaymentResource extends Resource
{ {
return $table return $table
->columns([ ->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('client.name')->label('Client')->searchable(), Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
Tables\Columns\TextColumn::make('workOrder.number')->label('Fișă')->placeholder('—'), Tables\Columns\TextColumn::make('workOrder.number')->label(__('Fișă'))->placeholder('—'),
Tables\Columns\TextColumn::make('method') Tables\Columns\TextColumn::make('method')
->formatStateUsing(fn ($s) => Payment::METHODS[$s] ?? $s) ->formatStateUsing(fn ($s) => Payment::METHODS[$s] ?? $s)
->badge(), ->badge(),
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable()->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label('Total')), 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('reference')->label(__('Ref.'))->placeholder('—')->toggleable(),
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('method')->options(Payment::METHODS), Tables\Filters\SelectFilter::make('method')->options(Payment::METHODS),
Tables\Filters\Filter::make('today') Tables\Filters\Filter::make('today')
->label('Astăzi') ->label(__('Astăzi'))
->query(fn ($q) => $q->whereDate('paid_at', today())), ->query(fn ($q) => $q->whereDate('paid_at', today())),
Tables\Filters\Filter::make('this_month') 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)), ->query(fn ($q) => $q->whereMonth('paid_at', now()->month)->whereYear('paid_at', now()->year)),
]) ])
->actions([ ->actions([
@@ -31,7 +31,12 @@ class PayrollAdjustmentResource extends Resource
protected static ?string $modelLabel = 'ajustare'; 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; protected static ?int $navigationSort = 54;
@@ -48,11 +53,11 @@ class PayrollAdjustmentResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Detalii') Schemas\Components\Section::make(__('Detalii'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('user_id') Forms\Components\Select::make('user_id')
->label('Utilizator') ->label(__('Utilizator'))
->options(fn () => User::orderBy('name')->pluck('name', 'id')) ->options(fn () => User::orderBy('name')->pluck('name', 'id'))
->searchable() ->searchable()
->required(), ->required(),
@@ -60,14 +65,14 @@ class PayrollAdjustmentResource extends Resource
->options(PayrollAdjustment::TYPES) ->options(PayrollAdjustment::TYPES)
->default('bonus') ->default('bonus')
->required(), ->required(),
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(), Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
Forms\Components\TextInput::make('period') Forms\Components\TextInput::make('period')
->label('Perioadă (YYYY-MM)') ->label(__('Perioadă (YYYY-MM)'))
->placeholder(now()->format('Y-m')) ->placeholder(now()->format('Y-m'))
->regex('/^\d{4}-\d{2}$/') ->regex('/^\d{4}-\d{2}$/')
->required(), ->required(),
Forms\Components\DatePicker::make('date')->default(today())->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 return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('date')->date('d.m.Y')->sortable(), 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') Tables\Columns\TextColumn::make('type')
->formatStateUsing(fn ($s) => PayrollAdjustment::TYPES[$s] ?? $s) ->formatStateUsing(fn ($s) => PayrollAdjustment::TYPES[$s] ?? $s)
->badge() ->badge()
@@ -89,12 +94,12 @@ class PayrollAdjustmentResource extends Resource
Tables\Columns\TextColumn::make('period'), Tables\Columns\TextColumn::make('period'),
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('reason')->limit(40), Tables\Columns\TextColumn::make('reason')->limit(40),
Tables\Columns\IconColumn::make('applied')->boolean()->label('Aplicat'), Tables\Columns\IconColumn::make('applied')->boolean()->label(__('Aplicat')),
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('type')->options(PayrollAdjustment::TYPES), Tables\Filters\SelectFilter::make('type')->options(PayrollAdjustment::TYPES),
Tables\Filters\SelectFilter::make('user_id') Tables\Filters\SelectFilter::make('user_id')
->label('Utilizator') ->label(__('Utilizator'))
->options(fn () => User::pluck('name', 'id')), ->options(fn () => User::pluck('name', 'id')),
]) ])
->actions([ ->actions([
@@ -50,40 +50,40 @@ class PayrollRunResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Detalii') Schemas\Components\Section::make(__('Detalii'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('user_id') Forms\Components\Select::make('user_id')
->label('Utilizator') ->label(__('Utilizator'))
->options(fn () => User::orderBy('name')->pluck('name', 'id')) ->options(fn () => User::orderBy('name')->pluck('name', 'id'))
->searchable() ->searchable()
->required(), ->required(),
Forms\Components\TextInput::make('period') Forms\Components\TextInput::make('period')
->label('Perioada (YYYY-MM)') ->label(__('Perioada (YYYY-MM)'))
->placeholder(now()->format('Y-m')) ->placeholder(now()->format('Y-m'))
->required() ->required()
->regex('/^\d{4}-\d{2}$/'), ->regex('/^\d{4}-\d{2}$/'),
]), ]),
Schemas\Components\Section::make('Calcul') Schemas\Components\Section::make(__('Calcul'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('base')->label('Bază')->numeric()->default(0), 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_revenue')->label(__('Venit manopere'))->numeric()->disabled(),
Forms\Components\TextInput::make('works_pct_amount')->label('Comision 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_margin')->label(__('Marja piese'))->numeric()->disabled(),
Forms\Components\TextInput::make('parts_pct_amount')->label('Comision 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('bonus')->numeric()->default(0),
Forms\Components\TextInput::make('fines')->label('Penalizări')->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('advance')->label(__('Avans'))->numeric()->default(0),
Forms\Components\TextInput::make('total')->label('Total net')->numeric()->disabled(), Forms\Components\TextInput::make('total')->label(__('Total net'))->numeric()->disabled(),
]), ]),
Schemas\Components\Section::make('Plată') Schemas\Components\Section::make(__('Plată'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Toggle::make('paid')->label('Achitat'), Forms\Components\Toggle::make('paid')->label(__('Achitat')),
Forms\Components\DatePicker::make('paid_at')->label('Data plății'), 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 return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('period')->label('Perioadă')->sortable(), Tables\Columns\TextColumn::make('period')->label(__('Perioadă'))->sortable(),
Tables\Columns\TextColumn::make('user.name')->label('Utilizator')->searchable(), Tables\Columns\TextColumn::make('user.name')->label(__('Utilizator'))->searchable(),
Tables\Columns\TextColumn::make('base')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('base')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('works_pct_amount')->label('% manopere')->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('parts_pct_amount')->label(__('% piese'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('bonus')->money('MDL')->alignRight()->color('success'), Tables\Columns\TextColumn::make('bonus')->money('MDL')->alignRight()->color('success'),
Tables\Columns\TextColumn::make('fines')->money('MDL')->alignRight()->color('danger'), Tables\Columns\TextColumn::make('fines')->money('MDL')->alignRight()->color('danger'),
Tables\Columns\TextColumn::make('advance')->money('MDL')->alignRight()->color('warning'), 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')), ->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')),
Tables\Columns\IconColumn::make('paid')->boolean(), Tables\Columns\IconColumn::make('paid')->boolean(),
]) ])
->headerActions([ ->headerActions([
Actions\Action::make('compute_all') Actions\Action::make('compute_all')
->label('Calculează luna curentă') ->label(__('Calculează luna curentă'))
->icon('heroicon-m-calculator') ->icon('heroicon-m-calculator')
->color('primary') ->color('primary')
->action(function () { ->action(function () {
@@ -122,13 +122,13 @@ class PayrollRunResource extends Resource
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('period') Tables\Filters\SelectFilter::make('period')
->label('Perioadă') ->label(__('Perioadă'))
->options(fn () => PayrollRun::distinct()->pluck('period', 'period')->toArray()), ->options(fn () => PayrollRun::distinct()->pluck('period', 'period')->toArray()),
Tables\Filters\TernaryFilter::make('paid')->label('Achitat'), Tables\Filters\TernaryFilter::make('paid')->label(__('Achitat')),
]) ])
->actions([ ->actions([
Actions\Action::make('recompute') Actions\Action::make('recompute')
->label('Recalculează') ->label(__('Recalculează'))
->icon('heroicon-m-arrow-path') ->icon('heroicon-m-arrow-path')
->action(fn (PayrollRun $r) => app(PayrollCalculator::class)->compute($r->user_id, $r->period)), ->action(fn (PayrollRun $r) => app(PayrollCalculator::class)->compute($r->user_id, $r->period)),
Actions\EditAction::make(), Actions\EditAction::make(),
+21 -16
View File
@@ -32,7 +32,12 @@ class PostResource extends Resource
protected static ?string $modelLabel = 'pod'; 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; protected static ?int $navigationSort = 76;
@@ -44,37 +49,37 @@ class PostResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Pod / Spațiu lucru') Schemas\Components\Section::make(__('Pod / Spațiu lucru'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('name') Forms\Components\TextInput::make('name')
->label('Nume') ->label(__('Nume'))
->required() ->required()
->maxLength(80) ->maxLength(80)
->placeholder('Ex: Pod 1, Curte 1, Atelier electric'), ->placeholder(__('Ex: Pod 1, Curte 1, Atelier electric')),
Forms\Components\ColorPicker::make('color') Forms\Components\ColorPicker::make('color')
->default('#3b82f6'), ->default('#3b82f6'),
Forms\Components\TextInput::make('hours_per_day') Forms\Components\TextInput::make('hours_per_day')
->label('Ore disponibile / zi') ->label(__('Ore disponibile / zi'))
->numeric() ->numeric()
->step(0.5) ->step(0.5)
->default(10) ->default(10)
->helperText('Capacitatea zilnică în ore'), ->helperText(__('Capacitatea zilnică în ore')),
Forms\Components\Select::make('default_master_id') Forms\Components\Select::make('default_master_id')
->label('Mecanic implicit') ->label(__('Mecanic implicit'))
->options(fn () => User::where('status', 'active')->pluck('name', 'id')) ->options(fn () => User::where('status', 'active')->pluck('name', 'id'))
->searchable() ->searchable()
->placeholder('Niciun mecanic implicit') ->placeholder(__('Niciun mecanic implicit'))
->helperText('Va fi pre-completat când creezi o programare pentru acest pod'), ->helperText(__('Va fi pre-completat când creezi o programare pentru acest pod')),
Forms\Components\TextInput::make('description') Forms\Components\TextInput::make('description')
->label('Descriere') ->label(__('Descriere'))
->maxLength(255) ->maxLength(255)
->placeholder('Ex: cu lift, fără lift, doar diagnoză...') ->placeholder(__('Ex: cu lift, fără lift, doar diagnoză...'))
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\TextInput::make('sort_order') Forms\Components\TextInput::make('sort_order')
->numeric() ->numeric()
->default(100), ->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([ ->columns([
Tables\Columns\TextColumn::make('name')->searchable()->sortable(), Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\ColorColumn::make('color'), Tables\Columns\ColorColumn::make('color'),
Tables\Columns\TextColumn::make('hours_per_day')->label('Ore/zi')->sortable(), 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('defaultMaster.name')->label(__('Mecanic implicit'))->placeholder('—'),
Tables\Columns\TextColumn::make('description')->placeholder('—')->limit(40)->toggleable(), Tables\Columns\TextColumn::make('description')->placeholder('—')->limit(40)->toggleable(),
Tables\Columns\TextColumn::make('appointments_count')->counts('appointments')->label('Programări')->badge(), Tables\Columns\TextColumn::make('appointments_count')->counts('appointments')->label(__('Programări'))->badge(),
Tables\Columns\ToggleColumn::make('is_active')->label('Activ'), Tables\Columns\ToggleColumn::make('is_active')->label(__('Activ')),
]) ])
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
@@ -28,57 +28,53 @@ class PricingCoefficientResource extends Resource
return __('nav.group.Depozit'); return __('nav.group.Depozit');
} }
protected static ?string $modelLabel = 'coeficient';
protected static ?string $pluralModelLabel = 'coeficienți preț';
protected static ?int $navigationSort = 46; protected static ?int $navigationSort = 46;
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Coeficient') Schemas\Components\Section::make(__('Coeficient'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('name')->label('Denumire')->required() Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()
->placeholder('ex: Mașină veche, Client VIP, Express')->columnSpanFull(), ->placeholder(__('ex: Mașină veche, Client VIP, Express'))->columnSpanFull(),
Forms\Components\TextInput::make('multiplier') Forms\Components\TextInput::make('multiplier')
->label('Multiplicator') ->label(__('Multiplicator'))
->numeric() ->numeric()
->required() ->required()
->default(1.10) ->default(1.10)
->helperText('1.15 = +15% peste prețul de bază. 0.95 = -5%.'), ->helperText(__('1.15 = +15% peste prețul de bază. 0.95 = -5%.')),
Forms\Components\TextInput::make('priority')->label('Prioritate')->numeric()->default(100), Forms\Components\TextInput::make('priority')->label(__('Prioritate'))->numeric()->default(100),
Forms\Components\Toggle::make('stackable') Forms\Components\Toggle::make('stackable')
->label('Cumulabil') ->label(__('Cumulabil'))
->default(true) ->default(true)
->helperText('Cumulabil = se înmulțește cu alți coeficienți. Necumulabil = doar cel mai mare necumulabil se aplică.'), ->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), Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
]), ]),
Schemas\Components\Section::make('Condiții (toate trebuie îndeplinite)') Schemas\Components\Section::make(__('Condiții (toate trebuie îndeplinite)'))
->description('Lasă gol = se aplică mereu. Combină condițiile pentru a ținti situații specifice.') ->description(__('Lasă gol = se aplică mereu. Combină condițiile pentru a ținti situații specifice.'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\CheckboxList::make('conditions.classes') Forms\Components\CheckboxList::make('conditions.classes')
->label('Clase auto') ->label(__('Clase auto'))
->options(PricingCoefficient::VEHICLE_CLASSES) ->options(PricingCoefficient::VEHICLE_CLASSES)
->columns(2) ->columns(2)
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\CheckboxList::make('conditions.body_types') Forms\Components\CheckboxList::make('conditions.body_types')
->label('Caroserie') ->label(__('Caroserie'))
->options(\App\Models\Tenant\Vehicle::BODY_TYPES) ->options(\App\Models\Tenant\Vehicle::BODY_TYPES)
->columns(3) ->columns(3)
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\CheckboxList::make('conditions.transmissions') Forms\Components\CheckboxList::make('conditions.transmissions')
->label('Cutie de viteze') ->label(__('Cutie de viteze'))
->options(\App\Models\Tenant\Vehicle::TRANSMISSION_TYPES) ->options(\App\Models\Tenant\Vehicle::TRANSMISSION_TYPES)
->columns(3) ->columns(3)
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\TextInput::make('conditions.age_min')->label('Vârstă min (ani)')->numeric(), 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\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\Toggle::make('conditions.client_vip')->label(__('Doar clienți VIP')),
Forms\Components\CheckboxList::make('conditions.urgency') Forms\Components\CheckboxList::make('conditions.urgency')
->label('Urgență') ->label(__('Urgență'))
->options(PricingCoefficient::URGENCY) ->options(PricingCoefficient::URGENCY)
->columns(3) ->columns(3)
->columnSpanFull(), ->columnSpanFull(),
@@ -90,25 +86,25 @@ class PricingCoefficientResource extends Resource
{ {
return $table return $table
->columns([ ->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('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('multiplier') Tables\Columns\TextColumn::make('multiplier')
->label('Multiplicator') ->label(__('Multiplicator'))
->formatStateUsing(fn ($s) => '×' . rtrim(rtrim(number_format((float) $s, 3), '0'), '.')) ->formatStateUsing(fn ($s) => '×' . rtrim(rtrim(number_format((float) $s, 3), '0'), '.'))
->alignRight() ->alignRight()
->color(fn ($s) => (float) $s >= 1 ? 'success' : 'warning'), ->color(fn ($s) => (float) $s >= 1 ? 'success' : 'warning'),
Tables\Columns\IconColumn::make('stackable')->label('Cumul.')->boolean(), Tables\Columns\IconColumn::make('stackable')->label(__('Cumul.'))->boolean(),
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(), Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
]) ])
->filters([ ->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Active'), Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
]) ])
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun coeficient') ->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.') ->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') ->emptyStateIcon('heroicon-o-adjustments-horizontal')
->defaultSort('priority'); ->defaultSort('priority');
} }
@@ -32,26 +32,36 @@ class PurchaseResource extends Resource
return __('nav.group.Depozit'); 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; protected static ?int $navigationSort = 43;
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Antet') Schemas\Components\Section::make(__('Antet'))
->columns(3) ->columns(3)
->schema([ ->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') Forms\Components\Select::make('supplier_id')
->label('Furnizor') ->label(__('Furnizor'))
->options(fn () => Supplier::where('is_active', true)->pluck('name', 'id')) ->options(fn () => Supplier::where('is_active', true)->pluck('name', 'id'))
->searchable() ->searchable()
->required(), ->required(),
Forms\Components\Select::make('warehouse_id') Forms\Components\Select::make('warehouse_id')
->label('Depozit țintă') ->label(__('Depozit țintă'))
->options(fn () => Warehouse::where('is_active', true)->pluck('name', 'id')) ->options(fn () => Warehouse::where('is_active', true)->pluck('name', 'id'))
->default(fn () => Warehouse::where('is_default', true)->value('id')) ->default(fn () => Warehouse::where('is_default', true)->value('id'))
->required(), ->required(),
@@ -59,12 +69,12 @@ class PurchaseResource extends Resource
->options(Purchase::STATUSES) ->options(Purchase::STATUSES)
->default('draft') ->default('draft')
->required(), ->required(),
Forms\Components\DatePicker::make('order_date')->label('Data comandă')->default(today())->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('expected_at')->label(__('Așteptată')),
Forms\Components\DatePicker::make('received_at')->label('Recepționată'), Forms\Components\DatePicker::make('received_at')->label(__('Recepționată')),
Forms\Components\DatePicker::make('paid_at')->label('Plătită')->columnSpanFull(), 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 return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(), Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->searchable(), 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('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('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('received_at')->label(__('Recepționată'))->date('d.m.Y')->placeholder('—'),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => Purchase::STATUSES[$s] ?? $s) ->formatStateUsing(fn ($s) => Purchase::STATUSES[$s] ?? $s)
->badge() ->badge()
@@ -88,7 +98,7 @@ class PurchaseResource extends Resource
'danger' => ['cancelled'], 'danger' => ['cancelled'],
]), ]),
Tables\Columns\TextColumn::make('received_progress') Tables\Columns\TextColumn::make('received_progress')
->label('Progres') ->label(__('Progres'))
->state(function (Purchase $r) { ->state(function (Purchase $r) {
$items = $r->items; $items = $r->items;
$ord = (float) $items->sum('qty'); $ord = (float) $items->sum('qty');
@@ -101,27 +111,27 @@ class PurchaseResource extends Resource
->filters([ ->filters([
Tables\Filters\SelectFilter::make('status')->options(Purchase::STATUSES), Tables\Filters\SelectFilter::make('status')->options(Purchase::STATUSES),
Tables\Filters\SelectFilter::make('supplier_id') Tables\Filters\SelectFilter::make('supplier_id')
->label('Furnizor') ->label(__('Furnizor'))
->options(fn () => Supplier::pluck('name', 'id')), ->options(fn () => Supplier::pluck('name', 'id')),
]) ])
->actions([ ->actions([
Actions\Action::make('receive_all') Actions\Action::make('receive_all')
->label('Recepție totală') ->label(__('Recepție totală'))
->icon('heroicon-m-check-circle') ->icon('heroicon-m-check-circle')
->color('success') ->color('success')
->visible(fn (Purchase $r) => ! in_array($r->status, ['received', 'cancelled', 'draft'], true)) ->visible(fn (Purchase $r) => ! in_array($r->status, ['received', 'cancelled', 'draft'], true))
->requiresConfirmation() ->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) { ->action(function (Purchase $r) {
try { try {
$r->receiveAllRemaining(); $r->receiveAllRemaining();
Notification::make() Notification::make()
->title('Recepție completă — batch-uri create') ->title(__('Recepție completă — batch-uri create'))
->success() ->success()
->send(); ->send();
} catch (\Throwable $e) { } catch (\Throwable $e) {
Notification::make() Notification::make()
->title('Eroare') ->title(__('Eroare'))
->body($e->getMessage()) ->body($e->getMessage())
->danger() ->danger()
->send(); ->send();
@@ -21,14 +21,14 @@ class ListPurchases extends ListRecords
{ {
return [ return [
Actions\Action::make('ocr') Actions\Action::make('ocr')
->label('Import factură (OCR)') ->label(__('Import factură (OCR)'))
->icon('heroicon-m-document-arrow-up') ->icon('heroicon-m-document-arrow-up')
->color('gray') ->color('gray')
->modalHeading('Import factură via OCR') ->modalHeading(__('Import factură via OCR'))
->modalDescription('Încarcă o poză cu factura. AI-ul extrage furnizorul, data și liniile. Verifici și salvezi.') ->modalDescription(__('Încarcă o poză cu factura. AI-ul extrage furnizorul, data și liniile. Verifici și salvezi.'))
->schema([ ->schema([
Forms\Components\FileUpload::make('invoice') Forms\Components\FileUpload::make('invoice')
->label('Foto factură') ->label(__('Foto factură'))
->image() ->image()
->disk('local') ->disk('local')
->directory('ocr-imports') ->directory('ocr-imports')
@@ -41,7 +41,7 @@ class ListPurchases extends ListRecords
if (! ($result['ok'] ?? false)) { if (! ($result['ok'] ?? false)) {
Notification::make() Notification::make()
->title('OCR eșuat') ->title(__('OCR eșuat'))
->body($result['error'] ?? 'Eroare necunoscută.') ->body($result['error'] ?? 'Eroare necunoscută.')
->danger()->send(); ->danger()->send();
@unlink($abs); @unlink($abs);
@@ -83,7 +83,7 @@ class ListPurchases extends ListRecords
@unlink($abs); @unlink($abs);
Notification::make() Notification::make()
->title('Factură importată') ->title(__('Factură importată'))
->body(sprintf('%d linii, total %.2f. Verifică și ajustează înainte de a confirma.', ->body(sprintf('%d linii, total %.2f. Verifică și ajustează înainte de a confirma.',
count($payload['items']), (float) $purchase->total)) count($payload['items']), (float) $purchase->total))
->success()->send(); ->success()->send();
@@ -18,13 +18,18 @@ class ItemsRelationManager extends RelationManager
{ {
protected static string $relationship = 'items'; 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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\Select::make('part_id') Forms\Components\Select::make('part_id')
->label('Piesă din catalog') ->label(__('Piesă din catalog'))
->options(fn () => Part::where('is_active', true) ->options(fn () => Part::where('is_active', true)
->get() ->get()
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')]) ->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])
@@ -40,11 +45,11 @@ class ItemsRelationManager extends RelationManager
} }
}) })
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(), Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
Forms\Components\TextInput::make('article')->label('Cod articol'), Forms\Components\TextInput::make('article')->label(__('Cod articol')),
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(), Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)->required(),
Forms\Components\TextInput::make('unit')->label('UM')->default('buc'), 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('buy_price')->label(__('Preț achiziție'))->numeric()->required(),
]); ]);
} }
@@ -55,13 +60,13 @@ class ItemsRelationManager extends RelationManager
->columns([ ->columns([
Tables\Columns\TextColumn::make('name')->wrap(), Tables\Columns\TextColumn::make('name')->wrap(),
Tables\Columns\TextColumn::make('article')->placeholder('—'), 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') Tables\Columns\TextColumn::make('qty_received')
->label('Recepționat') ->label(__('Recepționat'))
->alignRight() ->alignRight()
->color(fn ($state, $record) => $record->isFullyReceived() ? 'success' : ((float) $state > 0 ? 'warning' : 'gray')) ->color(fn ($state, $record) => $record->isFullyReceived() ? 'success' : ((float) $state > 0 ? 'warning' : 'gray'))
->formatStateUsing(fn ($state, $record) => sprintf('%.2f / %.2f', (float) $state, (float) $record->qty)), ->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('buy_price')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
]) ])
@@ -70,22 +75,22 @@ class ItemsRelationManager extends RelationManager
]) ])
->actions([ ->actions([
Actions\Action::make('receive_item') Actions\Action::make('receive_item')
->label('Recepționează') ->label(__('Recepționează'))
->icon('heroicon-m-arrow-down-tray') ->icon('heroicon-m-arrow-down-tray')
->color('success') ->color('success')
->visible(fn (PurchaseItem $r) => ! $r->isFullyReceived()) ->visible(fn (PurchaseItem $r) => ! $r->isFullyReceived())
->schema([ ->schema([
Forms\Components\Placeholder::make('outstanding') Forms\Components\Placeholder::make('outstanding')
->label('Restanță') ->label(__('Restanță'))
->content(fn (PurchaseItem $r) => sprintf('%.2f %s', $r->outstanding(), $r->unit ?? 'buc')), ->content(fn (PurchaseItem $r) => sprintf('%.2f %s', $r->outstanding(), $r->unit ?? 'buc')),
Forms\Components\TextInput::make('qty') Forms\Components\TextInput::make('qty')
->label('Cantitate recepționată') ->label(__('Cantitate recepționată'))
->numeric() ->numeric()
->required() ->required()
->minValue(0.001) ->minValue(0.001)
->default(fn (PurchaseItem $r) => $r->outstanding()), ->default(fn (PurchaseItem $r) => $r->outstanding()),
Forms\Components\Select::make('warehouse_id') Forms\Components\Select::make('warehouse_id')
->label('Depozit țintă') ->label(__('Depozit țintă'))
->options(fn () => Warehouse::where('is_active', true)->pluck('name', 'id')) ->options(fn () => Warehouse::where('is_active', true)->pluck('name', 'id'))
->default(fn (PurchaseItem $r) => $r->purchase?->warehouse_id ->default(fn (PurchaseItem $r) => $r->purchase?->warehouse_id
?? Warehouse::where('is_default', true)->value('id')) ?? Warehouse::where('is_default', true)->value('id'))
@@ -96,12 +101,12 @@ class ItemsRelationManager extends RelationManager
try { try {
$r->purchase->receiveItem($r, (float) $data['qty'], $wh); $r->purchase->receiveItem($r, (float) $data['qty'], $wh);
Notification::make() Notification::make()
->title('Recepționat — batch creat') ->title(__('Recepționat — batch creat'))
->success() ->success()
->send(); ->send();
} catch (\Throwable $e) { } catch (\Throwable $e) {
Notification::make() Notification::make()
->title('Eroare la recepție') ->title(__('Eroare la recepție'))
->body($e->getMessage()) ->body($e->getMessage())
->danger() ->danger()
->send(); ->send();
+10 -10
View File
@@ -43,16 +43,16 @@ class RoleResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Rol') Schemas\Components\Section::make(__('Rol'))
->columns(2) ->columns(2)
->schema([ ->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)) ->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(), Forms\Components\TextInput::make('guard_name')->default('web')->disabled(),
]), ]),
Schemas\Components\Section::make('Drepturi') Schemas\Components\Section::make(__('Drepturi'))
->description('Bifează ce poate face acest rol. Modificările au efect imediat.') ->description(__('Bifează ce poate face acest rol. Modificările au efect imediat.'))
->schema(self::permissionFields()) ->schema(self::permissionFields())
->columns(1), ->columns(1),
]); ]);
@@ -88,25 +88,25 @@ class RoleResource extends Resource
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('name') Tables\Columns\TextColumn::make('name')
->label('Rol') ->label(__('Rol'))
->formatStateUsing(fn ($state) => Permissions::roleLabels()[$state] ?? $state) ->formatStateUsing(fn ($state) => Permissions::roleLabels()[$state] ?? $state)
->searchable() ->searchable()
->sortable(), ->sortable(),
Tables\Columns\TextColumn::make('name') Tables\Columns\TextColumn::make('name')
->label('Slug') ->label(__('Slug'))
->copyable() ->copyable()
->color('gray'), ->color('gray'),
Tables\Columns\TextColumn::make('permissions_count') Tables\Columns\TextColumn::make('permissions_count')
->counts('permissions') ->counts('permissions')
->label('Drepturi') ->label(__('Drepturi'))
->badge(), ->badge(),
Tables\Columns\TextColumn::make('users_count') Tables\Columns\TextColumn::make('users_count')
->counts('users') ->counts('users')
->label('Utilizatori') ->label(__('Utilizatori'))
->badge(), ->badge(),
]) ])
->actions([ ->actions([
Actions\EditAction::make()->label('Editează drepturi'), Actions\EditAction::make()->label(__('Editează drepturi')),
Actions\DeleteAction::make() Actions\DeleteAction::make()
->hidden(fn ($record) => in_array($record->name, array_keys(Permissions::roleMatrix()), true)), ->hidden(fn ($record) => in_array($record->name, array_keys(Permissions::roleMatrix()), true)),
]) ])
@@ -30,9 +30,19 @@ class ServiceTemplateResource extends Resource
return __('nav.group.Service'); 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; protected static ?int $navigationSort = 33;
@@ -42,14 +52,14 @@ class ServiceTemplateResource extends Resource
Schemas\Components\Section::make() Schemas\Components\Section::make()
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('name')->label('Denumire')->required() Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()
->placeholder('ex: Revizie completă 15.000 km')->columnSpanFull(), ->placeholder(__('ex: Revizie completă 15.000 km'))->columnSpanFull(),
Forms\Components\Select::make('category') Forms\Components\Select::make('category')
->label('Categorie') ->label(__('Categorie'))
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES)) ->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES))
->searchable(), ->searchable(),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true), 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),
]), ]),
]); ]);
} }
@@ -60,18 +70,18 @@ class ServiceTemplateResource extends Resource
->columns([ ->columns([
Tables\Columns\TextColumn::make('name')->searchable()->sortable(), Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'), Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('items_count')->counts('items')->label('Linii')->alignRight(), Tables\Columns\TextColumn::make('items_count')->counts('items')->label(__('Linii'))->alignRight(),
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(), Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
]) ])
->filters([ ->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Active'), Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
]) ])
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun șablon') ->emptyStateHeading(__('Niciun șablon'))
->emptyStateDescription('Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet") și aplică-l pe o fișă cu un click.') ->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') ->emptyStateIcon('heroicon-o-clipboard-document-list')
->defaultSort('name'); ->defaultSort('name');
} }
@@ -18,19 +18,24 @@ class ItemsRelationManager extends RelationManager
{ {
protected static string $relationship = 'items'; 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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\Select::make('kind') Forms\Components\Select::make('kind')
->label('Tip') ->label(__('Tip'))
->options(ServiceTemplateItem::KINDS) ->options(ServiceTemplateItem::KINDS)
->default('labor') ->default('labor')
->live() ->live()
->required(), ->required(),
Forms\Components\Select::make('labor_id') Forms\Components\Select::make('labor_id')
->label('Manoperă') ->label(__('Manoperă'))
->options(fn () => Labor::where('is_active', true)->pluck('name_ro', 'id')) ->options(fn () => Labor::where('is_active', true)->pluck('name_ro', 'id'))
->searchable() ->searchable()
->visible(fn (Get $get) => $get('kind') === 'labor') ->visible(fn (Get $get) => $get('kind') === 'labor')
@@ -42,7 +47,7 @@ class ItemsRelationManager extends RelationManager
} }
}), }),
Forms\Components\Select::make('part_id') Forms\Components\Select::make('part_id')
->label('Piesă') ->label(__('Piesă'))
->options(fn () => Part::where('is_active', true) ->options(fn () => Part::where('is_active', true)
->get()->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])->toArray()) ->get()->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])->toArray())
->searchable() ->searchable()
@@ -51,10 +56,10 @@ class ItemsRelationManager extends RelationManager
->afterStateUpdated(function ($state, Set $set) { ->afterStateUpdated(function ($state, Set $set) {
if ($state && $p = Part::find($state)) $set('name', $p->name); if ($state && $p = Part::find($state)) $set('name', $p->name);
}), }),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(), Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
Forms\Components\TextInput::make('hours')->label('Ore')->numeric() Forms\Components\TextInput::make('hours')->label(__('Ore'))->numeric()
->visible(fn (Get $get) => $get('kind') === 'labor'), ->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'), ->visible(fn (Get $get) => $get('kind') === 'part'),
]); ]);
} }
@@ -65,13 +70,13 @@ class ItemsRelationManager extends RelationManager
->recordTitleAttribute('name') ->recordTitleAttribute('name')
->columns([ ->columns([
Tables\Columns\TextColumn::make('kind') Tables\Columns\TextColumn::make('kind')
->label('Tip') ->label(__('Tip'))
->formatStateUsing(fn ($s) => ServiceTemplateItem::KINDS[$s] ?? $s) ->formatStateUsing(fn ($s) => ServiceTemplateItem::KINDS[$s] ?? $s)
->badge() ->badge()
->color(fn ($s) => $s === 'labor' ? 'info' : 'gray'), ->color(fn ($s) => $s === 'labor' ? 'info' : 'gray'),
Tables\Columns\TextColumn::make('name')->wrap(), Tables\Columns\TextColumn::make('name')->wrap(),
Tables\Columns\TextColumn::make('hours')->label('Ore')->placeholder('—')->alignRight(), Tables\Columns\TextColumn::make('hours')->label(__('Ore'))->placeholder('—')->alignRight(),
Tables\Columns\TextColumn::make('qty')->label('Cant.')->placeholder('—')->alignRight(), Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->placeholder('—')->alignRight(),
]) ])
->headerActions([Actions\CreateAction::make()]) ->headerActions([Actions\CreateAction::make()])
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()]); ->actions([Actions\EditAction::make(), Actions\DeleteAction::make()]);
@@ -32,24 +32,20 @@ class ShopCustomerResource extends Resource
return __('nav.group.Magazin'); return __('nav.group.Magazin');
} }
protected static ?string $modelLabel = 'client magazin';
protected static ?string $pluralModelLabel = 'clienți magazin';
protected static ?int $navigationSort = 52; protected static ?int $navigationSort = 52;
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make()->columns(2)->schema([ 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\TextInput::make('phone')->label('Telefon')->required()->maxLength(40), Forms\Components\TextInput::make('phone')->label(__('Telefon'))->required()->maxLength(40),
Forms\Components\TextInput::make('email')->label('Email')->email()->maxLength(160), Forms\Components\TextInput::make('email')->label(__('Email'))->email()->maxLength(160),
Forms\Components\Select::make('client_id') Forms\Components\Select::make('client_id')
->label('Client legat (CRM)') ->label(__('Client legat (CRM)'))
->options(fn () => \App\Models\Tenant\Client::pluck('name', 'id')) ->options(fn () => \App\Models\Tenant\Client::pluck('name', 'id'))
->searchable() ->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('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(), Tables\Columns\TextColumn::make('phone')->copyable()->searchable(),
Tables\Columns\TextColumn::make('email')->placeholder('—')->copyable()->toggleable(), Tables\Columns\TextColumn::make('email')->placeholder('—')->copyable()->toggleable(),
Tables\Columns\TextColumn::make('client.name')->label('Client CRM')->placeholder('—')->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('orders_count')->counts('orders')->label(__('Comenzi'))->alignRight(),
Tables\Columns\TextColumn::make('last_login_at')->label('Ultim login')->since()->placeholder('Niciodată'), 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('created_at')->label(__('Înregistrat'))->date('d.m.Y')->toggleable(),
]) ])
->actions([ ->actions([
Actions\Action::make('reset_password') Actions\Action::make('reset_password')
->label('Trimite reset parolă') ->label(__('Trimite reset parolă'))
->icon('heroicon-m-key') ->icon('heroicon-m-key')
->color('warning') ->color('warning')
->visible(fn (ShopCustomer $r) => ! empty($r->email)) ->visible(fn (ShopCustomer $r) => ! empty($r->email))
->requiresConfirmation() ->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) { ->action(function (ShopCustomer $r) {
$status = Password::broker('shop_customers')->sendResetLink(['email' => $r->email]); $status = Password::broker('shop_customers')->sendResetLink(['email' => $r->email]);
Notification::make() Notification::make()
@@ -86,8 +82,8 @@ class ShopCustomerResource extends Resource
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun client magazin') ->emptyStateHeading(__('Niciun client magazin'))
->emptyStateDescription('Aici apar clienții care și-au creat cont în magazinul online (/shop/register).') ->emptyStateDescription(__('Aici apar clienții care și-au creat cont în magazinul online (/shop/register).'))
->emptyStateIcon('heroicon-o-user-circle') ->emptyStateIcon('heroicon-o-user-circle')
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -11,15 +11,20 @@ class OrdersRelationManager extends RelationManager
{ {
protected static string $relationship = 'orders'; 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 public function table(Table $table): Table
{ {
return $table return $table
->recordTitleAttribute('number') ->recordTitleAttribute('number')
->columns([ ->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.'), 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('created_at')->label(__('Data'))->dateTime('d.m.Y H:i'),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => OnlineOrder::STATUSES[$s] ?? $s) ->formatStateUsing(fn ($s) => OnlineOrder::STATUSES[$s] ?? $s)
->badge() ->badge()
@@ -33,6 +38,6 @@ class OrdersRelationManager extends RelationManager
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
]) ])
->defaultSort('created_at', 'desc') ->defaultSort('created_at', 'desc')
->emptyStateHeading('Nicio comandă încă'); ->emptyStateHeading(__('Nicio comandă încă'));
} }
} }
@@ -30,10 +30,6 @@ class SubcontractJobResource extends Resource
return __('nav.group.Subcontractare'); return __('nav.group.Subcontractare');
} }
protected static ?string $modelLabel = 'lucrare terți';
protected static ?string $pluralModelLabel = 'lucrări terți';
protected static ?int $navigationSort = 71; protected static ?int $navigationSort = 71;
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
@@ -45,43 +41,43 @@ class SubcontractJobResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Lucrare') Schemas\Components\Section::make(__('Lucrare'))
->columns(2) ->columns(2)
->schema([ ->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('status')->options(SubcontractJob::STATUSES)->default('sent')->required(),
Forms\Components\Select::make('subcontractor_id') Forms\Components\Select::make('subcontractor_id')
->label('Subcontractor') ->label(__('Subcontractor'))
->options(fn () => Subcontractor::where('is_active', true)->pluck('name', 'id')) ->options(fn () => Subcontractor::where('is_active', true)->pluck('name', 'id'))
->searchable(), ->searchable(),
Forms\Components\Select::make('work_order_id') Forms\Components\Select::make('work_order_id')
->label('Fișă asociată') ->label(__('Fișă asociată'))
->options(fn () => WorkOrder::whereNotIn('status', ['done', 'cancelled']) ->options(fn () => WorkOrder::whereNotIn('status', ['done', 'cancelled'])
->get()->mapWithKeys(fn ($w) => [$w->id => "#{$w->number} · " . ($w->vehicle?->plate ?? '')])->toArray()) ->get()->mapWithKeys(fn ($w) => [$w->id => "#{$w->number} · " . ($w->vehicle?->plate ?? '')])->toArray())
->searchable(), ->searchable(),
Forms\Components\Select::make('category') Forms\Components\Select::make('category')
->label('Categorie') ->label(__('Categorie'))
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES)) ->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
->searchable(), ->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) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('cost')->label('Cost (de la terț)')->numeric()->default(0)->required(), 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) Forms\Components\TextInput::make('markup_pct')->label(__('Markup %'))->numeric()->default(0)
->helperText('> 0 calculează automat prețul client.'), ->helperText(__('> 0 calculează automat prețul client.')),
Forms\Components\TextInput::make('client_price')->label('Preț client')->numeric()->default(0) Forms\Components\TextInput::make('client_price')->label(__('Preț client'))->numeric()->default(0)
->helperText('Setat manual dacă markup = 0.'), ->helperText(__('Setat manual dacă markup = 0.')),
Forms\Components\Toggle::make('paid_to_sub')->label('Plătit către terț'), 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) ->columns(3)
->schema([ ->schema([
Forms\Components\DatePicker::make('sent_at')->label('Trimis')->default(today()), Forms\Components\DatePicker::make('sent_at')->label(__('Trimis'))->default(today()),
Forms\Components\DatePicker::make('eta')->label('ETA'), Forms\Components\DatePicker::make('eta')->label(__('ETA')),
Forms\Components\DatePicker::make('returned_at')->label('Returnat'), Forms\Components\DatePicker::make('returned_at')->label(__('Returnat')),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2), Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]), ]),
]); ]);
} }
@@ -90,14 +86,14 @@ class SubcontractJobResource extends Resource
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(), Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('subcontractor.name')->label('Terț')->placeholder('—'), Tables\Columns\TextColumn::make('subcontractor.name')->label(__('Terț'))->placeholder('—'),
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'), Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('workOrder.number')->label('Fișă')->placeholder('—'), Tables\Columns\TextColumn::make('workOrder.number')->label(__('Fișă'))->placeholder('—'),
Tables\Columns\TextColumn::make('cost')->label('Cost')->money('MDL')->alignRight(), 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('client_price')->label(__('Preț client'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('margin') Tables\Columns\TextColumn::make('margin')
->label('Marjă') ->label(__('Marjă'))
->state(fn (SubcontractJob $r) => $r->margin()) ->state(fn (SubcontractJob $r) => $r->margin())
->money('MDL') ->money('MDL')
->alignRight() ->alignRight()
@@ -110,20 +106,20 @@ class SubcontractJobResource extends Resource
'success' => ['done', 'returned'], 'success' => ['done', 'returned'],
'danger' => ['cancelled'], '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([ ->filters([
Tables\Filters\SelectFilter::make('status')->options(SubcontractJob::STATUSES), Tables\Filters\SelectFilter::make('status')->options(SubcontractJob::STATUSES),
Tables\Filters\SelectFilter::make('subcontractor_id') Tables\Filters\SelectFilter::make('subcontractor_id')
->label('Subcontractor') ->label(__('Subcontractor'))
->options(fn () => Subcontractor::pluck('name', 'id')), ->options(fn () => Subcontractor::pluck('name', 'id')),
]) ])
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Nicio lucrare la terți') ->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.') ->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') ->emptyStateIcon('heroicon-o-arrow-top-right-on-square')
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -38,19 +38,19 @@ class SubcontractorResource extends Resource
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make()->columns(2)->schema([ 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') Forms\Components\Select::make('specialty')
->label('Specialitate') ->label(__('Specialitate'))
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES)) ->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
->searchable(), ->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\TextInput::make('email')->email()->maxLength(120),
Forms\Components\Select::make('rating') Forms\Components\Select::make('rating')
->label('Rating') ->label(__('Rating'))
->options([1 => '★', 2 => '★★', 3 => '★★★', 4 => '★★★★', 5 => '★★★★★']) ->options([1 => '★', 2 => '★★', 3 => '★★★', 4 => '★★★★', 5 => '★★★★★'])
->default(3), ->default(3),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true), 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),
]), ]),
]); ]);
} }
@@ -63,18 +63,18 @@ class SubcontractorResource extends Resource
Tables\Columns\TextColumn::make('specialty')->badge()->placeholder('—'), Tables\Columns\TextColumn::make('specialty')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('phone')->copyable()->placeholder('—'), Tables\Columns\TextColumn::make('phone')->copyable()->placeholder('—'),
Tables\Columns\TextColumn::make('rating')->formatStateUsing(fn ($s) => str_repeat('★', (int) $s)), 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(), Tables\Columns\IconColumn::make('is_active')->boolean(),
]) ])
->filters([ ->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Activi'), Tables\Filters\TernaryFilter::make('is_active')->label(__('Activi')),
]) ])
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun subcontractor') ->emptyStateHeading(__('Niciun subcontractor'))
->emptyStateDescription('Adaugă atelierele terțe la care trimiți lucrări (turbo, cutii, vopsitorie, PDR) și urmărește costul + marja.') ->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') ->emptyStateIcon('heroicon-o-user-group')
->defaultSort('name'); ->defaultSort('name');
} }
@@ -37,30 +37,30 @@ class SupplierResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identificare') Schemas\Components\Section::make(__('Identificare'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(160), 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('contact_name')->label(__('Persoană contact'))->maxLength(120),
Forms\Components\TextInput::make('phone')->tel()->maxLength(40), Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
Forms\Components\TextInput::make('email')->email()->maxLength(120), Forms\Components\TextInput::make('email')->email()->maxLength(120),
Forms\Components\TextInput::make('website')->url()->maxLength(160), Forms\Components\TextInput::make('website')->url()->maxLength(160),
]), ]),
Schemas\Components\Section::make('Comercial') Schemas\Components\Section::make(__('Comercial'))
->columns(3) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('pay_terms')->label('Termeni plată')->placeholder('Net 30 / Avans')->maxLength(60), 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('delivery_days')->label(__('Zile livrare'))->numeric()->default(0),
Forms\Components\Select::make('rating') Forms\Components\Select::make('rating')
->label('Rating') ->label(__('Rating'))
->options([1 => '★', 2 => '★★', 3 => '★★★', 4 => '★★★★', 5 => '★★★★★']) ->options([1 => '★', 2 => '★★', 3 => '★★★', 4 => '★★★★', 5 => '★★★★★'])
->default(3) ->default(3)
->required(), ->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\TagsInput::make('categories')->label('Categorii')->placeholder('Frâne, Ulei, ...')->columnSpan(2), 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\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 return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('name')->searchable()->sortable(), 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('phone')->copyable()->placeholder('—'),
Tables\Columns\TextColumn::make('rating') Tables\Columns\TextColumn::make('rating')
->label('Rating') ->label(__('Rating'))
->formatStateUsing(fn ($s) => str_repeat('★', (int) $s)), ->formatStateUsing(fn ($s) => str_repeat('★', (int) $s)),
Tables\Columns\TextColumn::make('on_time_pct') 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)) ->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->onTimeRate($r))
->formatStateUsing(fn ($s) => $s === null ? '—' : "{$s}%") ->formatStateUsing(fn ($s) => $s === null ? '—' : "{$s}%")
->color(fn ($s) => $s === null ? 'gray' : ($s >= 90 ? 'success' : ($s >= 70 ? 'warning' : 'danger'))) ->color(fn ($s) => $s === null ? 'gray' : ($s >= 90 ? 'success' : ($s >= 70 ? 'warning' : 'danger')))
->alignRight() ->alignRight()
->toggleable(), ->toggleable(),
Tables\Columns\TextColumn::make('avg_delivery_days') 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)) ->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->avgDeliveryDays($r))
->formatStateUsing(fn ($s) => $s === null ? '—' : (string) $s) ->formatStateUsing(fn ($s) => $s === null ? '—' : (string) $s)
->alignRight() ->alignRight()
->toggleable(), ->toggleable(),
Tables\Columns\TextColumn::make('spend_90d') Tables\Columns\TextColumn::make('spend_90d')
->label('Cheltuit 90d') ->label(__('Cheltuit 90d'))
->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->spend($r)) ->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->spend($r))
->money('MDL') ->money('MDL')
->alignRight() ->alignRight()
->toggleable(), ->toggleable(),
Tables\Columns\TextColumn::make('delivery_days')->label('Livrare (zile)')->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('discount_pct')->label(__('Discount'))
->formatStateUsing(fn ($s) => $s . '%')->alignRight()->toggleable(), ->formatStateUsing(fn ($s) => $s . '%')->alignRight()->toggleable(),
Tables\Columns\IconColumn::make('is_active')->boolean(), Tables\Columns\IconColumn::make('is_active')->boolean(),
]) ])
->filters([ ->filters([
Tables\Filters\TernaryFilter::make('is_active')->label('Activi'), Tables\Filters\TernaryFilter::make('is_active')->label(__('Activi')),
]) ])
->actions([ ->actions([
Actions\Action::make('rate') Actions\Action::make('rate')
->label('Rerating') ->label(__('Rerating'))
->icon('heroicon-m-arrow-path') ->icon('heroicon-m-arrow-path')
->color('gray') ->color('gray')
->action(function (Supplier $r) { ->action(function (Supplier $r) {
@@ -111,8 +111,8 @@ class SupplierResource extends Resource
->computedRating($r); ->computedRating($r);
if ($score === null) { if ($score === null) {
\Filament\Notifications\Notification::make() \Filament\Notifications\Notification::make()
->title('Date insuficiente') ->title(__('Date insuficiente'))
->body('Necesită cel puțin 2 recepții complete cu data așteptată setată.') ->body(__('Necesită cel puțin 2 recepții complete cu data așteptată setată.'))
->warning() ->warning()
->send(); ->send();
return; return;
@@ -32,10 +32,6 @@ class TireSetResource extends Resource
return __('nav.group.Anvelope'); return __('nav.group.Anvelope');
} }
protected static ?string $modelLabel = 'set anvelope';
protected static ?string $pluralModelLabel = 'seturi anvelope';
protected static ?int $navigationSort = 60; protected static ?int $navigationSort = 60;
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
@@ -52,53 +48,53 @@ class TireSetResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Proprietar') Schemas\Components\Section::make(__('Proprietar'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('client_id') Forms\Components\Select::make('client_id')
->label('Client') ->label(__('Client'))
->options(fn () => Client::pluck('name', 'id')) ->options(fn () => Client::pluck('name', 'id'))
->searchable() ->searchable()
->live() ->live()
->required(), ->required(),
Forms\Components\Select::make('vehicle_id') Forms\Components\Select::make('vehicle_id')
->label('Auto') ->label(__('Auto'))
->options(fn (Get $get) => $get('client_id') ->options(fn (Get $get) => $get('client_id')
? Vehicle::where('client_id', $get('client_id'))->get() ? Vehicle::where('client_id', $get('client_id'))->get()
->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray() ->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray()
: []) : [])
->searchable(), ->searchable(),
Forms\Components\TextInput::make('label')->label('Etichetă')->placeholder('ex: Iarnă Michelin'), 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\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) ->columns(3)
->schema([ ->schema([
Forms\Components\TextInput::make('width')->label('Lățime')->numeric()->placeholder('205'), 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('profile')->label(__('Profil'))->numeric()->placeholder('55'),
Forms\Components\TextInput::make('diameter')->label('Diametru R')->numeric()->placeholder('16'), Forms\Components\TextInput::make('diameter')->label(__('Diametru R'))->numeric()->placeholder('16'),
Forms\Components\TextInput::make('brand')->maxLength(64), Forms\Components\TextInput::make('brand')->maxLength(64),
Forms\Components\TextInput::make('model')->maxLength(64), Forms\Components\TextInput::make('model')->maxLength(64),
Forms\Components\TextInput::make('dot_year')->label('DOT')->maxLength(8)->placeholder('3621'), Forms\Components\TextInput::make('dot_year')->label(__('DOT'))->maxLength(8)->placeholder('3621'),
Forms\Components\Toggle::make('has_rims')->label('Cu jante'), 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('rim_type')->label(__('Tip jante'))->options(['steel' => 'Tablă', 'alloy' => 'Aliaj']),
Forms\Components\Select::make('condition')->label('Stare')->options(TireSet::CONDITIONS), 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) ->columns(4)
->schema([ ->schema([
Forms\Components\TextInput::make('tread.fl')->label('Față-Stânga')->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.fr')->label(__('Față-Dreapta'))->numeric(),
Forms\Components\TextInput::make('tread.rl')->label('Spate-Stânga')->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.rr')->label(__('Spate-Dreapta'))->numeric(),
]), ]),
Schemas\Components\Section::make('TPMS & foto') Schemas\Components\Section::make(__('TPMS & foto'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Toggle::make('tpms')->label('Senzori TPMS'), Forms\Components\Toggle::make('tpms')->label(__('Senzori TPMS')),
Forms\Components\TextInput::make('notes')->label('Observații'), Forms\Components\TextInput::make('notes')->label(__('Observații')),
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos') \Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos')
->label('Fotografii') ->label(__('Fotografii'))
->collection('photos') ->collection('photos')
->multiple() ->multiple()
->image() ->image()
@@ -112,23 +108,23 @@ class TireSetResource extends Resource
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable()->sortable(), Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable()->sortable(),
Tables\Columns\TextColumn::make('label')->label('Etichetă')->placeholder('—'), Tables\Columns\TextColumn::make('label')->label(__('Etichetă'))->placeholder('—'),
Tables\Columns\TextColumn::make('size') Tables\Columns\TextColumn::make('size')
->label('Dimensiune') ->label(__('Dimensiune'))
->state(fn (TireSet $r) => $r->sizeLabel()), ->state(fn (TireSet $r) => $r->sizeLabel()),
Tables\Columns\TextColumn::make('season') Tables\Columns\TextColumn::make('season')
->label('Sezon') ->label(__('Sezon'))
->formatStateUsing(fn ($s) => TireSet::SEASONS[$s] ?? $s) ->formatStateUsing(fn ($s) => TireSet::SEASONS[$s] ?? $s)
->badge() ->badge()
->colors(['warning' => ['summer'], 'info' => ['winter'], 'gray' => ['allseason']]), ->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' : '—') ->formatStateUsing(fn ($s) => $s ? $s . ' mm' : '—')
->color(fn ($s) => $s !== null && (float) $s < 3 ? 'danger' : null) ->color(fn ($s) => $s !== null && (float) $s < 3 ? 'danger' : null)
->alignRight(), ->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') Tables\Columns\TextColumn::make('storage_status')
->label('Depozit') ->label(__('Depozit'))
->state(fn (TireSet $r) => $r->isStored() ? ($r->currentStorage()?->location ?? 'da') : '—') ->state(fn (TireSet $r) => $r->isStored() ? ($r->currentStorage()?->location ?? 'da') : '—')
->badge() ->badge()
->color(fn ($state) => $state === '—' ? 'gray' : 'success'), ->color(fn ($state) => $state === '—' ? 'gray' : 'success'),
@@ -136,19 +132,19 @@ class TireSetResource extends Resource
->filters([ ->filters([
Tables\Filters\SelectFilter::make('season')->options(TireSet::SEASONS), Tables\Filters\SelectFilter::make('season')->options(TireSet::SEASONS),
Tables\Filters\Filter::make('stored') Tables\Filters\Filter::make('stored')
->label('În depozit') ->label(__('În depozit'))
->query(fn ($q) => $q->whereHas('storage', fn ($s) => $s->where('status', 'stored'))), ->query(fn ($q) => $q->whereHas('storage', fn ($s) => $s->where('status', 'stored'))),
]) ])
->actions([ ->actions([
Actions\Action::make('check_in') Actions\Action::make('check_in')
->label('Check-in depozit') ->label(__('Check-in depozit'))
->icon('heroicon-m-arrow-down-on-square') ->icon('heroicon-m-arrow-down-on-square')
->color('success') ->color('success')
->visible(fn (TireSet $r) => ! $r->isStored()) ->visible(fn (TireSet $r) => ! $r->isStored())
->schema([ ->schema([
Forms\Components\TextInput::make('location')->label('Locație (raft)')->required()->placeholder('A1-03'), 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('season_label')->label(__('Perioadă'))->placeholder(__('Iarnă 2025-2026')),
Forms\Components\TextInput::make('fee')->label('Taxă depozitare')->numeric()->default(0), Forms\Components\TextInput::make('fee')->label(__('Taxă depozitare'))->numeric()->default(0),
]) ])
->action(function (TireSet $r, array $data) { ->action(function (TireSet $r, array $data) {
\App\Models\Tenant\TireStorage::create([ \App\Models\Tenant\TireStorage::create([
@@ -159,27 +155,27 @@ class TireSetResource extends Resource
'status' => 'stored', 'status' => 'stored',
'checked_in_at' => now(), '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') Actions\Action::make('check_out')
->label('Eliberează') ->label(__('Eliberează'))
->icon('heroicon-m-arrow-up-on-square') ->icon('heroicon-m-arrow-up-on-square')
->color('warning') ->color('warning')
->visible(fn (TireSet $r) => $r->isStored()) ->visible(fn (TireSet $r) => $r->isStored())
->requiresConfirmation() ->requiresConfirmation()
->modalDescription('Marchează setul ca ridicat de client.') ->modalDescription(__('Marchează setul ca ridicat de client.'))
->action(function (TireSet $r) { ->action(function (TireSet $r) {
$storage = $r->currentStorage(); $storage = $r->currentStorage();
if ($storage) { if ($storage) {
$storage->update(['status' => 'retrieved', 'checked_out_at' => now()]); $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\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun set de anvelope') ->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.') ->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') ->emptyStateIcon('heroicon-o-lifebuoy')
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -11,25 +11,30 @@ class StorageRelationManager extends RelationManager
{ {
protected static string $relationship = 'storage'; 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 public function table(Table $table): Table
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('season_label')->label('Perioadă')->placeholder('—'), Tables\Columns\TextColumn::make('season_label')->label(__('Perioadă'))->placeholder('—'),
Tables\Columns\TextColumn::make('location')->label('Locație')->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_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('checked_out_at')->label(__('Ridicat'))->dateTime('d.m.Y')->placeholder('—'),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => TireStorage::STATUSES[$s] ?? $s) ->formatStateUsing(fn ($s) => TireStorage::STATUSES[$s] ?? $s)
->badge() ->badge()
->colors(['success' => ['stored'], 'gray' => ['retrieved']]), ->colors(['success' => ['stored'], 'gray' => ['retrieved']]),
Tables\Columns\TextColumn::make('fee')->money('MDL')->alignRight(), 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') ->defaultSort('checked_in_at', 'desc')
->emptyStateHeading('Niciun istoric') ->emptyStateHeading(__('Niciun istoric'))
->emptyStateDescription('Folosește „Check-in depozit" pe set pentru a înregistra prima depozitare.'); ->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 public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identitate') Schemas\Components\Section::make(__('Identitate'))
->columns(2) ->columns(2)
->schema([ ->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('email')->email()->required()->maxLength(120),
Forms\Components\TextInput::make('phone')->tel()->maxLength(40), Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
Forms\Components\Select::make('locale') Forms\Components\Select::make('locale')
->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English']) ->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English'])
->default('ro'), ->default('ro'),
]), ]),
Schemas\Components\Section::make('Acces') Schemas\Components\Section::make(__('Acces'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('role') Forms\Components\Select::make('role')
->label('Rol primar') ->label(__('Rol primar'))
->options(\App\Auth\Permissions::roleLabels()) ->options(\App\Auth\Permissions::roleLabels())
->required() ->required()
->default('mechanic') ->default('mechanic')
->helperText('Rolul principal — sincronizat automat cu drepturile RBAC.'), ->helperText(__('Rolul principal — sincronizat automat cu drepturile RBAC.')),
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
->options(['active' => 'Activ', 'inactive' => 'Inactiv', 'blocked' => 'Blocat']) ->options(['active' => 'Activ', 'inactive' => 'Inactiv', 'blocked' => 'Blocat'])
->default('active') ->default('active')
->required(), ->required(),
Forms\Components\TextInput::make('password') Forms\Components\TextInput::make('password')
->label('Parolă') ->label(__('Parolă'))
->password() ->password()
->required(fn (string $context) => $context === 'create') ->required(fn (string $context) => $context === 'create')
->dehydrated(fn ($state) => filled($state)) ->dehydrated(fn ($state) => filled($state))
->dehydrateStateUsing(fn ($state) => Hash::make($state)) ->dehydrateStateUsing(fn ($state) => Hash::make($state))
->minLength(6) ->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') Forms\Components\Select::make('roles_picked')
->label('Roluri suplimentare') ->label(__('Roluri suplimentare'))
->multiple() ->multiple()
->options(\App\Auth\Permissions::roleLabels()) ->options(\App\Auth\Permissions::roleLabels())
->afterStateHydrated(function ($component, $record) { ->afterStateHydrated(function ($component, $record) {
@@ -94,35 +94,35 @@ class UserResource extends Resource
}) })
->dehydrated(false) ->dehydrated(false)
->columnSpanFull() ->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ă') 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.') ->description(__('Configurează procentele pentru calcul salariu. Marja internă (nu TVA) se scade din prețul de manoperă pentru a determina baza salariului.'))
->columns(2) ->columns(2)
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false) ->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false)
->schema([ ->schema([
Forms\Components\TextInput::make('hourly_rate') Forms\Components\TextInput::make('hourly_rate')
->label('Tarif orar (MDL)') ->label(__('Tarif orar (MDL)'))
->numeric() ->numeric()
->step(0.01) ->step(0.01)
->placeholder('Ex: 100'), ->placeholder(__('Ex: 100')),
Forms\Components\TextInput::make('internal_margin_pct') Forms\Components\TextInput::make('internal_margin_pct')
->label('Marjă internă (%)') ->label(__('Marjă internă (%)'))
->numeric() ->numeric()
->step(0.01) ->step(0.01)
->minValue(0) ->minValue(0)
->maxValue(90) ->maxValue(90)
->placeholder('Ex: 20 pentru +20%') ->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.'), ->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) ->columns(2)
->schema([ ->schema([
Forms\Components\Placeholder::make('mfa_status') Forms\Components\Placeholder::make('mfa_status')
->label('Autentificare 2FA') ->label(__('Autentificare 2FA'))
->content(fn ($record) => $record && $record->hasTwoFactorEnabled() ? '✓ Activat (TOTP)' : '✗ Dezactivat'), ->content(fn ($record) => $record && $record->hasTwoFactorEnabled() ? '✓ Activat (TOTP)' : '✗ Dezactivat'),
Forms\Components\Placeholder::make('last_login') Forms\Components\Placeholder::make('last_login')
->label('Ultima autentificare') ->label(__('Ultima autentificare'))
->content(fn ($record) => $record?->last_login_at?->diffForHumans() ?? '—'), ->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) ->formatStateUsing(fn ($state) => \App\Auth\Permissions::roleLabels()[$state] ?? $state)
->badge(), ->badge(),
Tables\Columns\IconColumn::make('app_authentication_secret') Tables\Columns\IconColumn::make('app_authentication_secret')
->label('2FA') ->label(__('2FA'))
->boolean() ->boolean()
->getStateUsing(fn ($record) => $record->hasTwoFactorEnabled()) ->getStateUsing(fn ($record) => $record->hasTwoFactorEnabled())
->trueIcon('heroicon-o-shield-check') ->trueIcon('heroicon-o-shield-check')
@@ -147,14 +147,14 @@ class UserResource extends Resource
->falseIcon('heroicon-o-shield-exclamation') ->falseIcon('heroicon-o-shield-exclamation')
->falseColor('warning'), ->falseColor('warning'),
Tables\Columns\TextColumn::make('active_sessions') 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()) ->getStateUsing(fn ($record) => \Illuminate\Support\Facades\DB::table('sessions')->where('user_id', $record->id)->count())
->badge() ->badge()
->color(fn ($state) => $state > 0 ? 'success' : 'gray') ->color(fn ($state) => $state > 0 ? 'success' : 'gray')
->toggleable(), ->toggleable(),
Tables\Columns\TextColumn::make('permission_overrides_count') Tables\Columns\TextColumn::make('permission_overrides_count')
->counts('permissionOverrides') ->counts('permissionOverrides')
->label('Excepții') ->label(__('Excepții'))
->badge() ->badge()
->color('warning') ->color('warning')
->toggleable(isToggledHiddenByDefault: true), ->toggleable(isToggledHiddenByDefault: true),
@@ -180,7 +180,7 @@ class UserResource extends Resource
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\Action::make('force_logout') Actions\Action::make('force_logout')
->label('Force logout') ->label(__('Force logout'))
->icon('heroicon-o-arrow-right-on-rectangle') ->icon('heroicon-o-arrow-right-on-rectangle')
->color('warning') ->color('warning')
->visible(fn ($record) => \Illuminate\Support\Facades\DB::table('sessions')->where('user_id', $record->id)->exists()) ->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(); \Filament\Notifications\Notification::make()->title("$n sesiuni revoke-uite")->success()->send();
}), }),
Actions\Action::make('reset_2fa') Actions\Action::make('reset_2fa')
->label('Resetează 2FA') ->label(__('Resetează 2FA'))
->icon('heroicon-o-shield-exclamation') ->icon('heroicon-o-shield-exclamation')
->color('warning') ->color('warning')
->visible(fn ($record) => $record && $record->hasTwoFactorEnabled()) ->visible(fn ($record) => $record && $record->hasTwoFactorEnabled())
->requiresConfirmation() ->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) { ->action(function ($record) {
$record->saveAppAuthenticationSecret(null); $record->saveAppAuthenticationSecret(null);
$record->saveAppAuthenticationRecoveryCodes(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(), Actions\DeleteAction::make(),
]) ])
@@ -16,18 +16,23 @@ class PermissionOverridesRelationManager extends RelationManager
{ {
protected static string $relationship = 'permissionOverrides'; 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'; protected static string|\BackedEnum|null $icon = 'heroicon-o-shield-exclamation';
public function form(Schema $schema): Schema public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Excepție') Schemas\Components\Section::make(__('Excepție'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('permission_id') Forms\Components\Select::make('permission_id')
->label('Drept') ->label(__('Drept'))
->required() ->required()
->searchable() ->searchable()
->options(fn () => Permission::orderBy('name')->pluck('name', 'id')) ->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']) ->options(['grant' => 'GRANT — adaugă dreptul', 'deny' => 'DENY — interzice dreptul'])
->default('grant'), ->default('grant'),
Forms\Components\DatePicker::make('expires_at') Forms\Components\DatePicker::make('expires_at')
->label('Expiră la (opțional)') ->label(__('Expiră la (opțional)'))
->minDate(now()), ->minDate(now()),
Forms\Components\Textarea::make('reason') Forms\Components\Textarea::make('reason')
->label('Motiv') ->label(__('Motiv'))
->columnSpanFull() ->columnSpanFull()
->placeholder('Ex: lockdown temporar; acces pentru audit; etc.') ->placeholder(__('Ex: lockdown temporar; acces pentru audit; etc.'))
->rows(2), ->rows(2),
]), ]),
]); ]);
@@ -54,7 +59,7 @@ class PermissionOverridesRelationManager extends RelationManager
->recordTitleAttribute('mode') ->recordTitleAttribute('mode')
->columns([ ->columns([
Tables\Columns\TextColumn::make('permission.name') Tables\Columns\TextColumn::make('permission.name')
->label('Drept') ->label(__('Drept'))
->searchable() ->searchable()
->sortable(), ->sortable(),
Tables\Columns\TextColumn::make('mode') Tables\Columns\TextColumn::make('mode')
@@ -65,12 +70,12 @@ class PermissionOverridesRelationManager extends RelationManager
->limit(40) ->limit(40)
->placeholder('—'), ->placeholder('—'),
Tables\Columns\TextColumn::make('expires_at') Tables\Columns\TextColumn::make('expires_at')
->label('Expiră') ->label(__('Expiră'))
->date() ->date()
->placeholder('niciodată') ->placeholder(__('niciodată'))
->color(fn ($record) => $record?->isExpired() ? 'danger' : 'gray'), ->color(fn ($record) => $record?->isExpired() ? 'danger' : 'gray'),
Tables\Columns\TextColumn::make('grantedBy.name') Tables\Columns\TextColumn::make('grantedBy.name')
->label('Acordat de') ->label(__('Acordat de'))
->placeholder('—') ->placeholder('—')
->toggleable(), ->toggleable(),
]) ])
@@ -24,9 +24,19 @@ class VehicleResource extends Resource
return __('nav.label.Automobile'); 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; protected static ?int $navigationSort = 20;
@@ -51,21 +61,21 @@ class VehicleResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make('Identificare') Schemas\Components\Section::make(__('Identificare'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\Select::make('client_id') Forms\Components\Select::make('client_id')
->label('Proprietar') ->label(__('Proprietar'))
->options(fn () => Client::pluck('name', 'id')) ->options(fn () => Client::pluck('name', 'id'))
->searchable() ->searchable()
->required(), ->required(),
Forms\Components\TextInput::make('plate')->label('Nr. înmatriculare')->maxLength(16), Forms\Components\TextInput::make('plate')->label(__('Nr. înmatriculare'))->maxLength(16),
Forms\Components\TextInput::make('make')->label('Marca')->required()->maxLength(60), Forms\Components\TextInput::make('make')->label(__('Marca'))->required()->maxLength(60),
Forms\Components\TextInput::make('model')->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('year')->numeric()->minValue(1950)->maxValue(2100),
Forms\Components\TextInput::make('vin')->maxLength(32), Forms\Components\TextInput::make('vin')->maxLength(32),
]), ]),
Schemas\Components\Section::make('Tehnice') Schemas\Components\Section::make(__('Tehnice'))
->columns(2) ->columns(2)
->schema([ ->schema([
Forms\Components\TextInput::make('engine')->maxLength(60), Forms\Components\TextInput::make('engine')->maxLength(60),
@@ -76,13 +86,13 @@ class VehicleResource extends Resource
'EV' => 'Electric', 'GPL' => 'GPL', 'GNC' => 'GNC', 'EV' => 'Electric', 'GPL' => 'GPL', 'GNC' => 'GNC',
]), ]),
Forms\Components\Select::make('vehicle_class') Forms\Components\Select::make('vehicle_class')
->label('Clasă (pentru pricing)') ->label(__('Clasă (pentru pricing)'))
->options(\App\Models\Tenant\PricingCoefficient::VEHICLE_CLASSES) ->options(\App\Models\Tenant\PricingCoefficient::VEHICLE_CLASSES)
->helperText('Folosită de coeficienții de preț. Hibrid/EV se deduc și din combustibil.'), ->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('mileage')->label(__('Kilometraj'))->numeric()->default(0),
Forms\Components\TextInput::make('color')->maxLength(40), 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 return $table
->columns([ ->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('make')->sortable(),
Tables\Columns\TextColumn::make('model'), Tables\Columns\TextColumn::make('model'),
Tables\Columns\TextColumn::make('year'), 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('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(), Tables\Columns\TextColumn::make('created_at')->date()->sortable(),
]) ])
->actions([ ->actions([
Actions\Action::make('decode_vin') Actions\Action::make('decode_vin')
->label('Decode VIN') ->label(__('Decode VIN'))
->icon('heroicon-m-cpu-chip') ->icon('heroicon-m-cpu-chip')
->color('gray') ->color('gray')
->visible(fn (\App\Models\Tenant\Vehicle $r) => ! empty($r->vin) && strlen($r->vin) === 17) ->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]); return view('filament.tenant.vin-decode', ['info' => $info, 'vehicle' => $r]);
}), }),
Actions\Action::make('ai_recommend') Actions\Action::make('ai_recommend')
->label('AI: recomandări') ->label(__('AI: recomandări'))
->icon('heroicon-m-sparkles') ->icon('heroicon-m-sparkles')
->color('primary') ->color('primary')
->visible(fn (\App\Models\Tenant\Vehicle $r) => ! empty($r->vin)) ->visible(fn (\App\Models\Tenant\Vehicle $r) => ! empty($r->vin))
->modalHeading('Recomandări AI') ->modalHeading(__('Recomandări AI'))
->modalSubmitAction(false) ->modalSubmitAction(false)
->modalCancelActionLabel('Închide') ->modalCancelActionLabel('Închide')
->modalContent(function (\App\Models\Tenant\Vehicle $r) { ->modalContent(function (\App\Models\Tenant\Vehicle $r) {
@@ -128,8 +138,8 @@ class VehicleResource extends Resource
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Nicio mașină încă') ->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.') ->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') ->emptyStateIcon('heroicon-o-truck')
->defaultSort('created_at', 'desc'); ->defaultSort('created_at', 'desc');
} }
@@ -17,16 +17,16 @@ class ListVehicles extends ListRecords
{ {
return [ return [
Actions\Action::make('export') Actions\Action::make('export')
->label('Export CSV') ->label(__('Export CSV'))
->icon('heroicon-m-arrow-down-tray') ->icon('heroicon-m-arrow-down-tray')
->color('gray') ->color('gray')
->action(fn () => app(CsvImportExport::class)->exportVehicles()), ->action(fn () => app(CsvImportExport::class)->exportVehicles()),
Actions\Action::make('import') Actions\Action::make('import')
->label('Import CSV') ->label(__('Import CSV'))
->icon('heroicon-m-arrow-up-tray') ->icon('heroicon-m-arrow-up-tray')
->color('gray') ->color('gray')
->modalHeading('Import mașini din CSV') ->modalHeading(__('Import mașini din CSV'))
->modalDescription('CSV cu header: ' . implode(', ', CsvImportExport::VEHICLE_COLUMNS) . '. Coloana client_phone trebuie să existe deja la clienți.') ->modalDescription(__('CSV cu header: ' . implode(', ', CsvImportExport::VEHICLE_COLUMNS) . '. Coloana client_phone trebuie să existe deja la clienți.'))
->schema([ ->schema([
Forms\Components\FileUpload::make('file') Forms\Components\FileUpload::make('file')
->required() ->required()
@@ -38,11 +38,11 @@ class WarehouseResource extends Resource
{ {
return $schema->components([ return $schema->components([
Schemas\Components\Section::make()->columns(2)->schema([ Schemas\Components\Section::make()->columns(2)->schema([
Forms\Components\TextInput::make('code')->label('Cod')->required()->maxLength(32), Forms\Components\TextInput::make('code')->label(__('Cod'))->required()->maxLength(32),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->maxLength(120), Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->maxLength(120),
Forms\Components\TextInput::make('address')->label('Adresă')->columnSpanFull()->maxLength(200), Forms\Components\TextInput::make('address')->label(__('Adresă'))->columnSpanFull()->maxLength(200),
Forms\Components\Toggle::make('is_default')->label('Depozit implicit'), Forms\Components\Toggle::make('is_default')->label(__('Depozit implicit')),
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true), 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('code')->searchable()->sortable(),
Tables\Columns\TextColumn::make('name')->searchable()->sortable(), Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
Tables\Columns\TextColumn::make('address')->placeholder('—')->toggleable(), Tables\Columns\TextColumn::make('address')->placeholder('—')->toggleable(),
Tables\Columns\IconColumn::make('is_default')->label('Implicit')->boolean(), Tables\Columns\IconColumn::make('is_default')->label(__('Implicit'))->boolean(),
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(), Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
Tables\Columns\TextColumn::make('batches_count') Tables\Columns\TextColumn::make('batches_count')
->counts('batches') ->counts('batches')
->label('Loturi') ->label(__('Loturi'))
->alignRight(), ->alignRight(),
]) ])
->actions([ ->actions([
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Niciun depozit') ->emptyStateHeading(__('Niciun depozit'))
->emptyStateDescription('Un depozit implicit a fost creat la migrare. Adaugă altele dacă ai locații fizice separate (sucursală, hală, mobil).') ->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') ->emptyStateIcon('heroicon-o-building-storefront')
->defaultSort('code'); ->defaultSort('code');
} }
@@ -34,9 +34,19 @@ class WorkOrderResource extends Resource
return __('nav.group.Service'); 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; protected static ?int $navigationSort = 30;
@@ -63,76 +73,76 @@ class WorkOrderResource extends Resource
{ {
return $schema->components([ return $schema->components([
// Antet compact — o singură secțiune densă cu doar esențialul, mereu deschisă. // Antet compact — o singură secțiune densă cu doar esențialul, mereu deschisă.
Schemas\Components\Section::make('Antet fișă') Schemas\Components\Section::make(__('Antet fișă'))
->description('Informația de bază — click pe titlul secțiunilor de mai jos pentru detalii.') ->description(__('Informația de bază — click pe titlul secțiunilor de mai jos pentru detalii.'))
->compact() ->compact()
->columns(4) ->columns(4)
->schema([ ->schema([
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('auto'), 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\DatePicker::make('opened_at')->label(__('Deschis'))->default(today())->required(),
Forms\Components\Select::make('status')->options(WorkOrder::STATUSES)->default('new')->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(), ->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')) ->options(fn () => Client::pluck('name', 'id'))
->searchable()->live()->required()->columnSpan(2), ->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') ->options(fn (Get $get) => $get('client_id')
? Vehicle::where('client_id', $get('client_id'))->get() ? Vehicle::where('client_id', $get('client_id'))->get()
->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray() ->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray()
: []) : [])
->searchable()->columnSpan(2), ->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')) ->options(fn () => User::where('status', 'active')->pluck('name', 'id'))
->searchable()->columnSpan(2), ->searchable()->columnSpan(2),
Forms\Components\TextInput::make('mileage_in')->label('Km intrare')->numeric(), 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_out')->label(__('Km ieșire'))->numeric(),
]), ]),
// Toate secțiunile de detaliu — collapsible și collapsed by default // Toate secțiunile de detaliu — collapsible și collapsed by default
Schemas\Components\Section::make('Diagnostic') Schemas\Components\Section::make(__('Diagnostic'))
->collapsible()->collapsed()->compact() ->collapsible()->collapsed()->compact()
->schema([ ->schema([
Forms\Components\Textarea::make('complaint')->label('Plângere client')->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('diagnosis')->label(__('Diagnostic'))->rows(3)->columnSpanFull(),
Forms\Components\Textarea::make('recommendations')->label('Recomandări')->rows(2)->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() ->collapsible()->collapsed()->compact()
->schema([ ->schema([
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos') \Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos')
->label('Fotografii')->collection('photos') ->label(__('Fotografii'))->collection('photos')
->multiple()->reorderable()->image()->imageEditor()->maxFiles(20) ->multiple()->reorderable()->image()->imageEditor()->maxFiles(20)
->columnSpanFull(), ->columnSpanFull(),
]), ]),
Schemas\Components\Section::make('Tracking & ETA') Schemas\Components\Section::make(__('Tracking & ETA'))
->columns(3)->collapsible()->collapsed()->compact() ->columns(3)->collapsible()->collapsed()->compact()
->schema([ ->schema([
Forms\Components\DateTimePicker::make('eta_at')->label('Gata estimat (ETA)')->seconds(false), Forms\Components\DateTimePicker::make('eta_at')->label(__('Gata estimat (ETA)'))->seconds(false),
Forms\Components\TextInput::make('tracking_token')->label('Token public') Forms\Components\TextInput::make('tracking_token')->label(__('Token public'))
->disabled()->dehydrated(false)->columnSpan(2) ->disabled()->dehydrated(false)->columnSpan(2)
->helperText(fn (?WorkOrder $record) => $record?->tracking_token ->helperText(fn (?WorkOrder $record) => $record?->tracking_token
? 'Link client: ' . $record->trackingUrl() ? 'Link client: ' . $record->trackingUrl()
: 'Se generează la salvare'), : 'Se generează la salvare'),
]), ]),
Schemas\Components\Section::make('Plată & total') Schemas\Components\Section::make(__('Plată & total'))
->columns(3)->collapsible()->collapsed()->compact() ->columns(3)->collapsible()->collapsed()->compact()
->schema([ ->schema([
Forms\Components\Select::make('pay_status')->options(WorkOrder::PAY_STATUSES)->default('unpaid')->required(), 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') Forms\Components\Toggle::make('apply_margin')
->label('Aplică marjă internă') ->label(__('Aplică marjă internă'))
->default(true) ->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), ->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) ->numeric()->step(0.01)->minValue(0)->maxValue(90)
->placeholder('Ex: 25') ->placeholder(__('Ex: 25'))
->helperText('Doar pentru cazuri speciale. Lasă gol → folosește marja mecanicului.') ->helperText(__('Doar pentru cazuri speciale. Lasă gol → folosește marja mecanicului.'))
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false), ->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\TextInput::make('total')->label(__('Total'))->numeric()->disabled()->dehydrated(false),
Forms\Components\Toggle::make('approved')->label('Aprobat de client'), Forms\Components\Toggle::make('approved')->label(__('Aprobat de client')),
Forms\Components\DatePicker::make('closed_at')->label('Închis'), Forms\Components\DatePicker::make('closed_at')->label(__('Închis')),
]), ]),
]); ]);
} }
@@ -141,11 +151,11 @@ class WorkOrderResource extends Resource
{ {
return $table return $table
->columns([ ->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(), 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('opened_at')->label(__('Deschis'))->date('d.m.Y')->sortable(),
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable(), Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'), Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
Tables\Columns\TextColumn::make('master.name')->label('Maistru')->placeholder('—'), Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($state) => WorkOrder::STATUSES[$state] ?? $state) ->formatStateUsing(fn ($state) => WorkOrder::STATUSES[$state] ?? $state)
->badge() ->badge()
@@ -170,12 +180,12 @@ class WorkOrderResource extends Resource
Tables\Filters\SelectFilter::make('status')->options(WorkOrder::STATUSES), Tables\Filters\SelectFilter::make('status')->options(WorkOrder::STATUSES),
Tables\Filters\SelectFilter::make('pay_status')->options(WorkOrder::PAY_STATUSES), Tables\Filters\SelectFilter::make('pay_status')->options(WorkOrder::PAY_STATUSES),
Tables\Filters\SelectFilter::make('master_id') Tables\Filters\SelectFilter::make('master_id')
->label('Maistru') ->label(__('Maistru'))
->options(fn () => User::pluck('name', 'id')), ->options(fn () => User::pluck('name', 'id')),
]) ])
->actions([ ->actions([
Actions\Action::make('pdf') Actions\Action::make('pdf')
->label('PDF') ->label(__('PDF'))
->icon('heroicon-m-document-arrow-down') ->icon('heroicon-m-document-arrow-down')
->color('gray') ->color('gray')
->action(function (WorkOrder $r) { ->action(function (WorkOrder $r) {
@@ -190,8 +200,8 @@ class WorkOrderResource extends Resource
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
->emptyStateHeading('Nicio fișă de lucru') ->emptyStateHeading(__('Nicio fișă de lucru'))
->emptyStateDescription('Crează prima fișă pentru o mașină existentă. Adaugă manopere, piese, plăți — totalul se calculează automat.') ->emptyStateDescription(__('Crează prima fișă pentru o mașină existentă. Adaugă manopere, piese, plăți — totalul se calculează automat.'))
->emptyStateIcon('heroicon-o-wrench-screwdriver') ->emptyStateIcon('heroicon-o-wrench-screwdriver')
->defaultSort('opened_at', 'desc'); ->defaultSort('opened_at', 'desc');
} }
@@ -33,12 +33,12 @@ class EditWorkOrder extends EditRecord
{ {
return [ return [
Actions\Action::make('apply_template') Actions\Action::make('apply_template')
->label('Aplică șablon') ->label(__('Aplică șablon'))
->icon('heroicon-m-clipboard-document-list') ->icon('heroicon-m-clipboard-document-list')
->color('gray') ->color('gray')
->schema([ ->schema([
\Filament\Forms\Components\Select::make('template_id') \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')) ->options(fn () => \App\Models\Tenant\ServiceTemplate::where('is_active', true)->pluck('name', 'id'))
->searchable() ->searchable()
->required(), ->required(),
@@ -53,11 +53,11 @@ class EditWorkOrder extends EditRecord
->success()->send(); ->success()->send();
}), }),
Actions\Action::make('ai_diagnose') Actions\Action::make('ai_diagnose')
->label('AI: sugerează diagnostic') ->label(__('AI: sugerează diagnostic'))
->icon('heroicon-m-sparkles') ->icon('heroicon-m-sparkles')
->color('primary') ->color('primary')
->visible(fn () => ! empty($this->record->complaint)) ->visible(fn () => ! empty($this->record->complaint))
->modalHeading('Diagnostic AI bazat pe plângerea clientului') ->modalHeading(__('Diagnostic AI bazat pe plângerea clientului'))
->modalSubmitAction(false) ->modalSubmitAction(false)
->modalCancelActionLabel('Închide') ->modalCancelActionLabel('Închide')
->modalContent(function () { ->modalContent(function () {
@@ -66,7 +66,7 @@ class EditWorkOrder extends EditRecord
return view('filament.tenant.ai-reply', ['reply' => $reply, 'meta' => $meta]); return view('filament.tenant.ai-reply', ['reply' => $reply, 'meta' => $meta]);
}), }),
Actions\Action::make('tracking') Actions\Action::make('tracking')
->label('Link client (QR)') ->label(__('Link client (QR)'))
->icon('heroicon-m-qr-code') ->icon('heroicon-m-qr-code')
->color('primary') ->color('primary')
->modalHeading(fn () => 'Tracking client — WO #' . $this->record->number) ->modalHeading(fn () => 'Tracking client — WO #' . $this->record->number)
@@ -76,7 +76,7 @@ class EditWorkOrder extends EditRecord
'wo' => $this->record, 'wo' => $this->record,
])), ])),
Actions\Action::make('pdf') Actions\Action::make('pdf')
->label('Descarcă PDF') ->label(__('Descarcă PDF'))
->icon('heroicon-m-document-arrow-down') ->icon('heroicon-m-document-arrow-down')
->color('gray') ->color('gray')
->action(function () { ->action(function () {
@@ -19,13 +19,18 @@ class PartsRelationManager extends RelationManager
{ {
protected static string $relationship = 'parts'; 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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\Select::make('part_id') 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) ->options(fn () => Part::where('is_active', true)
->get() ->get()
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}] " : '') . "(stoc: {$p->qty})"]) ->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}] " : '') . "(stoc: {$p->qty})"])
@@ -43,20 +48,20 @@ class PartsRelationManager extends RelationManager
} }
}) })
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(), Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
Forms\Components\TextInput::make('article')->label('Cod articol')->maxLength(64), Forms\Components\TextInput::make('article')->label(__('Cod articol'))->maxLength(64),
Forms\Components\TextInput::make('brand')->maxLength(64), Forms\Components\TextInput::make('brand')->maxLength(64),
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(), 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('unit')->label(__('UM'))->maxLength(16)->default('buc'),
Forms\Components\TextInput::make('buy_price')->label('Preț achiziție')->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()->required(), 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('discount_pct')->label(__('Discount %'))->numeric()->default(0),
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
->options(WorkOrderPart::STATUSES) ->options(WorkOrderPart::STATUSES)
->default('needed') ->default('needed')
->required() ->required()
->helperText('La trecere pe „Montată" se scade automat din stoc.'), ->helperText(__('La trecere pe „Montată" se scade automat din stoc.')),
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2), Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
]); ]);
} }
@@ -65,11 +70,11 @@ class PartsRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('name') ->recordTitleAttribute('name')
->columns([ ->columns([
Tables\Columns\TextColumn::make('name')->label('Piesă')->wrap(), Tables\Columns\TextColumn::make('name')->label(__('Piesă'))->wrap(),
Tables\Columns\TextColumn::make('article')->label('Cod')->placeholder('—'), Tables\Columns\TextColumn::make('article')->label(__('Cod'))->placeholder('—'),
Tables\Columns\TextColumn::make('brand')->placeholder('—'), Tables\Columns\TextColumn::make('brand')->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(), Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
Tables\Columns\TextColumn::make('sell_price')->label('Preț')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('sell_price')->label(__('Preț'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(), Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => WorkOrderPart::STATUSES[$s] ?? $s) ->formatStateUsing(fn ($s) => WorkOrderPart::STATUSES[$s] ?? $s)
@@ -86,11 +91,11 @@ class PartsRelationManager extends RelationManager
]) ])
->actions([ ->actions([
Actions\Action::make('smart_price') Actions\Action::make('smart_price')
->label('Preț inteligent') ->label(__('Preț inteligent'))
->icon('heroicon-m-sparkles') ->icon('heroicon-m-sparkles')
->color('primary') ->color('primary')
->visible(fn (WorkOrderPart $r) => (bool) $r->part_id) ->visible(fn (WorkOrderPart $r) => (bool) $r->part_id)
->modalHeading('Preț contextual') ->modalHeading(__('Preț contextual'))
->modalSubmitActionLabel('Aplică prețul') ->modalSubmitActionLabel('Aplică prețul')
->modalContent(function (WorkOrderPart $r) { ->modalContent(function (WorkOrderPart $r) {
$wo = $r->workOrder; $wo = $r->workOrder;
@@ -112,7 +117,7 @@ class PartsRelationManager extends RelationManager
->success()->send(); ->success()->send();
}), }),
Actions\Action::make('issue_now') Actions\Action::make('issue_now')
->label('Eliberează') ->label(__('Eliberează'))
->icon('heroicon-m-arrow-up-on-square') ->icon('heroicon-m-arrow-up-on-square')
->color('warning') ->color('warning')
->visible(fn (WorkOrderPart $r) => $r->part_id ->visible(fn (WorkOrderPart $r) => $r->part_id
@@ -128,7 +133,7 @@ class PartsRelationManager extends RelationManager
->success()->send(); ->success()->send();
}), }),
Actions\Action::make('return_part') Actions\Action::make('return_part')
->label('Restituire') ->label(__('Restituire'))
->icon('heroicon-m-arrow-uturn-left') ->icon('heroicon-m-arrow-uturn-left')
->color('gray') ->color('gray')
->visible(fn (WorkOrderPart $r) => $r->part_id ->visible(fn (WorkOrderPart $r) => $r->part_id
@@ -137,12 +142,12 @@ class PartsRelationManager extends RelationManager
->exists()) ->exists())
->schema([ ->schema([
Forms\Components\TextInput::make('qty') Forms\Components\TextInput::make('qty')
->label('Cantitate restituită') ->label(__('Cantitate restituită'))
->numeric() ->numeric()
->required() ->required()
->minValue(0.001) ->minValue(0.001)
->default(fn (WorkOrderPart $r) => (float) $r->qty), ->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) { ->action(function (WorkOrderPart $r, array $data) {
$batch = app(WarehouseService::class)->returnPart( $batch = app(WarehouseService::class)->returnPart(
@@ -14,19 +14,24 @@ class PaymentsRelationManager extends RelationManager
{ {
protected static string $relationship = 'payments'; 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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\DatePicker::make('paid_at')->label('Data')->default(today())->required(), Forms\Components\DatePicker::make('paid_at')->label(__('Data'))->default(today())->required(),
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(), Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
Forms\Components\Select::make('method') Forms\Components\Select::make('method')
->options(Payment::METHODS) ->options(Payment::METHODS)
->default('cash') ->default('cash')
->required(), ->required(),
Forms\Components\TextInput::make('reference')->label('Referință')->maxLength(64), Forms\Components\TextInput::make('reference')->label(__('Referință'))->maxLength(64),
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2), Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
]); ]);
} }
@@ -35,9 +40,9 @@ class PaymentsRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('amount') ->recordTitleAttribute('amount')
->columns([ ->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() 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') Tables\Columns\TextColumn::make('method')
->formatStateUsing(fn ($s) => Payment::METHODS[$s] ?? $s) ->formatStateUsing(fn ($s) => Payment::METHODS[$s] ?? $s)
->badge(), ->badge(),
@@ -15,27 +15,32 @@ class SubcontractJobsRelationManager extends RelationManager
{ {
protected static string $relationship = 'subcontractJobs'; 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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\Select::make('subcontractor_id') Forms\Components\Select::make('subcontractor_id')
->label('Subcontractor') ->label(__('Subcontractor'))
->options(fn () => Subcontractor::where('is_active', true)->pluck('name', 'id')) ->options(fn () => Subcontractor::where('is_active', true)->pluck('name', 'id'))
->searchable() ->searchable()
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\Select::make('category') Forms\Components\Select::make('category')
->label('Categorie') ->label(__('Categorie'))
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES)) ->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
->searchable(), ->searchable(),
Forms\Components\Select::make('status')->options(SubcontractJob::STATUSES)->default('sent')->required(), Forms\Components\Select::make('status')->options(SubcontractJob::STATUSES)->default('sent')->required(),
Forms\Components\Textarea::make('description')->label('Descriere')->rows(2)->columnSpanFull(), 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('cost')->label(__('Cost (terț)'))->numeric()->default(0)->required(),
Forms\Components\TextInput::make('markup_pct')->label('Markup %')->numeric()->default(0), Forms\Components\TextInput::make('markup_pct')->label(__('Markup %'))->numeric()->default(0),
Forms\Components\TextInput::make('client_price')->label('Preț client')->numeric()->default(0) Forms\Components\TextInput::make('client_price')->label(__('Preț client'))->numeric()->default(0)
->helperText('Folosit dacă markup = 0.'), ->helperText(__('Folosit dacă markup = 0.')),
Forms\Components\DatePicker::make('eta')->label('ETA'), Forms\Components\DatePicker::make('eta')->label(__('ETA')),
]); ]);
} }
@@ -44,13 +49,13 @@ class SubcontractJobsRelationManager extends RelationManager
return $table return $table
->recordTitleAttribute('number') ->recordTitleAttribute('number')
->columns([ ->columns([
Tables\Columns\TextColumn::make('number')->label('Nr.'), Tables\Columns\TextColumn::make('number')->label(__('Nr.')),
Tables\Columns\TextColumn::make('subcontractor.name')->label('Terț')->placeholder('—'), Tables\Columns\TextColumn::make('subcontractor.name')->label(__('Terț'))->placeholder('—'),
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'), Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
Tables\Columns\TextColumn::make('cost')->money('MDL')->alignRight(), 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') Tables\Columns\TextColumn::make('margin')
->label('Marjă') ->label(__('Marjă'))
->state(fn (SubcontractJob $r) => $r->margin()) ->state(fn (SubcontractJob $r) => $r->margin())
->money('MDL')->alignRight() ->money('MDL')->alignRight()
->color(fn ($s) => (float) $s > 0 ? 'success' : ((float) $s < 0 ? 'danger' : 'gray')), ->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 $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 public function form(Schema $schema): Schema
{ {
return $schema->components([ return $schema->components([
Forms\Components\Select::make('labor_id') Forms\Components\Select::make('labor_id')
->label('Catalog manoperă') ->label(__('Catalog manoperă'))
->options(fn () => Labor::where('is_active', true) ->options(fn () => Labor::where('is_active', true)
->get() ->get()
->mapWithKeys(fn ($l) => [$l->id => "[{$l->category}] {$l->name_ro} ({$l->hours}h)"]) ->mapWithKeys(fn ($l) => [$l->id => "[{$l->category}] {$l->name_ro} ({$l->hours}h)"])
@@ -38,18 +43,18 @@ class WorksRelationManager extends RelationManager
} }
}) })
->columnSpanFull(), ->columnSpanFull(),
Forms\Components\TextInput::make('name')->label('Nume')->required()->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('hours')->label(__('Ore'))->numeric()->default(1)->required(),
Forms\Components\TextInput::make('price_per_hour')->label('Preț/h')->numeric()->required(), Forms\Components\TextInput::make('price_per_hour')->label(__('Preț/h'))->numeric()->required(),
Forms\Components\Select::make('master_id') Forms\Components\Select::make('master_id')
->label('Maistru') ->label(__('Maistru'))
->options(fn () => User::pluck('name', 'id')) ->options(fn () => User::pluck('name', 'id'))
->searchable(), ->searchable(),
Forms\Components\Select::make('status') Forms\Components\Select::make('status')
->options(WorkOrderWork::STATUSES) ->options(WorkOrderWork::STATUSES)
->default('todo') ->default('todo')
->required(), ->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 return $table
->recordTitleAttribute('name') ->recordTitleAttribute('name')
->columns([ ->columns([
Tables\Columns\TextColumn::make('name')->label('Manoperă')->wrap(), Tables\Columns\TextColumn::make('name')->label(__('Manoperă'))->wrap(),
Tables\Columns\TextColumn::make('hours')->label('Ore')->numeric(decimalPlaces: 2)->alignRight(), 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('price_per_hour')->label(__('Preț/h'))->money('MDL')->alignRight(),
Tables\Columns\TextColumn::make('total') Tables\Columns\TextColumn::make('total')
->label('Total') ->label(__('Total'))
->money('MDL') ->money('MDL')
->alignRight() ->alignRight()
->description(function ($record) { ->description(function ($record) {
@@ -82,7 +87,7 @@ class WorksRelationManager extends RelationManager
if ((float) $record->applied_margin_pct === 0.0) return null; 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'), '.') . '%'; 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') Tables\Columns\TextColumn::make('status')
->formatStateUsing(fn ($s) => WorkOrderWork::STATUSES[$s] ?? $s) ->formatStateUsing(fn ($s) => WorkOrderWork::STATUSES[$s] ?? $s)
->badge() ->badge()
@@ -25,13 +25,13 @@ class LowStockTable extends BaseWidget
->orderBy('qty') ->orderBy('qty')
) )
->columns([ ->columns([
Tables\Columns\TextColumn::make('name')->label('Piesă')->wrap()->searchable(), Tables\Columns\TextColumn::make('name')->label(__('Piesă'))->wrap()->searchable(),
Tables\Columns\TextColumn::make('article')->label('Cod')->placeholder('—'), Tables\Columns\TextColumn::make('article')->label(__('Cod'))->placeholder('—'),
Tables\Columns\TextColumn::make('qty')->label('Stoc')->alignRight() Tables\Columns\TextColumn::make('qty')->label(__('Stoc'))->alignRight()
->color(fn ($state) => $state <= 0 ? 'danger' : 'warning') ->color(fn ($state) => $state <= 0 ? 'danger' : 'warning')
->weight('bold'), ->weight('bold'),
Tables\Columns\TextColumn::make('min_qty')->label('Min.')->alignRight(), Tables\Columns\TextColumn::make('min_qty')->label(__('Min.'))->alignRight(),
Tables\Columns\TextColumn::make('preferredSupplier.name')->label('Furnizor')->placeholder('—'), Tables\Columns\TextColumn::make('preferredSupplier.name')->label(__('Furnizor'))->placeholder('—'),
]) ])
->paginated(false) ->paginated(false)
->defaultSort('qty'); ->defaultSort('qty');
@@ -22,22 +22,22 @@ class StatsOverview extends BaseWidget
return [ return [
Stat::make('Clienți', Client::count()) Stat::make('Clienți', Client::count())
->description('Total în baza de date') ->description(__('Total în baza de date'))
->icon('heroicon-o-users') ->icon('heroicon-o-users')
->color('primary'), ->color('primary'),
Stat::make('Mașini', Vehicle::count()) Stat::make('Mașini', Vehicle::count())
->description('Total înregistrate') ->description(__('Total înregistrate'))
->icon('heroicon-o-truck') ->icon('heroicon-o-truck')
->color('info'), ->color('info'),
Stat::make('Cereri noi', $newLeads) Stat::make('Cereri noi', $newLeads)
->description('De procesat') ->description(__('De procesat'))
->icon('heroicon-o-inbox-arrow-down') ->icon('heroicon-o-inbox-arrow-down')
->color($newLeads > 0 ? 'warning' : 'success'), ->color($newLeads > 0 ? 'warning' : 'success'),
Stat::make('Deal-uri active', $openDeals) Stat::make('Deal-uri active', $openDeals)
->description('În pipeline') ->description(__('În pipeline'))
->icon('heroicon-o-funnel') ->icon('heroicon-o-funnel')
->color('primary'), ->color('primary'),
+1419 -71
View File
File diff suppressed because it is too large Load Diff
+1419 -71
View File
File diff suppressed because it is too large Load Diff