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}); } } }