272 lines
13 KiB
PHP
272 lines
13 KiB
PHP
<?php
|
||
|
||
namespace App\Filament\Resources;
|
||
|
||
use App\Filament\Resources\OpzioneResource\Pages;
|
||
use App\Filament\Support\CampiConfiguratore;
|
||
use App\Models\GruppoOpzioni;
|
||
use App\Models\Modello;
|
||
use App\Models\Opzione;
|
||
use Filament\Forms;
|
||
use Filament\Forms\Form;
|
||
use Filament\Forms\Get;
|
||
use Filament\Forms\Set;
|
||
use Filament\Resources\Resource;
|
||
use Filament\Tables;
|
||
use Filament\Tables\Table;
|
||
use Illuminate\Database\Eloquent\Builder;
|
||
use Illuminate\Support\Collection;
|
||
use Illuminate\Validation\Rules\Unique;
|
||
use App\Filament\Concerns\SoloSuperadmin;
|
||
|
||
/** Opzioni e supplementi del sistema (le personalizzazioni per modello sono nella scheda del modello). */
|
||
|
||
class OpzioneResource extends Resource
|
||
{
|
||
use SoloSuperadmin;
|
||
|
||
/** Voce "Altro" del valore nelle opzioni personalizzate: valore scritto a mano. */
|
||
public const ALTRO = '__altro';
|
||
|
||
protected static ?string $model = Opzione::class;
|
||
|
||
protected static ?string $navigationIcon = 'heroicon-o-adjustments-horizontal';
|
||
|
||
protected static ?string $slug = 'opzioni';
|
||
|
||
protected static ?string $navigationGroup = 'Configuratore';
|
||
|
||
protected static ?string $navigationLabel = 'Opzioni e supplementi';
|
||
|
||
protected static ?string $modelLabel = 'opzione';
|
||
|
||
protected static ?string $pluralModelLabel = 'opzioni';
|
||
|
||
protected static ?int $navigationSort = 23;
|
||
|
||
public static function getEloquentQuery(): Builder
|
||
{
|
||
return parent::getEloquentQuery()->whereNull('modello_id');
|
||
}
|
||
|
||
public static function form(Form $form): Form
|
||
{
|
||
return $form->schema(self::campi(conSistema: true));
|
||
}
|
||
|
||
/**
|
||
* Campi dell'opzione; senza sistema (relation manager del modello) l'unicità è sul modello
|
||
* e il valore si sceglie tra quelli del gruppo sul sistema, o "Altro" per scriverlo a mano.
|
||
*
|
||
* @param \Closure(): Modello|null $modello
|
||
*/
|
||
public static function campi(bool $conSistema, ?\Closure $modello = null): array
|
||
{
|
||
$unicita = fn (Unique $rule, Get $get) => self::unicita($rule, $get, $modello);
|
||
$finitura = fn (Get $get) => $get('gruppo') === 'tipo_finitura';
|
||
|
||
return [
|
||
Forms\Components\Section::make()
|
||
->columns(2)
|
||
->schema(array_values(array_filter([
|
||
$conSistema ? CampiConfiguratore::sistema() : null,
|
||
Forms\Components\Select::make('gruppo')
|
||
->label('Gruppo')
|
||
->options(fn () => GruppoOpzioni::etichette())
|
||
->required()
|
||
->live()
|
||
->afterStateUpdated(function (Set $set) {
|
||
$set('valore', null);
|
||
$set('valore_altro', null);
|
||
}),
|
||
$modello ? null : Forms\Components\Select::make('valore')
|
||
->label('Valore')
|
||
->options(Opzione::TIPI_FINITURA)
|
||
->helperText('Il tipo finitura è calcolato dalle finiture interna ed esterna scelte.')
|
||
->visible($finitura)
|
||
->required()
|
||
->unique(ignoreRecord: true, modifyRuleUsing: $unicita),
|
||
$modello ? null : Forms\Components\TextInput::make('valore')
|
||
->label('Valore')
|
||
->helperText('Identificativo inviato dal configuratore (es. dx, telaio_l).')
|
||
->hidden($finitura)
|
||
->required()
|
||
->alphaDash()
|
||
->maxLength(100)
|
||
->unique(ignoreRecord: true, modifyRuleUsing: $unicita),
|
||
$modello ? Forms\Components\Select::make('valore')
|
||
->label('Valore')
|
||
->helperText(fn (Get $get) => $finitura($get)
|
||
? 'Il tipo finitura è calcolato dalle finiture interna ed esterna scelte.'
|
||
: 'Valori del gruppo definiti sul sistema: sceglierne uno ne precompila i dati. "Altro" per un valore nuovo.')
|
||
// Un solo campo per tutti i gruppi: con due campi "valore" Filament aggiorna solo il primo.
|
||
->options(fn (Get $get) => $finitura($get)
|
||
? Opzione::TIPI_FINITURA
|
||
: self::catalogo($modello(), $get('gruppo'))
|
||
->mapWithKeys(fn (Opzione $o) => [$o->valore => "{$o->etichetta} ({$o->valore})"])
|
||
->put(self::ALTRO, 'Altro…')
|
||
->all())
|
||
->required()
|
||
->live()
|
||
->unique(ignoreRecord: true, modifyRuleUsing: $unicita)
|
||
// Valore non presente sul sistema: in modifica compare come "Altro".
|
||
->afterStateHydrated(function (Forms\Components\Select $component, ?string $state, Get $get, Set $set) use ($modello) {
|
||
if (filled($state) && $get('gruppo') !== 'tipo_finitura'
|
||
&& ! self::catalogo($modello(), $get('gruppo'))->contains('valore', $state)) {
|
||
$component->state(self::ALTRO);
|
||
$set('valore_altro', $state);
|
||
}
|
||
})
|
||
->afterStateUpdated(fn (?string $state, Get $get, Set $set) => self::precompila($modello(), $get('gruppo'), $state, $set))
|
||
->dehydrateStateUsing(fn (?string $state, Get $get) => $state === self::ALTRO ? $get('valore_altro') : $state) : null,
|
||
$modello ? Forms\Components\TextInput::make('valore_altro')
|
||
->label('Nuovo valore')
|
||
->helperText('Identificativo inviato dal configuratore (es. dx, telaio_l).')
|
||
->visible(fn (Get $get) => $get('valore') === self::ALTRO)
|
||
->required()
|
||
->alphaDash()
|
||
->maxLength(100)
|
||
->notIn([self::ALTRO])
|
||
->unique(table: Opzione::class, column: 'valore', ignoreRecord: true, modifyRuleUsing: $unicita)
|
||
->dehydrated(false) : null,
|
||
Forms\Components\TextInput::make('etichetta')->label('Etichetta')->required()->maxLength(255),
|
||
Forms\Components\TextInput::make('alternativa')
|
||
->label('Alternativa a')
|
||
->helperText('Le opzioni del gruppo con lo stesso valore si escludono a vicenda (es. "coprifili": piatti o scatolati).')
|
||
->maxLength(50)
|
||
->visible(fn (Get $get) => GruppoOpzioni::where('codice', $get('gruppo'))->value('ui') === 'multipla'),
|
||
Forms\Components\Toggle::make('attivo')->label('Attiva')->default(true)->inline(false),
|
||
]))),
|
||
Forms\Components\Section::make('Supplemento')
|
||
->columns(2)
|
||
->schema([
|
||
Forms\Components\Select::make('supplemento_tipo')
|
||
->label('Tipo')
|
||
->options(Opzione::SUPPLEMENTI)
|
||
->placeholder('Nessun supplemento')
|
||
->live(),
|
||
Forms\Components\TextInput::make('supplemento')
|
||
->label('Valore')
|
||
->numeric()
|
||
->step(0.01)
|
||
->default(0)
|
||
->required()
|
||
->prefix(fn (Get $get) => in_array($get('supplemento_tipo'), ['fisso', 'metro_lineare'], true) ? '€' : null)
|
||
->suffix(fn (Get $get) => match ($get('supplemento_tipo')) {
|
||
'percentuale' => '%',
|
||
'metro_lineare' => '/ml',
|
||
default => null,
|
||
})
|
||
->visible(fn (Get $get) => filled($get('supplemento_tipo'))),
|
||
Forms\Components\Fieldset::make('Metri lineari')
|
||
->columns(3)
|
||
->visible(fn (Get $get) => $get('supplemento_tipo') === 'metro_lineare')
|
||
->schema([
|
||
Forms\Components\Placeholder::make('formula_ml')
|
||
->hiddenLabel()
|
||
->content('ml = (L × moltiplicatore L + H × moltiplicatore H + aggiunta) ÷ 1000, con L e H del serramento in mm. Es. "H + 100 mm, 2 pezzi": moltiplicatore H 2, aggiunta 200.')
|
||
->columnSpanFull(),
|
||
Forms\Components\TextInput::make('ml_per_larghezza')->label('Moltiplicatore L')->numeric()->step(0.001)->default(0)->required(),
|
||
Forms\Components\TextInput::make('ml_per_altezza')->label('Moltiplicatore H')->numeric()->step(0.001)->default(0)->required(),
|
||
Forms\Components\TextInput::make('ml_aggiunta_mm')->label('Aggiunta')->numeric()->step(1)->suffix('mm')->default(0)->required(),
|
||
]),
|
||
]),
|
||
Forms\Components\Section::make('Configuratore')
|
||
->columns(2)
|
||
->collapsible()
|
||
->hidden(fn (Get $get) => $get('gruppo') === 'tipo_finitura')
|
||
->schema([
|
||
CampiConfiguratore::immagine('icona', 'Icona', 'opzioni'),
|
||
Forms\Components\KeyValue::make('parametri')
|
||
->label('Parametri')
|
||
->helperText('Letti dal configuratore: scale_x (1 / -1) per l\'apertura, func e categoria per le select.')
|
||
->keyLabel('Nome')
|
||
->valueLabel('Valore'),
|
||
]),
|
||
];
|
||
}
|
||
|
||
public static function table(Table $table): Table
|
||
{
|
||
return self::tabella($table)
|
||
->filters([
|
||
Tables\Filters\SelectFilter::make('sistema')->label('Sistema')->relationship('sistema', 'nome'),
|
||
Tables\Filters\SelectFilter::make('gruppo')->label('Gruppo')->options(fn () => GruppoOpzioni::etichette()),
|
||
]);
|
||
}
|
||
|
||
/** Tabella condivisa con le opzioni personalizzate del modello. */
|
||
public static function tabella(Table $table): Table
|
||
{
|
||
return $table
|
||
->defaultSort('ordine')
|
||
->reorderable('ordine')
|
||
->defaultGroup('gruppo')
|
||
->groups([
|
||
Tables\Grouping\Group::make('gruppo')
|
||
->label('Gruppo')
|
||
->getTitleFromRecordUsing(fn (Opzione $record) => $record->definizione?->etichetta ?? $record->gruppo),
|
||
])
|
||
->columns([
|
||
Tables\Columns\ImageColumn::make('icona')->label('Icona')->disk('public')->height(40),
|
||
Tables\Columns\TextColumn::make('etichetta')->label('Etichetta')->searchable(),
|
||
Tables\Columns\TextColumn::make('valore')->label('Valore')->searchable()->color('gray'),
|
||
Tables\Columns\TextColumn::make('alternativa')->label('Alternativa')->badge()->color('gray')->placeholder('—')->toggleable(),
|
||
Tables\Columns\TextColumn::make('supplemento')
|
||
->label('Supplemento')
|
||
->state(fn (Opzione $record) => $record->descriviSupplemento()),
|
||
Tables\Columns\ToggleColumn::make('attivo')->label('Attiva'),
|
||
])
|
||
->actions([
|
||
Tables\Actions\EditAction::make()->iconButton(),
|
||
Tables\Actions\DeleteAction::make()->iconButton(),
|
||
])
|
||
->bulkActions([
|
||
Tables\Actions\BulkActionGroup::make([
|
||
Tables\Actions\DeleteBulkAction::make(),
|
||
]),
|
||
]);
|
||
}
|
||
|
||
public static function getPages(): array
|
||
{
|
||
return [
|
||
'index' => Pages\ManageOpzioni::route('/'),
|
||
];
|
||
}
|
||
|
||
private static function unicita(Unique $rule, Get $get, ?\Closure $modello): Unique
|
||
{
|
||
$rule->where('gruppo', $get('gruppo'));
|
||
|
||
return $modello
|
||
? $rule->where('modello_id', $modello()->getKey())
|
||
: $rule->where('sistema_id', $get('sistema_id'))->whereNull('modello_id');
|
||
}
|
||
|
||
/** Opzioni del gruppo definite sul sistema del modello. */
|
||
public static function catalogo(Modello $modello, ?string $gruppo): Collection
|
||
{
|
||
return Opzione::query()
|
||
->where('sistema_id', $modello->sistema_id)
|
||
->whereNull('modello_id')
|
||
->where('gruppo', $gruppo)
|
||
->orderBy('ordine')
|
||
->get();
|
||
}
|
||
|
||
/** Copia nel form i dati dell'opzione del sistema scelta come valore. */
|
||
private static function precompila(Modello $modello, ?string $gruppo, ?string $valore, Set $set): void
|
||
{
|
||
$set('valore_altro', null);
|
||
$origine = self::catalogo($modello, $gruppo)->firstWhere('valore', $valore);
|
||
if (! $origine) {
|
||
return;
|
||
}
|
||
|
||
foreach (['etichetta', 'alternativa', 'supplemento_tipo', 'supplemento', 'ml_per_larghezza', 'ml_per_altezza', 'ml_aggiunta_mm', 'parametri'] as $campo) {
|
||
$set($campo, $origine->{$campo});
|
||
}
|
||
}
|
||
}
|