currentSeasonScope()->count(); return "{$current} subscribers this season."; } /** Real subscriber: has customer, has payments, amount > 0, not soft-deleted */ private function realScope(): Builder { return ScheduledGivingDonation::query() ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('scheduled_giving_donations.deleted_at') ->whereHas('payments', fn ($q) => $q->whereNull('deleted_at')); } /** Current season: real + has at least one future payment */ private function currentSeasonScope(): Builder { return $this->realScope() ->whereHas('payments', fn ($q) => $q ->whereNull('deleted_at') ->where('expected_at', '>', now())); } /** Applies real + current season filters to the query */ private function applyCurrentSeason(Builder $q): Builder { return $q ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('scheduled_giving_donations.deleted_at') ->whereHas('payments', fn ($sub) => $sub->whereNull('deleted_at')) ->whereHas('payments', fn ($sub) => $sub ->whereNull('deleted_at') ->where('expected_at', '>', now())); } /** Applies real + expired (no future payments) filters */ private function applyExpired(Builder $q): Builder { return $q ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('scheduled_giving_donations.deleted_at') ->whereHas('payments', fn ($sub) => $sub->whereNull('deleted_at')) ->whereDoesntHave('payments', fn ($sub) => $sub ->whereNull('deleted_at') ->where('expected_at', '>', now())); } /** Applies real subscriber filters */ private function applyReal(Builder $q): Builder { return $q ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('scheduled_giving_donations.deleted_at') ->whereHas('payments', fn ($sub) => $sub->whereNull('deleted_at')); } public function getTabs(): array { $campaigns = ScheduledGivingCampaign::all(); $currentCount = $this->currentSeasonScope()->count(); $tabs = []; // Current season — the primary tab $tabs['current'] = Tab::make('This Season') ->icon('heroicon-o-sun') ->badge($currentCount) ->badgeColor('success') ->modifyQueryUsing(fn (Builder $q) => $this->applyCurrentSeason($q)); // Per-campaign tabs for current season foreach ($campaigns as $c) { $slug = str($c->title)->slug()->toString(); $count = $this->currentSeasonScope() ->where('scheduled_giving_campaign_id', $c->id) ->count(); if ($count === 0) continue; // Skip campaigns with no current subscribers $tabs[$slug] = Tab::make($c->title) ->icon('heroicon-o-calendar') ->badge($count) ->badgeColor('primary') ->modifyQueryUsing(fn (Builder $q) => $this->applyCurrentSeason($q) ->where('scheduled_giving_campaign_id', $c->id)); } // Failed (current season only) $failedCount = $this->currentSeasonScope() ->whereHas('payments', fn ($q) => $q ->where('is_paid', false) ->where('attempts', '>', 0) ->whereNull('deleted_at')) ->count(); if ($failedCount > 0) { $tabs['failed'] = Tab::make('Failed') ->icon('heroicon-o-exclamation-triangle') ->badge($failedCount) ->badgeColor('danger') ->modifyQueryUsing(fn (Builder $q) => $this->applyCurrentSeason($q) ->whereHas('payments', fn ($sub) => $sub ->where('is_paid', false) ->where('attempts', '>', 0) ->whereNull('deleted_at'))); } // Past seasons $expiredCount = $this->realScope() ->whereDoesntHave('payments', fn ($q) => $q ->whereNull('deleted_at') ->where('expected_at', '>', now())) ->count(); $tabs['past'] = Tab::make('Past Seasons') ->icon('heroicon-o-archive-box') ->badge($expiredCount > 0 ? $expiredCount : null) ->badgeColor('gray') ->modifyQueryUsing(fn (Builder $q) => $this->applyExpired($q)); // All real $tabs['all'] = Tab::make('All') ->icon('heroicon-o-squares-2x2') ->modifyQueryUsing(fn (Builder $q) => $this->applyReal($q)); return $tabs; } protected function getHeaderActions(): array { return []; } public function getDefaultActiveTab(): string|int|null { return 'current'; } }