Explorar el Código

modifiche reports

FabioFratini hace 7 meses
padre
commit
e04cb90e9f

+ 81 - 16
app/Http/Livewire/Reports.php

@@ -491,7 +491,7 @@ class Reports extends Component
         foreach ($monthOrder as $month) {
             $earned = round($monthlyData[$month]['earned'], 2);
             $total = round($monthlyData[$month]['total'], 2);
-            $delta = max(0, $total - $earned); // Only positive deltas (missing amounts)
+            $delta = max(0, $total - $earned);
             $participants = $monthlyData[$month]['participants'];
 
             $labels[] = $monthNames[$month];
@@ -499,7 +499,6 @@ class Reports extends Component
             $totalData[] = $total;
             $participantData[] = $participants;
 
-            // Fix percentage calculation: earned/total * 100 (not delta-based)
             $percentage = $total > 0 ? round(($earned / $total) * 100, 1) : 0;
 
             $tableData[] = [
@@ -556,18 +555,29 @@ class Reports extends Component
 
         $startYear = $endYear - $span + 1;
 
-        $memberCards = MemberCard::select('member_id', 'expire_date')
+        $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) {
@@ -582,32 +592,87 @@ class Reports extends Component
 
             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' => [
-                [
-                    'label' => 'Membri Tesserati',
-                    'data' => $memberCountData,
-                    'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
-                    'borderColor' => 'rgba(54, 162, 235, 1)',
-                    'borderWidth' => 2,
-                    'pointBackgroundColor' => 'rgba(54, 162, 235, 1)',
-                    'pointRadius' => 4,
-                    'tension' => 0.3,
-                    'fill' => true
-                ]
-            ]
+            'datasets' => $datasets
         ];
     }
 }

+ 27 - 8
public/css/chart-reports.css

@@ -293,13 +293,15 @@
 }
 
 .monthly-table-container {
-    background: #f8f9fa;
+    background: white;
+    border: 1px solid #F6F7FF;
     border-radius: 12px;
     box-shadow: var(--shadow-sm);
 }
 
 .members-table-container {
-    background: #f8f9fa;
+    background: white;
+    border: 1px solid #F6F7FF;
     border-radius: 12px;
     box-shadow: var(--shadow-sm);
 }
@@ -392,7 +394,7 @@
 }
 
 .table-row.neutral {
-    background-color: rgba(73, 80, 87, 0.02);
+    background-color: white;
 }
 
 .table-cell {
@@ -529,9 +531,9 @@
 }
 
 .course-delta-table {
-    background: rgb(248, 249, 250);
+    background: white;
+    border: 1px solid #F6F7FF;
     border-radius: 16px;
-    border: 1px solid #e5e7eb;
     box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
 }
 
@@ -555,12 +557,12 @@
 .course-table .table-header {
     display: grid;
     grid-template-columns: 1fr 40px 80px 50px;
-    background: #f8fafc;
+    background: white;
     padding: 12px 16px;
     font-weight: 600;
     font-size: 0.875rem;
     color: #374151;
-    border-bottom: 1px solid #e5e7eb;
+    border-bottom: 1px solid #F6F7FF;
 }
 
 .course-table .table-row {
@@ -706,7 +708,8 @@
     min-height: 500px;
 }
 .causals-table-container {
-    background: #f8f9fa;
+    background: white;
+    border: 1px solid #F6F7FF;
     border-radius: 12px;
     box-shadow: var(--shadow-sm);
 }
@@ -775,3 +778,19 @@
     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;
+}

+ 69 - 32
resources/views/livewire/reports.blade.php

@@ -214,12 +214,12 @@
                 const ctx = canvas.getContext('2d');
 
                 const incomeGradient = ctx.createLinearGradient(0, 0, 0, 400);
-                incomeGradient.addColorStop(0, 'rgba(0, 184, 148, 0.8)');
-                incomeGradient.addColorStop(1, 'rgba(0, 184, 148, 0.2)');
+                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, 0.8)');
-                expenseGradient.addColorStop(1, 'rgba(255, 107, 107, 0.2)');
+                expenseGradient.addColorStop(0, 'rgba(255, 107, 107, 1)');
+                expenseGradient.addColorStop(1, 'rgba(255, 107, 107, 1)');
 
                 this.charts[chartId] = new Chart(ctx, {
                     type: 'bar',
@@ -259,7 +259,7 @@
                                 }
                             },
                             tooltip: {
-                                backgroundColor: 'rgba(255, 255, 255, 0.95)',
+                                backgroundColor: 'rgba(255, 255, 255,1)',
                                 titleColor: '#212529',
                                 bodyColor: '#495057',
                                 borderColor: '#e9ecef',
@@ -400,7 +400,6 @@
                     }
                 });
 
-                // Create the causals table
                 this.updateCausalsTable(causalsData, dataValues, total);
             },
 
@@ -458,30 +457,61 @@
                 gradient.addColorStop(0, 'rgba(59, 91, 219, 0.3)');
                 gradient.addColorStop(1, 'rgba(59, 91, 219, 0.05)');
 
-                this.charts[chartId] = new Chart(ctx, {
-                    type: 'line',
-                    data: {
-                        labels: membersData.labels,
-                        datasets: [{
-                            label: 'Membri Tesserati',
-                            data: membersData.datasets[0].data,
-                            borderColor: '#3b5bdb',
+                const processedDatasets = membersData.datasets.map((dataset, index) => {
+                    if (dataset.label === 'Totale Membri Tesserati') {
+                        return {
+                            ...dataset,
                             backgroundColor: gradient,
+                            borderColor: '#3b5bdb',
                             borderWidth: 3,
-                            fill: true,
-                            tension: 0.4,
                             pointBackgroundColor: '#3b5bdb',
                             pointBorderColor: '#ffffff',
                             pointBorderWidth: 2,
                             pointRadius: 6,
-                            pointHoverRadius: 8
-                        }]
+                            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: false },
+                            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',
@@ -490,18 +520,31 @@
                                 borderWidth: 1,
                                 cornerRadius: 8,
                                 callbacks: {
+                                    title: function (context) {
+                                        return 'Stagione: ' + context[0].label;
+                                    },
                                     label: function (context) {
-                                        return 'Tesserati: ' + context.parsed.y;
+                                        return context.dataset.label + ': ' + context.parsed.y;
                                     }
                                 }
                             }
                         },
                         scales: {
-                            x: { grid: { display: false } },
+                            x: {
+                                grid: { display: false },
+                                ticks: {
+                                    font: { weight: '500' }
+                                }
+                            },
                             y: {
                                 beginAtZero: true,
                                 grid: { color: 'rgba(0, 0, 0, 0.05)' },
-                                ticks: { precision: 0 }
+                                ticks: {
+                                    precision: 0,
+                                    callback: function (value) {
+                                        return Math.floor(value); // Ensure integer values
+                                    }
+                                }
                             }
                         },
                         animation: {
@@ -511,7 +554,6 @@
                     }
                 });
             },
-
             updateMonthlyTable: function (monthlyData) {
                 const container = document.getElementById('monthly-table');
                 if (!container) return;
@@ -604,7 +646,6 @@
                 const seasonKey = '{{ str_replace('-', '', $seasonFilter) }}';
                 console.log('Selected course:', selectedCourse, 'for season:', seasonFilter);
 
-                // Add this check at the beginning
                 if (!selectedCourse || selectedCourse.trim() === '') {
                     console.log('No course selected, skipping chart creation');
                     return;
@@ -649,7 +690,7 @@
                             y: {
                                 beginAtZero: true,
                                 grid: {
-                                    color: 'rgba(0, 0, 0, 0.1)',
+                                    color: 'rgba(0, 0, 0, 1)',
                                     borderDash: [5, 5]
                                 },
                                 ticks: {
@@ -670,7 +711,7 @@
                                 }
                             },
                             tooltip: {
-                                backgroundColor: 'rgba(255, 255, 255, 0.95)',
+                                backgroundColor: 'rgba(255, 255, 255, 1)',
                                 titleColor: '#212529',
                                 bodyColor: '#495057',
                                 borderColor: '#e9ecef',
@@ -776,14 +817,12 @@
                     const ctx = canvasElement.getContext('2d');
 
                     const earnedGradient = ctx.createLinearGradient(0, 0, 0, 400);
-                    earnedGradient.addColorStop(0, 'rgba(16, 185, 129, 0.9)');
-                    earnedGradient.addColorStop(1, 'rgba(16, 185, 129, 0.3)');
+                    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 || [];
 
-                    // REMOVED: verticalMissingLinesPlugin completely
-
                     this.charts[chartId] = new Chart(ctx, {
                         type: 'bar',
                         data: {
@@ -881,7 +920,6 @@
                                         usePointStyle: true,
                                         padding: 15,
                                         font: { weight: '500', size: 12 },
-                                        // REMOVED: custom generateLabels function that added red dashed line legend
                                     }
                                 },
                                 tooltip: {
@@ -981,7 +1019,6 @@
                     const total = parseFloat(row.total) || 0;
                     const delta = Math.max(0, total - earned);
 
-                    // Fix percentage calculation and display logic
                     let percentage = 0;
                     let percentageDisplay = '—';
                     let percentageClass = 'neutral';