schema([ Forms\Components\Section::make('Accesso') ->description('Credenziali per l\'accesso del cliente. Generate automaticamente, ma modificabili.') ->columns(2) ->schema([ Forms\Components\TextInput::make('username') ->label('Nome utente') ->required() ->maxLength(50) ->unique(ignoreRecord: true) ->default(fn (string $operation) => $operation === 'create' ? ClienteCredenziali::generaUsername() : null) ->suffixAction( Forms\Components\Actions\Action::make('rigeneraUsername') ->icon('heroicon-m-arrow-path') ->tooltip('Genera un nuovo nome utente') ->action(fn (Forms\Set $set) => $set('username', ClienteCredenziali::generaUsername())) ), Forms\Components\TextInput::make('password') ->label('Password') ->password() ->revealable() ->required(fn (string $operation) => $operation === 'create') ->dehydrated(fn (?string $state) => filled($state)) ->default(fn (string $operation) => $operation === 'create' ? ClienteCredenziali::generaPassword() : null) ->suffixAction( Forms\Components\Actions\Action::make('rigeneraPassword') ->icon('heroicon-m-arrow-path') ->tooltip('Genera una nuova password') ->action(fn (Forms\Set $set) => $set('password', ClienteCredenziali::generaPassword())) ) ->helperText(fn (string $operation) => $operation === 'create' ? 'Generata automaticamente. Ricordati di copiarla dopo il salvataggio: non sarà più leggibile.' : 'Lascia vuoto per mantenere la password attuale, oppure genera/inserisci una nuova password.'), ]), Forms\Components\Section::make('Scontistica') ->description(fn () => sprintf( 'Sconti che il cliente può applicare al preventivo prima dell\'invio. Lascia vuoto per la regola base (1° sconto fino al %s%%, sconto extra fino al %s%% sul totale già scontato).', static::percentuale(Impostazione::scontiBase()['max_1']), static::percentuale(Impostazione::scontiBase()['max_2']), )) ->columns(2) ->schema([ Forms\Components\Toggle::make('sconti_abilitati') ->label('Può applicare sconti') ->default(true) ->live() ->columnSpanFull(), Forms\Components\TextInput::make('sconto_max_1') ->label('1° sconto massimo') ->numeric() ->minValue(0) ->maxValue(100) ->suffix('%') ->placeholder(fn () => static::percentuale(Impostazione::scontiBase()['max_1']).' (regola base)') ->visible(fn (Forms\Get $get) => (bool) $get('sconti_abilitati')), Forms\Components\TextInput::make('sconto_max_2') ->label('Sconto extra massimo') ->numeric() ->minValue(0) ->maxValue(100) ->suffix('%') ->placeholder(fn () => static::percentuale(Impostazione::scontiBase()['max_2']).' (regola base)') ->visible(fn (Forms\Get $get) => (bool) $get('sconti_abilitati')), ]), Forms\Components\Section::make('Anagrafica') ->description('Campi facoltativi.') ->columns(2) ->schema([ Forms\Components\TextInput::make('nome')->label('Nome'), Forms\Components\TextInput::make('cognome')->label('Cognome'), Forms\Components\TextInput::make('ragione_sociale')->label('Ragione sociale'), Forms\Components\TextInput::make('email')->label('Email')->email(), Forms\Components\TextInput::make('telefono')->label('Telefono')->tel(), Forms\Components\TextInput::make('indirizzo')->label('Indirizzo'), Forms\Components\TextInput::make('cap')->label('CAP')->maxLength(10), Forms\Components\TextInput::make('citta')->label('Città'), Forms\Components\TextInput::make('provincia')->label('Provincia')->maxLength(5), Forms\Components\Textarea::make('note')->label('Note')->columnSpanFull(), ]), ]); } public static function table(Table $table): Table { return $table ->defaultSort('created_at', 'desc') ->columns([ Tables\Columns\TextColumn::make('nominativo')->label('Nominativo')->searchable(['nome', 'cognome', 'ragione_sociale']), Tables\Columns\TextColumn::make('username')->label('Nome utente')->searchable()->placeholder('—'), Tables\Columns\TextColumn::make('email')->label('Email')->searchable()->copyable()->placeholder('—'), Tables\Columns\TextColumn::make('telefono')->label('Telefono')->placeholder('—'), Tables\Columns\TextColumn::make('citta')->label('Città')->placeholder('—'), Tables\Columns\TextColumn::make('preventivi_count')->label('Preventivi')->counts('preventivi'), Tables\Columns\TextColumn::make('created_at')->label('Registrato')->dateTime('d/m/Y')->sortable(), ]) ->actions([ Tables\Actions\ViewAction::make(), Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make() ->modalDescription('Verrà eliminato definitivamente il cliente insieme a tutti i suoi preventivi e ai file associati (PDF, immagini)') ->successRedirectUrl(static::getUrl('index')), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make() ->modalDescription('I clienti selezionati verranno eliminati definitivamente insieme a tutti i loro preventivi e ai file associati (PDF, immagini)') ->successRedirectUrl(static::getUrl('index')), ]), ]); } public static function infolist(Infolist $infolist): Infolist { return $infolist->schema([ Infolists\Components\View::make('filament.infolists.cliente-password-reveal') ->visible(fn () => filled(session('cliente_password_plain'))) ->viewData(fn () => [ 'username' => session('cliente_username_plain'), 'password' => session('cliente_password_plain'), ]), Infolists\Components\Section::make('Accesso') ->columns(2) ->schema([ Infolists\Components\TextEntry::make('username')->label('Nome utente')->copyable()->placeholder('—'), Infolists\Components\TextEntry::make('created_at')->label('Cliente dal')->dateTime('d/m/Y H:i'), Infolists\Components\TextEntry::make('scontistica') ->label('Scontistica') ->state(function (Cliente $record): string { if (! $record->sconti_abilitati) { return 'Disabilitata'; } $massimi = $record->scontiMassimi(); $base = $record->sconto_max_1 === null && $record->sconto_max_2 === null ? ' (regola base)' : ''; return sprintf('Fino al %s%% + extra fino al %s%%%s', static::percentuale($massimi['max_1']), static::percentuale($massimi['max_2']), $base); }) ->columnSpanFull(), ]), Infolists\Components\Section::make('Anagrafica') ->columns(3) ->schema([ Infolists\Components\TextEntry::make('nominativo')->label('Nominativo')->placeholder('—'), Infolists\Components\TextEntry::make('email')->label('Email')->copyable()->placeholder('—'), Infolists\Components\TextEntry::make('telefono')->label('Telefono')->placeholder('—'), Infolists\Components\TextEntry::make('indirizzo')->label('Indirizzo')->placeholder('—'), Infolists\Components\TextEntry::make('cap')->label('CAP')->placeholder('—'), Infolists\Components\TextEntry::make('citta')->label('Città')->placeholder('—'), Infolists\Components\TextEntry::make('provincia')->label('Provincia')->placeholder('—'), Infolists\Components\TextEntry::make('note')->label('Note')->columnSpanFull()->placeholder('—'), ]), ]); } /** 50.00 -> "50", 12.50 -> "12,5" */ private static function percentuale(mixed $valore): string { return rtrim(rtrim(number_format((float) $valore, 2, ',', ''), '0'), ','); } public static function getPages(): array { return [ 'index' => Pages\ListClientes::route('/'), 'create' => Pages\CreateCliente::route('/create'), 'view' => Pages\ViewCliente::route('/{record}'), 'edit' => Pages\EditCliente::route('/{record}/edit'), ]; } }