Files

272 lines
13 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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});
}
}
}