components([ Grid::make(3)->schema([ TextInput::make('name') ->label('Plan Name') ->required() ->maxLength(255), TextInput::make('price') ->label('Price') ->numeric() ->prefix('$') ->required(), Select::make('billing_cycle_days') ->label('Billing Cycle') ->options([ 30 => 'Monthly', 90 => 'Quarterly', 365 => 'Yearly', 60 => 'Bi-Monthly', 180 => 'Semi-Annual', ]) ->default(30) ->required(), ]), Grid::make(2)->schema([ TextInput::make('product_id') ->label('Product ID') ->required() ->helperText('External product identifier'), TextInput::make('pricing_id') ->label('Pricing ID') ->required() ->helperText('External pricing identifier'), ]), Textarea::make('description') ->label('Description') ->rows(3) ->maxLength(500), Grid::make(3)->schema([ Select::make('plan_tier_id') ->label('Plan Tier') ->options(PlanTier::pluck('name', 'id')) ->nullable() ->searchable() ->helperText('Optional tier classification'), Toggle::make('is_active') ->label('Active') ->default(true) ->helperText('Plan is available for new subscriptions'), TextInput::make('sort_order') ->label('Sort Order') ->numeric() ->default(0) ->helperText('Display order in pricing tables'), ]), Section::make('Legacy Settings') ->description('Legacy payment provider settings (will be migrated to new system)') ->collapsible() ->schema([ Grid::make(3)->schema([ Toggle::make('monthly_billing') ->label('Monthly Billing (Legacy)') ->helperText('Legacy monthly billing flag'), TextInput::make('mailbox_limit') ->label('Mailbox Limit') ->numeric() ->default(10) ->helperText('Maximum number of mailboxes'), TextInput::make('shoppy_product_id') ->label('Shoppy Product ID') ->nullable(), ]), ]), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name') ->label('Plan Name') ->searchable() ->sortable(), Tables\Columns\TextColumn::make('planTier.name') ->label('Tier') ->badge() ->sortable() ->placeholder('No Tier'), Tables\Columns\TextColumn::make('price') ->label('Price') ->money('USD') ->sortable(), Tables\Columns\TextColumn::make('billing_cycle_days') ->label('Billing Cycle') ->badge() ->color(fn (int $state): string => match ($state) { 1 => 'success', // Daily - green 7 => 'info', // Weekly - blue 14 => 'primary', // Bi-weekly - primary blue 30 => 'warning', // Monthly - orange 60 => 'gray', // Bi-monthly - gray 90 => 'purple', // Quarterly - purple 180 => 'danger', // Semi-annually - red 365 => 'primary', // Annually - primary blue 730 => 'gray', // Biennially - gray default => 'gray' // Custom cycles - gray }) ->formatStateUsing(fn (int $state): string => match ($state) { 1 => 'Daily', 7 => 'Weekly', 14 => 'Bi-weekly', 30 => 'Monthly', 60 => 'Bi-monthly', 90 => 'Quarterly', 180 => 'Semi-annually', 365 => 'Annually', 730 => 'Biennially', default => "{$state} days" }) ->sortable(), Tables\Columns\IconColumn::make('is_active') ->label('Active') ->boolean() ->trueColor('success') ->falseColor('danger'), Tables\Columns\TextColumn::make('plan_providers_count') ->label('Providers') ->getStateUsing(fn ($record) => $record->plan_providers_count ?? 0) ->badge() ->color(fn ($record) => match (true) { $record->plan_providers_count === 0 => 'danger', $record->plan_providers_count === 1 => 'warning', $record->plan_providers_count >= 3 => 'success', default => 'info' }) ->sortable(), Tables\Columns\TextColumn::make('plan_feature_limits_count') ->label('Features') ->getStateUsing(fn ($record) => $record->plan_feature_limits_count ?? 0) ->badge() ->color(fn ($record) => match (true) { $record->plan_feature_limits_count === 0 => 'danger', $record->plan_feature_limits_count >= 5 => 'success', $record->plan_feature_limits_count >= 3 => 'info', default => 'warning' }) ->sortable(), Tables\Columns\TextColumn::make('sort_order') ->label('Order') ->sortable() ->alignCenter(), ]) ->filters([ Tables\Filters\SelectFilter::make('plan_tier_id') ->label('Tier') ->options(PlanTier::pluck('name', 'id')) ->searchable(), Tables\Filters\TernaryFilter::make('is_active') ->label('Active Status') ->placeholder('All plans') ->trueLabel('Active only') ->falseLabel('Inactive only'), Tables\Filters\SelectFilter::make('billing_cycle_days') ->label('Billing Cycle') ->options([ 30 => 'Monthly', 90 => 'Quarterly', 365 => 'Yearly', ]), Tables\Filters\Filter::make('has_providers') ->label('Has Payment Providers') ->query(fn (Builder $query): Builder => $query->whereHas('planProviders')) ->toggle(), ]) ->recordActions([ ViewAction::make(), EditAction::make(), DeleteAction::make() ->before(function (Plan $record) { // Prevent deletion if plan has active subscriptions if ($record->subscriptions()->where('status', 'active')->exists()) { Log::error('Cannot delete plan with active subscriptions'); } }), ]) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make() ->before(function ($records) { foreach ($records as $record) { if ($record->subscriptions()->where('status', 'active')->exists()) { Log::error('Cannot delete plan(s) with active subscriptions'); } } }), ]), ]) ->emptyStateActions([ CreateAction::make(), ]) ->defaultSort('sort_order', 'asc') ->groups([ Tables\Grouping\Group::make('planTier.name') ->label('Tier') ->collapsible(), ]); } public static function getEloquentQuery(): Builder { return parent::getEloquentQuery() ->withCount([ 'planProviders', 'planFeatureLimits', ]); } public static function getRelations(): array { return [ // ]; } public static function getPages(): array { return [ 'index' => ListPlans::route('/'), 'create' => CreatePlan::route('/create'), 'edit' => EditPlan::route('/{record}/edit'), ]; } public static function getNavigationBadge(): ?string { return static::getModel()::active()->count(); } public static function getNavigationBadgeColor(): ?string { return 'success'; } }