startDate = now()->subDays(30)->format('Y-m-d'); $this->endDate = now()->format('Y-m-d'); } public function getTitle(): string { return 'Customer Analytics'; } protected function getHeaderActions(): array { return [ Action::make('export_report') ->label('Export Report') ->icon('heroicon-o-arrow-down-tray') ->color('success') ->action(fn () => $this->exportReport()), Action::make('refresh') ->label('Refresh') ->icon('heroicon-o-arrow-path') ->color('gray') ->action(fn () => $this->resetTable()), ]; } protected function getHeaderWidgets(): array { return [ \App\Filament\Widgets\CustomerAnalyticsOverview::class, \App\Filament\Widgets\SubscriptionMetrics::class, \App\Filament\Widgets\CouponPerformanceMetrics::class, ]; } protected function getFormSchema(): array { return [ Grid::make(4) ->schema([ DatePicker::make('startDate') ->label('Start Date') ->default(fn () => now()->subDays(30)) ->required(), DatePicker::make('endDate') ->label('End Date') ->default(fn () => now()) ->required(), Select::make('selectedProvider') ->label('Provider') ->options([ 'all' => 'All Providers', 'stripe' => 'Stripe', 'lemon_squeezy' => 'Lemon Squeezy', 'polar' => 'Polar.sh', 'oxapay' => 'OxaPay', 'crypto' => 'Crypto', 'activation_key' => 'Activation Key', ]) ->default('all'), Select::make('selectedPlan') ->label('Plan') ->options(function () { $plans = DB::table('plans')->pluck('name', 'id'); return ['all' => 'All Plans'] + $plans->toArray(); }) ->default('all'), ]), ]; } public function table(Table $table): Table { return $table ->query( $this->getCustomerAnalyticsQuery() ) ->columns([ TextColumn::make('user.name') ->label('Customer') ->searchable() ->sortable() ->weight('medium'), TextColumn::make('user.email') ->label('Email') ->searchable() ->copyable() ->toggleable(), TextColumn::make('plan.name') ->label('Plan') ->searchable() ->sortable() ->badge() ->color('primary'), TextColumn::make('provider') ->label('Provider') ->badge() ->colors([ 'blue' => 'stripe', 'green' => 'lemon_squeezy', 'purple' => 'polar', 'orange' => 'oxapay', 'gray' => 'crypto', 'pink' => 'activation_key', ]), TextColumn::make('status') ->label('Status') ->badge() ->colors([ 'success' => 'active', 'warning' => 'trialing', 'danger' => 'cancelled', 'secondary' => 'paused', 'gray' => 'incomplete', ]), TextColumn::make('subscription_age') ->label('Age') ->state(function ($record) { $started = $record->starts_at ?? $record->created_at; return $started ? $started->diffForHumans() : 'Unknown'; }) ->sortable(), TextColumn::make('total_coupon_discount') ->label('Total Discount') ->money('USD') ->sortable() ->toggleable(), TextColumn::make('trial_extensions_count') ->label('Trial Extensions') ->state(fn ($record) => $record->trial_extensions_count ?? 0) ->alignCenter() ->toggleable(), TextColumn::make('subscription_changes_count') ->label('Changes') ->state(fn ($record) => $record->subscription_changes_count ?? 0) ->alignCenter() ->toggleable(), TextColumn::make('mrr') ->label('MRR') ->money('USD') ->state(fn ($record) => $record->calculateMRR()) ->sortable() ->toggleable(), ]) ->defaultSort('created_at', 'desc') ->filters([ SelectFilter::make('provider') ->options([ 'stripe' => 'Stripe', 'lemon_squeezy' => 'Lemon Squeezy', 'polar' => 'Polar.sh', 'oxapay' => 'OxaPay', 'crypto' => 'Crypto', 'activation_key' => 'Activation Key', ]), SelectFilter::make('status') ->options([ 'active' => 'Active', 'trialing' => 'Trial', 'cancelled' => 'Cancelled', 'paused' => 'Paused', 'incomplete' => 'Incomplete', ]), Filter::make('date_range') ->schema([ DatePicker::make('start_date') ->label('Start Date') ->required(), DatePicker::make('end_date') ->label('End Date') ->required(), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['start_date'], fn (Builder $query, $date): Builder => $query->whereDate('subscriptions.created_at', '>=', $date) ) ->when( $data['end_date'], fn (Builder $query, $date): Builder => $query->whereDate('subscriptions.created_at', '<=', $date) ); }), Filter::make('has_coupon_usage') ->label('Has Coupon Usage') ->query(fn (Builder $query): Builder => $query->whereHas('couponUsages')), Filter::make('has_trial_extension') ->label('Has Trial Extension') ->query(fn (Builder $query): Builder => $query->whereHas('trialExtensions')), ]) ->emptyStateHeading('No customer data found') ->emptyStateDescription('No customer analytics data available for the selected filters.') ->emptyStateActions([ Action::make('reset_filters') ->label('Reset Filters') ->icon('heroicon-o-arrow-path') ->action(fn () => $this->resetTable()), ]); } protected function getCustomerAnalyticsQuery(): Builder { return Subscription::query() ->with(['user', 'plan', 'couponUsages', 'trialExtensions', 'subscriptionChanges']) ->withCount(['couponUsages', 'trialExtensions', 'subscriptionChanges']) ->when($this->selectedProvider !== 'all', function ($query) { $query->where('provider', $this->selectedProvider); }) ->when($this->selectedPlan !== 'all', function ($query) { $query->where('plan_id', $this->selectedPlan); }) ->when($this->startDate, function ($query) { $query->whereDate('subscriptions.created_at', '>=', $this->startDate); }) ->when($this->endDate, function ($query) { $query->whereDate('subscriptions.created_at', '<=', $this->endDate); }); } public function exportReport() { $subscriptions = $this->getCustomerAnalyticsQuery()->get(); $filename = 'customer_analytics_'.now()->format('Y_m_d_H_i_s').'.csv'; // Create a temporary file $handle = fopen('php://temp', 'r+'); // Add BOM for Excel UTF-8 support fwrite($handle, "\xEF\xBB\xBF"); // Write headers $headers = [ 'Customer', 'Email', 'Plan', 'Provider', 'Status', 'Subscription Age', 'Total Discount', 'Trial Extensions', 'Subscription Changes', 'MRR', 'Created At', 'Trial Ends At', 'Ends At', ]; fputcsv($handle, $headers); // Write data rows foreach ($subscriptions as $subscription) { $started = $subscription->starts_at ?? $subscription->created_at; fputcsv($handle, [ $subscription->user?->name ?? 'Unknown', $subscription->user?->email ?? 'Unknown', $subscription->plan?->name ?? 'Unknown', $subscription->provider, $subscription->status, $started ? $started->diffForHumans() : 'Unknown', $subscription->couponUsages()->sum('discount_amount'), $subscription->trialExtensions()->count(), $subscription->subscriptionChanges()->count(), $subscription->calculateMRR(), $subscription->created_at->toDateTimeString(), $subscription->trial_ends_at?->toDateTimeString() ?? 'N/A', $subscription->ends_at?->toDateTimeString() ?? 'N/A', ]); } // Rewind the file pointer rewind($handle); // Get the CSV content $csvContent = stream_get_contents($handle); // Close the file handle fclose($handle); // Return a download response return response()->streamDownload( function () use ($csvContent) { echo $csvContent; }, $filename, [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="'.$filename.'"', ] ); } }