feat(plans): enhance plan management with dynamic providers, improved UI, and bug fixes

Dynamic Provider Integration:
  - Replace hardcoded provider arrays with database-driven payment_providers lookup
  - Display provider status (Active/Inactive) in selection dropdowns
  - Add provider_variant_id and provider_product_id input fields to plan configuration
  - Update EditPlan and SubscriptionForm with dynamic provider selection
  - Add empty state handling with helpful guidance when no providers exist

  UI/UX Improvements:
  - Format billing_cycle_days to readable text (Daily, Weekly, Monthly, Quarterly, Annually)
  - Add color-coded badges for billing cycle frequency
  - Fix plan_providers and plan_feature_limits count display with eager loading
  - Implement intelligent color coding for count indicators
  - Add visual status indicators for provider availability

  Database Compatibility:
  - Fix SQLite strftime() compatibility across all dashboard widgets
  - Fix CAST AS REAL syntax in ChurnAnalysis widget
  - Add database-agnostic date and cast expression methods
  - Support MySQL, SQLite, PostgreSQL, and SQL Server

  Bug Fixes:
  - Fix null reference error in SubscriptionForm provider_data access
  - Add null safety checks for new subscription creation
  - Optimize queries with withCount() to prevent N+1 issues

  Performance Optimizations:
  - Add eager loading with withCount() for relationship counts
  - Optimize plan provider and feature limit queries
  - Prevent N+1 query issues in resource tables

  BREAKING CHANGE: Plan provider configuration now uses dynamic provider options
  from payment_providers table instead of hardcoded list.
This commit is contained in:
idevakk
2025-12-02 09:47:51 -08:00
parent e60973c391
commit d767c6cf59
4 changed files with 150 additions and 53 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Filament\Resources\Subscriptions\Schemas;
use App\Models\PaymentProvider;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
@@ -78,16 +79,50 @@ class SubscriptionForm
->schema([
Select::make('provider')
->label('Provider')
->options([
'stripe' => 'Stripe',
'lemon_squeezy' => 'Lemon Squeezy',
'polar' => 'Polar.sh',
'oxapay' => 'OxaPay',
'crypto' => 'Crypto',
'activation_key' => 'Activation Key',
])
->options(function () {
$providers = PaymentProvider::orderBy('priority', 'desc')
->orderBy('name')
->get();
if ($providers->isEmpty()) {
return [
'no_providers' => '⚠️ No payment providers configured - Please add providers first',
];
}
return $providers->mapWithKeys(function ($provider) {
$status = $provider->is_active ? 'Active' : 'Inactive';
return [
$provider->name => "{$provider->display_name} ({$status})",
];
})->toArray();
})
->required()
->default('stripe'),
->default(function () {
$providers = PaymentProvider::orderBy('priority', 'desc')
->orderBy('name')
->get();
if ($providers->isEmpty()) {
return 'no_providers';
}
return PaymentProvider::where('name', 'stripe')->exists()
? 'stripe'
: PaymentProvider::active()->first()?->name;
})
->helperText(function () {
$providers = PaymentProvider::orderBy('priority', 'desc')
->orderBy('name')
->get();
if ($providers->isEmpty()) {
return '⚠️ No payment providers available. Please configure payment providers in the Payment Providers section first.';
}
return 'Select a payment provider for this subscription';
}),
TextInput::make('provider_subscription_id')
->label('Provider Subscription ID'),
@@ -265,7 +300,7 @@ class SubscriptionForm
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
})
->state(fn ($record) => $record->provider_data)
->state(fn ($record) => $record?->provider_data ?? '{}')
->copyable()
->columnSpanFull(),
]),