startOfMonth(); $endDate = $startDate->copy()->endOfMonth(); $locale = Auth::user()->preferred_language ?? 'en'; $data = [ 'period' => $startDate->translatedFormat('F Y'), 'periodMonth' => $startDate->translatedFormat('F'), 'periodYear' => $year, 'generatedAt' => now()->translatedFormat('d M Y H:i'), 'locale' => $locale, 'userStats' => $this->getUserStats($startDate, $endDate), 'consultationStats' => $this->getConsultationStats($startDate, $endDate), 'timelineStats' => $this->getTimelineStats($startDate, $endDate), 'postStats' => $this->getPostStats($startDate, $endDate), 'charts' => $this->renderChartsAsImages($startDate, $endDate, $locale), 'previousMonth' => $this->getPreviousMonthComparison($startDate), 'executiveSummary' => $this->generateExecutiveSummary($startDate, $endDate, $locale), ]; $pdf = Pdf::loadView('pdf.monthly-report', $data) ->setPaper('a4', 'portrait'); $pdf->setOption('isHtml5ParserEnabled', true); $pdf->setOption('defaultFont', 'DejaVu Sans'); $filename = "monthly-report-{$year}-{$month}.pdf"; return response()->streamDownload( fn () => print ($pdf->output()), $filename ); } public function getUserStats(Carbon $start, Carbon $end): array { $newClients = User::query() ->whereBetween('created_at', [$start, $end]) ->whereIn('user_type', [UserType::Individual, UserType::Company]) ->count(); $totalActive = User::query() ->where('status', UserStatus::Active) ->where('created_at', '<=', $end) ->whereIn('user_type', [UserType::Individual, UserType::Company]) ->count(); $individual = User::query() ->where('user_type', UserType::Individual) ->where('status', UserStatus::Active) ->where('created_at', '<=', $end) ->count(); $company = User::query() ->where('user_type', UserType::Company) ->where('status', UserStatus::Active) ->where('created_at', '<=', $end) ->count(); return [ 'new_clients' => $newClients, 'total_active' => $totalActive, 'individual' => $individual, 'company' => $company, ]; } public function getConsultationStats(Carbon $start, Carbon $end): array { $total = Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->count(); $completed = Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->whereIn('status', [ConsultationStatus::Completed, ConsultationStatus::NoShow]) ->count(); $noShows = Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->where('status', ConsultationStatus::NoShow) ->count(); $free = Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->where('consultation_type', ConsultationType::Free) ->count(); $paid = Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->where('consultation_type', ConsultationType::Paid) ->count(); return [ 'total' => $total, 'approved' => Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->where('status', ConsultationStatus::Approved) ->count(), 'completed' => Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->where('status', ConsultationStatus::Completed) ->count(), 'cancelled' => Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->where('status', ConsultationStatus::Cancelled) ->count(), 'no_show' => $noShows, 'free' => $free, 'paid' => $paid, 'no_show_rate' => $completed > 0 ? round(($noShows / $completed) * 100, 1) : 0, ]; } public function getTimelineStats(Carbon $start, Carbon $end): array { return [ 'active' => Timeline::query() ->where('status', TimelineStatus::Active) ->where('created_at', '<=', $end) ->count(), 'new' => Timeline::query() ->whereBetween('created_at', [$start, $end]) ->count(), 'updates' => TimelineUpdate::query() ->whereBetween('created_at', [$start, $end]) ->count(), 'archived' => Timeline::query() ->where('status', TimelineStatus::Archived) ->whereBetween('updated_at', [$start, $end]) ->count(), ]; } public function getPostStats(Carbon $start, Carbon $end): array { return [ 'this_month' => Post::query() ->where('status', PostStatus::Published) ->whereBetween('published_at', [$start, $end]) ->count(), 'total' => Post::query() ->where('status', PostStatus::Published) ->where('published_at', '<=', $end) ->count(), ]; } private function renderChartsAsImages(Carbon $start, Carbon $end, string $locale): array { $free = Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->where('consultation_type', ConsultationType::Free) ->count(); $paid = Consultation::query() ->whereBetween('booking_date', [$start, $end]) ->where('consultation_type', ConsultationType::Paid) ->count(); $consultationPieChart = $this->generateQuickChart([ 'type' => 'pie', 'data' => [ 'labels' => [ __('report.free', [], $locale), __('report.paid', [], $locale), ], 'datasets' => [[ 'data' => [$free, $paid], 'backgroundColor' => ['#8AB357', '#A5C87A'], ]], ], 'options' => [ 'plugins' => [ 'legend' => [ 'position' => 'bottom', ], ], ], ]); $trendChart = $this->generateTrendChart($start, $locale); return [ 'consultation_pie' => $consultationPieChart, 'trend_line' => $trendChart, ]; } private function generateQuickChart(array $config): string { $url = 'https://quickchart.io/chart?c='.urlencode(json_encode($config)).'&w=400&h=300&bkg=white'; try { $response = Http::timeout(10)->get($url); if ($response->successful()) { return 'data:image/png;base64,'.base64_encode($response->body()); } return ''; } catch (\Exception $e) { return ''; } } private function generateTrendChart(Carbon $endMonth, string $locale): string { $labels = []; $data = []; for ($i = 5; $i >= 0; $i--) { $month = $endMonth->copy()->subMonths($i); $labels[] = $month->translatedFormat('M Y'); $data[] = Consultation::query() ->whereMonth('booking_date', $month->month) ->whereYear('booking_date', $month->year) ->count(); } return $this->generateQuickChart([ 'type' => 'line', 'data' => [ 'labels' => $labels, 'datasets' => [[ 'label' => __('report.consultations', [], $locale), 'data' => $data, 'borderColor' => '#8AB357', 'backgroundColor' => 'rgba(138, 179, 87, 0.1)', 'fill' => true, 'tension' => 0.3, ]], ], 'options' => [ 'plugins' => [ 'legend' => [ 'position' => 'bottom', ], ], 'scales' => [ 'y' => [ 'beginAtZero' => true, 'ticks' => [ 'precision' => 0, ], ], ], ], ]); } public function getPreviousMonthComparison(Carbon $currentStart): ?array { $prevStart = $currentStart->copy()->subMonth()->startOfMonth(); $prevEnd = $prevStart->copy()->endOfMonth(); $prevConsultations = Consultation::query() ->whereBetween('booking_date', [$prevStart, $prevEnd]) ->count(); $prevClients = User::query() ->whereBetween('created_at', [$prevStart, $prevEnd]) ->whereIn('user_type', [UserType::Individual, UserType::Company]) ->count(); if ($prevConsultations === 0 && $prevClients === 0) { return null; } return [ 'consultations' => $prevConsultations, 'clients' => $prevClients, ]; } private function generateExecutiveSummary(Carbon $start, Carbon $end, string $locale): array { $userStats = $this->getUserStats($start, $end); $consultationStats = $this->getConsultationStats($start, $end); $previousMonth = $this->getPreviousMonthComparison($start); $highlights = []; // New clients highlight if ($userStats['new_clients'] > 0) { $highlights[] = __('report.highlight_new_clients', ['count' => $userStats['new_clients']], $locale); } // Consultations highlight if ($consultationStats['total'] > 0) { $highlights[] = __('report.highlight_consultations', ['count' => $consultationStats['total']], $locale); } // Month-over-month comparison if ($previousMonth) { $consultationChange = $consultationStats['total'] - $previousMonth['consultations']; if ($consultationChange > 0) { $highlights[] = __('report.highlight_growth', ['count' => $consultationChange], $locale); } elseif ($consultationChange < 0) { $highlights[] = __('report.highlight_decrease', ['count' => abs($consultationChange)], $locale); } } // No-show rate alert if ($consultationStats['no_show_rate'] > 20) { $highlights[] = __('report.highlight_noshow_alert', ['rate' => $consultationStats['no_show_rate']], $locale); } return $highlights; } }