Luca Parisio 5 ay önce
ebeveyn
işleme
65b759c936

+ 701 - 0
app/Http/Livewire/Reports.php

@@ -0,0 +1,701 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Illuminate\Support\Facades\Auth;
+use Carbon\Carbon;
+
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use App\Models\Course;
+use App\Models\MemberCard;
+use App\Http\Middleware\TenantMiddleware;
+
+class Reports extends Component
+{
+    public $type = 'anagrafica';
+    public $seasonFilter;
+
+    public $courses = [];
+    public $selectedCourse = null;
+
+    public function boot()
+    {
+        //app(TenantMiddleware::class)->setupTenantConnection();
+
+    }
+
+    public function mount()
+    {
+        if (Auth::user()->level != env('LEVEL_ADMIN', 0))
+            return redirect()->to('/reports');
+
+        if (isset($_GET["type"]))
+            $this->type = $_GET["type"];
+
+        $this->seasonFilter = $this->getCurrentSeason();
+        $this->courses = $this->getCoursesForSelect();
+    }
+
+    public function render()
+    {
+        return view('livewire.reports');
+    }
+
+
+    private function getCurrentSeason()
+    {
+        $now = Carbon::now();
+        $currentYear = $now->year;
+        if ($now->month >= 9) {
+            return $currentYear . '-' . ($currentYear + 1);
+        } else {
+            return ($currentYear - 1) . '-' . $currentYear;
+        }
+    }
+
+    public function getAvailableSeasons()
+    {
+        $seasons = [];
+        $currentYear = Carbon::now()->year;
+        $startYear = 2023;
+
+        $endYear = Carbon::now()->month >= 9 ? $currentYear + 1 : $currentYear;
+
+        for ($year = $startYear; $year < $endYear; $year++) {
+            $seasons[] = $year . '-' . ($year + 1);
+        }
+
+        return array_reverse($seasons);
+    }
+
+
+    private function parseSeason($season)
+    {
+        $parts = explode('-', $season);
+        return [
+            'start_year' => (int)$parts[0],
+            'end_year' => (int)$parts[1]
+        ];
+    }
+
+
+    private function getSeasonDateRange($season)
+    {
+        $years = $this->parseSeason($season);
+
+        return [
+            'start' => Carbon::create($years['start_year'], 9, 1),
+            'end' => Carbon::create($years['end_year'], 8, 31)
+        ];
+    }
+
+    public function setSelectedCourse($courseId)
+    {
+        $this->selectedCourse = $courseId;
+        Log::info('Selected course set to: ' . $courseId);
+        return $this->getCourseMonthlyEarnings();
+    }
+
+    public function getTesseratiData()
+    {
+        $endYear = $this->parseSeason($this->seasonFilter)['end_year'];
+        return self::getMemberCountChartData($endYear);
+    }
+
+    public function change($type)
+    {
+        $this->type = $type;
+    }
+
+    public function updateCharts()
+    {
+        $this->courses = $this->getCoursesForSelect();
+        $this->emit('chartsUpdated');
+        $this->dispatchBrowserEvent('chartsUpdated');
+    }
+
+    public function updateCourseChart()
+    {
+        $this->emit('chartsUpdated');
+        $this->dispatchBrowserEvent('chartsUpdated');
+    }
+
+    public function updatedSeasonFilter()
+    {
+        $this->courses = $this->getCoursesForSelect();
+        $this->emit('chartsUpdated');
+        $this->dispatchBrowserEvent('chartsUpdated');
+    }
+
+    public function setSeasonFilter($season)
+    {
+        $this->seasonFilter = $season;
+    }
+
+    protected function setupTenantConnection()
+    {
+        $user = auth()->user();
+
+        config(['database.connections.tenant' => [
+            'driver' => 'mysql',
+            'host' => '127.0.0.1',
+            'port' => '3306',
+            'database' => $user->tenant_database,
+            'username' => $user->tenant_username,
+            'password' => $user->tenant_password,
+        ]]);
+
+        config(['database.default' => 'tenant']);
+        DB::purge('tenant');
+        DB::reconnect('tenant');
+    }
+    public function getMonthlyTotals()
+    {
+        Log::info('=== getMonthlyTotals called ===');
+        Log::info('Current seasonFilter: ' . $this->seasonFilter);
+
+        $dateRange = $this->getSeasonDateRange($this->seasonFilter);
+
+        Log::info('Date range start: ' . $dateRange['start']);
+        Log::info('Date range end: ' . $dateRange['end']);
+        $monthOrder = [9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8];
+        $monthNames = ['Set', 'Ott', 'Nov', 'Dic', 'Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago'];
+
+        $incomeData = array_fill(0, 12, 0);
+        $expenseData = array_fill(0, 12, 0);
+        //$this->setupTenantConnection();
+
+        $incomeRecords = DB::table('records')
+            ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+            ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']])
+            ->where('records.type', 'IN')
+            ->select(DB::raw('MONTH(records.date) as month_num'), DB::raw('SUM(records_rows.amount) as total'))
+            ->groupBy('month_num')
+            ->get();
+
+        $expenseRecords = DB::table('records')
+            ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+            ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']])
+            ->where('records.type', 'OUT')
+            ->select(DB::raw('MONTH(records.date) as month_num'), DB::raw('SUM(records_rows.amount) as total'))
+            ->groupBy('month_num')
+            ->get();
+
+        foreach ($incomeRecords as $record) {
+            $monthIndex = array_search($record->month_num, $monthOrder);
+            if ($monthIndex !== false) {
+                $incomeData[$monthIndex] = $record->total;
+            }
+        }
+
+        foreach ($expenseRecords as $record) {
+            $monthIndex = array_search($record->month_num, $monthOrder);
+            if ($monthIndex !== false) {
+                $expenseData[$monthIndex] = $record->total;
+            }
+        }
+
+        Log::info('Income data: ' . json_encode($incomeData));
+        Log::info('Expense data: ' . json_encode($expenseData));
+
+        return [
+            'labels' => $monthNames,
+            'datasets' => [
+                [
+                    'label' => 'Entrate',
+                    'data' => $incomeData,
+                    'backgroundColor' => 'rgba(54, 162, 235, 0.5)'
+                ],
+                [
+                    'label' => 'Uscite',
+                    'data' => $expenseData,
+                    'backgroundColor' => 'rgba(255, 99, 132, 0.5)'
+                ],
+            ]
+        ];
+    }
+
+    public function getYearlySummary()
+    {
+        $dateRange = $this->getSeasonDateRange($this->seasonFilter);
+
+        $totalIncome = DB::table('records')
+            ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+            ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']])
+            ->where('records.type', 'IN')
+            ->sum('records_rows.amount');
+
+        $totalExpenses = DB::table('records')
+            ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+            ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']])
+            ->where('records.type', 'OUT')
+            ->sum('records_rows.amount');
+
+        $delta = $totalIncome - $totalExpenses;
+
+        return [
+            'totalIncome' => $totalIncome,
+            'totalExpenses' => $totalExpenses,
+            'delta' => $delta
+        ];
+    }
+
+    public function getTopCausalsByAmount($limit = 10)
+    {
+        $dateRange = $this->getSeasonDateRange($this->seasonFilter);
+
+        $query = DB::table('records_rows')
+            ->join('records', 'records_rows.record_id', '=', 'records.id')
+            ->join('causals', 'records_rows.causal_id', '=', 'causals.id')
+            ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']]);
+
+        $query->where('records.type', 'IN');
+
+        Log::info('Query: ' . $query->toSql());
+
+        $causals = $query->select(
+            'causals.id',
+            'causals.name',
+            'causals.parent_id',
+            DB::raw('SUM(records_rows.amount) as total_amount')
+        )
+            ->groupBy('causals.id', 'causals.name', 'causals.parent_id')
+            ->orderBy('total_amount', 'desc')
+            ->limit($limit)
+            ->get();
+
+        Log::info('Causals: ' . json_encode($causals));
+
+        $inData = [];
+
+        foreach ($causals as $causal) {
+            $tempCausal = new \App\Models\Causal();
+            $tempCausal->id = $causal->id;
+            $tempCausal->name = $causal->name;
+            $tempCausal->parent_id = $causal->parent_id;
+
+            $treeName = $tempCausal->getTree();
+
+            //$displayName = strlen($treeName) > 30 ? substr($treeName, 0, 27) . '...' : $treeName;
+            $displayName = $treeName;
+            $inData[] = [
+                'label' => $displayName,
+                'value' => $causal->total_amount,
+                'fullName' => $treeName
+            ];
+        }
+
+        usort($inData, function ($a, $b) {
+            return $b['value'] <=> $a['value'];
+        });
+
+        $inData = array_slice($inData, 0, $limit);
+
+        return [
+            'inLabels' => array_column($inData, 'label'),
+            'inData' => $inData,
+            'datasets' => [
+                [
+                    'label' => 'Entrate per Causale',
+                    'data' => array_column($inData, 'value'),
+                ]
+            ]
+        ];
+    }
+
+    public function getCoursesForSelect()
+    {
+        $seasonYears = $this->parseSeason($this->seasonFilter);
+
+        Log::info('Getting courses for season: ' . $this->seasonFilter);
+        Log::info('Season years: ' . json_encode($seasonYears));
+
+        $courses = Course::with(['level', 'frequency'])
+            ->where('active', true)
+            ->where(function ($query) use ($seasonYears) {
+                $query->where('year', $this->seasonFilter)
+                    ->orWhere('year', 'like', '%' . $seasonYears['start_year'] . '-' . $seasonYears['end_year'] . '%')
+                    ->orWhere('year', 'like', '%' . $seasonYears['start_year'] . '%')
+                    ->orWhere('year', 'like', '%' . $seasonYears['end_year'] . '%');
+            })
+            ->orderBy('name')
+            ->get()
+            ->filter(function ($course) use ($seasonYears) {
+                $courseYear = $course->year;
+
+                if ($courseYear === $this->seasonFilter) {
+                    return true;
+                }
+
+                if (
+                    str_contains($courseYear, $seasonYears['start_year']) &&
+                    str_contains($courseYear, $seasonYears['end_year'])
+                ) {
+                    return true;
+                }
+
+                if ($courseYear == $seasonYears['start_year'] || $courseYear == $seasonYears['end_year']) {
+                    return true;
+                }
+
+                return false;
+            })
+            ->map(function ($course) {
+                Log::info('Processing course: ' . $course->name . ' (ID: ' . $course->id . ')' . $course);
+
+                $levelName = is_object($course->level) ? $course->level->name : 'No Level';
+                $typeName = '';//$course->getFormattedTypeField();
+                $frequencyName = is_object($course->frequency) ? $course->frequency->name : 'No Frequency';
+                $year = $course->year ?? '';
+
+                return [
+                    'id' => $course->id,
+                    'name' => $course->name,
+                    'full_name' => "{$course->name} - {$levelName} - {$typeName} - {$frequencyName} ({$year})",
+                    'level_name' => $levelName,
+                    'type_name' => $typeName,
+                    'frequency_name' => $frequencyName,
+                    'year' => $year
+                ];
+            })->values()->toArray();
+
+        Log::info('Found ' . count($courses) . ' courses for season ' . $this->seasonFilter);
+
+        return $courses;
+    }
+
+    public function getMonthlyTotalsForSeason($season)
+    {
+        $originalSeason = $this->seasonFilter;
+        $this->seasonFilter = $season;
+
+        $result = $this->getMonthlyTotals();
+
+        $this->seasonFilter = $originalSeason;
+        return $result;
+    }
+
+
+    public function getTopCausalsByAmountForSeason($season, $limit = 10)
+    {
+        $originalSeason = $this->seasonFilter;
+        $this->seasonFilter = $season;
+
+        $result = $this->getTopCausalsByAmount($limit);
+
+        $this->seasonFilter = $originalSeason;
+        return $result;
+    }
+
+    public function getTesseratiDataForSeason($season)
+    {
+        $originalSeason = $this->seasonFilter;
+        $this->seasonFilter = $season;
+
+        $result = $this->getTesseratiData();
+
+        $this->seasonFilter = $originalSeason;
+        return $result;
+    }
+
+    public function updatedSelectedCourse()
+    {
+        Log::info('updatedSelectedCourse called with: ' . $this->selectedCourse);
+        if ($this->selectedCourse) {
+            $this->emit('courseSelected', $this->selectedCourse);
+            Log::info('Event emitted with course ID: ' . $this->selectedCourse);
+        }
+    }
+    public function getCourseData($courseId)
+    {
+        $this->selectedCourse = $courseId;
+        return $this->getCourseMonthlyEarnings($courseId);
+    }
+    public function getCourseMonthlyEarnings($courseId = null)
+    {
+        $courseId = $courseId ?? $this->selectedCourse;
+        Log::info('Getting earnings for course ID: ' . $courseId);
+
+        if (!$courseId) {
+            return [
+                'labels' => [],
+                'datasets' => [],
+                'tableData' => [],
+                'isEmpty' => true,
+                'message' => 'Seleziona un corso per visualizzare i dati'
+            ];
+        }
+
+        $monthOrder = [9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8];
+        $monthNames = [
+            9 => 'Set',
+            10 => 'Ott',
+            11 => 'Nov',
+            12 => 'Dic',
+            1 => 'Gen',
+            2 => 'Feb',
+            3 => 'Mar',
+            4 => 'Apr',
+            5 => 'Mag',
+            6 => 'Giu',
+            7 => 'Lug',
+            8 => 'Ago'
+        ];
+
+        $monthlyData = [];
+        foreach ($monthOrder as $i) {
+            $monthlyData[$i] = [
+                'earned' => 0,
+                'total' => 0,
+                'participants' => 0
+            ];
+        }
+
+        $rates = \App\Models\Rate::whereHas('member_course', function ($query) use ($courseId) {
+            $query->where('course_id', $courseId);
+        })->with('member_course')->get();
+
+        if ($rates->isEmpty()) {
+            return [
+                'labels' => [],
+                'datasets' => [],
+                'tableData' => [],
+                'isEmpty' => true,
+                'message' => 'Nessun dato disponibile per questo corso nella stagione ' . $this->seasonFilter
+            ];
+        }
+
+        $hasData = false;
+
+        foreach ($rates as $rate) {
+            $totalPrice = (float)($rate->price ?? 0);
+
+            if ($rate->months) {
+                $monthsData = json_decode($rate->months, true);
+
+                if (is_array($monthsData) && count($monthsData) > 0) {
+                    $pricePerMonth = $totalPrice / count($monthsData);
+
+                    foreach ($monthsData as $month) {
+                        $monthNumber = (int)$month;
+
+                        if (isset($monthlyData[$monthNumber])) {
+                            $monthlyData[$monthNumber]['total'] += $pricePerMonth;
+                            $monthlyData[$monthNumber]['participants']++;
+                            $hasData = true;
+
+                            if (!is_null($rate->record_id) && $rate->record_id !== '') {
+                                $monthlyData[$monthNumber]['earned'] += $pricePerMonth;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!$hasData) {
+            return [
+                'labels' => [],
+                'datasets' => [],
+                'tableData' => [],
+                'isEmpty' => true,
+                'message' => 'Nessun pagamento registrato per questo corso nella stagione ' . $this->seasonFilter
+            ];
+        }
+
+        $labels = [];
+        $earnedData = [];
+        $totalData = [];
+        $participantData = [];
+        $tableData = [];
+
+        foreach ($monthOrder as $month) {
+            $earned = round($monthlyData[$month]['earned'], 2);
+            $total = round($monthlyData[$month]['total'], 2);
+            $delta = max(0, $total - $earned);
+            $participants = $monthlyData[$month]['participants'];
+
+            $labels[] = $monthNames[$month];
+            $earnedData[] = $earned;
+            $totalData[] = $total;
+            $participantData[] = $participants;
+
+            $percentage = $total > 0 ? round(($earned / $total) * 100, 1) : 0;
+
+            $tableData[] = [
+                'month' => $monthNames[$month],
+                'participants' => $participants,
+                'earned' => $earned,
+                'total' => $total,
+                'delta' => $delta,
+                'percentage' => $percentage
+            ];
+        }
+
+        return [
+            'labels' => $labels,
+            'datasets' => [
+                [
+                    'label' => 'Pagamenti Effettuati',
+                    'backgroundColor' => 'rgba(16, 185, 129, 0.8)',
+                    'borderColor' => 'rgba(16, 185, 129, 1)',
+                    'borderWidth' => 0,
+                    'borderRadius' => 8,
+                    'borderSkipped' => false,
+                    'data' => $earnedData,
+                    'type' => 'bar',
+                    'order' => 2
+                ],
+                [
+                    'label' => 'Pagamenti Attesi',
+                    'backgroundColor' => 'transparent',
+                    'borderColor' => 'rgba(59, 130, 246, 1)',
+                    'borderWidth' => 3,
+                    'pointBackgroundColor' => 'rgba(59, 130, 246, 1)',
+                    'pointBorderColor' => '#ffffff',
+                    'pointBorderWidth' => 3,
+                    'pointRadius' => 7,
+                    'pointHoverRadius' => 9,
+                    'data' => $totalData,
+                    'type' => 'line',
+                    'tension' => 0.3,
+                    'order' => 1,
+                    'participantData' => $participantData
+                ]
+            ],
+            'tableData' => $tableData,
+            'isEmpty' => false
+        ];
+    }
+
+    public static function getMemberCountChartData($endYear = null, $span = 5)
+    {
+        if ($endYear === null) {
+            $endYear = date('Y');
+        }
+
+        $startYear = $endYear - $span + 1;
+
+        $memberCards = MemberCard::select('member_id', 'expire_date', 'card_id')
+            ->with('card:id,name')
+            ->whereNotNull('expire_date')
+            ->whereNotNull('member_id')
+            ->whereNotNull('card_id')
+            ->where('status', '!=', 'cancelled')
+            ->whereRaw('YEAR(expire_date) >= ?', [$startYear])
+            ->whereRaw('YEAR(expire_date) <= ?', [$endYear])
+            ->get();
+
+        $cardTypes = $memberCards->pluck('card.name')->unique()->filter()->sort()->values();
+
+        $seasonCounts = [];
+        $seasonCardCounts = [];
+
+        for ($year = $startYear; $year <= $endYear; $year++) {
+            $seasonPeriod = ($year - 1) . '-' . $year;
+            $seasonCounts[$seasonPeriod] = [];
+            $seasonCardCounts[$seasonPeriod] = [];
+
+            foreach ($cardTypes as $cardType) {
+                $seasonCardCounts[$seasonPeriod][$cardType] = [];
+            }
+        }
+
+        foreach ($memberCards as $card) {
+            $expireYear = date('Y', strtotime($card->expire_date));
+            $expireMonth = date('n', strtotime($card->expire_date));
+
+            if ($expireMonth >= 9) {
+                $seasonPeriod = $expireYear . '-' . ($expireYear + 1);
+            } else {
+                $seasonPeriod = ($expireYear - 1) . '-' . $expireYear;
+            }
+
+            if (isset($seasonCounts[$seasonPeriod])) {
+                $seasonCounts[$seasonPeriod][$card->member_id] = true;
+
+                $cardTypeName = $card->card->name ?? 'Unknown';
+                if (isset($seasonCardCounts[$seasonPeriod][$cardTypeName])) {
+                    $seasonCardCounts[$seasonPeriod][$cardTypeName][$card->member_id] = true;
+                }
+            }
+        }
+
+        $seasonLabels = [];
+        $memberCountData = [];
+        $cardTypeDatasets = [];
+
+        $colors = [
+            'rgba(255, 99, 132, 0.2)',
+            'rgba(54, 162, 235, 0.2)',
+            'rgba(255, 205, 86, 0.2)',
+            'rgba(75, 192, 192, 0.2)',
+            'rgba(153, 102, 255, 0.2)',
+            'rgba(255, 159, 64, 0.2)',
+            'rgba(199, 199, 199, 0.2)',
+            'rgba(83, 102, 255, 0.2)',
+        ];
+
+        $borderColors = [
+            'rgba(255, 99, 132, 1)',
+            'rgba(54, 162, 235, 1)',
+            'rgba(255, 205, 86, 1)',
+            'rgba(75, 192, 192, 1)',
+            'rgba(153, 102, 255, 1)',
+            'rgba(255, 159, 64, 1)',
+            'rgba(199, 199, 199, 1)',
+            'rgba(83, 102, 255, 1)',
+        ];
+
+        foreach ($cardTypes as $index => $cardType) {
+            $cardTypeDatasets[$cardType] = [
+                'label' => $cardType,
+                'data' => [],
+                'backgroundColor' => $colors[$index % count($colors)],
+                'borderColor' => $borderColors[$index % count($borderColors)],
+                'borderWidth' => 2,
+                'pointBackgroundColor' => $borderColors[$index % count($borderColors)],
+                'pointRadius' => 4,
+                'tension' => 0.3,
+                'fill' => true
+            ];
+        }
+
+        foreach ($seasonCounts as $seasonPeriod => $members) {
+            $seasonLabels[] = $seasonPeriod;
+            $memberCountData[] = count($members);
+
+            foreach ($cardTypes as $cardType) {
+                $cardTypeCount = isset($seasonCardCounts[$seasonPeriod][$cardType])
+                    ? count($seasonCardCounts[$seasonPeriod][$cardType])
+                    : 0;
+                $cardTypeDatasets[$cardType]['data'][] = $cardTypeCount;
+            }
+        }
+
+        $datasets = [
+            [
+                'label' => 'Totale Membri Tesserati',
+                'data' => $memberCountData,
+                'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
+                'borderColor' => 'rgba(54, 162, 235, 1)',
+                'borderWidth' => 3,
+                'pointBackgroundColor' => 'rgba(54, 162, 235, 1)',
+                'pointRadius' => 6,
+                'tension' => 0.3,
+                'fill' => true,
+                'type' => 'line'
+            ]
+        ];
+        foreach ($cardTypeDatasets as $dataset) {
+            $datasets[] = $dataset;
+        }
+
+        return [
+            'labels' => $seasonLabels,
+            'datasets' => $datasets
+        ];
+    }
+}

+ 982 - 0
public/css/chart-reports.css

@@ -0,0 +1,982 @@
+@charset "UTF-8";
+
+:root {
+    --primary-color: #6366f1;
+    --primary-light: #818cf8;
+    --primary-dark: #4f46e5;
+    --secondary-color: #64748b;
+    --success-color: #10b981;
+    --success-light: #34d399;
+    --info-color: #06b6d4;
+    --warning-color: #f59e0b;
+    --danger-color: #ef4444;
+    --danger-light: #f87171;
+    --dark-color: #1e293b;
+    --light-color: #f8fafc;
+    --border-color: #e2e8f0;
+    --border-light: #f1f5f9;
+    --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+    --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+    --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    --gradient-success: linear-gradient(135deg, #10b981 0%, #059669 100%);
+    --gradient-danger: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+}
+
+.dashboard-container {
+    padding: 2rem;
+    max-width: 1600px;
+    margin: 0 auto;
+    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+    min-height: 100vh;
+}
+
+.dashboard-header {
+    text-align: center;
+    margin-bottom: 3rem;
+    padding: 3rem 2rem;
+    background: var(--gradient-primary);
+    border-radius: 24px;
+    color: white;
+    position: relative;
+    overflow: hidden;
+}
+
+.dashboard-header::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="white" opacity="0.1"/><circle cx="75" cy="75" r="1" fill="white" opacity="0.1"/><circle cx="50" cy="10" r="0.5" fill="white" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
+    pointer-events: none;
+}
+
+.dashboard-header h1 {
+    font-size: 3rem;
+    font-weight: 800;
+    margin-bottom: 0.75rem;
+    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    position: relative;
+    z-index: 1;
+}
+
+.dashboard-header p {
+    font-size: 1.25rem;
+    opacity: 0.9;
+    position: relative;
+    z-index: 1;
+}
+
+.controls-section {
+    background: rgba(255, 255, 255, 0.8);
+    backdrop-filter: blur(10px);
+    border-radius: 20px;
+    padding: 2rem;
+    box-shadow: var(--shadow);
+    border: 1px solid rgba(255, 255, 255, 0.2);
+    margin-bottom: 3rem;
+    display: flex;
+    gap: 2rem;
+    align-items: center;
+    flex-wrap: wrap;
+}
+
+.control-group {
+    display: flex;
+    flex-direction: column;
+    gap: 0.75rem;
+}
+
+.control-group label {
+    font-weight: 700;
+    font-size: 0.875rem;
+    color: var(--dark-color);
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+}
+
+.form-select {
+    border-radius: 16px;
+    border: 2px solid var(--border-color);
+    padding: 1rem 1.25rem;
+    font-size: 1rem;
+    font-weight: 500;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    min-width: 250px;
+    background: white;
+    box-shadow: var(--shadow-sm);
+}
+
+.form-select:focus {
+    border-color: var(--primary-color);
+    box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
+    outline: none;
+    transform: translateY(-1px);
+}
+
+.summary-cards {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+    gap: 2rem;
+    margin-bottom: 3rem;
+}
+
+.summary-card {
+    background: rgba(255, 255, 255, 0.9);
+    backdrop-filter: blur(10px);
+    border-radius: 24px;
+    padding: 2.5rem;
+    box-shadow: var(--shadow);
+    border: 1px solid rgba(255, 255, 255, 0.2);
+    text-align: center;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    position: relative;
+    overflow: hidden;
+}
+
+.summary-card::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 4px;
+    transition: all 0.3s ease;
+}
+
+.summary-card.income::before {
+    background: var(--gradient-success);
+}
+
+.summary-card.expense::before {
+    background: var(--gradient-danger);
+}
+
+.summary-card.delta::before {
+    background: var(--gradient-primary);
+}
+
+.summary-card.delta.negative::before {
+    background: var(--gradient-danger);
+}
+
+.summary-card:hover {
+    transform: translateY(-8px);
+    box-shadow: var(--shadow-lg);
+}
+
+.summary-card h3 {
+    font-size: 0.875rem;
+    font-weight: 700;
+    color: var(--secondary-color);
+    text-transform: uppercase;
+    letter-spacing: 1px;
+    margin-bottom: 1rem;
+}
+
+.summary-card .value {
+    font-size: 2.5rem;
+    font-weight: 900;
+    margin-bottom: 0.5rem;
+    line-height: 1;
+}
+
+.summary-card.income .value {
+    background: var(--gradient-success);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+}
+
+.summary-card.expense .value {
+    background: var(--gradient-danger);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+}
+
+.summary-card.delta .value {
+    background: var(--gradient-primary);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+}
+
+.summary-card.delta.negative .value {
+    background: var(--gradient-danger);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+}
+
+.chart-row {
+    width: 100%;
+    margin-bottom: 3rem;
+}
+
+.chart-card {
+    background: rgba(255, 255, 255, 0.9);
+    backdrop-filter: blur(10px);
+    border-radius: 24px;
+    box-shadow: var(--shadow);
+    border: 1px solid rgba(255, 255, 255, 0.2);
+    overflow: hidden;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.chart-card:hover {
+    box-shadow: var(--shadow-lg);
+    transform: translateY(-2px);
+}
+
+.chart-header {
+    padding: 2rem 2.5rem 1.5rem;
+    border-bottom: 1px solid var(--border-light);
+    background: linear-gradient(135deg, rgba(248, 250, 252, 0.8) 0%, rgba(255, 255, 255, 0.9) 100%);
+}
+
+.chart-title {
+    font-size: 1.5rem;
+    font-weight: 700;
+    color: var(--dark-color);
+    margin: 0;
+}
+
+.chart-body {
+    padding: 2.5rem;
+}
+
+.chart-container {
+    position: relative;
+    height: 450px !important;
+    width: 90%;
+    border-radius: 16px;
+    overflow: hidden;
+}
+
+.chart-container canvas {
+    max-height: 450px !important;
+    height: 450px !important;
+}
+
+.course-controls {
+    background: linear-gradient(135deg, rgba(248, 250, 252, 0.8) 0%, rgba(255, 255, 255, 0.9) 100%);
+    border-radius: 20px;
+    padding: 2rem;
+    margin-bottom: 2rem;
+    border: 1px solid var(--border-light);
+}
+
+.legend-container {
+    display: flex;
+    gap: 2rem;
+    margin-top: 1.5rem;
+    flex-wrap: wrap;
+}
+
+.legend-item {
+    display: flex;
+    align-items: center;
+    gap: 0.75rem;
+    font-size: 0.9rem;
+    font-weight: 600;
+    color: var(--dark-color);
+}
+
+.legend-color {
+    width: 16px;
+    height: 16px;
+    border-radius: 50%;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.monthly-table-container {
+    background: white;
+    border: 1px solid #F6F7FF;
+    border-radius: 12px;
+    box-shadow: var(--shadow-sm);
+}
+
+.members-table-container {
+    background: white;
+    border: 1px solid #F6F7FF;
+    border-radius: 12px;
+    box-shadow: var(--shadow-sm);
+}
+
+.monthly-table,
+.members-table {
+    font-size: 0.775rem;
+}
+
+.members-table .table-header {
+    display: grid;
+    grid-template-columns: 1fr 0.8fr 1fr 1.5fr;
+    gap: 0.5rem;
+    margin-bottom: 0.5rem;
+    padding-bottom: 0.5rem;
+    border-bottom: 2px solid var(--border-color);
+}
+
+
+.members-table .table-row {
+    display: grid;
+    grid-template-columns: 1fr 0.8fr 1fr 1.5fr;
+    gap: 0.5rem;
+    padding: 0.5rem 0;
+    border-bottom: 1px solid #F6F7FF;
+    transition: background-color 0.2s ease;
+    align-items: start;
+}
+
+.table-cell.season-name {
+    text-align: center;
+    font-weight: 500;
+    font-size: 0.7rem;
+}
+
+.table-cell.members-count {
+    text-align: center;
+    font-weight: 600;
+    color: var(--primary-color);
+}
+
+.table-cell.variation {
+    text-align: center;
+    font-size: 0.7rem;
+}
+
+.variation-positive {
+    color: var(--success-color);
+    font-weight: 600;
+}
+
+.variation-negative {
+    color: var(--danger-color);
+    font-weight: 600;
+}
+
+.variation-neutral {
+    color: var(--secondary-color);
+    font-weight: 500;
+}
+
+.monthly-table {
+    font-size: 0.775rem;
+}
+
+.table-header {
+    display: grid;
+    grid-template-columns: 1fr 1fr 1fr 1fr;
+    gap: 0.5rem;
+    margin-bottom: 0.5rem;
+    padding-bottom: 0.5rem;
+    border-bottom: 2px solid var(--border-color);
+}
+
+.table-row {
+    display: grid;
+    grid-template-columns: 1fr 1fr 1fr 1fr;
+    gap: 0.5rem;
+    padding: 0.2rem 0;
+    border-bottom: 1px solid #e9ecef;
+    transition: background-color 0.2s ease;
+}
+
+.table-row:hover {
+    background-color: rgba(0, 0, 0, 0.02);
+}
+
+.table-row.positive {
+    background-color: rgba(0, 184, 148, 0.05);
+}
+
+.table-row.negative {
+    background-color: rgba(255, 107, 107, 0.05);
+}
+
+.table-row.neutral {
+    background-color: white;
+}
+
+.table-cell {
+    padding: 0.25rem 0.5rem;
+    text-align: right;
+}
+
+.table-header .table-cell {
+    font-weight: 600;
+    color: var(--secondary-color);
+    text-align: center;
+    text-transform: uppercase;
+    font-size: 0.75rem;
+    letter-spacing: 0.5px;
+}
+
+.table-cell.month-name {
+    text-align: left;
+    font-weight: 600;
+}
+
+.table-cell.income {
+    color: var(--success-color);
+    font-weight: 500;
+}
+
+.table-cell.expense {
+    color: var(--danger-color);
+    font-weight: 500;
+}
+
+.table-cell.net {
+    font-weight: 600;
+}
+
+.table-row.positive .table-cell.net {
+    color: var(--success-color);
+}
+
+.table-row.negative .table-cell.net {
+    color: var(--danger-color);
+}
+
+.table-row.neutral .table-cell.net {
+    color: var(--secondary-color);
+}
+
+@media (max-width: 1024px) {
+    .chart-body>div[style*="grid-template-columns"] {
+        grid-template-columns: 1fr !important;
+        gap: 1rem !important;
+    }
+
+    .monthly-table-container {
+        order: 2;
+    }
+}
+
+.loading {
+    text-align: center;
+    padding: 2rem;
+    color: var(--secondary-color);
+}
+
+.error-message {
+    background: #fff5f5;
+    border: 1px solid #fed7d7;
+    color: #c53030;
+    padding: 1rem;
+    border-radius: 8px;
+    margin: 1rem 0;
+}
+
+@media (max-width: 768px) {
+    .dashboard-container {
+        padding: 0.5rem;
+    }
+
+    .dashboard-header h1 {
+        font-size: 2rem;
+    }
+
+    .controls-section {
+        flex-direction: column;
+        align-items: stretch;
+        gap: 1rem;
+    }
+
+    .chart-body {
+        padding: 1rem;
+    }
+
+    .chart-container {
+        height: 300px !important;
+    }
+
+    .chart-container canvas {
+        max-height: 300px !important;
+        height: 300px !important;
+    }
+
+    .legend-container {
+        gap: 1rem;
+    }
+}
+
+.modern-course-card {
+    background: rgb(255, 255, 255, 0.9);
+    border: 1px solid #e2e8f0;
+    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+}
+
+.modern-select {
+    background: white;
+    border: 2px solid #e2e8f0;
+    border-radius: 12px;
+    padding: 12px 16px;
+    font-weight: 500;
+    transition: all 0.3s ease;
+}
+
+.modern-select:focus {
+    border-color: #3b82f6;
+    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+    outline: none;
+}
+
+.modern-chart-layout {
+    display: grid;
+    grid-template-columns: 280px 1fr;
+    gap: 24px;
+    align-items: start;
+    margin-top: 20px;
+}
+
+.course-delta-table {
+    background: white;
+    border: 1px solid #F6F7FF;
+    border-radius: 16px;
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
+}
+
+.course-table-header {
+    background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+    padding: 16px 20px;
+    color: white;
+}
+
+.course-table-header h4 {
+    margin: 0;
+    font-size: 1rem;
+    font-weight: 600;
+}
+
+.course-table {
+    max-height: 500px;
+    overflow-y: auto;
+}
+
+.course-table .table-header {
+    display: grid;
+    grid-template-columns: 1fr 40px 80px 50px;
+    background: white;
+    padding: 12px 16px;
+    font-weight: 600;
+    font-size: 0.875rem;
+    color: #374151;
+    border-bottom: 1px solid #F6F7FF;
+}
+
+.course-table .table-row {
+    display: grid;
+    grid-template-columns: 1fr 40px 80px 50px;
+    padding: 12px 16px;
+    border-bottom: 1px solid #f3f4f6;
+    transition: background-color 0.2s;
+}
+
+.course-table .table-row:hover {
+    background: #f9fafb;
+}
+
+.course-table .table-row:last-child {
+    border-bottom: none;
+}
+
+.course-table .table-cell {
+    font-size: 0.875rem;
+    display: flex;
+    align-items: center;
+}
+
+.course-table .table-cell.month {
+    font-weight: 600;
+    color: #111827;
+}
+
+.course-table .table-cell.participants {
+    justify-content: center;
+    color: #6b7280;
+    font-weight: 500;
+}
+
+.course-table .table-cell.delta {
+    justify-content: flex-end;
+    font-weight: 600;
+}
+
+.course-table .table-cell.delta.positive {
+    color: #059669;
+}
+
+.course-table .table-cell.delta.negative {
+    color: #dc2626;
+}
+
+.course-table .table-cell.delta.neutral {
+    color: #6b7280;
+}
+
+.course-table .table-cell.percentage {
+    justify-content: center;
+    font-weight: 600;
+    font-size: 0.8rem;
+}
+
+.course-table .table-cell.percentage.good {
+    color: #059669;
+}
+
+.course-table .table-cell.percentage.warning {
+    color: #d97706;
+}
+
+.course-table .table-cell.percentage.bad {
+    color: #dc2626;
+}
+
+.modern-chart-container {
+    background: white;
+    border-radius: 16px;
+    border: 1px solid #e5e7eb;
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
+    padding: 20px;
+}
+
+.chart-placeholder {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    min-height: 400px;
+    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+    border-radius: 16px;
+    border: 2px dashed #cbd5e1;
+    margin-top: 20px;
+}
+
+.placeholder-content {
+    text-align: center;
+    color: #64748b;
+}
+
+.placeholder-icon {
+    width: 48px;
+    height: 48px;
+    margin: 0 auto 16px;
+    opacity: 0.5;
+}
+
+.placeholder-content p {
+    font-size: 1.1rem;
+    font-weight: 500;
+    margin: 0;
+}
+
+@media (max-width: 1024px) {
+    .modern-chart-layout {
+        grid-template-columns: 1fr;
+        gap: 16px;
+    }
+
+    .course-delta-table {
+        order: 2;
+    }
+
+    .modern-chart-container {
+        order: 1;
+    }
+}
+
+
+.modern-chart-container {
+    background: white;
+    border-radius: 16px;
+    border: 1px solid #e5e7eb;
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
+    padding: 20px;
+    height: 500px;
+}
+
+.modern-chart-container canvas {
+    height: 460px !important;
+}
+
+.modern-chart-layout {
+    display: grid;
+    grid-template-columns: 280px 1fr;
+    gap: 24px;
+    align-items: start;
+    margin-top: 20px;
+    min-height: 500px;
+}
+.causals-table-container {
+    background: white;
+    border: 1px solid #F6F7FF;
+    border-radius: 12px;
+    box-shadow: var(--shadow-sm);
+}
+
+.causale-indicator {
+    display: inline-block;
+    width: 12px;
+    height: 12px;
+    border-radius: 50%;
+    margin-right: 8px;
+}
+
+.table-cell.causale-name {
+    text-align: left;
+    font-weight: 500;
+    display: flex;
+    align-items: center;
+}
+
+.causals-table.compact {
+    font-size: 0.875rem;
+}
+
+.causals-table.compact .table-header {
+    display: grid;
+    grid-template-columns: 2fr 60px 45px;
+    gap: 8px;
+    padding: 8px 12px;
+    font-size: 0.7rem;
+}
+
+.causals-table.compact .table-row {
+    display: grid;
+    grid-template-columns: 2fr 60px 45px;
+    gap: 8px;
+    padding: 6px 12px;
+    font-size: 0.75rem;
+}
+
+.causals-table.compact .table-cell.causale {
+    text-align: left;
+    font-weight: 500;
+    display: flex;
+    align-items: center;
+    min-width: 0;
+    overflow: hidden;
+}
+
+.causals-table.compact .table-cell.euro {
+    text-align: right;
+    font-weight: 600;
+    color: var(--success-color);
+    font-size: 0.7rem;
+}
+
+.causals-table.compact .table-cell.percent {
+    text-align: center;
+    font-weight: 600;
+    color: var(--primary-color);
+    font-size: 0.7rem;
+}
+
+.causals-table.compact .causale-indicator {
+    width: 8px;
+    height: 8px;
+    margin-right: 6px;
+    flex-shrink: 0;
+}
+
+.monthly-table .table-row,
+.members-table .table-row,
+.causals-table.compact .table-row {
+    border-bottom: 1px solid #F6F7FF;
+}
+
+.course-table .table-row {
+    border-bottom: 1px solid #F6F7FF;
+}
+
+.monthly-table .table-header,
+.members-table .table-header,
+.causals-table.compact .table-header {
+    border-bottom: 2px solid #F6F7FF;
+}
+
+.table-cell.card-types {
+    text-align: left;
+    padding: 0.25rem 0.5rem;
+}
+
+.card-types-container {
+    display: flex;
+    flex-direction: column;
+    gap: 0.25rem;
+    max-height: 120px;
+    overflow-y: auto;
+}
+
+.card-type-item {
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+    font-size: 0.75rem;
+    padding: 0.25rem 0;
+    min-height: 20px;
+}
+
+.card-type-indicator {
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    flex-shrink: 0;
+    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.card-type-name {
+    flex: 1;
+    font-weight: 500;
+    color: #374151;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.card-type-count {
+    font-weight: 600;
+    color: #6366f1;
+    min-width: 20px;
+    text-align: right;
+    font-size: 0.7rem;
+}
+
+.no-card-types {
+    font-size: 0.75rem;
+    color: #9ca3af;
+    font-style: italic;
+    padding: 0.25rem 0;
+}
+
+/* Responsive adjustments for smaller screens */
+@media (max-width: 1024px) {
+    .members-table .table-header {
+        grid-template-columns: 1fr;
+        gap: 0.25rem;
+    }
+
+    .members-table .table-row {
+        grid-template-columns: 1fr;
+        gap: 0.25rem;
+        padding: 1rem 0;
+        border-bottom: 2px solid #F6F7FF;
+    }
+
+    .table-cell::before {
+        content: attr(data-label) ": ";
+        font-weight: 600;
+        color: #6b7280;
+        text-transform: uppercase;
+        font-size: 0.75rem;
+        letter-spacing: 0.5px;
+    }
+
+    .table-cell.season-name::before {
+        content: "Stagione: ";
+    }
+
+    .table-cell.members-count::before {
+        content: "Tesserati: ";
+    }
+
+    .table-cell.variation::before {
+        content: "Variazione: ";
+    }
+
+    .table-cell.card-types::before {
+        content: "Tipologie: ";
+    }
+
+    .card-types-container {
+        margin-top: 0.5rem;
+        max-height: none;
+        overflow-y: visible;
+    }
+
+    .card-type-item {
+        background: #f8f9fa;
+        border-radius: 6px;
+        padding: 0.5rem;
+        margin-bottom: 0.25rem;
+    }
+}
+
+/* Scrollbar styling for card types container */
+.card-types-container::-webkit-scrollbar {
+    width: 4px;
+}
+
+.card-types-container::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 2px;
+}
+
+.card-types-container::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 2px;
+}
+
+.card-types-container::-webkit-scrollbar-thumb:hover {
+    background: #a8a8a8;
+}
+
+.chart-body,
+.chart-body *,
+.modern-chart-layout,
+.modern-chart-layout * {
+    box-sizing: border-box;
+    max-width: 100%;
+}
+
+.chart-container canvas,
+.modern-chart-container canvas {
+    max-width: 100% !important;
+    width: 100% !important;
+    height: auto !important;
+    display: block;
+}
+
+.dashboard-container,
+.chart-card,
+.chart-body {
+    overflow-x: hidden;
+    max-width: 100%;
+}
+
+#courses-chart-\2024 2025-\*,
+canvas[id^="courses-chart-"] {
+    max-width: 100% !important;
+    width: 100% !important;
+    box-sizing: border-box;
+}
+
+.modern-chart-layout {
+    display: grid;
+    grid-template-columns: minmax(280px, 280px) minmax(0, 1fr);
+    gap: 24px;
+    align-items: start;
+    margin-top: 20px;
+    min-height: 500px;
+    max-width: 100%;
+    overflow: hidden;
+    width: 100%;
+}
+
+.modern-chart-container {
+    background: white;
+    border-radius: 16px;
+    border: 1px solid #e5e7eb;
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
+    padding: 20px;
+    height: 500px;
+    max-width: 100%;
+    min-width: 0;
+    overflow: hidden;
+    position: relative;
+    width: 100%;
+}

+ 11 - 0
resources/views/layouts/app.blade.php

@@ -202,6 +202,8 @@
                 print "Metodi di pagamento";
             if (Request::is('users'))
                 print "Profilo utenti";
+            if (Request::is('reports'))
+                print "Reports";
             @endphp
             </h3>
 
@@ -362,6 +364,15 @@
                                 </div>
                             @endif
                         @endif
+                        @if(Auth::user()->level == env('LEVEL_ADMIN', 0))
+                            <div class="accordion-item " style="{{Request::is('reports') ? 'background-color: #c5d9e6;' : ''}}">
+                                <h2 class="accordion-header linkMenu">
+                                    <a class="accordion-button collapsed" href="/reports">
+                                        Reports
+                                    </a>
+                                </h2>
+                            </div>
+                        @endif
                     </div>
                     </div>
                 </div>

+ 1132 - 0
resources/views/livewire/reports.blade.php

@@ -0,0 +1,1132 @@
+{{-- resources/views/livewire/reports.blade.php --}}
+<div>
+    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
+    <link rel="stylesheet" href="{{ asset('css/chart-reports.css') }}">
+
+    <div class="dashboard-container">
+        <div class="controls-section">
+            <div class="control-group">
+                <label for="season-filter">Stagione di Riferimento:</label>
+                <select class="form-select" wire:model="seasonFilter" wire:change="updateCharts">
+                    @foreach($this->getAvailableSeasons() as $season)
+                        <option value="{{ $season }}">{{ $season }}</option>
+                    @endforeach
+                </select>
+            </div>
+        </div>
+
+        @php
+            $summary = $this->getYearlySummary();
+        @endphp
+        <div class="summary-cards">
+            <div class="summary-card income">
+                <h3>Entrate Totali</h3>
+                <div class="value">€{{ number_format($summary['totalIncome'], 2, ',', '.') }}</div>
+            </div>
+            <div class="summary-card expense">
+                <h3>Uscite Totali</h3>
+                <div class="value">€{{ number_format($summary['totalExpenses'], 2, ',', '.') }}</div>
+            </div>
+            <div class="summary-card delta {{ $summary['delta'] < 0 ? 'negative' : '' }}">
+                <h3>Bilancio Netto</h3>
+                <div class="value">€{{ number_format($summary['delta'], 2, ',', '.') }}</div>
+            </div>
+        </div>
+
+        <div wire:ignore>
+            <div class="chart-row">
+                <div class="chart-card">
+                    <div class="chart-header">
+                        <h3 class="chart-title">Entrate e Uscite Mensili - <span
+                                id="monthly-season-title">{{ $seasonFilter }}</span></h3>
+                    </div>
+                    <div class="chart-body">
+                        <div style="display: grid; grid-template-columns: 1fr 300px; align-items: start;">
+                            <div class="chart-container">
+                                <canvas id="monthly-chart-{{ str_replace('-', '', $seasonFilter) }}"></canvas>
+                            </div>
+                            <div class="monthly-table-container" id="monthly-table">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="chart-row">
+                <div class="chart-card">
+                    <div class="chart-header">
+                        <h3 class="chart-title">Causali Performanti - <span
+                                id="causals-season-title">{{ $seasonFilter }}</span></h3>
+                    </div>
+                    <div class="chart-body">
+                        <div style="display: grid; grid-template-columns: 1fr 700px; gap: 1rem; align-items: start;">
+                            <div class="causals-table-container" id="causals-table">
+                            </div>
+
+                            <div class="chart-container">
+                                <canvas id="causals-chart-{{ str_replace('-', '', $seasonFilter) }}"></canvas>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="chart-row">
+                <div class="chart-card">
+                    <div class="chart-header">
+                        <h3 class="chart-title">Tesserati per Stagione</h3>
+                    </div>
+                    <div class="chart-body">
+                        <div style="display: grid; grid-template-columns: 1fr 500px; gap: 1rem; align-items: start;">
+                            <div class="chart-container">
+                                <canvas id="members-chart-{{ str_replace('-', '', $seasonFilter) }}"></canvas>
+                            </div>
+                            <div class="members-table-container" id="members-table">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        @if(false)
+            <div class="chart-row">
+                <div class="chart-card modern-course-card">
+                    <div class="chart-header">
+                        <h3 class="chart-title">Analisi Corsi</h3>
+                    </div>
+                    <div class="chart-body">
+                        <div class="course-controls">
+                            <div class="control-group">
+                                <label>Seleziona Corso ({{ $seasonFilter }}):</label>
+                                <select class="form-select modern-select" wire:model.live="selectedCourse">
+                                    <option value="">Seleziona un Corso</option>
+                                    @foreach($this->getCoursesForSelect() as $course)
+                                        <option value="{{ $course['id'] }}">{{ $course['full_name'] }}</option>
+                                    @endforeach
+                                </select>
+                            </div>
+                        </div>
+
+                        @if($selectedCourse)
+                            <div wire:ignore wire:key="course-chart-{{ $seasonFilter }}-{{ $selectedCourse }}">
+                                <div class="modern-chart-layout">
+                                    <div class="course-delta-table"
+                                        id="course-delta-table-{{ str_replace('-', '', $seasonFilter) }}-{{ $selectedCourse }}">
+                                    </div>
+
+                                    <div class="modern-chart-container">
+                                        <canvas
+                                            id="courses-chart-{{ str_replace('-', '', $seasonFilter) }}-{{ $selectedCourse }}"></canvas>
+                                    </div>
+                                </div>
+                            </div>
+                        @else
+                            <div class="chart-placeholder">
+                                <div style="text-align: center;">
+                                    <div style="font-size: 3rem; margin-bottom: 1rem; opacity: 0.3;">📊</div>
+                                    <p style="font-size: 1.25rem; font-weight: 600; margin: 0;">Seleziona un corso per
+                                        visualizzare il grafico</p>
+                                    <p style="font-size: 1rem; opacity: 0.7; margin-top: 0.5rem;">Usa il menu a tendina sopra
+                                        per scegliere un corso</p>
+                                </div>
+                            </div>
+                        @endif
+                    </div>
+                </div>
+            </div>
+        @endif
+    </div>
+
+    <script>
+        window.ReportsChartManager = window.ReportsChartManager || {
+            charts: {},
+            currentSeason: null,
+
+            destroyChart: function (chartId) {
+                if (this.charts[chartId]) {
+                    this.charts[chartId].destroy();
+                    delete this.charts[chartId];
+                }
+            },
+
+            destroyAllCharts: function () {
+                Object.keys(this.charts).forEach(chartId => {
+                    this.destroyChart(chartId);
+                });
+            },
+
+            destroySeasonCharts: function (oldSeasonKey) {
+                const chartsToDestroy = Object.keys(this.charts).filter(chartId =>
+                    chartId.includes(oldSeasonKey)
+                );
+                chartsToDestroy.forEach(chartId => this.destroyChart(chartId));
+            },
+
+            updateMainCharts: function () {
+                console.log('=== updateMainCharts called ===');
+
+                const seasonFilter = @this.get('seasonFilter');
+
+                const monthlyTitle = document.getElementById('monthly-season-title');
+                const causalsTitle = document.getElementById('causals-season-title');
+                if (monthlyTitle) {
+                    monthlyTitle.textContent = seasonFilter;
+                }
+                if (causalsTitle) {
+                    causalsTitle.textContent = seasonFilter;
+                }
+
+                const originalSeasonKey = '{{ str_replace('-', '', $seasonFilter) }}';
+                console.log('Using original season key for canvas IDs:', originalSeasonKey);
+
+                @this.call('getMonthlyTotals').then(monthlyData => {
+                    console.log('Got monthly data:', monthlyData);
+                    this.createMonthlyChart(originalSeasonKey, monthlyData);
+                    this.updateMonthlyTable(monthlyData);
+                });
+
+                @this.call('getTopCausalsByAmount').then(causalsData => {
+                    console.log('Got causals data:', causalsData);
+                    this.createCausalsChart(originalSeasonKey, causalsData);
+                });
+
+                @this.call('getTesseratiData').then(membersData => {
+                    console.log('Got members data:', membersData);
+                    this.createMembersChart(originalSeasonKey, membersData);
+                    this.updateMembersTable(membersData);
+                });
+            },
+            forceUpdateCharts: function () {
+                console.log('Force updating charts...');
+                this.currentSeason = null;
+                this.updateMainCharts();
+            },
+
+            createMonthlyChart: function (seasonKey, monthlyData) {
+                const chartId = `monthly-chart-${seasonKey}`;
+                const canvas = document.getElementById(chartId);
+                if (!canvas) {
+                    console.error('Canvas not found for ID:', chartId);
+                    return;
+                }
+
+                this.destroyChart(chartId);
+
+                const ctx = canvas.getContext('2d');
+
+                const incomeGradient = ctx.createLinearGradient(0, 0, 0, 400);
+                incomeGradient.addColorStop(0, 'rgba(0, 184, 148, 1)');
+                incomeGradient.addColorStop(1, 'rgba(0, 184, 148, 1)');
+
+                const expenseGradient = ctx.createLinearGradient(0, 0, 0, 400);
+                expenseGradient.addColorStop(0, 'rgba(255, 107, 107, 1)');
+                expenseGradient.addColorStop(1, 'rgba(255, 107, 107, 1)');
+
+                this.charts[chartId] = new Chart(ctx, {
+                    type: 'bar',
+                    data: {
+                        labels: monthlyData.labels,
+                        datasets: [
+                            {
+                                label: 'Entrate',
+                                data: monthlyData.datasets[0].data,
+                                backgroundColor: incomeGradient,
+                                borderColor: '#00b894',
+                                borderWidth: 2,
+                                borderRadius: 12,
+                                borderSkipped: false,
+                            },
+                            {
+                                label: 'Uscite',
+                                data: monthlyData.datasets[1].data,
+                                backgroundColor: expenseGradient,
+                                borderColor: '#ff6b6b',
+                                borderWidth: 2,
+                                borderRadius: 12,
+                                borderSkipped: false,
+                            }
+                        ]
+                    },
+                    options: {
+                        responsive: true,
+                        maintainAspectRatio: false,
+                        plugins: {
+                            legend: {
+                                position: 'top',
+                                labels: {
+                                    usePointStyle: true,
+                                    padding: 20,
+                                    font: { weight: '500' }
+                                }
+                            },
+                            tooltip: {
+                                backgroundColor: 'rgba(255, 255, 255,1)',
+                                titleColor: '#212529',
+                                bodyColor: '#495057',
+                                borderColor: '#e9ecef',
+                                borderWidth: 12,
+                                cornerRadius: 8,
+                                callbacks: {
+                                    label: function (context) {
+                                        return context.dataset.label + ': €' +
+                                            new Intl.NumberFormat('it-IT').format(context.parsed.y);
+                                    }
+                                }
+                            }
+                        },
+                        scales: {
+                            x: {
+                                grid: { display: false },
+                                ticks: { font: { weight: '500' } }
+                            },
+                            y: {
+                                beginAtZero: true,
+                                grid: { color: 'rgba(0, 0, 0, 0.05)' },
+                                ticks: {
+                                    callback: function (value) {
+                                        return '€' + new Intl.NumberFormat('it-IT').format(value);
+                                    }
+                                }
+                            }
+                        },
+                        elements: {
+                            bar: {
+                                borderRadius: {
+                                    topLeft: 12,
+                                    topRight: 12,
+                                    bottomLeft: 0,
+                                    bottomRight: 0
+                                }
+                            }
+                        },
+                        animation: {
+                            duration: 1000,
+                            easing: 'easeOutQuart'
+                        }
+                    }
+                });
+            },
+            createCausalsChart: function (seasonKey, causalsData) {
+                const chartId = `causals-chart-${seasonKey}`;
+                const canvas = document.getElementById(chartId);
+                if (!canvas) return;
+
+                this.destroyChart(chartId);
+
+                const ctx = canvas.getContext('2d');
+
+                const colors = [
+                    'rgba(59, 91, 219, 0.8)',
+                    'rgba(0, 184, 148, 0.8)',
+                    'rgba(34, 184, 207, 0.8)',
+                    'rgba(255, 212, 59, 0.8)',
+                    'rgba(255, 107, 107, 0.8)',
+                    'rgba(142, 68, 173, 0.8)',
+                    'rgba(230, 126, 34, 0.8)',
+                    'rgba(149, 165, 166, 0.8)',
+                    'rgba(241, 196, 15, 0.8)',
+                    'rgba(231, 76, 60, 0.8)'
+                ];
+
+                const dataValues = causalsData.inData.map(item => parseFloat(item.value));
+                const total = dataValues.reduce((sum, value) => sum + value, 0);
+
+                this.charts[chartId] = new Chart(ctx, {
+                    type: 'doughnut',
+                    data: {
+                        labels: causalsData.inLabels,
+                        datasets: [{
+                            label: 'Importo',
+                            data: dataValues,
+                            backgroundColor: colors,
+                            borderColor: colors.map(color => color.replace('0.8', '1')),
+                            borderWidth: 2,
+                            hoverOffset: 8
+                        }]
+                    },
+                    options: {
+                        responsive: true,
+                        maintainAspectRatio: false,
+                        cutout: '30%',
+                        layout: {
+                            padding: {
+                                top: 10,
+                                right: 10,
+                                bottom: 10,
+                                left: 10
+                            }
+                        },
+                        plugins: {
+                            legend: {
+                                display: false
+                            },
+                            tooltip: {
+                                backgroundColor: 'rgba(255, 255, 255, 0.95)',
+                                titleColor: '#212529',
+                                bodyColor: '#495057',
+                                borderColor: '#e9ecef',
+                                borderWidth: 1,
+                                cornerRadius: 8,
+                                titleFont: {
+                                    size: 13,
+                                    weight: 'bold'
+                                },
+                                bodyFont: {
+                                    size: 12,
+                                    weight: '500'
+                                },
+                                padding: 12,
+                                callbacks: {
+                                    title: function (context) {
+                                        return context[0].label;
+                                    },
+                                    label: function (context) {
+                                        const value = context.raw;
+                                        const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0;
+                                        return [
+                                            `Importo: €${new Intl.NumberFormat('it-IT', {
+                                                minimumFractionDigits: 2,
+                                                maximumFractionDigits: 2
+                                            }).format(value)}`,
+                                            `Percentuale: ${percentage}%`
+                                        ];
+                                    }
+                                }
+                            },
+                        },
+                        animation: {
+                            animateRotate: true,
+                            duration: 1000
+                        }
+                    }
+                });
+
+                this.updateCausalsTable(causalsData, dataValues, total);
+            },
+
+            updateCausalsTable: function (causalsData, dataValues, total) {
+                const container = document.getElementById('causals-table');
+                if (!container) return;
+
+                const colors = [
+                    '#3b5bdb', '#00b894', '#22b8cf', '#ffd43b', '#ff6b6b',
+                    '#8e44ad', '#e67e22', '#95a5a6', '#f1c40f', '#e74c3c'
+                ];
+
+                let tableHtml = `
+        <div class="causals-table compact">
+            <div class="table-header">
+                <div class="table-cell causale">Causale</div>
+                <div class="table-cell euro">Importo</div>
+                <div class="table-cell percent">%</div>
+            </div>
+    `;
+
+                causalsData.inLabels.forEach((label, index) => {
+                    const value = dataValues[index] || 0;
+                    const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0;
+                    const color = colors[index % colors.length];
+
+                    tableHtml += `
+            <div class="table-row">
+                <div class="table-cell causale">
+                    <span class="causale-indicator" style="background-color: ${color}"></span>
+                    ${label}
+                </div>
+                <div class="table-cell euro">€${new Intl.NumberFormat('it-IT', {
+                        minimumFractionDigits: 2,
+                        maximumFractionDigits: 2
+                    }).format(value)}</div>
+                <div class="table-cell percent">${percentage}%</div>
+            </div>
+        `;
+                });
+
+                tableHtml += '</div>';
+                container.innerHTML = tableHtml;
+            },
+            createMembersChart: function (seasonKey, membersData) {
+                const chartId = `members-chart-${seasonKey}`;
+                const canvas = document.getElementById(chartId);
+                if (!canvas) return;
+
+                this.destroyChart(chartId);
+
+                const ctx = canvas.getContext('2d');
+
+                const gradient = ctx.createLinearGradient(0, 0, 0, 400);
+                gradient.addColorStop(0, 'rgba(59, 91, 219, 0.3)');
+                gradient.addColorStop(1, 'rgba(59, 91, 219, 0.05)');
+
+                const processedDatasets = membersData.datasets.map((dataset, index) => {
+                    if (dataset.label === 'Totale Membri Tesserati') {
+                        return {
+                            ...dataset,
+                            backgroundColor: gradient,
+                            borderColor: '#3b5bdb',
+                            borderWidth: 3,
+                            pointBackgroundColor: '#3b5bdb',
+                            pointBorderColor: '#ffffff',
+                            pointBorderWidth: 2,
+                            pointRadius: 6,
+                            pointHoverRadius: 8,
+                            type: 'line',
+                            order: 1,
+                            fill: true
+                        };
+                    } else {
+                        return {
+                            ...dataset,
+                            borderWidth: 2,
+                            pointRadius: 4,
+                            pointHoverRadius: 6,
+                            pointBorderColor: '#ffffff',
+                            pointBorderWidth: 1,
+                            type: 'line',
+                            order: 2,
+                            fill: false,
+                            backgroundColor: 'transparent'
+                        };
+                    }
+                });
+
+                this.charts[chartId] = new Chart(ctx, {
+                    type: 'line',
+                    data: {
+                        labels: membersData.labels,
+                        datasets: processedDatasets
+                    },
+                    options: {
+                        responsive: true,
+                        maintainAspectRatio: false,
+                        interaction: {
+                            mode: 'index',
+                            intersect: false,
+                        },
+                        plugins: {
+                            legend: {
+                                display: true,
+                                position: 'top',
+                                labels: {
+                                    usePointStyle: true,
+                                    padding: 15,
+                                    font: { weight: '500', size: 12 }
+                                }
+                            },
+                            tooltip: {
+                                backgroundColor: 'rgba(255, 255, 255, 0.95)',
+                                titleColor: '#212529',
+                                bodyColor: '#495057',
+                                borderColor: '#e9ecef',
+                                borderWidth: 1,
+                                cornerRadius: 8,
+                                callbacks: {
+                                    title: function (context) {
+                                        return 'Stagione: ' + context[0].label;
+                                    },
+                                    label: function (context) {
+                                        return context.dataset.label + ': ' + context.parsed.y;
+                                    }
+                                }
+                            }
+                        },
+                        scales: {
+                            x: {
+                                grid: { display: false },
+                                ticks: {
+                                    font: { weight: '500' }
+                                }
+                            },
+                            y: {
+                                beginAtZero: true,
+                                grid: { color: 'rgba(0, 0, 0, 0.05)' },
+                                ticks: {
+                                    precision: 0,
+                                    callback: function (value) {
+                                        return Math.floor(value); // Ensure integer values
+                                    }
+                                }
+                            }
+                        },
+                        animation: {
+                            duration: 1000,
+                            easing: 'easeOutQuart'
+                        }
+                    }
+                });
+            },
+            updateMonthlyTable: function (monthlyData) {
+                const container = document.getElementById('monthly-table');
+                if (!container) return;
+
+                const incomeData = monthlyData.datasets[0].data;
+                const expenseData = monthlyData.datasets[1].data;
+                const monthNames = monthlyData.labels;
+
+                let tableHtml = `
+                    <div class="monthly-table">
+                        <div class="table-header">
+                            <div class="table-cell month">Mese</div>
+                            <div class="table-cell">Entrate</div>
+                            <div class="table-cell">Uscite</div>
+                            <div class="table-cell">Delta</div>
+                        </div>
+                `;
+
+                monthNames.forEach((month, index) => {
+                    const income = parseFloat(incomeData[index] || 0);
+                    const expense = parseFloat(expenseData[index] || 0);
+                    const net = income - expense;
+                    const rowClass = net < 0 ? 'negative' : (net > 0 ? 'positive' : 'neutral');
+
+                    tableHtml += `
+                        <div class="table-row ${rowClass}">
+                            <div class="table-cell month-name">${month}</div>
+                            <div class="table-cell income">€${new Intl.NumberFormat('it-IT').format(income)}</div>
+                            <div class="table-cell expense">€${new Intl.NumberFormat('it-IT').format(expense)}</div>
+                            <div class="table-cell net">€${new Intl.NumberFormat('it-IT').format(net)}</div>
+                        </div>
+                    `;
+                });
+
+                tableHtml += '</div>';
+                container.innerHTML = tableHtml;
+            },
+
+            updateMembersTable: function (membersData) {
+                const container = document.getElementById('members-table');
+                if (!container) return;
+
+                const seasonLabels = membersData.labels;
+                const totalDataset = membersData.datasets.find(d => d.label === 'Totale Membri Tesserati');
+                const cardTypeDatasets = membersData.datasets.filter(d => d.label !== 'Totale Membri Tesserati');
+
+                const memberCounts = totalDataset ? totalDataset.data : [];
+
+                let tableHtml = `
+                    <div class="members-table">
+                        <div class="table-header">
+                            <div class="table-cell">Stagione</div>
+                            <div class="table-cell">Totale</div>
+                            <div class="table-cell">Variazione</div>
+                            <div class="table-cell">Tipologie</div>
+                        </div>
+                `;
+
+                seasonLabels.forEach((season, index) => {
+                    const current = parseInt(memberCounts[index] || 0);
+                    const previous = index > 0 ? parseInt(memberCounts[index - 1] || 0) : 0;
+                    const variation = index > 0 ? current - previous : 0;
+                    const variationPercent = previous > 0 ? Math.round((variation / previous) * 100 * 10) / 10 : 0;
+                    const rowClass = variation > 0 ? 'positive' : (variation < 0 ? 'negative' : 'neutral');
+
+                    let variationText = '—';
+                    if (index > 0) {
+                        if (variation > 0) {
+                            variationText = `<span class="variation-positive">+${variation} (+${variationPercent}%)</span>`;
+                        } else if (variation < 0) {
+                            variationText = `<span class="variation-negative">${variation} (${variationPercent}%)</span>`;
+                        } else {
+                            variationText = `<span class="variation-neutral">${variation}</span>`;
+                        }
+                    }
+
+                    // Build card type breakdown
+                    let cardTypeBreakdown = '';
+                    cardTypeDatasets.forEach((dataset, datasetIndex) => {
+                        const count = dataset.data[index] || 0;
+                        if (count > 0) {
+                            const color = dataset.borderColor || '#6b7280';
+                            cardTypeBreakdown += `
+                    <div class="card-type-item">
+                        <span class="card-type-indicator" style="background-color: ${color}"></span>
+                        <span class="card-type-name">${dataset.label}</span>
+                        <span class="card-type-count">${count}</span>
+                    </div>
+                `;
+                        }
+                    });
+
+                    if (!cardTypeBreakdown) {
+                        cardTypeBreakdown = '<div class="no-card-types">Nessun dettaglio</div>';
+                    }
+
+                    tableHtml += `
+            <div class="table-row ${rowClass}">
+                <div class="table-cell season-name">${season}</div>
+                <div class="table-cell members-count">${new Intl.NumberFormat('it-IT').format(current)}</div>
+                <div class="table-cell variation">${variationText}</div>
+                <div class="table-cell card-types">
+                    <div class="card-types-container">
+                        ${cardTypeBreakdown}
+                    </div>
+                </div>
+            </div>
+        `;
+                });
+
+                tableHtml += '</div>';
+                container.innerHTML = tableHtml;
+            },
+
+            createCourseChart: function () {
+                console.log('Creating course chart...');
+                const seasonFilter = '{{ $seasonFilter }}';
+                const selectedCourse = '{{ $selectedCourse ?? '' }}';
+                const seasonKey = '{{ str_replace('-', '', $seasonFilter) }}';
+                console.log('Selected course:', selectedCourse, 'for season:', seasonFilter);
+
+                if (!selectedCourse || selectedCourse.trim() === '') {
+                    console.log('No course selected, skipping chart creation');
+                    return;
+                }
+
+                const chartId = `courses-chart-${seasonKey}-${selectedCourse}`;
+                const canvas = document.getElementById(chartId);
+                if (!canvas) return;
+
+                this.destroyChart(chartId);
+
+                const courseData = @json($this->getCourseMonthlyEarnings());
+                const ctx = canvas.getContext('2d');
+                this.charts[chartId] = new Chart(ctx, {
+                    type: 'bar',
+                    data: {
+                        labels: courseData.labels,
+                        datasets: courseData.datasets.map(dataset => {
+                            if (dataset.type === 'line') {
+                                return {
+                                    ...dataset,
+                                    type: 'line',
+                                    fill: false,
+                                    backgroundColor: 'transparent'
+                                };
+                            }
+                            return dataset;
+                        })
+                    },
+                    options: {
+                        responsive: true,
+                        maintainAspectRatio: false,
+                        interaction: {
+                            mode: 'index',
+                            intersect: false,
+                        },
+                        scales: {
+                            x: {
+                                grid: { display: false },
+                                ticks: { font: { weight: '500' } }
+                            },
+                            y: {
+                                beginAtZero: true,
+                                grid: {
+                                    color: 'rgba(0, 0, 0, 1)',
+                                    borderDash: [5, 5]
+                                },
+                                ticks: {
+                                    callback: function (value) {
+                                        return '€' + new Intl.NumberFormat('it-IT').format(value);
+                                    }
+                                }
+                            }
+                        },
+                        plugins: {
+                            legend: {
+                                display: true,
+                                position: 'top',
+                                labels: {
+                                    usePointStyle: true,
+                                    padding: 20,
+                                    font: { weight: '500' }
+                                }
+                            },
+                            tooltip: {
+                                backgroundColor: 'rgba(255, 255, 255, 1)',
+                                titleColor: '#212529',
+                                bodyColor: '#495057',
+                                borderColor: '#e9ecef',
+                                borderWidth: 1,
+                                cornerRadius: 8,
+                                callbacks: {
+                                    label: function (context) {
+                                        return context.dataset.label + ': €' +
+                                            new Intl.NumberFormat('it-IT').format(context.parsed.y);
+                                    }
+                                }
+                            }
+                        },
+                        animation: {
+                            duration: 1000,
+                            easing: 'easeOutQuart'
+                        }
+                    }
+                });
+            },
+            createCourseChartWithValue: function (selectedCourseValue) {
+                console.log('Creating modern course chart with value:', selectedCourseValue);
+                const seasonFilter = '{{ $seasonFilter }}';
+                const seasonKey = '{{ str_replace('-', '', $seasonFilter) }}';
+
+                const chartId = `courses-chart-${seasonKey}-${selectedCourseValue}`;
+                const tableId = `course-delta-table-${seasonKey}-${selectedCourseValue}`;
+                let canvas = document.getElementById(chartId);
+                const tableContainer = document.getElementById(tableId);
+
+                if (!canvas) {
+                    console.log('Canvas not found for chart ID:', chartId);
+
+                    const chartContainer = document.querySelector('.modern-chart-container');
+                    if (chartContainer) {
+                        chartContainer.innerHTML = `
+                <div class="chart-empty-state">
+                    <div style="text-align: center; padding: 4rem 2rem;">
+                        <div style="font-size: 4rem; margin-bottom: 1.5rem; opacity: 0.3;">📊</div>
+                        <h3 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem; color: #374151;">
+                            Grafico non disponibile
+                        </h3>
+                        <p style="font-size: 1rem; opacity: 0.7; margin: 0; max-width: 400px; margin-left: auto; margin-right: auto; line-height: 1.5;">
+                            Il grafico per questo corso non può essere visualizzato nella stagione selezionata.
+                        </p>
+                    </div>
+                </div>
+            `;
+                    }
+
+                    if (tableContainer) {
+                        tableContainer.innerHTML = '';
+                    }
+
+                    return;
+                }
+
+                this.destroyChart(chartId);
+
+                @this.call('getCourseData', selectedCourseValue).then(courseData => {
+                    console.log('Received course data:', courseData);
+
+                    if (courseData.isEmpty) {
+                        console.log('No data available for course, showing message');
+
+                        if (tableContainer) {
+                            tableContainer.innerHTML = '';
+                        }
+
+                        const chartContainer = canvas.parentElement;
+                        chartContainer.innerHTML = `
+                <div class="chart-empty-state">
+                    <div style="text-align: center; padding: 4rem 2rem;">
+                        <div style="font-size: 4rem; margin-bottom: 1.5rem; opacity: 0.3;">📊</div>
+                        <h3 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem; color: #374151;">
+                            ${courseData.message}
+                        </h3>
+                        <p style="font-size: 1rem; opacity: 0.7; margin: 0; max-width: 400px; margin-left: auto; margin-right: auto; line-height: 1.5;">
+                            Questo corso non ha pagamenti registrati per la stagione selezionata.
+                        </p>
+                    </div>
+                </div>
+            `;
+                        return;
+                    }
+
+                    if (!courseData || !courseData.labels || courseData.labels.length === 0) {
+                        console.log('No data available for chart');
+                        return;
+                    }
+
+                    let canvasElement = document.getElementById(chartId);
+                    if (!canvasElement) {
+                        const chartContainer = canvas.parentElement;
+                        chartContainer.innerHTML = `<canvas id="${chartId}"></canvas>`;
+                        canvasElement = document.getElementById(chartId);
+                    }
+
+                    this.updateCourseTable(tableContainer, courseData.tableData);
+
+                    const participantData = courseData.datasets.find(d => d.participantData)?.participantData || [];
+
+                    const ctx = canvasElement.getContext('2d');
+
+                    const earnedGradient = ctx.createLinearGradient(0, 0, 0, 400);
+                    earnedGradient.addColorStop(0, 'rgba(16, 185, 129, 1)');
+                    earnedGradient.addColorStop(1, 'rgba(16, 185, 129, 1)');
+
+                    const totalData = courseData.datasets.find(d => d.label === 'Pagamenti Attesi')?.data || [];
+                    const earnedData = courseData.datasets.find(d => d.label === 'Pagamenti Effettuati')?.data || [];
+
+                    this.charts[chartId] = new Chart(ctx, {
+                        type: 'bar',
+                        data: {
+                            labels: courseData.labels,
+                            datasets: [
+                                {
+                                    label: 'Pagamenti Effettuati',
+                                    backgroundColor: earnedGradient,
+                                    borderColor: 'rgba(16, 185, 129, 1)',
+                                    borderWidth: 0,
+                                    borderRadius: 8,
+                                    borderSkipped: false,
+                                    data: earnedData,
+                                    type: 'bar',
+                                    order: 2
+                                },
+                                {
+                                    label: 'Pagamenti Attesi',
+                                    backgroundColor: 'transparent',
+                                    borderColor: 'rgba(59, 130, 246, 1)',
+                                    borderWidth: 3,
+                                    pointBackgroundColor: 'rgba(59, 130, 246, 1)',
+                                    pointBorderColor: '#ffffff',
+                                    pointBorderWidth: 3,
+                                    pointRadius: 7,
+                                    pointHoverRadius: 9,
+                                    data: totalData,
+                                    type: 'line',
+                                    tension: 0.3,
+                                    order: 1,
+                                    participantData: participantData
+                                }
+                            ]
+                        },
+                        options: {
+                            responsive: true,
+                            maintainAspectRatio: false,
+                            interaction: {
+                                mode: 'index',
+                                intersect: false,
+                            },
+                            layout: {
+                                padding: {
+                                    top: 20,
+                                    right: 20,
+                                    bottom: 20,
+                                    left: 10
+                                }
+                            },
+                            scales: {
+                                x: {
+                                    grid: {
+                                        display: false
+                                    },
+                                    ticks: {
+                                        font: {
+                                            weight: '600',
+                                            size: 13
+                                        },
+                                        color: '#6b7280'
+                                    },
+                                    border: {
+                                        display: false
+                                    }
+                                },
+                                y: {
+                                    beginAtZero: true,
+                                    grid: {
+                                        color: 'rgba(156, 163, 175, 0.15)',
+                                        borderDash: [3, 3]
+                                    },
+                                    border: {
+                                        display: false
+                                    },
+                                    ticks: {
+                                        font: {
+                                            size: 12,
+                                            weight: '500'
+                                        },
+                                        color: '#6b7280',
+                                        callback: function (value) {
+                                            return '€' + new Intl.NumberFormat('it-IT', {
+                                                minimumFractionDigits: 0,
+                                                maximumFractionDigits: 0
+                                            }).format(value);
+                                        }
+                                    }
+                                }
+                            },
+                            plugins: {
+                                legend: {
+                                    display: true,
+                                    position: 'top',
+                                    labels: {
+                                        usePointStyle: true,
+                                        padding: 15,
+                                        font: { weight: '500', size: 12 },
+                                    }
+                                },
+                                tooltip: {
+                                    backgroundColor: 'rgba(255, 255, 255, 0.98)',
+                                    titleColor: '#111827',
+                                    bodyColor: '#374151',
+                                    borderColor: 'rgba(229, 231, 235, 0.8)',
+                                    borderWidth: 1,
+                                    cornerRadius: 12,
+                                    titleFont: {
+                                        weight: 'bold',
+                                        size: 15
+                                    },
+                                    bodyFont: {
+                                        size: 14,
+                                        weight: '500'
+                                    },
+                                    padding: 16,
+                                    boxPadding: 8,
+                                    usePointStyle: true,
+                                    displayColors: true,
+                                    callbacks: {
+                                        title: function (context) {
+                                            return context[0].label;
+                                        },
+                                        label: function (context) {
+                                            let label = context.dataset.label + ': €' +
+                                                new Intl.NumberFormat('it-IT').format(context.parsed.y);
+
+                                            if (context.dataset.label === 'Pagamenti Effettuati') {
+                                                const earnedValue = parseFloat(context.parsed.y) || 0;
+                                                const totalValue = parseFloat(totalData[context.dataIndex]) || 0;
+                                                const missingValue = Math.max(0, totalValue - earnedValue);
+
+                                                if (participantData[context.dataIndex]) {
+                                                    label += '\n👥 Partecipanti: ' + participantData[context.dataIndex];
+                                                }
+
+                                                if (missingValue > 0) {
+                                                    label += '\n🔴 Mancanti: €' + new Intl.NumberFormat('it-IT').format(missingValue);
+                                                }
+                                            }
+
+                                            if (context.dataset.label === 'Pagamenti Attesi' && participantData[context.dataIndex]) {
+                                                label += '\n👥 Partecipanti: ' + participantData[context.dataIndex];
+                                            }
+
+                                            return label;
+                                        }
+                                    }
+                                }
+                            },
+                            animation: {
+                                duration: 1500,
+                                easing: 'easeOutCubic'
+                            },
+                            elements: {
+                                bar: {
+                                    borderRadius: {
+                                        topLeft: 8,
+                                        topRight: 8,
+                                        bottomLeft: 0,
+                                        bottomRight: 0
+                                    }
+                                },
+                                line: {
+                                    borderCapStyle: 'round',
+                                    borderJoinStyle: 'round'
+                                },
+                                point: {
+                                    hoverBorderWidth: 4,
+                                    borderWidth: 3
+                                }
+                            }
+                        }
+                    });
+                }).catch(error => {
+                    console.error('Error calling getCourseData:', error);
+                });
+            },
+
+            updateCourseTable: function (container, tableData) {
+                if (!container || !tableData) return;
+
+                let tableHtml = `
+        <div class="course-table">
+            <div class="table-header">
+                <div class="table-cell month">Mese</div>
+                <div class="table-cell participants">👥</div>
+                <div class="table-cell delta">Mancanti</div>
+                <div class="table-cell percentage">%</div>
+            </div>
+    `;
+
+                tableData.forEach(row => {
+                    const earned = parseFloat(row.earned) || 0;
+                    const total = parseFloat(row.total) || 0;
+                    const delta = Math.max(0, total - earned);
+
+                    let percentage = 0;
+                    let percentageDisplay = '—';
+                    let percentageClass = 'neutral';
+
+                    if (total > 0) {
+                        percentage = Math.round((earned / total) * 100);
+                        percentageDisplay = percentage + '%';
+
+                        // Color based on completion
+                        if (percentage >= 100) {
+                            percentageClass = 'good';
+                        } else if (percentage >= 80) {
+                            percentageClass = 'warning';
+                        } else {
+                            percentageClass = 'bad';
+                        }
+                    }
+
+                    // Delta styling: positive when delta is 0 (fully paid), negative when there's missing amount
+                    const deltaClass = (total > 0 && delta === 0) ? 'positive' :
+                        (delta > 0) ? 'negative' : 'neutral';
+
+                    tableHtml += `
+            <div class="table-row">
+                <div class="table-cell month">${row.month}</div>
+                <div class="table-cell participants">${row.participants}</div>
+                <div class="table-cell delta ${deltaClass}">€${new Intl.NumberFormat('it-IT').format(delta)}</div>
+                <div class="table-cell percentage ${percentageClass}">${percentageDisplay}</div>
+            </div>
+        `;
+                });
+
+                tableHtml += '</div>';
+                container.innerHTML = tableHtml;
+            },
+            updateCourseChart: function () {
+                if (this.selectedCourse) {
+                    const seasonFilter = @json($seasonFilter);
+                    const seasonKey = seasonFilter.replace('-', '');
+                    this.createCourseChartWithValue(this.selectedCourse);
+                }
+            }
+        };
+
+        document.addEventListener('DOMContentLoaded', function () {
+            setTimeout(() => {
+                window.ReportsChartManager.updateMainCharts();
+            }, 100);
+        });
+
+        document.addEventListener('livewire:navigated', function () {
+            setTimeout(() => {
+                window.ReportsChartManager.updateMainCharts();
+            }, 100);
+        });
+
+        document.addEventListener('livewire:updated', function (event) {
+            console.log('Livewire updated, waiting for component to fully update...');
+            setTimeout(() => {
+                console.log('Now updating charts after delay');
+                window.ReportsChartManager.forceUpdateCharts();
+            }, 800);
+        });
+        document.addEventListener('livewire:load', function () {
+            Livewire.on('courseSelected', (courseId) => {
+                console.log('Course selected event received:', courseId);
+                setTimeout(() => {
+                    window.ReportsChartManager.createCourseChartWithValue(courseId);
+                }, 200);
+            });
+
+            Livewire.on('chartsUpdated', () => {
+                console.log('Charts updated event received from Livewire');
+                setTimeout(() => {
+                    window.ReportsChartManager.forceUpdateCharts();
+                }, 200);
+            });
+        });
+    </script>
+</div>

+ 1 - 0
routes/web.php

@@ -91,6 +91,7 @@ Route::group(['middleware' => 'auth'], function () {
     Route::get('/presences', \App\Http\Livewire\Presence::class);
     Route::get('/courts', \App\Http\Livewire\Court::class);
     Route::get('/motivations', \App\Http\Livewire\Motivation::class);
+    Route::get('/reports', \App\Http\Livewire\Reports::class);
 });
 
 Route::get('/receipt/{id}', function ($id) {