getTrialConversionRates(); $trialExtensions = $this->getTrialExtensionsTrend(); return [ 'datasets' => [ [ 'label' => 'Trial Conversion Rate (%)', 'data' => array_values($trialConversion), 'borderColor' => 'rgba(168, 85, 247, 1)', 'backgroundColor' => 'rgba(168, 85, 247, 0.1)', 'fill' => true, 'tension' => 0.4, ], [ 'label' => 'Trial Extensions', 'data' => array_values($trialExtensions), 'borderColor' => 'rgba(251, 146, 60, 1)', 'backgroundColor' => 'rgba(251, 146, 60, 0.1)', 'fill' => true, 'tension' => 0.4, ], ], 'labels' => array_keys($trialConversion), ]; } protected function getType(): string { return 'line'; } protected function getOptions(): array { return [ 'responsive' => true, 'interaction' => [ 'intersect' => false, 'mode' => 'index', ], 'plugins' => [ 'legend' => [ 'position' => 'top', ], 'tooltip' => [ 'callbacks' => [ 'label' => 'function(context) { let label = context.dataset.label || ""; if (label) { label += ": "; } if (context.parsed.y !== null) { if (label.includes("Rate")) { label += context.parsed.y.toFixed(1) + "%"; } else { label += context.parsed.y; } } return label; }', ], ], ], 'scales' => [ 'y' => [ 'beginAtZero' => true, 'ticks' => [ 'callback' => 'function(value, index, values) { if (index === 0 || index === values.length - 1) { return value + (this.chart.data.datasets[0].label.includes("Rate") ? "%" : ""); } return ""; }', ], ], ], ]; } private function getTrialConversionRates(): array { $conversionData = []; for ($i = 5; $i >= 0; $i--) { $month = now()->subMonths($i); $monthStart = $month->copy()->startOfMonth(); $monthEnd = $month->copy()->endOfMonth(); // Trials that ended during this month $endedTrials = Subscription::query() ->where('status', 'trialing') ->whereBetween('trial_ends_at', [$monthStart, $monthEnd]) ->get(); $totalTrials = $endedTrials->count(); $convertedTrials = $endedTrials->filter(function ($trial) use ($monthEnd) { // Check if trial converted to paid subscription return Subscription::query() ->where('user_id', $trial->user_id) ->where('status', 'active') ->where('created_at', '>', $trial->trial_ends_at) ->where('created_at', '<=', $monthEnd) ->exists(); })->count(); $conversionRate = $totalTrials > 0 ? ($convertedTrials / $totalTrials) * 100 : 0; $conversionData[$month->format('M Y')] = round($conversionRate, 2); } return $conversionData; } private function getTrialExtensionsTrend(): array { $dateFormat = $this->getDateFormatExpression(); return TrialExtension::query() ->select( DB::raw("{$dateFormat} as month"), DB::raw('COUNT(*) as extensions'), DB::raw('SUM(extension_days) as total_days') ) ->where('trial_extensions.granted_at', '>=', now()->subMonths(6)) ->groupBy('month') ->orderBy('month') ->get() ->mapWithKeys(function ($item) { $date = \Carbon\Carbon::createFromFormat('Y-m', $item->month); return [$date->format('M Y') => $item->extensions]; }) ->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() ->where('status', 'trialing') ->orWhere('status', 'active') ->whereNotNull('trial_ends_at') ->selectRaw('AVG(DATEDIFF(trial_ends_at, created_at)) as avg_trial_days') ->value('avg_trial_days') ?? 0; } private function getMostCommonExtensionReasons(): array { return TrialExtension::query() ->select('reason', DB::raw('COUNT(*) as count')) ->whereNotNull('reason') ->groupBy('reason') ->orderBy('count', 'desc') ->limit(5) ->pluck('count', 'reason') ->toArray(); } }