Jelajahi Sumber

Merge grafiche

Luca Parisio 4 bulan lalu
induk
melakukan
dae5955a6f

+ 78 - 17
app/Http/Livewire/Member.php

@@ -791,8 +791,74 @@ class Member extends Component
         $this->annullate = 0;
         $this->recuperi = 0;
 
+        define("PRESENZE", 0);
+        define("ASSENZE", 1);
+        define("RECUPERO", 2);
+        define("ANNULLATE", 3);
+
+        $chartData = [
+            PRESENZE => [
+                "label" => "Presenze",
+                "backgroundColor" => "#0c6197",
+                "data" => array_fill(0, 12, 0),
+                "grouped" => true,
+                "stack" => "chartData",
+                "barThickness" => "flex",
+                "barPercentage" => 0.5,
+                "categoryPercentage" => 0.3,
+            ],
+            ASSENZE => [
+                "label" => "Assenze",
+                "backgroundColor" => "#ff0000",
+                "data" => array_fill(0, 12, 0),
+                "grouped" => true,
+                "stack" => "chartData",
+                "barThickness" => "flex",
+                "barPercentage" => 0.5,
+                "categoryPercentage" => 0.3,
+                
+            ],
+            RECUPERO => [
+                "label" => "Recupero",
+                "backgroundColor" => "#7136f6",
+                "data" => array_fill(0, 12, 0),
+                "grouped" => true,
+                "stack" => "chartData",
+                "barThickness" => "flex",
+                "barPercentage" => 0.5,
+                "categoryPercentage" => 0.3,
+                
+            ],
+            ANNULLATE => [
+                "label" => "Annullate",
+                "backgroundColor" => "#808080",
+                "data" => array_fill(0, 12, 0),
+                "grouped" => true,
+                "stack" => "chartData",
+                "barThickness" => "flex",
+                "barPercentage" => 0.5,
+                "categoryPercentage" => 0.3,
+                
+            ],
+        ];
+        $monthMap = [
+            9 => 0,
+            10 => 1,
+            11 => 2,
+            12 => 3,
+            1 => 4,
+            2 => 5,
+            3 => 6,
+            4 => 7,
+            5 => 8,
+            6 => 9,
+            7 => 10,
+            8 => 11,
+        ];
+
         foreach($calendars as $calendar)
         {
+            
             $days = ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'];
             $dow = date('w', strtotime($calendar->from));
             $d = $days[$dow];
@@ -808,23 +874,10 @@ class Member extends Component
                 $status = '';
                 if (in_array($calendar->id, $presences))
                 {
-                    $status = "<span style=\"color:green\">Prezenza ordinaria</span>";
+                    $status = "<span style=\"color:#0c6197\">Prezenza ordinaria</span>";
                     $this->presenze += 1;
-                    $monthMap = [
-                        9 => 0,
-                        10 => 1,
-                        11 => 2,
-                        12 => 3,
-                        1 => 4,
-                        2 => 5,
-                        3 => 6,
-                        4 => 7,
-                        5 => 8,
-                        6 => 9,
-                        7 => 10,
-                        8 => 11,
-                    ];
                     $this->valori[$monthMap[date('n', strtotime($calendar->from))]] += 1;
+                    $chartData[PRESENZE]["data"][$monthMap[date('n', strtotime($calendar->from))]] += 1;
                 }
                 else
                 {
@@ -832,6 +885,8 @@ class Member extends Component
                     {
                         $status = "<span style=\"color:gray\">Annullata</span>";
                         $this->annullate += 1;
+
+                        $chartData[ANNULLATE]["data"][$monthMap[date('n', strtotime($calendar->from))]] += 1;
                     }
                     else
                     {
@@ -839,6 +894,8 @@ class Member extends Component
                         {
                             $status = "<span style=\"color:red\">Assenza</span>";
                             $this->assenze += 1;
+
+                            $chartData[ASSENZE]["data"][$monthMap[date('n', strtotime($calendar->from))]] += 1;
                         }
                     }
                 }
@@ -847,6 +904,8 @@ class Member extends Component
                 {
                     $status = "<span style=\"color:gray\">Annullata</span>";
                     $this->annullate += 1;
+
+                    $chartData[ANNULLATE]["data"][$monthMap[date('n', strtotime($calendar->from))]] += 1;
                 }
                 
                 $this->member_presences[] = array('calendar_id' => $calendar->id, 'from' => $calendar->from, 'to' => $calendar->to, 'status' => $status);//\App\Models\Presence::where('member_id', $this->dataId)->get();
@@ -867,8 +926,10 @@ class Member extends Component
         $presences_recuperi = \App\Models\Presence::whereIn('calendar_id', $calendar_recuperi)->where('member_id', $this->dataId)->get();
         foreach($presences_recuperi as $p)
         {
-            $this->member_presences[] = array('calendar_id' => $p->calendar->id, 'from' => $p->calendar->from, 'to' => $p->calendar->to, 'status' => '<span style="color:violet">Recupero</span>');//\App\Models\Presence::where('member_id', $this->dataId)->get();
+            $this->member_presences[] = array('calendar_id' => $p->calendar->id, 'from' => $p->calendar->from, 'to' => $p->calendar->to, 'status' => '<span style="color:#7136f6">Recupero</span>');//\App\Models\Presence::where('member_id', $this->dataId)->get();
             $this->recuperi += 1;
+
+            $chartData[RECUPERO]["data"][$monthMap[date('n', strtotime($p->calendar->from))]] += 1;
         }
 
         $sortVariable='from';
@@ -878,7 +939,7 @@ class Member extends Component
         );
 
         
-        $this->emit('load-chart', $this->mesi, $this->valori);
+        $this->emit('load-chart', $this->mesi, $this->valori, $chartData);
 
         //usort($this->member_presences, $this->cmp);
         //usort($this->member_presences, function ($a, $b) { return $a['from'] > $b['from']; });

+ 48 - 76
app/Http/Livewire/Presence.php

@@ -50,25 +50,24 @@ class Presence extends Component
         $this->instructors = \App\Models\User::select('*')->where('level', 2)->where('enabled', true)->get();
         $this->motivations = \App\Models\Motivation::select('*')->where('enabled', true)->where('type', 'del')->get();
         $this->motivations_add = \App\Models\Motivation::select('*')->where('enabled', true)->where('type', 'add')->get();
-        
     }
 
-    public function updatedNewMemberMotivationId() {
+    public function updatedNewMemberMotivationId()
+    {
         $this->emit('reload');
     }
 
     public function render()
     {
-        
+
         $this->records = [];
 
         setlocale(LC_ALL, 'it_IT');
 
         $presenceMembers = [];
 
-        if (!$this->manual)
-        {
-        
+        if (!$this->manual) {
+
             // Carco tutti gli iscritti a un corso padre in quel giorno con un range orario simile
             $days = ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'];
             $dow = date('w', strtotime($this->calendar->from));
@@ -83,51 +82,43 @@ class Presence extends Component
             // Elenco utenti iscritti al corso "padre"
             $members_courses = \App\Models\MemberCourse::where('when', 'like', "%" . $d . "%")->where('when', 'like', '%"from":"' . $h . '"%')->whereIn('course_id', $courses)->pluck('member_id')->toArray();
 
-            if ($this->filter != '')
-            {
+            if ($this->filter != '') {
                 $filter = $this->filter;
                 $members = \App\Models\Member::whereIn('id', $members_courses)->where(function ($query) use ($filter) {
                     $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $filter . "%'")
                         ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $filter . "%'");
                 })->orderBy('last_name')->orderBy('first_name')->get();
-            }
-            else
+            } else
                 $members = \App\Models\Member::whereIn('id', $members_courses)->orderBy('last_name')->orderBy('first_name')->get();
 
             // $presences = \App\Models\Presence::where('calendar_id', $this->calendar->id)->pluck('member_id')->toArray();
             // $my_presences = \App\Models\Presence::where('calendar_id', $this->calendar->id)->where('user_id', \Auth::user()->id)->pluck('member_id')->toArray();
 
-            foreach($members as $member)
-            {
+            foreach ($members as $member) {
                 $presenceMembers[] = $member->id;
                 //$this->member_ids[] = $member->id;
                 $this->records[] = $this->getMember($member);
             }
-
         }
 
         // Aggiungo i membri iscritti
         $members_presences = \App\Models\Presence::where('calendar_id', $this->calendar->id)->whereNotIn('member_id', $presenceMembers)->pluck('member_id')->toArray();
 
-        if ($this->filter != '')
-        {
+        if ($this->filter != '') {
             $filter = $this->filter;
             $members = \App\Models\Member::whereIn('id', $members_presences)->where(function ($query) use ($filter) {
                 $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $filter . "%'")
                     ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $filter . "%'");
             })->get();
-        }
-        else
+        } else
             $members = \App\Models\Member::whereIn('id', $members_presences)->get();
 
-        foreach($members as $member)
-        {
+        foreach ($members as $member) {
             //$this->member_ids[] = $member->id;
             $this->records[] = $this->getMember($member);
         }
 
-        foreach($this->newMembers as $m)
-        {
+        foreach ($this->newMembers as $m) {
             $member = \App\Models\Member::findOrFail($m);
             //$this->member_ids[] = $member->id;
             $this->records[] = $this->getMember($member);
@@ -157,8 +148,7 @@ class Presence extends Component
 
         $certificate = '';
         $y = "|";
-        if ($latestCert) 
-        {
+        if ($latestCert) {
             $latest_date = $latestCert->expire_date;
             $status = '';
             if ($latest_date < date("Y-m-d")) {
@@ -177,30 +167,31 @@ class Presence extends Component
         $status = 0;
 
         $has_presence = \App\Models\Presence::where('calendar_id', $this->calendar->id)->where('member_id', $member->id)->first();
-        if ($has_presence)            
-        {
+        if ($has_presence) {
             $presence = true;
             $my_presence = $has_presence->user_id == \Auth::user()->id;
-            if ($has_presence->motivation_id > 0)
-            {
+            if ($has_presence->motivation_id > 0) {
                 $motivation = \App\Models\Motivation::findOrFail($has_presence->motivation_id)->name;
             }
             $status = $has_presence->status;
         }
 
-        if (in_array($member->id, $this->newMembers))
-        {
+        if (in_array($member->id, $this->newMembers)) {
             $presence = true;
             $my_presence = true;
         }
 
         return array('id' => $member->id, 'first_name' => $member->first_name, 'last_name' => $member->last_name, 'certificate' => $y, 'presence' => $presence, 'my_presence' => $my_presence, 'status' => $status, 'motivation' => $motivation);
-
     }
 
     public function save($ids)
     {
+        $this->saveAndStay($ids);
+        return redirect()->to('/calendar');
+    }
 
+    public function saveAndStay($ids)
+    {
         $this->calendar->court_id = $this->court_id;
         $this->calendar->instructor_id = $this->instructor_id;
         $this->calendar->note = $this->note;
@@ -212,13 +203,11 @@ class Presence extends Component
 
         $x = \App\Models\Presence::where('calendar_id', $this->calendar->id)->where('user_id', \Auth::user()->id)->where('status', '<>', 99)->first();
         $mid = null;
-        if ($x)
-        {
+        if ($x) {
             $mid = $x->motivation_id;
             $x->delete();
         }
-        foreach($ids as $id)
-        {
+        foreach ($ids as $id) {
             $p = new \App\Models\Presence();
             $p->member_id = $id;
             $p->calendar_id = $this->calendar->id;
@@ -228,22 +217,18 @@ class Presence extends Component
             $p->save();
         }
         $this->emit('setSaving');
-        return redirect()->to('/calendar');
-
     }
 
     public function cancel($ids, $motivation_id)
     {
 
         $presences = \App\Models\Presence::where('calendar_id', $this->calendar->id)->whereIn('member_id', $ids)->get();
-        foreach($presences as $presence)
-        {
+        foreach ($presences as $presence) {
             $presence->motivation_id = $motivation_id;
             $presence->status = 99;
             $presence->save();
         }
         return redirect()->to('/presences?calendarId=' . $this->calendar->id);
-
     }
 
     public function addMember($ids)
@@ -252,11 +237,10 @@ class Presence extends Component
         $this->added = true;
         //if (!in_array($id, $this->newMembers))
         //    $this->newMembers[] = $id;
-        
+
         $this->member_ids = $ids;
 
         $this->emit('reload');
-
     }
 
     public function cancelCalendar()
@@ -270,8 +254,7 @@ class Presence extends Component
     public function createMember()
     {
 
-        if (!$this->added)
-        {
+        if (!$this->added) {
             $this->newMemberFiscalCodeExist = false;
             $this->validate([
                 "newMemberMotivationId" => 'required',
@@ -285,13 +268,11 @@ class Presence extends Component
 
             // Check fiscal code exist
             $exist = false;
-            if ($this->newMemberFiscalCode != '')
-            {
+            if ($this->newMemberFiscalCode != '') {
                 $check = \App\Models\Member::where('fiscal_code', $this->newMemberFiscalCode)->get();
                 $exist = $check->count() > 0;
             }
-            if (!$exist)
-            {
+            if (!$exist) {
                 $member = \App\Models\Member::create([
                     'first_name' => strtoupper($this->newMemberFirstName),
                     'last_name' => strtoupper($this->newMemberLastName),
@@ -311,42 +292,36 @@ class Presence extends Component
                 $p->user_id = \Auth::user()->id;
                 $p->status = 0;
                 $p->save();
-                
+
                 $this->emit('reload');
                 $this->emit('saved');
                 /*$this->newMemberFirstName = '';
                 $this->newMemberLastName = '';
                 $this->newMemberEmail = '';
                 $this->newMemberFiscalCode = '';*/
-            }
-            else
-            {
+            } else {
                 $this->newMemberFiscalCodeExist = true;
             }
-        }
-        else
-        {
+        } else {
 
-            if ($this->member_ids != null)
-            {
+            if ($this->member_ids != null) {
                 $this->validate([
                     "newMemberMotivationId" => 'required',
                 ]);
-                foreach($this->member_ids as $m)
-                {
+                foreach ($this->member_ids as $m) {
                     //if ($this->manual)
                     //{
-                        //\App\Models\Presence::where('calendar_id', $this->calendar->id)->where('user_id', \Auth::user()->id)->where('status', '<>', 99)->delete();
-                        //foreach($ids as $id)
-                        //{
-                            $p = new \App\Models\Presence();
-                            $p->member_id = $m;
-                            $p->calendar_id = $this->calendar->id;
-                            $p->motivation_id = $this->newMemberMotivationId;
-                            $p->user_id = \Auth::user()->id;
-                            $p->status = 0;
-                            $p->save();
-                        //}
+                    //\App\Models\Presence::where('calendar_id', $this->calendar->id)->where('user_id', \Auth::user()->id)->where('status', '<>', 99)->delete();
+                    //foreach($ids as $id)
+                    //{
+                    $p = new \App\Models\Presence();
+                    $p->member_id = $m;
+                    $p->calendar_id = $this->calendar->id;
+                    $p->motivation_id = $this->newMemberMotivationId;
+                    $p->user_id = \Auth::user()->id;
+                    $p->status = 0;
+                    $p->save();
+                    //}
                     /*}
                     else
                     {
@@ -359,7 +334,7 @@ class Presence extends Component
             //$this->member_id = 0;
             $this->member_ids = [];
             $this->added = false;
-            
+
             $this->emit('reload');
             $this->emit('saved');
         }
@@ -367,7 +342,7 @@ class Presence extends Component
 
     public function createInstructor()
     {
-        
+
         $user = \App\Models\User::create([
             'name' => $this->userName,
             'email' => $this->userEmail,
@@ -378,8 +353,7 @@ class Presence extends Component
 
         $this->instructor_id = $user->id;
         $this->instructors = \App\Models\User::select('*')->where('level', 2)->where('enabled', true)->get();
-        $this->emit('saved');        
-
+        $this->emit('saved');
     }
 
     public function removeSingle($id)
@@ -387,16 +361,14 @@ class Presence extends Component
 
         \App\Models\Presence::where('calendar_id', $this->calendar->id)->where('member_id', $id)->delete();
         $this->emit('reload');
-
     }
 
     public function revert($id)
     {
-
         $p = \App\Models\Presence::where('calendar_id', $this->calendar->id)->where('member_id', $id)->first();
+        $p->motivation_id = null;
         $p->status = 0;
         $p->save();
         $this->emit('reload');
-
     }
 }

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

@@ -216,6 +216,71 @@ class Reports extends Component
             ]
         ];
     }
+    
+    public function getYearlyTotals()
+    {
+        Log::info('=== getyearlyTotals called ===');
+
+        $incomeRecords = DB::table('records')
+            ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+            ->where('records.type', 'IN')
+            ->selectRaw("
+                CASE
+                    WHEN MONTH(records.date) >= 9
+                        THEN CONCAT(YEAR(records.date), '/', YEAR(records.date) + 1)
+                    ELSE CONCAT(YEAR(records.date) - 1, '/', YEAR(records.date))
+                END AS year_num,
+                SUM(records_rows.amount) AS total
+            ")
+            ->groupBy('year_num')
+            ->get();
+
+        $expenseRecords = DB::table('records')
+            ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+            ->where('records.type', 'OUT')
+            ->selectRaw("
+                CASE
+                    WHEN MONTH(records.date) >= 9
+                        THEN CONCAT(YEAR(records.date), '/', YEAR(records.date) + 1)
+                    ELSE CONCAT(YEAR(records.date) - 1, '/', YEAR(records.date))
+                END AS year_num,
+                SUM(records_rows.amount) AS total
+            ")
+            ->groupBy('year_num')
+            ->get();
+        
+        // Mappa anno/totale
+        $incomeByYear = $incomeRecords->pluck('total', 'year_num');
+        $expenseByYear = $expenseRecords->pluck('total', 'year_num');
+
+        // Unione di tutti gli anni presenti ordinati
+        $allYears = $incomeByYear->keys()
+                                ->merge($expenseByYear->keys())
+                                ->unique()
+                                ->sort()
+                                ->values();
+
+        // Allineo i dati dei due array, se non presente l'anno: default 0
+        $incomeData  = $allYears->map(fn ($y) => (float) ($incomeByYear[$y]  ?? 0))->toArray();
+        $expenseData = $allYears->map(fn ($y) => (float) ($expenseByYear[$y] ?? 0))->toArray();
+
+
+        return [
+            'labels' => $allYears->toArray(),
+            '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()
     {

+ 16 - 3
public/css/calendar.css

@@ -24,7 +24,8 @@
     --fc-bg-event-color: #8fdf82;
     --fc-bg-event-opacity: 0.3;
     --fc-highlight-color: rgba(188, 232, 241, 0.3);
-    --fc-today-bg-color: rgba(255, 220, 40, 0.15);
+    /* --fc-today-bg-color: rgba(255, 220, 40, 0.15); */
+    --fc-today-bg-color: #c5d9e6;
     --fc-now-indicator-color: red;
 
     --bs-primary: #0c6197;
@@ -89,7 +90,7 @@ body label.form-label {
     margin-bottom: 0;
 }
 
-body input[type=checkbox]:checked {
+body input[type="checkbox"]:checked {
     -webkit-appearance: none;
     background-color: #0c6197 !important;
     border-radius: 2px;
@@ -108,7 +109,19 @@ body .fc-event.festivity .fc-event-title-container {
     margin-top: 9px;
 }
 
-@media (max-width: 1024px) { 
+body input[type="checkbox"] + label.form-label {
+    margin-top: 0;
+}
+
+.fc .fc-col-header-cell.fc-day-today {
+    background-color: var(--fc-today-bg-color);
+}
+
+.fc .fc-col-header-cell-cushion {
+    text-transform: capitalize;
+}
+
+@media (max-width: 1024px) {
     body .fc .fc-toolbar.fc-header-toolbar .fc-toolbar-chunk .fc-button {
         font-size: 14px;
     }

+ 6 - 5
public/css/chart-reports.css

@@ -1,9 +1,9 @@
 @charset "UTF-8";
 
 :root {
-    --primary-color: #6366f1;
-    --primary-light: #818cf8;
-    --primary-dark: #4f46e5;
+    --primary-color: #0c6197;
+    --primary-light: #3585b8;
+    --primary-dark: #074c77;
     --secondary-color: #64748b;
     --success-color: #10b981;
     --success-light: #34d399;
@@ -18,7 +18,8 @@
     --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-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); */
+    --gradient-primary: linear-gradient(135deg, #0c6197 0%, #0c6197 100%);
     --gradient-success: linear-gradient(135deg, #10b981 0%, #059669 100%);
     --gradient-danger: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
 }
@@ -979,4 +980,4 @@ canvas[id^="courses-chart-"] {
     overflow: hidden;
     position: relative;
     width: 100%;
-}
+}

+ 16 - 1
public/css/style.css

@@ -16794,7 +16794,22 @@ body #card--dashboard > .btn--ui:has(i[class*="fa-arrow-left"]):hover {
   color: white;
   background-color: #0c6197 !important;
 }
-  /* END CSS Ferrari - Modifiche UI */
+
+body input[type="checkbox"]:checked:disabled {
+    background-color: #b1b1b1 !important;
+}
+
+input[type=checkbox][name="annulla_lezione"] {
+    border-color: red;
+    color: red;
+}
+
+body input[type=checkbox][name="annulla_lezione"]:checked {
+    background-color: red !important;
+    background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 485.6 485.6' xml:space='preserve'%3E%3Cpath d='M105.4,60.2c-12.5-12.5-32.8-12.5-45.3,0s-12.5,32.8,0,45.3l137.4,137.3L60.2,380.2c-12.5,12.5-12.5,32.8,0,45.3 s32.8,12.5,45.3,0l137.3-137.4l137.4,137.3c12.5,12.5,32.8,12.5,45.3,0s12.5-32.8,0-45.3L288.1,242.8l137.3-137.4 c12.5-12.5,12.5-32.8,0-45.3s-32.8-12.5-45.3,0L242.8,197.5L105.4,60.2z' fill='white'/%3E%3C/svg%3E%0A");
+    background-size: 88%;
+}
+/* END CSS Ferrari - Modifiche UI */
 
 body div.dt-button-background {
   pointer-events: none;

+ 7 - 6
resources/views/livewire/calendar.blade.php

@@ -1,6 +1,6 @@
 <style>
     {!! $css_festivities !!} {
-        background: #dcf1ff !important;
+        background: #ffff0040 !important;
     }
 </style>
 
@@ -15,7 +15,7 @@
             <div class="col"></div>
             <div class="col-auto text-end mt-2">
                 <div class="form--item d-flex align-items-center form--item gap-3">
-                    <label for="inputName" class="form-label mb-0">Tipologia</label>
+                    <label for="inputName" class="form-label mb-0">CORSO</label>
                     <select class="form-select form-select-lg me-1" id="name_filter" onchange="reloadCalendar()">
                         <option value="">
                         @foreach($names as $n)
@@ -46,7 +46,7 @@
                     <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                 </div>
                 <div class="modal-body">
-                    <div class="row align-items-center">
+                    <div class="row align-items-center flex-nowrap">
                         <div class="col-auto">
                             <label for="course_subscription_id" class="form-label text-primary">Ora inizio</label>
                             <h3 class="time mb-0 text-primary">ORA</h3>
@@ -248,17 +248,18 @@
 
         document.addEventListener('DOMContentLoaded', function() {
             var calendarEl = document.getElementById('calendar');
+
+            initialView = document.body.clientWidth < 768 ? 'timeGridDay' : 'timeGridWeek';
             var calendar = new FullCalendar.Calendar(calendarEl, {
-                initialView: 'timeGridWeek',
+                initialView: initialView,
                 slotMinTime: '06:00:00',
                 headerToolbar: {
                     // left: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth',
-                    left: 'dayGridMonth,timeGridWeek',
+                    left: 'timeGridDay,timeGridWeek,dayGridMonth',
                     center: 'title',
                     right: 'prevYear,prev,next,nextYear today',
                 },
                 dateClick: function(info) {
-                    
                     var x = info.dateStr.split("T");
                     $("#date").val(x[0]);
                     var y = x[1].split("+");

+ 43 - 24
resources/views/livewire/member.blade.php

@@ -2826,41 +2826,60 @@
             }, 200);
         });
 
-        Livewire.on('load-chart', (mesi, valori) => {
+        Livewire.on('load-chart', (mesi, valori, chartData) => {
 
-            try 
-            {
+            try {
 
                 const ctx = document.getElementById('presenzeChart');
 
-                try
-                {
+                try {
                     x.destroy();
                 }
-                catch (ee)
-                {}
+                catch (ee) {}
 
                 x = new Chart(ctx, {
-                    type: 'bar',
-                    data: {
-                    labels: mesi,
-                    datasets: [{
-                        label: 'Presenze',
-                        data: valori,
-                        borderWidth: 1
-                    }]
-                    },
-                    options: {
-                    scales: {
-                        y: {
-                        beginAtZero: true
+                        type: 'bar',
+                        data: {
+                            labels: mesi,
+                            // datasets: [
+                            //     {
+                            //         label: 'Presenze',
+                            //         data: valori,
+                            //         borderWidth: 1
+                            //     }
+                            // ],
+                            datasets: chartData,
+                        },
+                        options: {
+                            responsive: true,
+                            // maintainAspectRatio: false,
+                            interaction: {
+                                mode: 'index',
+                                intersect: false,
+                            },
+                            scales: {
+                                y: {
+                                beginAtZero: true
+                                }
+                            },
+                            plugins: {
+                                legend: {
+                                    display: false,
+                                },
+                                tooltip: {
+                                    backgroundColor: 'rgba(255, 255, 255, 1)',
+                                    titleColor: '#212529',
+                                    bodyColor: '#495057',
+                                    borderColor: '#e9ecef',
+                                    borderWidth: 2,
+                                    cornerRadius: 0,
+                                },
+                            },
                         }
                     }
-                    }
-                });
+                );
             }
-            catch(e)
-            {
+            catch(e) {
                 console.log(e);
             }
         });

+ 67 - 12
resources/views/livewire/presence.blade.php

@@ -74,6 +74,7 @@
             <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
                 <thead>
                     <tr>
+                        <th scope="col" class="annulla-lezione" style="display: none;">Annullamento</th>
                         <th scope="col">#</th>
                         <th scope="col">Cognome</th>
                         <th scope="col">Nome</th>
@@ -85,6 +86,11 @@
                 <tbody id="checkall-target">
                     @foreach($records as $idx => $record)
                         <tr>
+                            <td class="annulla-lezione" style="display: none;">
+                                @if ($record["status"] != 99)
+                                    <input name="annulla_lezione" class="member chkM" type="checkbox" value="{{$record["id"]}}">
+                                @endif
+                            </td>
                             <td>{{$idx + 1}}</td>
                             <td>{{$record["last_name"]}}</td>
                             <td>{{$record["first_name"]}}</td>                            
@@ -115,16 +121,16 @@
                                             @if($manual)
                                                 <a onclick="removeSingle({{$record['id']}})"><i class="fas fa-trash"></i></a>
                                             @else
-                                                <input class="member chkM" type="checkbox" value="{{$record["id"]}}" {{$record["presence"] ? 'checked' : ''}}>
+                                                <input name="presence" class="member chkM" type="checkbox" value="{{$record["id"]}}" {{$record["presence"] ? 'checked' : ''}}>
                                             @endif
                                         @else
                                             <span style="color:#0C6197;font-size:25px;">&#10003;</span>
                                         @endif
                                     @else
-                                        <input class="member chkM" type="checkbox" value="{{$record["id"]}}" {{$record["presence"] ? 'checked' : ''}}>
+                                        <input name="presence" class="member chkM" type="checkbox" value="{{$record["id"]}}" {{$record["presence"] ? 'checked' : ''}}>
                                     @endif
                                 @else
-                                    Annullata (<a href="#" wire:click="revert({{$record["id"]}})">Ripristina</a>)
+                                    Annullata &nbsp;&nbsp;-&nbsp;&nbsp; <a href="#" wire:click="revert({{$record["id"]}})" style="text-decoration: underline;color: #0c6197;"><small><i class="fa-solid fa-arrow-left-rotate"></i></small> Ripristina</a>
                                 @endif
                             </td>
                         </tr>
@@ -158,12 +164,13 @@
                             </select>                            
                         </div>
                         <button type="button" class="btn--ui lightGrey btSave" {{-- style="background-color:rgb(111, 31, 31) !important" --}} onclick="showHideDelete()">Annulla lezione per selezionati</button>                    
+                        <button type="button" class="btn--ui btSave" onclick="saveAndStay()">Salva presenze</button>
                         
                     @endif              
                 </div>
                 @if(!$manual)
                     <div class="col-auto mt-2 text-end">
-                        <button type="button" class="btn--ui btSave" onclick="save()">Salva</button>        
+                        <button type="button" class="btn--ui btSave" onclick="saveAndQuit()">Salva e chiudi</button>        
                     </div>
                     <div class="col-xs-12 mt-2">
                         <div class="showDelete" style="float:left;display:none;">
@@ -247,7 +254,7 @@
                         </div>
                     </div>
                     <div class="row mt-2 ">
-                        <div class="col-md-6">
+                        <div class="col-md-6 d-flex gap-1 pt-3">
                             <input type="checkbox" id="newMemberToComplete" wire:model="newMemberToComplete">
                             <label for="newMemberToComplete" class="form-label">Tesserato</label>
                         </div>
@@ -299,6 +306,14 @@
     </div>
     <br><br>
 
+    <div wire:ignore.self class="modal fade saved-modal" id="savedModal" tabindex="-1" role="dialog" aria-labelledby="savedModal" aria-hidden="true">
+        <div class="modal-dialog">
+            <div class="modal-content">
+                <div class="modal-header"></div>
+                <div class="modal-body pt-4 pb-4 text-center fw-bold">Presenze salvate con successo</div>
+            </div>
+        </div>
+    </div>
 </div>
 
 @push('scripts')
@@ -430,16 +445,26 @@
 
         window.livewire.on('setSaving', () => {
             isSaving = true;
+            showSavedAlert();
         });
 
-        function save()
+        let saved_alert_timeout;
+        function showSavedAlert() {
+            $('#savedModal').modal("show");
+            clearTimeout(saved_alert_timeout);
+            saved_alert_timeout = setTimeout(() => {
+                $('#savedModal').modal("hide");
+            }, 3000);
+        }
+
+        function saveAndQuit()
         {
-            var ids = [];
-            $('input[type=checkbox]').each(function () {
+            let presence_ids = [];
+            $('input[name="presence"][type="checkbox"]').each(function () {
                 if ($(this).is(":checked")) 
                 {
                     var val = $(this).val();
-                    ids.push(val);
+                    presence_ids.push(val);
                 }
             });
 
@@ -448,13 +473,32 @@
                 @this.set('motivation_manual_id', motivation_manual_id);
             @endif
 
-            @this.save(ids);
+            @this.save(presence_ids);
+        }
+
+        function saveAndStay()
+        {   
+            let presence_ids = [];
+            $('input[name="presence"][type="checkbox"]').each(function () {
+                if ($(this).is(":checked")) 
+                {
+                    var val = $(this).val();
+                    presence_ids.push(val);
+                }
+            });
+
+            @if($manual)
+                var motivation_manual_id = $("#motivation_manual_id").val();
+                @this.set('motivation_manual_id', motivation_manual_id);
+            @endif
+
+            @this.saveAndStay(presence_ids);
         }
 
         function cancel()
         {
             var ids = [];
-            $('input[type=checkbox]').each(function () {
+            $('input[name="annulla_lezione"][type="checkbox"]').each(function () {
                 if ($(this).is(":checked")) 
                 {
                     var val = $(this).val();
@@ -508,14 +552,25 @@
             $('#instructorModal').modal('hide');
         }
 
+        function togglePresenceCheckboxDisabled(disabled = false) {
+            let presence_checkboxes = document.querySelectorAll("input[name='presence'][type='checkbox']");
+            presence_checkboxes.forEach((presence_checkbox) => {
+                presence_checkbox.disabled = disabled;
+            });
+        }
+
         function showHideDelete()
         {
+            togglePresenceCheckboxDisabled(true);
+            $(".annulla-lezione").show();
             $(".showDelete").show();
             $(".btSave").hide();
         }
-
+        
         function hideShowDelete()
         {
+            togglePresenceCheckboxDisabled(false);
+            $(".annulla-lezione").hide();
             $(".showDelete").hide();
             $(".btSave").show();
         }

+ 244 - 89
resources/views/livewire/reports.blade.php

@@ -4,19 +4,39 @@
     <link rel="stylesheet" href="/css/chart-reports.css">
 
     <div class="dashboard-container">
+
+        <div class="chart-row" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; align-items: start;">
+            <div class="chart-card">
+                <div class="chart-header">
+                    <h3 class="chart-title">Entrate/Uscite totali</h3>
+                </div>
+                <div class="chart-body">
+                    <div class="yearly-table-container" id="yearly-table"></div>
+                </div>
+            </div>
+            <div class="chart-card">
+                <div class="chart-header">
+                    <h3 class="chart-title">Tesserati per Stagione</h3>
+                </div>
+                <div class="chart-body">
+                    <div class="members-table-container" id="members-table"></div>
+                </div>
+            </div>
+        </div>
+
         <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>
+                    <option value="{{ $season }}">{{ $season }}</option>
                     @endforeach
                 </select>
             </div>
         </div>
 
         @php
-            $summary = $this->getYearlySummary();
+        $summary = $this->getYearlySummary();
         @endphp
         <div class="summary-cards">
             <div class="summary-card income">
@@ -37,8 +57,7 @@
             <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>
+                        <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;">
@@ -55,8 +74,7 @@
             <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>
+                        <h3 class="chart-title">Redditività per Causale - <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;">
@@ -79,9 +97,7 @@
                     <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">
+                                <canvas id="members-chart"></canvas>
                             </div>
                         </div>
                     </div>
@@ -102,35 +118,33 @@
                             <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>
+                                <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 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 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>
@@ -186,6 +200,11 @@
                     this.updateMonthlyTable(monthlyData);
                 });
 
+                @this.call('getYearlyTotals').then(yearlyData => {
+                    console.log('Got yearly data:', yearlyData);
+                    this.updateYearlyTable(yearlyData);
+                });
+
                 @this.call('getTopCausalsByAmount').then(causalsData => {
                     console.log('Got causals data:', causalsData);
                     this.createCausalsChart(originalSeasonKey, causalsData);
@@ -233,47 +252,58 @@
                                 data: monthlyData.datasets[0].data,
                                 backgroundColor: incomeGradient,
                                 borderColor: '#00b894',
-                                borderWidth: 2,
-                                borderRadius: 12,
+                                borderWidth: 0,
+                                borderRadius: 0,
                                 borderSkipped: false,
+                                barThickness: "flex",
+                                barPercentage: 0.65,
+                                categoryPercentage: 0.4,
                             },
                             {
                                 label: 'Uscite',
                                 data: monthlyData.datasets[1].data,
                                 backgroundColor: expenseGradient,
                                 borderColor: '#ff6b6b',
-                                borderWidth: 2,
-                                borderRadius: 12,
+                                borderWidth: 0,
+                                borderRadius: 0,
                                 borderSkipped: false,
+                                barThickness: "flex",
+                                barPercentage: 0.65,
+                                categoryPercentage: 0.4,
                             }
                         ]
                     },
                     options: {
                         responsive: true,
                         maintainAspectRatio: false,
+                        interaction: {
+                            mode: 'index',
+                            intersect: false,
+                        },
                         plugins: {
                             legend: {
-                                position: 'top',
+                                position: 'bottom',
                                 labels: {
                                     usePointStyle: true,
                                     padding: 20,
+                                    pointStyle: "rect",
                                     font: { weight: '500' }
                                 }
                             },
                             tooltip: {
-                                backgroundColor: 'rgba(255, 255, 255,1)',
+                                backgroundColor: 'rgba(255, 255, 255, 1)',
                                 titleColor: '#212529',
                                 bodyColor: '#495057',
                                 borderColor: '#e9ecef',
-                                borderWidth: 12,
-                                cornerRadius: 8,
+                                borderWidth: 2,
+                                cornerRadius: 0,
                                 callbacks: {
                                     label: function (context) {
                                         return context.dataset.label + ': €' +
                                             new Intl.NumberFormat('it-IT').format(context.parsed.y);
                                     }
                                 }
-                            }
+                            },
                         },
                         scales: {
                             x: {
@@ -332,6 +362,90 @@
                 const dataValues = causalsData.inData.map(item => parseFloat(item.value));
                 const total = dataValues.reduce((sum, value) => sum + value, 0);
 
+                // alwaysTooltip
+                const alwaysShowTooltip = {
+                    id: "alwaysShowTooltip",
+                    afterDraw(chart) {
+                        const { ctx } = chart;
+                        ctx.save();
+
+                        const linePad = 12; // distanza dal bordo della ciambella
+                        const textOffset = 20; // distanza fissa del testo dal bordo
+                        const lineGap = 8; // margine tra linea e testo
+                        const lineWidth = 1;
+
+                        chart.data.datasets.forEach((dataset, di) => {
+                            const meta = chart.getDatasetMeta(di);
+                            const total = dataset.data.reduce((s, v) => s + v, 0);
+
+                            meta.data.forEach((datapoint, index) => {
+                                const value = dataset.data[index];
+                                const perc = total > 0 ? (value / total) * 100 : 0;
+                                const text = `${perc.toFixed(1)}%`;
+
+                                // calcolo angolo e anchor
+                                const { x, y } = datapoint.tooltipPosition();
+                                const coords = outsideLabelPoint({
+                                    cx: datapoint.x,
+                                    cy: datapoint.y,
+                                    px: x,
+                                    py: y,
+                                    radius: datapoint.outerRadius,
+                                    pad: linePad,
+                                });
+
+                                const ux = Math.cos(coords.theta);
+                                const uy = Math.sin(coords.theta);
+
+                                const ax = coords.anchor.x;
+                                const ay = coords.anchor.y;
+
+                                // centro testo
+                                const tx = ax + ux * textOffset;
+                                const ty = ay + uy * textOffset;
+
+                                // fine della linea = poco prima del testo
+                                const lx = tx - ux * lineGap;
+                                const ly = ty - uy * lineGap;
+
+                                // linea: anchor -> vicino al testo
+                                ctx.lineWidth = lineWidth;
+                                ctx.strokeStyle = datapoint.options.borderColor;
+                                ctx.beginPath();
+                                ctx.moveTo(ax, ay);
+                                ctx.lineTo(lx, ly);
+                                ctx.stroke();
+
+                                // testo
+                                ctx.font = "12px greycliff-cf, sans-serif";
+                                ctx.fillStyle = datapoint.options.borderColor;
+                                ctx.textAlign = "center";
+                                ctx.textBaseline = "middle";
+                                ctx.fillText(text, tx, ty);
+                            });
+                        });
+
+                        ctx.restore();
+                    },
+                };
+
+                function outsideLabelPoint({ cx, cy, px, py, radius, pad = 20 }) {
+                    const theta = Math.atan2(py - cy, px - cx); // angolo rad
+                    const anchor = { // punto sul bordo del pie
+                        x: cx + radius * Math.cos(theta),
+                        y: cy + radius * Math.sin(theta)
+                    };
+                    const label = { // punto esterno per il tooltip/etichetta
+                        x: cx + (radius + pad) * Math.cos(theta),
+                        y: cy + (radius + pad) * Math.sin(theta)
+                    };
+                    const align = {
+                        textAlign: Math.cos(theta) >= 0 ? 'left' : 'right',
+                        textBaseline: Math.sin(theta) > 0 ? 'top' : 'bottom'
+                    };
+                    return { theta, anchor, label, align };
+                }
+
                 this.charts[chartId] = new Chart(ctx, {
                     type: 'doughnut',
                     data: {
@@ -351,10 +465,10 @@
                         cutout: '30%',
                         layout: {
                             padding: {
-                                top: 10,
-                                right: 10,
-                                bottom: 10,
-                                left: 10
+                                top: 30,
+                                right: 30,
+                                bottom: 30,
+                                left: 30
                             }
                         },
                         plugins: {
@@ -362,12 +476,12 @@
                                 display: false
                             },
                             tooltip: {
-                                backgroundColor: 'rgba(255, 255, 255, 0.95)',
+                                backgroundColor: 'rgba(255, 255, 255, 1)',
                                 titleColor: '#212529',
                                 bodyColor: '#495057',
                                 borderColor: '#e9ecef',
-                                borderWidth: 1,
-                                cornerRadius: 8,
+                                borderWidth: 2,
+                                cornerRadius: 0,
                                 titleFont: {
                                     size: 13,
                                     weight: 'bold'
@@ -399,7 +513,8 @@
                             animateRotate: true,
                             duration: 1000
                         }
-                    }
+                    },
+                    plugins: [alwaysShowTooltip]
                 });
 
                 this.updateCausalsTable(causalsData, dataValues, total);
@@ -419,7 +534,7 @@
             <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 class="table-cell percent">%</div> -->
             </div>
     `;
 
@@ -438,7 +553,7 @@
                         minimumFractionDigits: 2,
                         maximumFractionDigits: 2
                     }).format(value)}</div>
-                <div class="table-cell percent">${percentage}%</div>
+                <!-- <div class="table-cell percent">${percentage}%</div> -->
             </div>
         `;
                 });
@@ -447,7 +562,7 @@
                 container.innerHTML = tableHtml;
             },
             createMembersChart: function (seasonKey, membersData) {
-                const chartId = `members-chart-${seasonKey}`;
+                const chartId = `members-chart`;
                 const canvas = document.getElementById(chartId);
                 if (!canvas) return;
 
@@ -463,36 +578,37 @@
                     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',
+                            // backgroundColor: gradient,
+                            backgroundColor: dataset.borderColor,
+                            borderColor: dataset.borderColor,
+                            borderWidth: 0,
+                            type: 'bar',
                             order: 1,
-                            fill: true
+                            fill: true,
+                            barThickness: "flex",
+                            barPercentage: 0.65,
+                            categoryPercentage: 0.4,
+
                         };
                     } else {
                         return {
                             ...dataset,
-                            borderWidth: 2,
-                            pointRadius: 4,
-                            pointHoverRadius: 6,
-                            pointBorderColor: '#ffffff',
-                            pointBorderWidth: 1,
-                            type: 'line',
+                            backgroundColor: dataset.borderColor,
+                            borderColor: dataset.borderColor,
+                            borderWidth: 0,
+                            type: 'bar',
                             order: 2,
-                            fill: false,
-                            backgroundColor: 'transparent'
+                            fill: true,
+                            barThickness: "flex",
+                            barPercentage: 0.65,
+                            categoryPercentage: 0.4,
+
                         };
                     }
                 });
 
-                this.charts[chartId] = new Chart(ctx, {
-                    type: 'line',
+                let options = {
+                    type: 'bar',
                     data: {
                         labels: membersData.labels,
                         datasets: processedDatasets
@@ -506,21 +622,21 @@
                         },
                         plugins: {
                             legend: {
-                                display: true,
-                                position: 'top',
+                                position: 'bottom',
                                 labels: {
                                     usePointStyle: true,
-                                    padding: 15,
-                                    font: { weight: '500', size: 12 }
+                                    padding: 20,
+                                    pointStyle: "rect",
+                                    font: { weight: '500' }
                                 }
                             },
                             tooltip: {
-                                backgroundColor: 'rgba(255, 255, 255, 0.95)',
+                                backgroundColor: 'rgba(255, 255, 255, 1)',
                                 titleColor: '#212529',
                                 bodyColor: '#495057',
                                 borderColor: '#e9ecef',
-                                borderWidth: 1,
-                                cornerRadius: 8,
+                                borderWidth: 2,
+                                cornerRadius: 0,
                                 callbacks: {
                                     title: function (context) {
                                         return 'Stagione: ' + context[0].label;
@@ -554,7 +670,9 @@
                             easing: 'easeOutQuart'
                         }
                     }
-                });
+                };
+
+                this.charts[chartId] = new Chart(ctx, options);
             },
             updateMonthlyTable: function (monthlyData) {
                 const container = document.getElementById('monthly-table');
@@ -593,6 +711,43 @@
                 tableHtml += '</div>';
                 container.innerHTML = tableHtml;
             },
+            updateYearlyTable: function (yearlyData) {
+                const container = document.getElementById('yearly-table');
+                if (!container) return;
+
+                const incomeData = yearlyData.datasets[0].data;
+                const expenseData = yearlyData.datasets[1].data;
+                const years = yearlyData.labels;
+
+                let tableHtml = `
+                    <div class="monthly-table">
+                        <div class="table-header">
+                            <div class="table-cell month">Anno</div>
+                            <div class="table-cell">Entrate</div>
+                            <div class="table-cell">Uscite</div>
+                            <div class="table-cell">Delta</div>
+                        </div>
+                `;
+
+                years.forEach((year, 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">${year}</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');
@@ -610,7 +765,7 @@
                             <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 class="table-cell">Ente</div>
                         </div>
                 `;
 
@@ -733,11 +888,11 @@
                         },
                         plugins: {
                             legend: {
-                                display: true,
-                                position: 'top',
+                                position: 'bottom',
                                 labels: {
                                     usePointStyle: true,
                                     padding: 20,
+                                    pointStyle: "rect",
                                     font: { weight: '500' }
                                 }
                             },
@@ -746,8 +901,8 @@
                                 titleColor: '#212529',
                                 bodyColor: '#495057',
                                 borderColor: '#e9ecef',
-                                borderWidth: 1,
-                                cornerRadius: 8,
+                                borderWidth: 2,
+                                cornerRadius: 0,
                                 callbacks: {
                                     label: function (context) {
                                         return context.dataset.label + ': €' +
@@ -945,21 +1100,21 @@
                             },
                             plugins: {
                                 legend: {
-                                    display: true,
-                                    position: 'top',
+                                    position: 'bottom',
                                     labels: {
                                         usePointStyle: true,
-                                        padding: 15,
-                                        font: { weight: '500', size: 12 },
+                                        padding: 20,
+                                        pointStyle: "rect",
+                                        font: { weight: '500' }
                                     }
                                 },
                                 tooltip: {
-                                    backgroundColor: 'rgba(255, 255, 255, 0.98)',
+                                    backgroundColor: 'rgba(255, 255, 255, 1)',
                                     titleColor: '#111827',
                                     bodyColor: '#374151',
                                     borderColor: 'rgba(229, 231, 235, 0.8)',
-                                    borderWidth: 1,
-                                    cornerRadius: 12,
+                                    borderWidth: 2,
+                                    cornerRadius: 0,
                                     titleFont: {
                                         weight: 'bold',
                                         size: 15
@@ -1129,4 +1284,4 @@
             });
         });
     </script>
-</div>
+</div>