From e60973c391fe284d82f5148d0a63b839554afc8c Mon Sep 17 00:00:00 2001 From: idevakk <219866223+idevakk@users.noreply.github.com> Date: Tue, 2 Dec 2025 07:09:30 -0800 Subject: [PATCH] fix(widgets): add multi-database compatibility to all dashboard widgets Replace SQLite-specific functions with database-agnostic expressions to support MySQL, SQLite, PostgreSQL, and SQL Server across all Filament dashboard widgets. - Fix strftime() date formatting in SubscriptionMetrics, RevenueMetrics, and TrialPerformance - Fix CAST AS REAL syntax in ChurnAnalysis widget - Add getDateFormatExpression() method for date function compatibility - Add getCastExpression() method for CAST syntax compatibility - Support MySQL/MariaDB, SQLite, PostgreSQL, and SQL Server drivers - Maintain identical functionality across all database types Fixes multiple SQLSTATE[42000] syntax errors when using MySQL/MariaDB databases. --- app/Filament/Widgets/ChurnAnalysis.php | 21 ++++++++++++++++++-- app/Filament/Widgets/RevenueMetrics.php | 17 +++++++++++++++- app/Filament/Widgets/SubscriptionMetrics.php | 17 +++++++++++++++- app/Filament/Widgets/TrialPerformance.php | 17 +++++++++++++++- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/app/Filament/Widgets/ChurnAnalysis.php b/app/Filament/Widgets/ChurnAnalysis.php index 485d1e1..2c10e17 100644 --- a/app/Filament/Widgets/ChurnAnalysis.php +++ b/app/Filament/Widgets/ChurnAnalysis.php @@ -112,12 +112,14 @@ class ChurnAnalysis extends ChartWidget private function getChurnByProvider(): array { + $castExpression = $this->getCastExpression(); + return Subscription::query() ->select( 'provider', DB::raw('COUNT(CASE WHEN status = "cancelled" THEN 1 END) as cancelled'), DB::raw('COUNT(*) as total'), - DB::raw('(CAST(COUNT(CASE WHEN status = "cancelled" THEN 1 END) AS REAL) * 100.0 / COUNT(*)) as churn_rate') + DB::raw("({$castExpression} * 100.0 / COUNT(*)) as churn_rate") ) ->groupBy('provider') ->orderBy('churn_rate', 'desc') @@ -130,12 +132,14 @@ class ChurnAnalysis extends ChartWidget private function getChurnByPlan(): array { + $castExpression = $this->getCastExpression(); + return Subscription::query() ->select( 'plans.name as plan_name', DB::raw('COUNT(CASE WHEN subscriptions.status = "cancelled" THEN 1 END) as cancelled'), DB::raw('COUNT(*) as total'), - DB::raw('(CAST(COUNT(CASE WHEN subscriptions.status = "cancelled" THEN 1 END) AS REAL) * 100.0 / COUNT(*)) as churn_rate') + DB::raw("({$castExpression} * 100.0 / COUNT(*)) as churn_rate") ) ->join('plans', 'subscriptions.plan_id', '=', 'plans.id') ->groupBy('plans.id', 'plans.name') @@ -147,4 +151,17 @@ class ChurnAnalysis extends ChartWidget }) ->toArray(); } + + private function getCastExpression(): string + { + $connection = DB::connection()->getDriverName(); + + return match ($connection) { + 'sqlite' => 'CAST(COUNT(CASE WHEN status = "cancelled" THEN 1 END) AS REAL)', + 'mysql', 'mariadb' => 'CAST(COUNT(CASE WHEN status = "cancelled" THEN 1 END) AS DECIMAL(10,2))', + 'pgsql' => 'CAST(COUNT(CASE WHEN status = "cancelled" THEN 1 END) AS NUMERIC)', + 'sqlsrv' => 'CAST(COUNT(CASE WHEN status = "cancelled" THEN 1 END) AS FLOAT)', + default => 'CAST(COUNT(CASE WHEN status = "cancelled" THEN 1 END) AS DECIMAL(10,2))', // fallback to MySQL format + }; + } } diff --git a/app/Filament/Widgets/RevenueMetrics.php b/app/Filament/Widgets/RevenueMetrics.php index f27ba66..14d9a95 100644 --- a/app/Filament/Widgets/RevenueMetrics.php +++ b/app/Filament/Widgets/RevenueMetrics.php @@ -92,9 +92,11 @@ class RevenueMetrics extends ChartWidget private function getMonthlyRevenueTrend(): array { + $dateFormat = $this->getDateFormatExpression(); + return Subscription::query() ->select( - DB::raw("strftime('%Y-%m', subscriptions.created_at) as month"), + DB::raw("{$dateFormat} as month"), DB::raw('SUM(plans.price) as revenue') ) ->join('plans', 'subscriptions.plan_id', '=', 'plans.id') @@ -106,6 +108,19 @@ class RevenueMetrics extends ChartWidget ->toArray(); } + private function getDateFormatExpression(): string + { + $connection = DB::connection()->getDriverName(); + + return match ($connection) { + 'sqlite' => "strftime('%Y-%m', subscriptions.created_at)", + 'mysql', 'mariadb' => "DATE_FORMAT(subscriptions.created_at, '%Y-%m')", + 'pgsql' => "TO_CHAR(subscriptions.created_at, 'YYYY-MM')", + 'sqlsrv' => "FORMAT(subscriptions.created_at, 'yyyy-MM')", + default => "DATE_FORMAT(subscriptions.created_at, '%Y-%m')", // fallback to MySQL format + }; + } + private function getMRRByProvider(): array { return Subscription::query() diff --git a/app/Filament/Widgets/SubscriptionMetrics.php b/app/Filament/Widgets/SubscriptionMetrics.php index f695cb8..c88ad38 100644 --- a/app/Filament/Widgets/SubscriptionMetrics.php +++ b/app/Filament/Widgets/SubscriptionMetrics.php @@ -113,9 +113,11 @@ class SubscriptionMetrics extends ChartWidget private function getMonthlySubscriptionTrend(): array { + $dateFormat = $this->getDateFormatExpression(); + return Subscription::query() ->select( - DB::raw("strftime('%Y-%m', subscriptions.created_at) as month"), + DB::raw("{$dateFormat} as month"), DB::raw('count(*) as count') ) ->groupBy('month') @@ -123,4 +125,17 @@ class SubscriptionMetrics extends ChartWidget ->pluck('count', 'month') ->toArray(); } + + private function getDateFormatExpression(): string + { + $connection = DB::connection()->getDriverName(); + + return match ($connection) { + 'sqlite' => "strftime('%Y-%m', subscriptions.created_at)", + 'mysql', 'mariadb' => "DATE_FORMAT(subscriptions.created_at, '%Y-%m')", + 'pgsql' => "TO_CHAR(subscriptions.created_at, 'YYYY-MM')", + 'sqlsrv' => "FORMAT(subscriptions.created_at, 'yyyy-MM')", + default => "DATE_FORMAT(subscriptions.created_at, '%Y-%m')", // fallback to MySQL format + }; + } } diff --git a/app/Filament/Widgets/TrialPerformance.php b/app/Filament/Widgets/TrialPerformance.php index 620f81e..95e5944 100644 --- a/app/Filament/Widgets/TrialPerformance.php +++ b/app/Filament/Widgets/TrialPerformance.php @@ -134,9 +134,11 @@ class TrialPerformance extends ChartWidget private function getTrialExtensionsTrend(): array { + $dateFormat = $this->getDateFormatExpression(); + return TrialExtension::query() ->select( - DB::raw("strftime('%Y-%m', trial_extensions.granted_at) as month"), + DB::raw("{$dateFormat} as month"), DB::raw('COUNT(*) as extensions'), DB::raw('SUM(extension_days) as total_days') ) @@ -152,6 +154,19 @@ class TrialPerformance extends ChartWidget ->toArray(); } + private function getDateFormatExpression(): string + { + $connection = DB::connection()->getDriverName(); + + return match ($connection) { + 'sqlite' => "strftime('%Y-%m', trial_extensions.granted_at)", + 'mysql', 'mariadb' => "DATE_FORMAT(trial_extensions.granted_at, '%Y-%m')", + 'pgsql' => "TO_CHAR(trial_extensions.granted_at, 'YYYY-MM')", + 'sqlsrv' => "FORMAT(trial_extensions.granted_at, 'yyyy-MM')", + default => "DATE_FORMAT(trial_extensions.granted_at, '%Y-%m')", // fallback to MySQL format + }; + } + private function getAverageTrialLength(): float { return Subscription::query()