schema([ TextInput::make('name') ->label('Name') ->required() ->maxLength(255), Select::make('type') ->label('Type') ->options([ 'endcustomer' => 'Endcustomer', 'reseller' => 'Reseller', ]) ->required(), TextInput::make('price') ->label('Price') ->prefix('€') ->numeric() ->step(0.01) ->required() ->default(0), TextInput::make('max_photos') ->label('Max Photos') ->numeric() ->nullable(), TextInput::make('max_guests') ->label('Max Guests') ->numeric() ->nullable(), TextInput::make('gallery_days') ->label('Gallery Days') ->numeric() ->nullable(), TextInput::make('max_tasks') ->label('Max Tasks') ->numeric() ->nullable(), Toggle::make('watermark_allowed') ->label('Watermark Allowed') ->default(true), Toggle::make('branding_allowed') ->label('Branding Allowed') ->default(false), TextInput::make('max_events_per_year') ->label('Max Events per Year') ->numeric() ->nullable(), Repeater::make('features') ->label('Features') ->schema([ TextInput::make('key') ->label('Feature Key'), TextInput::make('value') ->label('Feature Value'), ]) ->columns(2) ->defaultItems(0), ]); } public static function featuresToRepeaterItems(mixed $features): array { if (is_string($features)) { $decoded = json_decode($features, true); if (is_string($decoded)) { $decoded = json_decode($decoded, true); } $features = json_last_error() === JSON_ERROR_NONE ? $decoded : null; } if ($features === null) { return []; } if (! is_array($features)) { return []; } if (! array_is_list($features)) { return collect($features) ->map(function ($value, $key) { return [ 'key' => (string) $key, 'value' => is_bool($value) ? ($value ? 'true' : 'false') : (string) $value, ]; }) ->values() ->all(); } return collect($features) ->map(function ($item) { if (is_array($item)) { return [ 'key' => (string) ($item['key'] ?? ''), 'value' => (string) ($item['value'] ?? ''), ]; } return [ 'key' => (string) $item, 'value' => 'true', ]; }) ->values() ->all(); } public static function featuresFromRepeaterItems(mixed $items): array { if (! is_array($items)) { return []; } $features = []; foreach ($items as $item) { if (! is_array($item)) { continue; } $key = isset($item['key']) ? trim((string) $item['key']) : ''; if ($key === '') { continue; } $value = $item['value'] ?? true; if (is_string($value)) { $normalized = strtolower(trim($value)); if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) { $value = true; } elseif (in_array($normalized, ['0', 'false', 'no', 'off'], true)) { $value = false; } } if (is_array($value)) { $value = $value['value'] ?? $value['enabled'] ?? true; } $features[$key] = (bool) $value; } return $features; } public static function formatFeaturesForDisplay(mixed $features): string { $map = $features; if (! is_array($map)) { if (is_string($map)) { $decoded = json_decode($map, true); if (is_string($decoded)) { $decoded = json_decode($decoded, true); } $map = json_last_error() === JSON_ERROR_NONE ? $decoded : []; } else { $map = []; } } if (! array_is_list($map)) { return collect($map) ->filter(fn ($value) => (bool) $value) ->keys() ->implode(', '); } return collect($map) ->map(function ($item) { if (is_array($item)) { return (string) ($item['key'] ?? ''); } return (string) $item; }) ->filter() ->implode(', '); } public static function table(Table $table): Table { return $table ->columns([ TextColumn::make('name') ->label('Name') ->searchable() ->sortable(), TextColumn::make('type') ->label('Type') ->badge() ->color(fn (string $state): string => match ($state) { 'endcustomer' => 'info', 'reseller' => 'warning', default => 'gray', }), TextColumn::make('price') ->label('Price') ->money('EUR') ->sortable(), IconColumn::make('max_photos') ->label('Max Photos') ->icon('heroicon-o-photo') ->color('primary'), TextColumn::make('features') ->label('Features') ->formatStateUsing(fn ($state) => static::formatFeaturesForDisplay($state)) ->limit(50), ]) ->filters([ // ]) ->actions([ EditAction::make(), DeleteAction::make(), ]) ->bulkActions([ BulkActionGroup::make([ DeleteBulkAction::make(), ]), ]); } public static function getRelations(): array { return [ // ]; } public static function getPages(): array { return [ 'index' => Pages\ListPackages::route('/'), 'create' => Pages\CreatePackage::route('/create'), 'edit' => Pages\EditPackage::route('/{record}/edit'), ]; } }