Просмотр исходного кода

manual merge con ferrari branch iao_team

Minus193 1 месяц назад
Родитель
Сommit
4285c94e42
87 измененных файлов с 17345 добавлено и 1438 удалено
  1. 36 9
      app/Console/Commands/DispatchDueEmails.php
  2. 54 0
      app/Console/Commands/DispatchDueSms.php
  3. 2 0
      app/Console/Kernel.php
  4. 2 2
      app/Http/Livewire/Azienda.php
  5. 385 0
      app/Http/Livewire/Azienda.php.bak
  6. 1 1
      app/Http/Livewire/Category.php
  7. 132 0
      app/Http/Livewire/Category.php.bak
  8. 19 2
      app/Http/Livewire/Causals.php
  9. 134 0
      app/Http/Livewire/Causals.php.bak
  10. 66 41
      app/Http/Livewire/Course.php
  11. 566 0
      app/Http/Livewire/Course.php.bak
  12. 2 2
      app/Http/Livewire/CourseList.php
  13. 918 0
      app/Http/Livewire/CourseList.php.bak
  14. 5 3
      app/Http/Livewire/CourseMember.php
  15. 303 0
      app/Http/Livewire/CourseMember.php.bak
  16. 1 1
      app/Http/Livewire/CourseMemberOne.php
  17. 17 0
      app/Http/Livewire/CourseMemberOne.php.bak
  18. 118 28
      app/Http/Livewire/Dashboard.php
  19. 834 0
      app/Http/Livewire/Dashboard.php.bak
  20. 30 7
      app/Http/Livewire/EmailComunications.php
  21. 429 0
      app/Http/Livewire/EmailComunications.php.bak
  22. 290 80
      app/Http/Livewire/Member.php
  23. 2889 0
      app/Http/Livewire/Member.php.bak
  24. 138 0
      app/Http/Livewire/MemberArchive.php
  25. 21 2
      app/Http/Livewire/Profile.php
  26. 243 0
      app/Http/Livewire/Profile.php.bak
  27. 38 22
      app/Http/Livewire/Rate.php
  28. 275 0
      app/Http/Livewire/Rate.php.bak
  29. 205 40
      app/Http/Livewire/RecordIN.php
  30. 1452 0
      app/Http/Livewire/RecordIN.php.bak
  31. 2 2
      app/Http/Livewire/RecordINOUT.php
  32. 811 0
      app/Http/Livewire/RecordINOUT.php.bak
  33. 15 1
      app/Http/Livewire/Reports.php
  34. 727 0
      app/Http/Livewire/Reports.php.bak
  35. 245 129
      app/Http/Livewire/SmsComunications.php
  36. 200 0
      app/Http/Livewire/SmsComunications.php.bak
  37. 284 0
      app/Http/Livewire/Subscription.php
  38. 105 0
      app/Http/Livewire/SubscriptionMember.php
  39. 16 0
      app/Http/Livewire/SubscriptionMembers.php
  40. 13 4
      app/Http/Livewire/User.php
  41. 770 0
      app/Http/Livewire/User.php.bak
  42. 134 0
      app/Jobs/SendSmsMessage.php
  43. 1 1
      app/Models/Azienda.php
  44. 15 0
      app/Models/Course.php
  45. 7 0
      app/Models/Member.php
  46. 52 0
      app/Models/MemberSubscription.php
  47. 6 1
      app/Models/Rate.php
  48. 12 0
      app/Models/ReceiptRow.php
  49. 11 0
      app/Models/RecordRow.php
  50. 69 0
      app/Models/SmsMessage.php
  51. 28 0
      app/Models/SmsMessageRecipient.php
  52. 35 0
      app/Models/Subscription.php
  53. 181 177
      composer.lock
  54. 34 0
      database/migrations/2025_11_12_164023_add_is_deleted_to_members.php
  55. 33 0
      database/migrations/2025_11_14_145909_add_course_id_to_records_rows.php
  56. 33 0
      database/migrations/2025_11_17_114817_add_course_id_to_receipts_rows.php
  57. 41 0
      database/migrations/2025_11_21_144756_create_sms_messages.php
  58. 87 0
      database/migrations/2025_11_26_132239_create_subscriptions_tables.php
  59. 17 1
      public/css/chart-reports.css
  60. 44 26
      public/css/new_style.css
  61. 10 0
      resources/views/first-login.blade.php
  62. 57 25
      resources/views/layouts/app.blade.php
  63. 45 53
      resources/views/livewire/category.blade.php
  64. 12 10
      resources/views/livewire/course.blade.php
  65. 2 2
      resources/views/livewire/course_list.blade.php
  66. 2 2
      resources/views/livewire/course_list_original.blade.php
  67. 23 5
      resources/views/livewire/course_member.blade.php
  68. 6 4
      resources/views/livewire/course_member_two.blade.php
  69. 2 2
      resources/views/livewire/course_subscription.blade.php
  70. 2 14
      resources/views/livewire/dashboard.blade.php
  71. 57 43
      resources/views/livewire/email_comunications.blade.php
  72. 523 291
      resources/views/livewire/member.blade.php
  73. 613 0
      resources/views/livewire/member_archive.blade.php
  74. 30 5
      resources/views/livewire/profile.blade.php
  75. 29 19
      resources/views/livewire/rate.blade.php
  76. 165 60
      resources/views/livewire/records_in.blade.php
  77. 24 3
      resources/views/livewire/records_in_out.blade.php
  78. 22 21
      resources/views/livewire/reports.blade.php
  79. 13 6
      resources/views/livewire/settings.blade.php
  80. 571 232
      resources/views/livewire/sms_comunications.blade.php
  81. 318 0
      resources/views/livewire/subscription.blade.php
  82. 503 0
      resources/views/livewire/subscription_member.blade.php
  83. 141 0
      resources/views/livewire/subscription_members.blade.php
  84. 55 15
      resources/views/livewire/user.blade.php
  85. 37 1
      resources/views/login.blade.php
  86. 6 0
      resources/views/receipt.blade.php
  87. 444 43
      routes/web.php

+ 36 - 9
app/Console/Commands/DispatchDueEmails.php

@@ -5,6 +5,8 @@ namespace App\Console\Commands;
 use Illuminate\Console\Command;
 use App\Models\EmailMessage;
 use App\Jobs\SendEmailMessage;
+use DateTimeZone;
+use Illuminate\Foundation\Auth\User;
 
 class DispatchDueEmails extends Command
 {
@@ -13,15 +15,40 @@ class DispatchDueEmails extends Command
 
     public function handle()
     {
-        app(\App\Http\Middleware\TenantMiddleware::class)->setupTenantConnection();
-
-        EmailMessage::where('status', 'scheduled')
-            ->where('schedule_at', '<=', now())
-            ->chunkById(100, function ($chunk) {
-                foreach ($chunk as $msg) {
-                    dispatch(new SendEmailMessage($msg->id));
-                }
-            });
+        $users = User::whereNotNull('tenant_host')
+            ->whereNotNull('tenant_database')
+            ->whereNotNull('tenant_username')
+            ->whereNotNull('tenant_password')
+            ->get([
+                'id',
+                'tenant_host',
+                'tenant_database',
+                'tenant_username',
+                'tenant_password',
+            ]);
+
+        if ($users->isEmpty()) {
+            $this->warn('Nessun utente con info di database trovata.');
+            return Command::SUCCESS;
+        }
+
+        $tenants = $users->unique(function ($u) {
+            return $u->tenant_host . '|' . $u->tenant_database . '|' . $u->tenant_username;
+        });
+
+        foreach ($tenants as $userTenant) {
+            $this->info("Processo tenant db={$userTenant->tenant_database} (user id={$userTenant->id})");
+
+            app(\App\Http\Middleware\TenantMiddleware::class)->setupTenantConnection($userTenant);
+
+            EmailMessage::where('status', 'scheduled')
+                ->where('schedule_at', '<=', now())
+                ->chunkById(100, function ($chunk) {
+                    foreach ($chunk as $msg) {
+                        dispatch(new SendEmailMessage($msg->id));
+                    }
+                });
+        }
 
         return Command::SUCCESS;
     }

+ 54 - 0
app/Console/Commands/DispatchDueSms.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Models\SmsMessage;
+use App\Jobs\SendSmsMessage;
+use Illuminate\Foundation\Auth\User;
+
+class DispatchDueSms extends Command
+{
+    protected $signature = 'sms:dispatch-due';
+    protected $description = 'Invia gli sms programmati giunti a scadenza';
+
+    public function handle()
+    {
+        $users = User::whereNotNull('tenant_host')
+            ->whereNotNull('tenant_database')
+            ->whereNotNull('tenant_username')
+            ->whereNotNull('tenant_password')
+            ->get([
+                'id',
+                'tenant_host',
+                'tenant_database',
+                'tenant_username',
+                'tenant_password',
+            ]);
+
+        if ($users->isEmpty()) {
+            $this->warn('Nessun utente con info di database trovata.');
+            return Command::SUCCESS;
+        }
+
+        $tenants = $users->unique(function ($u) {
+            return $u->tenant_host . '|' . $u->tenant_database . '|' . $u->tenant_username;
+        });
+
+        foreach ($tenants as $userTenant) {
+            $this->info("Processo tenant db={$userTenant->tenant_database} (user id={$userTenant->id})");
+
+            app(\App\Http\Middleware\TenantMiddleware::class)->setupTenantConnection($userTenant);
+
+            // SmsMessage::where('status', 'scheduled')
+            SmsMessage::where('schedule_at', '<=', now())
+                ->chunkById(100, function ($chunk) {
+                    foreach ($chunk as $msg) {
+                        dispatch(new SendSmsMessage($msg->id));
+                    }
+                });
+        }
+
+        return Command::SUCCESS;
+    }
+}

+ 2 - 0
app/Console/Kernel.php

@@ -19,6 +19,8 @@ class Kernel extends ConsoleKernel
 
         // invia email programmate
         $schedule->command('emails:dispatch-due')->everyMinute();
+        // invia sms programmati
+        $schedule->command('sms:dispatch-due')->everyMinute();
     }
 
     /**

+ 2 - 2
app/Http/Livewire/Azienda.php

@@ -279,8 +279,8 @@ class Azienda extends Component
             }
 
             session()->flash('message', $this->azienda->wasRecentlyCreated ?
-                'Dati aziendali creati con successo!' :
-                'Dati aziendali aggiornati con successo!'
+                'Dati societari creati con successo' :
+                'Dati societari aggiornati con successo'
             );
 
             $this->update = false;

+ 385 - 0
app/Http/Livewire/Azienda.php.bak

@@ -0,0 +1,385 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Livewire\WithFileUploads;
+use Illuminate\Support\Str;
+
+use App\Models\Azienda as AziendaModel;
+use App\Services\LogoUploadServices;
+use Illuminate\Support\Facades\Log;
+use App\Http\Middleware\TenantMiddleware;
+
+class Azienda extends Component
+{
+    use WithFileUploads;
+
+    public $update = false;
+    public $azienda;
+    public $ragione_sociale;
+    public $nome_associazione;
+    public $tipologia;
+    public $logo;
+    public $temp_logo;
+    public $sede_legale_nazione;
+    public $sede_legale_provincia;
+    public $sede_legale_comune;
+    public $sede_legale_indirizzo;
+    public $sede_legale_cap;
+    public $same_address = false;
+    public $sede_operativa_nazione;
+    public $sede_operativa_provincia;
+    public $sede_operativa_comune;
+    public $sede_operativa_indirizzo;
+    public $sede_operativa_cap;
+    public $email;
+    public $pec;
+    public $telefono;
+    public $cellulare;
+    public $partita_iva;
+    public $codice_fiscale;
+    public $codice_sdi;
+    public $chiusura_anno_fiscale;
+    public $scadenza_abbonamenti;
+    public $scadenza_pagamenti_uscita;
+
+    public $search = '';
+    public $selectedDisciplines = [];
+    public $disciplineId = '';
+    public $discipline = [];
+    public $activeTab = 'generale';
+
+    protected $rules = [
+        'ragione_sociale' => 'required|string|max:255',
+        'email' => 'required|email|max:255',
+        'pec' => 'required|email|max:255',
+        'cellulare' => 'required|string|max:20',
+    ];
+
+    public function resetFields()
+    {
+        $this->ragione_sociale = null;
+        $this->nome_associazione = null;
+        $this->tipologia = null;
+        $this->discipline = null;
+        $this->temp_logo = null;
+
+        $this->sede_legale_nazione = null;
+        $this->sede_legale_provincia = null;
+        $this->sede_legale_comune = null;
+        $this->sede_legale_indirizzo = null;
+        $this->sede_legale_cap = null;
+
+        $this->same_address = false;
+        $this->sede_operativa_nazione = null;
+        $this->sede_operativa_provincia = null;
+        $this->sede_operativa_comune = null;
+        $this->sede_operativa_indirizzo = null;
+        $this->sede_operativa_cap = null;
+
+        $this->email = null;
+        $this->pec = null;
+        $this->telefono = null;
+        $this->cellulare = null;
+
+        $this->partita_iva = null;
+        $this->codice_fiscale = null;
+        $this->codice_sdi = null;
+    }
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function mount()
+    {
+        $this->azienda = AziendaModel::first();
+
+        if ($this->azienda) {
+            $this->ragione_sociale = $this->azienda->ragione_sociale;
+            $this->nome_associazione = $this->azienda->nome_associazione;
+            $this->tipologia = $this->azienda->tipologia;
+            $this->discipline = $this->azienda->discipline;
+            $this->logo = $this->azienda->logo;
+
+            $this->sede_legale_nazione = $this->azienda->sede_legale_nazione;
+            $this->sede_legale_provincia = $this->azienda->sede_legale_provincia;
+            $this->sede_legale_comune = $this->azienda->sede_legale_comune;
+            $this->sede_legale_indirizzo = $this->azienda->sede_legale_indirizzo;
+            $this->sede_legale_cap = $this->azienda->sede_legale_cap;
+
+            $this->sede_operativa_nazione = $this->azienda->sede_operativa_nazione;
+            $this->sede_operativa_provincia = $this->azienda->sede_operativa_provincia;
+            $this->sede_operativa_comune = $this->azienda->sede_operativa_comune;
+            $this->sede_operativa_indirizzo = $this->azienda->sede_operativa_indirizzo;
+            $this->sede_operativa_cap = $this->azienda->sede_operativa_cap;
+
+            $this->email = $this->azienda->email;
+            $this->pec = $this->azienda->pec;
+            $this->telefono = $this->azienda->telefono;
+            $this->cellulare = $this->azienda->cellulare;
+
+            $this->partita_iva = $this->azienda->partita_iva;
+            $this->codice_fiscale = $this->azienda->codice_fiscale;
+            $this->codice_sdi = $this->azienda->codice_sdi;
+
+
+            if (
+                $this->sede_legale_nazione == $this->sede_operativa_nazione &&
+                $this->sede_legale_provincia == $this->sede_operativa_provincia &&
+                $this->sede_legale_comune == $this->sede_operativa_comune &&
+                $this->sede_legale_indirizzo == $this->sede_operativa_indirizzo &&
+                $this->sede_legale_cap == $this->sede_operativa_cap
+            ) {
+                $this->same_address = true;
+            }
+            Log::info('Azienda disciplines: ' . json_encode($this->azienda->discipline));
+            if ($this->azienda && $this->azienda->discipline) {
+                $this->selectedDisciplines = array_map('trim', array: explode(';', $this->azienda->discipline));
+            }
+        } else {
+            $this->update = true;
+            $this->resetFields();
+        }
+        $this->loadDisciplines();
+    }
+
+    public function render()
+    {
+        return view('livewire.azienda');
+    }
+
+    public function updatedSameAddress()
+    {
+        if ($this->same_address) {
+            $this->sede_operativa_nazione = $this->sede_legale_nazione;
+            $this->sede_operativa_provincia = $this->sede_legale_provincia;
+            $this->sede_operativa_comune = $this->sede_legale_comune;
+            $this->sede_operativa_indirizzo = $this->sede_legale_indirizzo;
+            $this->sede_operativa_cap = $this->sede_legale_cap;
+        } else {
+            $this->sede_operativa_nazione = null;
+            $this->sede_operativa_provincia = null;
+            $this->sede_operativa_comune = null;
+            $this->sede_operativa_indirizzo = null;
+            $this->sede_operativa_cap = null;
+        }
+    }
+
+    public function updatedSedeOperativaNazione()
+    {
+        $this->checkAddressDifference();
+    }
+
+    public function updatedSedeOperativaProvincia()
+    {
+        $this->checkAddressDifference();
+    }
+
+    public function updatedSedeOperativaComune()
+    {
+        $this->checkAddressDifference();
+    }
+
+    public function updatedSedeOperativaIndirizzo()
+    {
+        $this->checkAddressDifference();
+    }
+
+    public function updatedSedeOperativaCap()
+    {
+        $this->checkAddressDifference();
+    }
+
+    public function checkAddressDifference()
+    {
+        if (
+            $this->sede_legale_nazione == $this->sede_operativa_nazione &&
+            $this->sede_legale_provincia == $this->sede_operativa_provincia &&
+            $this->sede_legale_comune == $this->sede_operativa_comune &&
+            $this->sede_legale_indirizzo == $this->sede_operativa_indirizzo &&
+            $this->sede_legale_cap == $this->sede_operativa_cap
+        ) {
+            $this->same_address = true;
+        } else {
+            $this->same_address = false;
+        }
+    }
+
+    public function setTab($tab)
+    {
+        $this->activeTab = $tab;
+    }
+
+    public function edit()
+    {
+        $this->update = true;
+    }
+
+    public function save()
+    {
+        $this->validate();
+        Log::info('Saving discipline: ' . json_encode(implode('; ', $this->selectedDisciplines)));
+
+        try {
+            $data = [
+                'ragione_sociale' => $this->ragione_sociale,
+                'nome_associazione' => $this->nome_associazione,
+                'tipologia' => $this->tipologia,
+                'discipline' => implode('; ', $this->selectedDisciplines),
+
+                'sede_legale_nazione' => $this->sede_legale_nazione,
+                'sede_legale_provincia' => $this->sede_legale_provincia,
+                'sede_legale_comune' => $this->sede_legale_comune,
+                'sede_legale_indirizzo' => $this->sede_legale_indirizzo,
+                'sede_legale_cap' => $this->sede_legale_cap,
+
+                'sede_operativa_nazione' => $this->sede_operativa_nazione,
+                'sede_operativa_provincia' => $this->sede_operativa_provincia,
+                'sede_operativa_comune' => $this->sede_operativa_comune,
+                'sede_operativa_indirizzo' => $this->sede_operativa_indirizzo,
+                'sede_operativa_cap' => $this->sede_operativa_cap,
+
+                'same_address' => $this->same_address,
+
+                'email' => $this->email,
+                'pec' => $this->pec,
+                'telefono' => $this->telefono,
+                'cellulare' => $this->cellulare,
+
+                'partita_iva' => $this->partita_iva,
+                'codice_fiscale' => $this->codice_fiscale,
+                'codice_sdi' => $this->codice_sdi,
+            ];
+
+                      // Create or update azienda first
+            if ($this->azienda) {
+                $this->azienda->update($data);
+            } else {
+                $this->azienda = AziendaModel::create($data);
+            }
+
+            // Handle logo upload using the service
+            if ($this->temp_logo) {
+                try {
+                    Log::info('Starting logo upload with service');
+                    $logoService = app(LogoUploadServices::class);
+                    $logoPath = $logoService->uploadLogo($this->temp_logo, $this->azienda);
+                    Log::info('Logo uploaded successfully to: ' . $logoPath);
+
+                    // Reset temp logo after successful upload
+                    $this->temp_logo = null;
+
+                } catch (\Exception $e) {
+                    Log::error('Error uploading logo via service: ' . $e->getMessage());
+                    session()->flash('error', 'Errore durante il caricamento del logo: ' . $e->getMessage());
+                    return;
+                }
+            }
+
+            session()->flash('message', $this->azienda->wasRecentlyCreated ?
+                'Dati aziendali creati con successo!' :
+                'Dati aziendali aggiornati con successo!'
+            );
+
+            $this->update = false;
+
+        } catch (\Exception $ex) {
+            Log::error('Error in save method: ' . $ex->getMessage());
+            session()->flash('error', 'Errore: ' . $ex->getMessage());
+        }
+    }
+
+    public function getSelectedDisciplineNamesProperty()
+    {
+        if (empty($this->selectedDisciplines)) {
+            return [];
+        }
+        Log::info('Selected disciplines: ' . json_encode($this->selectedDisciplines));
+
+        return \App\Models\Discipline::whereIn('id', $this->selectedDisciplines)
+            ->pluck('name')
+            ->toArray();
+    }
+
+    public function cancel()
+    {
+        $this->resetFields();
+        $this->mount();
+        $this->update = false;
+    }
+
+    public function loadDisciplines()
+    {
+        $this->discipline = \App\Models\Discipline::select('id', 'name')->get();
+    }
+
+    public function addDiscipline()
+    {
+        if (!empty($this->disciplineId)) {
+            $discipline = \App\Models\Discipline::find($this->disciplineId);
+
+            if ($discipline && !in_array($discipline->name, $this->selectedDisciplines)) {
+                $this->selectedDisciplines[] = $discipline->name;
+                $this->disciplineId = '';
+            }
+        }
+    }
+
+    public function removeDiscipline($index)
+    {
+        if (isset($this->selectedDisciplines[$index])) {
+            unset($this->selectedDisciplines[$index]);
+            $this->selectedDisciplines = array_values($this->selectedDisciplines);
+        }
+    }
+
+    public function updatedSearch()
+    {
+        $this->loadDisciplines();
+    }
+
+      public function removeLogo()
+    {
+        if ($this->azienda) {
+            try {
+                $logoService = app(LogoUploadServices::class);
+                if ($logoService->deleteLogo($this->azienda)) {
+                    $this->logo = null;
+                    session()->flash('message', 'Logo rimosso con successo!');
+                    Log::info('Logo removed successfully for azienda: ' . $this->azienda->id);
+                } else {
+                    session()->flash('error', 'Nessun logo da rimuovere.');
+                }
+            } catch (\Exception $e) {
+                Log::error('Error removing logo: ' . $e->getMessage());
+                session()->flash('error', 'Errore durante la rimozione del logo: ' . $e->getMessage());
+            }
+        }
+    }
+
+    /**
+     * Get logo URL for display
+     */
+    public function getLogoUrlProperty()
+    {
+        if ($this->azienda && $this->azienda->logo) {
+            $logoService = app(LogoUploadServices::class);
+            return $logoService->getLogoUrl($this->azienda);
+        }
+        return null;
+    }
+
+    /**
+     * Check if logo exists
+     */
+    public function getHasLogoProperty()
+    {
+        if ($this->azienda) {
+            $logoService = app(LogoUploadServices::class);
+            return $logoService->logoExists($this->azienda);
+        }
+        return false;
+    }
+}

+ 1 - 1
app/Http/Livewire/Category.php

@@ -40,7 +40,7 @@ class Category extends Component
 
     public function render()
     {
-        $this->records = \App\Models\Category::where('parent_id', null)->get();
+        $this->records = \App\Models\Category::where('parent_id', null)->orderBy('name')->get();
         return view('livewire.category');
     }
 

+ 132 - 0
app/Http/Livewire/Category.php.bak

@@ -0,0 +1,132 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+use Illuminate\Support\Facades\Auth;
+
+class Category extends Component
+{
+    public $records, $parent_id, $name, $enabled, $dataId, $update = false, $add = false;
+
+    public $parent = '';
+
+    protected $rules = [
+        'name' => 'required'
+    ];
+
+    protected $messages = [
+        'name.required' => 'Il nome è obbligatorio'
+    ];
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+    public function mount(){
+
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
+            return redirect()->to('/dashboard');
+
+    }
+
+    public function resetFields(){
+        $this->name = '';
+        $this->parent_id = null;
+        $this->parent = '';
+        $this->enabled = true;
+    }
+
+    public function render()
+    {
+        $this->records = \App\Models\Category::where('parent_id', null)->get();
+        return view('livewire.category');
+    }
+
+    public function add()
+    {
+        $this->resetFields();
+        $this->add = true;
+        $this->update = false;
+    }
+
+    public function addLevel($parent_id)
+    {
+        $this->resetFields();
+        $this->parent_id = $parent_id;
+        $this->parent = \App\Models\Category::findOrFail($parent_id)->name;
+        $this->add = true;
+        $this->update = false;
+    }
+
+    public function store()
+    {
+        $this->validate();
+        try {
+            \App\Models\Category::create([
+                'name' => $this->name,
+                'parent_id' => $this->parent_id,
+                'enabled' => $this->enabled
+            ]);
+            session()->flash('success','Categoria creata');
+            $this->resetFields();
+            $this->add = false;
+        } catch (\Exception $ex) {
+            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function edit($id){
+        try {
+            $category = \App\Models\Category::findOrFail($id);
+            if( !$category) {
+                session()->flash('error','Categoria non trovata');
+            } else {
+                $this->name = $category->name;
+                $this->enabled = $category->enabled;
+                $this->parent_id = $category->parent_id;
+                $this->dataId = $category->id;
+                $this->update = true;
+                $this->add = false;
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function update()
+    {
+        $this->validate();
+        try {
+            \App\Models\Category::whereId($this->dataId)->update([
+                'name' => $this->name,
+                'parent_id' => $this->parent_id,
+                'enabled' => $this->enabled
+            ]);
+            session()->flash('success','Categoria aggiornata');
+            $this->resetFields();
+            $this->update = false;
+        } catch (\Exception $ex) {
+            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function cancel()
+    {
+        $this->add = false;
+        $this->update = false;
+        $this->resetFields();
+    }
+
+    public function delete($id)
+    {
+        try{
+            \App\Models\Category::find($id)->delete();
+            session()->flash('success',"Categoria eliminata");
+            return redirect(request()->header('Referer'));
+        }catch(\Exception $e){
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
+        }
+    }
+}

+ 19 - 2
app/Http/Livewire/Causals.php

@@ -20,6 +20,7 @@ class Causals extends Component
     public $idx = -1;
     public $causal_id = null;
     public $show_hidden = true;
+    public $show_course_causals = true;
 
     public function boot()
     {
@@ -32,7 +33,7 @@ class Causals extends Component
         $this->idx = $idx;
         $this->causal_id = $causal_id;
 
-        if ($this->causal_id != null) {
+        if ($this->causal_id != null && $this->causal_id != 0) {
             $c = \App\Models\Causal::findOrFail($this->causal_id);
             $ids = array_reverse($c->recursiveParent($c->parent_id, [$c->id]));
 
@@ -75,16 +76,23 @@ class Causals extends Component
     {
         $reset = false;
 
-        // Build query with visibility filter
         $visibilityFilter = function($query) {
             return $query;
         };
+        $courseFilter = function($query) {
+            return $query;
+        };
 
         if ($this->show_hidden == false) {
             $visibilityFilter = function($query) {
                 return $query->where('hidden', false);
             };
         }
+        if ($this->show_course_causals == false) {
+            $courseFilter = function($query) {
+                return $query->whereNotIn('name', ['PAGAMENTO CORSO', 'PAGAMENTO ISCRIZIONE']);
+            };
+        }
 
         if ($this->level_1_id > 0) {
             $this->level_2 = \App\Models\Causal::where('parent_id', $this->level_1_id)
@@ -92,6 +100,9 @@ class Causals extends Component
                 ->where(function($query) use ($visibilityFilter) {
                     return $visibilityFilter($query);
                 })
+                ->where(function($query) use ($courseFilter) {
+                    return $courseFilter($query);
+                })
                 ->orderBy('name')
                 ->get();
 
@@ -107,6 +118,9 @@ class Causals extends Component
                 ->where(function($query) use ($visibilityFilter) {
                     return $visibilityFilter($query);
                 })
+                ->where(function($query) use ($courseFilter) {
+                    return $courseFilter($query);
+                })
                 ->orderBy('name')
                 ->get();
 
@@ -126,6 +140,9 @@ class Causals extends Component
             ->where(function($query) use ($visibilityFilter) {
                 return $visibilityFilter($query);
             })
+            ->where(function($query) use ($courseFilter) {
+                return $courseFilter($query);
+            })
             ->orderBy('name')
             ->get();
 

+ 134 - 0
app/Http/Livewire/Causals.php.bak

@@ -0,0 +1,134 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+
+class Causals extends Component
+{
+    public $level_1 = [];
+    public $level_2 = [];
+    public $level_3 = [];
+
+    public $level_1_id = 0;
+    public $level_2_id = 0;
+    public $level_3_id = 0;
+
+    public $type = '';
+    public $emit = 'setCausal';
+    public $idx = -1;
+    public $causal_id = null;
+    public $show_hidden = true;
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function mount($type, $idx, $causal_id)
+    {
+        $this->type = $type;
+        $this->idx = $idx;
+        $this->causal_id = $causal_id;
+
+        if ($this->causal_id != null) {
+            $c = \App\Models\Causal::findOrFail($this->causal_id);
+            $ids = array_reverse($c->recursiveParent($c->parent_id, [$c->id]));
+
+            foreach($ids as $ii => $i) {
+                if ($ii == 0) {
+                    $this->level_1_id = $i;
+                }
+                if ($ii == 1) {
+                    $this->level_2_id = $i;
+                }
+                if ($ii == 2) {
+                    $this->level_3_id = $i;
+                }
+            }
+        }
+    }
+
+    public function updatedLevel1Id()
+    {
+        $this->emit($this->emit, null, $this->idx);
+        $this->level_2_id = 0;
+        $this->level_2 = [];
+        $this->level_3_id = 0;
+        $this->level_3 = [];
+    }
+
+    public function updatedLevel2Id()
+    {
+        $this->emit($this->emit, null, $this->idx);
+        $this->level_3_id = 0;
+        $this->level_3 = [];
+    }
+
+    public function updatedLevel3Id()
+    {
+        $this->emit($this->emit, null, $this->idx);
+    }
+
+    public function render()
+    {
+        $reset = false;
+
+        // Build query with visibility filter
+        $visibilityFilter = function($query) {
+            return $query;
+        };
+
+        if ($this->show_hidden == false) {
+            $visibilityFilter = function($query) {
+                return $query->where('hidden', false);
+            };
+        }
+
+        if ($this->level_1_id > 0) {
+            $this->level_2 = \App\Models\Causal::where('parent_id', $this->level_1_id)
+                ->where('type', $this->type)
+                ->where(function($query) use ($visibilityFilter) {
+                    return $visibilityFilter($query);
+                })
+                ->orderBy('name')
+                ->get();
+
+            if (sizeof($this->level_2) == 0) {
+                $this->emit($this->emit, $this->level_1_id, $this->idx);
+                $reset = true;
+            }
+        }
+
+        if ($this->level_2_id > 0) {
+            $this->level_3 = \App\Models\Causal::where('parent_id', $this->level_2_id)
+                ->where('type', $this->type)
+                ->where(function($query) use ($visibilityFilter) {
+                    return $visibilityFilter($query);
+                })
+                ->orderBy('name')
+                ->get();
+
+            if (sizeof($this->level_3) == 0) {
+                $this->emit($this->emit, $this->level_2_id, $this->idx);
+                $reset = true;
+            }
+        }
+
+        if ($this->level_3_id > 0) {
+            $this->emit($this->emit, $this->level_3_id, $this->idx);
+            $reset = true;
+        }
+
+        $this->level_1 = \App\Models\Causal::where('parent_id', null)
+            ->where('type', $this->type)
+            ->where(function($query) use ($visibilityFilter) {
+                return $visibilityFilter($query);
+            })
+            ->orderBy('name')
+            ->get();
+
+        return view('livewire.causals');
+    }
+}

+ 66 - 41
app/Http/Livewire/Course.php

@@ -35,7 +35,7 @@ class Course extends Component
     public $msgPrices = '';
     public $msgWhen = '';
 
-    public $course_types = [];
+    // public $course_types = [];
     public $course_durations = [];
     public $course_frequencies = [];
     public $course_levels = [];
@@ -112,7 +112,7 @@ class Course extends Component
             // $this->categories[] = array('id' => $record->id, 'name' => str_repeat(" / ", $indentation) . $record->name);
             $this->categories[] = array('id' => $record->id, 'name' => $record->getTree(), 'indentation' => $indentation);
             if(count($record->childs))
-                $this->getCategories($record->childs, $indentation + 1);
+                $this->getCategories($record->childs->sortBy('name'), $indentation + 1);
         }
     }
 
@@ -123,7 +123,7 @@ class Course extends Component
 
         $this->categories = array();
 
-        $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->get(), 0);
+        $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
 
         for($i=date("Y"); $i<=date("Y") + 1; $i++)
         {
@@ -140,7 +140,7 @@ class Course extends Component
             $this->monthList[$i][11] = "Novembre";
             $this->monthList[$i][12] = "Dicembre";
         }
-        $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
+        // $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
         $this->course_durations = \App\Models\CourseDuration::select('*')->where('enabled', true)->get();
         $this->course_levels = \App\Models\CourseLevel::select('*')->where('enabled', true)->get();
         $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->where('enabled', true)->get();
@@ -201,7 +201,7 @@ class Course extends Component
             $subscriptions = array_column($this->prices, 'course_subscription_id');
             $unique_subscriptions = array_unique($subscriptions);
             if (count($subscriptions) != count($unique_subscriptions))
-                $this->msgPrices = 'È possibile aggiungere solo un abbonamento per ciascun tipo';
+                $this->msgPrices = 'Non è possibile aggiungere più volte la stessa tipologia di pagamento';
 
             if ($this->msgPrices == '' &&  $this->msgWhen == '')
             {
@@ -322,7 +322,8 @@ class Course extends Component
                 //$causal = "PAGAMENTO CORSO [nome corso]-LIVELLO-FREQUENZA-ANNO-[tipo abbonamento]-[mese inizio]/[mese fine]
 
                 /*
-                $causal = "PAGAMENTO CORSO " . $this->name . " - " . $lev . " - " . $freq . " - " . $this->year . " - " . $mFrom . "/" . $mTo;
+                // $causal = "PAGAMENTO CORSO " . $this->name . " - " . $lev . " - " . $freq . " - " . $this->year . " - " . $mFrom . "/" . $mTo;
+                $causal = "PAGAMENTO CORSO";
                 $cp = \App\Models\Causal::where('name', $causal)->first();
                 if (!$cp)
                 {
@@ -341,7 +342,8 @@ class Course extends Component
                 }
                 $course->causal_id = $cp->id;
 
-                $causal = "PAGAMENTO ISCRIZIONE " . $this->name . " - " . $lev . " - " . $freq . " - " . $this->year . " - " . $mFrom . "/" . $mTo;
+                // $causal = "PAGAMENTO ISCRIZIONE " . $this->name . " - " . $lev . " - " . $freq . " - " . $this->year . " - " . $mFrom . "/" . $mTo;
+                $causal = "PAGAMENTO ISCRIZIONE";
                 $ci = \App\Models\Causal::where('name', $causal)->first();
                 if (!$ci)
                 {
@@ -438,33 +440,52 @@ class Course extends Component
     {
         $this->validate();
         try {
-            \App\Models\Course::whereId($this->dataId)->update([
-                'name' => $this->name,
-                'parent_id' => $this->parent_id,
-                'course_type_id' => $this->course_type_id,
-                'course_duration_id' => $this->course_duration_id,
-                'course_frequency_id' => $this->course_frequency_id,
-                'course_level_id' => $this->course_level_id,
-                'date_from' => $this->date_from,
-                'date_to' => $this->date_to,
-                'category_id' => $this->category_id,
-                'causal_id' => $this->causal_id,
-                'sub_causal_id' => $this->sub_causal_id,
-                'max_members' => $this->max_members,
-                'instructor_id' => $this->instructor_id,
-                'year' => $this->year,
-                'price' => currencyToDouble($this->price),
-                'subscription_price' => currencyToDouble($this->subscription_price),
-                'months' => json_encode($this->months),
-                'type' => $this->type,
-                'when' => json_encode($this->when),
-                'prices' => json_encode($this->prices),
-                'enabled' => $this->enabled
-            ]);
-            session()->flash('success','Corso aggiornato');
-            $this->resetFields();
-            $this->update = false;
-            $this->emit('setEdit', false);
+            $this->msgPrices = '';
+            $this->msgWhen = '';
+
+            if ($this->type == 'standard') {
+                if ($this->when[0]['from'] == '')
+                    $this->msgWhen = 'Devi inserire almeno un giorno';
+            }
+    
+            if ($this->prices[0]['course_subscription_id'] == null)
+                $this->msgPrices = 'Devi inserire almeno un prezzo';
+            
+            $subscriptions = array_column($this->prices, 'course_subscription_id');
+            $unique_subscriptions = array_unique($subscriptions);
+            if (count($subscriptions) != count($unique_subscriptions))
+                $this->msgPrices = 'Non è possibile aggiungere più volte la stessa tipologia di pagamento';
+
+            if ($this->msgPrices == '' &&  $this->msgWhen == '')
+            {
+                \App\Models\Course::whereId($this->dataId)->update([
+                    'name' => $this->name,
+                    'parent_id' => $this->parent_id,
+                    'course_type_id' => $this->course_type_id,
+                    'course_duration_id' => $this->course_duration_id,
+                    'course_frequency_id' => $this->course_frequency_id,
+                    'course_level_id' => $this->course_level_id,
+                    'date_from' => $this->date_from,
+                    'date_to' => $this->date_to,
+                    'category_id' => $this->category_id,
+                    'causal_id' => $this->causal_id,
+                    'sub_causal_id' => $this->sub_causal_id,
+                    'max_members' => $this->max_members,
+                    'instructor_id' => $this->instructor_id,
+                    'year' => $this->year,
+                    'price' => currencyToDouble($this->price),
+                    'subscription_price' => currencyToDouble($this->subscription_price),
+                    'months' => json_encode($this->months),
+                    'type' => $this->type,
+                    'when' => json_encode($this->when),
+                    'prices' => json_encode($this->prices),
+                    'enabled' => $this->enabled
+                ]);
+                session()->flash('success','Corso aggiornato');
+                $this->resetFields();
+                $this->update = false;
+                $this->emit('setEdit', false);
+            }
         } catch (\Exception $ex) {
             session()->flash('error','Errore (' . $ex->getMessage() . ')');
         }
@@ -507,13 +528,17 @@ class Course extends Component
     public function duplicate($id, $isMultiple){
         $course = \App\Models\Course::findOrFail($id);
         $newCourse = $course->replicate();
-        $newYear = date("Y") . "-" . (date("Y") + 1);
-        if ($course->year != '')
-        {
-            list($u, $y) = explode("-", $course->year);
-            $newYear = ($u + 1) . "-" . ($u + 2);
-        }
-        $newCourse->year = $newYear;
+        // $newYear = date("Y") . "-" . (date("Y") + 1);
+        // if ($course->year != '')
+        // {
+        //     // list($u, $y) = explode("-", $course->year);
+        //     // $newYear = ($u + 1) . "-" . ($u + 2);
+        //     $newYear = $course->year;
+        // }
+        // $newCourse->year = $newYear;
+        $newCourse->date_from = null;
+        $newCourse->date_to = null;
+        $newCourse->year = null;
         $newCourse->save();
         if (!$isMultiple)
             $this->edit($newCourse->id);

+ 566 - 0
app/Http/Livewire/Course.php.bak

@@ -0,0 +1,566 @@
+<?php
+
+namespace App\Http\Livewire;
+use App\Http\Middleware\TenantMiddleware;
+use Livewire\Component;
+use Illuminate\Support\Facades\Auth;
+class Course extends Component
+{
+
+    protected $listeners = ['setCausal' => 'setCausal', 'setSubscriptionCausal' => 'setSubscriptionCausal'];
+
+    public $records, $parent_id, $name, $enabled, $dataId, $update = false, $add = false;
+    public $course_type_id,
+        $course_duration_id,
+        $course_frequency_id,
+        $course_level_id,
+        $causal_id,
+        $category_id,
+        $sub_causal_id,
+        $max_members,
+        $instructor_id,
+        $year,
+        $price,
+        $subscription_price,
+        $months,
+        $when,
+        $prices,
+        $type,
+        $date_from, $date_to;
+
+    public $categories = array();
+
+    public $selectedYear = '';
+
+    public $msgPrices = '';
+    public $msgWhen = '';
+
+    public $course_types = [];
+    public $course_durations = [];
+    public $course_frequencies = [];
+    public $course_levels = [];
+    public $course_subscriptions = [];
+    public $instructors = [];
+    public $causals = [];
+
+    public $course_years = [];
+
+    public $monthList = [];
+
+    public $typeIN = 'IN';
+    public $setSubscriptionCausal = 'setSubscriptionCausal';
+
+    // public $selectedMonthList = [];
+
+    protected $rules = [
+        'name' => 'required',
+        'course_frequency_id' => 'required',
+        'course_level_id' => 'required',
+        'date_from' => 'required',
+        'date_to' => 'required',
+        'year' => 'required',
+        'subscription_price' => 'required|min:0|not_in:0',
+        /*'course_type_id' => 'required',
+        'course_duration_id' => 'required',
+
+        'causal_id' => 'required',
+        'sub_causal_id' => 'required',*/
+    ];
+
+    protected $messages = [
+        'name.required' => 'Il nome è obbligatorio',
+        'subscription_price.required' => 'Deve essere maggionre di zero',
+        'subscription_price.not_in' => 'Deve essere maggionre di zero',
+        /*'causal_id' => 'Campo obbligatorio',
+        'sub_causal_id' => 'Campo obbligatorio',*/
+    ];
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+    public function resetFields(){
+        $this->name = '';
+        $this->parent_id = null;
+        $this->course_type_id = null;
+        $this->course_duration_id = null;
+        $this->course_frequency_id = null;
+        $this->course_level_id = null;
+        $this->category_id = null;
+        $this->causal_id = null;
+        $this->sub_causal_id = null;
+        $this->max_members = 0;
+        $this->instructor_id = null;
+        $this->year = null;
+        $this->price = 0;
+        $this->subscription_price = 0;
+        $this->date_from = null;
+        $this->date_to = null;
+        $this->months = array();
+        $this->enabled = true;
+        $this->type = 'standard';
+        $this->when = array();
+        $this->when[] = array('day' => array(), 'from' => '', 'to' => '');
+        $this->prices = [];
+        $this->prices[] = array('course_subscription_id' => null, 'price' => 0);
+        $this->emit('load-data-table');
+    }
+
+    public function getCategories($records, $indentation)
+    {
+        foreach($records as $record)
+        {
+            // $this->categories[] = array('id' => $record->id, 'name' => str_repeat(" / ", $indentation) . $record->name);
+            $this->categories[] = array('id' => $record->id, 'name' => $record->getTree(), 'indentation' => $indentation);
+            if(count($record->childs))
+                $this->getCategories($record->childs, $indentation + 1);
+        }
+    }
+
+    public function mount(){
+
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
+            return redirect()->to('/dashboard');
+
+        $this->categories = array();
+
+        $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->get(), 0);
+
+        for($i=date("Y"); $i<=date("Y") + 1; $i++)
+        {
+            $this->monthList[$i][1] = "Gennaio";
+            $this->monthList[$i][2] = "Febbraio";
+            $this->monthList[$i][3] = "Marzo";
+            $this->monthList[$i][4] = "Aprile";
+            $this->monthList[$i][5] = "Maggio";
+            $this->monthList[$i][6] = "Giugno";
+            $this->monthList[$i][7] = "Luglio";
+            $this->monthList[$i][8] = "Agosto";
+            $this->monthList[$i][9] = "Settembre";
+            $this->monthList[$i][10] = "Ottobre";
+            $this->monthList[$i][11] = "Novembre";
+            $this->monthList[$i][12] = "Dicembre";
+        }
+        $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
+        $this->course_durations = \App\Models\CourseDuration::select('*')->where('enabled', true)->get();
+        $this->course_levels = \App\Models\CourseLevel::select('*')->where('enabled', true)->get();
+        $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->where('enabled', true)->get();
+        $this->course_frequencies = \App\Models\CourseFrequency::select('*')->where('enabled', true)->get();
+        $this->causals = \App\Models\Causal::select('*')->where('type', 'IN')->where('enabled', true)->get();
+        $this->instructors = \App\Models\User::select('*')->where('level', 2)->get();
+
+        $this->course_years = \App\Models\Course::select('year')->where('year', '<>', '')->groupBy('year')->pluck('year');
+
+    }
+
+    public function render()
+    {
+        if (isset($_GET["year"]))
+            $this->selectedYear = $_GET["year"];
+        else
+            // $this->selectedYear =  sizeof($this->course_years) > 0 ? $this->course_years[0] : '';
+            $this->selectedYear = date("Y") . "-" . (date("Y") + 1);
+
+        $this->records = \App\Models\Course::where('parent_id', null)->where('year', $this->selectedYear)->with('type', 'duration')->get();
+        return view('livewire.course');
+    }
+
+    public function add()
+    {
+        $this->resetFields();
+        $this->add = true;
+        $this->update = false;
+
+        $this->emit('setEdit', true);
+    }
+
+    /*
+    public function addLevel($parent_id)
+    {
+        $this->resetFields();
+        $this->parent_id = $parent_id;
+        $this->add = true;
+        $this->update = false;
+    }
+    */
+    public function store()
+    {
+        $this->validate();
+        try {
+
+            $this->msgPrices = '';
+            $this->msgWhen = '';
+
+            if ($this->type == 'standard') {
+                if ($this->when[0]['from'] == '')
+                    $this->msgWhen = 'Devi inserire almeno un giorno';
+            }
+    
+            if ($this->prices[0]['course_subscription_id'] == null)
+                $this->msgPrices = 'Devi inserire almeno un prezzo';
+            
+            $subscriptions = array_column($this->prices, 'course_subscription_id');
+            $unique_subscriptions = array_unique($subscriptions);
+            if (count($subscriptions) != count($unique_subscriptions))
+                $this->msgPrices = 'È possibile aggiungere solo un abbonamento per ciascun tipo';
+
+            if ($this->msgPrices == '' &&  $this->msgWhen == '')
+            {
+
+                $course = new \App\Models\Course();
+
+                $course->name = $this->name;
+                $course->parent_id = $this->parent_id;
+                $course->course_type_id = $this->course_type_id;
+                $course->course_duration_id = $this->course_duration_id;
+                $course->course_frequency_id = $this->course_frequency_id;
+                $course->course_level_id = $this->course_level_id;
+                $course->date_from = $this->date_from;
+                $course->date_to = $this->date_to;
+                $course->category_id = $this->category_id;
+                $course->causal_id = $this->causal_id;
+                $course->sub_causal_id = $this->sub_causal_id;
+                $course->max_members = $this->max_members;
+                $course->instructor_id = $this->instructor_id;
+                $course->year = $this->year;
+                $course->price = currencyToDouble($this->price);
+                $course->subscription_price = currencyToDouble($this->subscription_price);
+                $course->months = json_encode($this->months);
+                $course->type = $this->type;
+                $course->when = json_encode($this->when);
+                $course->prices = json_encode($this->prices);
+                $course->enabled = $this->enabled;
+                $course->save();
+
+                $lev = '';
+                if ($this->course_level_id > 0)
+                    $lev = \App\Models\CourseLevel::findOrFail($this->course_level_id)->name;
+
+                $freq = '';
+                if ($this->course_frequency_id > 0)
+                    $freq = \App\Models\CourseFrequency::findOrFail($this->course_frequency_id)->name;
+
+                $mFrom = getMonthName(date("n", strtotime($this->date_from)));
+                $mTo = getMonthName(date("n", strtotime($this->date_to)));
+
+                $course_name = $course->name;
+
+                // creo il calendario
+                $from = date("Y-m-d", strtotime($this->date_from));
+                $to = date("Y-m-d", strtotime($this->date_to));
+
+                $endDate = strtotime($to);
+
+                foreach($this->when as $d)
+                {
+
+                    foreach($d["day"] as $dd)
+                    {
+
+                        $day = '';
+                        switch ($dd) {
+                            case 'lun':
+                                $day = $days[0];
+                                break;
+                            case 'mar':
+                                $day = $days[1];
+                                break;
+                            case 'mer':
+                                $day = $days[2];
+                                break;
+                            case 'gio':
+                                $day = $days[3];
+                                break;
+                            case 'ven':
+                                $day = $days[4];
+                                break;
+                            case 'sab':
+                                $day = $days[5];
+                                break;
+                            case 'dom':
+                                $day = $days[6];
+                                break;
+                            default:
+                                $day = '';
+                                break;
+                        }
+                        
+                        if ($day != '')
+                        {
+                            for($i = strtotime($day, strtotime($from)); $i <= $endDate; $i = strtotime('+1 week', $i))
+                            {
+
+                                // Controllo che non esiste un corso così
+                                $exist = \App\Models\Calendar::where('from', date('Y-m-d ' . $d["from"] . ":00", $i))->where('to', date('Y-m-d ' . $d["to"] . ":00", $i))->where('name', $course_name)->first();
+
+                                if (!$exist && !in_array(date('Y-m-d', $i), $this->festivita))
+                                {
+
+                                    // Creo il calendario del corso
+                                    $calendar = new \App\Models\Calendar();
+                                    $calendar->course_id = $this->course->id;
+                                    $calendar->court_id = null;
+                                    $calendar->name = $course_name;
+                                    $calendar->course_type_id = null;
+                                    $calendar->course_duration_id = null;
+                                    $calendar->course_frequency_id = null;
+                                    $calendar->course_level_id = null;
+                                    $calendar->instructor_id = null;
+                                    $calendar->from = date('Y-m-d ' . $d["from"] . ":00", $i);
+                                    $calendar->to = date('Y-m-d ' . $d["to"] . ":00", $i);
+                                    $calendar->note = '';
+                                    $calendar->status = 0;
+                                    $calendar->save();
+
+                                }
+                                
+                            }
+                        }
+                    }
+                }
+
+                // Creo le causali di pagamento
+                //$causal = "PAGAMENTO CORSO [nome corso]-LIVELLO-FREQUENZA-ANNO-[tipo abbonamento]-[mese inizio]/[mese fine]
+
+                /*
+                $causal = "PAGAMENTO CORSO " . $this->name . " - " . $lev . " - " . $freq . " - " . $this->year . " - " . $mFrom . "/" . $mTo;
+                $cp = \App\Models\Causal::where('name', $causal)->first();
+                if (!$cp)
+                {
+                    $cp = new \App\Models\Causal();
+                    $cp->name = $causal;
+                    $cp->type = "IN";
+                    $cp->parent_id = null;
+                    $cp->money = false;
+                    $cp->corrispettivo_fiscale = false;
+                    $cp->no_receipt = false;
+                    $cp->user_status = false;
+                    $cp->no_first = false;
+                    $cp->no_records = false;
+                    $cp->enabled = true;
+                    $cp->save();
+                }
+                $course->causal_id = $cp->id;
+
+                $causal = "PAGAMENTO ISCRIZIONE " . $this->name . " - " . $lev . " - " . $freq . " - " . $this->year . " - " . $mFrom . "/" . $mTo;
+                $ci = \App\Models\Causal::where('name', $causal)->first();
+                if (!$ci)
+                {
+                    $ci = new \App\Models\Causal();
+                    $ci->name = $causal;
+                    $ci->type = "IN";
+                    $ci->parent_id = null;
+                    $ci->money = false;
+                    $ci->corrispettivo_fiscale = false;
+                    $ci->no_receipt = false;
+                    $ci->user_status = false;
+                    $ci->no_first = false;
+                    $ci->no_records = false;
+                    $ci->enabled = true;
+                    $ci->save();
+                }
+                $course->sub_causal_id = $ci->id;
+                */
+
+                $course->save();
+
+                session()->flash('success','Corso creato');
+                $this->resetFields();
+                $this->add = false;
+                $this->emit('setEdit', false);
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function edit($id){
+        $this->resetFields();
+        try {
+            $course = \App\Models\Course::findOrFail($id);
+            if( !$course) {
+                session()->flash('error','Corso non trovato');
+            } else {
+                $this->name = $course->name;
+                $this->enabled = $course->enabled;
+                $this->parent_id = $course->parent_id;
+                $this->course_type_id = $course->course_type_id;
+                $this->course_duration_id = $course->course_duration_id;
+                $this->course_frequency_id = $course->course_frequency_id;
+                $this->course_level_id = $course->course_level_id;
+                $this->date_from = $course->date_from;
+                $this->date_to = $course->date_to;
+                $this->category_id = $course->category_id;
+                $this->causal_id = $course->causal_id;
+                $this->sub_causal_id = $course->sub_causal_id;
+                $this->max_members = $course->max_members;
+                $this->instructor_id = $course->instructor_id;
+                $this->year = $course->year;
+                $this->price = formatPrice($course->price);
+                $this->subscription_price = formatPrice($course->subscription_price);
+                $this->months = json_decode($course->months);
+                $this->when = array();
+                if ($course->when != null)
+                {
+                    foreach(json_decode($course->when) as $z)
+                    {
+                        $this->when[] = array("day" => $z->day, "from" => $z->from, "to" => $z->to);
+                    }
+                }
+                else
+                {
+                    $this->when[] = array('day' => array(), 'from' => '', 'to' => '');
+                }
+
+                $this->prices = array();
+                if ($course->prices != null)
+                {
+                    foreach(json_decode($course->prices) as $z)
+                    {
+                        $this->prices[] = array("course_subscription_id" => $z->course_subscription_id, "price" => $z->price);
+                    }
+                }
+                else
+                {
+                    $this->prices[] = array('course_subscription_id' => null, 'price' => 0);
+                }
+                $this->type = $course->type;
+                $this->dataId = $course->id;
+                $this->update = true;
+                $this->add = false;
+            }
+            $this->emit('setEdit', true);
+        } catch (\Exception $ex) {
+            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function update()
+    {
+        $this->validate();
+        try {
+            \App\Models\Course::whereId($this->dataId)->update([
+                'name' => $this->name,
+                'parent_id' => $this->parent_id,
+                'course_type_id' => $this->course_type_id,
+                'course_duration_id' => $this->course_duration_id,
+                'course_frequency_id' => $this->course_frequency_id,
+                'course_level_id' => $this->course_level_id,
+                'date_from' => $this->date_from,
+                'date_to' => $this->date_to,
+                'category_id' => $this->category_id,
+                'causal_id' => $this->causal_id,
+                'sub_causal_id' => $this->sub_causal_id,
+                'max_members' => $this->max_members,
+                'instructor_id' => $this->instructor_id,
+                'year' => $this->year,
+                'price' => currencyToDouble($this->price),
+                'subscription_price' => currencyToDouble($this->subscription_price),
+                'months' => json_encode($this->months),
+                'type' => $this->type,
+                'when' => json_encode($this->when),
+                'prices' => json_encode($this->prices),
+                'enabled' => $this->enabled
+            ]);
+            session()->flash('success','Corso aggiornato');
+            $this->resetFields();
+            $this->update = false;
+            $this->emit('setEdit', false);
+        } catch (\Exception $ex) {
+            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function cancel()
+    {
+        $this->add = false;
+        $this->update = false;
+        $this->resetFields();
+        $this->emit('setEdit', false);
+    }
+
+    public function delete($id)
+    {
+        try{
+            \App\Models\Course::find($id)->delete();
+            session()->flash('success',"Corso eliminato");
+            return redirect(request()->header('Referer'));
+        }catch(\Exception $e){
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function setCausal($id, $idx)
+    {
+        $this->causal_id = $id;
+    }
+
+    public function setSubscriptionCausal($id, $idx)
+    {
+        $this->sub_causal_id = $id;
+    }
+
+    public function setCategory($id)
+    {
+        $this->category_id = $id;
+    }
+
+    public function duplicate($id, $isMultiple){
+        $course = \App\Models\Course::findOrFail($id);
+        $newCourse = $course->replicate();
+        $newYear = date("Y") . "-" . (date("Y") + 1);
+        if ($course->year != '')
+        {
+            list($u, $y) = explode("-", $course->year);
+            $newYear = ($u + 1) . "-" . ($u + 2);
+        }
+        $newCourse->year = $newYear;
+        $newCourse->save();
+        if (!$isMultiple)
+            $this->edit($newCourse->id);
+    }
+
+    public function duplicateMultiple($ids){
+        foreach($ids as $id)
+        {
+            $this->duplicate($id, true);
+        }
+        return redirect()->to('/courses');
+    }
+
+    public function setDay($idx, $d)
+    {
+
+        if (in_array($d, $this->when[$idx]["day"]))
+        {
+            $i = array_search($d, $this->when[$idx]["day"]);
+            array_splice($this->when[$idx]["day"], $i, 1);
+
+        }
+        else
+        {
+            $this->when[$idx]["day"][] = $d;
+        }
+
+    }
+
+    public function addRow()
+    {
+        $this->when[] = array('day' => array(), 'from' => '', 'to' => '');
+    }
+
+    public function delRow($idx)
+    {
+        unset($this->when[$idx]);
+    }
+
+    public function addPrice()
+    {
+        $this->prices[] = array('course_subscription_id' => null, 'price' => 0);
+    }
+
+    public function delPrice($idx)
+    {
+        unset($this->prices[$idx]);
+    }
+
+}

+ 2 - 2
app/Http/Livewire/CourseList.php

@@ -41,7 +41,7 @@ class CourseList extends Component
     public $filterDuration = [];
 
     public $course_durations = [];
-    public $course_types = [];
+    // public $course_types = [];
     public $course_frequencies = [];
     public $course_levels = [];
     public $course_years = [];
@@ -64,7 +64,7 @@ class CourseList extends Component
         $this->selectedCourseId = 0;
         $this->selectedMemberId = 0;
 
-        $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
+        // $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
         $this->course_durations = \App\Models\CourseDuration::select('*')->where('enabled', true)->get();
         $this->course_levels = \App\Models\CourseLevel::select('*')->where('enabled', true)->get();
         $this->course_frequencies = \App\Models\CourseFrequency::select('*')->where('enabled', true)->get();

+ 918 - 0
app/Http/Livewire/CourseList.php.bak

@@ -0,0 +1,918 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+use App\Models\MemberCourse;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
+class CourseList extends Component
+{
+
+    public $records = array();
+    public $recordsNoPaginate = array();
+    public $courses = array();
+
+    public $start = 0;
+    public $totalRecords = 0;
+    public $pages = 0;
+    public $currentPage = 1;
+    public $pageLength = 10;
+
+    public $totS = [];
+    public $totSExcel = [];
+
+    public $sort = '';
+    public $dir = '';
+
+    public $hasFilter = false;
+
+    public $courseId = 0;
+
+    public $filterCourse = [];
+    public $filterLevel = [];
+    public $filterFrequency = [];
+    public $filterType = [];
+    public $filterDuration = [];
+
+    public $course_durations = [];
+    public $course_types = [];
+    public $course_frequencies = [];
+    public $course_levels = [];
+    public $course_years = [];
+
+    public $totals = [];
+    public $totalIsc = [];
+
+    public $selectedCourseId;
+    public $selectedMemberId;
+    public $aaa;
+
+    public $months = array('Set', 'Ott', 'Nov', 'Dic', 'Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago');
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+    public function mount()
+    {
+
+        $this->selectedCourseId = 0;
+        $this->selectedMemberId = 0;
+
+        $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
+        $this->course_durations = \App\Models\CourseDuration::select('*')->where('enabled', true)->get();
+        $this->course_levels = \App\Models\CourseLevel::select('*')->where('enabled', true)->get();
+        $this->course_frequencies = \App\Models\CourseFrequency::select('*')->where('enabled', true)->get();
+        $this->course_years = \App\Models\Course::select('year')->where('year', '<>', '')->groupBy('year')->pluck('year');
+
+        $this->courses = \App\Models\Course::orderBy('name')->groupBy('name')->pluck('name');
+
+        //if (sizeof($this->courses) > 0)
+        //    $this->courseId = $this->courses[0]->id;
+
+    }
+
+    public function updatedfilterCourse($value)
+    {
+        $this->emit('load-data-table');
+    }
+
+    public function updatedfilterLevel($value)
+    {
+        $this->emit('load-data-table');
+    }
+
+    public function updatedfilterFrequency($value)
+    {
+        $this->emit('load-data-table');
+    }
+
+    public function updatedfilterType($value)
+    {
+        $this->emit('load-data-table');
+    }
+
+    public function updatedfilterDuration($value)
+    {
+        $this->emit('load-data-table');
+    }
+
+    public function updatedpageLength($value)
+    {
+        $this->emit('load-data-table');
+    }
+
+
+    public function render()
+    {
+
+        $member_course = \App\Models\MemberCourse::with('member')->with('course');
+
+        if ($this->hasFilter) {
+            if (isset($_GET["search"]["value"])) {
+
+                if ($_GET["search"]["value"] != '') {
+                    $v = str_replace("'", "\'", stripcslashes($_GET["search"]["value"]));
+                    $member_ids = \App\Models\Member::where(function ($query) use ($v) {
+                        $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $v . "%'")
+                            ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $v . "%'");
+                    })->pluck('id');
+                    /*
+                    $v = str_replace("'", "\'", stripcslashes($_GET["search"]["value"]));
+                    $member_ids = \App\Models\Member::where(function ($query) use ($v) {
+                        $query->where('first_name', 'like', '%' . $v . '%')
+                                ->orWhere('last_name', 'like', '%' . $v . '%');
+                    })->pluck('id');*/
+                    $member_course = $member_course->whereIn('member_id', $member_ids);
+                }
+            }
+
+            if (sizeof($this->filterCourse) > 0) {
+                $course_ids = [];
+                $courses = $this->filterCourse;
+                foreach ($courses as $c) {
+                    $all = \App\Models\Course::where('name', 'like', '%' . $c . "%")->get();
+                    foreach ($all as $a) {
+                        $course_ids[] = $a->id;
+                    }
+                }
+                $member_course = $member_course->whereIn('course_id', $course_ids);
+            }
+
+            if (sizeof($this->filterLevel) > 0) {
+                $course_ids = \App\Models\Course::whereIn('course_level_id', $this->filterLevel)->pluck('id');
+                $member_course = $member_course->whereIn('course_id', $course_ids);
+            }
+
+            if (sizeof($this->filterFrequency) > 0) {
+                $course_ids = \App\Models\Course::whereIn('course_frequency_id', $this->filterFrequency)->pluck('id');
+                $member_course = $member_course->whereIn('course_id', $course_ids);
+            }
+            if (sizeof($this->filterType) > 0) {
+                $course_ids = \App\Models\Course::whereIn('course_type_id', $this->filterType)->pluck('id');
+                $member_course = $member_course->whereIn('course_id', $course_ids);
+            }
+            if (sizeof($this->filterDuration) > 0) {
+                $course_ids = \App\Models\Course::whereIn('course_duration_id', $this->filterDuration)->pluck('id');
+                $member_course = $member_course->whereIn('course_id', $course_ids);
+            }
+        }
+
+        $totals = [];
+        $totalIsc = [];
+
+        $datas = [];
+
+        $xxx = 1;
+
+        /*
+        $sortColumn = '';
+        if (isset($_GET["order"]))
+        {
+
+        }
+        */
+
+        $column = '';
+        $sort_value = 0;
+        if ($this->sort != '') {
+            $f = $this->sort;
+            $d = $this->dir;
+            if ($f >= 5 && $f <= 16) {
+                $column = 'column_' . ($f - 2);
+                if (session()->get('sort_column')) {
+                    if (session()->get('sort_column') != $f) {
+                        session()->put('sort_column', $f);
+                        session()->put('sort_order', $d);
+                        session()->put('sort_value', 0);
+                        $sort_value = 0;
+                    } else {
+                        if (session()->get('sort_order') == $d) {
+                            //session()->put('sort_value', 0);
+                            $sort_value = session()->get('sort_value', 0);
+                        } else {
+                            if (session()->get('sort_value', 0) == 0) {
+                                $sort_value = 1;
+                            }
+                            if (session()->get('sort_value', 0) == 1) {
+                                $sort_value = 2;
+                            }
+                            if (session()->get('sort_value', 0) == 2) {
+                                $sort_value = 3;
+                            }
+                            if (session()->get('sort_value', 0) == 3) {
+                                $sort_value = 0;
+                            }
+                            session()->put('sort_value', $sort_value);
+                        }
+                        session()->put('sort_order', $d);
+                    }
+                } else {
+                    session()->put('sort_column', $f);
+                    session()->put('sort_order', $d);
+                    session()->put('sort_value', 0);
+                    $sort_value = 0;
+                }
+            }
+        }
+
+        //print $sort_value;
+
+        $totals = [];
+        $prices = [];
+
+        $member_course_totals = $member_course->get();
+        foreach ($member_course_totals as $x) {
+
+            $price = 0;
+            $price = $x->price; // $x->course->price;
+            $subPrice = $x->subscription_price; // $x->course->subscription_price;
+            $records = \App\Models\Record::where('member_course_id', $x->id)->where('deleted', 0)->get();
+            $prices = [];
+            foreach ($records as $record) {
+                foreach ($record->rows as $row) {
+
+                    if ($row->causal_id == $x->course->sub_causal_id) //  || str_contains(strtolower($row->note), 'iscrizione'))
+                    //if (str_contains(strtolower($row->note), 'iscrizione'))
+                    {
+                        $subPrice = $row->amount;
+                    }
+                    if ($row->causal_id == $x->course->causal_id && !str_contains(strtolower($row->note), 'iscrizione')) {
+                        $tot = sizeof(json_decode($row->when));
+                        foreach (json_decode($row->when) as $m) {
+                            if (isset($prices[$m->month]))
+                                $prices[$m->month] += $row->amount / $tot;
+                            else
+                                $prices[$m->month] = $row->amount / $tot;
+                        }
+                    }
+                }
+            }
+
+            for ($i = 1; $i <= 12; $i++) {
+
+                $cls = $this->getColor($x->months, $i, isset($prices[$i]) && $prices[$i] == $price);
+                if ($cls != 'wgrey') {
+
+                    if (!isset($totals[$i])) {
+                        $totals[$i]['green'] = 0;
+                        $totals[$i]['orange'] = 0;
+                        $totals[$i]['yellow'] = 0;
+                    }
+                    if ($cls == 'yellow') {
+                        $totals[$i][$cls] += 1;
+                    } else {
+                        $p = isset($prices[$i]) ? $prices[$i] : $price;
+                        //if (isset($totals[$i][$cls]))
+                        $totals[$i][$cls] += $p;
+                        //else
+                        //$totals[$i][$cls] = $p;
+                    }
+                }
+            }
+
+            $sub = $x->subscribed ? "Y" : "N";
+            if (isset($totalIsc[$sub]))
+                $totalIsc[$sub] += $subPrice;
+            else
+                $totalIsc[$sub] = $subPrice;
+
+            $s = 0;
+            if ($column != '') {
+                $z = 0;
+                switch ($column) {
+                    case 'column_3':
+                        $z = 9;
+                        break;
+                    case 'column_4':
+                        $z = 10;
+                        break;
+                    case 'column_5':
+                        $z = 11;
+                        break;
+                    case 'column_6':
+                        $z = 12;
+                        break;
+                    case 'column_7':
+                        $z = 1;
+                        break;
+                    case 'column_8':
+                        $z = 2;
+                        break;
+                    case 'column_9':
+                        $z = 3;
+                        break;
+                    case 'column_10':
+                        $z = 4;
+                        break;
+                    case 'column_11':
+                        $z = 5;
+                        break;
+                    case 'column_12':
+                        $z = 6;
+                        break;
+                    case 'column_13':
+                        $z = 7;
+                        break;
+                    case 'column_14':
+                        $z = 8;
+                        break;
+                    default:
+                        $z = 0;
+                        break;
+                }
+                $c = getColor($x->months, $z);
+                if ($sort_value == 0) {
+                    switch ($c) {
+                        case 'wgrey':
+                            $s = 0;
+                            break;
+                        case 'orange':
+                            $s = 1;
+                            break;
+                        case 'green':
+                            $s = 2;
+                            break;
+                        case 'yellow':
+                            $s = 3;
+                            break;
+                        default:
+                            $s = 0;
+                            break;
+                    }
+                }
+                if ($sort_value == 1) {
+                    switch ($c) {
+                        case 'wgrey':
+                            $s = 3;
+                            break;
+                        case 'orange':
+                            $s = 0;
+                            break;
+                        case 'green':
+                            $s = 1;
+                            break;
+                        case 'yellow':
+                            $s = 2;
+                            break;
+                        default:
+                            $s = 0;
+                            break;
+                    }
+                }
+                if ($sort_value == 2) {
+                    switch ($c) {
+                        case 'wgrey':
+                            $s = 2;
+                            break;
+                        case 'orange':
+                            $s = 3;
+                            break;
+                        case 'green':
+                            $s = 0;
+                            break;
+                        case 'yellow':
+                            $s = 1;
+                            break;
+                        default:
+                            $s = 0;
+                            break;
+                    }
+                }
+                if ($sort_value == 3) {
+                    switch ($c) {
+                        case 'wgrey':
+                            $s = 1;
+                            break;
+                        case 'orange':
+                            $s = 2;
+                            break;
+                        case 'green':
+                            $s = 3;
+                            break;
+                        case 'yellow':
+                            $s = 0;
+                            break;
+                        default:
+                            $s = 0;
+                            break;
+                    }
+                }
+            }
+
+            $datas[] = array(
+                "column_19" => $x->course->name,
+                "column_0" => $x->member->last_name,
+                "column_1" => $x->member->first_name,
+                "column_2" => $x->subscribed . "§" . formatPrice($subPrice),
+                "column_3" => $this->getColor($x->months, 9, isset($prices[9]) && $prices[9] >= $price) . "§" . formatPrice(isset($prices[9]) ? $prices[9] : $price) . "§" . (isset($prices[9]) && $prices[9] <= $price ? 'X' : ''),
+                "column_4" => $this->getColor($x->months, 10, isset($prices[10]) && $prices[10] >= $price) . "§" . formatPrice(isset($prices[10]) ? $prices[10] : $price) . "§" . (isset($prices[10]) && $prices[10] <= $price ? 'X' : ''),
+                "column_5" => $this->getColor($x->months, 11, isset($prices[11]) && $prices[11] >= $price) . "§" . formatPrice(isset($prices[11]) ? $prices[11] : $price) . "§" . (isset($prices[11]) && $prices[11] <= $price ? 'X' : ''),
+                "column_6" => $this->getColor($x->months, 12, isset($prices[12]) && $prices[12] >= $price) . "§" . formatPrice(isset($prices[12]) ? $prices[12] : $price) . "§" . (isset($prices[12]) && $prices[12] <= $price ? 'X' : ''),
+                "column_7" => $this->getColor($x->months, 1, isset($prices[1]) && $prices[1] >= $price) . "§" . formatPrice(isset($prices[1]) ? $prices[1] : $price) . "§" . (isset($prices[1]) && $prices[1] <= $price ? 'X' : ''),
+                "column_8" => $this->getColor($x->months, 2, isset($prices[2]) && $prices[2] >= $price) . "§" . formatPrice(isset($prices[2]) ? $prices[2] : $price) . "§" . (isset($prices[2]) && $prices[2] <= $price ? 'X' : ''),
+                "column_9" => $this->getColor($x->months, 3, isset($prices[3]) && $prices[3] >= $price) . "§" . formatPrice(isset($prices[3]) ? $prices[3] : $price) . "§" . (isset($prices[3]) && $prices[3] <= $price ? 'X' : ''),
+                "column_10" => $this->getColor($x->months, 4, isset($prices[4]) && $prices[4] >= $price) . "§" . formatPrice(isset($prices[4]) ? $prices[4] : $price) . "§" . (isset($prices[4]) && $prices[4] <= $price ? 'X' : ''),
+                "column_11" => $this->getColor($x->months, 5, isset($prices[5]) && $prices[5] >= $price) . "§" . formatPrice(isset($prices[5]) ? $prices[5] : $price) . "§" . (isset($prices[5]) && $prices[5] <= $price ? 'X' : ''),
+                "column_12" => $this->getColor($x->months, 6, isset($prices[6]) && $prices[6] >= $price) . "§" . formatPrice(isset($prices[6]) ? $prices[6] : $price) . "§" . (isset($prices[6]) && $prices[6] <= $price ? 'X' : ''),
+                "column_13" => $this->getColor($x->months, 7, isset($prices[7]) && $prices[7] >= $price) . "§" . formatPrice(isset($prices[7]) ? $prices[7] : $price) . "§" . (isset($prices[7]) && $prices[7] <= $price ? 'X' : ''),
+                "column_14" => $this->getColor($x->months, 8, isset($prices[8]) && $prices[8] >= $price) . "§" . formatPrice(isset($prices[8]) ? $prices[8] : $price) . "§" . (isset($prices[8]) && $prices[8] <= $price ? 'X' : ''),
+                "column_15" => $x->course_id,
+                "column_16" => $x->id,
+                "column_17" => $x->member_id,
+                "column_18" => $xxx++,
+                "column_20" => $s
+            );
+        }
+
+        $count = $member_course->count();
+
+        $this->totSExcel = [];
+        $this->totS = [];
+
+        //$js = '';
+        $xx = 4;
+        $str = '';
+        if ($count > 0) {
+            $str .= "<a style='width:100%;float:right; text-align:right; display:block;' class=green><small>" . (isset($totalIsc["Y"]) ? formatPrice($totalIsc["Y"]) : 0) . "</small></a><br>";
+            $str .= "<a style='width:100%;float:right; text-align:right; display:block;' class=orange><small>" . (isset($totalIsc["N"]) ? formatPrice($totalIsc["N"]) : 0) . "</small></a><br>";
+            $str .= "<a style='width:100%;float:right; text-align:right; display:block;' class=yellow><small>0</small></a><br>";
+
+            $this->totSExcel[] = array('green' => (isset($totalIsc["Y"]) ? formatPrice($totalIsc["Y"]) : 0), 'orange' => (isset($totalIsc["N"]) ? formatPrice($totalIsc["N"]) : 0), 'yellow' => 0);
+        }
+        $this->totS[] = $str;
+
+        $str = "";
+        foreach ($totals as $z => $t) {
+            if ($z == 1) $xx = 5;
+            if ($z == 2) $xx = 6;
+            if ($z == 3) $xx = 7;
+            if ($z == 4) $xx = 8;
+            if ($z == 5) $xx = 9;
+            if ($z == 6) $xx = 10;
+            if ($z == 7) $xx = 11;
+            if ($z == 8) $xx = 12;
+            if ($z == 9) $xx = 1;
+            if ($z == 10) $xx = 2;
+            if ($z == 11) $xx = 3;
+            if ($z == 12) $xx = 4;
+            $str = '';
+            $aaa = [];
+            foreach ($t as $x => $c) {
+                $y = $x == 'yellow' ? $c : formatPrice($c);
+                $str .= "<a style='width:100%;float:right; text-align:right; display:block;' class=" . $x . "><small>" . $y . "</small></a><br>";
+                $aaa[$x] = $y;
+            }
+
+            $this->totSExcel[$xx] = $aaa;
+            $this->totS[$xx] = $str;
+            //$js .= $xx . "§" . $str . "_";
+
+            $xx += 1;
+        }
+
+        for ($e = sizeof($this->totS); $e <= 12; $e++) {
+            $this->totS[] = '';
+        }
+
+        if ($this->sort != '') {
+            $s = $this->sort;
+            if ($s == 1) $s = 21;
+            if ($column != '')
+                array_multisort(array_column($datas, 'column_20'), SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $datas);
+            else
+                array_multisort(array_column($datas, 'column_' . ($s - 2)), $this->dir == "ASC" ? SORT_ASC : SORT_DESC, SORT_NATURAL | SORT_FLAG_CASE, $datas);
+        }
+
+        $xxx = 1;
+        foreach ($datas as $yyy => $d) {
+            $datas[$yyy]["column_18"] = $xxx++;
+        }
+
+        $this->totalRecords = sizeof($datas);
+
+        //$start = 0;
+
+        $this->recordsNoPaginate = [];
+        $this->recordsNoPaginate = $datas;
+        //if (isset($_GET["start"]))
+        $datas = array_slice($datas, $this->start, $this->pageLength);
+
+        $this->pages = ceil($this->totalRecords / $this->pageLength);
+        $this->records = [];
+        $this->records = $datas;
+
+        //$this->totS = $js;
+
+        return view('livewire.course_list');
+    }
+
+    public function setPage($page)
+    {
+        $this->currentPage = $page;
+        $this->start = $this->pageLength * ($page - 1);
+        $this->emit('load-data-table');
+    }
+
+    public function setSort($sort)
+    {
+        $this->sort = $sort;
+        if ($this->dir == '')
+            $this->dir = 'ASC';
+        else
+            $this->dir = $this->dir == 'ASC' ? 'DESC' : 'ASC';
+        $this->emit('load-data-table');
+    }
+
+    public function search()
+    {
+        $this->currentPage = 1;
+        $this->start = 0;
+        $this->hasFilter = true;
+        $this->emit('load-data-table');
+    }
+
+    public function getColor($months, $m, $all)
+    {
+        $class = "wgrey";
+        foreach (json_decode($months) as $mm) {
+            if ($mm->m == $m) {
+                if ($mm->status == "") {
+                    $class = "orange";
+                }
+                if ($mm->status == "1") {
+                    $class = "green";
+                }
+                if ($mm->status == "2") {
+                    $class = "yellow";
+                }
+                if (!$all && $class == "green") {
+                    $class = "orange";
+                }
+            }
+        }
+        return $class;
+    }
+
+    public function newPayment($course_id, $months, $member_id, $id, $subscription)
+    {
+
+        $newMonths = array();
+        if ($months != '') {
+            $mm = explode(",", $months);
+            foreach ($mm as $month) {
+                if ($month < 5) $month += 12;
+                if ($month >= 5) $month -= 4;
+                $newMonths[] = $month;
+            }
+        }
+
+        $c = \App\Models\Course::findOrFail($course_id);
+        $m = \App\Models\MemberCourse::findOrFail($id);
+
+        $price = $m->price;
+        $subscription_price = $m->subscription_price;
+
+        $records = \App\Models\Record::where('member_course_id', $m->id)->where('deleted', 0)->get();
+        foreach ($records as $record) {
+
+            if (in_array($month, json_decode($record->months))) {
+
+                foreach ($record->rows as $row) {
+
+
+                    if ($row->causal_id == $c->causal_id && !str_contains(strtolower($row->note), 'iscrizione')) {
+                        $tot = sizeof(json_decode($row->when));
+                        foreach (json_decode($row->when) as $m) {
+                            $price -= $row->amount / $tot;
+                        }
+                    }
+                }
+            }
+        }
+
+        return redirect()->to('/in?new=1&memberId=' . $member_id . (sizeof($newMonths) > 0 ? '&causalId=' . $c->causal_id : '') . '&subCausalId=' . $c->sub_causal_id . '&createSubscription=' . ($subscription ? 1 : 0) . (sizeof($newMonths) > 0 ? '&months=' . implode("|", $newMonths) : '') . (sizeof($newMonths) > 0 ? ('&price=' . $price) : '') . '&subscription_price=' . $subscription_price . "&courseId=" . $id);
+    }
+
+    public function suspendPayment($course_id, $month, $member_id, $id, $subscription)
+    {
+
+        $monthMap = [
+            1 => 9,  // September
+            2 => 10, // October
+            3 => 11, // November
+            4 => 12, // December
+            5 => 1,  // January
+            6 => 2,  // February
+            7 => 3,  // March
+            8 => 4,  // April
+            9 => 5,  // May
+            10 => 6, // June
+            11 => 7, // July
+            12 => 8  // August
+        ];
+
+        $dbMonth = isset($monthMap[$month]) ? $monthMap[$month] : $month;
+
+
+        $memberCourse = MemberCourse::where('id', $id)
+            ->where('member_id', $member_id)
+            ->where('course_id', $course_id)
+            ->first();
+
+
+        if (!$memberCourse) {
+            return response()->json(['error' => 'Non Trovato'], 404);
+        }
+
+        $monthsData = json_decode($memberCourse->months, true);
+        if (!is_array($monthsData)) {
+            return response()->json(['error' => 'Invalid months data format'], 400);
+        }
+
+        $monthUpdated = false;
+        foreach ($monthsData as &$monthData) {
+            if ($monthData['m'] == $dbMonth) {
+                $monthData['status'] = 2;
+                $monthUpdated = true;
+            }
+        }
+
+        if (!$monthUpdated) {
+            return response()->json(['error' => 'Month not found in data'], 404);
+        }
+
+        $memberCourse->months = json_encode($monthsData);
+        $memberCourse->save();
+        session()->flash('success', 'Payment suspended successfully');
+        return redirect()->to('/course_list');
+    }
+
+    public function resumePayment($course_id, $month, $member_id, $id, $subscription)
+    {
+        Log::info('resumePayment');
+        Log::info($course_id);
+        Log::info($month);
+        Log::info($member_id);
+        Log::info($id);
+
+        $monthMap = [
+            1 => 9,  // September
+            2 => 10, // October
+            3 => 11, // November
+            4 => 12, // December
+            5 => 1,  // January
+            6 => 2,  // February
+            7 => 3,  // March
+            8 => 4,  // April
+            9 => 5,  // May
+            10 => 6, // June
+            11 => 7, // July
+            12 => 8  // August
+        ];
+
+        $dbMonth = isset($monthMap[$month]) ? $monthMap[$month] : $month;
+
+
+        $memberCourse = MemberCourse::where('id', $id)
+            ->where('member_id', $member_id)
+            ->where('course_id', $course_id)
+            ->first();
+
+        if (!$memberCourse) {
+            return response()->json(['error' => 'Non Trovato'], 404);
+        }
+
+        $monthsData = json_decode($memberCourse->months, true);
+        Log::info('data mese', $monthsData);
+        if (!is_array($monthsData)) {
+            return response()->json(['error' => 'Invalid months data format'], 400);
+        }
+
+        $monthUpdated = false;
+        foreach ($monthsData as &$monthData) {
+            if ($monthData['m'] == $dbMonth) {
+                $monthData['status'] = "";
+                $monthUpdated = true;
+            }
+        }
+        Log::info($monthUpdated);
+
+        if (!$monthUpdated) {
+            return response()->json(['error' => 'Month not found in data'], 404);
+        }
+
+        $memberCourse->months = json_encode($monthsData);
+        $memberCourse->save();
+        session()->flash('success', 'Payment resumed successfully');
+        return redirect()->to('/course_list');
+    }
+    /*
+    public function newPayment()
+    {
+        $c = \App\Models\Course::findOrFail($this->selectedCourseId);
+        return redirect()->to('/in?new=1&memberId=' . $this->selectedMemberId . '&causalId=' . $c->causal_id . '&subCausalId=' . $c->sub_causal_id . '&createSubscription=0' . (sizeof($this->payMonths) > 0 ? '&months=' . implode("|", $this->payMonths) : "") . '&price=' . $c->price . '&subscription_price=' . $c->subscription_price . "&courseId=" . $this->selectedCourseId);
+    }
+    */
+    public function newSubscription($course_id, $member_id, $id)
+    {
+        $c = \App\Models\Course::findOrFail($course_id);
+        return redirect()->to('/in?new=1&memberId=' . $member_id . '&causalId=' . $c->causal_id . '&subCausalId=' . $c->sub_causal_id . '&createSubscription=1&price=0.00&subscription_price=' . $c->subscription_price . "&courseId=" . $id);
+    }
+
+    public function disableSearch()
+    {
+        $this->filterCourse = [];
+        $this->filterLevel = [];
+        $this->filterType = [];
+        $this->filterDuration = [];
+        $this->filterFrequency = [];
+        $this->hasFilter = false;
+    }
+
+    public function export()
+    {
+
+        $letters = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z');
+        $spreadsheet = new Spreadsheet();
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+
+        //$activeWorksheet->setCellValue('A1', 'PrimaNota');
+        $activeWorksheet->setCellValue('A1', "Corso");
+        $activeWorksheet->setCellValue('B1', "Cognome");
+        $activeWorksheet->setCellValue('C1', "Nome");
+        $activeWorksheet->setCellValue('D1', "Iscrizione");
+        $activeWorksheet->setCellValue('E1', "Settembre");
+        $activeWorksheet->setCellValue('F1', "Ottobre");
+        $activeWorksheet->setCellValue('G1', "Novembre");
+        $activeWorksheet->setCellValue('H1', "Dicembre");
+        $activeWorksheet->setCellValue('I1', "Gennaio");
+        $activeWorksheet->setCellValue('J1', "Febbraio");
+        $activeWorksheet->setCellValue('K1', "Marzo");
+        $activeWorksheet->setCellValue('L1', "Aprile");
+        $activeWorksheet->setCellValue('M1', "Maggio");
+        $activeWorksheet->setCellValue('N1', "Giugno");
+        $activeWorksheet->setCellValue('O1', "Luglio");
+        $activeWorksheet->setCellValue('P1', "Agosto");
+
+        $count = 2;
+        foreach ($this->recordsNoPaginate as $idx => $record) {
+
+            $activeWorksheet->setCellValue('A' . $count, $record["column_19"]);
+            $activeWorksheet->setCellValue('B' . $count, $record["column_0"]);
+            $activeWorksheet->setCellValue('C' . $count, $record["column_1"]);
+            list($color, $value) = explode("§", $record["column_2"]);
+            $activeWorksheet->setCellValue('D' . $count, $value);
+            $c = '#FFFFFF';
+            if ($color == 0)
+                $c = 'ffa500';
+            if ($color == 1)
+                $c = '00ff00';
+
+            $activeWorksheet->getStyle('D' . $count . ':D' . $count)
+                ->getFill()
+                ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+                ->getStartColor()
+                ->setARGB($c);
+
+            for ($ii = 3; $ii <= 14; $ii++) {
+                list($color, $value) = explode("§", $record["column_" . $ii]);
+                $c = 'FFFFFF';
+                if ($color == 'orange')
+                    $c = 'ffa500';
+                if ($color == 'green')
+                    $c = '00ff00';
+                if ($color == 'yellow') {
+                    $c = '5088bf';
+                    $activeWorksheet->getStyle($letters[$ii + 1] . $count . ':' . $letters[$ii + 1] . $count)
+                        ->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_WHITE);;
+                }
+                if ($color == 'wgrey')
+                    $value = '';
+
+                $activeWorksheet->setCellValue($letters[$ii + 1] . $count, $value);
+
+                $activeWorksheet->getStyle($letters[$ii + 1] . $count . ':' . $letters[$ii + 1] . $count)
+                    ->getFill()
+                    ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+                    ->getStartColor()
+                    ->setARGB($c);
+                /*$activeWorksheet->cells($letters[$ii + 1] . $count . ':' . $letters[$ii + 1] . $count, function ($cells) use ($c)
+                {
+                    $cells->setBackground($c);
+                    //$cells->setAlignment('center');
+                });*/
+            }
+
+            $activeWorksheet->getStyle("A1:P1")->getFont()->setBold(true);
+
+            $count++;
+        }
+
+        // Totali
+        $activeWorksheet->setCellValue('A' . (1 + $count), '');
+        $activeWorksheet->setCellValue('B' . (1 + $count), '');
+        $activeWorksheet->setCellValue('C' . (1 + $count), '');
+        for ($x = 0; $x <= sizeof($this->totSExcel); $x++) {
+
+            if (isset($this->totSExcel[$x])) {
+
+                $activeWorksheet->setCellValue($letters[$x + 3] . (1 + $count), isset($this->totSExcel[$x]['green']) ? $this->totSExcel[$x]['green'] : 0);
+                $activeWorksheet->getStyle($letters[$x + 3] . (1 + $count) . ':' . $letters[$x + 3] . (1 + $count))
+                    ->getFill()
+                    ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+                    ->getStartColor()
+                    ->setARGB('00ff00');
+
+                $activeWorksheet->setCellValue($letters[$x + 3] . (2 + $count), isset($this->totSExcel[$x]['orange']) ? $this->totSExcel[$x]['orange'] : 0);
+                $activeWorksheet->getStyle($letters[$x + 3] . (2 + $count) . ':' . $letters[$x + 3] . (2 + $count))
+                    ->getFill()
+                    ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+                    ->getStartColor()
+                    ->setARGB('ffa500');
+
+                $activeWorksheet->setCellValue($letters[$x + 3] . (3 + $count), isset($this->totSExcel[$x]['yellow']) ? $this->totSExcel[$x]['yellow'] : 0);
+                $activeWorksheet->getStyle($letters[$x + 3] . (3 + $count) . ':' . $letters[$x + 3] . (3 + $count))
+                    ->getFill()
+                    ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+                    ->getStartColor()
+                    ->setARGB('5088bf');
+                $activeWorksheet->getStyle($letters[$x + 3] . (3 + $count) . ':' . $letters[$x + 3] . (3 + $count))
+                    ->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_WHITE);;
+            }
+        }
+
+        try {
+            $currentClient = session('currentClient', 'default');
+
+            $filename = 'pagamento_corsi_' . date("YmdHis") . '.xlsx';
+
+            $tempPath = sys_get_temp_dir() . '/' . $filename;
+
+            $writer = new Xlsx($spreadsheet);
+            $writer->save($tempPath);
+
+            $disk = Storage::disk('s3');
+
+            $s3Path = $currentClient . '/exports/' . $filename;
+
+            $exportFolderPath = $currentClient . '/exports/.gitkeep';
+            if (!$disk->exists($exportFolderPath)) {
+                $disk->put($exportFolderPath, '');
+                Log::info("Created exports folder for client: {$currentClient}");
+            }
+
+            $fileContent = file_get_contents($tempPath);
+            $uploaded = $disk->put($s3Path, $fileContent, 'private');
+
+            if (!$uploaded) {
+                throw new \Exception('Failed to upload file to Wasabi S3');
+            }
+
+            Log::info("Excel file uploaded to Wasabi", [
+                'client' => $currentClient,
+                'path' => $s3Path,
+                'size' => filesize($tempPath)
+            ]);
+
+            $downloadUrl = $disk->temporaryUrl($s3Path, now()->addHour());
+
+            if (file_exists($tempPath)) {
+                unlink($tempPath);
+            }
+
+            $this->emit('load-data-table');
+
+            return redirect($downloadUrl);
+        } catch (\Exception $e) {
+            Log::error('Error exporting to Wasabi S3', [
+                'error' => $e->getMessage(),
+                'client' => session('currentClient', 'unknown')
+            ]);
+
+            $currentClient = session('currentClient', 'default');
+            $clientFolder = storage_path('app/exports/' . $currentClient);
+
+            if (!is_dir($clientFolder)) {
+                mkdir($clientFolder, 0755, true);
+                Log::info("Created local client folder: {$clientFolder}");
+            }
+
+            $localPath = $clientFolder . '/pagamento_corsi_' . date("YmdHis") . '.xlsx';
+            $writer = new Xlsx($spreadsheet);
+            $writer->save($localPath);
+
+            $this->emit('load-data-table');
+
+            // Show error message to user
+            session()->flash('warning', 'File saved locally due to cloud storage error.');
+
+            return response()->download($localPath)->deleteFileAfterSend();
+        }
+    }
+}

+ 5 - 3
app/Http/Livewire/CourseMember.php

@@ -11,9 +11,10 @@ class CourseMember extends Component
 
     public $records = array();
 
+    public $course = null;
     public $courses = [];
     public $course_frequencies = [];
-    public $course_types = [];
+    // public $course_types = [];
     public $course_levels = [];
     public $course_durations = [];
     public $course_years = [];
@@ -56,10 +57,11 @@ class CourseMember extends Component
         if (isset($_GET["id"]))
         {
             $this->filterFromPrevious = $_GET["id"];
-            $this->type = \App\Models\Course::findOrFail($_GET["id"])->type;
+            $this->course = \App\Models\Course::findOrFail($_GET["id"]);
+            $this->type = $this->course->type;
         }
 
-        $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
+        // $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
         $this->course_frequencies = \App\Models\CourseFrequency::select('*')->where('enabled', true)->get();
         $this->course_levels = \App\Models\CourseLevel::select('*')->where('enabled', true)->get();
         $this->course_durations = \App\Models\CourseDuration::select('*')->where('enabled', true)->get();

+ 303 - 0
app/Http/Livewire/CourseMember.php.bak

@@ -0,0 +1,303 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
+use Livewire\Component;
+
+class CourseMember extends Component
+{
+
+    public $records = array();
+
+    public $courses = [];
+    public $course_frequencies = [];
+    public $course_types = [];
+    public $course_levels = [];
+    public $course_durations = [];
+    public $course_years = [];
+
+    public $fromYear = "";
+    public $toYear = "";
+    public $fromFromYear = "";
+    public $toToYear = "";
+
+    public $filterCourse = [];
+    public $filterLevel = [];
+    public $filterFrequency = [];
+    public $filterType = [];
+    public $filterDuration = [];
+
+    public $filterDays = [];
+    public $filterHours = [];
+    public $filterSubscription = "";
+    public $filterStatus = [];
+    public $filterYear = "";
+
+    /*public $chkCertificateNormal = 0;
+    public $chkCertificateAgonistico = 0;
+    public $chkCertificateScaduti = 0;*/
+    public $chkCertificateType = "";
+    public $chkCertificateScadenza = 0;
+    public $chkCard = [];
+
+    public $filter = '';
+    public $type = '';
+
+    public $filterFromPrevious = '';
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+    public function mount()
+    {
+
+        if (isset($_GET["id"]))
+        {
+            $this->filterFromPrevious = $_GET["id"];
+            $this->type = \App\Models\Course::findOrFail($_GET["id"])->type;
+        }
+
+        $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->get();
+        $this->course_frequencies = \App\Models\CourseFrequency::select('*')->where('enabled', true)->get();
+        $this->course_levels = \App\Models\CourseLevel::select('*')->where('enabled', true)->get();
+        $this->course_durations = \App\Models\CourseDuration::select('*')->where('enabled', true)->get();
+        $this->course_years = \App\Models\Course::select('year')->where('year', '<>', '')->groupBy('year')->pluck('year');
+        $this->courses = \App\Models\Course::orderBy('name')->groupBy('name')->pluck('name');
+
+    }
+
+    public function render()
+    {
+
+        // Carico tutti i corsi associati
+        $this->filter = '';
+        $datas = \App\Models\MemberCourse::with('member');
+
+        if (sizeof($this->filterCourse) > 0)
+        {
+            $course_ids = [];
+            foreach($this->filterCourse as $c)
+            {
+                $all = \App\Models\Course::where('name', 'like', '%' . $c . "%")->get();
+                foreach($all as $a)
+                {
+                    $course_ids[] = $a->id;
+                }
+            }
+            $datas = $datas->whereIn('course_id', $course_ids);
+        }
+        if (sizeof($this->filterLevel) > 0)
+        {
+            $course_ids = \App\Models\Course::whereIn('course_level_id', $this->filterLevel)->pluck('id');
+            $datas = $datas->whereIn('course_id', $course_ids);
+        }
+        if (sizeof($this->filterFrequency) > 0)
+        {
+            $course_ids = \App\Models\Course::whereIn('course_frequency_id', $this->filterFrequency)->pluck('id');
+            $datas = $datas->whereIn('course_id', $course_ids);
+        }
+        if (sizeof($this->filterType) > 0)
+        {
+            $course_ids = \App\Models\Course::whereIn('course_type_id', $this->filterType)->pluck('id');
+            $datas = $datas->whereIn('course_id', $course_ids);
+        }
+        if (sizeof($this->filterDuration) > 0)
+        {
+            $course_ids = \App\Models\Course::whereIn('course_duration_id', $this->filterDuration)->pluck('id');
+            $datas = $datas->whereIn('course_id', $course_ids);
+        }
+        if (sizeof($this->filterDays) > 0)
+        {
+            $m_ids = [];
+            foreach($this->filterDays as $c)
+            {
+                $all = \App\Models\MemberCourse::where('when', 'like', "%" . $c . "%")->get();
+                foreach($all as $a)
+                {
+                    $m_ids[] = $a->member_id;
+                }
+            }
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+        if (sizeof($this->filterHours) > 0)
+        {
+            $m_ids = [];
+            foreach($this->filterHours as $c)
+            {
+                $all = \App\Models\MemberCourse::where('when', 'like', '%"from":"' . $c . "%")->get();
+                foreach($all as $a)
+                {
+                    $m_ids[] = $a->member_id;
+                }
+            }
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+        /*if ($this->filterDays != "")
+        {
+            $members_ids = \App\Models\MemberCourse::where('when', 'like', "%" . $this->filterDays . "%")->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $members_ids);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Giorni : " . $this->filterDays . " ";
+        }
+        if ($this->filterHours != "")
+        {
+            $members_ids = \App\Models\MemberCourse::where('when', 'like', '%"from":"' . $this->filterHours . "%")->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $members_ids);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Ore : " . $this->filterHours . " ";
+        }*/
+
+        if ($this->filterSubscription != "")
+        {
+            $ids = \App\Models\MemberCourse::where('subscribed', $this->filterSubscription == 1 ? true : false)->pluck('id');
+            $datas = $datas->whereIn('id', $ids);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Pagata sottoscrizione : " . ($this->filterSubscription == 1 ? "SI" : "NO") . " ";
+        }
+        if ($this->chkCertificateType != "")
+        {
+            $types = \App\Models\MemberCertificate::where('type', $this->chkCertificateType)->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $types);
+        }
+        if ($this->chkCertificateScadenza > 0)
+        {
+            if ($this->chkCertificateScadenza == "1")
+                $scad = \App\Models\MemberCertificate::where('expire_date', '<', date("Y-m-d"))->pluck('member_id');
+            if ($this->chkCertificateScadenza == "2")
+                $scad = \App\Models\MemberCertificate::whereBetween('expire_date', [date("Y-m-d"), date("Y-m-d", strtotime("+1 month"))])->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $scad);
+        }
+        if ($this->fromYear != "")
+        {
+            $m_ids = \App\Models\Member::where('birth_date', '<', date("Y-m-d", strtotime("-" . $this->fromYear . " year", time())))->pluck('id');
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+        if ($this->toYear != "")
+        {
+            $m_ids = \App\Models\Member::where('birth_date', '>', date("Y-m-d", strtotime("-" . $this->toYear . " year", time())))->pluck('id');
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+        if ($this->fromFromYear != "")
+        {
+            $m_ids = \App\Models\Member::whereYear('birth_date', '>=', $this->fromFromYear)->pluck('id');
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+        if ($this->toToYear != "")
+        {
+            $m_ids = \App\Models\Member::whereYear('birth_date', '<=', $this->toToYear)->pluck('id');
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+        /*if ($_GET["toYear"] != "")
+        {
+            $x = $x->where('birth_date', '>', date("Y-m-d", strtotime("-" . $_GET["toYear"] . " year", time())));
+        }
+        if ($_GET["fromYearYear"] != "")
+        {
+            $x = $x->whereYear('birth_date', '>=', $_GET["fromYearYear"]);
+        }
+        if ($_GET["toYearYear"] != "")
+        {
+            $x = $x->whereYear('birth_date', '<=', $_GET["toYearYear"]);
+        }*/
+        /*
+        if ($this->chkCertificateNormal > 0)
+        {
+            $normal = \App\Models\MemberCertificate::where('type', 'N')->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $normal);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Certificato normale : SI ";
+        }
+        if ($this->chkCertificateAgonistico > 0)
+        {
+            $agonistic = \App\Models\MemberCertificate::where('type', 'A')->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $agonistic);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Certificato agonistico : SI ";
+        }
+        if ($this->chkCertificateScaduti > 0)
+        {
+            $scaduto = \App\Models\MemberCertificate::where('expire_date', '<', date("Y-m-d"))->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $scaduto);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Certificato scaduto : SI ";
+        }
+        if ($this->chkCertificateScadenza > 0)
+        {
+            $scadenza = \App\Models\MemberCertificate::whereBetween('expire_date', [date("Y-m-d"), date("Y-m-d", strtotime("+1 month"))])->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $scadenza);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Certificato in scadenza : SI ";
+        }*/
+        if (sizeof($this->chkCard) > 0)
+        {
+            $card_ids = \App\Models\MemberCard::whereIn('card_id', $this->chkCard)->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $card_ids);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Tessera : ";
+            foreach($this->chkCard as $card)
+            {
+                $this->filter .= \App\Models\Card::findOrFail($card)->name . " ";
+            }
+
+        }
+        if ($this->filterYear != "")
+        {
+            $course_ids = \App\Models\Course::where('year', $this->filterYear)->pluck('id');
+            $datas = $datas->whereIn('course_id', $course_ids);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Anno : " . $this->filterYear . " ";
+        }
+
+        $aRet = [];
+        if (sizeof($this->filterStatus) > 0)
+        {
+            foreach($this->filterStatus as $s)
+            {
+                foreach($datas->get() as $aaa)
+                {
+
+                    $state = \App\Models\Member::findOrFail($aaa->member_id)->isActive();
+                    if ($state["status"] == $s)
+                        $aRet[] = $aaa;
+                }
+            }
+        }
+        else
+            $aRet = $datas->get();
+
+        $this->records = $aRet;
+
+        $this->emit('load-data-table');
+
+        return view('livewire.course_member');
+
+    }
+
+    public function disableSearch()
+    {
+        $this->filterCourse = [];
+        $this->filterLevel = [];
+        $this->filterType = [];
+        $this->filterDuration = [];
+        $this->filterFrequency = [];
+        $this->filterDays = [];
+        $this->filterHours = [];
+        $this->filterSubscription = "";
+        $this->filterStatus = [];
+        $this->chkCertificateNormal = 0;
+        $this->chkCertificateAgonistico = 0;
+        $this->chkCertificateScaduti = 0;
+        $this->chkCertificateScadenza = 0;
+        $this->chkCard = [];
+
+        $this->filterYear = "";
+        $this->fromYear = "";
+        $this->toYear = "";
+        $this->fromFromYear = "";
+        $this->toToYear = "";
+
+    }
+
+}

+ 1 - 1
app/Http/Livewire/CourseMemberOne.php

@@ -10,7 +10,7 @@ class CourseMemberOne extends Component
 
     public function render()
     {
-        $this->records[] = array('name' => 'Standard', 'type' => 'standard');
+        $this->records[] = array('name' => 'Singoli', 'type' => 'standard');
         $this->records[] = array('name' => 'Personalizzati', 'type' => 'custom');
         return view('livewire.course_member_one');
     }

+ 17 - 0
app/Http/Livewire/CourseMemberOne.php.bak

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+
+class CourseMemberOne extends Component
+{
+    public $records = [];
+
+    public function render()
+    {
+        $this->records[] = array('name' => 'Standard', 'type' => 'standard');
+        $this->records[] = array('name' => 'Personalizzati', 'type' => 'custom');
+        return view('livewire.course_member_one');
+    }
+}

+ 118 - 28
app/Http/Livewire/Dashboard.php

@@ -203,31 +203,132 @@ class Dashboard extends Component
             $currentDate = now()->format('Y-m-d');
             Log::info('Calculating financial stats for month', ['month' => $currentMonth]);
 
-            $this->received = \App\Models\Record::where('type', 'IN')
+            // excluded members
+            $excluded_members = \App\Models\Member::where('exclude_from_records', true)->pluck('id')->toArray();
+            // excluded payment method
+            $excluded_payment_methods = \App\Models\PaymentMethod::where('money', true)->pluck('id')->toArray();
+            // excluded causals
+            $excluded_causals = \App\Models\Causal::orWhereIn('name', [
+                'Corrispettivo fiscale giornaliero',
+                'CFG',
+            ])->orWhere('hidden', 1)->orWhere('enabled', 0)->pluck('id')->toArray();
+
+
+            // Incassato
+            $this->received = 0;
+            $records_in = \App\Models\Record::with('rows')->where('type', 'IN')
                 ->whereRaw('DATE_FORMAT(date, "%Y-%m") = ?', [$currentMonth])
                 ->where(function ($query) {
                     $query->where('deleted', false)->orWhere('deleted', null);
-                })
-                ->sum('amount') ?? 0;
+                })->get();
+            foreach ($records_in as $record) {
+                if (in_array($record->payment_method_id, $excluded_payment_methods)) {
+                    continue;
+                }
 
-            $this->paid = \App\Models\Record::where('type', 'OUT')
-                ->whereRaw('DATE_FORMAT(date, "%Y-%m") = ?', [$currentMonth])
-                ->where('data_pagamento', '<=', $currentDate)
+                if (in_array($record->member_id, $excluded_members)) {
+                    continue;
+                }
+
+                foreach ($record->rows as $row) {
+                    if ($row->causal_id) {
+                        $causal = \App\Models\Causal::find($row->causal_id);
+
+                        if (!$causal || (isset($causal->hidden) && $causal->hidden)) {
+                            continue;
+                        }
+
+                        if (in_array($row->causal_id, $excluded_causals)) {
+                            continue;
+                        }
+
+                        $this->received += $row->amount;
+                        if ($row->vat_id > 0) {
+                            $this->received += getVatValue($row->amount, $row->vat_id);
+                        }
+                    }
+                }
+            }
+            // END - Incassato
+
+
+            // Pagato
+            $this->paid = 0;
+            $records_out = \App\Models\Record::with('rows')->where('type', 'OUT')
+                ->whereRaw('DATE_FORMAT(data_pagamento, "%Y-%m") = ?', [$currentMonth])
                 ->where(function ($query) {
                     $query->where('deleted', false)->orWhere('deleted', null);
-                })
-                ->sum('amount') ?? 0;
+                })->get();
+            foreach ($records_out as $record) {
+                if (in_array($record->payment_method_id, $excluded_payment_methods)) {
+                    continue;
+                }
 
-            $this->toPay = \App\Models\Record::where('type', 'OUT')
-                ->whereRaw('DATE_FORMAT(date, "%Y-%m") = ?', [$currentMonth])
-                ->where(function ($query) use($currentDate) {
+                if (in_array($record->member_id, $excluded_members)) {
+                    continue;
+                }
+
+                foreach ($record->rows as $row) {
+                    if ($row->causal_id) {
+                        $causal = \App\Models\Causal::find($row->causal_id);
+
+                        if (!$causal || (isset($causal->hidden) && $causal->hidden)) {
+                            continue;
+                        }
+
+                        if (in_array($row->causal_id, $excluded_causals)) {
+                            continue;
+                        }
+
+                        $this->paid += $row->amount;
+                        if ($row->vat_id > 0) {
+                            $this->paid += getVatValue($row->amount, $row->vat_id);
+                        }
+                    }
+                }
+            }
+            // END - Pagato
+
+
+            // Da pagare
+            $this->toPay = 0;
+            $records_toPay = \App\Models\Record::with('rows')->where('type', 'OUT')
+                ->where(function ($query) use ($currentDate) {
                     $query->where('data_pagamento', '>', $currentDate)
                         ->orWhereNull('data_pagamento');
                 })
                 ->where(function ($query) {
                     $query->where('deleted', false)->orWhere('deleted', null);
-                })
-                ->sum('amount') ?? 0;
+                })->get();
+            foreach ($records_toPay as $record) {
+                if (in_array($record->payment_method_id, $excluded_payment_methods)) {
+                    continue;
+                }
+
+                if (in_array($record->member_id, $excluded_members)) {
+                    continue;
+                }
+
+                foreach ($record->rows as $row) {
+                    if ($row->causal_id) {
+                        $causal = \App\Models\Causal::find($row->causal_id);
+
+                        if (!$causal || (isset($causal->hidden) && $causal->hidden)) {
+                            continue;
+                        }
+
+                        if (in_array($row->causal_id, $excluded_causals)) {
+                            continue;
+                        }
+
+                        $this->toPay += $row->amount;
+                        if ($row->vat_id > 0) {
+                            $this->toPay += getVatValue($row->amount, $row->vat_id);
+                        }
+                    }
+                }
+            }
+            // END - Da pagare
 
             $endTime = microtime(true);
             Log::info('Financial stats loaded successfully', [
@@ -431,12 +532,7 @@ class Dashboard extends Component
                 $frequencyName = $course->frequency?->name ?? '';
                 $typeName = $course->getFormattedTypeField() ?? '';
 
-                $courseNameParts = [$courseName];
-                if ($levelName) $courseNameParts[] = $levelName;
-                if ($typeName) $courseNameParts[] = $typeName;
-                if ($frequencyName) $courseNameParts[] = $frequencyName;
-
-                $fullCourseName = implode(' - ', $courseNameParts);
+                $fullCourseName = $course->getDetailsName();
 
                 return [
                     'time' => $todaySchedule['from'] . ' - ' . $todaySchedule['to'],
@@ -451,9 +547,9 @@ class Dashboard extends Component
                     'course_id' => $course->id,
                     'member_course_id' => $memberCourse->id
                 ];
-            })->filter()->values();
+            })->filter()->unique('course_id')->values();
 
-            $sortedCourses = $this->courses->sortBy('from_time')->take(5);
+            $sortedCourses = $this->courses->sortBy('from_time')->take(5)->values();
 
             Log::info('Courses sorted by time', [
                 'sorted_courses' => $sortedCourses->map(function ($course) {
@@ -529,13 +625,7 @@ class Dashboard extends Component
                 $frequencyName = is_object($course->frequency) ? $course->frequency->name : '';
                 $typeName = $course->getFormattedTypeField() ?? '';
 
-                // Build display name with level and frequency
-                $displayNameParts = [$courseName];
-                if ($levelName) $displayNameParts[] = $levelName;
-                if ($typeName) $displayNameParts[] = $typeName;
-                if ($frequencyName) $displayNameParts[] = $frequencyName;
-
-                $displayName = implode(' - ', $displayNameParts);
+                $displayName = $course->getDetailsName();
 
                 // Assegna colori basati sul nome del corso
                 $color = $this->getCourseColor($courseName);

+ 834 - 0
app/Http/Livewire/Dashboard.php.bak

@@ -0,0 +1,834 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Carbon\Carbon;
+use DateTimeZone;
+use Illuminate\Support\Facades\Log;
+
+class Dashboard extends Component
+{
+    // Existing properties
+    public $totMembers = 0;
+    public $totSuppliers = 0;
+    public $totTodayIn = 0;
+    public $totTodayOut = 0;
+    public $dayName;
+    public $in;
+    public $out;
+    public $members;
+    public $activeUsers = 0;
+    public $registeredUsers = 0;
+    public $expiredCertificates = 0;
+    public $suspendedSubscriptions = 0;
+    public $activeUsersChange = 0;
+    public $registeredUsersChange = 0;
+    public $expiredCertificatesChange = 0;
+    public $suspendedSubscriptionsChange = 0;
+
+    public $received = 0;
+    public $toPay = 0;
+    public $paid = 0;
+
+    public $courses = [];
+    public $fields = [];
+    public $recentUsers = [];
+    public $recentTransactions = [];
+    public $coursesParticipation = [];
+    public $notes = '';
+    public $savedNotes = [];
+    public array $membersDatas = [];
+    public array $recordDatas = [];
+    public array $labels = [];
+    public array $monthlyLabels = [];
+    public array $monthlyIncomeData = [];
+    public array $monthlyExpenseData = [];
+
+    public function render()
+    {
+        Log::info('Dashboard render method called');
+        return view('livewire.dashboard');
+    }
+
+    public function mount()
+    {
+        Log::info('Dashboard mount started');
+        $startTime = microtime(true);
+
+        $this->dayName = Carbon::now()->locale('it_IT')->dayName;
+        Log::info('Day name set', ['day' => $this->dayName]);
+
+        $this->loadBasicStats();
+        $this->loadUserStats();
+        $this->loadFinancialStats();
+        $this->loadRecentData();
+        $this->loadSavedNotes();
+
+        $endTime = microtime(true);
+        $executionTime = round(($endTime - $startTime) * 1000, 2);
+        Log::info('Dashboard mount completed', [
+            'execution_time_ms' => $executionTime,
+            'active_users' => $this->activeUsers,
+            'courses_count' => count($this->courses),
+            'participation_count' => count($this->coursesParticipation)
+        ]);
+    }
+
+    private function loadBasicStats()
+    {
+        Log::info('Loading basic stats');
+        $startTime = microtime(true);
+
+        try {
+            $this->totMembers = \App\Models\Member::count();
+            $this->totSuppliers = \App\Models\Supplier::count();
+            Log::info('Basic counts loaded', [
+                'total_members' => $this->totMembers,
+                'total_suppliers' => $this->totSuppliers
+            ]);
+
+            // Calculate today's income and expenses
+            $this->totTodayIn = 0;
+            $todayRecordsIn = \App\Models\Record::where('type', 'IN')
+                ->where('date', date("Y-m-d"))
+                ->get();
+
+            foreach ($todayRecordsIn as $record) {
+                foreach ($record->rows as $row) {
+                    $this->totTodayIn += $row->amount;
+                }
+            }
+
+            $this->totTodayOut = 0;
+            $todayRecordsOut = \App\Models\Record::where('type', 'OUT')
+                ->where('date', date("Y-m-d"))
+                ->get();
+
+            foreach ($todayRecordsOut as $record) {
+                foreach ($record->rows as $row) {
+                    $this->totTodayOut += $row->amount;
+                }
+            }
+
+            $endTime = microtime(true);
+            Log::info('Basic stats loaded successfully', [
+                'today_income' => $this->totTodayIn,
+                'today_expenses' => $this->totTodayOut,
+                'execution_time_ms' => round(($endTime - $startTime) * 1000, 2)
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error loading basic stats', [
+                'error' => $e->getMessage(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine()
+            ]);
+        }
+    }
+
+    private function loadUserStats()
+    {
+        Log::info('Loading user stats');
+        $startTime = microtime(true);
+
+        try {
+            $this->activeUsers = \App\Models\Member::where('is_archived', 0)->orWhere('is_archived', NULL)->count();
+            $this->registeredUsers = \App\Models\Member::where('current_status', 2)->count();
+            $this->suspendedSubscriptions = \App\Models\Member::where('current_status', 1)->count();
+
+            Log::info('User counts loaded', [
+                'active_users' => $this->activeUsers,
+                'registered_users' => $this->registeredUsers,
+                'suspended_subscriptions' => $this->suspendedSubscriptions
+            ]);
+
+            $this->expiredCertificates = \App\Models\Member::whereHas('certificates', function ($query) {
+                $query->where('expire_date', '<', now());
+            })->whereDoesntHave('certificates', function ($query) {
+                $query->where('expire_date', '>=', now());
+            })->count();
+
+            Log::info('Expired certificates count', ['expired_certificates' => $this->expiredCertificates]);
+
+            // Calculate changes from last month
+            $lastMonth = now()->subMonth();
+            $endOfLastMonth = $lastMonth->copy()->endOfMonth();
+
+            $lastMonthActiveUsers = \App\Models\Member::where('is_archived', false)
+                ->where('created_at', '<=', $endOfLastMonth)
+                ->count();
+            $lastMonthRegisteredUsers = \App\Models\Member::where('current_status', 2)
+                ->where('updated_at', '<=', $endOfLastMonth)
+                ->count();
+            $lastMonthSuspendedSubscriptions = \App\Models\Member::where('current_status', 1)
+                ->where('updated_at', '<=', $endOfLastMonth)
+                ->count();
+            $lastMonthExpiredCertificates = \App\Models\Member::whereHas('certificates', function ($query) use ($endOfLastMonth) {
+                $query->where('expire_date', '<', $endOfLastMonth);
+            })->whereDoesntHave('certificates', function ($query) use ($endOfLastMonth) {
+                $query->where('expire_date', '>=', $endOfLastMonth);
+            })->count();
+
+            $this->activeUsersChange = $this->activeUsers - $lastMonthActiveUsers;
+            $this->registeredUsersChange = $this->registeredUsers - $lastMonthRegisteredUsers;
+            $this->expiredCertificatesChange = $this->expiredCertificates - $lastMonthExpiredCertificates;
+            $this->suspendedSubscriptionsChange = $this->suspendedSubscriptions - $lastMonthSuspendedSubscriptions;
+
+            $endTime = microtime(true);
+            Log::info('User stats loaded successfully', [
+                'changes' => [
+                    'active_users' => $this->activeUsersChange,
+                    'registered_users' => $this->registeredUsersChange,
+                    'expired_certificates' => $this->expiredCertificatesChange,
+                    'suspended_subscriptions' => $this->suspendedSubscriptionsChange
+                ],
+                'execution_time_ms' => round(($endTime - $startTime) * 1000, 2)
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error loading user stats', [
+                'error' => $e->getMessage(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine()
+            ]);
+        }
+    }
+
+    private function loadFinancialStats()
+    {
+        Log::info('Loading financial stats');
+        $startTime = microtime(true);
+
+        try {
+            $currentMonth = now()->format('Y-m');
+            $currentDate = now()->format('Y-m-d');
+            Log::info('Calculating financial stats for month', ['month' => $currentMonth]);
+
+            $this->received = \App\Models\Record::where('type', 'IN')
+                ->whereRaw('DATE_FORMAT(date, "%Y-%m") = ?', [$currentMonth])
+                ->where(function ($query) {
+                    $query->where('deleted', false)->orWhere('deleted', null);
+                })
+                ->sum('amount') ?? 0;
+
+            $this->paid = \App\Models\Record::where('type', 'OUT')
+                ->whereRaw('DATE_FORMAT(date, "%Y-%m") = ?', [$currentMonth])
+                ->where('data_pagamento', '<=', $currentDate)
+                ->where(function ($query) {
+                    $query->where('deleted', false)->orWhere('deleted', null);
+                })
+                ->sum('amount') ?? 0;
+
+            $this->toPay = \App\Models\Record::where('type', 'OUT')
+                ->whereRaw('DATE_FORMAT(date, "%Y-%m") = ?', [$currentMonth])
+                ->where(function ($query) use($currentDate) {
+                    $query->where('data_pagamento', '>', $currentDate)
+                        ->orWhereNull('data_pagamento');
+                })
+                ->where(function ($query) {
+                    $query->where('deleted', false)->orWhere('deleted', null);
+                })
+                ->sum('amount') ?? 0;
+
+            $endTime = microtime(true);
+            Log::info('Financial stats loaded successfully', [
+                'received' => $this->received,
+                'paid' => $this->paid,
+                'to_pay' => $this->toPay,
+                'execution_time_ms' => round(($endTime - $startTime) * 1000, 2)
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error loading financial stats', [
+                'error' => $e->getMessage(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine()
+            ]);
+        }
+    }
+
+    private function loadRecentData()
+    {
+        Log::info('Loading recent data');
+        $startTime = microtime(true);
+
+        try {
+            // Load recent users
+            $recentMembers = \App\Models\Member::where('is_archived', 0)
+                ->orWhere('is_archived', NULL)
+                ->orderBy('created_at', 'desc')
+                ->limit(5)
+                ->get();
+
+            $this->recentUsers = $recentMembers->map(function ($member) {
+                return [
+                    'surname' => strtoupper($member->last_name),
+                    'name' => strtoupper($member->first_name),
+                    'phone' => $member->phone ?? '',
+                    'email' => $member->email ?? ''
+                ];
+            })->toArray();
+
+            Log::info('Recent users loaded', ['count' => count($this->recentUsers)]);
+
+            // Load recent transactions
+            $recentRecords = \App\Models\Record::where('date', '>=', now()->subDays(30))
+                ->with(['member', 'supplier'])
+                ->orderBy('date', 'desc')
+                ->orderBy('created_at', 'desc')
+                ->limit(10)
+                ->get();
+
+            $this->recentTransactions = $recentRecords->map(function ($record) {
+                if ($record->type == 'IN') {
+                    $name = $record->member ?
+                        strtoupper($record->member->last_name) . ' ' . strtoupper($record->member->first_name) :
+                        'MEMBRO SCONOSCIUTO';
+                } else {
+                    $name = $record->supplier ?
+                        strtoupper($record->supplier->name) :
+                        'FORNITORE SCONOSCIUTO';
+                }
+
+                $totalAmount = 0;
+                foreach ($record->rows as $row) {
+                    $totalAmount += $row->amount;
+                }
+
+                return [
+                    'name' => $name,
+                    'amount' => $totalAmount,
+                    'type' => $record->type == 'IN' ? 'ENTRATA' : 'USCITA'
+                ];
+            })->toArray();
+
+            Log::info('Recent transactions loaded', ['count' => count($this->recentTransactions)]);
+
+            $this->loadCoursesData();
+            $this->loadCoursesParticipation();
+
+            $endTime = microtime(true);
+            Log::info('Recent data loaded successfully', [
+                'execution_time_ms' => round(($endTime - $startTime) * 1000, 2)
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error loading recent data', [
+                'error' => $e->getMessage(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine()
+            ]);
+        }
+    }
+
+    private function loadCoursesData()
+    {
+        Log::info('Loading courses data');
+        $startTime = microtime(true);
+
+        try {
+            $today = now()->format('N');
+            $dayNames = [
+                1 => 'lun',
+                2 => 'mar',
+                3 => 'mer',
+                4 => 'gio',
+                5 => 'ven',
+                6 => 'sab',
+                7 => 'dom'
+            ];
+            $todayName = $dayNames[$today];
+
+            Log::info('Searching courses for today', [
+                'today_number' => $today,
+                'today_name' => $todayName
+            ]);
+
+            $memberCourses = \App\Models\MemberCourse::with(['course.level', 'course.frequency', 'member'])
+                ->whereIn('status', [0, 1])
+                ->whereHas('course', function ($query) {
+                    $query->whereNotNull('when');
+                })
+                ->get();
+
+            Log::info('Total member courses found', [
+                'count' => $memberCourses->count()
+            ]);
+
+            $activeCourses = $memberCourses->filter(function ($memberCourse) use ($todayName) {
+                try {
+                    $whenData = json_decode($memberCourse->course->when, true);
+
+                    if (!is_array($whenData)) {
+                        return false;
+                    }
+
+                    foreach ($whenData as $schedule) {
+                        if (
+                            isset($schedule['day']) &&
+                            is_array($schedule['day']) &&
+                            in_array($todayName, $schedule['day'])
+                        ) {
+                            return true;
+                        }
+                    }
+                } catch (\Exception $e) {
+                    Log::debug('Error parsing course schedule', [
+                        'member_course_id' => $memberCourse->id,
+                        'course_id' => $memberCourse->course->id,
+                        'when' => $memberCourse->course->when,
+                        'error' => $e->getMessage()
+                    ]);
+                }
+                return false;
+            });
+
+            Log::info('Active courses found for today', [
+                'count' => $activeCourses->count(),
+                'course_ids' => $activeCourses->pluck('course.id')->toArray()
+            ]);
+
+            $this->courses = $activeCourses->map(function ($memberCourse) use ($todayName) {
+                $whenData = json_decode($memberCourse->course->when, true);
+
+                Log::debug('Processing course schedule', [
+                    'member_course_id' => $memberCourse->id,
+                    'course_id' => $memberCourse->course->id,
+                    'course_name' => $memberCourse->course->name,
+                    'when_data' => $whenData,
+                    'looking_for_day' => $todayName
+                ]);
+
+                $todaySchedule = null;
+
+                if (is_array($whenData)) {
+                    foreach ($whenData as $schedule) {
+                        if (
+                            isset($schedule['day']) &&
+                            is_array($schedule['day']) &&
+                            in_array($todayName, $schedule['day'])
+                        ) {
+                            $todaySchedule = $schedule;
+                            Log::debug('Found matching schedule', [
+                                'schedule' => $schedule,
+                                'course_id' => $memberCourse->course->id
+                            ]);
+                            break;
+                        }
+                    }
+                }
+
+                if (!$todaySchedule) {
+                    Log::debug('No matching schedule found for today', [
+                        'course_id' => $memberCourse->course->id,
+                        'when_data' => $whenData
+                    ]);
+                    return null;
+                }
+
+                $days = implode('-', array_map('ucfirst', $todaySchedule['day']));
+                $course = $memberCourse->course;
+
+                $courseName = $course->name ?? 'Corso Sconosciuto';
+                $levelName = $course->level?->name ?? '';
+                $frequencyName = $course->frequency?->name ?? '';
+                $typeName = $course->getFormattedTypeField() ?? '';
+
+                $courseNameParts = [$courseName];
+                if ($levelName) $courseNameParts[] = $levelName;
+                if ($typeName) $courseNameParts[] = $typeName;
+                if ($frequencyName) $courseNameParts[] = $frequencyName;
+
+                $fullCourseName = implode(' - ', $courseNameParts);
+
+                return [
+                    'time' => $todaySchedule['from'] . ' - ' . $todaySchedule['to'],
+                    'course_name' => $courseName,
+                    'full_name' => $fullCourseName,
+                    'level_name' => $levelName,
+                    'type_name' => $typeName,
+                    'frequency_name' => $frequencyName,
+                    'days' => $days,
+                    'type' => $course->type ?? 'Standard',
+                    'from_time' => $todaySchedule['from'],
+                    'course_id' => $course->id,
+                    'member_course_id' => $memberCourse->id
+                ];
+            })->filter()->values();
+
+            $sortedCourses = $this->courses->sortBy('from_time')->take(5);
+
+            Log::info('Courses sorted by time', [
+                'sorted_courses' => $sortedCourses->map(function ($course) {
+                    return [
+                        'time' => $course['time'],
+                        'from_time' => $course['from_time'],
+                        'course_name' => $course['course_name'],
+                        'course_id' => $course['course_id']
+                    ];
+                })->toArray()
+            ]);
+
+            $this->courses = $sortedCourses->map(function ($course) {
+                unset($course['from_time'], $course['member_course_id'], $course['course_id']);
+                return $course;
+            })->toArray();
+
+            $endTime = microtime(true);
+            Log::info('Courses data loaded successfully', [
+                'final_courses_count' => count($this->courses),
+                'execution_time_ms' => round(($endTime - $startTime) * 1000, 2),
+                'final_courses_display' => $this->courses
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error loading courses data', [
+                'error' => $e->getMessage(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine()
+            ]);
+            $this->courses = [];
+        }
+    }
+
+    private function loadCoursesParticipation()
+    {
+        Log::info('Loading courses participation');
+        $startTime = microtime(true);
+
+        try {
+            // Conta le partecipazioni per corso (include tutti gli status)
+            $courseStats = \App\Models\MemberCourse::with(['course.level', 'course.frequency'])
+                ->whereIn('status', [0, 1]) // Include both statuses
+                ->selectRaw('course_id, COUNT(*) as participants')
+                ->groupBy('course_id')
+                ->orderBy('participants', 'desc')
+                ->limit(4)
+                ->get();
+
+            Log::info('Course participation stats', [
+                'courses_found' => $courseStats->count(),
+                'stats' => $courseStats->map(function ($stat) {
+                    $course = $stat->course;
+                    $levelName = is_object($course->level) ? $course->level->name : '';
+                    $frequencyName = is_object($course->frequency) ? $course->frequency->name : '';
+
+                    return [
+                        'course_id' => $stat->course_id,
+                        'course_name' => $course->name ?? 'Unknown',
+                        'level_name' => $levelName,
+                        'frequency_name' => $frequencyName,
+                        'participants' => $stat->participants
+                    ];
+                })->toArray()
+            ]);
+
+            $totalParticipants = $courseStats->sum('participants');
+
+            $this->coursesParticipation = $courseStats->map(function ($stat) use ($totalParticipants) {
+                $percentage = $totalParticipants > 0 ? ($stat->participants / $totalParticipants) * 100 : 0;
+                $course = $stat->course;
+                $courseName = $course->name ?? 'Corso Sconosciuto';
+                $levelName = is_object($course->level) ? $course->level->name : '';
+                $frequencyName = is_object($course->frequency) ? $course->frequency->name : '';
+                $typeName = $course->getFormattedTypeField() ?? '';
+
+                // Build display name with level and frequency
+                $displayNameParts = [$courseName];
+                if ($levelName) $displayNameParts[] = $levelName;
+                if ($typeName) $displayNameParts[] = $typeName;
+                if ($frequencyName) $displayNameParts[] = $frequencyName;
+
+                $displayName = implode(' - ', $displayNameParts);
+
+                // Assegna colori basati sul nome del corso
+                $color = $this->getCourseColor($courseName);
+
+                return [
+                    'course_name' => $displayName,
+                    'base_course_name' => $courseName,
+                    'level_name' => $levelName,
+                    'type_name' => $typeName,
+                    'frequency_name' => $frequencyName,
+                    'participants' => $stat->participants,
+                    'percentage' => round($percentage, 1),
+                    'color' => $color
+                ];
+            })->toArray();
+
+            $endTime = microtime(true);
+            Log::info('Courses participation loaded successfully', [
+                'total_participants' => $totalParticipants,
+                'participation_data' => $this->coursesParticipation,
+                'execution_time_ms' => round(($endTime - $startTime) * 1000, 2)
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error loading courses participation', [
+                'error' => $e->getMessage(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine()
+            ]);
+            $this->coursesParticipation = [];
+        }
+    }
+
+    private function getCourseColor($courseName)
+    {
+        $colors = [
+            'padel',
+            'tennis',
+            'pallavolo',
+            'yoga',
+            'blue',
+            'pink',
+            'green',
+            'red',
+            'indigo',
+            'amber',
+            'cyan',
+            'lime'
+        ];
+        $hash = crc32($courseName);
+        $colorIndex = abs($hash) % count($colors);
+        $assignedColor = $colors[$colorIndex];
+
+        Log::debug('Course color assigned', [
+            'course_name' => $courseName,
+            'hash' => $hash,
+            'color_index' => $colorIndex,
+            'assigned_color' => $assignedColor
+        ]);
+
+        return $assignedColor;
+    }
+
+    private function loadSavedNotes()
+    {
+        Log::info('Loading saved notes');
+
+        try {
+            $this->savedNotes = session()->get('dashboard_notes', []);
+
+            Log::info('Saved notes loaded', [
+                'notes_count' => count($this->savedNotes)
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error loading saved notes', [
+                'error' => $e->getMessage()
+            ]);
+            $this->savedNotes = [];
+        }
+    }
+
+    private function saveSavedNotes()
+    {
+        try {
+            session()->put('dashboard_notes', $this->savedNotes);
+
+            Log::info('Notes saved to session', [
+                'notes_count' => count($this->savedNotes)
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error saving notes', [
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+
+    public function saveNote()
+    {
+        Log::info('Save note called', ['note_text' => $this->notes]);
+
+        try {
+            if (trim($this->notes) !== '') {
+                $newNote = [
+                    'id' => uniqid(),
+                    'text' => trim($this->notes),
+                    'created_at' => now()->timezone('Europe/Rome')->format('d/m/Y H:i'),
+                    'completed' => false
+                ];
+
+                array_unshift($this->savedNotes, $newNote);
+
+                $this->saveSavedNotes();
+
+                $this->notes = '';
+
+                $this->dispatchBrowserEvent('note-saved');
+
+                Log::info('Note saved successfully', [
+                    'note_id' => $newNote['id'],
+                    'total_notes' => count($this->savedNotes)
+                ]);
+            } else {
+                Log::warning('Attempted to save empty note');
+            }
+        } catch (\Exception $e) {
+            Log::error('Error saving note', [
+                'error' => $e->getMessage(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine()
+            ]);
+        }
+    }
+
+    public function completeNote($noteId)
+    {
+        Log::info('Complete note called', ['note_id' => $noteId]);
+
+        try {
+            $initialCount = count($this->savedNotes);
+
+            $this->savedNotes = array_filter($this->savedNotes, function ($note) use ($noteId) {
+                return $note['id'] !== $noteId;
+            });
+
+            $this->savedNotes = array_values($this->savedNotes);
+
+            $finalCount = count($this->savedNotes);
+
+            if ($initialCount > $finalCount) {
+                $this->saveSavedNotes();
+
+                $this->dispatchBrowserEvent('note-completed');
+
+                Log::info('Note completed successfully', [
+                    'note_id' => $noteId,
+                    'remaining_notes' => $finalCount
+                ]);
+            } else {
+                Log::warning('Note not found for completion', ['note_id' => $noteId]);
+            }
+        } catch (\Exception $e) {
+            Log::error('Error completing note', [
+                'note_id' => $noteId,
+                'error' => $e->getMessage(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine()
+            ]);
+        }
+    }
+
+    public function addMember()
+    {
+        Log::info('Redirecting to add member');
+        return redirect()->to('/members?new=1');
+    }
+
+    public function addSupplier()
+    {
+        Log::info('Redirecting to add supplier');
+        return redirect()->to('/suppliers?new=1');
+    }
+
+    public function addIn()
+    {
+        Log::info('Redirecting to add income record');
+        return redirect()->to('/in?new=1');
+    }
+
+    public function addOut()
+    {
+        Log::info('Redirecting to add expense record');
+        return redirect()->to('/out?new=1');
+    }
+
+    public function debugCourses()
+    {
+        Log::info('=== DEBUG COURSES CALLED ===');
+
+        $today = now()->format('N');
+        $dayNames = [
+            1 => 'lun',
+            2 => 'mar',
+            3 => 'mer',
+            4 => 'gio',
+            5 => 'ven',
+            6 => 'sab',
+            7 => 'dom'
+        ];
+        $todayName = $dayNames[$today];
+
+        // Get all member courses
+        $allCourses = \App\Models\MemberCourse::with('course')->get();
+
+        Log::info('All courses debug', [
+            'total_courses' => $allCourses->count(),
+            'today_name' => $todayName,
+            'courses' => $allCourses->map(function ($mc) {
+                return [
+                    'id' => $mc->id,
+                    'status' => $mc->status,
+                    'when' => $mc->when,
+                    'course_name' => $mc->course->name ?? 'Unknown'
+                ];
+            })->toArray()
+        ]);
+
+        $this->dispatchBrowserEvent('debug-completed');
+    }
+
+    public function activateTestCourses()
+    {
+        try {
+            $updated = \App\Models\MemberCourse::whereIn('id', [21, 22, 23, 24])
+                ->update(['status' => 1]);
+
+            Log::info('Activated test courses', ['updated_count' => $updated]);
+
+            $this->loadCoursesData();
+            $this->loadCoursesParticipation();
+
+            $this->dispatchBrowserEvent('courses-activated', [
+                'message' => "Attivati $updated corsi per test"
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Error activating courses', ['error' => $e->getMessage()]);
+        }
+    }
+
+    private function getLabels()
+    {
+        $labels = array();
+        for ($i = 6; $i >= 0; $i--) {
+            $labels[] = date("d/M", strtotime('-' . $i . ' days'));
+        }
+        return $labels;
+    }
+
+    private function getRecordData($type)
+    {
+        $data = [];
+        for ($i = 6; $i >= 0; $i--) {
+            $found = false;
+            $records = $type == 'IN' ? $this->in : $this->out;
+
+            foreach ($records as $record) {
+                if (date("Y-m-d", strtotime($record->date)) == date("Y-m-d", strtotime('-' . $i . ' days'))) {
+                    $data[] = $record->total;
+                    $found = true;
+                    break;
+                }
+            }
+            if (!$found) {
+                $data[] = 0;
+            }
+        }
+        return $data;
+    }
+
+    private function getMemberData()
+    {
+        $data = [];
+        for ($i = 6; $i >= 0; $i--) {
+            $found = false;
+            foreach ($this->members as $member) {
+                if (date("Y-m-d", strtotime($member->created_at)) == date("Y-m-d", strtotime('-' . $i . ' days'))) {
+                    $data[] = $member->total;
+                    $found = true;
+                    break;
+                }
+            }
+            if (!$found) {
+                $data[] = 0;
+            }
+        }
+        return $data;
+    }
+}

+ 30 - 7
app/Http/Livewire/EmailComunications.php

@@ -27,6 +27,7 @@ class EmailComunications extends Component
 
     public string $mode = 'now'; // 'now' | 'schedule'
     public ?string $schedule_at = null;
+    public ?string $timezone = 'UTC';
 
     public $records;
     public $categories;
@@ -55,7 +56,7 @@ class EmailComunications extends Component
         $this->courses = [];
         $this->getCourses(\App\Models\Course::select('id', 'name')->where('parent_id', null)->orderBy('name', 'ASC')->get(), 0);
 
-        $this->schedule_at = now()->addHour()->format('Y-m-d\TH:i');
+        $this->schedule_at = now($this->timezone)->addHour()->format('Y-m-d\TH:i');
     }
 
     public function render()
@@ -89,7 +90,10 @@ class EmailComunications extends Component
 
     protected function validateDraft(): void
     {
-        $this->validate($this->baseRules());
+        $rules = [];
+        // $rules = $this->baseRules();
+        $rules['subject'] = 'required|string|max:255';
+        $this->validate($rules);
     }
 
     protected function validateSend(): void
@@ -108,7 +112,7 @@ class EmailComunications extends Component
     {
         $this->reset(['messageId', 'subject', 'content_html', 'recipients', 'newAttachments', 'mode', 'schedule_at']);
         $this->mode = 'now';
-        $this->schedule_at = now()->addHour()->format('Y-m-d\TH:i');
+        $this->schedule_at = now($this->timezone)->addHour()->format('Y-m-d\TH:i');
         $this->existingAttachments = [];
 
         $this->showForm = true;
@@ -136,8 +140,15 @@ class EmailComunications extends Component
                 'first_name'    => optional($r->member)->first_name,
                 'last_name'     => optional($r->member)->last_name,
             ])->toArray();
+            usort($this->recipients, function($a, $b) {
+                $last_name = strcmp($a['last_name'], $b['last_name']);
+                $first_name = strcmp($a['first_name'], $b['first_name']);
+
+                return $last_name == 0 ? $first_name : $last_name;
+            });
+
             $this->mode = $msg->status === 'scheduled' ? 'schedule' : 'now';
-            $this->schedule_at = optional($msg->schedule_at)?->format('Y-m-d\TH:i');
+            $this->schedule_at = optional($msg->schedule_at)?->setTimezone($this->timezone)?->format('Y-m-d\TH:i');
             $this->existingAttachments = $msg->attachments->map(fn($a) => [
                 'id'   => $a->id,
                 'name' => $a->name ?: basename($a->path),
@@ -189,6 +200,7 @@ class EmailComunications extends Component
 
         $this->success = 'Bozza salvata';
 
+        $this->dispatchBrowserEvent('scroll-top');
         $this->dispatchBrowserEvent('load-editor', [
             'html'   => $this->content_html ?? '',
             'locked' => $this->locked,
@@ -220,6 +232,7 @@ class EmailComunications extends Component
         dispatch(new \App\Jobs\SendEmailMessage($this->messageId));
         $this->success = 'Invio avviato';
 
+        $this->dispatchBrowserEvent('scroll-top');
         $this->dispatchBrowserEvent('load-editor', [
             'html'   => $this->content_html ?? '',
             'locked' => $this->locked,
@@ -239,8 +252,10 @@ class EmailComunications extends Component
             }
         }
 
-        DB::transaction(function () {
-            $msg = $this->upsertMessage(status: 'scheduled', scheduleAt: \Carbon\Carbon::parse($this->schedule_at));
+        $scheduledAt = \Carbon\Carbon::parse($this->schedule_at, $this->timezone)->setTimezone('UTC');
+
+        DB::transaction(function () use ($scheduledAt) {
+            $msg = $this->upsertMessage(status: 'scheduled', scheduleAt: $scheduledAt);
             $this->upsertRecipients($msg, true);
             $this->upsertAttachments($msg, true);
             $this->messageId = $msg->id;
@@ -250,6 +265,7 @@ class EmailComunications extends Component
 
         $this->success = 'Email programmata';
 
+        $this->dispatchBrowserEvent('scroll-top');
         $this->dispatchBrowserEvent('load-editor', [
             'html'   => $this->content_html ?? '',
             'locked' => $this->locked,
@@ -310,7 +326,7 @@ class EmailComunications extends Component
         $this->showForm = false;
         $this->reset(['messageId', 'subject', 'content_html', 'recipients', 'newAttachments', 'mode', 'schedule_at']);
         $this->mode = 'now';
-        $this->schedule_at = now()->addHour()->format('Y-m-d\TH:i');
+        $this->schedule_at = now($this->timezone)->addHour()->format('Y-m-d\TH:i');
 
         $this->dispatchBrowserEvent('init-archive-table');
     }
@@ -353,6 +369,13 @@ class EmailComunications extends Component
             'first_name' => $m->first_name,
             'last_name' => $m->last_name,
         ];
+
+        usort($this->recipients, function($a, $b) {
+            $last_name = strcmp($a['last_name'], $b['last_name']);
+            $first_name = strcmp($a['first_name'], $b['first_name']);
+
+            return $last_name == 0 ? $first_name : $last_name;
+        });
     }
 
     public function removeNewAttachment(int $index): void

+ 429 - 0
app/Http/Livewire/EmailComunications.php.bak

@@ -0,0 +1,429 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Livewire\WithFileUploads;
+use Illuminate\Support\Facades\DB;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Storage;
+
+use App\Http\Middleware\TenantMiddleware;
+use App\Models\EmailMessage;
+use App\Models\Member;
+
+class EmailComunications extends Component
+{
+    use WithFileUploads;
+
+    public ?int $messageId = null;
+    public string $subject = '';
+    public string $content_html = '';
+
+    public array $recipients = [];
+
+    public array $existingAttachments = [];
+    public $newAttachments = [];
+
+    public string $mode = 'now'; // 'now' | 'schedule'
+    public ?string $schedule_at = null;
+
+    public $records;
+    public $categories;
+    public $courses;
+
+    public bool $showForm = false;
+    public bool $locked = false;
+
+    public $success;
+    public $error;
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function mount()
+    {
+        if (auth()->user()?->level != env('LEVEL_ADMIN', 0)) {
+            return redirect()->to('/dashboard');
+        }
+
+        $this->categories = [];
+        $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
+
+        $this->courses = [];
+        $this->getCourses(\App\Models\Course::select('id', 'name')->where('parent_id', null)->orderBy('name', 'ASC')->get(), 0);
+
+        $this->schedule_at = now()->addHour()->format('Y-m-d\TH:i');
+    }
+
+    public function render()
+    {
+        if (!$this->showForm) {
+            $this->records = EmailMessage::withCount(['attachments', 'recipients'])
+                ->orderBy('created_at', 'desc')
+                ->get();
+        } else {
+            $this->categories = [];
+            $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
+
+            $this->courses = [];
+            $this->getCourses(\App\Models\Course::select('id', 'name')->where('parent_id', null)->orderBy('name', 'ASC')->get(), 0);
+        }
+
+        return view('livewire.email_comunications');
+    }
+
+    protected function baseRules(): array
+    {
+        return [
+            'subject' => 'required|string|max:255',
+            'content_html' => 'required|string',
+            'recipients' => 'required|array|min:1',
+            'recipients.*.email_address' => 'required|email',
+            'newAttachments' => 'nullable',
+            'newAttachments.*' => 'file|max:20480',
+        ];
+    }
+
+    protected function validateDraft(): void
+    {
+        $this->validate($this->baseRules());
+    }
+
+    protected function validateSend(): void
+    {
+        $this->validate($this->baseRules());
+    }
+
+    protected function validateSchedule(): void
+    {
+        $rules = $this->baseRules();
+        $rules['schedule_at'] = 'required|date|after:now';
+        $this->validate($rules);
+    }
+
+    public function add()
+    {
+        $this->reset(['messageId', 'subject', 'content_html', 'recipients', 'newAttachments', 'mode', 'schedule_at']);
+        $this->mode = 'now';
+        $this->schedule_at = now()->addHour()->format('Y-m-d\TH:i');
+        $this->existingAttachments = [];
+
+        $this->showForm = true;
+
+        $this->dispatchBrowserEvent('load-editor', [
+            'html'   => $this->content_html ?? '',
+            'locked' => $this->locked,
+        ]);
+        $this->dispatchBrowserEvent('init-recipients-table', [
+            'selected' => collect($this->recipients)->pluck('member_id')->filter()->values()->all(),
+        ]);
+    }
+
+    public function edit($id)
+    {
+        try {
+            $msg = EmailMessage::with(['recipients', 'attachments'])->findOrFail($id);
+
+            $this->messageId = $msg->id;
+            $this->subject = $msg->subject;
+            $this->content_html = $msg->content_html;
+            $this->recipients = $msg->recipients->map(fn($r) => [
+                'member_id' => $r->member_id,
+                'email_address' => $r->email_address,
+                'first_name'    => optional($r->member)->first_name,
+                'last_name'     => optional($r->member)->last_name,
+            ])->toArray();
+            $this->mode = $msg->status === 'scheduled' ? 'schedule' : 'now';
+            $this->schedule_at = optional($msg->schedule_at)?->format('Y-m-d\TH:i');
+            $this->existingAttachments = $msg->attachments->map(fn($a) => [
+                'id'   => $a->id,
+                'name' => $a->name ?: basename($a->path),
+                'size' => $a->size_human,
+                'url'  => $a->public_url,
+                'img'  => $a->is_image,
+            ])->toArray();
+
+            $this->showForm = true;
+            $this->locked = $msg->isLocked();
+
+            $this->dispatchBrowserEvent('load-editor', [
+                'html' => $this->content_html ?? '',
+                'locked' => $this->locked,
+            ]);
+            $this->dispatchBrowserEvent('init-recipients-table', [
+                'selected' => collect($this->recipients)->pluck('member_id')->filter()->values()->all(),
+            ]);
+        } catch (\Throwable $ex) {
+            $this->error = 'Errore (' . $ex->getMessage() . ')';
+        }
+    }
+
+
+    public function duplicate($id, $withRecipients = true)
+    {
+        try {
+            $copy = EmailMessage::with(['recipients', 'attachments'])->findOrFail($id)->duplicate($withRecipients);
+            $this->edit($copy->id);
+            $this->success = 'Bozza duplicata';
+        } catch (\Throwable $ex) {
+            $this->error = 'Errore (' . $ex->getMessage() . ')';
+        }
+    }
+
+    public function saveDraft($html = null)
+    {
+        if ($html !== null) $this->content_html = $html;
+        $this->validateDraft();
+
+        DB::transaction(function () {
+            $msg = $this->upsertMessage(status: 'draft', scheduleAt: null);
+            $this->upsertRecipients($msg);
+            $this->upsertAttachments($msg);
+            $this->messageId = $msg->id;
+            $this->locked = $msg->isLocked();
+            $this->refreshAttachments($msg);
+        });
+
+        $this->success = 'Bozza salvata';
+
+        $this->dispatchBrowserEvent('load-editor', [
+            'html'   => $this->content_html ?? '',
+            'locked' => $this->locked,
+        ]);
+    }
+
+    public function sendNow($html = null)
+    {
+        if ($html !== null) $this->content_html = $html;
+        $this->validateSend();
+
+        if ($this->messageId) {
+            $existing = EmailMessage::findOrFail($this->messageId);
+            if ($existing->isLocked()) {
+                $this->error = 'Questa email è già in invio o inviata e non può essere modificata.';
+                return;
+            }
+        }
+
+        DB::transaction(function () {
+            $msg = $this->upsertMessage(status: 'processing', scheduleAt: null);
+            $this->upsertRecipients($msg, true);
+            $this->upsertAttachments($msg, true);
+            $this->messageId = $msg->id;
+            $this->locked = true;
+            $this->refreshAttachments($msg);
+        });
+
+        dispatch(new \App\Jobs\SendEmailMessage($this->messageId));
+        $this->success = 'Invio avviato';
+
+        $this->dispatchBrowserEvent('load-editor', [
+            'html'   => $this->content_html ?? '',
+            'locked' => $this->locked,
+        ]);
+    }
+
+    public function scheduleMessage($html = null)
+    {
+        if ($html !== null) $this->content_html = $html;
+        $this->validateSchedule();
+
+        if ($this->messageId) {
+            $existing = EmailMessage::findOrFail($this->messageId);
+            if ($existing->isLocked()) {
+                $this->error = 'Questa email è già in invio o inviata e non può essere modificata.';
+                return;
+            }
+        }
+
+        DB::transaction(function () {
+            $msg = $this->upsertMessage(status: 'scheduled', scheduleAt: \Carbon\Carbon::parse($this->schedule_at));
+            $this->upsertRecipients($msg, true);
+            $this->upsertAttachments($msg, true);
+            $this->messageId = $msg->id;
+            $this->locked = $msg->isLocked();
+            $this->refreshAttachments($msg);
+        });
+
+        $this->success = 'Email programmata';
+
+        $this->dispatchBrowserEvent('load-editor', [
+            'html'   => $this->content_html ?? '',
+            'locked' => $this->locked,
+        ]);
+    }
+
+    protected function upsertMessage(string $status, $scheduleAt): EmailMessage
+    {
+        return EmailMessage::updateOrCreate(
+            ['id' => $this->messageId],
+            [
+                'subject'      => $this->subject,
+                'content_html' => $this->content_html,
+                'status'       => $status,
+                'schedule_at'  => $scheduleAt,
+                'created_by'   => auth()->id(),
+            ]
+        );
+    }
+
+    protected function upsertRecipients(EmailMessage $msg, bool $force = false): void
+    {
+        if (!$force && $msg->isLocked()) return;
+
+        $msg->recipients()->delete();
+
+        $rows = collect($this->recipients)->map(fn($r) => [
+            'email_message_id' => $msg->id,
+            'member_id' => $r['member_id'] ?? null,
+            'email_address' => $r['email_address'],
+            'status' => 'pending',
+            'created_at' => now(),
+            'updated_at' => now(),
+        ])->values()->all();
+
+        if ($rows) \App\Models\EmailMessageRecipient::insert($rows);
+    }
+
+    protected function upsertAttachments(EmailMessage $msg, bool $force = false): void
+    {
+        if (!$force && $msg->isLocked()) return;
+
+        $files = is_array($this->newAttachments) ? $this->newAttachments : [$this->newAttachments];
+        foreach ($files as $upload) {
+            if (!$upload) continue;
+            $path = $upload->store('emails/' . \Illuminate\Support\Str::uuid(), 'public');
+            $msg->attachments()->create([
+                'disk' => 'public',
+                'path' => $path,
+                'name' => $upload->getClientOriginalName(),
+                'size' => $upload->getSize(),
+            ]);
+        }
+    }
+
+    public function cancel()
+    {
+        $this->showForm = false;
+        $this->reset(['messageId', 'subject', 'content_html', 'recipients', 'newAttachments', 'mode', 'schedule_at']);
+        $this->mode = 'now';
+        $this->schedule_at = now()->addHour()->format('Y-m-d\TH:i');
+
+        $this->dispatchBrowserEvent('init-archive-table');
+    }
+
+    public function getCategories($records, $indentation)
+    {
+        foreach ($records as $record) {
+            $this->categories[] = array('id' => $record->id, 'name' => $record->getTree());
+            if (count($record->childs))
+                $this->getCategories($record->childs, $indentation + 1);
+        }
+    }
+
+    public function getCourses($records, $indentation)
+    {
+        /** @var \App\Models\Course $record */
+        foreach ($records as $record) {
+            $this->courses[] = array('id' => $record->id, 'name' => $record->getTree());
+            if (count($record->childs))
+                $this->getCourses($record->childs, $indentation + 1);
+        }
+    }
+
+    public function toggleRecipient($id)
+    {
+        $id = (int)$id;
+
+        $idx = collect($this->recipients)->search(fn($r) => (int)($r['member_id'] ?? 0) === $id);
+        if ($idx !== false) {
+            array_splice($this->recipients, $idx, 1);
+            return;
+        }
+
+        $m = Member::select('id', 'email', 'first_name', 'last_name')->find($id);
+        if (!$m || empty($m->email)) return;
+
+        $this->recipients[] = [
+            'member_id' => $m->id,
+            'email_address' => $m->email,
+            'first_name' => $m->first_name,
+            'last_name' => $m->last_name,
+        ];
+    }
+
+    public function removeNewAttachment(int $index): void
+    {
+        if ($this->locked) return;
+        if (is_array($this->newAttachments) && array_key_exists($index, $this->newAttachments)) {
+            array_splice($this->newAttachments, $index, 1);
+        }
+    }
+
+    public function removeExistingAttachment(int $id): void
+    {
+        if ($this->locked || !$this->messageId) return;
+
+        $att = \App\Models\EmailMessageAttachment::find($id);
+        if (!$att || $att->email_message_id !== $this->messageId) return;
+
+        try {
+            if ($att->disk && $att->path) {
+                $disk = Storage::disk($att->disk);
+                $dir = dirname($att->path);
+                $disk->delete($att->path);
+
+                if (empty($disk->files($dir)) && empty($disk->directories($dir))) {
+                    $disk->deleteDirectory($dir);
+                }
+            }
+        } catch (\Throwable $e) {
+        }
+
+        $att->delete();
+
+        $this->existingAttachments = array_values(array_filter(
+            $this->existingAttachments,
+            fn($a) => (int)$a['id'] !== (int)$id
+        ));
+    }
+
+
+    protected function refreshAttachments(EmailMessage $msg): void
+    {
+        $this->existingAttachments = $msg->attachments()
+            ->get()
+            ->map(fn($a) => [
+                'id'   => $a->id,
+                'name' => $a->name ?: basename($a->path),
+                'size' => $a->size_human,
+                'url'  => $a->public_url,
+                'img'  => $a->is_image,
+            ])->toArray();
+
+        $this->newAttachments = [];
+    }
+
+    public function deleteMessage(int $id)
+    {
+        $msg = \App\Models\EmailMessage::with(['attachments'])->findOrFail($id);
+
+        if (! in_array($msg->status, ['draft', 'failed'], true)) {
+            return;
+        }
+
+        foreach ($msg->attachments as $a) {
+            try {
+                Storage::disk($a->disk ?? 'public')->delete($a->path);
+            } catch (\Throwable $e) {
+            }
+        }
+
+        $msg->delete();
+
+        $this->dispatchBrowserEvent('email-deleted', ['id' => $id]);
+    }
+}

+ 290 - 80
app/Http/Livewire/Member.php

@@ -45,7 +45,7 @@ class Member extends Component
     public function change($type)
     {
         $this->type = $type;
-            if ($type === 'corsi' && $this->dataId > 0) {
+        if ($type === 'corsi' && $this->dataId > 0) {
             $this->loadMemberCards();
             $this->loadMemberCertificates();
             // TODO $this->checkCourseAvailability();
@@ -106,7 +106,7 @@ class Member extends Component
 
     public $course_names = array();
     public $course_levels = array();
-    public $course_types = array();
+    // public $course_types = array();
     public $course_frequencies = array();
 
     public $course_subscriptions = array();
@@ -164,6 +164,22 @@ class Member extends Component
         'birth_date' => 'before_or_equal:today'
     ];
 
+    /** Cambi Subscription */
+    public $member_subscriptions = [];
+
+    public $subscriptions;
+    public $subscription_exist;
+    public $subscription_subscriptions;
+
+    public $subscription_id;
+    public $subscription_course_subscription_id;
+    public $subscription_price;
+    public $subscription_subscription_price;
+    public $subscription_date_from;
+    public $subscription_date_to;
+    public $subscription_notes;
+    /** END - Cambi Subscription */
+
     public function resetFields()
     {
         $this->dataId = -1;
@@ -275,6 +291,21 @@ class Member extends Component
         $this->course_level_id = '';
         $this->course_type_id = '';
         $this->course_frequency_id = '';
+
+        $this->subscription_id = null;
+        $this->resetSubscriptionFields();
+    }
+
+    public function resetSubscriptionFields()
+    {
+        $this->subscription_exist = false;
+        $this->subscription_subscriptions = [];
+        $this->subscription_course_subscription_id = null;
+        $this->subscription_price = null;
+        $this->subscription_subscription_price = null;
+        $this->subscription_date_from = null;
+        $this->subscription_date_to = null;
+        $this->subscription_notes = null;
     }
 
     public function executeMultipleAction()
@@ -375,7 +406,6 @@ class Member extends Component
             }
 
             $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
-
         } else {
             $this->course_price = 0;
             $this->course_subscription_price = 0;
@@ -554,12 +584,15 @@ class Member extends Component
             $this->showDetailF($_GET["member_detail"]);
             $this->refreshAfter = 1;
         }
+
+        $this->subscription_id = null;
+        $this->subscriptions = \App\Models\Subscription::where('enabled', 1)->get();
     }
 
-    public function boot(){
+    public function boot()
+    {
         $this->fileService = app(\App\Services\MemberFileService::class);
         app(abstract: TenantMiddleware::class)->setupTenantConnection();
-
     }
 
     public function updated()
@@ -776,6 +809,45 @@ class Member extends Component
         }
     }
 
+    public function updatedSubscriptionId()
+    {
+        $this->resetSubscriptionFields();
+
+        if ($this->subscription_id > 0 && $this->subscription_id != 'course') {
+            $subscription = \App\Models\Subscription::findOrFail($this->subscription_id);
+            $this->subscription_subscription_price = formatPrice($subscription->subscription_price);
+
+            // Controllo se l'utente ha già l'abbonamento
+            $this->subscription_exist = \App\Models\MemberSubscription::where('subscription_id', $this->subscription_id)->where('member_id', $this->dataId)->count() > 0;
+
+            $subscription_subscription_ids = [];
+            if ($subscription->prices != null) {
+                foreach (json_decode($subscription->prices) as $z) {
+                    $subscription_subscription_ids[] = $z->course_subscription_id;
+                }
+            }
+            $this->subscription_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $subscription_subscription_ids)->where('enabled', true)->get();
+        } else {
+            $this->subscription_subscription_price = 0;
+            $this->subscription_exist = false;
+        }
+    }
+
+    public function updatedSubscriptionCourseSubscriptionId() {
+        if ($this->subscription_id > 0 && $this->subscription_id != 'course') {
+            $subscription = \App\Models\Subscription::findOrFail($this->subscription_id);
+
+            if ($subscription->prices != null) {
+                foreach (json_decode($subscription->prices) as $z) {
+                    if ($z->course_subscription_id == $this->subscription_course_subscription_id) {
+                        $this->subscription_price = $z->price;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
     public function loadMemberCards()
     {
         $this->member_cards = \App\Models\MemberCard::where('member_id', $this->dataId)->get();
@@ -784,11 +856,19 @@ class Member extends Component
     public function loadMemberCourses()
     {
         $this->member_courses = \App\Models\MemberCourse::where('member_id', $this->dataId)->get();
+        $this->member_subscriptions = \App\Models\MemberSubscription::where('member_id', $this->dataId)->get();
     }
 
     public function loadMemberCategories()
     {
-        $this->member_categories = \App\Models\MemberCategory::where('member_id', $this->dataId)->get();
+        $this->member_categories = [];
+        $categories = \App\Models\MemberCategory::with('category')->where('member_id', $this->dataId)->get();
+        foreach ($categories as $category) {
+            $this->member_categories[$category->category_id] = $category;
+        }
+        usort($this->member_categories, function ($a, $b) {
+            return strcmp($a->category->getTree(), $b->category->getTree());
+        });
     }
 
     public function loadMemberCertificates()
@@ -801,6 +881,14 @@ class Member extends Component
         if (!isset($_GET["from"]) && $this->from == '')
             $this->from = 'members';
         $this->currentMember = \App\Models\Member::findOrFail($id);
+        $categories = [];
+        foreach ($this->currentMember->categories as $category) {
+            $categories[$category->category_id] = $category;
+        }
+        usort($categories, function ($a, $b) {
+            return strcmp($a->category->getTree(), $b->category->getTree());
+        });
+        $this->currentMember->categories = $categories;
         $this->currentStatus = $this->currentMember->getStatus();
         $this->showDetail = true;
     }
@@ -895,7 +983,7 @@ class Member extends Component
             $rules['email'] = 'required';
             $rules['phone'] = 'required';
         }
-        
+
         $std_rules = $rules;
         // $rules = [
         //     'first_name' => 'required',
@@ -1184,15 +1272,15 @@ class Member extends Component
                 $this->age = $interval->y . " anni";
                 $this->under18 = $interval->y < 18;
                 $this->update = true;
-                $this->add = false;                
+                $this->add = false;
 
                 $this->emit('setIds', $this->nation_id, $this->birth_nation_id);
 
                 $this->emit('load-select');
-                
+
                 $this->emit('load-provinces', $this->nation_id, 'provinceClass');
                 $this->emit('load-provinces', $this->birth_nation_id, 'provinceBirthClass');
-                
+
                 $this->emit('load-cities', $this->province_id, 'cityClass');
                 $this->emit('load-cities', $this->birth_province_id, 'cityBirthClass');
 
@@ -1272,7 +1360,7 @@ class Member extends Component
             $rules['phone'] = 'required';
         }
 
-        
+
         $std_rules = $rules;
         // $rules = [
         //     'first_name' => 'required',
@@ -1653,19 +1741,32 @@ class Member extends Component
             $mc->save();
 
             // Se il corso ha associato una categoria iscrivo anche al gruppo
-            $c = \App\Models\Course::findOrFail($this->course_course_id);
-            if ($c) {
-                if ($c->category_id > 0) {
-                    \App\Models\MemberCategory::create([
-                        'member_id' => $this->dataId,
-                        'category_id' => $c->category_id,
-                        'date' => \Carbon\Carbon::now()
-                    ]);
+            if ($this->course_course_id) {
+                try {
+                    $c = \App\Models\Course::findOrFail($this->course_course_id);
+                    if ($c) {
+                        if ($c->category_id > 0) {
+                            if (!\App\Models\MemberCategory::where('member_id', $this->dataId)->where('category_id', $c->category_id)->first()) {
+                                \App\Models\MemberCategory::create([
+                                    'member_id' => $this->dataId,
+                                    'category_id' => $c->category_id,
+                                    'date' => \Carbon\Carbon::now()
+                                ]);
+                            }
+                        }
+                    }
+                } catch (\Throwable $e) {
                 }
             }
 
             // Creo i pagamenti in base alla tipologia
-            $r = \App\Models\CourseSubscription::findOrFail($this->course_course_subscription_id);
+            if ($this->course_course_subscription_id) {
+                try {
+                    $r = \App\Models\CourseSubscription::findOrFail($this->course_course_subscription_id);
+                } catch (\Throwable $e) {
+                    $r = new \App\Models\CourseSubscription();
+                }
+            }
 
             $start = $this->course_date_from;
 
@@ -1687,12 +1788,9 @@ class Member extends Component
                 $mms = [];
                 $mms[] = date("n", strtotime($start));
                 for ($jj = 1; $jj < $r->months; $jj++) {
-                    if (date('Ymd', strtotime("+" . $jj . " months", strtotime($start))) < date("Ymd", strtotime($this->course_date_to)))
-                    {
+                    if (date('Ymd', strtotime("+" . $jj . " months", strtotime($start))) < date("Ymd", strtotime($this->course_date_to))) {
                         $mms[] = date('n', strtotime("+" . $jj . " months", strtotime($start)));
-                    }
-                    else
-                    {
+                    } else {
                         break;
                     }
                 }
@@ -1710,7 +1808,7 @@ class Member extends Component
 
                 $start = date('Y-m-d', strtotime("+" . $r->months . " months", strtotime($start)));
 
-                if ($start > $this->course_date_to) {
+                if ($start > $this->course_date_to || $r->months == 0) {
                     $go = false;
                     break;
                 }
@@ -1753,7 +1851,7 @@ class Member extends Component
                 foreach ($all as $a) {
                     $types_ids[] = $a->course_type_id;
                 }
-                $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->whereIn('id', $types_ids)->get();
+                // $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->whereIn('id', $types_ids)->get();
 
                 $frequencies_ids = [];
                 $all = \App\Models\Course::where('name', 'like', '%' . $c->name . "%")->where('enabled', true)->where('course_level_id', $this->course_level_id)->where('course_type_id', $this->course_type_id)->get();
@@ -1790,10 +1888,13 @@ class Member extends Component
                 }
 
                 $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
-                
+
                 $this->courseDataId = $memberCourse->id;
                 $this->updateCourse = true;
                 $this->addCourse = false;
+
+                $this->subscription_id = 'course';
+
                 $this->emit('setEditCorso', true);
             }
         } catch (\Exception $ex) {
@@ -1841,13 +1942,172 @@ class Member extends Component
     public function deleteCourse($id)
     {
         try {
-            \App\Models\MemberCourse::find($id)->delete();
+            $member_course = \App\Models\MemberCourse::find($id);
+
+            $category = \App\Models\Course::findOrFail($member_course->course_id);
+            if ($category) {
+                if ($category->category_id > 0) {
+                    \App\Models\MemberCategory::where('member_id', $this->dataId)->where('category_id', $category->category_id)->delete();
+                }
+            }
+
+            $member_course->delete();
             session()->flash('success', "Corso eliminato");
         } catch (\Exception $e) {
             session()->flash('error', 'Errore (' . $e->getMessage() . ')');
         }
     }
 
+    public function storeSubscription()
+    {
+        $this->validate(['subscription_id' => 'required']);
+        try {
+            $ms = new \App\Models\MemberSubscription();
+            $ms->member_id = $this->dataId;
+            $ms->subscription_id = $this->subscription_id;
+            $ms->date_from = $this->subscription_date_from;
+            $ms->date_to = $this->subscription_date_to;
+            $ms->course_subscription_id = $this->subscription_course_subscription_id;
+            $ms->price = currencyToDouble($this->subscription_price);
+            $ms->subscription_price = currencyToDouble($this->subscription_subscription_price);
+            $ms->notes = $this->subscription_notes;
+            $ms->save();
+
+            // Creo i pagamenti in base alla tipologia
+            if ($this->subscription_course_subscription_id) {
+                try {
+                    $r = \App\Models\CourseSubscription::findOrFail($this->subscription_course_subscription_id);
+                } catch (\Throwable $e) {
+                    $r = new \App\Models\CourseSubscription();
+                }
+            }
+
+            $start = $this->subscription_date_from;
+
+            // Creo il pagamento per l'iscrizione
+            $rate = new \App\Models\Rate();
+            $rate->member_id = $this->dataId;
+            $rate->member_subscription_id = $ms->id;
+            $rate->price = currencyToDouble($this->subscription_subscription_price);
+            $rate->date = $start;
+            $rate->months = json_encode([]);
+            $rate->note = '';
+            $rate->status = 0;
+            $rate->is_subscription = true;
+            $rate->save();
+            $go = true;
+            while ($go) {
+
+                $mms = [];
+                $mms[] = date("n", strtotime($start));
+                for ($jj = 1; $jj < $r->months; $jj++) {
+                    if (date('Ymd', strtotime("+" . $jj . " months", strtotime($start))) < date("Ymd", strtotime($this->subscription_date_to))) {
+                        $mms[] = date('n', strtotime("+" . $jj . " months", strtotime($start)));
+                    } else {
+                        break;
+                    }
+                }
+
+                $rate = new \App\Models\Rate();
+                $rate->member_id = $this->dataId;
+                $rate->member_subscription_id = $ms->id;
+                $rate->course_subscription_id = $this->subscription_course_subscription_id;
+                $rate->price = currencyToDouble($this->subscription_price);
+                $rate->date = $start;
+                $rate->months = json_encode($mms);
+                $rate->note = '';
+                $rate->status = 0;
+                $rate->save();
+
+                $start = date('Y-m-d', strtotime("+" . $r->months . " months", strtotime($start)));
+
+                if ($start > $this->subscription_date_to || $r->months == 0) {
+                    $go = false;
+                    break;
+                }
+            }
+
+            session()->flash('success, Abbonamento creato');
+            $this->resetCourseFields();
+            $this->addCourse = false;
+            $this->emit('setEditCorso', false);
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function editSubscription($id)
+    {
+        try {
+            $memberSubscription = \App\Models\MemberSubscription::findOrFail($id);
+            if (!$memberSubscription) {
+                session()->flash('error', 'Abbonamento non trovato');
+            } else {
+                $this->subscription_id = $memberSubscription->subscription_id;
+
+                $subscription = \App\Models\Subscription::findOrFail($this->subscription_id);
+
+                $this->subscription_date_from = $memberSubscription->date_from;
+                $this->subscription_date_to = $memberSubscription->date_to;
+                $this->subscription_course_subscription_id = $memberSubscription->course_subscription_id;
+                $this->subscription_price = formatPrice($memberSubscription->price);
+                $this->subscription_subscription_price = formatPrice($memberSubscription->subscription_price);
+                $this->subscription_notes = $memberSubscription->notes;
+
+                $subscription_subscription_ids = [];
+                if ($subscription->prices != null) {
+                    foreach (json_decode($subscription->prices) as $z) {
+                        $subscription_subscription_ids[] = $z->course_subscription_id;
+                    }
+                }
+
+                $this->subscription_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $subscription_subscription_ids)->where('enabled', true)->get();
+
+                $this->courseDataId = $memberSubscription->id;
+                $this->updateCourse = true;
+                $this->addCourse = false;
+
+                $this->emit('setEditCorso', true);
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function updateSubscription()
+    {
+        $this->validate(['subscription_id' => 'required']);
+        try {
+            \App\Models\MemberSubscription::whereId($this->courseDataId)->update([
+                'member_id' => $this->dataId,
+                'subscription_id' => $this->subscription_id,
+                'date_from' => $this->subscription_date_from,
+                'date_to' => $this->subscription_date_to,
+                'course_subscription_id' => $this->subscription_course_subscription_id,
+                'price' => currencyToDouble($this->subscription_price),
+                'subscription_price' => currencyToDouble($this->subscription_subscription_price),
+                'notes' => $this->subscription_notes,
+            ]);
+            session()->flash('success', 'Abbonamento aggiornato');
+            $this->resetCourseFields();
+            $this->updateCourse = false;
+            $this->emit('setEditCorso', false);
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function deleteSubscription($id)
+    {
+        try {
+            $member_subscription = \App\Models\MemberSubscription::find($id);
+            $member_subscription->delete();
+            session()->flash('success', "Abbonamento eliminato");
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+    }
+
     public function addRow()
     {
         $this->course_when[] = array('day' => array(), 'from' => '', 'to' => '');
@@ -2003,7 +2263,7 @@ class Member extends Component
         $this->groupMsg = '';
         try {
             if (\App\Models\MemberCategory::where('member_id', $this->dataId)->where('category_id', $id)->first()) {
-                $this->groupMsg = '<br>Attenzione, questo corso è stato già inserito';
+                $this->groupMsg = '<br>Attenzione, l\'utente fa già parte di questo gruppo';
             } else {
                 \App\Models\MemberCategory::create([
                     'member_id' => $this->dataId,
@@ -2385,58 +2645,9 @@ class Member extends Component
         try {
             $member = \App\Models\Member::findOrFail($id);
 
-            $originalFirstName = $member->first_name;
-            $originalLastName = $member->last_name;
-
             $member->update([
                 'is_archived' => true,
                 'archived_date' => now(),
-
-                'first_name' => 'Nome Archiviato',
-                'last_name' => 'Cognome Archiviato',
-
-                'fiscal_code' => null,
-                'email' => null,
-                'phone' => null,
-                'phone2' => null,
-                'phone3' => null,
-                'address' => null,
-                'zip_code' => null,
-
-                'birth_place' => null,
-                'birth_date' => null,
-                'birth_city_id' => null,
-                'birth_province_id' => null,
-                'birth_nation_id' => null,
-
-                'nation_id' => null,
-                'province_id' => null,
-                'city_id' => null,
-
-                'document_type' => null,
-                'document_number' => null,
-                'document_from' => null,
-                'document_expire_date' => null,
-                'document_files' => null,
-
-                'father_name' => null,
-                'mother_name' => null,
-                'father_email' => null,
-                'mother_email' => null,
-                'father_phone' => null,
-                'mother_phone' => null,
-                'father_fiscal_code' => null,
-                'mother_fiscal_code' => null,
-                'father_doc_number' => null,
-                'father_doc_type' => null,
-                'mother_doc_number' => null,
-                'mother_doc_type' => null,
-                'father_document_files' => null,
-                'mother_document_files' => null,
-
-                'image' => null,
-
-                'gender' => null,
                 'status' => 'archived',
 
                 'enabled' => false
@@ -2447,7 +2658,6 @@ class Member extends Component
             session()->flash('success', 'Membro archiviato con successo');
 
             $this->emit('reload');
-
         } catch (\Exception $e) {
             session()->flash('error', 'Errore durante l\'archiviazione: ' . $e->getMessage());
             Log::error('Archive member error', [

+ 2889 - 0
app/Http/Livewire/Member.php.bak

@@ -0,0 +1,2889 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Livewire\Attributes\Url;
+use Livewire\WithFileUploads;
+use Livewire\WithPagination;
+use Illuminate\Support\Facades\Log;
+use App\Http\Middleware\TenantMiddleware;
+
+use DateTime;
+
+class Member extends Component
+{
+
+    use WithPagination;
+    protected $paginationTheme = 'bootstrap';
+
+    protected $listeners = ['storeCategoryWithID' => 'storeCategoryWithID',  'setCourse' => 'setCourse', 'hideMsg' => 'hideMsg'];
+
+    use WithFileUploads;
+
+    public $sortField = 'id';
+    public $sortAsc = false;
+
+    public $groupMsg = '';
+
+    public $type = 'dati';
+
+    public $from = '';
+
+    public $selectedCourseMember = 0;
+
+    protected $messages = [
+        'birth_nation_id.required' => 'birth_nation_id',
+        'birth_province_id.required' => 'birth_province_id',
+        'birth_city_id.required' => 'birth_city_id',
+        'nation_id.required' => 'nation_id',
+        'province_id.required' => 'province_id',
+        'city_id.required' => 'city_id',
+        'gender' => 'gender'
+    ];
+
+    public function change($type)
+    {
+        $this->type = $type;
+            if ($type === 'corsi' && $this->dataId > 0) {
+            $this->loadMemberCards();
+            $this->loadMemberCertificates();
+            // TODO $this->checkCourseAvailability();
+        }
+
+        $this->dispatchBrowserEvent('scroll-to-top');
+    }
+
+    public function sortBy($field)
+    {
+        if ($this->sortField === $field) {
+            $this->sortAsc = ! $this->sortAsc;
+        } else {
+            $this->sortAsc = true;
+        }
+
+        $this->sortField = $field;
+    }
+
+    public $records, $first_name, $last_name, $image, $image_old, $status, $birth_place, $birth_city_id, $birth_province_id, $birth_nation_id, $birth_date, $gender, $no_send_mail, $exclude_from_records, $fiscal_code, $address, $zip_code, $nation_id, $province_id, $city_id, $phone, $phone2, $phone3, $email, $enabled, $dataId, $update = false, $add = false;
+
+    public $isSaving = false;
+
+    public $importoBorsellino, $father_name, $mother_name, $father_email, $mother_email, $father_phone, $mother_phone, $father_fiscal_code, $mother_fiscal_code, $father_doc_number, $father_doc_type, $mother_doc_number, $mother_doc_type, $mother_document_files, $father_document_files;
+
+    public $document_type, $document_number, $document_from, $document_expire_date, $document_files, $document_files_old;
+
+    public $selectId = 0;
+
+    public $causalId = 0;
+
+    public $error_fc = false;
+
+    public $under18 = false;
+
+    public $money = 0;
+
+    public $refreshAfter = 0;
+
+    public $checkedAll = false;
+    public $multipleIds = [];
+    public $multipleAction = '';
+
+    public $isItaly = true;
+    public $isBirthItaly = true;
+
+    public $showDetail = false;
+
+    public $currentMember;
+    public $currentStatus;
+
+    public $age = '';
+
+    public $disciplines = array();
+    public $cards = array();
+    public $categories = array();
+    public $courses = array();
+
+    public $course_names = array();
+    public $course_levels = array();
+    public $course_types = array();
+    public $course_frequencies = array();
+
+    public $course_subscriptions = array();
+
+    public $createSubscription = false;
+    public $payMonths = array();
+    public $suspendedMonths = array();
+    public $showCourse = array();
+
+    public $searchTxt;
+    public $search;
+    public $showReset = false;
+
+    public $advanced = false;
+
+    public $birthNations = array();
+    public $birthProvinces = array();
+    public $birthCities = array();
+
+    public $active;
+
+    // Card data
+    public $member_cards = array(), $card_card_id, $card_number, $card_date, $card_accept_date, $card_status, $card_discipline1_id, $card_discipline2_id, $card_discipline3_id, $addCard, $updateCard, $cardDataId;
+
+    // Categories data
+    public $member_categories = array(), $category_category_id;
+
+    // Courses data
+    public $member_courses = array(), $course_course_id, $course_course_type, $course_date_from, $course_date_to, $course_when = array(), $addCourse, $updateCourse, $courseDataId, $course_course_subscription_id, $course_status, $course_note, $course_price, $course_subscription_price, $course_subscribed, $course_months = array(), $course_exist;
+
+    public $course_months_list = [];
+    public $course_price_list = [];
+
+    public $course_name, $course_level_id, $course_type_id, $course_frequency_id;
+
+    // Certificates data
+    public $member_certificates = array(), $certificate_type, $certificate_filename_old, $certificate_filename, $certificate_expire_date, $certificate_status, $addCertificate, $updateCertificate, $certificateDataId, $cardCertificateId;
+
+    public $filterCard = [];
+    public $filterCategory = [];
+    public $filterCertNormal = 0;
+    public $filterCertAgonistic = 0;
+    public $filterCertScaduto = 0;
+    public $filterCertInScadenza = 0;
+    public $already_existing = false;
+    private $fileService;
+
+    public $hasCertificate = false;
+
+    protected $rules = [
+        'first_name' => 'required',
+        'last_name' => 'required',
+        // 'email' => 'required',
+        // 'phone' => 'required',
+        'birth_date' => 'before_or_equal:today'
+    ];
+
+    public function resetFields()
+    {
+        $this->dataId = -1;
+        $this->first_name = '';
+        $this->last_name = '';
+        $this->status = '';
+        $this->birth_city_id = null;
+
+        $this->birth_province_id = null;
+        $this->birth_nation_id = null;
+
+        $this->birth_date = null;
+        $this->birth_place = '';
+        $this->importoBorsellino = 0;
+        $this->father_name = '';
+        $this->mother_name = '';
+        $this->father_email = '';
+        $this->mother_email = '';
+        $this->father_phone = '';
+        $this->mother_phone = '';
+        $this->father_fiscal_code = '';
+        $this->mother_fiscal_code = '';
+        $this->father_doc_number = '';
+        $this->mother_doc_number = '';
+        $this->father_doc_type = '';
+        $this->mother_doc_type = '';
+        $this->mother_document_files = array();
+        $this->father_document_files = array();
+
+        $this->gender = null;
+        $this->no_send_mail = false;
+        $this->exclude_from_records = false;
+        $this->fiscal_code = '';
+        $this->address = '';
+        $this->zip_code = '';
+
+        $this->document_type = '';
+        $this->document_number = '';
+        $this->document_from = '';
+        $this->document_expire_date = null;
+        $this->document_files = array();
+
+        //$this->nation_id = null;
+        //$this->province_id = null;
+        $this->nation_id = null;
+        $this->province_id = null;
+
+        $this->city_id = null;
+        $this->phone = '';
+        $this->phone2 = '';
+        $this->phone3 = '';
+        $this->email = '';
+        $this->enabled = true;
+        $this->under18 = false;
+        $this->hasCertificate = false;
+        $this->money = 0;
+        $this->image = null;
+
+        $this->age = '';
+
+        $this->error_fc = false;
+        $this->already_existing = false;
+
+        $this->isSaving = false;
+
+        $this->emit('load-data-table');
+    }
+
+    public function resetCardFields()
+    {
+        $this->card_card_id = null;
+        $this->card_number = '';
+        $this->card_date = null;
+        $this->card_accept_date = null;
+        $this->card_status = 0;
+        $this->card_discipline1_id = null;
+        $this->card_discipline2_id = null;
+        $this->card_discipline3_id = null;
+    }
+
+    public function resetCertificateFields()
+    {
+        $this->certificate_type = '';
+        $this->certificate_filename = '';
+        $this->certificate_filename_old = '';
+        $this->certificate_expire_date = date('Y-m-d', strtotime('+1 year'));
+        $this->certificate_status = 0;
+    }
+
+    public function resetCourseFields()
+    {
+        $this->course_course_id = null;
+        $this->course_course_type = 'standard';
+        $this->course_months_list = [];
+        $this->course_price_list = [];
+        $this->course_when = array();
+        $this->course_when[] = array('day' => array(), 'from' => '', 'to' => '');
+        $this->course_date_from = null;
+        $this->course_date_to = null;
+        $this->course_course_subscription_id = null;
+        $this->course_status = 0;
+        $this->course_price = 0;
+        $this->course_subscription_price = 0;
+        $this->course_exist = false;
+        $this->course_subscribed = false;
+        $this->course_months = array();
+        $this->course_note = '';
+        $this->course_name = '';
+        $this->course_level_id = '';
+        $this->course_type_id = '';
+        $this->course_frequency_id = '';
+    }
+
+    public function executeMultipleAction()
+    {
+
+        if ($this->multipleAction == 'delete')
+            $this->multipleDelete();
+    }
+
+    public function updatedImage()
+    {
+        $this->validate([
+            'image' => 'image|max:1024',
+        ]);
+        $this->image_old = '';
+    }
+
+    public function updatedCourseName()
+    {
+        $this->course_course_id = null;
+        $this->course_level_id = '';
+        $this->course_frequency_id = '';
+        $levels_ids = [];
+        if ($this->course_name != '') {
+            list($n, $y) = explode("(", $this->course_name);
+            $y = trim(str_replace(")", "", $y));
+            $all = \App\Models\Course::where('name', 'like', '%' . trim($n) . "%")->where('year', $y)->where('enabled', true)->get();
+            foreach ($all as $a) {
+                $levels_ids[] = $a->course_level_id;
+            }
+        }
+        $this->course_levels = \App\Models\CourseLevel::select('*')->where('enabled', true)->whereIn('id', $levels_ids)->get();
+        $this->course_frequencies = [];
+    }
+
+    public function updatedCourseLevelId()
+    {
+        $this->course_course_id = null;
+        $this->course_frequency_id = '';
+        $frequencies_ids = [];
+        if ($this->course_level_id != '') {
+            list($n, $y) = explode("(", $this->course_name);
+            $y = trim(str_replace(")", "", $y));
+            $all = \App\Models\Course::where('name', 'like', '%' . trim($n) . "%")->where('year', $y)->where('enabled', true)->where('course_level_id', $this->course_level_id)->get();
+            foreach ($all as $a) {
+                $frequencies_ids[] = $a->course_frequency_id;
+            }
+        }
+        $this->course_frequencies = \App\Models\CourseFrequency::select('*')->where('enabled', true)->whereIn('id', $frequencies_ids)->get();
+    }
+
+    /*
+    public function updatedCourseTypeId()
+    {
+        $this->course_course_id = null;
+        $this->course_frequency_id = '';
+        $frequencies_ids = [];
+        if ($this->course_type_id != '') {
+            list($n, $y) = explode("(", $this->course_name);
+            $y = trim(str_replace(")", "", $y));
+            $all = \App\Models\Course::where('name', 'like', '%' . trim($n) . "%")->where('year', $y)->where('enabled', true)->where('course_level_id', $this->course_level_id)->where('course_type_id', $this->course_type_id)->get();
+            foreach ($all as $a) {
+                $frequencies_ids[] = $a->course_frequency_id;
+            }
+        }
+        $this->course_frequencies = \App\Models\CourseFrequency::select('*')->where('enabled', true)->whereIn('id', $frequencies_ids)->get();
+    }*/
+
+    public function updatedCourseFrequencyId()
+    {
+        $this->course_course_id = null;
+        if ($this->course_frequency_id != '') {
+            list($n, $y) = explode("(", $this->course_name);
+            $y = trim(str_replace(")", "", $y));
+            $this->course_course_id = \App\Models\Course::where('name', 'like', '%' . trim($n) . "%")->where('year', $y)->where('course_level_id', $this->course_level_id)->where('course_frequency_id', $this->course_frequency_id)->first()->id;
+
+            $c = \App\Models\Course::findOrFail($this->course_course_id);
+            $this->course_price = formatPrice($c->price);
+            $this->course_subscription_price = formatPrice($c->subscription_price);
+            $this->course_date_from = $c->date_from;
+            $this->course_date_to = $c->date_to;
+
+            // Carico gli abbonamenti e i mesi
+            $period = \Carbon\CarbonPeriod::create($c->date_from, '1 month', $c->date_to);
+            $this->course_months_list = [];
+            foreach ($period as $dt) {
+                $this->course_months_list[] = $dt->format("m");
+            }
+
+            $course_subscription_ids = [];
+            $this->course_price_list = [];
+            if ($c->prices != null) {
+                foreach (json_decode($c->prices) as $z) {
+                    $this->course_price_list[$z->course_subscription_id] = $z->price;
+                    if ($z->price > 0)
+                        $course_subscription_ids[] = $z->course_subscription_id;
+                }
+            }
+
+            $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
+
+        } else {
+            $this->course_price = 0;
+            $this->course_subscription_price = 0;
+            $this->course_exist = false;
+        }
+    }
+
+    public function updatedCourseCourseType()
+    {
+        $this->course_level_id = '';
+        $this->course_frequency_id = '';
+        $this->course_course_id = null;
+    }
+
+    public function updatedCourseCourseSubscriptionId()
+    {
+        $this->course_price = 0;
+        if (isset($this->course_price_list[$this->course_course_subscription_id]))
+            $this->course_price = $this->course_price_list[$this->course_course_subscription_id];
+    }
+
+    public $documents = [];
+
+    public $father_documents = [];
+
+    public $mother_documents = [];
+
+
+    public function removeDocument($idx, $type)
+    {
+        try {
+            if ($type == 'father') {
+                if (isset($this->father_document_files[$idx])) {
+                    $filePath = $this->father_document_files[$idx];
+                    $this->fileService->deleteFile($filePath);
+                    unset($this->father_document_files[$idx]);
+                    $this->father_document_files = array_values($this->father_document_files);
+                }
+            } elseif ($type == 'mother') {
+                if (isset($this->mother_document_files[$idx])) {
+                    $filePath = $this->mother_document_files[$idx];
+                    $this->fileService->deleteFile($filePath);
+                    unset($this->mother_document_files[$idx]);
+                    $this->mother_document_files = array_values($this->mother_document_files);
+                }
+            } else {
+                if (isset($this->document_files[$idx])) {
+                    $filePath = $this->document_files[$idx];
+                    $this->fileService->deleteFile($filePath);
+                    unset($this->document_files[$idx]);
+                    $this->document_files = array_values($this->document_files);
+                }
+            }
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error removing document: ' . $e->getMessage());
+        }
+    }
+
+
+    public function updatedDocuments()
+    {
+        try {
+            foreach ($this->documents as $document) {
+                if ($this->dataId > 0) {
+                    $s3Path = $this->fileService->uploadDocument($document, $this->dataId, 'self');
+                    $this->document_files[] = $s3Path;
+                } else {
+                    $name = $document->getClientOriginalName();
+                    $document->storeAs('public', $name);
+                    $this->document_files[] = $name;
+                }
+            }
+            $this->documents = [];
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error uploading documents: ' . $e->getMessage());
+        }
+    }
+
+    public function updatedFatherDocuments()
+    {
+        try {
+            foreach ($this->father_documents as $document) {
+                if ($this->dataId > 0) {
+                    $s3Path = $this->fileService->uploadDocument($document, $this->dataId, 'father');
+                    $this->father_document_files[] = $s3Path;
+                } else {
+                    $name = $document->getClientOriginalName();
+                    $document->storeAs('public', $name);
+                    $this->father_document_files[] = $name;
+                }
+            }
+            $this->father_documents = [];
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error uploading father documents: ' . $e->getMessage());
+        }
+    }
+
+    public function updatedMotherDocuments()
+    {
+        try {
+            foreach ($this->mother_documents as $document) {
+                if ($this->dataId > 0) {
+                    $s3Path = $this->fileService->uploadDocument($document, $this->dataId, 'mother');
+                    $this->mother_document_files[] = $s3Path;
+                } else {
+                    $name = $document->getClientOriginalName();
+                    $document->storeAs('public', $name);
+                    $this->mother_document_files[] = $name;
+                }
+            }
+            $this->mother_documents = [];
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error uploading mother documents: ' . $e->getMessage());
+        }
+    }
+    public function resetCategoryFields()
+    {
+        $this->category_category_id = null;
+    }
+
+    public function getCategories($records, $indentation)
+    {
+        foreach ($records as $record) {
+            // $this->categories[] = array('id' => $record->id, 'name' => str_repeat(" / ", $indentation) . $record->name);
+            $this->categories[] = array('id' => $record->id, 'name' => $record->getTree());
+            if (count($record->childs))
+                $this->getCategories($record->childs, $indentation + 1);
+        }
+    }
+
+    public function getCourses($records, $indentation)
+    {
+        foreach ($records as $record) {
+            // $this->categories[] = array('id' => $record->id, 'name' => str_repeat(" / ", $indentation) . $record->name);
+            $this->courses[] = array('id' => $record->id, 'name' => $record->getTree());
+            if (count($record->childs))
+                $this->getCourses($record->childs, $indentation + 1);
+        }
+    }
+
+    public function updatedBirthDate()
+    {
+        $this->validateOnly('birth_date');
+        $date1 = new DateTime($this->birth_date);
+        $date2 = new DateTime("now");
+        $interval = $date1->diff($date2);
+        $this->age = $interval->y . " anni";
+        $this->under18 = $interval->y < 18;
+    }
+
+    public function mount()
+    {
+
+        if (isset($_GET["new"])) {
+            $this->refreshAfter = 1;
+            $this->add();
+        }
+
+        if (isset($_GET["from"])) {
+            $this->from = $_GET["from"];
+        }
+
+        $this->cards = \App\Models\Card::select('id', 'name')->get();
+
+        $this->disciplines = \App\Models\Discipline::select('id', 'name')->get();
+
+        $this->categories = array();
+
+        $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
+
+        $c = \App\Models\Causal::where('type', 'IN')->where('money', true)->first();
+        if ($c)
+            $this->causalId = $c->id;
+
+        if (isset($_GET["member_detail"])) {
+            $this->showDetailF($_GET["member_detail"]);
+            $this->refreshAfter = 1;
+        }
+    }
+
+    public function boot(){
+        $this->fileService = app(\App\Services\MemberFileService::class);
+        app(abstract: TenantMiddleware::class)->setupTenantConnection();
+
+    }
+
+    public function updated()
+    {
+        if ($this->isSaving) {
+            $zzz = [];
+            if ($this->birth_nation_id == null)
+                $zzz[] = 'birth_nation_id=' . $this->birth_nation_id;
+            if ($this->isBirthItaly) {
+                if ($this->birth_province_id == null)
+                    $zzz[] = 'birth_province_id=' . $this->birth_province_id;
+                if ($this->birth_city_id == null)
+                    $zzz[] = 'birth_city_id=' . $this->birth_city_id;
+            }
+            if ($this->nation_id == null)
+                $zzz[] = 'nation_id=' . $this->nation_id;
+            if ($this->isItaly) {
+                if ($this->province_id == null)
+                    $zzz[] = 'province_id=' . $this->province_id;
+                if ($this->city_id == null)
+                    $zzz[] = 'city_id=' . $this->city_id;
+            }
+
+            $this->emit('setErrorMsg', $zzz);
+        }
+        // $this->emit('load-select');
+    }
+
+    public function hydrate()
+    {
+
+        if ($this->isSaving) {
+            $zzz = [];
+            if ($this->birth_nation_id == null)
+                $zzz[] = 'birth_nation_id=' . $this->birth_nation_id;
+            if ($this->isBirthItaly) {
+                if ($this->birth_province_id == null)
+                    $zzz[] = 'birth_province_id=' . $this->birth_province_id;
+                if ($this->birth_city_id == null)
+                    $zzz[] = 'birth_city_id=' . $this->birth_city_id;
+            }
+            if ($this->nation_id == null)
+                $zzz[] = 'nation_id=' . $this->nation_id;
+            if ($this->isItaly) {
+                if ($this->province_id == null)
+                    $zzz[] = 'province_id=' . $this->province_id;
+                if ($this->city_id == null)
+                    $zzz[] = 'city_id=' . $this->city_id;
+            }
+
+            $this->emit('setErrorMsg', $zzz);
+        }
+
+        $this->emit('load-select');
+        // $this->emit('destroy-data-table');
+
+    }
+
+    public function checkIsItaly()
+    {
+        if ($this->nation_id > 0) {
+            $n = \App\Models\Nation::findOrFail($this->nation_id);
+            $this->isItaly = $n->is_italy;
+        } else {
+            $this->isItaly = false;
+        }
+    }
+
+    public function checkIsBirthItaly()
+    {
+        if ($this->birth_nation_id > 0) {
+            $n = \App\Models\Nation::findOrFail($this->birth_nation_id);
+            $this->isBirthItaly = $n->is_italy;
+        } else
+            $this->isBirthItaly = false;
+    }
+    public function search()
+    {
+        if ($this->searchTxt != '') {
+            $this->search = $this->searchTxt;
+            $this->showReset = true;
+        }
+    }
+    public function resetSearch()
+    {
+        $this->showReset = false;
+        $this->searchTxt = '';
+        $this->search = $this->searchTxt;
+    }
+
+    public function advancedSearch()
+    {
+        $this->advanced = true;
+    }
+
+    public function advancedSearchCancel()
+    {
+        $this->filterCard = [];
+        $this->filterCategory = [];
+        $this->filterCertNormal = 0;
+        $this->filterCertAgonistic = 0;
+        $this->filterCertScaduto = 0;
+        $this->filterCertInScadenza = 0;
+        $this->advanced = false;
+    }
+
+    public function render()
+    {
+
+        $this->course_names = [];
+        $allC = \App\Models\Course::where('type', $this->course_course_type)->where('enabled', true)->orderBy('name')->get();
+        foreach ($allC as $c) {
+            $cN = $c->name . " (" . $c->year . ")";
+            if (!in_array($cN, $this->course_names))
+                $this->course_names[] = $cN;
+        }
+
+
+        $datas = [];
+        if (false) {
+
+            if (!$this->advanced) {
+                $this->records = \App\Models\Member::select('id', 'first_name', 'last_name', 'phone')->get();
+            } else {
+                $datas = \App\Models\Member::select('members.*')->where('id', '>', 0);
+                if (sizeof($this->filterCard) > 0) {
+                    $card_ids = \App\Models\MemberCard::whereIn('card_id', $this->filterCard)->pluck('member_id');
+                    $datas = $datas->whereIn('id', $card_ids);
+                }
+                if (sizeof($this->filterCategory) > 0) {
+                    $cats_ids = \App\Models\MemberCategory::whereIn('category_id', $this->filterCategory)->pluck('member_id');
+                    $datas = $datas->whereIn('id', $cats_ids);
+                }
+                $certs = [];
+
+                if ($this->filterCertNormal > 0) {
+                    $normal = \App\Models\MemberCertificate::where('type', 'N')->pluck('member_id');
+                    $datas = $datas->whereIn('id', $normal);;
+                }
+                if ($this->filterCertAgonistic > 0) {
+                    $agonistic = \App\Models\MemberCertificate::where('type', 'A')->pluck('member_id');
+                    $datas = $datas->whereIn('id', $agonistic);
+                }
+                if ($this->filterCertScaduto > 0) {
+                    $scaduto = \App\Models\MemberCertificate::where('expire_date', '<', date("Y-m-d"))->pluck('member_id');
+                    $datas = $datas->whereIn('id', $scaduto);
+                }
+                if ($this->filterCertInScadenza > 0) {
+                    $scaduto = \App\Models\MemberCertificate::whereBetween('expire_date', [date("Y-m-d"), date("Y-m-d", strtotime("+1 month"))])->pluck('member_id');
+                    $datas = $datas->whereIn('id', $scaduto);
+                }
+                if (sizeof($certs) > 0) {
+                    $datas = $datas->whereIn('id', $certs);
+                }
+                $this->records = $datas->get();
+            }
+
+            foreach ($this->records as $r) {
+                $r->age = $r->getAge();
+                $active = $r->isActive();
+                $r->status = $active["status"];
+                $r->date = $active["date"];
+                $r->certificate = $r->hasCertificate()["date"];
+                $r->state = $r->getStatus()["status"];
+            }
+
+
+            $this->emit('load-data-table');
+        }
+
+        $this->loadMemberCards();
+        $this->loadMemberCourses();
+        $this->loadMemberCategories();
+        $this->loadMemberCertificates();
+
+        $this->courses = \App\Models\Course::select('id', 'name')->where('type', $this->course_course_type)->get();
+
+        return view('livewire.member', ['datas' => $datas]);
+    }
+
+    public function updatedCourseCourseId()
+    {
+        if ($this->course_course_id > 0) {
+            $c = \App\Models\Course::findOrFail($this->course_course_id);
+            $this->course_price = formatPrice($c->price);
+            $this->course_subscription_price = formatPrice($c->subscription_price);
+            $this->course_date_from = $c->date_from;
+            $this->course_date_to = $c->date_to;
+            // Controllo se sono già iscritto la corso
+            $this->course_exist = \App\Models\MemberCourse::where('course_id', $this->course_course_id)->where('member_id', $this->dataId)->count() > 0;
+
+            // Carico gli abbonamenti e i mesi
+            $period = \Carbon\CarbonPeriod::create($c->date_from, '1 month', $c->date_to);
+            $this->course_months_list = [];
+            foreach ($period as $dt) {
+                $this->course_months_list[] = $dt->format("m");
+            }
+
+            $course_subscription_ids = [];
+            $this->course_price_list = [];
+            if ($c->prices != null) {
+                foreach (json_decode($c->prices) as $z) {
+                    $this->course_price_list[$z->course_subscription_id] = $z->price;
+                    if ($z->price > 0)
+                        $course_subscription_ids[] = $z->course_subscription_id;
+                }
+            }
+
+            $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
+        } else {
+            $this->course_price = 0;
+            $this->course_subscription_price = 0;
+            $this->course_exist = false;
+        }
+    }
+
+    public function loadMemberCards()
+    {
+        $this->member_cards = \App\Models\MemberCard::where('member_id', $this->dataId)->get();
+    }
+
+    public function loadMemberCourses()
+    {
+        $this->member_courses = \App\Models\MemberCourse::where('member_id', $this->dataId)->get();
+    }
+
+    public function loadMemberCategories()
+    {
+        $this->member_categories = \App\Models\MemberCategory::where('member_id', $this->dataId)->get();
+    }
+
+    public function loadMemberCertificates()
+    {
+        $this->member_certificates = \App\Models\MemberCertificate::where('member_id', $this->dataId)->orderBy('expire_date', 'DESC')->get();
+    }
+
+    public function showDetailF($id)
+    {
+        if (!isset($_GET["from"]) && $this->from == '')
+            $this->from = 'members';
+        $this->currentMember = \App\Models\Member::findOrFail($id);
+        $this->currentStatus = $this->currentMember->getStatus();
+        $this->showDetail = true;
+    }
+
+    public function add()
+    {
+        //$this->from = 'members';
+        $this->emit('load-select');
+        $this->emit('hide-search');
+        $this->showDetail = false;
+        $this->resetFields();
+        $this->add = true;
+        $this->update = false;
+        $this->emit('setEdit', true);
+        $this->emit('setEditCorso', false);
+        $this->dispatchBrowserEvent('scroll-to-top');
+    }
+
+    public function store($close)
+    {
+
+        $this->isSaving = true;
+
+        $this->emit('load-select');
+
+        $this->emit('goToTop');
+
+        $rules = [
+            'first_name' => 'required',
+            'last_name' => 'required',
+            'birth_nation_id' => 'required',
+            // 'email' => 'required',
+            // 'phone' => 'required',
+            'address' => 'required',
+            'zip_code' => 'required',
+            'nation_id' => 'required',
+            'birth_date' => 'before_or_equal:today',
+            'gender' => 'required'
+        ];
+
+        if ($this->isBirthItaly) {
+            $rules['birth_province_id'] = 'required';
+            $rules['birth_city_id'] = 'required';
+            $rules['fiscal_code'] = 'required';
+        } else {
+            $rules['birth_place'] = 'required';
+        }
+
+        if ($this->isItaly) {
+            $rules['province_id'] = 'required';
+            $rules['city_id'] = 'required';
+        }
+
+        $zzz = [];
+        if ($this->birth_nation_id == null)
+            $zzz[] = 'birth_nation_id=' . $this->birth_nation_id;
+        if ($this->isBirthItaly) {
+            if ($this->birth_province_id == null)
+                $zzz[] = 'birth_province_id=' . $this->birth_province_id;
+            if ($this->birth_city_id == null)
+                $zzz[] = 'birth_city_id=' . $this->birth_city_id;
+        }
+        if ($this->nation_id == null)
+            $zzz[] = 'nation_id=' . $this->nation_id;
+        if ($this->isItaly) {
+            if ($this->province_id == null)
+                $zzz[] = 'province_id=' . $this->province_id;
+            if ($this->city_id == null)
+                $zzz[] = 'city_id=' . $this->city_id;
+        }
+
+        $this->emit('setErrorMsg', $zzz);
+
+        if (!empty($this->fiscal_code)) {
+            $existingMember = \App\Models\Member::where('fiscal_code', $this->fiscal_code)->first();
+            if ($existingMember) {
+                $this->already_existing = true;
+                $this->emit('focus-error-field', 'fiscal_code');
+
+                return;
+            }
+        }
+
+        if ($this->under18) {
+            $rules['father_name'] = 'required_without:mother_name';
+            $rules['father_email'] = 'required_without:mother_email|email';
+            $rules['father_fiscal_code'] = 'required_without:mother_fiscal_code';
+            $rules['mother_name'] = 'required_without:father_name';
+            $rules['mother_email'] = 'required_without:father_email|email';
+            $rules['mother_fiscal_code'] = 'required_without:father_fiscal_code';
+        } else {
+            $rules['email'] = 'required';
+            $rules['phone'] = 'required';
+        }
+        
+        $std_rules = $rules;
+        // $rules = [
+        //     'first_name' => 'required',
+        //     'last_name' => 'required'
+        // ];
+
+        try {
+            $this->validate($rules);
+        } catch (\Illuminate\Validation\ValidationException $e) {
+            $this->isSaving = false;
+            $errorFields = array_keys($e->errors());
+            if (!empty($errorFields)) {
+                $this->emit('focus-error-field', $errorFields[0]);
+            }
+            Log::error('Validation failed', ['errors' => $e->errors()]);
+            return;
+        }
+        try {
+
+            $imageName = '';
+            if ($this->image) {
+                $imageName = md5($this->image . microtime()) . '.' . $this->image->extension();
+                $this->image->storeAs('public', $imageName);
+            }
+
+            $docs = implode("|", $this->document_files);
+            $father_docs = implode("|", $this->father_document_files);
+            $mother_docs = implode("|", $this->mother_document_files);
+
+
+            $to_complete = false;
+            // try {
+            //     $this->validate($std_rules);
+            // } catch(\Illuminate\Validation\ValidationException $e) {
+            //     $to_complete = true;
+            // } catch (\Exception $e) {
+            //     $to_complete = true;
+            // }
+
+            $member = \App\Models\Member::create([
+                'first_name' => strtoupper($this->first_name),
+                'last_name' => strtoupper($this->last_name),
+                'status' => $this->status,
+                'birth_city_id' => $this->birth_city_id > 0 ? $this->birth_city_id : null,
+                'birth_province_id' => $this->birth_province_id > 0 ? $this->birth_province_id : null,
+                'birth_nation_id' => $this->birth_nation_id > 0 ? $this->birth_nation_id : null,
+                'birth_date' => $this->birth_date,
+                'birth_place' => $this->birth_place,
+                'father_name' => $this->father_name,
+                'mother_name' => $this->mother_name,
+                'father_email' => strtolower($this->father_email),
+                'mother_email' => strtolower($this->mother_email),
+                'father_phone' => $this->father_phone,
+                'mother_phone' => $this->mother_phone,
+                'father_fiscal_code' => $this->father_fiscal_code,
+                'mother_fiscal_code' => $this->mother_fiscal_code,
+                'father_doc_number' => $this->father_doc_number,
+                'father_doc_type' => $this->father_doc_type,
+                'mother_doc_number' => $this->mother_doc_number,
+                'mother_doc_type' => $this->mother_doc_type,
+                'document_type' => $this->document_type,
+                'document_number' => $this->document_number,
+                'document_from' => $this->document_from,
+                'document_expire_date' => $this->document_expire_date,
+                'document_files' => $docs,
+                'mother_document_files' => $mother_docs,
+                'father_document_files' => $father_docs,
+                'gender' => $this->gender,
+                'no_send_mail' => $this->no_send_mail,
+                'exclude_from_records' => $this->exclude_from_records,
+                'fiscal_code' => $this->fiscal_code,
+                'address' => $this->address,
+                'zip_code' => $this->zip_code,
+                'nation_id' => $this->nation_id > 0 ? $this->nation_id : null,
+                'province_id' => $this->province_id > 0 ? $this->province_id : null,
+                'city_id' => $this->city_id > 0 ? $this->city_id : null,
+                'phone' => $this->phone,
+                'phone2' => $this->phone2,
+                'phone3' => $this->phone3,
+                'email' => strtolower($this->email),
+                'image' => $imageName,
+                'to_complete' => $to_complete,
+                'enabled' => $this->enabled
+            ]);
+            $this->fileService->createMemberFolders($member->id);
+
+            if ($this->image) {
+                $s3ImagePath = $this->fileService->uploadProfileImage($this->image, $member->id);
+                $member->update(['image' => $s3ImagePath]);
+            }
+            $this->migrateTemporaryFiles($member->id);
+
+
+            session()->flash('success, Tesserato creato');
+            updateMemberData($member->id);
+            $this->resetFields();
+            if ($close) {
+                $this->add = false;
+                return redirect()->to('/members');
+            } else {
+                $this->edit($member->id);
+                $this->emit('saved-and-continue', $this->type);
+                $this->dispatchBrowserEvent('scroll-to-top');
+            }
+            $this->emit('setEdit', false);
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    private function migrateTemporaryFiles($memberId)
+    {
+        try {
+            $updatedPaths = [];
+
+            // Migrate document files
+            $newDocumentFiles = [];
+            foreach ($this->document_files as $filePath) {
+                if (strpos($filePath, '/members/') === false) {
+                    // This is a temporary local file, move to S3
+                    $localPath = storage_path('app/public/' . $filePath);
+                    if (file_exists($localPath)) {
+                        $uploadedFile = new \Illuminate\Http\UploadedFile(
+                            $localPath,
+                            basename($filePath),
+                            mime_content_type($localPath),
+                            filesize($localPath),
+                            0,
+                            true
+                        );
+                        $s3Path = $this->fileService->uploadDocument($uploadedFile, $memberId, 'self');
+                        $newDocumentFiles[] = $s3Path;
+                        // Delete temporary file
+                        unlink($localPath);
+                    }
+                } else {
+                    $newDocumentFiles[] = $filePath;
+                }
+            }
+            if (!empty($newDocumentFiles)) {
+                $updatedPaths['document_files'] = implode('|', $newDocumentFiles);
+            }
+
+            // Migrate father document files
+            $newFatherFiles = [];
+            foreach ($this->father_document_files as $filePath) {
+                if (strpos($filePath, '/members/') === false) {
+                    $localPath = storage_path('app/public/' . $filePath);
+                    if (file_exists($localPath)) {
+                        $uploadedFile = new \Illuminate\Http\UploadedFile(
+                            $localPath,
+                            basename($filePath),
+                            mime_content_type($localPath),
+                            filesize($localPath),
+                            0,
+                            true
+                        );
+                        $s3Path = $this->fileService->uploadDocument($uploadedFile, $memberId, 'father');
+                        $newFatherFiles[] = $s3Path;
+                        unlink($localPath);
+                    }
+                } else {
+                    $newFatherFiles[] = $filePath;
+                }
+            }
+            if (!empty($newFatherFiles)) {
+                $updatedPaths['father_document_files'] = implode('|', $newFatherFiles);
+            }
+
+            // Migrate mother document files
+            $newMotherFiles = [];
+            foreach ($this->mother_document_files as $filePath) {
+                if (strpos($filePath, '/members/') === false) {
+                    $localPath = storage_path('app/public/' . $filePath);
+                    if (file_exists($localPath)) {
+                        $uploadedFile = new \Illuminate\Http\UploadedFile(
+                            $localPath,
+                            basename($filePath),
+                            mime_content_type($localPath),
+                            filesize($localPath),
+                            0,
+                            true
+                        );
+                        $s3Path = $this->fileService->uploadDocument($uploadedFile, $memberId, 'mother');
+                        $newMotherFiles[] = $s3Path;
+                        unlink($localPath);
+                    }
+                } else {
+                    $newMotherFiles[] = $filePath;
+                }
+            }
+            if (!empty($newMotherFiles)) {
+                $updatedPaths['mother_document_files'] = implode('|', $newMotherFiles);
+            }
+
+            // Update member with new S3 paths
+            if (!empty($updatedPaths)) {
+                \App\Models\Member::whereId($memberId)->update($updatedPaths);
+            }
+        } catch (\Exception $e) {
+            Log::error('Error migrating temporary files', [
+                'member_id' => $memberId,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+
+
+    public function duplicate($id)
+    {
+        $member = \App\Models\Member::findOrFail($id);
+        $newMember = $member->replicate();
+        $newMember->save();
+        $this->edit($newMember->id);
+    }
+
+    public function edit($id)
+    {
+
+        if (!isset($_GET["from"]) && $this->from == '')
+            $this->from = 'members';
+        $this->showDetail = false;
+        $this->emit('setEdit', true);
+        $this->emit('setEditCorso', false);
+        $this->emit('hide-search');
+        try {
+            $member = \App\Models\Member::findOrFail($id);
+            if (!$member) {
+                session()->flash('error', 'Tesserato non trovato');
+            } else {
+
+                $this->first_name = $member->first_name;
+                $this->last_name = $member->last_name;
+                $this->status = $member->status;
+                $this->birth_city_id = $member->birth_city_id;
+                $this->birth_province_id = $member->birth_province_id;
+                $this->birth_nation_id = $member->birth_nation_id;
+                $this->birth_date = $member->birth_date;
+                $this->birth_place = $member->birth_place;
+                $this->father_name = $member->father_name;
+                $this->mother_name = $member->mother_name;
+                $this->father_email = strtolower($member->father_email);
+                $this->mother_email = strtolower($member->mother_email);
+                $this->father_phone = $member->father_phone;
+                $this->mother_phone = $member->mother_phone;
+                $this->father_fiscal_code = $member->father_fiscal_code;
+                $this->mother_fiscal_code = $member->mother_fiscal_code;
+                $this->father_doc_number = $member->father_doc_number;
+                $this->mother_doc_number = $member->mother_doc_number;
+                $this->father_doc_type = $member->father_doc_type;
+                $this->mother_doc_type = $member->mother_doc_type;
+                $this->father_document_files = explode("|", $member->father_document_files);
+                $this->mother_document_files = explode("|", $member->mother_document_files);
+                $this->document_type = $member->document_type;
+                $this->document_number = $member->document_number;
+                $this->document_from = $member->document_from;
+                $this->document_expire_date = $member->document_expire_date;
+                $this->document_files = explode("|", $member->document_files);
+                $this->gender = $member->gender;
+                $this->no_send_mail = $member->no_send_mail;
+                $this->exclude_from_records = $member->exclude_from_records;
+                $this->fiscal_code = $member->fiscal_code;
+                $this->address = $member->address;
+                $this->zip_code = $member->zip_code;
+                $this->nation_id = $member->nation_id;
+                $this->province_id = $member->province_id;
+                $this->city_id = $member->city_id;
+                $this->phone = $member->phone;
+                $this->phone2 = $member->phone2;
+                $this->phone3 = $member->phone3;
+                $this->email = strtolower($member->email);
+                $this->image_old = $member->image;
+                $this->enabled = $member->enabled;
+                $this->dataId = $member->id;
+
+                $this->active = $member->getStatus();
+                $this->hasCertificate = $member->hasCertificate()["status"];
+
+                $this->money = $member->getMoney();
+
+                $this->checkIsBirthItaly();
+
+                $date1 = new DateTime($this->birth_date);
+                $date2 = new DateTime("now");
+                $interval = $date1->diff($date2);
+                $this->age = $interval->y . " anni";
+                $this->under18 = $interval->y < 18;
+                $this->update = true;
+                $this->add = false;                
+
+                $this->emit('setIds', $this->nation_id, $this->birth_nation_id);
+
+                $this->emit('load-select');
+                
+                $this->emit('load-provinces', $this->nation_id, 'provinceClass');
+                $this->emit('load-provinces', $this->birth_nation_id, 'provinceBirthClass');
+                
+                $this->emit('load-cities', $this->province_id, 'cityClass');
+                $this->emit('load-cities', $this->birth_province_id, 'cityBirthClass');
+
+                $this->dispatchBrowserEvent('scroll-to-top');
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function updateAAA($close)
+    {
+
+        $this->emit('goToTop');
+
+        $this->isSaving = true;
+
+        $this->emit('load-select');
+
+        $rules = [
+            'first_name' => 'required',
+            'last_name' => 'required',
+            // 'email' => 'required',
+            // 'phone' => 'required',
+            'birth_nation_id' => 'required',
+            'address' => 'required',
+            'zip_code' => 'required',
+            'nation_id' => 'required',
+            'birth_date' => 'before_or_equal:today'
+        ];
+
+        if ($this->isBirthItaly) {
+            $rules['birth_province_id'] = 'required';
+            $rules['birth_city_id'] = 'required';
+            $rules['fiscal_code'] = 'required';
+        } else {
+            $rules['birth_place'] = 'required';
+        }
+        Log::info('isItaly', ['isItaly' => $this->isItaly]);
+        $this->isItaly = $this->checkIsItaly();
+
+
+        if ($this->isItaly) {
+            $rules['province_id'] = 'required';
+            $rules['city_id'] = 'required';
+        }
+
+        $zzz = [];
+        if ($this->birth_nation_id == null)
+            $zzz[] = 'birth_nation_id=' . $this->birth_nation_id;
+        if ($this->isBirthItaly) {
+            if ($this->birth_province_id == null)
+                $zzz[] = 'birth_province_id=' . $this->birth_province_id;
+            if ($this->birth_city_id == null)
+                $zzz[] = 'birth_city_id=' . $this->birth_city_id;
+        }
+        if ($this->nation_id == null)
+            $zzz[] = 'nation_id=' . $this->nation_id;
+        if ($this->isItaly) {
+            if ($this->province_id == null)
+                $zzz[] = 'province_id=' . $this->province_id;
+            if ($this->city_id == null)
+                $zzz[] = 'city_id=' . $this->city_id;
+        }
+
+        $this->emit('setErrorMsg', $zzz);
+
+        if ($this->under18) {
+            $rules['father_name'] = 'required_without:mother_name';
+            $rules['father_email'] = 'required_without:mother_email|email';
+            $rules['father_fiscal_code'] = 'required_without:mother_fiscal_code';
+            $rules['mother_name'] = 'required_without:father_name';
+            $rules['mother_email'] = 'required_without:father_email|email';
+            $rules['mother_fiscal_code'] = 'required_without:father_fiscal_code';
+        } else {
+            $rules['email'] = 'required';
+            $rules['phone'] = 'required';
+        }
+
+        
+        $std_rules = $rules;
+        // $rules = [
+        //     'first_name' => 'required',
+        //     'last_name' => 'required'
+        // ];
+
+        try {
+            $this->validate($rules);
+        } catch (\Illuminate\Validation\ValidationException $e) {
+            $this->isSaving = false;
+            $errorFields = array_keys($e->errors());
+            if (!empty($errorFields)) {
+                $this->emit('focus-error-field', $errorFields[0]);
+            }
+            Log::error('Validation failed', ['errors' => $e->errors()]);
+            return;
+        }
+
+        try {
+
+            $imagePath = $this->image_old; // Keep existing if no new image
+            if ($this->image) {
+                $imagePath = $this->fileService->uploadProfileImage($this->image, $this->dataId);
+            }
+
+
+            $docs = implode("|", $this->document_files);
+            $father_docs = implode("|", $this->father_document_files);
+            $mother_docs = implode("|", $this->mother_document_files);
+
+            $to_complete = false;
+            // try {
+            //     $this->validate($std_rules);
+            // } catch(\Illuminate\Validation\ValidationException $e) {
+            //     $to_complete = true;
+            // } catch (Exception $e) {
+            //     $to_complete = true;
+            // }
+
+            \App\Models\Member::whereId($this->dataId)->update([
+                'first_name' => strtoupper($this->first_name),
+                'last_name' => strtoupper($this->last_name),
+                'status' => $this->status,
+                'birth_city_id' => $this->birth_city_id > 0 ? $this->birth_city_id : null,
+                'birth_province_id' => $this->birth_province_id > 0 ? $this->birth_province_id : null,
+                'birth_nation_id' => $this->birth_nation_id > 0 ? $this->birth_nation_id : null,
+                'birth_date' => $this->birth_date,
+                'birth_place' => $this->birth_place,
+                'father_name' => $this->father_name,
+                'mother_name' => $this->mother_name,
+                'father_email' => strtolower($this->father_email),
+                'mother_email' => strtolower($this->mother_email),
+                'father_phone' => $this->father_phone,
+                'mother_phone' => $this->mother_phone,
+                'father_fiscal_code' => $this->father_fiscal_code,
+                'mother_fiscal_code' => $this->mother_fiscal_code,
+                'father_doc_number' => $this->father_doc_number,
+                'father_doc_type' => $this->father_doc_type,
+                'mother_doc_number' => $this->mother_doc_number,
+                'mother_doc_type' => $this->mother_doc_type,
+                'father_document_files' => $father_docs,
+                'mother_document_files' => $mother_docs,
+                'document_type' => $this->document_type,
+                'document_number' => $this->document_number,
+                'document_from' => $this->document_from,
+                'document_expire_date' => $this->document_expire_date,
+                'document_files' => $docs,
+                'gender' => $this->gender,
+                'no_send_mail' => $this->no_send_mail,
+                'exclude_from_records' => $this->exclude_from_records,
+                'fiscal_code' => $this->fiscal_code,
+                'address' => $this->address,
+                'zip_code' => $this->zip_code,
+                'nation_id' => $this->nation_id > 0 ? $this->nation_id : null,
+                'province_id' => $this->province_id > 0 ? $this->province_id : null,
+                'city_id' => $this->city_id > 0 ? $this->city_id : null,
+                'image' => $imagePath,
+                'phone' => $this->phone,
+                'phone2' => $this->phone2,
+                'phone3' => $this->phone3,
+                'email' => strtolower($this->email),
+                'to_complete' => $to_complete,
+                'enabled' => $this->enabled
+            ]);
+            updateMemberData($this->dataId);
+            session()->flash('success', 'Tesserato aggiornato');
+            if ($close) {
+                $this->resetFields();
+                $this->update = false;
+                return redirect()->to('/members');
+            } else {
+                $this->emit('saved-and-continue', $this->type);
+                $this->dispatchBrowserEvent('scroll-to-top');
+            }
+            $this->emit('setEdit', false);
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function cancel()
+    {
+        $this->add = false;
+        $this->update = false;
+        $this->showDetail = false;
+        $this->resetFields();
+        $this->emit('setEdit', false);
+        //$this->emit('setEditCorso', false);
+        $this->emit('reload');
+    }
+
+    public function delete($id)
+    {
+        try {
+            \App\Models\Member::find($id)->delete();
+            session()->flash('success', "Tesserato eliminato");
+            $this->emit('reload');
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function checkUncheckAll()
+    {
+        if (!$this->checkedAll) {
+            $this->multipleIds = array();
+        } else {
+            foreach ($this->records as $r) {
+                $this->multipleIds[] = $r->id;
+            }
+        }
+    }
+
+    public function multipleDelete()
+    {
+        try {
+            foreach ($this->multipleIds as $id) {
+                \App\Models\Member::find($id)->delete();
+            }
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+        $this->multipleAction = '';
+    }
+
+    // Card
+
+    public function addCard()
+    {
+        $this->resetCardFields();
+        $this->addCard = true;
+        $this->updateCard = false;
+    }
+
+    public function storeCard()
+    {
+
+        $this->validate(['card_card_id' => 'required']);
+        try {
+            // in base alla card selezionata calcolo la scadenza
+            $expire_date = null;
+            if ($this->card_date != '') {
+                $y = date("Y", strtotime($this->card_date));
+                $card = \App\Models\Card::findOrFail($this->card_card_id);
+                if ($card->next_day_expire > 0 && $card->next_month_expire > 0) {
+
+                    $m = strlen($card->next_month_expire) == 1 ? ('0' . $card->next_month_expire) : $card->next_month_expire;
+
+                    if (date("md", strtotime($this->card_date)) > ($m . $card->next_day_expire))
+                        $y += 1;
+
+                    $next_exp = date($y . "-" . $m . "-" . $card->next_day_expire);
+
+                    if ($next_exp > $this->card_date) {
+                        $expire_date = $next_exp;
+                    } else
+                        $expire_date = date($y . "-" . $card->next_month_expire . "-" . $card->next_day_expire, strtotime(' + 1 years'));
+                } else {
+                    if ($card->one_year_expire) {
+                        $expire_date = date("Y-m-d", strtotime($this->card_date . ' + 1 years'));
+                    }
+                }
+            }
+            \App\Models\MemberCard::create([
+                'member_id' => $this->dataId,
+                'card_id' => $this->card_card_id,
+                'number' => $this->card_number,
+                'date' => $this->card_date,
+                'accept_date' => $this->card_accept_date != '' ? $this->card_accept_date : $this->card_date,
+                'expire_date' => $expire_date,
+                'status' => $this->card_status,
+                'discipline1_id' => $this->card_discipline1_id,
+                'discipline2_id' => $this->card_discipline2_id,
+                'discipline3_id' => $this->card_discipline3_id,
+            ]);
+            updateMemberData($this->dataId);
+            session()->flash('success, Tesserato creato');
+            $this->resetCardFields();
+            $this->addCard = false;
+            $this->loadMemberCards();
+            // TODO $this->checkCourseAvailability();
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function editCard($id)
+    {
+        try {
+            $memberCard = \App\Models\MemberCard::findOrFail($id);
+            if (!$memberCard) {
+                session()->flash('error', 'Tesserato non trovato');
+            } else {
+                $this->card_card_id = $memberCard->card_id;
+                $this->card_number = $memberCard->number;
+                $this->card_date = $memberCard->date;
+                $this->card_accept_date = $memberCard->accept_date;
+                $this->card_status = $memberCard->status;
+                $this->card_discipline1_id = $memberCard->discipline1_id;
+                $this->card_discipline2_id = $memberCard->discipline2_id;
+                $this->card_discipline3_id = $memberCard->discipline3_id;
+                $this->cardDataId = $memberCard->id;
+                $this->updateCard = true;
+                $this->addCard = false;
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function updateCard()
+    {
+        $this->validate(['card_card_id' => 'required']);
+
+        try {
+            Log::info('Starting card update', [
+                'member_id' => $this->dataId,
+                'card_id' => $this->card_card_id,
+                'card_number' => $this->card_number
+            ]);
+
+            $expire_date = null;
+            if ($this->card_date != '') {
+                $card = \App\Models\Card::findOrFail($this->card_card_id);
+
+                Log::info('Card details', [
+                    'card_id' => $card->id,
+                    'next_day_expire' => $card->next_day_expire,
+                    'next_month_expire' => $card->next_month_expire,
+                    'one_year_expire' => $card->one_year_expire
+                ]);
+
+                if ($card->next_day_expire > 0 && $card->next_month_expire > 0) {
+                    $y = date("Y", strtotime($this->card_date));
+                    $m = strlen($card->next_month_expire) == 1 ? ('0' . $card->next_month_expire) : $card->next_month_expire;
+                    $d = strlen($card->next_day_expire) == 1 ? ('0' . $card->next_day_expire) : $card->next_day_expire;
+                    $next_exp = $y . "-" . $m . "-" . $d;
+
+                    // Format dates for comparison
+                    $input_date_obj = new \DateTime($this->card_date);
+                    $next_exp_obj = new \DateTime($next_exp);
+
+                    if ($next_exp_obj > $input_date_obj) {
+                        $expire_date = $next_exp;
+                    } else {
+                        // Add 1 year to the expiration date if the input date is after the expiration date
+                        $next_exp_obj->modify('+1 year');
+                        $expire_date = $next_exp_obj->format('Y-m-d');
+                    }
+
+                    Log::info('Calculated expiration date (next_day_expire/next_month_expire rule)', [
+                        'input_date' => $this->card_date,
+                        'next_exp' => $next_exp,
+                        'expire_date' => $expire_date,
+                        'comparison' => ($next_exp_obj > $input_date_obj) ? 'next_exp is after input_date' : 'next_exp is before or equal to input_date'
+                    ]);
+                } else {
+                    if ($card->one_year_expire) {
+                        $expire_date = date("Y-m-d", strtotime($this->card_date . ' + 1 years'));
+
+                        Log::info('Calculated expiration date (one_year_expire rule)', [
+                            'input_date' => $this->card_date,
+                            'expire_date' => $expire_date
+                        ]);
+                    }
+                }
+            }
+
+            Log::info('Updating member card', [
+                'card_id' => $this->cardDataId,
+                'member_id' => $this->dataId,
+                'card_number' => $this->card_number,
+                'date' => $this->card_date,
+                'expire_date' => $expire_date,
+                'status' => $this->card_status
+            ]);
+
+            \App\Models\MemberCard::whereId($this->cardDataId)->update([
+                'member_id' => $this->dataId,
+                'card_id' => $this->card_card_id,
+                'number' => $this->card_number,
+                'date' => $this->card_date,
+                'accept_date' => $this->card_accept_date != '' ? $this->card_accept_date : $this->card_date,
+                'expire_date' => $expire_date,
+                'status' => $this->card_status,
+                'discipline1_id' => $this->card_discipline1_id,
+                'discipline2_id' => $this->card_discipline2_id,
+                'discipline3_id' => $this->card_discipline3_id,
+            ]);
+
+            updateMemberData($this->dataId);
+
+            Log::info('Card updated successfully', [
+                'card_id' => $this->cardDataId,
+                'member_id' => $this->dataId
+            ]);
+
+            session()->flash('success', 'Tesserato aggiornato');
+            $this->resetCardFields();
+            $this->updateCard = false;
+            $this->loadMemberCards();
+            // TODO $this->checkCourseAvailability();
+        } catch (\Exception $ex) {
+            Log::error('Error updating card', [
+                'card_id' => $this->cardDataId,
+                'member_id' => $this->dataId,
+                'error_message' => $ex->getMessage(),
+                'error_trace' => $ex->getTraceAsString()
+            ]);
+
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+    public function cancelCard()
+    {
+        $this->addCard = false;
+        $this->updateCard = false;
+        $this->resetCardFields();
+    }
+
+    public function deleteCard($id)
+    {
+        try {
+            \App\Models\MemberCard::find($id)->delete();
+            session()->flash('success', "Tesserato eliminato");
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function addCourse()
+    {
+        $this->resetCourseFields();
+        $this->addCourse = true;
+        $this->updateCourse = false;
+        $this->emit('setEditCorso', true);
+    }
+
+    public function storeCourse()
+    {
+
+        $this->validate(['course_course_id' => 'required']);
+        try {
+
+            $mc = new \App\Models\MemberCourse();
+            $mc->member_id = $this->dataId;
+            $mc->course_id = $this->course_course_id;
+            $mc->date_from = $this->course_date_from;
+            $mc->date_to = $this->course_date_to;
+            $mc->course_subscription_id = $this->course_course_subscription_id;
+            $mc->status = $this->course_status;
+            $mc->subscribed = false;
+            $mc->price = currencyToDouble($this->course_price);
+            $mc->subscription_price = currencyToDouble($this->course_subscription_price);
+            $mc->notes = $this->course_note;
+            $mc->months = json_encode($this->course_months);
+            $mc->when = json_encode($this->course_when);
+            $mc->save();
+
+            // Se il corso ha associato una categoria iscrivo anche al gruppo
+            $c = \App\Models\Course::findOrFail($this->course_course_id);
+            if ($c) {
+                if ($c->category_id > 0) {
+                    \App\Models\MemberCategory::create([
+                        'member_id' => $this->dataId,
+                        'category_id' => $c->category_id,
+                        'date' => \Carbon\Carbon::now()
+                    ]);
+                }
+            }
+
+            // Creo i pagamenti in base alla tipologia
+            $r = \App\Models\CourseSubscription::findOrFail($this->course_course_subscription_id);
+
+            $start = $this->course_date_from;
+
+            // Creo il pagamento per l'iscrizione
+            $rate = new \App\Models\Rate();
+            $rate->member_id = $this->dataId;
+            $rate->member_course_id = $mc->id;
+            $rate->course_subscription_id = null;
+            $rate->price = currencyToDouble($this->course_subscription_price);
+            $rate->date = $start;
+            $rate->months = json_encode([]);
+            $rate->note = '';
+            $rate->status = 0;
+            $rate->is_subscription = true;
+            $rate->save();
+            $go = true;
+            while ($go) {
+
+                $mms = [];
+                $mms[] = date("n", strtotime($start));
+                for ($jj = 1; $jj < $r->months; $jj++) {
+                    if (date('Ymd', strtotime("+" . $jj . " months", strtotime($start))) < date("Ymd", strtotime($this->course_date_to)))
+                    {
+                        $mms[] = date('n', strtotime("+" . $jj . " months", strtotime($start)));
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+
+                $rate = new \App\Models\Rate();
+                $rate->member_id = $this->dataId;
+                $rate->member_course_id = $mc->id;
+                $rate->course_subscription_id = $this->course_course_subscription_id;
+                $rate->price = currencyToDouble($this->course_price);
+                $rate->date = $start;
+                $rate->months = json_encode($mms);
+                $rate->note = '';
+                $rate->status = 0;
+                $rate->save();
+
+                $start = date('Y-m-d', strtotime("+" . $r->months . " months", strtotime($start)));
+
+                if ($start > $this->course_date_to) {
+                    $go = false;
+                    break;
+                }
+            }
+
+            session()->flash('success, Corso creato');
+            $this->resetCourseFields();
+            $this->addCourse = false;
+            $this->emit('setEditCorso', false);
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function editCourse($id)
+    {
+        try {
+
+            $memberCourse = \App\Models\MemberCourse::findOrFail($id);
+            if (!$memberCourse) {
+                session()->flash('error', 'Corso non trovato');
+            } else {
+                $this->course_course_id = $memberCourse->course_id;
+
+                // Carico i dati relativi alla struttura ad albero
+                $c = \App\Models\Course::findOrFail($this->course_course_id);
+                $this->course_name = $c->name . " (" . $c->year . ")";
+                $this->course_level_id = $c->course_level_id;
+                $this->course_course_type = $c->type;
+                $this->course_type_id = $c->course_type_id;
+                $this->course_frequency_id = $c->course_frequency_id;
+
+                $all = \App\Models\Course::where('name', 'like', '%' . $c->name . "%")->where('enabled', true)->get();
+                foreach ($all as $a) {
+                    $levels_ids[] = $a->course_level_id;
+                }
+                $this->course_levels = \App\Models\CourseLevel::select('*')->where('enabled', true)->whereIn('id', $levels_ids)->get();
+
+                $all = \App\Models\Course::where('name', 'like', '%' . $c->name . "%")->where('enabled', true)->where('course_level_id', $this->course_level_id)->get();
+                foreach ($all as $a) {
+                    $types_ids[] = $a->course_type_id;
+                }
+                $this->course_types = \App\Models\CourseType::select('*')->where('enabled', true)->whereIn('id', $types_ids)->get();
+
+                $frequencies_ids = [];
+                $all = \App\Models\Course::where('name', 'like', '%' . $c->name . "%")->where('enabled', true)->where('course_level_id', $this->course_level_id)->where('course_type_id', $this->course_type_id)->get();
+                foreach ($all as $a) {
+                    $frequencies_ids[] = $a->course_frequency_id;
+                }
+                $this->course_frequencies = \App\Models\CourseFrequency::select('*')->where('enabled', true)->whereIn('id', $frequencies_ids)->get();
+
+                $this->course_when = array();
+                $this->course_date_from = $memberCourse->date_from;
+                $this->course_date_to = $memberCourse->date_to;
+                foreach (json_decode($memberCourse->when) as $z) {
+                    $this->course_when[] = array("day" => $z->day, "from" => $z->from, "to" => $z->to);
+                }
+                //$this->course_when = json_decode($memberCourse->when);
+                $this->course_course_subscription_id = $memberCourse->course_subscription_id;
+                $this->course_status = $memberCourse->status;
+                $this->course_subscribed = $memberCourse->subscribed == 1 ? true : false;
+                $this->course_price = formatPrice($memberCourse->price);
+                $this->course_subscription_price = formatPrice($memberCourse->subscription_price);
+                $this->course_note = $memberCourse->notes;
+                foreach (json_decode($memberCourse->months) as $z) {
+                    $this->course_months[] = array("m" => $z->m, "status" => $z->status);
+                }
+
+                $course_subscription_ids = [];
+                $this->course_price_list = [];
+                if ($c->prices != null) {
+                    foreach (json_decode($c->prices) as $z) {
+                        $this->course_price_list[$z->course_subscription_id] = $z->price;
+                        if ($z->price > 0)
+                            $course_subscription_ids[] = $z->course_subscription_id;
+                    }
+                }
+
+                $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
+                
+                $this->courseDataId = $memberCourse->id;
+                $this->updateCourse = true;
+                $this->addCourse = false;
+                $this->emit('setEditCorso', true);
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function updateCourse()
+    {
+        //$this->validate();
+        $this->validate(['course_course_id' => 'required']);
+        try {
+
+
+            \App\Models\MemberCourse::whereId($this->courseDataId)->update([
+                'member_id' => $this->dataId,
+                'course_id' => $this->course_course_id,
+                'date_from' => $this->course_date_from,
+                'date_to' => $this->course_date_to,
+                'course_subscription_id' => $this->course_course_subscription_id,
+                'status' => $this->course_status,
+                'price' => currencyToDouble($this->course_price),
+                'subscription_price' => currencyToDouble($this->course_subscription_price),
+                'notes' => $this->course_note,
+                'months' => json_encode($this->course_months),
+                'when' => json_encode($this->course_when)
+            ]);
+            session()->flash('success', 'Corso aggiornato');
+            $this->resetCourseFields();
+            $this->updateCourse = false;
+            $this->emit('setEditCorso', false);
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function cancelCourse()
+    {
+        $this->addCourse = false;
+        $this->updateCourse = false;
+        $this->emit('setEditCorso', false);
+        $this->resetCourseFields();
+    }
+
+    public function deleteCourse($id)
+    {
+        try {
+            \App\Models\MemberCourse::find($id)->delete();
+            session()->flash('success', "Corso eliminato");
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function addRow()
+    {
+        $this->course_when[] = array('day' => array(), 'from' => '', 'to' => '');
+    }
+
+    public function delRow($idx)
+    {
+        unset($this->course_when[$idx]);
+    }
+
+    // Certificates
+
+    public function addCertificate()
+    {
+        $this->resetCertificateFields();
+        $this->addCertificate = true;
+        $this->updateCertificate = false;
+    }
+
+    public function storeCertificate()
+    {
+        $this->validate(['certificate_expire_date' => 'required']);
+
+        try {
+            $certificatePath = '';
+            if ($this->certificate_filename) {
+                $certificatePath = $this->fileService->uploadCertificate($this->certificate_filename, $this->dataId);
+            }
+
+            if ($this->dataId > -1) {
+                \App\Models\MemberCertificate::create([
+                    'member_id' => $this->dataId,
+                    'type' => $this->certificate_type,
+                    'filename' => $certificatePath,
+                    'expire_date' => $this->certificate_expire_date,
+                    'status' => $this->certificate_status
+                ]);
+                updateMemberData($this->dataId);
+            }
+
+            session()->flash('success', 'Certificato creato');
+            $this->resetCertificateFields();
+            $this->addCertificate = false;
+            $this->loadMemberCertificates();
+            // TODO $this->checkCourseAvailability();
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function editCertificate($id)
+    {
+        try {
+            $memberCertificate = \App\Models\MemberCertificate::findOrFail($id);
+            if (!$memberCertificate) {
+                session()->flash('error', 'Tesserato non trovato');
+            } else {
+                $this->certificate_type = $memberCertificate->type;
+                $this->certificate_filename_old = $memberCertificate->filename;
+                $this->certificate_expire_date = $memberCertificate->expire_date;
+                $this->certificate_status = $memberCertificate->status;
+                $this->cardCertificateId = $memberCertificate->id;
+                $this->updateCertificate = true;
+                $this->addCertificate = false;
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function updateCertificate()
+    {
+        $this->validate(['certificate_expire_date' => 'required']);
+
+        try {
+            $certificatePath = $this->certificate_filename_old; // Keep existing if no new file
+            if ($this->certificate_filename) {
+                $certificatePath = $this->fileService->uploadCertificate($this->certificate_filename, $this->dataId);
+            }
+
+            \App\Models\MemberCertificate::whereId($this->cardCertificateId)->update([
+                'member_id' => $this->dataId,
+                'type' => $this->certificate_type,
+                'filename' => $certificatePath,
+                'expire_date' => $this->certificate_expire_date,
+                'status' => $this->certificate_status
+            ]);
+
+            updateMemberData($this->dataId);
+            session()->flash('success', 'Certificato aggiornato');
+            $this->resetCertificateFields();
+            $this->updateCertificate = false;
+            $this->loadMemberCertificates();
+            // TODO $this->checkCourseAvailability();
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function getFileUrl($filePath)
+    {
+        if (!$filePath) {
+            return null;
+        }
+
+        return $this->fileService->getFileUrl($filePath);
+    }
+
+    public function cancelCertificate()
+    {
+        $this->addCertificate = false;
+        $this->updateCertificate = false;
+        $this->resetCertificateFields();
+    }
+
+    public function deleteCertificate($id)
+    {
+        try {
+            \App\Models\MemberCertificate::find($id)->delete();
+            session()->flash('success', "Tesserato eliminato");
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    // Gruppi di appartenenza
+
+    public function storeCategory()
+    {
+
+        $this->validate(['category_category_id' => 'required']);
+        try {
+            \App\Models\MemberCategory::create([
+                'member_id' => $this->dataId,
+                'category_id' => $this->category_category_id,
+                'date' => \Carbon\Carbon::now()
+            ]);
+            session()->flash('success, Associazione creato');
+            $this->resetCategoryFields();
+            $this->addCard = false;
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function hideMsg()
+    {
+        $this->groupMsg = '';
+    }
+
+    public function storeCategoryWithID($id)
+    {
+        $this->groupMsg = '';
+        try {
+            if (\App\Models\MemberCategory::where('member_id', $this->dataId)->where('category_id', $id)->first()) {
+                $this->groupMsg = '<br>Attenzione, questo corso è stato già inserito';
+            } else {
+                \App\Models\MemberCategory::create([
+                    'member_id' => $this->dataId,
+                    'category_id' => $id,
+                    'date' => \Carbon\Carbon::now()
+                ]);
+                session()->flash('success, Associazione creato');
+                $this->resetCategoryFields();
+                $this->addCard = false;
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function deleteCategory($id)
+    {
+        try {
+            \App\Models\MemberCategory::find($id)->delete();
+            session()->flash('success', "Associazione eliminata");
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function setCourse($id)
+    {
+        $this->course_course_id = $id;
+    }
+
+    public function getNation($nation)
+    {
+        if ($nation > 0) {
+            $ret = \App\Models\Nation::findOrFail($nation);
+            return $ret->name;
+        }
+        return "";
+    }
+
+    public function getProvince($province)
+    {
+        if ($province > 0) {
+            $ret = \App\Models\Province::findOrFail($province);
+            return $ret->name;
+        }
+        return "";
+    }
+
+    public function getCity($city)
+    {
+        if ($city > 0) {
+            $ret = \App\Models\City::findOrFail($city);
+            return $ret->name;
+        }
+        return "";
+    }
+
+    public function getFiscalCode()
+    {
+
+        $this->error_fc = false;
+
+        $cf = new codicefiscale();
+        $cf->setDateSeparator('-');
+
+        if ($this->first_name != '' && $this->last_name != '' && $this->birth_date != '' && $this->gender != '' && $this->birth_city_id > 0) {
+
+            $code = '';
+            if ($this->birth_city_id > 0) {
+                $code = \App\Models\City::findOrFail($this->birth_city_id)->code;
+            }
+
+            $codice = $cf->calcola($this->first_name, $this->last_name, $this->birth_date, $this->gender, $code);
+
+            $this->fiscal_code = $codice;
+        } else
+            $this->error_fc = true;
+    }
+
+    public function setMonth($m)
+    {
+
+        $exist = -1;
+        foreach ($this->course_months as $idx => $x) {
+            if ($x["m"] == $m) {
+                $exist = $idx;
+                break;
+            }
+        }
+        if ($exist > -1) {
+            array_splice($this->course_months, $exist, 1);
+        } else {
+            $this->course_months[] = array("m" => $m, "status" => "");
+        }
+    }
+
+    public function newPayment($course)
+    {
+        $c = \App\Models\Course::findOrFail($course["course_id"]);
+
+        $price = $course["price"];
+
+        if (sizeof($this->payMonths) == 1) {
+            $month = $this->payMonths[0];
+            $records = \App\Models\Record::where('member_course_id', $this->selectedCourseMember)->where('deleted', 0)->get();
+            foreach ($records as $record) {
+
+                if (in_array($month, json_decode($record->months))) {
+
+                    foreach ($record->rows as $row) {
+
+
+                        if ($row->causal_id == $c->causal_id && !str_contains(strtolower($row->note), 'iscrizione')) {
+                            $tot = sizeof(json_decode($row->when));
+                            foreach (json_decode($row->when) as $m) {
+                                $price -= $row->amount / $tot;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        $this->emit('setEdit', false);
+        $this->emit('setEditCorso', false);
+        return redirect()->to('/in?new=1&memberId=' . $this->dataId . '&causalId=' . $c->causal_id . '&subCausalId=' . $c->sub_causal_id . '&createSubscription=' . $this->createSubscription .  (sizeof($this->payMonths) > 0 ? '&months=' . implode("|", $this->payMonths) : "") . '&price=' . $price . '&subscription_price=' . $course["subscription_price"] . "&courseId=" . $course["id"]);
+    }
+
+    public function block($course)
+    {
+        $xxx = json_decode($course["months"]);
+        foreach ($xxx as $idx => $mm) {
+            if (in_array($mm->m, $this->payMonths)) {
+                $xxx[$idx]->status = 2;
+            }
+        }
+        $c = \App\Models\MemberCourse::findOrFail($course["id"]);
+        $c->months = json_encode($xxx);
+        $c->save();
+        $this->payMonths = array();
+    }
+
+    public function reactivate($course)
+    {
+        $xxx = json_decode($course["months"]);
+        foreach ($xxx as $idx => $mm) {
+            if (in_array($mm->m, $this->suspendedMonths)) {
+                $xxx[$idx]->status = "";
+            }
+        }
+        $c = \App\Models\MemberCourse::findOrFail($course["id"]);
+        $c->months = json_encode($xxx);
+        $c->save();
+        $this->suspendedMonths = array();
+    }
+
+    public function setPayMonth($m, $months, $selectedCourseMember)
+    {
+
+        if ($this->selectedCourseMember != $selectedCourseMember)
+            $this->payMonths = array();
+        $this->selectedCourseMember = $selectedCourseMember;
+
+        if (in_array($m, $this->payMonths)) {
+            $i = array_search($m, $this->payMonths);
+            array_splice($this->payMonths, $i, 1);
+        } else if (in_array($m, $this->suspendedMonths)) {
+            $i = array_search($m, $this->suspendedMonths);
+            array_splice($this->suspendedMonths, $i, 1);
+        } else {
+            foreach (json_decode($months) as $mm) {
+                if ($mm->m == $m) {
+                    if ($mm->status == "")
+                        $this->payMonths[] = $m;
+                    if ($mm->status == "1") {
+                        $mc = \App\Models\MemberCourse::findOrFail($selectedCourseMember);
+                        $price = $mc->price;
+                        $payed = 0;
+                        $extraC = '';
+                        $recordsPayed = \App\Models\Record::where('member_course_id', $selectedCourseMember)->where('deleted', 0)->get();
+                        foreach ($recordsPayed as $record) {
+                            if (in_array($m, json_decode($record->months))) {
+                                foreach ($record->rows as $row) {
+                                    if ($row->causal_id == $mc->course->causal_id && !str_contains(strtolower($row->note), 'iscrizione')) {
+                                        $tot = sizeof(json_decode($row->when));
+                                        $payed += $row->amount / $tot;
+                                    }
+                                }
+                                if ($payed < $price)
+                                    $this->payMonths[] = $m;
+                            }
+                        }
+                    }
+
+                    if ($mm->status == "2")
+                        $this->suspendedMonths[] = $m;
+                }
+            }
+        }
+        if (sizeof($this->payMonths) > 0 && sizeof($this->suspendedMonths) > 0) {
+            $this->payMonths = array();
+            $this->suspendedMonths = array();
+        }
+    }
+
+    public function setCreateSubscription($subscribed, $selectedCourseMember)
+    {
+
+        if ($this->selectedCourseMember != $selectedCourseMember)
+            $this->payMonths = array();
+        $this->selectedCourseMember = $selectedCourseMember;
+
+        if (!$subscribed)
+            $this->createSubscription = !$this->createSubscription;
+    }
+
+    public function showHideCourse($id)
+    {
+        if (in_array($id, $this->showCourse)) {
+            $i = array_search($id, $this->showCourse);
+            array_splice($this->showCourse, $i, 1);
+        } else {
+            $this->showCourse[] = $id;
+        }
+    }
+
+    public function getMonthStatus($m, $months, $selectedCourseMember)
+    {
+
+        $class = "grey";
+
+        foreach (json_decode($months) as $mm) {
+            if ($mm->m == $m) {
+                if ($mm->status == "") {
+                    if (in_array($m, $this->payMonths) && $this->selectedCourseMember == $selectedCourseMember)
+                        $class = "blue";
+                    else
+                        $class = "orange";
+                }
+                if ($mm->status == "1") {
+                    $class = "green";
+                }
+                if ($mm->status == "2") {
+                    if (in_array($m, $this->suspendedMonths) && $this->selectedCourseMember == $selectedCourseMember)
+                        $class = "blue";
+                    else
+                        $class = "yellow";
+                }
+
+                if ($class == 'green') {
+
+                    $mc = \App\Models\MemberCourse::findOrFail($selectedCourseMember);
+                    $price = $mc->price;
+                    $payed = 0;
+                    $extraC = '';
+                    $recordsPayed = \App\Models\Record::where('member_course_id', $selectedCourseMember)->where('deleted', 0)->get();
+                    foreach ($recordsPayed as $record) {
+                        if (in_array($m, json_decode($record->months))) {
+                            foreach ($record->rows as $row) {
+                                if ($row->causal_id == $mc->course->causal_id && !str_contains(strtolower($row->note), 'iscrizione')) {
+                                    $tot = sizeof(json_decode($row->when));
+                                    $payed += $row->amount / $tot;
+                                }
+                            }
+                        }
+                    }
+                    if ($payed < $price)
+                        $class = 'orange half';
+                }
+            }
+        }
+
+        return $class;
+    }
+
+    public function getSubscriptionStatus($subscribed, $selectedCourseMember)
+    {
+
+        $class = "grey";
+
+        if ($this->createSubscription && $this->selectedCourseMember == $selectedCourseMember)
+            $class = "blue";
+        else
+            $class = "orange";
+        if ($subscribed)
+            $class = "green";
+
+        return $class;
+    }
+
+    public function checkMonth($m)
+    {
+        $ret = false;
+        foreach ($this->course_months as $idx => $x) {
+            if ($x["m"] == $m) {
+                $ret = true;
+                break;
+            }
+        }
+        return $ret;
+    }
+
+    public function setDay($idx, $d)
+    {
+
+        if (in_array($d, $this->course_when[$idx]["day"])) {
+            $i = array_search($d, $this->course_when[$idx]["day"]);
+            array_splice($this->course_when[$idx]["day"], $i, 1);
+        } else {
+            $this->course_when[$idx]["day"][] = $d;
+        }
+    }
+
+    public function updateBorsellino($importoBorsellino, $s)
+    {
+        if ($importoBorsellino > 0) {
+            $imp = $importoBorsellino;
+            if ($s == "-")
+                $imp = $imp * -1;
+            $money = new \App\Models\Money();
+            $money->member_id = $this->currentMember->id;
+            $money->record_id = null;
+            $money->amount = $imp;
+            $money->date = date("Y-m-d");
+            $money->note = 'Forzato ' . $imp;
+            $money->save();
+        }
+        $this->emit('saved');
+    }
+
+    public function getMonth($m)
+    {
+        $ret = '';
+        switch ($m) {
+            case 1:
+                $ret = 'Gennaio';
+                break;
+            case 2:
+                $ret = 'Febbraio';
+                break;
+            case 3:
+                $ret = 'Marzo';
+                break;
+            case 4:
+                $ret = 'Aprile';
+                break;
+            case 5:
+                $ret = 'Maggio';
+                break;
+            case 6:
+                $ret = 'Giugno';
+                break;
+            case 7:
+                $ret = 'Luglio';
+                break;
+            case 8:
+                $ret = 'Agosto';
+                break;
+            case 9:
+                $ret = 'Settembre';
+                break;
+            case 10:
+                $ret = 'Ottobre';
+                break;
+            case 11:
+                $ret = 'Novembre';
+                break;
+            case 12:
+                $ret = 'Dicembre';
+                break;
+            default:
+                $ret = '';
+                break;
+        }
+        return $ret;
+    }
+    public function archive($id)
+    {
+        try {
+            $member = \App\Models\Member::findOrFail($id);
+
+            $originalFirstName = $member->first_name;
+            $originalLastName = $member->last_name;
+
+            $member->update([
+                'is_archived' => true,
+                'archived_date' => now(),
+
+                'first_name' => 'Nome Archiviato',
+                'last_name' => 'Cognome Archiviato',
+
+                'fiscal_code' => null,
+                'email' => null,
+                'phone' => null,
+                'phone2' => null,
+                'phone3' => null,
+                'address' => null,
+                'zip_code' => null,
+
+                'birth_place' => null,
+                'birth_date' => null,
+                'birth_city_id' => null,
+                'birth_province_id' => null,
+                'birth_nation_id' => null,
+
+                'nation_id' => null,
+                'province_id' => null,
+                'city_id' => null,
+
+                'document_type' => null,
+                'document_number' => null,
+                'document_from' => null,
+                'document_expire_date' => null,
+                'document_files' => null,
+
+                'father_name' => null,
+                'mother_name' => null,
+                'father_email' => null,
+                'mother_email' => null,
+                'father_phone' => null,
+                'mother_phone' => null,
+                'father_fiscal_code' => null,
+                'mother_fiscal_code' => null,
+                'father_doc_number' => null,
+                'father_doc_type' => null,
+                'mother_doc_number' => null,
+                'mother_doc_type' => null,
+                'father_document_files' => null,
+                'mother_document_files' => null,
+
+                'image' => null,
+
+                'gender' => null,
+                'status' => 'archived',
+
+                'enabled' => false
+            ]);
+
+            updateMemberData($id);
+
+            session()->flash('success', 'Membro archiviato con successo');
+
+            $this->emit('reload');
+
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore durante l\'archiviazione: ' . $e->getMessage());
+            Log::error('Archive member error', [
+                'member_id' => $id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        }
+    }
+}
+
+class codicefiscale
+{
+
+    /**
+     * Array delle consonanti
+     */
+    protected $_consonanti = array(
+        'B',
+        'C',
+        'D',
+        'F',
+        'G',
+        'H',
+        'J',
+        'K',
+        'L',
+        'M',
+        'N',
+        'P',
+        'Q',
+        'R',
+        'S',
+        'T',
+        'V',
+        'W',
+        'X',
+        'Y',
+        'Z'
+    );
+
+    /**
+     * Array delle vocali
+     */
+    protected $_vocali = array(
+        'A',
+        'E',
+        'I',
+        'O',
+        'U'
+    );
+
+    /**
+     * Array per il calcolo della lettera del mese
+     * Al numero del mese corrisponde una lettera
+     */
+    protected $_mesi = array(
+        1  => 'A',
+        2 => 'B',
+        3 => 'C',
+        4 => 'D',
+        5 => 'E',
+        6  => 'H',
+        7 => 'L',
+        8 => 'M',
+        9 => 'P',
+        10 => 'R',
+        11 => 'S',
+        12 => 'T'
+    );
+
+
+    protected $_pari = array(
+        '0' =>  0,
+        '1' =>  1,
+        '2' =>  2,
+        '3' =>  3,
+        '4' =>  4,
+        '5' =>  5,
+        '6' =>  6,
+        '7' =>  7,
+        '8' =>  8,
+        '9' =>  9,
+        'A' =>  0,
+        'B' =>  1,
+        'C' =>  2,
+        'D' =>  3,
+        'E' =>  4,
+        'F' =>  5,
+        'G' =>  6,
+        'H' =>  7,
+        'I' =>  8,
+        'J' =>  9,
+        'K' => 10,
+        'L' => 11,
+        'M' => 12,
+        'N' => 13,
+        'O' => 14,
+        'P' => 15,
+        'Q' => 16,
+        'R' => 17,
+        'S' => 18,
+        'T' => 19,
+        'U' => 20,
+        'V' => 21,
+        'W' => 22,
+        'X' => 23,
+        'Y' => 24,
+        'Z' => 25
+    );
+
+    protected $_dispari = array(
+        '0' =>  1,
+        '1' =>  0,
+        '2' =>  5,
+        '3' =>  7,
+        '4' =>  9,
+        '5' => 13,
+        '6' => 15,
+        '7' => 17,
+        '8' => 19,
+        '9' => 21,
+        'A' =>  1,
+        'B' =>  0,
+        'C' =>  5,
+        'D' =>  7,
+        'E' =>  9,
+        'F' => 13,
+        'G' => 15,
+        'H' => 17,
+        'I' => 19,
+        'J' => 21,
+        'K' =>  2,
+        'L' =>  4,
+        'M' => 18,
+        'N' => 20,
+        'O' => 11,
+        'P' =>  3,
+        'Q' =>  6,
+        'R' =>  8,
+        'S' => 12,
+        'T' => 14,
+        'U' => 16,
+        'V' => 10,
+        'W' => 22,
+        'X' => 25,
+        'Y' => 24,
+        'Z' => 23
+    );
+
+    protected $_controllo = array(
+        '0'  => 'A',
+        '1'  => 'B',
+        '2'  => 'C',
+        '3'  => 'D',
+        '4'  => 'E',
+        '5'  => 'F',
+        '6'  => 'G',
+        '7'  => 'H',
+        '8'  => 'I',
+        '9'  => 'J',
+        '10' => 'K',
+        '11' => 'L',
+        '12' => 'M',
+        '13' => 'N',
+        '14' => 'O',
+        '15' => 'P',
+        '16' => 'Q',
+        '17' => 'R',
+        '18' => 'S',
+        '19' => 'T',
+        '20' => 'U',
+        '21' => 'V',
+        '22' => 'W',
+        '23' => 'X',
+        '24' => 'Y',
+        '25' => 'Z'
+    );
+
+    /**
+     * Stringa di errore
+     */
+    protected $_error = null;
+
+    /**
+     * Separatore per la data di nascita
+     */
+    protected $_dateSeparator = '/';
+
+
+    /**
+     * Percorso del file del database SQLite
+     * dei codici catastali
+     */
+    protected $_dbCatastali = null;
+
+
+    /**
+     * Trasforma la stringa passata in un array di lettere
+     * e lo incrocia con un ulteriore array
+     */
+    protected function _getLettere($string, array $haystack)
+    {
+        $letters = array();
+        foreach (str_split($string) as $needle) {
+            if (in_array($needle, $haystack)) {
+                $letters[] = $needle;
+            }
+        }
+        return $letters;
+    }
+
+    /**
+     * Ritorna un array con le vocali di una data stringa
+     */
+    protected function _getVocali($string)
+    {
+        return $this->_getLettere($string, $this->_vocali);
+    }
+
+    /**
+     * Ritorna un array con le consonanti di una data stringa
+     */
+    protected function _getConsonanti($string)
+    {
+        return $this->_getLettere($string, $this->_consonanti);
+    }
+
+    /**
+     * Pulisce la stringa filtrando tutti i caratteri che
+     * non sono lettere. Lo switch $toupper se impostato a TRUE
+     * converte la stringa risultante in MAIUSCOLO.
+     */
+    protected function _sanitize($string, $toupper = true)
+    {
+        $result = preg_replace('/[^A-Za-z]*/', '', $string);
+        return ($toupper) ? strtoupper($result) : $result;
+    }
+
+    /**
+     * Se la stringa passata a funzione e' costituita
+     * da meno di 3 caratteri, rimpiazza le lettere
+     * mancanti con la lettera X.
+     */
+    protected function _addMissingX($string)
+    {
+        $code = $string;
+        while (strlen($code) < 3) {
+            $code .= 'X';
+        }
+        return $code;
+    }
+
+    /**
+     * Ottiene il codice identificativo del nome
+     */
+    protected function _calcolaNome($string)
+    {
+        $nome = $this->_sanitize($string);
+        $code = '';
+        // Se il nome inserito e' piu' corto di 3 lettere
+        // si aggiungono tante X quanti sono i caratteri
+        // mancanti.
+        if (strlen($nome) < 3) {
+            return $this->_addMissingX($nome);
+        }
+
+        $nome_cons = $this->_getConsonanti($nome);
+
+        // Se le consonanti contenute nel nome sono minori
+        // o uguali a 3 vengono considerate nell'ordine in cui
+        // compaiono.
+        if (count($nome_cons) <= 3) {
+            $code = implode('', $nome_cons);
+        } else {
+            // Se invece abbiamo almeno 4 consonanti, prendiamo
+            // la prima, la terza e la quarta.
+            for ($i = 0; $i < 4; $i++) {
+                if ($i == 1) continue;
+                if (!empty($nome_cons[$i])) {
+                    $code .= $nome_cons[$i];
+                }
+            }
+        }
+
+        // Se compaiono meno di 3 consonanti nel nome, si
+        // utilizzano le vocali, nell'ordine in cui compaiono
+        // nel nome.
+        if (strlen($code) < 3) {
+            $nome_voc = $this->_getVocali($nome);
+            while (strlen($code) < 3) {
+                $code .= array_shift($nome_voc);
+            }
+        }
+
+        return $code;
+    }
+
+    protected function _calcolaCognome($string)
+    {
+        $cognome = $this->_sanitize($string);
+        $code = '';
+        // Se il cognome inserito e' piu' corto di 3 lettere
+        // si aggiungono tante X quanti sono i caratteri
+        // mancanti.
+        if (strlen($cognome) < 3) {
+            return $this->_addMissingX($cognome);
+        }
+
+        $cognome_cons = $this->_getConsonanti($cognome);
+
+        // Per il calcolo del cognome si prendono le prime
+        // 3 consonanti.
+        for ($i = 0; $i < 3; $i++) {
+            if (array_key_exists($i, $cognome_cons)) {
+                $code .= $cognome_cons[$i];
+            }
+        }
+
+        // Se le consonanti non bastano, vengono prese
+        // le vocali nell'ordine in cui compaiono.
+        if (strlen($code) < 3) {
+            $cognome_voc = $this->_getVocali($cognome);
+            while (strlen($code) < 3) {
+                $code .= array_shift($cognome_voc);
+            }
+        }
+
+        return $code;
+    }
+
+    /**
+     * Imposta il separatore di data ( default: / )
+     */
+    public function setDateSeparator($char)
+    {
+        $this->_dateSeparator = $char;
+        return $this;
+    }
+
+    /**
+     * Ritorna la parte di codice fiscale corrispondente
+     * alla data di nascita del soggetto (Forma: AAMGG)
+     */
+    protected function _calcolaDataNascita($data, $sesso)
+    {
+        $dn = explode($this->_dateSeparator, $data);
+
+        $giorno = (int) @$dn[2];
+        $mese   = (int) @$dn[1];
+        $anno   = (int) @$dn[0];
+
+        // Le ultime due cifre dell'anno di nascita
+        $aa = substr($anno, -2);
+
+        // La lettera corrispondente al mese di nascita
+        $mm = $this->_mesi[$mese];
+
+        // Il giorno viene calcolato a seconda del sesso
+        // del soggetto di cui si calcola il codice:
+        // se e' Maschio si mette il giorno reale, se e'
+        // Femmina viene aggiungo 40 a questo numero.
+        $gg = (strtoupper($sesso) == 'M') ? $giorno : ($giorno + 40);
+
+        // Bug #1: Thanks to Luca
+        if (strlen($gg) < 2) $gg = '0' . $gg;
+
+
+        return $aa . $mm . $gg;
+    }
+
+    /**
+     * Ritorna la cifra di controllo sulla base dei
+     * 15 caratteri del codice fiscale calcolati.
+     */
+    protected function _calcolaCifraControllo($codice)
+    {
+        $code = str_split($codice);
+        $sum  = 0;
+
+        for ($i = 1; $i <= count($code); $i++) {
+            $cifra = $code[$i - 1];
+            $sum += ($i % 2) ? $this->_dispari[$cifra] : $this->_pari[$cifra];
+        }
+
+        $sum %= 26;
+
+        return $this->_controllo[$sum];
+    }
+
+    /**
+     * Imposta il messaggio di errore
+     */
+    protected function _setError($string)
+    {
+        $this->_error = $string;
+    }
+
+    /**
+     * Verifica la presenza di un errore.
+     * Ritorna TRUE se presente, FALSE altrimenti.
+     */
+    public function hasError()
+    {
+        return !is_null($this->_error);
+    }
+
+    /**
+     * Ritorna la stringa di errore
+     */
+    public function getError()
+    {
+        return $this->_error;
+    }
+
+    /**
+     * Ritorna il codice fiscale utilizzando i parametri
+     * passati a funzione. Se si verifica
+     */
+    public function calcola($nome, $cognome, $data, $sesso, $comune)
+    {
+        $codice = $this->_calcolaCognome($cognome) .
+            $this->_calcolaNome($nome) .
+            $this->_calcolaDataNascita($data, $sesso) .
+            $comune;
+
+        if ($this->hasError()) {
+            return false;
+        }
+
+        $codice .= $this->_calcolaCifraControllo($codice);
+
+        if (strlen($codice) != 16) {
+            return 'ERROR';
+        }
+
+        return $codice;
+    }
+}

+ 138 - 0
app/Http/Livewire/MemberArchive.php

@@ -0,0 +1,138 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Illuminate\Support\Facades\Log;
+use App\Http\Middleware\TenantMiddleware;
+
+use DateTime;
+
+class MemberArchive extends Component
+{
+    public $categories = [];
+
+    public function boot(){
+        app(abstract: TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function mount()
+    {
+        $this->categories = [];
+        $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
+    }
+
+    public function render()
+    {
+        return view('livewire.member_archive');
+    }
+
+    public function getCategories($records, $indentation)
+    {
+        foreach ($records as $record) {
+            $this->categories[] = array('id' => $record->id, 'name' => $record->getTree());
+            if (count($record->childs))
+                $this->getCategories($record->childs, $indentation + 1);
+        }
+    }
+    public function restore($id)
+    {
+        try {
+            $member = \App\Models\Member::findOrFail($id);
+
+            $member->update([
+                'is_archived' => false,
+                'archived_date' => null,
+                'status' => '',
+                'enabled' => true
+            ]);
+
+            updateMemberData($id);
+
+            session()->flash('success', 'Membro riattivato con successo');
+
+            $this->emit('reload');
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore durante la riattivazione: ' . $e->getMessage());
+            Log::error('Restore member error', [
+                'member_id' => $id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        }
+    }
+
+    public function archive($id)
+    {
+        try {
+            $member = \App\Models\Member::findOrFail($id);
+
+            $member->update([
+                'is_deleted' => true,
+                'deleted_date' => now(),
+
+                'first_name' => 'Nome Archiviato',
+                'last_name' => 'Cognome Archiviato',
+
+                'fiscal_code' => null,
+                'email' => null,
+                'phone' => null,
+                'phone2' => null,
+                'phone3' => null,
+                'address' => null,
+                'zip_code' => null,
+
+                'birth_place' => null,
+                'birth_date' => null,
+                'birth_city_id' => null,
+                'birth_province_id' => null,
+                'birth_nation_id' => null,
+
+                'nation_id' => null,
+                'province_id' => null,
+                'city_id' => null,
+
+                'document_type' => null,
+                'document_number' => null,
+                'document_from' => null,
+                'document_expire_date' => null,
+                'document_files' => null,
+
+                'father_name' => null,
+                'mother_name' => null,
+                'father_email' => null,
+                'mother_email' => null,
+                'father_phone' => null,
+                'mother_phone' => null,
+                'father_fiscal_code' => null,
+                'mother_fiscal_code' => null,
+                'father_doc_number' => null,
+                'father_doc_type' => null,
+                'mother_doc_number' => null,
+                'mother_doc_type' => null,
+                'father_document_files' => null,
+                'mother_document_files' => null,
+
+                'image' => null,
+
+                'gender' => null,
+                'status' => 'archived',
+
+                'enabled' => false
+            ]);
+
+            updateMemberData($id);
+
+            session()->flash('success', 'Membro archiviato con successo');
+
+            $this->emit('reload');
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore durante l\'archiviazione: ' . $e->getMessage());
+            Log::error('Archive member error', [
+                'member_id' => $id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+        }
+    }
+}

+ 21 - 2
app/Http/Livewire/Profile.php

@@ -20,6 +20,7 @@ class Profile extends Component
     public $telefono;
     public $cellulare;
     public $password;
+    public $password_confirmation;
 
     public function boot()
     {
@@ -44,12 +45,28 @@ class Profile extends Component
 
     public function save()
     {
-        $this->validate([
+
+        $rules = [
             'name' => 'required',
             'cognome' => 'required',
             'email' => 'required|email',
             'password' => 'nullable|min:6',
-        ]);
+            'password_confirmation' => 'nullable|same:password'
+        ];
+
+        $messages = [
+            'name.required' => 'Il nome è obbligatorio',
+            'cognome.required' => 'Il cognome è obbligatorio',
+            'email.required' => 'La mail è obbligatoria',
+            'email.email' => 'La mail deve essere un indirizzo valido',
+            'email.unique' => 'Questa mail è già stata utilizzata',
+            'password.required' => 'La password è obbligatoria',
+            'password.min' => 'La password deve essere di almeno 6 caratteri',
+            'password_confirmation.required' => 'Ripeti la password inserita',
+            'password_confirmation.same' => 'Le password non coincidono',
+        ];
+
+        $this->validate($rules, $messages);
 
         $currentUser = Auth::user();
 
@@ -102,6 +119,7 @@ class Profile extends Component
 
             $this->editMode = false;
             $this->password = ''; // Clear password field
+            $this->password_confirmation = ''; // Clear password_confirmation field
 
         } catch (\Exception $e) {
             Log::error('Profile update failed', [
@@ -203,6 +221,7 @@ class Profile extends Component
         $this->telefono = '';
         $this->cellulare = '';
         $this->password = '';
+        $this->password_confirmation = '';
     }
 
     public function render()

+ 243 - 0
app/Http/Livewire/Profile.php.bak

@@ -0,0 +1,243 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+use Illuminate\Support\Facades\Mail;
+
+class Profile extends Component
+{
+    public $editMode = false;
+
+    public $name;
+    public $cognome;
+    public $email;
+    public $telefono;
+    public $cellulare;
+    public $password;
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function mount()
+    {
+        $currentUser = Auth::user();
+        $user = \App\Models\User::findOrFail($currentUser->id);
+        $this->name = $user->name;
+        $this->cognome = $user->cognome;
+        $this->email = $user->email;
+        $this->telefono = $user->telefono;
+        $this->cellulare = $user->cellulare;
+    }
+
+    public function enableEditMode()
+    {
+        $this->editMode = true;
+    }
+
+    public function save()
+    {
+        $this->validate([
+            'name' => 'required',
+            'cognome' => 'required',
+            'email' => 'required|email',
+            'password' => 'nullable|min:6',
+        ]);
+
+        $currentUser = Auth::user();
+
+        try {
+            // Update user in tenant database
+            $user = \App\Models\User::findOrFail($currentUser->id);
+            $oldEmail = $user->email;
+            $oldName = $user->name;
+            $passwordChanged = !empty($this->password);
+            $emailChanged = $oldEmail !== $this->email;
+            $nameChanged = $oldName !== $this->name;
+
+            $user->name = $this->name;
+            $user->cognome = $this->cognome;
+            $user->email = $this->email;
+            $user->telefono = $this->telefono;
+            $user->cellulare = $this->cellulare;
+
+            if ($passwordChanged) {
+                $user->password = Hash::make($this->password);
+            }
+
+            $user->save();
+
+            Log::info('Updated user in tenant database', [
+                'user_id' => $user->id,
+                'tenant_database' => DB::connection()->getDatabaseName(),
+                'email_changed' => $emailChanged,
+                'name_changed' => $nameChanged,
+                'password_changed' => $passwordChanged
+            ]);
+
+            // Update master database if needed
+            if ($emailChanged || $passwordChanged || $nameChanged) {
+                $masterUpdated = $this->updateMasterDatabase($currentUser, $oldEmail, $passwordChanged);
+            }
+
+            // Send password change notification if password was changed
+            if ($passwordChanged) {
+                $notificationSent = $this->sendPasswordChangeNotification($this->email, $this->name);
+
+                if ($notificationSent) {
+                    session()->flash('message', 'Profilo aggiornato con successo! Ti abbiamo inviato una email di conferma per la modifica della password.');
+                } else {
+                    session()->flash('message', 'Profilo aggiornato con successo! (Errore nell\'invio dell\'email di notifica)');
+                }
+            } else {
+                session()->flash('message', 'Profilo aggiornato con successo!');
+            }
+
+            $this->editMode = false;
+            $this->password = ''; // Clear password field
+
+        } catch (\Exception $e) {
+            Log::error('Profile update failed', [
+                'user_id' => $currentUser->id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+
+            session()->flash('error', 'Errore durante l\'aggiornamento: ' . $e->getMessage());
+        }
+    }
+    /**
+     * Update user information in master database
+     */
+    private function updateMasterDatabase($currentUser, $oldEmail, $passwordChanged)
+    {
+        try {
+            // Store current tenant connection info
+            $currentConnection = DB::getDefaultConnection();
+            $currentDatabase = DB::connection()->getDatabaseName();
+
+            Log::info('Updating master database', [
+                'current_connection' => $currentConnection,
+                'current_database' => $currentDatabase,
+                'user_id' => $currentUser->id,
+                'old_email' => $oldEmail,
+                'new_email' => $this->email
+            ]);
+
+            $masterConfig = [
+                'driver' => 'mysql',
+                'host' => env('DB_HOST', '127.0.0.1'),
+                'port' => env('DB_PORT', '3306'),
+                'database' => env('DB_DATABASE'),
+                'username' => env('DB_USERNAME'),
+                'password' => env('DB_PASSWORD'),
+                'charset' => 'utf8mb4',
+                'collation' => 'utf8mb4_unicode_ci',
+                'prefix' => '',
+                'strict' => true,
+                'engine' => null,
+            ];
+
+            config(['database.connections.master_temp' => $masterConfig]);
+
+            $updateData = [
+                'name' => $this->name,
+                'email' => $this->email
+            ];
+
+            if ($passwordChanged) {
+                $updateData['password'] = Hash::make($this->password);
+            }
+
+            $updated = DB::connection('master_temp')
+                ->table('users')
+                ->where('email', $oldEmail)
+                ->update($updateData);
+
+            if ($updated) {
+                Log::info('Successfully updated user in master database', [
+                    'old_email' => $oldEmail,
+                    'new_email' => $this->email,
+                    'password_changed' => $passwordChanged
+                ]);
+            } else {
+                Log::warning('No user found in master database to update', [
+                    'email' => $oldEmail
+                ]);
+            }
+
+            config(['database.default' => $currentConnection]);
+            DB::purge('master_temp');
+        } catch (\Exception $e) {
+            Log::error('Failed to update master database', [
+                'error' => $e->getMessage(),
+                'user_id' => $currentUser->id,
+                'old_email' => $oldEmail,
+                'new_email' => $this->email
+            ]);
+        }
+    }
+
+    public function cancel()
+    {
+        return redirect()->to('/dashboard');
+
+        // $this->editMode = false;
+        // $this->password = '';
+
+        // $this->mount();
+    }
+
+    private function resetInputFields()
+    {
+        $this->name = '';
+        $this->cognome = '';
+        $this->email = '';
+        $this->telefono = '';
+        $this->cellulare = '';
+        $this->password = '';
+    }
+
+    public function render()
+    {
+        return view('livewire.profile');
+    }
+
+    private function sendPasswordChangeNotification($email, $name)
+    {
+        try {
+            $emailData = [
+                'name' => $name,
+                'email' => $email,
+                'change_time' => now()->format('d/m/Y H:i'),
+                'ip_address' => request()->ip()
+            ];
+
+            Mail::send('emails.password-changed', $emailData, function ($message) use ($email, $name) {
+                $message->to($email, $name)
+                    ->subject('La tua password è stata modificata')
+                    ->from(config('mail.from.address'), config('mail.from.name'));
+            });
+
+            Log::info('Password change notification sent from profile', [
+                'email' => $email,
+                'name' => $name
+            ]);
+
+            return true;
+        } catch (\Exception $e) {
+            Log::error('Failed to send password change notification from profile', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+}

+ 38 - 22
app/Http/Livewire/Rate.php

@@ -15,8 +15,11 @@ class Rate extends Component
 
     public $member_id = 0;
     public $member_course_id = 0;
+    public $member_subscription_id = 0;
+    public $rate_from = 'course';
 
     public $mc = null;
+    public $ms = null;
 
     public $filterStatus = '';
     public $hasFilter = false;
@@ -65,10 +68,11 @@ class Rate extends Component
         // Load members for the dropdown
         $this->member_id = isset($_GET["member_id"]) ? $_GET["member_id"] : 0;
         $this->member_course_id = isset($_GET["member_course_id"]) ? $_GET["member_course_id"] : 0;
+
         if ($this->member_id > 0 && $this->member_course_id > 0)
         {
             $this->mc = \App\Models\MemberCourse::findOrFail($this->member_course_id);
-            $this->detail = $this->mc->member->first_name . " " . $this->mc->member->last_name;
+            $this->detail = $this->mc->course->getDetailsName() . "<br/>" .  $this->mc->member->first_name . " " . $this->mc->member->last_name;
 
             $this->price_list = [];
             $c = $this->mc->course;
@@ -89,34 +93,47 @@ class Rate extends Component
             }
 
             $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
+
+            $this->rate_from = 'course';
         }
 
-        //$this->course_subscriptions = \App\Models\CourseSubscription::select('*')->where('enabled', true)->get();
+        $this->member_subscription_id = isset($_GET["member_subscription_id"]) ? $_GET["member_subscription_id"] : 0;
+        if ($this->member_id > 0 && $this->member_subscription_id > 0)
+        {
+            $this->ms = \App\Models\MemberSubscription::findOrFail($this->member_subscription_id);
+            $this->detail = $this->ms->subscription->name . "<br/>" .  $this->ms->member->first_name . " " . $this->ms->member->last_name;
 
+            $this->price_list = [];
+            $s = $this->ms->subscription;
+            if ($s->prices != null)
+            {
+                foreach(json_decode($s->prices) as $z)
+                {
+                    $this->price_list[$z->course_subscription_id] = $z->price;
+                }
+            }
 
+            $course_subscription_ids = [];
+            if ($s->prices != null) {
+                foreach (json_decode($s->prices) as $z) {
+                    if ($z->price > 0)
+                        $course_subscription_ids[] = $z->course_subscription_id;
+                }
+            }
+
+            $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
+            
+            $this->rate_from = 'subscription';
+        }
     }
 
     public function render()
     {
-
-        /*if ($this->hasFilter)
-        {
-            $r = \App\Models\Receipt::with('member');
-            if ($this->filterStatus != '')
-                $r = $r->where('status', $this->filterStatus);
-            if ($this->filterFrom != '')
-                $r = $r->where('date', '>=', $this->filterFrom);
-            if ($this->filterTo != '')
-                $r = $r->where('date', '<=', $this->filterTo);
-            if ($this->filteredMemberId != '')
-                $r = $r->where('member_id', $this->filteredMemberId);
-
-            $this->records = $r->get();
-        }
-        else
-        {*/
+        if ($this->member_id > 0 && $this->member_course_id > 0) {
             $this->records = \App\Models\Rate::with('member')->where('member_course_id', $this->member_course_id)->where('member_id', $this->member_id)->orderBy('date')->get();
-        //}
+        } elseif ($this->member_id > 0 && $this->member_subscription_id > 0) {
+            $this->records = \App\Models\Rate::with('member')->where('member_subscription_id', $this->member_subscription_id)->where('member_id', $this->member_id)->orderBy('date')->get();
+        }
 
         $this->disabled = [];
         foreach($this->records as $r)
@@ -126,8 +143,6 @@ class Rate extends Component
             }
         }
 
-        //$this->emit('load-data-table');
-
         return view('livewire.rate');
     }
 
@@ -171,6 +186,7 @@ class Rate extends Component
                     $rate = new \App\Models\Rate();
                     $rate->member_id = $this->member_id;
                     $rate->member_course_id = $this->member_course_id;
+                    $rate->member_subscription_id = $this->member_subscription_id;
                     $rate->course_subscription_id = $this->course_subscription_id;
                     $rate->price = currencyToDouble($this->price);
                     $rate->date = $this->date;

+ 275 - 0
app/Http/Livewire/Rate.php.bak

@@ -0,0 +1,275 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Barryvdh\DomPDF\Facade\Pdf;
+use App\Models\Member;
+use App\Http\Middleware\TenantMiddleware;
+use Illuminate\Support\Facades\DB;
+
+
+class Rate extends Component
+{
+    public $records;
+
+    public $member_id = 0;
+    public $member_course_id = 0;
+
+    public $mc = null;
+
+    public $filterStatus = '';
+    public $hasFilter = false;
+    public $filterFrom = '', $filterTo = '';
+    public $filteredMemberId = '';
+    public $members = [];
+    public $detail = '';
+
+    public $course_subscription_id;
+    public $price;
+    public $date;
+    public $month = '';
+    public $months = [];
+    public $disabled = [];
+    public $course_subscriptions = [];
+    public $price_list = [];
+    public $type = '';
+
+    public $errorMsg = '';
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    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 mount()
+    {
+        // Load members for the dropdown
+        $this->member_id = isset($_GET["member_id"]) ? $_GET["member_id"] : 0;
+        $this->member_course_id = isset($_GET["member_course_id"]) ? $_GET["member_course_id"] : 0;
+        if ($this->member_id > 0 && $this->member_course_id > 0)
+        {
+            $this->mc = \App\Models\MemberCourse::findOrFail($this->member_course_id);
+            $this->detail = $this->mc->member->first_name . " " . $this->mc->member->last_name;
+
+            $this->price_list = [];
+            $c = $this->mc->course;
+            if ($c->prices != null)
+            {
+                foreach(json_decode($c->prices) as $z)
+                {
+                    $this->price_list[$z->course_subscription_id] = $z->price;
+                }
+            }
+
+            $course_subscription_ids = [];
+            if ($c->prices != null) {
+                foreach (json_decode($c->prices) as $z) {
+                    if ($z->price > 0)
+                        $course_subscription_ids[] = $z->course_subscription_id;
+                }
+            }
+
+            $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
+        }
+
+        //$this->course_subscriptions = \App\Models\CourseSubscription::select('*')->where('enabled', true)->get();
+
+
+    }
+
+    public function render()
+    {
+
+        /*if ($this->hasFilter)
+        {
+            $r = \App\Models\Receipt::with('member');
+            if ($this->filterStatus != '')
+                $r = $r->where('status', $this->filterStatus);
+            if ($this->filterFrom != '')
+                $r = $r->where('date', '>=', $this->filterFrom);
+            if ($this->filterTo != '')
+                $r = $r->where('date', '<=', $this->filterTo);
+            if ($this->filteredMemberId != '')
+                $r = $r->where('member_id', $this->filteredMemberId);
+
+            $this->records = $r->get();
+        }
+        else
+        {*/
+            $this->records = \App\Models\Rate::with('member')->where('member_course_id', $this->member_course_id)->where('member_id', $this->member_id)->orderBy('date')->get();
+        //}
+
+        $this->disabled = [];
+        foreach($this->records as $r)
+        {
+            foreach (json_decode($r->months) as $m) {
+                $this->disabled[] = $m;
+            }
+        }
+
+        //$this->emit('load-data-table');
+
+        return view('livewire.rate');
+    }
+
+    public function updatedCourseSubscriptionId()
+    {
+        $this->price = 0;
+        if (isset($this->price_list[$this->course_subscription_id]))
+            $this->price = $this->price_list[$this->course_subscription_id];
+
+        $this->type = '';
+        if ($this->course_subscription_id > 0)
+            $this->type = \App\Models\CourseSubscription::findOrFail($this->course_subscription_id)->months;
+    }
+
+    public function add()
+    {
+
+        $this->errorMsg = '';
+        if ($this->date == '')
+        {
+            $this->errorMsg = 'La data di scadenza è obbligatoria';
+        }
+        else
+        {
+
+            if (!$this->course_subscription_id > 0)
+            {
+                $this->errorMsg = 'Devi selezionare un tipo di abbonamento';
+            }
+            else
+            {
+
+                if ($this->type > 1 && $this->type < sizeof($this->months))
+                {
+                    // $this->errorMsg = 'Hai selezionato un numero di mesi errato in base all\'abbonamento selezionato' . $this->type .'.'. sizeof($this->months);
+                    $this->errorMsg = 'Mesi selezionati superiori a quelli previsti dall’abbonamento';
+                }
+                elseif ($this->type == 1 && !$this->month) {
+                    $this->errorMsg = 'Seleziona un mese';
+                } else {
+                    $rate = new \App\Models\Rate();
+                    $rate->member_id = $this->member_id;
+                    $rate->member_course_id = $this->member_course_id;
+                    $rate->course_subscription_id = $this->course_subscription_id;
+                    $rate->price = currencyToDouble($this->price);
+                    $rate->date = $this->date;
+                    if ($this->type == '1')
+                        $this->months[] = $this->month;
+                    $rate->months = json_encode($this->months);
+                    $rate->note = '';
+                    $rate->status = 0;
+                    $rate->save();
+
+                    $this->course_subscription_id = null;
+                    $this->price = 0;
+                    $this->date = null;
+                    $this->month = '';
+                    $this->months = [];
+
+                    $this->emit('close-popup');
+                }
+            }
+
+        }
+
+    }
+
+    public function printReceipt($id)
+    {
+        $this->emit('load-data-table');
+        $receipt = \App\Models\Receipt::findOrFail($id);
+        //$pdf = PDF::loadView('pdf/receipt', array('datas' => $datas, 'from' => $x, 'to' => $y, 'who' => '', 'matricola' => $matricola));
+        $pdf = PDF::loadView('receipt', array('receipt' => $receipt));//->output();
+        return $pdf->stream('aaa.pdf');
+
+    }
+
+    public function search()
+    {
+        $this->hasFilter = true;
+    }
+
+    public function disableSearch()
+    {
+        $this->filterStatus = "";
+        $this->filterTo = '';
+        $this->filterFrom = '';
+        $this->filteredMemberId = '';
+        $this->hasFilter = false;
+    }
+
+    public function delete($id)
+    {
+        try{
+            \App\Models\Rate::find($id)->delete();
+            //$this->emit('load-data-table');
+            session()->flash('success',"Rata eliminata");
+        }catch(\Exception $e){
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function deleteMultiple($ids)
+    {
+        try{
+            foreach($ids as $id)
+            {
+                \App\Models\Rate::find($id)->delete();
+            }
+            //$this->emit('load-data-table');
+            session()->flash('success',"Rata eliminata");
+        }catch(\Exception $e){
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function suspendMultiple($ids)
+    {
+        try{
+            foreach($ids as $id)
+            {
+                $r = \App\Models\Rate::find($id);
+                $r->status = $r->status == 2 ? 0 : 2;
+                $r->save();
+            }
+
+            //$this->emit('load-data-table');
+            session()->flash('success',"Rata eliminata");
+        }catch(\Exception $e){
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function addDeleteMonth($m)
+    {
+        if (!in_array($m, $this->months)) {
+            $this->months[] = $m;
+        } else {
+            if (($key = array_search($m, $this->months)) !== false) {
+                unset($this->months[$key]);
+                $this->months = array_values($this->months);
+            }
+        }
+    }
+}

+ 205 - 40
app/Http/Livewire/RecordIN.php

@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Mail;
 use Illuminate\Contracts\Validation\Validator;
 use Illuminate\Support\Facades\Log;
 use App\Http\Middleware\TenantMiddleware;
+use Illuminate\Support\Carbon;
 
 class RecordIN extends Component
 {
@@ -49,9 +50,11 @@ class RecordIN extends Component
     }
 
     public $courseId = 0;
+    public $subscriptionId = 0;
+    public $course = null;
+    public $subscription = null;
     public $rateId = 0;
     public $months = array();
-    
 
     public $records, $dataId, $member_id, $supplier_id,
         $causal_id,
@@ -75,7 +78,7 @@ class RecordIN extends Component
 
     public $currentReceip;
 
-    public $filterMember = 0, $filterPaymentMethod = 0, $filterCausals = 0, $filterFrom = '', $filterTo = '', $filterCommercial = 0;
+    public $filterMember = 0, $filterPaymentMethod = 0, $filterCausals = 0, $filterCourses = 0, $filterFrom = '', $filterTo = '', $filterCommercial = 0;
 
     public $hasFilter = false;
 
@@ -100,6 +103,8 @@ class RecordIN extends Component
     public $newMemberFiscalCodeExist = false;
 
     public $causals = array();
+    public $courses = array();
+    public $subscriptions = array();
     public $payments = array();
     public $banks = array();
     public $members = array();
@@ -109,6 +114,8 @@ class RecordIN extends Component
 
     public $fromCourse = false;
 
+    public $backToRates = false;
+
     protected $rules = [
         'member_id' => 'required',
         'payment_method_id' => 'required',
@@ -164,6 +171,15 @@ class RecordIN extends Component
     public function hydrate()
     {
         $this->emit('load-select');
+
+        foreach ($this->rows as $i => $row) {
+            if (isset($row['course']) && is_array($row['course']) && isset($row['course']['id'])) {
+                $this->rows[$i]['course'] = \App\Models\Course::find($row['course']['id']);
+            }
+            if (isset($row['subscription']) && is_array($row['subscription']) && isset($row['subscription']['id'])) {
+                $this->rows[$i]['subscription'] = \App\Models\Subscription::find($row['subscription']['id']);
+            }
+        }
     }
 
     /*public function updated() {
@@ -261,10 +277,12 @@ class RecordIN extends Component
         $this->currentReceip = null;
         $this->parent = '';
         $this->courseId = 0;
+        $this->subscriptionId = 0;
         $this->rateId = 0;
+        $this->backToRates = false;
         $this->months = array();
         $this->rows = array();
-        $this->rows[] = array('causal_id' => isset($_GET["causalId"]) ? $_GET["causalId"] : null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0, 'sconto' => 0);
+        $this->rows[] = array('causal_id' => isset($_GET["causalId"]) ? $_GET["causalId"] : null, 'course_id' => null, 'course' => null, 'subscription_id' => null, 'subscription' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0, 'sconto' => 0);
         $this->corrispettivo = [];
         $this->emit('load-data-table');
     }
@@ -331,6 +349,22 @@ class RecordIN extends Component
         }
     }
 
+    public function getCourses($records, $indentation)
+    {
+        foreach ($records as $record) {
+            $this->courses[] = array('id' => $record->id, 'name' => $record->getDetailsName(), 'text' => $record->getDetailsName(), 'level' => $indentation);
+            if (count($record->childs))
+                $this->getCourses($record->childs, $indentation + 1);
+        }
+    }
+
+    public function getSubscriptions($records, $indentation)
+    {
+        foreach ($records as $record) {
+            $this->subscriptions[] = array('id' => $record->id, 'name' => $record->name, 'text' => $record->name, 'level' => $indentation);
+        }
+    }
+
     public function mount()
     {
 
@@ -339,6 +373,8 @@ class RecordIN extends Component
         }
 
         $this->causals = array();
+        $this->courses = array();
+        $this->subscriptions = array();
 
         $this->multiMonthFrom = date("n");
         $this->multiYearFrom = date("Y");
@@ -351,6 +387,9 @@ class RecordIN extends Component
 
         $this->getCausale(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->where('type', 'IN')->get(), 0);
 
+        $this->getCourses(\App\Models\Course::where('parent_id', null)->get(), 0);
+        $this->getSubscriptions(\App\Models\Subscription::get(), 0);
+
         //$this->buildTree(\App\Models\Causal::all(), null);
 
         $this->refreshMembers();
@@ -385,14 +424,28 @@ class RecordIN extends Component
                 $this->edit($_GET["id"]);
             }
             $count = 0;
-            if (isset($_GET["courseId"])) {
+            if (isset($_GET["courseId"]) && $_GET["courseId"] > 0) {
+                $this->fromCourse = true;
                 $this->courseId = $_GET["courseId"];
                 $mc = \App\Models\MemberCourse::findOrFail($this->courseId);
-                $course = \App\Models\Course::findOrFail($mc->course_id);
+                $this->course = \App\Models\Course::findOrFail($mc->course_id);
+                $this->rows[0]['course_id'] = $this->course->id;
+                $this->rows[0]['course'] = $this->course;
+            }
+            if (isset($_GET["subscriptionId"]) && $_GET["subscriptionId"] > 0) {
+                $this->fromCourse = true;
+                $this->subscriptionId = $_GET["subscriptionId"];
+                $ms = \App\Models\MemberSubscription::findOrFail($this->subscriptionId);
+                $this->subscription = \App\Models\Subscription::findOrFail($ms->subscription_id);
+                $this->rows[0]['subscription_id'] = $this->subscription->id;
+                $this->rows[0]['subscription'] = $this->subscription;
             }
             if (isset($_GET["rateId"])) {
                 $this->rateId = $_GET["rateId"];
             }
+            if (isset($_GET["backToRates"])) {
+                $this->backToRates = true;
+            }
             if (isset($_GET["months"]) && $_GET["months"] != '') {
 
                 $this->fromCourse = true;
@@ -404,37 +457,62 @@ class RecordIN extends Component
                 $this->months = $months;
                 //$this->rows[0]["amount"] = formatPrice($price * sizeof($months));
                 $this->rows[0]["amount"] = formatPrice($price);
-                foreach ($months as $idx => $m) {
-                    $this->rows[0]["when"][$idx]["month"] = $m;
-
-                    if ($m >= date("m"))
-                        $this->rows[0]["when"][$idx]["year"] = date("Y");
-                    else
-                        $this->rows[0]["when"][$idx]["year"] = date("Y") + 1;
-                    /*if (env('FISCAL_YEAR_MONTH_FROM', 1) > 1)
-                    {
-                        if ($m > date("M"))
-                            $this->rows[0]["when"][$idx]["year"] = $m < env('FISCAL_YEAR_MONTH_FROM', 1) ? (date("Y")) : date("Y" + 1);
-                        else
-                            $this->rows[0]["when"][$idx]["year"] = $m > env('FISCAL_YEAR_MONTH_TO', 1) ? (date("Y")) : date("Y" + 1);
-                    }
-                    else
-                    {
 
+                $rates = [];
+                if (isset($_GET["rateId"])) {
+                    try {
+                        $rateIds = explode(",", $_GET["rateId"]);
+                        $rates = \App\Models\Rate::whereIn('id', $rateIds)->orderBy('date', 'asc')->get();
+                    } catch (\Throwable $e) {
                     }
 
-                    if ($m > date("M"))
-                        $this->rows[0]["when"][$idx]["year"] = $m < env('FISCAL_YEAR_MONTH_FROM', 1) ? (date("Y")) : date("Y" + 1);
-                    else
-                        $this->rows[0]["when"][$idx]["year"] = $m > env('FISCAL_YEAR_MONTH_TO', 1) ? (date("Y")) : date("Y" + 1);
-                    */
-                    if ($idx > 0) {
-                        if ($this->rows[0]["when"][$idx - 1]["year"] != $this->rows[0]["when"][$idx]["year"])
-                            $desc .= " " . $this->rows[0]["when"][$idx - 1]["year"] . " ";
+                    $order = [9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8];
+                    $index = 0;
+                    foreach ($rates as $rate) {
+                        $rate_date = Carbon::createFromFormat("Y-m-d H:i:s", $rate->date);
+
+                        $rate_months = json_decode($rate->months, true);
+                        usort($rate_months, function ($a, $b) use ($order) {
+                            $posA = array_search($a, $order);
+                            $posB = array_search($b, $order);
+                            return $posA <=> $posB;
+                        });
+
+                        foreach ($rate_months as $month) {
+                            $rate_year = $rate_date->year;
+                            $rate_month = $rate_date->month;
+                            if ($rate_month < 9 && $rate_month >= 1) {
+                                $rate_year -= 1;
+                            }
+
+                            if ($month >= 9 && $month <= 12) {
+                            } elseif ($month < 9 && $month >= 1) {
+                                $rate_year += 1;
+                            }
+
+                            $this->rows[0]["when"][$index]["month"] = $month;
+                            $this->rows[0]["when"][$index]["year"] = $rate_year;
+                            $index++;
+
+                            $desc .= " " . $this->getMonth($month) . " " . $rate_year;
+                        }
                     }
-                    $desc .= " " . $this->getMonth($m); // . " " . $this->rows[0]["when"][$idx]["year"];
                 }
-                $desc .= " " . $this->rows[0]["when"][$idx]["year"];
+
+                // foreach ($months as $idx => $m) {
+                //     $this->rows[0]["when"][$idx]["month"] = $m;
+                //     if ($m >= date("m"))
+                //         $this->rows[0]["when"][$idx]["year"] = date("Y");
+                //     else
+                //         $this->rows[0]["when"][$idx]["year"] = date("Y") + 1;
+
+                //     if ($idx > 0) {
+                //         if ($this->rows[0]["when"][$idx - 1]["year"] != $this->rows[0]["when"][$idx]["year"])
+                //             $desc .= " " . $this->rows[0]["when"][$idx - 1]["year"] . " ";
+                //     }
+                //     $desc .= " " . $this->getMonth($m); // . " " . $this->rows[0]["when"][$idx]["year"];
+                // }
+                // $desc .= " " . $this->rows[0]["when"][$idx]["year"];
                 $this->rows[0]["note"] = $desc;
                 $count += 1;
             }
@@ -443,15 +521,38 @@ class RecordIN extends Component
             if (isset($_GET["createSubscription"]) && $_GET["createSubscription"] == 1) {
 
                 $this->createSubscription = 1;
-                $this->courseId = $_GET["courseId"];
+
+                if (isset($_GET["courseId"]) && $_GET["courseId"] > 0) {
+                    $this->courseId = $_GET["courseId"];
+                }
+                if (isset($_GET["subscriptionId"]) && $_GET["subscriptionId"] > 0) {
+                    $this->subscriptionId = $_GET["subscriptionId"];
+                }
+
+                if ($this->courseId && $this->courseId > 0) {
+                    $mc = \App\Models\MemberCourse::findOrFail($this->courseId);
+                    $this->course = \App\Models\Course::findOrFail($mc->course_id);
+                }
+                if ($this->subscriptionId && $this->subscriptionId > 0) {
+                    $ms = \App\Models\MemberSubscription::findOrFail($this->subscriptionId);
+                    $this->subscription = \App\Models\Subscription::findOrFail($ms->subscription_id);
+                }
                 $this->rateId = $_GET["rateId"];
                 $price = $_GET["subscription_price"] / 100 * 100;
                 $this->refreshAfter = 1;
                 if ($count == 1)
-                    $this->rows[] = array('causal_id' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0);
+                    $this->rows[] = array('causal_id' => null, 'course_id' => null, 'course' => null, 'subscription_id' => null, 'subscription' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0);
                 $this->rows[$count]["causal_id"] = null;
+                $this->rows[$count]["course_id"] = $this->course->id ?? null;
+                $this->rows[$count]["course"] = $this->course ?? null;
+                $this->rows[$count]["subscription_id"] = $this->subscription->id ?? null;
+                $this->rows[$count]["subscription"] = $this->subscription ?? null;
                 $this->rows[$count]["amount"] = formatPrice($price);
-                $this->rows[$count]["note"] = "Pagamento iscrizione " . $course->name;
+                $note = implode(", ", array_filter([
+                    $this->course ? $this->course->name : "",
+                    $this->subscription ? $this->subscription->name : ""
+                ]));
+                $this->rows[$count]["note"] = "Pagamento iscrizione " . $note;
                 if (isset($_GET["subCausalId"])) {
                     $this->refreshAfter = 1;
                     $this->rows[$count]["causal_id"] = $_GET["subCausalId"];
@@ -471,6 +572,8 @@ class RecordIN extends Component
         $this->filterMember = 0;
         $this->filterPaymentMethod = 0;
         $this->filterCausals = 0;
+        $this->filterCourses = 0;
+        $this->filterSubscriptions = 0;
         $this->filterTo = '';
         $this->filterFrom = '';
         $this->filterCommercial = 0;
@@ -480,7 +583,6 @@ class RecordIN extends Component
 
     public function render()
     {
-
         $datas = [];
         return view('livewire.records_in', ['datas' => $datas]);
     }
@@ -537,10 +639,21 @@ class RecordIN extends Component
                         'financial_movement' => $this->financial_movement,
                         'deleted' => $this->deleted
                     ]);
+                        
+                    if ($this->courseId && $this->courseId > 0) {
+                        $mc = \App\Models\MemberCourse::findOrFail($this->courseId);
+                        $this->course = \App\Models\Course::findOrFail($mc->course_id);
+                    }
+                    if ($this->subscriptionId && $this->subscriptionId > 0) {
+                        $ms = \App\Models\MemberSubscription::findOrFail($this->subscriptionId);
+                        $this->subscription = \App\Models\Subscription::findOrFail($ms->subscription_id);
+                    }
 
                     \App\Models\RecordRow::create([
                         'record_id' => $record->id,
                         'causal_id' => $this->corrispettivo_causal_id,
+                        'course_id' => $this->course->id ?? null,
+                        'subscription_id' => $this->subscription->id ?? null,
                         'note' => '',
                         'amount' => $price,
                         'vat_id' => null,
@@ -660,6 +773,8 @@ class RecordIN extends Component
                 \App\Models\RecordRow::create([
                     'record_id' => $this->dataId,
                     'causal_id' => $row["causal_id"],
+                    'course_id' => $row["course_id"],
+                    'subscription_id' => $row["subscription_id"],
                     'note' => $row["note"],
                     'amount' => $rowNet,
                     'prediscount_amount' => $rowAmount,
@@ -699,22 +814,27 @@ class RecordIN extends Component
             }
 
             if ($this->rateId > 0) {
-
                 $rates = explode(",", $this->rateId);
-                foreach($rates as $rid)
-                {
+                foreach ($rates as $rid) {
                     $rate = \App\Models\Rate::findOrFail($rid);
                     $rate->status = 1;
                     $rate->record_id = $record->id;
                     $rate->save();
                 }
+            }
 
+            if ($this->backToRates) {
+                if ($this->courseId > 0)
+                    return redirect()->to("/rates?member_id={$this->member_id}&member_course_id={$this->courseId}");
+                if ($this->subscriptionId > 0)
+                    return redirect()->to("/rates?member_id={$this->member_id}&member_subscription_id={$this->subscriptionId}");
             }
 
             session()->flash('success', 'Movimento creato');
             $this->resetFields();
             $this->add = false;
             $this->isDuplicate = false;
+
             $this->emit('setEdit', false);
         } catch (\Exception $ex) {
             Log::error("Error in store method: " . $ex->getMessage());
@@ -789,7 +909,7 @@ class RecordIN extends Component
                 $this->add = false;
 
                 $rows = \App\Models\RecordRow::where('record_id', $this->dataId)
-                    ->select('causal_id', 'note', 'commercial', 'when', 'amount', 'prediscount_amount', 'sconto', 'vat_id')
+                    ->select('causal_id', 'course_id', 'subscription_id', 'note', 'commercial', 'when', 'amount', 'prediscount_amount', 'sconto', 'vat_id')
                     ->get();
 
                 $this->rows = [];
@@ -804,8 +924,28 @@ class RecordIN extends Component
                         }
                     }
 
+                    $course = null;
+                    if ($r->course_id) {
+                        try {
+                            $course = \App\Models\Course::findOrFail($r->course_id);
+                        } catch (\Throwable $th) {
+                        }
+                    }
+
+                    $subscription = null;
+                    if ($r->subscription_id) {
+                        try {
+                            $subscription = \App\Models\Subscription::findOrFail($r->subscription_id);
+                        } catch (\Throwable $th) {
+                        }
+                    }
+
                     $this->rows[] = [
                         'causal_id' => $causalId,
+                        'course_id' => $r->course_id,
+                        'course' => $course,
+                        'subscription_id' => $r->subscription_id,
+                        'subscription' => $subscription,
                         'note' => $r->note,
                         'commercial' => $r->commercial,
                         'when' => json_decode($r->when),
@@ -971,6 +1111,8 @@ class RecordIN extends Component
                 \App\Models\RecordRow::create([
                     'record_id' => $this->dataId,
                     'causal_id' => $row["causal_id"],
+                    'course_id' => $row["course_id"],
+                    'subscription_id' => $row["subscription_id"],
                     'note' => $row["note"],
                     'vat_id' => $row["vat_id"],
                     'amount' => $rowNet,
@@ -997,6 +1139,14 @@ class RecordIN extends Component
                 }
             }
             session()->flash('success', 'Movimento aggiornato');
+
+            if ($this->backToRates) {
+                if ($this->courseId > 0)
+                    return redirect()->to("/rates?member_id={$this->member_id}&member_course_id={$this->courseId}");
+                if ($this->subscriptionId > 0)
+                    return redirect()->to("/rates?member_id={$this->member_id}&member_subscription_id={$this->subscriptionId}");
+            }
+
             $this->resetFields();
             $this->update = false;
             $this->isDuplicate = false;
@@ -1088,6 +1238,19 @@ class RecordIN extends Component
                 $c->save();
             }
 
+            if ($r->member_subscription_id > 0) {
+                $months = json_decode($r->months);
+                $s = \App\Models\MemberSubscription::findOrFail($r->member_subscription_id);
+                $xxx = json_decode($s->months);
+                foreach ($xxx as $idx => $mm) {
+                    if (in_array($mm->m, $months)) {
+                        $xxx[$idx]->status = "";
+                    }
+                }
+                $s->months = json_encode($xxx);
+                $s->save();
+            }
+
             $r->deleted = true;
             $r->save();
 
@@ -1218,6 +1381,8 @@ class RecordIN extends Component
                     \App\Models\ReceiptRow::create([
                         'receip_id' => $receipt->id,
                         'causal_id' => $row["causal_id"],
+                        'course_id' => $row["course_id"],
+                        'subscription_id' => $row["subscription_id"],
                         'note' => $row["note"],
                         'vat_id' => $row["vat_id"],
                         'amount' => $rowNet,
@@ -1259,7 +1424,7 @@ class RecordIN extends Component
 
     public function addRow()
     {
-        $this->rows[] = array('causal_id' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0);
+        $this->rows[] = array('causal_id' => null, 'course_id' => null, 'course' => null, 'subscription_id' => null, 'subscription' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0);
         $this->emit('load-select');
     }
 

+ 1452 - 0
app/Http/Livewire/RecordIN.php.bak

@@ -0,0 +1,1452 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Livewire\WithPagination;
+use Barryvdh\DomPDF\Facade\Pdf;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Contracts\Validation\Validator;
+use Illuminate\Support\Facades\Log;
+use App\Http\Middleware\TenantMiddleware;
+
+class RecordIN extends Component
+{
+    use WithPagination;
+    protected $paginationTheme = 'bootstrap';
+
+    protected $listeners = ['setCausal' => 'setCausal', 'refreshMembers' => 'refreshMembers', 'scontoChanged' => 'updateSconto'];
+
+    public $sortField = 'date';
+    public $sortAsc = false;
+
+    public $typeIN = "IN";
+
+    public $fromPage = '';
+
+    public $isDuplicate = false;
+    public $multiP = false;
+    public $from, $to;
+
+    public $multiMonthTo = 0;
+    public $multiYearTo = 0;
+    public $multiMonthFrom = 0;
+    public $multiYearFrom = 0;
+    public $createSubscription = 0;
+
+    public $corrispettivo_causal_id = 0;
+
+    public function sortBy($field)
+    {
+        if ($this->sortField === $field) {
+            $this->sortAsc = ! $this->sortAsc;
+        } else {
+            $this->sortAsc = true;
+        }
+
+        $this->sortField = $field;
+    }
+
+    public $courseId = 0;
+    public $rateId = 0;
+    public $months = array();
+    
+
+    public $records, $dataId, $member_id, $supplier_id,
+        $causal_id,
+        $payment_method_id,
+        $destination_id,
+        $date,
+        $month,
+        $year,
+        $type,
+        $deleted,
+        $financial_movement,
+        $amount,
+        $vat,
+        $virtual,
+        $note,
+        $parent,
+        $corrispettivo_fiscale,
+        $commercial, $update = false, $add = false;
+
+    public $corrispettivo = [];
+
+    public $currentReceip;
+
+    public $filterMember = 0, $filterPaymentMethod = 0, $filterCausals = 0, $filterFrom = '', $filterTo = '', $filterCommercial = 0;
+
+    public $hasFilter = false;
+
+    public $total = 0;
+
+    public $selectedFilter = '';
+
+    public $multipleIds = [];
+    public $multipleAction = '';
+
+    public $first = true;
+
+    public $selectId = 0;
+    public $refreshAfter = 0;
+    public $sconto = 0;
+    public $canSave = true;
+    public $newMemberFirstName = '';
+    public $newMemberLastName = '';
+    public $newMemberEmail = '';
+    public $newMemberToComplete = false;
+    public $newMemberFiscalCode = '';
+    public $newMemberFiscalCodeExist = false;
+
+    public $causals = array();
+    public $payments = array();
+    public $banks = array();
+    public $members = array();
+    public $vats = array();
+
+    public $rows = array();
+
+    public $fromCourse = false;
+
+    protected $rules = [
+        'member_id' => 'required',
+        'payment_method_id' => 'required',
+        'rows.*.causal_id' => 'required',
+        'rows.*.amount' => 'required'
+    ];
+
+    protected $messages = [
+        'member_id.required' => 'La persona è obbligatorio',
+        'payment_method_id.required' => 'Il metodo di pagamento è obbligatorio',
+        'rows.*.causal_id.required' => 'La causale è obbligatoria',
+        'rows.*.amount.required' => 'L\'importo è obbligatorio',
+    ];
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function updatedMemberId()
+    {
+        $this->emit('refresh');
+        if ($this->member_id > 0) {
+            $member = \App\Models\Member::findOrFail($this->member_id);
+            if (!$member->isAdult()) {
+                if ($member->father_name != '')
+                    $this->parent = $member->father_name . " (C.F. " . $member->father_fiscal_code . ")";
+                else if ($member->mother_name != '')
+                    $this->parent = $member->mother_name . " (C.F. " . $member->mother_fiscal_code . ")";
+                else
+                    $this->parent = '';
+            }
+            $this->virtual = $member->getMoney();
+            $this->newMemberFirstName = '';
+            $this->newMemberLastName = '';
+            $this->newMemberEmail = '';
+            $this->newMemberToComplete = false;
+            $this->newMemberFiscalCode = '';
+            $this->newMemberFiscalCodeExist = false;
+        }
+    }
+
+    public function updatedCausalId()
+    {
+        //$this->emit('refresh');
+    }
+
+    public function updatedDate()
+    {
+        //$this->emit('refresh');
+    }
+
+    public function hydrate()
+    {
+        $this->emit('load-select');
+    }
+
+    /*public function updated() {
+        $this->emit('refresh');
+    }*/
+
+    public function updatedPaymentMethodId()
+    {
+        //$this->emit('refresh');
+        $this->canSave = $this->checkCanSave();
+    }
+
+    public function updatedAmount()
+    {
+        // $this->emit('refresh');
+        $this->canSave = $this->checkCanSave();
+    }
+
+    public function updatedCommercial($value)
+    {
+        $this->emitSelf('refreshMembers');
+    }
+
+    public function updateSconto()
+    {
+        $totalSconto = 0;
+        foreach ($this->rows as $row) {
+            if (isset($row['sconto']) && $row['sconto'] != null && $row['sconto'] != "") {
+                $totalSconto += $this->currencyToDouble($row['sconto']);
+            }
+        }
+        $this->sconto = formatPrice($totalSconto);
+        Log::info("Total sconto updated to: " . $this->sconto);
+    }
+
+
+    public function checkCanSave()
+    {
+        $ret = true;
+        if ($this->payment_method_id != null) {
+            $payment_method = \App\Models\PaymentMethod::findOrFail($this->payment_method_id);
+            if ($payment_method->money) {
+                $from = str_replace(".", "", str_replace(",", "", number_format($this->virtual, 2)));
+                $this->from = $this->virtual;
+                $from = $this->from;
+                $to = str_replace(".", "", str_replace(",", "", $this->currencyToDouble($this->amount)));
+                $this->to = $this->amount;
+                $to = $this->to;
+                $ret = $from >= $to;
+            }
+        }
+        return $ret;
+    }
+
+    public function refreshMembers()
+    {
+        if ($this->add || $this->update) {
+            if ($this->commercial) {
+                $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])->orderBy('last_name')->orderBy('first_name')->get();
+            } else {
+                $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])->where('current_status', 2)->orWhere('current_status', 1)->orderBy('last_name')->orderBy('first_name')->get();
+            }
+        } else
+            $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])->orderBy('last_name')->orderBy('first_name')->get();
+    }
+
+    public function setAmount()
+    {
+        $tot = 0;
+        foreach ($this->rows as $r) {
+            $tot += $this->currencyToDouble($r["amount"]);
+        }
+        $this->amount = $tot;
+        $this->canSave = $this->checkCanSave();
+    }
+
+    public function resetFields()
+    {
+        $this->member_id = null;
+        $this->supplier_id = null;
+        $this->payment_method_id = null;
+        $this->destination_id = null;
+        $this->commercial = 0;
+        $this->corrispettivo_fiscale = false;
+        $this->date = date("Y-m-d");
+        $this->type = 'IN';
+        $this->deleted = false;
+        $this->financial_movement = false;
+        $this->newMemberFirstName = '';
+        $this->newMemberLastName = '';
+        $this->newMemberEmail = '';
+        $this->newMemberToComplete = false;
+        $this->newMemberFiscalCode = '';
+        $this->newMemberFiscalCodeExist = false;
+        $this->currentReceip = null;
+        $this->parent = '';
+        $this->courseId = 0;
+        $this->rateId = 0;
+        $this->months = array();
+        $this->rows = array();
+        $this->rows[] = array('causal_id' => isset($_GET["causalId"]) ? $_GET["causalId"] : null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0, 'sconto' => 0);
+        $this->corrispettivo = [];
+        $this->emit('load-data-table');
+    }
+
+    public function getMemberProperty()
+    {
+        $ret = null;
+        if ($this->member_id > 0) {
+            $ret = \App\Models\Member::findOrFail($this->member_id);
+        }
+        return $ret;
+    }
+
+    public function getCausalProperty()
+    {
+        $ret = null;
+        if ($this->causal_id > 0) {
+            $ret = \App\Models\Causal::findOrFail($this->causal_id);
+        }
+        return $ret;
+    }
+
+    public function getCausal($causal)
+    {
+        $ret = '';
+        if ($causal > 0) {
+            $ret = \App\Models\Causal::findOrFail($causal)->getTree();
+        }
+        return $ret;
+    }
+
+    public function getPaymentMethod($payment)
+    {
+        $ret = '';
+        if ($payment > 0) {
+            $ret = \App\Models\PaymentMethod::findOrFail($payment)->name;
+        }
+        return $ret;
+    }
+
+    function buildTree($records, $parentId = 0)
+    {
+        $this->causals = array();
+
+        foreach ($records as $record) {
+            if ($record->parent_id == $parentId) {
+                $children = $this->buildTree($record, $record->id);
+                if ($children) {
+                    $record->children = $children;
+                }
+                $this->causals[] = $record;
+            }
+        }
+
+        return $this->causals;
+    }
+
+    public function getCausale($records, $indentation)
+    {
+        foreach ($records as $record) {
+            $this->causals[] = array('id' => $record->id, 'name' => $record->getTree(), 'text' => $record->getTree(), 'level' => $indentation);
+            if (count($record->childs))
+                $this->getCausale($record->childs, $indentation + 1);
+        }
+    }
+
+    public function mount()
+    {
+
+        if (isset($_GET["from"])) {
+            $this->fromPage = $_GET["from"];
+        }
+
+        $this->causals = array();
+
+        $this->multiMonthFrom = date("n");
+        $this->multiYearFrom = date("Y");
+        $this->multiMonthTo = date("n");
+        $this->multiYearTo = date("Y");
+
+        $fisc = \App\Models\Causal::where('corrispettivo_fiscale', true)->first();
+        if ($fisc)
+            $this->corrispettivo_causal_id = $fisc->id;
+
+        $this->getCausale(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->where('type', 'IN')->get(), 0);
+
+        //$this->buildTree(\App\Models\Causal::all(), null);
+
+        $this->refreshMembers();
+        $this->payments = \App\Models\PaymentMethod::select('id', 'name')->where('enabled', true)->whereIn('type', array('ALL', 'IN'))->orderBy('name')->get();
+        $this->banks = \App\Models\Bank::select('id', 'name')->where('enabled', true)->whereIn('visibility', array('ALL', 'IN'))->orderBy('name')->get();
+        $this->vats = \App\Models\Vat::select('id', 'name', 'value')->orderBy('value')->get();
+
+        if ($this->first) {
+            if (isset($_GET["new"])) {
+                $this->refreshAfter = 1;
+                $this->add();
+            }
+            if (isset($_GET["memberId"])) {
+                $this->refreshAfter = 1;
+                $this->member_id = $_GET["memberId"];
+                $member = \App\Models\Member::findOrFail($this->member_id);
+                if (!$member->isAdult()) {
+                    if ($member->father_name != '')
+                        $this->parent = $member->father_name . " (C.F. " . $member->father_fiscal_code . ")";
+                    else if ($member->mother_name != '')
+                        $this->parent = $member->mother_name . " (C.F. " . $member->mother_fiscal_code . ")";
+                    else
+                        $this->parent = '';
+                }
+            }
+            if (isset($_GET["causalId"])) {
+                $this->refreshAfter = 1;
+                $this->causal_id = $_GET["causalId"];
+            }
+            if (isset($_GET["id"])) {
+                $this->refreshAfter = 1;
+                $this->edit($_GET["id"]);
+            }
+            $count = 0;
+            if (isset($_GET["courseId"])) {
+                $this->courseId = $_GET["courseId"];
+                $mc = \App\Models\MemberCourse::findOrFail($this->courseId);
+                $course = \App\Models\Course::findOrFail($mc->course_id);
+            }
+            if (isset($_GET["rateId"])) {
+                $this->rateId = $_GET["rateId"];
+            }
+            if (isset($_GET["months"]) && $_GET["months"] != '') {
+
+                $this->fromCourse = true;
+
+                $price = $_GET["price"] / 100 * 100;
+                $this->refreshAfter = 1;
+                $desc = "Pagamento";
+                $months = explode("|", $_GET["months"]);
+                $this->months = $months;
+                //$this->rows[0]["amount"] = formatPrice($price * sizeof($months));
+                $this->rows[0]["amount"] = formatPrice($price);
+                foreach ($months as $idx => $m) {
+                    $this->rows[0]["when"][$idx]["month"] = $m;
+
+                    if ($m >= date("m"))
+                        $this->rows[0]["when"][$idx]["year"] = date("Y");
+                    else
+                        $this->rows[0]["when"][$idx]["year"] = date("Y") + 1;
+                    /*if (env('FISCAL_YEAR_MONTH_FROM', 1) > 1)
+                    {
+                        if ($m > date("M"))
+                            $this->rows[0]["when"][$idx]["year"] = $m < env('FISCAL_YEAR_MONTH_FROM', 1) ? (date("Y")) : date("Y" + 1);
+                        else
+                            $this->rows[0]["when"][$idx]["year"] = $m > env('FISCAL_YEAR_MONTH_TO', 1) ? (date("Y")) : date("Y" + 1);
+                    }
+                    else
+                    {
+
+                    }
+
+                    if ($m > date("M"))
+                        $this->rows[0]["when"][$idx]["year"] = $m < env('FISCAL_YEAR_MONTH_FROM', 1) ? (date("Y")) : date("Y" + 1);
+                    else
+                        $this->rows[0]["when"][$idx]["year"] = $m > env('FISCAL_YEAR_MONTH_TO', 1) ? (date("Y")) : date("Y" + 1);
+                    */
+                    if ($idx > 0) {
+                        if ($this->rows[0]["when"][$idx - 1]["year"] != $this->rows[0]["when"][$idx]["year"])
+                            $desc .= " " . $this->rows[0]["when"][$idx - 1]["year"] . " ";
+                    }
+                    $desc .= " " . $this->getMonth($m); // . " " . $this->rows[0]["when"][$idx]["year"];
+                }
+                $desc .= " " . $this->rows[0]["when"][$idx]["year"];
+                $this->rows[0]["note"] = $desc;
+                $count += 1;
+            }
+
+
+            if (isset($_GET["createSubscription"]) && $_GET["createSubscription"] == 1) {
+
+                $this->createSubscription = 1;
+                $this->courseId = $_GET["courseId"];
+                $this->rateId = $_GET["rateId"];
+                $price = $_GET["subscription_price"] / 100 * 100;
+                $this->refreshAfter = 1;
+                if ($count == 1)
+                    $this->rows[] = array('causal_id' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0);
+                $this->rows[$count]["causal_id"] = null;
+                $this->rows[$count]["amount"] = formatPrice($price);
+                $this->rows[$count]["note"] = "Pagamento iscrizione " . $course->name;
+                if (isset($_GET["subCausalId"])) {
+                    $this->refreshAfter = 1;
+                    $this->rows[$count]["causal_id"] = $_GET["subCausalId"];
+                }
+            }
+        }
+        $this->first = false;
+    }
+
+    public function search()
+    {
+        $this->hasFilter = true;
+    }
+
+    public function disableSearch()
+    {
+        $this->filterMember = 0;
+        $this->filterPaymentMethod = 0;
+        $this->filterCausals = 0;
+        $this->filterTo = '';
+        $this->filterFrom = '';
+        $this->filterCommercial = 0;
+        $this->hasFilter = false;
+        $this->total = 0;
+    }
+
+    public function render()
+    {
+
+        $datas = [];
+        return view('livewire.records_in', ['datas' => $datas]);
+    }
+
+    public function executeMultipleAction()
+    {
+
+        if ($this->multipleAction == 'delete')
+            $this->multipleDelete();
+    }
+
+    public function add()
+    {
+
+        $this->emit('load-select');
+        //if ($this->hasFilter)
+        $this->emit('hide-search');
+        $this->resetFields();
+        $this->dataId = 0;
+        $this->add = true;
+        $this->update = false;
+        $this->emit('setEdit', true);
+    }
+
+    public function storeCorrispettivo()
+    {
+
+        $this->emit('refresh');
+
+        $rules = [
+            'member_id' => 'required'
+        ];
+
+        $this->validate($rules);
+
+
+
+        foreach ($this->payments as $p) {
+            if ($p->corrispettivo_fiscale) {
+                $price = isset($this->corrispettivo[$p->id]) ? $this->currencyToDouble($this->corrispettivo[$p->id]) : 0;
+
+                if ($price > 0) {
+
+                    $record = \App\Models\Record::create([
+                        'member_id' => $this->member_id,
+                        'supplier_id' => null,
+                        'payment_method_id' => $p->id,
+                        'destination_id' => $this->destination_id,
+                        'commercial' => $this->commercial,
+                        'corrispettivo_fiscale' => $this->corrispettivo_fiscale,
+                        'date' => $this->date,
+                        'type' => $this->type,
+                        'amount' => $price,
+                        'financial_movement' => $this->financial_movement,
+                        'deleted' => $this->deleted
+                    ]);
+
+                    \App\Models\RecordRow::create([
+                        'record_id' => $record->id,
+                        'causal_id' => $this->corrispettivo_causal_id,
+                        'note' => '',
+                        'amount' => $price,
+                        'vat_id' => null,
+                        'commercial' => $this->commercial,
+                        'when' => json_encode([])
+                    ]);
+                }
+            }
+        }
+
+        $this->resetFields();
+        $this->add = false;
+        $this->isDuplicate = false;
+        $this->emit('setEdit', false);
+    }
+
+    public function store($generate)
+    {
+        Log::info("Starting store method");
+        $this->emit('refresh');
+
+        $rules = [
+            'payment_method_id' => 'required',
+            'rows.*.causal_id' => 'required',
+            'rows.*.amount' => 'required'
+        ];
+
+        if (!$this->commercial)
+            $rules["member_id"] = 'required';
+
+        $this->validate($rules);
+
+        /* Validazione campi azienda per generazione ricevuta */
+        try {
+            if (!$this->financial_movement && !$this->commercial) {
+                $no_receipt_causal_azienda = false;
+                foreach ($this->rows as $row) {
+                    $no_receipt_causal_azienda = false;
+
+                    $cau = \App\Models\Causal::findOrFail($row["causal_id"]);
+                    if ($cau->no_receipt)
+                        $no_receipt_causal_azienda = true;
+                }
+
+                $azienda = \App\Models\Azienda::first();
+
+                $payment_method = \App\Models\PaymentMethod::findOrFail($this->payment_method_id);
+                if (!$payment_method->money) {
+                    if (!$no_receipt_causal_azienda) {
+                        if (!$azienda->isValid()) {
+                            session()->flash('error_ricevuta', implode("", array_map(function ($item) {
+                                return "<li>$item</li>";
+                            }, $azienda->validate())));
+                            return false;
+                        }
+                    }
+                }
+            }
+        } catch (\Exception $ex) {
+            Log::error("Error in store method: " . $ex->getMessage());
+            Log::error("Stack trace: " . $ex->getTraceAsString());
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+        /* END - Validazione campi azienda per generazione ricevuta */
+
+        try {
+
+
+            $totalGross = 0;
+            $totalSconto = 0;
+            $totalNet = 0;
+
+            foreach ($this->rows as $row) {
+                $rowAmount = $this->currencyToDouble($row["amount"]);
+                $rowSconto = isset($row['sconto']) ? $this->currencyToDouble($row['sconto']) : 0;
+                $rowNet = max(0, $rowAmount - $rowSconto);
+
+                $totalGross += $rowAmount;
+                $totalSconto += $rowSconto;
+                $totalNet += $rowNet;
+            }
+
+            Log::info("Total gross: " . $totalGross . ", Total sconto: " . $totalSconto . ", Total net: " . $totalNet);
+
+            $record = \App\Models\Record::create([
+                'member_id' => $this->member_id,
+                'supplier_id' => $this->supplier_id,
+                'payment_method_id' => $this->payment_method_id,
+                'destination_id' => $this->destination_id,
+                'commercial' => $this->commercial,
+                'corrispettivo_fiscale' => $this->corrispettivo_fiscale,
+                'date' => $this->date,
+                'type' => $this->type,
+                'amount' => $totalNet,
+                'prediscount_amount' => $totalGross,
+                'financial_movement' => $this->financial_movement,
+                'deleted' => $this->deleted,
+                'numero_fattura' => '',
+            ]);
+
+            $this->dataId = $record->id;
+
+            $no_receipt_causal = false;
+            $vat = 0;
+
+            foreach ($this->rows as $row) {
+                foreach ($row["when"] as $x => $y) {
+                    $row["when"][$x]['period'] = $row["when"][$x]['month'] . "-" . $row["when"][$x]['year'];
+                }
+
+                $rowAmount = $this->currencyToDouble($row["amount"]);
+                $rowSconto = isset($row['sconto']) ? $this->currencyToDouble($row['sconto']) : 0;
+                $rowNet = max(0, $rowAmount - $rowSconto);
+
+                Log::info("Row - Gross: " . $rowAmount . ", Sconto: " . $rowSconto . ", Net: " . $rowNet);
+
+                \App\Models\RecordRow::create([
+                    'record_id' => $this->dataId,
+                    'causal_id' => $row["causal_id"],
+                    'note' => $row["note"],
+                    'amount' => $rowNet,
+                    'prediscount_amount' => $rowAmount,
+                    'sconto' => $rowSconto,
+                    'vat_id' => $row["vat_id"],
+                    'commercial' => $this->commercial,
+                    'when' => json_encode($row["when"])
+                ]);
+
+                $vat += getVatValue($rowNet, $row["vat_id"]);
+
+                $cau = \App\Models\Causal::findOrFail($row["causal_id"]);
+                if ($cau->no_receipt)
+                    $no_receipt_causal = true;
+
+                if ($cau->money) {
+                    if ($rowNet >= env('MIN_MONEY', 100)) {
+                        $money = new \App\Models\Money();
+                        $money->member_id = $this->member_id;
+                        $money->record_id = $this->dataId;
+                        $money->amount = env('EXTRA_MONEY', 10);
+                        $money->date = date("Y-m-d");
+                        $money->note = env('EXTRA_MONEY', 10) . ' in più per aver caricato ' . $rowNet;
+                        $money->save();
+                    }
+                }
+            }
+
+            Log::info("Final amount saved to record: " . $totalNet);
+
+            if (!$this->financial_movement) {
+                $payment_method = \App\Models\PaymentMethod::findOrFail($this->payment_method_id);
+                if (!$payment_method->money) {
+                    if ($generate && !$no_receipt_causal)
+                        $this->createReceipt();
+                }
+            }
+
+            if ($this->rateId > 0) {
+
+                $rates = explode(",", $this->rateId);
+                foreach($rates as $rid)
+                {
+                    $rate = \App\Models\Rate::findOrFail($rid);
+                    $rate->status = 1;
+                    $rate->record_id = $record->id;
+                    $rate->save();
+                }
+
+            }
+
+            session()->flash('success', 'Movimento creato');
+            $this->resetFields();
+            $this->add = false;
+            $this->isDuplicate = false;
+            $this->emit('setEdit', false);
+        } catch (\Exception $ex) {
+            Log::error("Error in store method: " . $ex->getMessage());
+            Log::error("Stack trace: " . $ex->getTraceAsString());
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function duplicate($id)
+    {
+        $record = \App\Models\Record::findOrFail($id);
+        $newRecord = $record->replicate();
+        $newRecord->save();
+        $rows = \App\Models\RecordRow::where('record_id', $id)->get();
+        foreach ($rows as $r) {
+            $newRow = $r->replicate();
+            $newRow->record_id = $newRecord->id;
+            $newRow->save();
+        }
+        $this->edit($newRecord->id);
+        $this->isDuplicate = true;
+    }
+
+
+    public function edit($id)
+    {
+        if (!isset($_GET["from"]) && $this->fromPage == '')
+            $this->fromPage = 'in';
+        $this->emit('setEdit', true);
+        //if ($this->hasFilter)
+        $this->emit('hide-search');
+        $this->emit('load-select');
+
+        try {
+            $record = \App\Models\Record::findOrFail($id);
+            if (!$record) {
+                session()->flash('error', 'Movimento non trovato');
+            } else {
+                if ($record->member_id) {
+                    $member = \App\Models\Member::find($record->member_id);
+                    if (!$member || $member->status == 'archived') {
+                        $this->member_id = null;
+                    } else {
+                        $this->member_id = $record->member_id;
+                    }
+                } else {
+                    $this->member_id = $record->member_id;
+                }
+
+                if ($record->payment_method_id) {
+                    $paymentMethod = \App\Models\PaymentMethod::find($record->payment_method_id);
+                    if (!$paymentMethod || (isset($paymentMethod->hidden) && $paymentMethod->hidden)) {
+                        $this->payment_method_id = null;
+                    } else {
+                        $this->payment_method_id = $record->payment_method_id;
+                    }
+                } else {
+                    $this->payment_method_id = $record->payment_method_id;
+                }
+
+                $this->destination_id = $record->destination_id;
+                $this->supplier_id = $record->supplier_id;
+                $this->amount = $record->amount;
+                $this->commercial = $record->commercial;
+                $this->corrispettivo_fiscale = $record->corrispettivo_fiscale;
+                $this->date = date("Y-m-d", strtotime($record->date));
+                $this->type = $record->type;
+                $this->deleted = $record->deleted;
+                $this->financial_movement = $record->financial_movement;
+                $this->dataId = $record->id;
+                $this->update = true;
+                $this->add = false;
+
+                $rows = \App\Models\RecordRow::where('record_id', $this->dataId)
+                    ->select('causal_id', 'note', 'commercial', 'when', 'amount', 'prediscount_amount', 'sconto', 'vat_id')
+                    ->get();
+
+                $this->rows = [];
+                foreach ($rows as $i => $r) {
+                    $grossAmount = $r->prediscount_amount ?? ($r->amount + $r->sconto);
+
+                    $causalId = $r->causal_id;
+                    if ($causalId) {
+                        $causal = \App\Models\Causal::find($causalId);
+                        if (!$causal || (isset($causal->hidden) && $causal->hidden)) {
+                            $causalId = null;
+                        }
+                    }
+
+                    $this->rows[] = [
+                        'causal_id' => $causalId,
+                        'note' => $r->note,
+                        'commercial' => $r->commercial,
+                        'when' => json_decode($r->when),
+                        'amount' => formatPrice($grossAmount),
+                        'sconto' => formatPrice($r->sconto),
+                        'vat_id' => $r->vat_id
+                    ];
+                }
+
+                $exist = \App\Models\Receipt::where('record_id', $id)->orderBy('id', 'DESC')->first();
+                if ($exist != null) {
+                    $this->currentReceip = $exist;
+                    $this->parent = $this->currentReceip->parent;
+                }
+
+                $this->refreshEditOptions();
+            }
+        } catch (\Exception $ex) {
+            Log::error("Error in edit method: " . $ex->getMessage());
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    private function refreshEditOptions()
+    {
+        $this->causals = array();
+        $visibleCausals = \App\Models\Causal::select('id', 'name')
+            ->where('parent_id', null)
+            ->where('type', 'IN')
+            ->where(function ($query) {
+                $query->where('hidden', false)->orWhereNull('hidden');
+            })
+            ->get();
+        $this->getCausale($visibleCausals, 0);
+
+        $this->payments = \App\Models\PaymentMethod::select('id', 'name')
+            ->where('enabled', true)
+            ->whereIn('type', array('ALL', 'IN'))
+            ->where(function ($query) {
+                $query->where('hidden', false)->orWhereNull('hidden');
+            })
+            ->orderBy('name')
+            ->get();
+
+        if ($this->commercial) {
+            $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])
+                ->where(function ($query) {
+                    $query->where('status', '!=', 'archived');
+                })
+                ->orderBy('last_name')
+                ->orderBy('first_name')
+                ->get();
+        } else {
+            $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])
+                ->where(function ($query) {
+                    $query->where('current_status', 2)->orWhere('current_status', 1);
+                })
+                ->where(function ($query) {
+                    $query->where('status', '!=', 'archived');
+                })
+                ->orderBy('last_name')
+                ->orderBy('first_name')
+                ->get();
+        }
+    }
+
+    public function update($generate = false)
+    {
+        Log::info("Starting update method");
+        $this->emit('refresh');
+        $rules = [
+            'payment_method_id' => 'required',
+            'rows.*.causal_id' => 'required',
+            'rows.*.amount' => 'required'
+        ];
+
+        if (!$this->commercial)
+            $rules["member_id"] = 'required';
+
+        $this->validate($rules);
+
+        /* Validazione campi azienda per generazione ricevuta */
+        try {
+            if (!$this->financial_movement && !$this->commercial) {
+                $no_receipt_causal_azienda = false;
+                foreach ($this->rows as $row) {
+                    $no_receipt_causal_azienda = false;
+
+                    $cau = \App\Models\Causal::findOrFail($row["causal_id"]);
+                    if ($cau->no_receipt)
+                        $no_receipt_causal_azienda = true;
+                }
+
+                $azienda = \App\Models\Azienda::first();
+
+                $payment_method = \App\Models\PaymentMethod::findOrFail($this->payment_method_id);
+                if (!$payment_method->money) {
+                    if (!$no_receipt_causal_azienda) {
+                        if (!$azienda->isValid()) {
+                            session()->flash('error_ricevuta', implode("", array_map(function ($item) {
+                                return "<li>$item</li>";
+                            }, $azienda->validate())));
+                            return false;
+                        }
+                    }
+                }
+            }
+        } catch (\Exception $ex) {
+            Log::error("Error in store method: " . $ex->getMessage());
+            Log::error("Stack trace: " . $ex->getTraceAsString());
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+        /* END - Validazione campi azienda per generazione ricevuta */
+
+        try {
+            $totalGross = 0;
+            $totalSconto = 0;
+            $totalNet = 0;
+
+            foreach ($this->rows as $row) {
+                $rowAmount = $this->currencyToDouble($row["amount"]);
+                $rowSconto = isset($row['sconto']) ? $this->currencyToDouble($row['sconto']) : 0;
+                $rowNet = max(0, $rowAmount - $rowSconto);
+
+                $totalGross += $rowAmount;
+                $totalSconto += $rowSconto;
+                $totalNet += $rowNet;
+            }
+
+            Log::info("Total gross: " . $totalGross . ", Total sconto: " . $totalSconto . ", Total net: " . $totalNet);
+
+            \App\Models\Record::whereId($this->dataId)->update([
+                'member_id' => $this->member_id,
+                'supplier_id' => $this->supplier_id,
+                'payment_method_id' => $this->payment_method_id,
+                'destination_id' => $this->destination_id,
+                'commercial' => $this->commercial,
+                'corrispettivo_fiscale' => $this->corrispettivo_fiscale,
+                'date' => date("Y-m-d", strtotime($this->date)),
+                'type' => $this->type,
+                'financial_movement' => $this->financial_movement,
+                'deleted' => $this->deleted,
+                'amount' => $totalNet,
+                'prediscount_amount' => $totalGross,
+            ]);
+
+            $no_receipt_causal = false;
+            $vat = 0;
+
+            \App\Models\RecordRow::where('record_id', $this->dataId)->delete();
+
+            foreach ($this->rows as $row) {
+                foreach ($row["when"] as $x => $y) {
+                    $row["when"][$x]['period'] = $row["when"][$x]['month'] . "-" . $row["when"][$x]['year'];
+                }
+
+                $rowAmount = $this->currencyToDouble($row["amount"]);
+                $rowSconto = isset($row['sconto']) ? $this->currencyToDouble($row['sconto']) : 0;
+                $rowNet = max(0, $rowAmount - $rowSconto);
+
+                Log::info("Row - Gross: " . $rowAmount . ", Sconto: " . $rowSconto . ", Net: " . $rowNet);
+
+                \App\Models\RecordRow::create([
+                    'record_id' => $this->dataId,
+                    'causal_id' => $row["causal_id"],
+                    'note' => $row["note"],
+                    'vat_id' => $row["vat_id"],
+                    'amount' => $rowNet,
+                    'prediscount_amount' => $rowAmount,
+                    'sconto' => $rowSconto,
+                    'commercial' => $this->commercial,
+                    'when' => json_encode($row["when"])
+                ]);
+
+                $vat += getVatValue($rowNet, $row["vat_id"]);
+
+                $cau = \App\Models\Causal::findOrFail($row["causal_id"]);
+                if ($cau->no_receipt)
+                    $no_receipt_causal = true;
+            }
+
+            Log::info("Final amount saved to record: " . $totalNet);
+
+            if (!$this->financial_movement) {
+                $payment_method = \App\Models\PaymentMethod::findOrFail($this->payment_method_id);
+                if (!$payment_method->money) {
+                    if ($generate && !$no_receipt_causal)
+                        $this->createReceipt();
+                }
+            }
+            session()->flash('success', 'Movimento aggiornato');
+            $this->resetFields();
+            $this->update = false;
+            $this->isDuplicate = false;
+            $this->emit('setEdit', false);
+        } catch (\Exception $ex) {
+            Log::error("Error in update method: " . $ex->getMessage());
+            Log::error("Stack trace: " . $ex->getTraceAsString());
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function rigenerate()
+    {
+        $this->emit('refresh');
+
+        try {
+
+            $record = \App\Models\Record::findOrFail($this->dataId);
+            $newRecord = $record->replicate();
+            $newRecord->date = date("Y-m-d");
+            $newRecord->save();
+
+            $recordRows = \App\Models\RecordRow::where('record_id', $this->dataId)->get();
+            // Inserisco le righe
+            foreach ($recordRows as $row) {
+                $newRow = $row->replicate();
+                $newRow->record_id = $newRecord->id;
+                $newRow->save();
+            }
+
+            $this->dataId = $newRecord->id;
+
+            $this->createReceipt();
+
+            session()->flash('success', 'Movimento aggiornato');
+            $this->resetFields();
+            $this->update = false;
+            $this->isDuplicate = false;
+            $this->emit('setEdit', false);
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function cancel()
+    {
+        // Se arrivo da duplica elimino
+        if ($this->isDuplicate) {
+            try {
+                \App\Models\Record::find($this->dataId)->delete();
+                session()->flash('success', "Movimento eliminato");
+            } catch (\Exception $e) {
+                session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+            }
+        }
+        $this->isDuplicate = false;
+
+        $this->add = false;
+        $this->update = false;
+        $this->resetFields();
+        $this->emit('setEdit', false);
+        return redirect()->to('/in');
+    }
+
+    public function delete($id)
+    {
+        try {
+
+            $r = \App\Models\Record::find($id);
+
+            // Annullo la ricevuta
+            $receipt = \App\Models\Receipt::where('record_id', $r->id)->first();
+            if ($receipt) {
+                $receipt->status = 99;
+                sendReceiptDeleteEmail($receipt);
+                $receipt->save();
+            }
+
+            if ($r->member_course_id > 0) {
+                $months = json_decode($r->months);
+                $c = \App\Models\MemberCourse::findOrFail($r->member_course_id);
+                $xxx = json_decode($c->months);
+                foreach ($xxx as $idx => $mm) {
+                    if (in_array($mm->m, $months)) {
+                        $xxx[$idx]->status = "";
+                    }
+                }
+                $c->months = json_encode($xxx);
+                $c->save();
+            }
+
+            $r->deleted = true;
+            $r->save();
+
+            session()->flash('success', 'Movimento aggiornato');
+            $this->resetFields();
+            $this->update = false;
+            $this->isDuplicate = false;
+            $this->emit('setEdit', false);
+
+            $this->emit('reload');
+
+            //$this->emit('load-data-table');
+
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function multipleDelete()
+    {
+        try {
+            foreach ($this->multipleIds as $id) {
+                \App\Models\Record::find($id)->delete();
+            }
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+        $this->multipleAction = '';
+    }
+
+    public function createMember()
+    {
+
+        $this->newMemberFiscalCodeExist = false;
+        $this->validate([
+            // 'newMemberFiscalCode'=>'required|max:16',
+            'newMemberFirstName' => 'required',
+            'newMemberLastName' => 'required',
+            //'newMemberEmail'=>'required',
+        ]);
+
+        // Check fiscal code exist
+        $exist = false;
+        if ($this->newMemberFiscalCode != '') {
+            $check = \App\Models\Member::where('fiscal_code', $this->newMemberFiscalCode)->get();
+            $exist = $check->count() > 0;
+        }
+        if (!$exist) {
+            $member = \App\Models\Member::create([
+                'first_name' => strtoupper($this->newMemberFirstName),
+                'last_name' => strtoupper($this->newMemberLastName),
+                'email' => strtoupper($this->newMemberEmail),
+                'to_complete' => $this->newMemberToComplete,
+                'fiscal_code' => $this->newMemberFiscalCode,
+                'status' => true
+            ]);
+            $this->member_id = $member->id;
+            $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])->get();
+            // $this->emit('reloadMembers');
+            $this->emit('refresh');
+            $this->newMemberFirstName = '';
+            $this->newMemberLastName = '';
+            $this->newMemberEmail = '';
+            $this->newMemberToComplete = false;
+            $this->newMemberFiscalCode = '';
+            $this->emit('saved');
+            //$this->validate();
+            $this->selectId++;
+        } else {
+            $this->newMemberFiscalCodeExist = true;
+        }
+    }
+
+    public function createReceipt()
+    {
+        // Ulteriore controllo commerciale/non commerciale
+        if (!$this->commercial) {
+            $create = false;
+            $receipt = \App\Models\Receipt::where('record_id', $this->dataId)->orderBy('id', 'DESC')->first();
+            if ($receipt != null) {
+
+                // Controllo lo stato, se 99 ne genero una nuova
+                if ($receipt->status == 99) {
+                    $create = true;
+                }
+            } else {
+                $create = true;
+            }
+
+            if ($create) {
+                $number = 1;
+                $exist = \App\Models\Receipt::where('year', date("Y"))->orderBy('number', 'DESC')->first();
+                if ($exist != null)
+                    $number = $exist->number + 1;
+
+                // Calculate totals for the receipt
+                $totalGross = 0;
+                $totalNet = 0;
+                foreach ($this->rows as $row) {
+                    $rowAmount = $this->currencyToDouble($row["amount"]);
+                    $rowSconto = isset($row['sconto']) ? $this->currencyToDouble($row['sconto']) : 0;
+                    $rowNet = max(0, $rowAmount - $rowSconto);
+
+                    $totalGross += $rowAmount;
+                    $totalNet += $rowNet;
+                }
+
+                $receipt = \App\Models\Receipt::create([
+                    'record_id' => $this->dataId,
+                    'member_id' => $this->member_id,
+                    'supplier_id' => $this->supplier_id,
+                    'payment_method_id' => $this->payment_method_id,
+                    'number' => $number,
+                    'date' => $this->date,
+                    'year' => date("Y"),
+                    'type' => $this->type,
+                    'parent' => $this->parent,
+                    'status' => 1,
+                ]);
+
+                foreach ($this->rows as $row) {
+                    $rowAmount = $this->currencyToDouble($row["amount"]);
+                    $rowSconto = isset($row['sconto']) ? $this->currencyToDouble($row['sconto']) : 0;
+                    $rowNet = max(0, $rowAmount - $rowSconto);
+
+                    Log::info("Receipt Row - Gross: " . $rowAmount . ", Sconto: " . $rowSconto . ", Net: " . $rowNet);
+
+                    \App\Models\ReceiptRow::create([
+                        'receip_id' => $receipt->id,
+                        'causal_id' => $row["causal_id"],
+                        'note' => $row["note"],
+                        'vat_id' => $row["vat_id"],
+                        'amount' => $rowNet,
+                        'prediscount_amount' => $rowAmount,
+                        'sconto' => $rowSconto,
+                        'commercial' => $row["commercial"],
+                        'when' => json_encode($row["when"])
+                    ]);
+                }
+
+                $this->currentReceip = $receipt;
+
+                sendReceiptEmail($receipt);
+
+                session()->flash('receipt', "Ricevuta " . $number . "/" . date("Y") . " creata correttamente");
+
+                $this->emit('showReceipt', $this->currentReceip->id);
+            }
+        }
+    }
+
+    public function removeReceipt()
+    {
+        $receipt = \App\Models\Receipt::findOrFail($this->currentReceip->id);
+        $receipt->status = 99;
+        $receipt->save();
+        sendReceiptDeleteEmail($receipt);
+        $this->currentReceip = $receipt;
+    }
+
+    function currencyToDouble($val)
+    {
+        $x = str_replace("€", "", $val);
+        $x = str_replace(".", "", $x);
+        $x = str_replace(",", ".", $x);
+        return floatval(trim($x));
+    }
+
+
+    public function addRow()
+    {
+        $this->rows[] = array('causal_id' => null, 'when' => array(array('month' => date("n"), 'year' => date("Y"), 'period' => '')),  'amount' => null, 'vat_id' => null, 'note' => '', 'commercial' => 0);
+        $this->emit('load-select');
+    }
+
+    public function delRow($idx)
+    {
+        unset($this->rows[$idx]);
+        // $this->emit('load-select');
+    }
+
+    public function addPeriod($idx)
+    {
+        $x = sizeof($this->rows[$idx]['when']) - 1;
+        $newDate = \Carbon\Carbon::create($this->rows[$idx]['when'][$x]["year"] . "-" . $this->rows[$idx]['when'][$x]["month"] . '-01')->addMonth();
+        $this->rows[$idx]['when'][] = array('month' => $newDate->format("n"), 'year' => $newDate->format("Y"), 'period' => '');
+    }
+
+    public function delPeriod($idx, $xxx)
+    {
+        array_splice($this->rows[$idx]['when'], $xxx, $xxx);
+        //unset($this->rows[$idx]['when'][$xxx]);
+        // $this->emit('load-select');
+    }
+    public function getTotal()
+    {
+        $total = 0.00;
+
+        foreach ($this->rows as $r) {
+            if ($r["amount"] != null && $r["amount"] != "") {
+                $rowAmount = $this->currencyToDouble($r["amount"]);
+                $rowSconto = isset($r['sconto']) ? $this->currencyToDouble($r['sconto']) : 0;
+                $netAmount = max(0, $rowAmount - $rowSconto);
+
+                $total += $netAmount;
+
+                if ($r["vat_id"] > 0)
+                    $total += getVatValue($netAmount, $r["vat_id"]);
+            }
+        }
+
+        return formatPrice($total);
+    }
+    public function getTotalCorrispettivo()
+    {
+        $total = 0.00;
+        foreach ($this->corrispettivo as $r) {
+            $total += $this->currencyToDouble($r);
+        }
+        return formatPrice($total);
+        // $this->emit('load-select');
+    }
+
+    public function getPrice()
+    {
+        $total = 0.00;
+        foreach ($this->rows as $r) {
+            if ($r["amount"] != null && $r["amount"] != "")
+                $total += $this->currencyToDouble($r["amount"]);
+        }
+        return formatPrice($total);
+        // $this->emit('load-select');
+    }
+
+    public function getVat()
+    {
+        $total = 0.00;
+        foreach ($this->rows as $r) {
+            if ($r["amount"] != null && $r["amount"] != "" && $r["vat_id"] > 0)
+                $total += getVatValue($this->currencyToDouble($r["amount"]), $r["vat_id"]);
+        }
+        return formatPrice($total);
+        // $this->emit('load-select');
+    }
+
+    public function getVats()
+    {
+        $vats = array();
+        foreach ($this->rows as $r) {
+            if ($r["amount"] != null && $r["amount"] != "" && $r["vat_id"] > 0) {
+                $vat = getVatValue($this->currencyToDouble($r["amount"]), $r["vat_id"]);
+                $vatName = "";
+                foreach ($this->vats as $v) {
+                    if ($v->id == $r["vat_id"])
+                        $vatName = $v->name;
+                }
+                if (isset($vats[$vatName]))
+                    $vats[$vatName] += $vat;
+                else
+                    $vats[$vatName] = $vat;
+            }
+        }
+        return $vats;
+        // $this->emit('load-select');
+    }
+
+    public function setCausal($id, $idx)
+    {
+        $this->rows[$idx]["causal_id"] = $id;
+    }
+
+    public function printReceipt()
+    {
+        //$pdf = PDF::loadView('pdf/receipt', array('datas' => $datas, 'from' => $x, 'to' => $y, 'who' => '', 'matricola' => $matricola));
+        $pdf = PDF::loadView('receipt', array('receipt' => $this->currentReceip)); //->output();
+        $pdfName = "Ricevuta_" . $this->currentReceip->member->last_name . "_" . $this->currentReceip->number . "_" . $this->currentReceip->year . ".pdf";
+        return $pdf->stream();
+        /*return response()->streamDownload(
+            fn () => print($pdf),
+            "ricevuta_" . $this->currentReceip->number . "_" . $this->currentReceip->year . ".pdf"
+        );*/
+        /*return response()->streamDownload(function () {
+            echo $pdf->stream();
+        }, 'test.pdf');*/
+    }
+
+    public function multiPeriod()
+    {
+        $this->multiP = true;
+    }
+
+    public function multiPeriodCreate($idx)
+    {
+
+        $period = \Carbon\CarbonPeriod::create($this->multiYearFrom . '-' . $this->multiMonthFrom . '-01', '1 month', $this->multiYearTo . '-' . $this->multiMonthTo . '-01');
+        $this->rows[$idx]['when'] = [];
+        foreach ($period as $dt) {
+            if (!in_array(array('month' => $dt->format("n"), 'year' => $dt->format("Y"), 'period' => ''), $this->rows[$idx]['when']))
+                $this->rows[$idx]['when'][] = array('month' => $dt->format("n"), 'year' => $dt->format("Y"), 'period' => '');
+        }
+
+        $this->multiP = false;
+    }
+
+    public function multiPeriodCancel()
+    {
+        $this->multiP = false;
+    }
+
+    public function getMonth($m)
+    {
+        $ret = '';
+        switch ($m) {
+            case 1:
+                $ret = 'Gennaio';
+                break;
+            case 2:
+                $ret = 'Febbraio';
+                break;
+            case 3:
+                $ret = 'Marzo';
+                break;
+            case 4:
+                $ret = 'Aprile';
+                break;
+            case 5:
+                $ret = 'Maggio';
+                break;
+            case 6:
+                $ret = 'Giugno';
+                break;
+            case 7:
+                $ret = 'Luglio';
+                break;
+            case 8:
+                $ret = 'Agosto';
+                break;
+            case 9:
+                $ret = 'Settembre';
+                break;
+            case 10:
+                $ret = 'Ottobre';
+                break;
+            case 11:
+                $ret = 'Novembre';
+                break;
+            case 12:
+                $ret = 'Dicembre';
+                break;
+            default:
+                $ret = '';
+                break;
+        }
+        return $ret;
+    }
+
+    public function setYear($idx, $xxx)
+    {
+        $m = $this->rows[$idx]["when"][$xxx]["month"];
+        $this->rows[$idx]["when"][$xxx]["year"] = $m < env('FISCAL_YEAR_MONTH_FROM', 1) ? (date("Y") + 1) : date("Y");
+    }
+}

+ 2 - 2
app/Http/Livewire/RecordINOUT.php

@@ -554,7 +554,7 @@ class RecordINOUT extends Component
             if ($level == 0) return true; // padre sempre incluso
 
             $pid = $row['parent_id'] ?? null;
-            // se non c'è il padre includi il figlio
+            // se non c'è il padre includi il figlio
             return !in_array($pid, $presentParentIds, true);
         };
 
@@ -650,7 +650,7 @@ class RecordINOUT extends Component
             $col = Coordinate::stringFromColumnIndex($firstDataIdx + $idx);
             $activeWorksheet->setCellValue($col . $count, $this->getMonth($column));
         }
-        $activeWorksheet->setCellValue($colRowTot . '1', 'Totale'); // già impostato sopra, qui lasciamo l’header a riga corrente
+        $activeWorksheet->setCellValue($colRowTot . '1', 'Totale'); // già impostato sopra, qui lasciamo l’header a riga corrente
         $activeWorksheet->setCellValue($colRowTot . $count, 'Totale');
 
         $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)->getFont()->setBold(true);

+ 811 - 0
app/Http/Livewire/RecordINOUT.php.bak

@@ -0,0 +1,811 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use Illuminate\Support\Facades\Log;
+use SimpleXMLElement;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Storage;
+use App\Http\Middleware\TenantMiddleware;
+
+class RecordINOUT extends Component
+{
+    //public $records_in, $records_out;
+    public $total_in = 0;
+    public $total_out = 0;
+
+    public $datas = [];
+    public $month;
+    public $year;
+
+    public $months_1 = [];
+    public $year_1;
+
+    public $months_2 = [];
+    public $year_2;
+    public $show_block_2;
+
+    //public $borsellino_id = 0;
+
+    public $columns = array();
+    public $rows_in = array();
+    public $rows_out = array();
+    public $records_in = array();
+    public $records_out = array();
+
+    public $showData = true;
+
+    public $hasFilter = false;
+
+    public $total = 0;
+
+    public $selectedFilter = '';
+
+    public $causalsIn = array();
+    public $causalsOut = array();
+    public $payments = array();
+    public $members = array();
+    public $filterCausalsIn = null;
+    public $filterCausalsOut = null;
+
+    public $excludeCausals = array();
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+
+    public function mount()
+    {
+
+        if (Auth::user()->level != env('LEVEL_ADMIN', 0))
+            return redirect()->to('/dashboard');
+
+        $borsellino = \App\Models\Causal::where('money', true)->first();
+        if ($borsellino)
+            $this->excludeCausals[] = $borsellino->id;
+        //$this->borsellino_id = $borsellino->id;
+
+        // Aggiungo
+        $excludes = \App\Models\Causal::where('no_records', true)->get();
+        foreach ($excludes as $e) {
+            $this->excludeCausals[] = $e->id;
+        }
+
+
+        $this->month = date("m");
+        $this->year = date("Y");
+        $this->year_1 = date("Y");
+        $this->year_2 = date("Y");
+        $this->show_block_2 = false;
+
+        $this->getCausale(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->where('type', 'IN')->whereNotIn('id', $this->excludeCausals)->get(), 'IN', 0);
+        $this->getCausale(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->where('type', 'OUT')->whereNotIn('id', $this->excludeCausals)->get(), 'OUT', 0);
+
+        $this->getCausalsIn(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->where('type', 'IN')->get(), 0);
+
+        $this->getCausalsOut(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->where('type', 'OUT')->get(), 0);
+
+        //$this->causalsIn = \App\Models\Causal::where('parent_id', null)->where('type', 'IN')->whereNotIn('id', $this->excludeCausals)->get();
+        //$this->causalsOut = \App\Models\Causal::where('parent_id', null)->where('type', 'OUT')->whereNotIn('id', $this->excludeCausals)->get();
+    }
+
+    public function getCausalsIn($records, $indentation)
+    {
+        foreach ($records as $record) {
+            $this->causalsIn[] = array('id' => $record->id, 'name' => $record->getTree(), 'text' => $record->getTree(), 'level' => $indentation);
+            if (count($record->childs))
+                $this->getCausalsIn($record->childs, $indentation + 1);
+        }
+    }
+
+    public function getCausalsOut($records, $indentation)
+    {
+        foreach ($records as $record) {
+            $this->causalsOut[] = array('id' => $record->id, 'name' => $record->getTree(), 'text' => $record->getTree(), 'level' => $indentation);
+            if (count($record->childs))
+                $this->getCausalsOut($record->childs, $indentation + 1);
+        }
+    }
+
+    public function getCausale($records, $type, $indentation)
+    {
+        foreach ($records as $record) {
+            $first_parent_id = null;
+            if ($record->parent_id != null) {
+                $first_parent_id = \App\Models\Causal::where('id', $record->parent_id)->first()->parent_id;
+            }
+            if ($type == 'IN')
+                $this->rows_in[] = array('id' => $record->id, 'name' => $record->name, 'level' => $indentation, 'parent_id' => $record->parent_id, 'parent_name' => $this->getCausalName($record->parent_id), 'first_parent_id' => $first_parent_id, 'first_parent_name' => $this->getCausalName($first_parent_id), 'all_childs' => $this->getAllChild($record->id));
+            if ($type == 'OUT')
+                $this->rows_out[] = array('id' => $record->id, 'name' => $record->name, 'level' => $indentation, 'parent_id' => $record->parent_id, 'parent_name' => $this->getCausalName($record->parent_id), 'first_parent_id' => $first_parent_id, 'first_parent_name' => $this->getCausalName($first_parent_id), 'all_childs' => $this->getAllChild($record->id));
+            if (count($record->childs))
+                $this->getCausale($record->childs, $type, $indentation + 1);
+        }
+    }
+
+    public function getAllChild($id)
+    {
+        $aChilds = array();
+        $aChilds[] = $id;
+
+
+        $record = \App\Models\Causal::findOrFail($id);
+        $aChilds[] = $record->parent_id;
+        if ($record->parent_id != null) {
+            $first_parent_id = \App\Models\Causal::where('id', $record->parent_id)->first()->parent_id;
+            $aChilds[] = $first_parent_id;
+        }
+
+        $childs = \App\Models\Causal::where('parent_id', $id)->get();
+        foreach ($childs as $child) {
+            $aChilds[] = $child->id;
+            $childs2 = \App\Models\Causal::where('parent_id', $child->id)->get();
+            foreach ($childs2 as $child2) {
+                $aChilds[] = $child2->id;
+                $childs3 = \App\Models\Causal::where('parent_id', $child2->id)->get();
+                foreach ($childs3 as $child3) {
+                    $aChilds[] = $child3->id;
+                }
+            }
+        }
+        return $aChilds;
+    }
+
+
+    public function getCausalName($id)
+    {
+        if ($id > 0)
+            return \App\Models\Causal::findOrFail($id)->name;
+        else
+            return "";
+    }
+
+    public function render()
+    {
+        return view('livewire.records_in_out');
+    }
+
+    public function hydrate()
+    {
+        $this->emit('load-select');
+    }
+
+    public function show($m, $y)
+    {
+        if ($m != "" && $y != "" && !in_array($m . "-" . $y, $this->datas))
+            $this->datas[] = $m . "-" . $y;
+
+        $this->columns = array();
+        $this->records_in = [];
+        $this->records_out = [];
+
+        $exclude_from_records = \App\Models\Member::where('exclude_from_records', true)->pluck('id')->toArray();
+
+        if (sizeof($this->datas) > 0) {
+            foreach ($this->datas as $filter) {
+                $this->columns[] = $filter;
+
+                $f = $filter;
+                if ($m == 'x')
+                    $f = str_replace("x-", "", $filter);
+                $records = \App\Models\Record::where('type', 'IN')
+                    ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+                    ->whereNotIn('records_rows.causal_id', $this->excludeCausals)
+                    ->where(function ($query) {
+                        $query->where('deleted', false)->orWhere('deleted', null);
+                    })
+                    ->where(function ($query) {
+                        $query->where('financial_movement', false)->orWhere('financial_movement', null);
+                    })
+                    ->whereNotIn('member_id', $exclude_from_records)
+                    ->where('records_rows.when', 'like', '%"' . $f . '"%')
+                    ->get();
+
+                //Log::info('Record In' . $records);
+                foreach ($records as $record) {
+                    $amount = $record->amount;
+                    $amount += getVatValue($amount, $record->vat_id);
+                    $when = sizeof(json_decode($record->when));
+                    if ($when > 1) {
+                        $amount = $amount / $when;
+                        $record->amount = $amount;
+                    }
+                    // Aggiungo/aggiorno i dati
+                    if (isset($this->records_in[$filter][$record->causal_id]))
+                        $this->records_in[$filter][$record->causal_id] += $amount;
+                    else
+                        $this->records_in[$filter][$record->causal_id] = $amount;
+                    // Aggiorno i dati del padre
+                    $this->updateParent("IN", $record->causal_id, $amount, $filter);
+                }
+                $records = \App\Models\Record::where('type', 'OUT')
+                    ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+                    ->whereNotIn('records_rows.causal_id', $this->excludeCausals)
+                    ->where(function ($query) {
+                        $query->where('deleted', false)->orWhere('deleted', null);
+                    })
+
+                    ->where(function ($query) use ($exclude_from_records) {
+                        $query->whereNull('member_id')
+                            ->orWhereNotIn('member_id', $exclude_from_records);
+                    })
+                    ->where('records_rows.when', 'like', '%"' . $f . '"%')->get();
+                Log::info('Record Out' . $records);
+                foreach ($records as $record) {
+                    $amount = $record->amount;
+                    $amount += getVatValue($amount, $record->vat_id);
+                    $when = sizeof(json_decode($record->when));
+                    if ($when > 1) {
+                        $amount = $amount / $when;
+                        $record->amount = $amount;
+                    }
+                    // Aggiungo/aggiorno i dati
+                    if (isset($this->records_out[$filter][$record->causal_id]))
+                        $this->records_out[$filter][$record->causal_id] += $amount;
+                    else
+                        $this->records_out[$filter][$record->causal_id] = $amount;
+                    $this->updateParent("OUT", $record->causal_id, $amount, $filter);
+                }
+            }
+        }
+
+        //$this->showData = true;
+        $this->emit('load-table');
+    }
+
+    public function updateParent($type, $causal_id, $amount, $filter)
+    {
+        if ($type == "IN") {
+            foreach ($this->rows_in as $r) {
+                if ($r["id"] == $causal_id) {
+                    if (isset($this->records_in[$filter][$r["parent_id"]]))
+                        $this->records_in[$filter][$r["parent_id"]] += $amount;
+                    else
+                        $this->records_in[$filter][$r["parent_id"]] = $amount;
+                    if ($r["parent_id"] > 0)
+                        $this->updateParent("IN", $r["parent_id"], $amount, $filter);
+                }
+            }
+        }
+        if ($type == "OUT") {
+            foreach ($this->rows_out as $r) {
+                if ($r["id"] == $causal_id) {
+                    if (isset($this->records_out[$filter][$r["parent_id"]]))
+                        $this->records_out[$filter][$r["parent_id"]] += $amount;
+                    else
+                        $this->records_out[$filter][$r["parent_id"]] = $amount;
+                    if ($r["parent_id"] > 0)
+                        $this->updateParent("OUT", $r["parent_id"], $amount, $filter);
+                }
+            }
+        }
+    }
+
+    public function updateParentYear($type, $causal_id, $amount, $filter, &$records_in, &$records_out)
+    {
+        if ($type == "IN") {
+            foreach ($this->rows_in as $r) {
+                if ($r["id"] == $causal_id) {
+                    if (isset($records_in[$filter][$r["parent_id"]]))
+                        $records_in[$filter][$r["parent_id"]] += $amount;
+                    else
+                        $records_in[$filter][$r["parent_id"]] = $amount;
+                    if ($r["parent_id"] > 0)
+                        $this->updateParentYear("IN", $r["parent_id"], $amount, $filter, $records_in, $records_out);
+                }
+            }
+        }
+        if ($type == "OUT") {
+            foreach ($this->rows_out as $r) {
+                if ($r["id"] == $causal_id) {
+                    if (isset($records_out[$filter][$r["parent_id"]]))
+                        $records_out[$filter][$r["parent_id"]] += $amount;
+                    else
+                        $records_out[$filter][$r["parent_id"]] = $amount;
+                    if ($r["parent_id"] > 0)
+                        $this->updateParentYear("OUT", $r["parent_id"], $amount, $filter, $records_in, $records_out);
+                }
+            }
+        }
+    }
+
+    public function getCausal($causal)
+    {
+        $ret = '';
+        if ($causal > 0) {
+            $ret = \App\Models\Causal::findOrFail($causal)->getTree();
+        }
+        return $ret;
+    }
+
+    public function getMonth($str)
+    {
+        $ret = '';
+        list($m, $y) = explode("-", $str);
+        switch ($m) {
+            case 'x':
+                $ret = '';
+                break;
+            case '01':
+                $ret = 'Gennaio ';
+                break;
+            case '02':
+                $ret = 'Febbraio ';
+                break;
+            case '03':
+                $ret = 'Marzo ';
+                break;
+            case '04':
+                $ret = 'Aprile ';
+                break;
+            case '05':
+                $ret = 'Maggio ';
+                break;
+            case '06':
+                $ret = 'Giugno ';
+                break;
+            case '07':
+                $ret = 'Luglio ';
+                break;
+            case '08':
+                $ret = 'Agosto ';
+                break;
+            case '09':
+                $ret = 'Settembre ';
+                break;
+            case '10':
+                $ret = 'Ottobre ';
+                break;
+            case '11':
+                $ret = 'Novembre ';
+                break;
+            case '12':
+                $ret = 'Dicembre ';
+                break;
+            default:
+                $ret = '';
+                break;
+        }
+        $ret .= $y;
+        return $ret;
+    }
+
+    public function clear()
+    {
+        $this->columns = [];
+        $this->datas = [];
+        $this->records_out = [];
+        $this->records_in = [];
+        $this->emit('load-select');
+        $this->emit('reset-collapse');
+        //$this->showData = false;
+    }
+
+    public function remove($idx)
+    {
+
+        if (sizeof($this->datas) > 1)
+            array_splice($this->datas, $idx, 1);
+        //unset($this->datas[$idx]);
+        else
+            $this->datas = array();
+
+        $this->show('', '');
+    }
+
+    public function export()
+    {
+
+        $rows_in = array();
+        $rows_out = array();
+
+        if ($this->filterCausalsIn != null && sizeof($this->filterCausalsIn) > 0) {
+            foreach ($this->rows_in as $r) {
+                if (in_array($r["id"], $this->filterCausalsIn) || in_array($r["parent_id"], $this->filterCausalsIn) || in_array($r["first_parent_id"], $this->filterCausalsIn)) {
+                    $rows_in[] = $r;
+                }
+            }
+        } else {
+            $rows_in = $this->rows_in;
+        }
+
+        if ($this->filterCausalsOut != null && sizeof($this->filterCausalsOut) > 0) {
+            foreach ($this->rows_out as $r) {
+                if (in_array($r["id"], $this->filterCausalsOut) || in_array($r["parent_id"], $this->filterCausalsOut) || in_array($r["first_parent_id"], $this->filterCausalsOut)) {
+                    $rows_out[] = $r;
+                }
+            }
+        } else {
+            $rows_out = $this->rows_out;
+        }
+
+        $result = $this->generateExcel($this->columns, $rows_in, $this->records_in, $rows_out, $this->records_out, false);
+
+        if ($result['storage_type'] === 's3') {
+            try {
+                $disk = Storage::disk('s3');
+                $downloadUrl = $disk->temporaryUrl($result['path'], now()->addHour());
+
+                return redirect($downloadUrl);
+            } catch (\Exception $e) {
+                Log::error('Error generating S3 download URL for export', [
+                    'error' => $e->getMessage(),
+                    'path' => $result['path']
+                ]);
+                session()->flash('error', 'Errore durante la generazione del link di download. Riprova.');
+                return back();
+            }
+        } else {
+            return response()->download($result['path'])->deleteFileAfterSend();
+        }
+    }
+
+    public function exportYear($year)
+    {
+        $records_in = [];
+        $records_out = [];
+        $datas = [];
+        if (env('FISCAL_YEAR_MONTH_FROM', 1) > 1) {
+            if (date("m") < env('FISCAL_YEAR_MONTH_FROM', 1))
+                $year -= 1;
+            for ($m = env('FISCAL_YEAR_MONTH_FROM', 1); $m <= 12; $m++) {
+                $datas[] = $m . "-" . $year;
+            }
+            for ($m = 1; $m <= env('FISCAL_YEAR_MONTH_TO', 12); $m++) {
+                $datas[] = $m . "-" . ($year + 1);
+            }
+        } else {
+            for ($m = 1; $m <= 12; $m++) {
+                $datas[] = $m . "-" . $year;
+            }
+        }
+
+        $exclude_from_records = \App\Models\Member::where('exclude_from_records', true)->pluck('id')->toArray();
+
+        foreach ($datas as $filter) {
+
+            $columns[] = $filter;
+
+            $records = \App\Models\Record::where('type', 'IN')
+                ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+                ->whereNotIn('records_rows.causal_id', $this->excludeCausals)
+                ->where(function ($query) {
+                    $query->where('deleted', false)->orWhere('deleted', null);
+                })
+                ->where(function ($query) {
+                    $query->where('financial_movement', false)->orWhere('financial_movement', null);
+                })
+                ->whereNotIn('member_id', $exclude_from_records)
+                ->where('records_rows.when', 'like', '%"' . $filter . '"%')->get();
+            //$records = $records->orderBy('date', 'DESC')->get();
+
+            foreach ($records as $record) {
+                $amount = $record->amount;
+                $amount += getVatValue($amount, $record->vat_id);
+                $when = sizeof(json_decode($record->when));
+                if ($when > 1) {
+                    $amount = $amount / $when;
+                    $record->amount = $amount;
+                }
+                // Aggiungo/aggiorno i dati
+                if (isset($records_in[$filter][$record->causal_id]))
+                    $records_in[$filter][$record->causal_id] += $amount;
+                else
+                    $records_in[$filter][$record->causal_id] = $amount;
+                // Aggiorno i dati del padre
+                $this->updateParentYear("IN", $record->causal_id, $amount, $filter, $records_in, $records_out);
+            }
+
+            $records = \App\Models\Record::where('type', 'OUT')
+                ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
+                ->whereNotIn('records_rows.causal_id', $this->excludeCausals)
+                ->where(function ($query) {
+                    $query->where('deleted', false)->orWhere('deleted', null);
+                })
+                ->where(function ($query) use ($exclude_from_records) {
+                    $query->whereNull('member_id')
+                        ->orWhereNotIn('member_id', $exclude_from_records);
+                })
+                ->where('records_rows.when', 'like', '%"' . $filter . '"%')->get();
+            //$records = $records->orderBy('date', 'DESC')->get();
+            foreach ($records as $record) {
+                $amount = $record->amount;
+                $when = sizeof(json_decode($record->when));
+                if ($when > 1) {
+                    $amount = $amount / $when;
+                    $record->amount = $amount;
+                }
+                // Aggiungo/aggiorno i dati
+                if (isset($records_out[$filter][$record->causal_id]))
+                    $records_out[$filter][$record->causal_id] += $amount;
+                else
+                    $records_out[$filter][$record->causal_id] = $amount;
+                $this->updateParentYear("OUT", $record->causal_id, $amount, $filter, $records_in, $records_out);
+            }
+        }
+
+        $result = $this->generateExcel($columns, $this->rows_in, $records_in, $this->rows_out, $records_out, true);
+
+        if ($result['storage_type'] === 's3') {
+            $disk = Storage::disk('s3');
+            $downloadUrl = $disk->temporaryUrl($result['path'], now()->addHour());
+            return redirect($downloadUrl);
+        } else {
+            return response()->download($result['path'])->deleteFileAfterSend();
+        }
+    }
+
+    public function generateExcel($columns, $rows_in, $records_in, $rows_out, $records_out, $isYearExport)
+    {
+        $writeNumber = function ($sheet, string $cell, $value, string $format = '#,##0.00') {
+            $sheet->setCellValueExplicit($cell, (float)$value, DataType::TYPE_NUMERIC);
+            $sheet->getStyle($cell)->getNumberFormat()->setFormatCode($format);
+        };
+
+        $shouldCountFn = function (array $row, array $presentParentIds): bool {
+            $level = $row['level'] ?? 0;
+            if ($level == 0) return true; // padre sempre incluso
+
+            $pid = $row['parent_id'] ?? null;
+            // se non c'è il padre includi il figlio
+            return !in_array($pid, $presentParentIds, true);
+        };
+
+        $spreadsheet = new Spreadsheet();
+        $activeWorksheet = $spreadsheet->getActiveSheet();
+
+        // Mappa colonne dinamica
+        $monthCount   = count($columns);
+        $nameColIdx   = 1;
+        $firstDataIdx = 2;
+        $lastDataIdx  = 1 + $monthCount;
+        $rowTotalIdx  = $lastDataIdx + 1;
+
+        $colA = Coordinate::stringFromColumnIndex($nameColIdx);
+        $colStart = Coordinate::stringFromColumnIndex($firstDataIdx);
+        $colEnd = Coordinate::stringFromColumnIndex($lastDataIdx);
+        $colRowTot = Coordinate::stringFromColumnIndex($rowTotalIdx);
+
+        // ========== ENTRATE ==========
+        // Header
+        $activeWorksheet->setCellValue($colA . '1', 'Entrate');
+        foreach ($columns as $idx => $column) {
+            $col = Coordinate::stringFromColumnIndex($firstDataIdx + $idx);
+            $activeWorksheet->setCellValue($col . '1', $this->getMonth($column));
+        }
+        $activeWorksheet->setCellValue($colRowTot . '1', 'Totale');
+
+        $activeWorksheet->getStyle($colA . '1:' . $colRowTot . '1')->getFont()->setBold(true);
+        $activeWorksheet->getStyle($colA . '1:' . $colRowTot . '1')
+            ->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+            ->getStartColor()->setARGB('C6E0B4'); // verde chiaro
+
+        $count = 2;
+
+        $parentIdsIn = array_column(array_filter($rows_in, function ($r) {
+            return (int)($r['level'] ?? 0) === 0;
+        }), 'id');
+
+        $totalsIn = [];
+
+        foreach ($rows_in as $in) {
+            $activeWorksheet->setCellValue($colA . $count, str_repeat("  ", (int)$in["level"]) . $in["name"]);
+
+            $rowSum = 0.0;
+
+            foreach ($columns as $idx => $column) {
+                $col = Coordinate::stringFromColumnIndex($firstDataIdx + $idx);
+
+                if (isset($records_in[$column][$in["id"]])) {
+                    $val = (float)$records_in[$column][$in["id"]];
+                    $writeNumber($activeWorksheet, $col . $count, $val);
+
+                    if ($shouldCountFn($in, $parentIdsIn)) {
+                        $totalsIn[$idx] = ($totalsIn[$idx] ?? 0) + $val;
+                    }
+
+                    $rowSum += $val;
+                }
+            }
+
+            $writeNumber($activeWorksheet, $colRowTot . $count, $rowSum);
+
+            if ((int)$in["level"] === 0) {
+                $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)->getFont()->setBold(true);
+            }
+
+            $count++;
+        }
+
+        // Riga "Totale" ENTRATE
+        $activeWorksheet->setCellValue($colA . $count, 'Totale');
+
+        $grandTotalIn = 0.0;
+        foreach ($totalsIn as $idx => $total) {
+            $col = Coordinate::stringFromColumnIndex($firstDataIdx + $idx);
+            $writeNumber($activeWorksheet, $col . $count, (float)$total);
+            $grandTotalIn += (float)$total;
+        }
+        // Totale complessivo nella colonna "Totale"
+        $writeNumber($activeWorksheet, $colRowTot . $count, $grandTotalIn);
+
+        $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)->getFont()->setBold(true);
+        $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)
+            ->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+            ->getStartColor()->setARGB('C6E0B4');
+
+        // ========== USCITE ==========
+        $count += 2;
+
+        // Header
+        $activeWorksheet->setCellValue($colA . $count, 'Uscite');
+        foreach ($columns as $idx => $column) {
+            $col = Coordinate::stringFromColumnIndex($firstDataIdx + $idx);
+            $activeWorksheet->setCellValue($col . $count, $this->getMonth($column));
+        }
+        $activeWorksheet->setCellValue($colRowTot . '1', 'Totale'); // già impostato sopra, qui lasciamo l’header a riga corrente
+        $activeWorksheet->setCellValue($colRowTot . $count, 'Totale');
+
+        $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)->getFont()->setBold(true);
+        $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)
+            ->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+            ->getStartColor()->setARGB('F8CBAD'); // rosso chiaro
+
+        $count++;
+
+        $parentIdsOut = array_column(array_filter($rows_out, function ($r) {
+            return (int)($r['level'] ?? 0) === 0;
+        }), 'id');
+
+        $totalsOut = [];
+
+        foreach ($rows_out as $out) {
+            $activeWorksheet->setCellValue($colA . $count, str_repeat("  ", (int)$out["level"]) . $out["name"]);
+
+            $rowSum = 0.0;
+
+            foreach ($columns as $idx => $column) {
+                $col = Coordinate::stringFromColumnIndex($firstDataIdx + $idx);
+
+                if (isset($records_out[$column][$out["id"]])) {
+                    $val = (float)$records_out[$column][$out["id"]];
+                    $writeNumber($activeWorksheet, $col . $count, $val);
+
+                    if ($shouldCountFn($out, $parentIdsOut)) {
+                        $totalsOut[$idx] = ($totalsOut[$idx] ?? 0) + $val;
+                    }
+
+                    $rowSum += $val;
+                }
+            }
+
+            $writeNumber($activeWorksheet, $colRowTot . $count, $rowSum);
+
+            if ((int)$out["level"] === 0) {
+                $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)->getFont()->setBold(true);
+            }
+
+            $count++;
+        }
+
+        // Riga "Totale" USCITE
+        $activeWorksheet->setCellValue($colA . $count, 'Totale');
+
+        $grandTotalOut = 0.0;
+        foreach ($totalsOut as $idx => $total) {
+            $col = Coordinate::stringFromColumnIndex($firstDataIdx + $idx);
+            $writeNumber($activeWorksheet, $col . $count, (float)$total);
+            $grandTotalOut += (float)$total;
+        }
+        $writeNumber($activeWorksheet, $colRowTot . $count, $grandTotalOut);
+
+        $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)->getFont()->setBold(true);
+        $activeWorksheet->getStyle($colA . $count . ':' . $colRowTot . $count)
+            ->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
+            ->getStartColor()->setARGB('F8CBAD');
+
+        // ========== Larghezze colonne ==========
+        $activeWorksheet->getColumnDimension($colA)->setWidth(35);
+        for ($i = $firstDataIdx; $i <= $rowTotalIdx; $i++) {
+            $activeWorksheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setWidth(20);
+        }
+
+        // ========== Salvataggio ==========
+        $fileSuffix = $isYearExport ? 'AnnoFiscale' : 'Selezione';
+        $filename = date("Ymd") . '_Gestionale_' . $fileSuffix . '.xlsx';
+
+        try {
+            // Get current client for folder organization
+            $currentClient = session('currentClient', 'default');
+
+            // Create temporary file to write Excel data
+            $tempPath = sys_get_temp_dir() . '/' . $filename;
+
+            // Write to temporary file
+            $writer = new Xlsx($spreadsheet);
+            $writer->save($tempPath);
+
+            // Get Wasabi disk
+            $disk = Storage::disk('s3');
+
+            // Create the S3 path (client folder + reports subfolder)
+            $s3Path = $currentClient . '/reports/' . $filename;
+
+            // Ensure the client reports folder exists
+            $reportsFolderPath = $currentClient . '/reports/.gitkeep';
+            if (!$disk->exists($reportsFolderPath)) {
+                $disk->put($reportsFolderPath, '');
+                Log::info("Created reports folder for client: {$currentClient}");
+            }
+
+            // Upload file to Wasabi S3
+            $fileContent = file_get_contents($tempPath);
+            $uploaded = $disk->put($s3Path, $fileContent, 'private');
+
+            if (!$uploaded) {
+                throw new \Exception('Failed to upload file to Wasabi S3');
+            }
+
+            Log::info("Management report uploaded to Wasabi", [
+                'client' => $currentClient,
+                'path' => $s3Path,
+                'size' => filesize($tempPath),
+                'type' => $fileSuffix
+            ]);
+
+            // Clean up temporary file
+            if (file_exists($tempPath)) {
+                unlink($tempPath);
+            }
+
+            // Return S3 path for further processing or download URL generation
+            return [
+                'success' => true,
+                'path' => $s3Path,
+                'local_path' => null,
+                'storage_type' => 's3'
+            ];
+        } catch (\Exception $e) {
+            Log::error('Error uploading management report to Wasabi S3', [
+                'error' => $e->getMessage(),
+                'client' => session('currentClient', 'unknown'),
+                'filename' => $filename
+            ]);
+
+            // Fallback to local storage if S3 fails
+            $currentClient = session('currentClient', 'default');
+            $clientFolder = storage_path('app/reports/' . $currentClient);
+
+            // Ensure client folder exists
+            if (!is_dir($clientFolder)) {
+                mkdir($clientFolder, 0755, true);
+                Log::info("Created local client reports folder: {$clientFolder}");
+            }
+
+            $localPath = $clientFolder . '/' . $filename;
+            $writer = new Xlsx($spreadsheet);
+            $writer->save($localPath);
+
+            Log::warning("Management report saved locally due to S3 error", [
+                'client' => $currentClient,
+                'local_path' => $localPath,
+                'error' => $e->getMessage()
+            ]);
+
+            // Return local path for backward compatibility
+            return [
+                'success' => true,
+                'path' => $localPath,
+                'local_path' => $localPath,
+                'storage_type' => 'local'
+            ];
+        }
+    }
+}

+ 15 - 1
app/Http/Livewire/Reports.php

@@ -249,6 +249,8 @@ class Reports extends Component
         $query = DB::table('records_rows')
             ->join('records', 'records_rows.record_id', '=', 'records.id')
             ->join('causals', 'records_rows.causal_id', '=', 'causals.id')
+            ->leftJoin('courses', 'records_rows.course_id', '=', 'courses.id')
+            ->leftJoin('subscriptions', 'records_rows.subscription_id', '=', 'subscriptions.id')
             ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']]);
 
         $query->where('records.type', 'IN');
@@ -259,13 +261,15 @@ class Reports extends Component
             'causals.id',
             'causals.name',
             'causals.parent_id',
+            'courses.id as course_id',
+            'subscriptions.id as subscription_id',
             DB::raw('SUM(records_rows.amount) as total_amount')
         )
             ->where(function ($query) {
                 $query->where('causals.no_reports', '=', '0')
                     ->orWhereNull('causals.no_reports');
             })
-            ->groupBy('causals.id', 'causals.name', 'causals.parent_id')
+            ->groupBy('causals.id', 'causals.name', 'causals.parent_id', 'courses.id', 'subscriptions.id')
             ->orderBy('total_amount', 'desc')
             ->limit($limit)
             ->get();
@@ -284,6 +288,16 @@ class Reports extends Component
 
             //$displayName = strlen($treeName) > 30 ? substr($treeName, 0, 27) . '...' : $treeName;
             $displayName = $treeName;
+
+            if (isset($causal->course_id) && $causal->course_id) {
+                $course = \App\Models\Course::find($causal->course_id);
+                $displayName .= " - " . $course->getDetailsName();
+            }
+            if (isset($causal->subscription_id) && $causal->subscription_id) {
+                $subscription = \App\Models\Subscription::find($causal->subscription_id);
+                $displayName .= " - " . $subscription->name;
+            }
+
             $inData[] = [
                 'label' => $displayName,
                 'value' => $causal->total_amount,

+ 727 - 0
app/Http/Livewire/Reports.php.bak

@@ -0,0 +1,727 @@
+<?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, 3);
+    }
+
+    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')
+        )
+            ->where(function ($query) {
+                $query->where('causals.no_reports', '=', '0')
+                    ->orWhereNull('causals.no_reports');
+            })
+            ->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'
+        ];
+        $monthNamesExtended = [
+            0 => 'Settembre',
+            1 => 'Ottobre',
+            2 => 'Novembre',
+            3 => 'Dicembre',
+            4 => 'Gennaio',
+            5 => 'Febbraio',
+            6 => 'Marzo',
+            7 => 'Aprile',
+            8 => 'Maggio',
+            9 => 'Giugno',
+            10 => 'Luglio',
+            11 => 'Agosto'
+        ];
+
+        $monthlyData = [];
+        foreach ($monthOrder as $i) {
+            $monthlyData[$i] = [
+                'earned' => 0,
+                'total' => 0,
+                'suspended' => 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;
+                            // }
+
+                            // pagamenti effettuati
+                            if (!is_null($rate->record_id) && $rate->record_id !== '' && $rate->status == 1) {
+                                $monthlyData[$monthNumber]['participants']--;
+                                $monthlyData[$monthNumber]['earned'] += $pricePerMonth;
+                            }
+                            // pagamenti sospesi
+                            if ($rate->status == 2) {
+                                $monthlyData[$monthNumber]['participants']--;
+                                $monthlyData[$monthNumber]['total'] -= $pricePerMonth;
+                                $monthlyData[$monthNumber]['suspended']++;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        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'];
+            $suspended = $monthlyData[$month]['suspended'];
+
+            $labels[] = $monthNames[$month];
+            $earnedData[] = $earned;
+            $totalData[] = $total;
+            $participantData[] = $participants;
+            $suspendedData[] = $suspended;
+
+            $percentage = $total > 0 ? round(($earned / $total) * 100, 1) : 0;
+
+            $tableData[] = [
+                'month' => $monthNames[$month],
+                'participants' => $participants,
+                'suspended' => $suspended,
+                'earned' => $earned,
+                'total' => $total,
+                'delta' => $delta,
+                'percentage' => $percentage
+            ];
+        }
+
+        $daIncassareData = array_map(function($tot, $inc) {
+            return $tot - $inc;
+        }, $totalData, $earnedData);
+
+
+        return [
+            'labels' => $labels,
+            'datasets' => [
+                [
+                    'label' => 'TOT. INCASSATO',
+                    'data' => $earnedData,
+                    'participantData' => $participantData,
+                    'suspendedData' => $suspendedData,
+                    'monthNamesExtended' => $monthNamesExtended,
+                ],
+                [
+                    'label' => 'TOT. DA INCASSARE',
+                    'data' => $daIncassareData,
+                    'participantData' => $participantData,
+                    'suspendedData' => $suspendedData,
+                    'monthNamesExtended' => $monthNamesExtended,
+                ]
+            ],
+            '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
+        ];
+    }
+}

+ 245 - 129
app/Http/Livewire/SmsComunications.php

@@ -3,198 +3,314 @@
 namespace App\Http\Livewire;
 
 use Livewire\Component;
-use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\DB;
+use Carbon\Carbon;
+
 use App\Http\Middleware\TenantMiddleware;
-use App\Models\SmsTemplate;
-use App\Models\SmsScheduled;
-use App\Models\Category;
+use App\Models\SmsMessage;
 use App\Models\Member;
-use App\Models\User;
-use Carbon\Carbon;
-use Illuminate\Support\Facades\Log;
+
 class SmsComunications extends Component
 {
-    public $records, $subject, $message, $selectedRecipients = [], $scheduledDateTime, $sendNow = true;
-    public $dataId, $update = false, $add = false;
-    public $members = [];
-
-    protected $rules = [
-        'subject' => 'required|string|max:255',
-        'message' => 'required|string|max:160',
-        'selectedRecipients' => 'required|array|min:1',
-        'scheduledDateTime' => 'required_if:sendNow,false|date|after:now',
-    ];
-
-    protected $messages = [
-        'subject.required' => 'L\'oggetto è obbligatorio',
-        'message.required' => 'Il messaggio è obbligatorio',
-        'message.max' => 'Il messaggio non può superare 160 caratteri',
-        'selectedRecipients.required' => 'Seleziona almeno un gruppo di destinatari',
-        'scheduledDateTime.required_if' => 'La data di programmazione è obbligatoria',
-        'scheduledDateTime.after' => 'La data di programmazione deve essere futura',
-    ];
-
-    public $sortField = 'name';
-    public $sortAsc = true;
+    public ?int $messageId = null;
+    public string $subject = '';
+    public string $content = '';
+
+    public array $recipients = [];
+
+    public string $mode = 'now'; // 'now' | 'schedule'
+    public ?string $schedule_at = null;
+    public ?string $timezone = 'UTC';
+
+    public $records;
+    public $categories;
+    public $courses;
+
+    public bool $showForm = false;
+    public bool $locked = false;
+
+    public $success;
+    public $error;
 
     public function boot()
     {
         app(TenantMiddleware::class)->setupTenantConnection();
     }
 
-    public function sortBy($field)
+    public function mount()
     {
-        if($this->sortField === $field) {
-            $this->sortAsc = ! $this->sortAsc;
+        if (auth()->user()?->level != env('LEVEL_ADMIN', 0)) {
+            return redirect()->to('/dashboard');
+        }
+
+        $this->categories = [];
+        $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
+
+        $this->courses = [];
+        $this->getCourses(\App\Models\Course::select('id', 'name')->where('parent_id', null)->orderBy('name', 'ASC')->get(), 0);
+
+        $this->schedule_at = now($this->timezone)->addHour()->format('Y-m-d\TH:i');
+    }
+
+    public function render()
+    {
+        if (!$this->showForm) {
+            $this->records = SmsMessage::withCount(['recipients'])
+                ->orderBy('created_at', 'desc')
+                ->get();
         } else {
-            $this->sortAsc = true;
+            $this->categories = [];
+            $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
+
+            $this->courses = [];
+            $this->getCourses(\App\Models\Course::select('id', 'name')->where('parent_id', null)->orderBy('name', 'ASC')->get(), 0);
         }
 
-        $this->sortField = $field;
+        return view('livewire.sms_comunications');
     }
 
-    public function resetFields()
+    protected function baseRules(): array
     {
-        $this->subject = '';
-        $this->message = '';
-        $this->selectedRecipients = [];
-        $this->sendNow = true;
-        $this->scheduledDateTime = now()->addHour()->format('Y-m-d\TH:i');
-        $this->emit('load-data-table');
+        return [
+            'subject' => 'required|string|max:255',
+            'content' => 'required|string',
+            'recipients' => 'required|array|min:1',
+            'recipients.*.phone' => 'required|string',
+        ];
     }
 
-    public function mount()
+    protected function validateDraft(): void
     {
-        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
-            return redirect()->to('/dashboard');
+        $rules = [];
+        // $rules = $this->baseRules();
+        $rules['subject'] = 'required|string|max:255';
+        $this->validate($rules);
+    }
 
-        $this->members = Member::select('id', 'last_name', 'email')->get();
-        $this->scheduledDateTime = now()->addHour()->format('Y-m-d\TH:i');
+    protected function validateSend(): void
+    {
+        $this->validate($this->baseRules());
     }
 
-    public function render()
+    protected function validateSchedule(): void
     {
-        $this->records = SmsTemplate::orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')->get();
-        return view('livewire.sms_comunications');
+        $rules = $this->baseRules();
+        $rules['schedule_at'] = 'required|date|after:now';
+        $this->validate($rules);
     }
 
     public function add()
     {
-        $this->resetFields();
-        $this->add = true;
-        $this->update = false;
+        $this->reset(['messageId', 'subject', 'content', 'recipients', 'mode', 'schedule_at']);
+        $this->mode = 'now';
+        $this->schedule_at = now($this->timezone)->addHour()->format('Y-m-d\TH:i');
+
+        $this->showForm = true;
+
+        $this->dispatchBrowserEvent('init-recipients-table', [
+            'selected' => collect($this->recipients)->pluck('member_id')->filter()->values()->all(),
+        ]);
     }
 
-    public function store()
+    public function edit($id)
     {
-        $this->validate();
-
         try {
-            $template = SmsTemplate::create([
-                'name' => $this->subject,
-                'content' => $this->message,
-                'created_by' => Auth::id(),
-            ]);
+            $msg = SmsMessage::with(['recipients'])->findOrFail($id);
 
-            $recipients = User::whereIn('id', $this->selectedRecipients)->get();
+            $this->messageId = $msg->id;
+            $this->subject = $msg->subject;
+            $this->content = $msg->content;
+            $this->recipients = $msg->recipients->map(fn($r) => [
+                'member_id' => $r->member_id,
+                'phone' => $r->phone,
+                'first_name'    => optional($r->member)->first_name,
+                'last_name'     => optional($r->member)->last_name,
+            ])->toArray();
+            $this->mode = $msg->status === 'scheduled' ? 'schedule' : 'now';
+            $this->schedule_at = optional($msg->schedule_at)?->setTimezone($this->timezone)?->format('Y-m-d\TH:i');
 
-            if ($this->sendNow) {
-                $this->sendSmsNow($template, $recipients);
-                session()->flash('success', 'Template creato e SMS inviato a ' . $recipients->count() . ' destinatari!');
-            } else {
-                $this->scheduleSms($template, $recipients);
-                $scheduledDate = Carbon::parse($this->scheduledDateTime)->format('d/m/Y H:i');
-                session()->flash('success', 'Template creato e SMS programmato per ' . $scheduledDate);
-            }
+            $this->showForm = true;
+            $this->locked = $msg->isLocked();
 
-            $this->resetFields();
-            $this->add = false;
-        } catch (\Exception $ex) {
-            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+            $this->dispatchBrowserEvent('init-recipients-table', [
+                'selected' => collect($this->recipients)->pluck('member_id')->filter()->values()->all(),
+            ]);
+        } catch (\Throwable $ex) {
+            $this->error = 'Errore (' . $ex->getMessage() . ')';
         }
     }
 
-    public function edit($id)
+
+    public function duplicate($id, $withRecipients = true)
     {
         try {
-            $template = SmsTemplate::findOrFail($id);
-            if (!$template) {
-                session()->flash('error', 'Template SMS non trovato');
-            } else {
-                $this->subject = $template->name;
-                $this->message = $template->content;
-                $this->dataId = $template->id;
-                $this->update = true;
-                $this->add = false;
+            $copy = SmsMessage::with(['recipients'])->findOrFail($id)->duplicate($withRecipients);
+            $this->edit($copy->id);
+            $this->success = 'Bozza duplicata';
+        } catch (\Throwable $ex) {
+            $this->error = 'Errore (' . $ex->getMessage() . ')';
+        }
+    }
+
+    public function saveDraft()
+    {
+        $this->validateDraft();
+
+        DB::transaction(function () {
+            $msg = $this->upsertMessage(status: 'draft', scheduleAt: null);
+            $this->upsertRecipients($msg);
+            $this->messageId = $msg->id;
+            $this->locked = $msg->isLocked();
+        });
+
+        $this->success = 'Bozza salvata';
+
+        $this->dispatchBrowserEvent('scroll-top');
+    }
+
+    public function sendNow()
+    {
+        $this->validateSend();
+
+        if ($this->messageId) {
+            $existing = SmsMessage::findOrFail($this->messageId);
+            if ($existing->isLocked()) {
+                $this->error = 'Questo sms è già in invio o inviato e non può essere modificato.';
+                return;
             }
-        } catch (\Exception $ex) {
-            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
         }
+
+        DB::transaction(function () {
+            $msg = $this->upsertMessage(status: 'processing', scheduleAt: null);
+            $this->upsertRecipients($msg, true);
+            $this->messageId = $msg->id;
+            $this->locked = true;
+        });
+
+        dispatch(new \App\Jobs\SendSmsMessage($this->messageId));
+        $this->success = 'Invio avviato';
+
+        $this->dispatchBrowserEvent('scroll-top');
     }
 
-    public function update()
+    public function scheduleMessage()
     {
-        $this->validate([
-            'subject' => 'required|string|max:255',
-            'message' => 'required|string|max:160',
-        ]);
+        $this->validateSchedule();
 
-        try {
-            SmsTemplate::whereId($this->dataId)->update([
-                'name' => $this->subject,
-                'content' => $this->message,
-            ]);
-            session()->flash('success', 'Template SMS aggiornato');
-            $this->resetFields();
-            $this->update = false;
-        } catch (\Exception $ex) {
-            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        if ($this->messageId) {
+            $existing = SmsMessage::findOrFail($this->messageId);
+            if ($existing->isLocked()) {
+                $this->error = 'Questo sms è già in invio o inviato e non può essere modificato.';
+                return;
+            }
         }
+
+        $scheduledAt = \Carbon\Carbon::parse($this->schedule_at, $this->timezone)->setTimezone('UTC');
+
+        DB::transaction(function () use ($scheduledAt) {
+            $msg = $this->upsertMessage(status: 'scheduled', scheduleAt: $scheduledAt);
+            $this->upsertRecipients($msg, true);
+            $this->messageId = $msg->id;
+            $this->locked = $msg->isLocked();
+        });
+
+        $this->success = 'Sms programmato';
+
+        $this->dispatchBrowserEvent('scroll-top');
+    }
+
+    protected function upsertMessage(string $status, $scheduleAt): SmsMessage
+    {
+        return SmsMessage::updateOrCreate(
+            ['id' => $this->messageId],
+            [
+                'subject' => $this->subject,
+                'content' => $this->content,
+                'status' => $status,
+                'schedule_at' => $scheduleAt,
+                'created_by'   => auth()->id(),
+            ]
+        );
+    }
+
+    protected function upsertRecipients(SmsMessage $msg, bool $force = false): void
+    {
+        if (!$force && $msg->isLocked()) return;
+
+        $msg->recipients()->delete();
+
+        $rows = collect($this->recipients)->map(fn($r) => [
+            'sms_message_id' => $msg->id,
+            'member_id' => $r['member_id'] ?? null,
+            'phone' => $r['phone'],
+            'status' => 'pending',
+            'created_at' => now(),
+            'updated_at' => now(),
+        ])->values()->all();
+
+        if ($rows) \App\Models\SmsMessageRecipient::insert($rows);
     }
 
     public function cancel()
     {
-        $this->add = false;
-        $this->update = false;
-        $this->resetFields();
+        $this->showForm = false;
+        $this->reset(['messageId', 'subject', 'content', 'recipients', 'mode', 'schedule_at']);
+        $this->mode = 'now';
+        $this->schedule_at = now($this->timezone)->addHour()->format('Y-m-d\TH:i');
+
+        $this->dispatchBrowserEvent('init-archive-table');
     }
 
-    public function sendTemplate($id)
+    public function getCategories($records, $indentation)
     {
-        try {
-            $template = SmsTemplate::findOrFail($id);
-            $this->subject = $template->name;
-            $this->message = $template->content;
-            $this->add = true;
-            $this->update = false;
-        } catch (\Exception $ex) {
-            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        foreach ($records as $record) {
+            $this->categories[] = array('id' => $record->id, 'name' => $record->getTree());
+            if (count($record->childs))
+                $this->getCategories($record->childs, $indentation + 1);
         }
     }
 
-    private function sendSmsNow($template, $recipients)
+    public function getCourses($records, $indentation)
     {
-        // Simple SMS sending logic
-        foreach ($recipients as $recipient) {
-            if ($recipient->phone) {
-                // Here you would integrate with your SMS provider
-                Log::info("SMS sent to {$recipient->name} ({$recipient->phone}): {$template->content}");
-            }
+        /** @var \App\Models\Course $record */
+        foreach ($records as $record) {
+            $this->courses[] = array('id' => $record->id, 'name' => $record->getTree());
+            if (count($record->childs))
+                $this->getCourses($record->childs, $indentation + 1);
         }
     }
 
-    private function scheduleSms($template, $recipients)
+    public function toggleRecipient($id)
     {
-        $scheduled = SmsScheduled::create([
-            'template_id' => $template->id,
-            'content' => $template->content,
-            'scheduled_at' => Carbon::parse($this->scheduledDateTime),
-            'status' => 'scheduled',
-            'created_by' => Auth::id(),
-        ]);
+        $id = (int)$id;
+
+        $idx = collect($this->recipients)->search(fn($r) => (int)($r['member_id'] ?? 0) === $id);
+        if ($idx !== false) {
+            array_splice($this->recipients, $idx, 1);
+            return;
+        }
+
+        $m = Member::select('id', 'phone', 'first_name', 'last_name')->find($id);
+        if (!$m || empty($m->phone)) return;
+
+        $this->recipients[] = [
+            'member_id' => $m->id,
+            'phone' => $m->phone,
+            'first_name' => $m->first_name,
+            'last_name' => $m->last_name,
+        ];
+    }
+
+    public function deleteMessage(int $id)
+    {
+        $msg = \App\Models\SmsMessage::findOrFail($id);
+
+        if (! in_array($msg->status, ['draft', 'failed'], true)) {
+            return;
+        }
 
-        $scheduled->recipients()->attach($recipients->pluck('id'));
+        $msg->delete();
 
-        Log::info("SMS scheduled for {$this->scheduledDateTime} to {$recipients->count()} recipients: {$template->content}");
+        $this->dispatchBrowserEvent('sms-deleted', ['id' => $id]);
     }
 }

+ 200 - 0
app/Http/Livewire/SmsComunications.php.bak

@@ -0,0 +1,200 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
+use App\Models\SmsTemplate;
+use App\Models\SmsScheduled;
+use App\Models\Category;
+use App\Models\Member;
+use App\Models\User;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Log;
+class SmsComunications extends Component
+{
+    public $records, $subject, $message, $selectedRecipients = [], $scheduledDateTime, $sendNow = true;
+    public $dataId, $update = false, $add = false;
+    public $members = [];
+
+    protected $rules = [
+        'subject' => 'required|string|max:255',
+        'message' => 'required|string|max:160',
+        'selectedRecipients' => 'required|array|min:1',
+        'scheduledDateTime' => 'required_if:sendNow,false|date|after:now',
+    ];
+
+    protected $messages = [
+        'subject.required' => 'L\'oggetto è obbligatorio',
+        'message.required' => 'Il messaggio è obbligatorio',
+        'message.max' => 'Il messaggio non può superare 160 caratteri',
+        'selectedRecipients.required' => 'Seleziona almeno un gruppo di destinatari',
+        'scheduledDateTime.required_if' => 'La data di programmazione è obbligatoria',
+        'scheduledDateTime.after' => 'La data di programmazione deve essere futura',
+    ];
+
+    public $sortField = 'name';
+    public $sortAsc = true;
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function sortBy($field)
+    {
+        if($this->sortField === $field) {
+            $this->sortAsc = ! $this->sortAsc;
+        } else {
+            $this->sortAsc = true;
+        }
+
+        $this->sortField = $field;
+    }
+
+    public function resetFields()
+    {
+        $this->subject = '';
+        $this->message = '';
+        $this->selectedRecipients = [];
+        $this->sendNow = true;
+        $this->scheduledDateTime = now()->addHour()->format('Y-m-d\TH:i');
+        $this->emit('load-data-table');
+    }
+
+    public function mount()
+    {
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
+            return redirect()->to('/dashboard');
+
+        $this->members = Member::select('id', 'last_name', 'email')->get();
+        $this->scheduledDateTime = now()->addHour()->format('Y-m-d\TH:i');
+    }
+
+    public function render()
+    {
+        $this->records = SmsTemplate::orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')->get();
+        return view('livewire.sms_comunications');
+    }
+
+    public function add()
+    {
+        $this->resetFields();
+        $this->add = true;
+        $this->update = false;
+    }
+
+    public function store()
+    {
+        $this->validate();
+
+        try {
+            $template = SmsTemplate::create([
+                'name' => $this->subject,
+                'content' => $this->message,
+                'created_by' => Auth::id(),
+            ]);
+
+            $recipients = User::whereIn('id', $this->selectedRecipients)->get();
+
+            if ($this->sendNow) {
+                $this->sendSmsNow($template, $recipients);
+                session()->flash('success', 'Template creato e SMS inviato a ' . $recipients->count() . ' destinatari!');
+            } else {
+                $this->scheduleSms($template, $recipients);
+                $scheduledDate = Carbon::parse($this->scheduledDateTime)->format('d/m/Y H:i');
+                session()->flash('success', 'Template creato e SMS programmato per ' . $scheduledDate);
+            }
+
+            $this->resetFields();
+            $this->add = false;
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function edit($id)
+    {
+        try {
+            $template = SmsTemplate::findOrFail($id);
+            if (!$template) {
+                session()->flash('error', 'Template SMS non trovato');
+            } else {
+                $this->subject = $template->name;
+                $this->message = $template->content;
+                $this->dataId = $template->id;
+                $this->update = true;
+                $this->add = false;
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function update()
+    {
+        $this->validate([
+            'subject' => 'required|string|max:255',
+            'message' => 'required|string|max:160',
+        ]);
+
+        try {
+            SmsTemplate::whereId($this->dataId)->update([
+                'name' => $this->subject,
+                'content' => $this->message,
+            ]);
+            session()->flash('success', 'Template SMS aggiornato');
+            $this->resetFields();
+            $this->update = false;
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function cancel()
+    {
+        $this->add = false;
+        $this->update = false;
+        $this->resetFields();
+    }
+
+    public function sendTemplate($id)
+    {
+        try {
+            $template = SmsTemplate::findOrFail($id);
+            $this->subject = $template->name;
+            $this->message = $template->content;
+            $this->add = true;
+            $this->update = false;
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    private function sendSmsNow($template, $recipients)
+    {
+        // Simple SMS sending logic
+        foreach ($recipients as $recipient) {
+            if ($recipient->phone) {
+                // Here you would integrate with your SMS provider
+                Log::info("SMS sent to {$recipient->name} ({$recipient->phone}): {$template->content}");
+            }
+        }
+    }
+
+    private function scheduleSms($template, $recipients)
+    {
+        $scheduled = SmsScheduled::create([
+            'template_id' => $template->id,
+            'content' => $template->content,
+            'scheduled_at' => Carbon::parse($this->scheduledDateTime),
+            'status' => 'scheduled',
+            'created_by' => Auth::id(),
+        ]);
+
+        $scheduled->recipients()->attach($recipients->pluck('id'));
+
+        Log::info("SMS scheduled for {$this->scheduledDateTime} to {$recipients->count()} recipients: {$template->content}");
+    }
+}

+ 284 - 0
app/Http/Livewire/Subscription.php

@@ -0,0 +1,284 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use App\Http\Middleware\TenantMiddleware;
+use Livewire\Component;
+use Illuminate\Support\Facades\Auth;
+
+class Subscription extends Component
+{
+
+    protected $listeners = ['setCausal' => 'setCausal', 'setSubscriptionCausal' => 'setSubscriptionCausal'];
+
+    public $records;
+
+    public $dataId;
+    public $update = false;
+    public $add = false;
+
+    public $name;
+    public $causal_id;
+    public $sub_causal_id;
+    public $subscription_price;
+    public $prices;
+    public $enabled;
+
+    public $msgPrices = '';
+
+    public $course_subscriptions = [];
+
+    public $typeIN = 'IN';
+    public $setSubscriptionCausal = 'setSubscriptionCausal';
+
+    protected $rules = [
+        'name' => 'required',
+        'subscription_price' => 'required|min:0|not_in:0'
+    ];
+
+    protected $messages = [
+        'name.required' => 'Il nome è obbligatorio',
+        'subscription_price.required' => 'Deve essere maggiore di zero',
+        'subscription_price.not_in' => 'Deve essere maggiore di zero',
+    ];
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function resetFields()
+    {
+        $this->name = '';
+        $this->causal_id = null;
+        $this->sub_causal_id = null;
+        $this->subscription_price = 0;
+        $this->prices = [];
+        $this->prices[] = array('course_subscription_id' => null, 'price' => 0);
+        $this->enabled = true;
+
+        $this->emit('load-data-table');
+    }
+
+
+    public function mount()
+    {
+        if (Auth::user()->level != env('LEVEL_ADMIN', 0))
+            return redirect()->to('/dashboard');
+
+        $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->where('enabled', true)->get();
+    }
+
+    public function render()
+    {
+        $this->records = \App\Models\Subscription::all();
+
+        return view('livewire.subscription');
+    }
+
+    public function add()
+    {
+        $this->resetFields();
+
+        $this->add = true;
+        $this->update = false;
+
+        $this->emit('setEdit', true);
+    }
+
+    public function store()
+    {
+        $this->validate();
+        try {
+            $this->msgPrices = '';
+
+            if ($this->prices[0]['course_subscription_id'] == null)
+                $this->msgPrices = 'Devi inserire almeno un prezzo';
+
+            $subscriptions = array_column($this->prices, 'course_subscription_id');
+            $unique_subscriptions = array_unique($subscriptions);
+            if (count($subscriptions) != count($unique_subscriptions))
+                $this->msgPrices = 'Non è possibile aggiungere più volte la stessa tipologia di pagamento';
+
+            if ($this->msgPrices == '') {
+                $subscription = new \App\Models\Subscription();
+
+                $subscription->name = $this->name;
+                $subscription->subscription_price = currencyToDouble($this->subscription_price);
+                $subscription->prices = json_encode($this->prices);
+                $subscription->enabled = $this->enabled;
+
+                $causal = "PAGAMENTO ABBONAMENTO";
+                $cp = \App\Models\Causal::where('name', $causal)->first();
+                if (!$cp) {
+                    $cp = new \App\Models\Causal();
+                    $cp->name = $causal;
+                    $cp->type = "IN";
+                    $cp->parent_id = null;
+                    $cp->money = false;
+                    $cp->corrispettivo_fiscale = false;
+                    $cp->no_receipt = false;
+                    $cp->user_status = false;
+                    $cp->no_first = false;
+                    $cp->no_records = false;
+                    $cp->enabled = true;
+                    $cp->save();
+                }
+                $subscription->causal_id = $cp->id;
+
+                $causal = "PAGAMENTO ISCRIZIONE";
+                $ci = \App\Models\Causal::where('name', $causal)->first();
+                if (!$ci) {
+                    $ci = new \App\Models\Causal();
+                    $ci->name = $causal;
+                    $ci->type = "IN";
+                    $ci->parent_id = null;
+                    $ci->money = false;
+                    $ci->corrispettivo_fiscale = false;
+                    $ci->no_receipt = false;
+                    $ci->user_status = false;
+                    $ci->no_first = false;
+                    $ci->no_records = false;
+                    $ci->enabled = true;
+                    $ci->save();
+                }
+                $subscription->sub_causal_id = $ci->id;
+
+                $subscription->save();
+
+                session()->flash('success', 'Abbonamento creato');
+                $this->resetFields();
+                $this->add = false;
+                $this->emit('setEdit', false);
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function edit($id)
+    {
+        $this->resetFields();
+
+        try {
+            $subscription = \App\Models\Subscription::findOrFail($id);
+
+            if (!$subscription) {
+                session()->flash('error', 'Abbonamento non trovato');
+            } else {
+                $this->name = $subscription->name;
+                $this->enabled = $subscription->enabled;
+                $this->causal_id = $subscription->causal_id;
+                $this->sub_causal_id = $subscription->sub_causal_id;
+                $this->subscription_price = formatPrice($subscription->subscription_price);
+                $this->prices = array();
+                if ($subscription->prices != null) {
+                    foreach (json_decode($subscription->prices) as $z) {
+                        $this->prices[] = array("course_subscription_id" => $z->course_subscription_id, "price" => $z->price);
+                    }
+                } else {
+                    $this->prices[] = array('course_subscription_id' => null, 'price' => 0);
+                }
+                $this->dataId = $subscription->id;
+                $this->update = true;
+                $this->add = false;
+            }
+            $this->emit('setEdit', true);
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function update()
+    {
+        $this->validate();
+        try {
+            $this->msgPrices = '';
+
+            if ($this->prices[0]['course_subscription_id'] == null)
+                $this->msgPrices = 'Devi inserire almeno un prezzo';
+
+            $subscriptions = array_column($this->prices, 'course_subscription_id');
+            $unique_subscriptions = array_unique($subscriptions);
+            if (count($subscriptions) != count($unique_subscriptions))
+                $this->msgPrices = 'Non è possibile aggiungere più volte la stessa tipologia di pagamento';
+
+            if ($this->msgPrices == '') {
+                \App\Models\Subscription::whereId($this->dataId)->update([
+                    'name' => $this->name,
+                    'causal_id' => $this->causal_id,
+                    'sub_causal_id' => $this->sub_causal_id,
+                    'subscription_price' => currencyToDouble($this->subscription_price),
+                    'prices' => json_encode($this->prices),
+                    'enabled' => $this->enabled
+                ]);
+                session()->flash('success', 'Abbonamento aggiornato');
+                $this->resetFields();
+                $this->update = false;
+                $this->emit('setEdit', false);
+            }
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function cancel()
+    {
+        $this->add = false;
+        $this->update = false;
+
+        $this->resetFields();
+
+        $this->emit('setEdit', false);
+    }
+
+    public function delete($id)
+    {
+        try {
+            \App\Models\Subscription::find($id)->delete();
+            session()->flash('success', "Abbonamento eliminato");
+
+            return redirect(request()->header('Referer'));
+        } catch (\Exception $e) {
+            session()->flash('error', 'Errore (' . $e->getMessage() . ')');
+        }
+    }
+
+    public function setCausal($id, $idx)
+    {
+        $this->causal_id = $id;
+    }
+
+    public function setSubscriptionCausal($id, $idx)
+    {
+        $this->sub_causal_id = $id;
+    }
+
+    public function duplicate($id, $isMultiple)
+    {
+        $subscription = \App\Models\Subscription::findOrFail($id);
+        $new_subscription = $subscription->replicate();
+        $new_subscription->save();
+
+        if (!$isMultiple)
+            $this->edit($new_subscription->id);
+    }
+
+    public function duplicateMultiple($ids)
+    {
+        foreach ($ids as $id) {
+            $this->duplicate($id, true);
+        }
+        return redirect()->to('/subscriptions');
+    }
+
+    public function addPrice()
+    {
+        $this->prices[] = array('course_subscription_id' => null, 'price' => 0);
+    }
+
+    public function delPrice($idx)
+    {
+        unset($this->prices[$idx]);
+    }
+}

+ 105 - 0
app/Http/Livewire/SubscriptionMember.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use App\Http\Middleware\TenantMiddleware;
+use Livewire\Component;
+
+class SubscriptionMember extends Component
+{
+    public $records = [];
+
+    public $subscription_id;
+    public $subscription = null;
+
+    public $filter = "";
+
+    public $filterSubscription = "";
+    public $chkCertificateType = "";
+    public $chkCertificateScadenza = 0;
+    public $fromYear = "";
+    public $toYear = "";
+    public $fromFromYear = "";
+    public $toToYear = "";
+    public $chkCard = [];
+    public $filterStatus = [];
+
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function mount($id)
+    {
+        $this->subscription_id = $id;
+        $this->subscription = \App\Models\Subscription::findOrFail($this->subscription_id);
+    }
+
+    public function render()
+    {
+        $this->filter = '';
+        $members = \App\Models\MemberSubscription::with('member')->where('subscription_id', $this->subscription_id);
+
+        if ($this->filterSubscription != "") {
+            $ids = \App\Models\MemberSubscription::where('subscribed', $this->filterSubscription == 1 ? true : false)->pluck('id');
+            $members = $members->whereIn('id', $ids);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Pagata sottoscrizione: " . ($this->filterSubscription == 1 ? "SI" : "NO") . " ";
+        }
+        if ($this->chkCertificateType != "") {
+            $types = \App\Models\MemberCertificate::where('type', $this->chkCertificateType)->pluck('member_id');
+            $members = $members->whereIn('member_id', $types);
+        }
+        if ($this->chkCertificateScadenza > 0) {
+            if ($this->chkCertificateScadenza == "1")
+                $scad = \App\Models\MemberCertificate::where('expire_date', '<', date("Y-m-d"))->pluck('member_id');
+            if ($this->chkCertificateScadenza == "2")
+                $scad = \App\Models\MemberCertificate::whereBetween('expire_date', [date("Y-m-d"), date("Y-m-d", strtotime("+1 month"))])->pluck('member_id');
+            $members = $members->whereIn('member_id', $scad);
+        }
+        if ($this->fromYear != "") {
+            $m_ids = \App\Models\Member::where('birth_date', '<', date("Y-m-d", strtotime("-" . $this->fromYear . " year", time())))->pluck('id');
+            $members = $members->whereIn('member_id', $m_ids);
+        }
+        if ($this->toYear != "") {
+            $m_ids = \App\Models\Member::where('birth_date', '>', date("Y-m-d", strtotime("-" . $this->toYear . " year", time())))->pluck('id');
+            $members = $members->whereIn('member_id', $m_ids);
+        }
+        if ($this->fromFromYear != "") {
+            $m_ids = \App\Models\Member::whereYear('birth_date', '>=', $this->fromFromYear)->pluck('id');
+            $members = $members->whereIn('member_id', $m_ids);
+        }
+        if ($this->toToYear != "") {
+            $m_ids = \App\Models\Member::whereYear('birth_date', '<=', $this->toToYear)->pluck('id');
+            $members = $members->whereIn('member_id', $m_ids);
+        }
+        if (sizeof($this->chkCard) > 0) {
+            $card_ids = \App\Models\MemberCard::whereIn('card_id', $this->chkCard)->pluck('member_id');
+            $members = $members->whereIn('member_id', $card_ids);
+            $this->filter .= $this->filter != '' ? ', ' : '';
+            $this->filter .= "Tessera: ";
+            foreach ($this->chkCard as $card) {
+                $this->filter .= \App\Models\Card::findOrFail($card)->name . " ";
+            }
+        }
+
+        $aRet = [];
+        if (sizeof($this->filterStatus) > 0) {
+            foreach ($this->filterStatus as $s) {
+                foreach ($members->get() as $aaa) {
+                    $state = \App\Models\Member::findOrFail($aaa->member_id)->isActive();
+
+                    if ($state["status"] == $s)
+                        $aRet[] = $aaa;
+                }
+            }
+        } else {
+            $aRet = $members->get();
+        }
+
+        $this->records = $aRet;
+
+        return view('livewire.subscription_member');
+    }
+}

+ 16 - 0
app/Http/Livewire/SubscriptionMembers.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+
+class SubscriptionMembers extends Component
+{
+    public $records = [];
+
+    public function render()
+    {
+        $this->records = \App\Models\Subscription::where('enabled', true)->get();
+        return view('livewire.subscription_members');
+    }
+}

+ 13 - 4
app/Http/Livewire/User.php

@@ -19,12 +19,14 @@ class User extends Component
 
     public $records, $name, $cognome, $email, $password, $oldPassword, $level, $enabled, $dataId, $update = false, $add = false, $oldEmail = null;
     public $userExists = false;
+    public $password_confirmation;
 
     protected $rules = [
         'name' => 'required',
         'cognome' => 'required',
         'email' => 'required',
-        'password' => 'required'
+        'password' => 'required',
+        'password_confirmation' => 'required|same:password'
     ];
 
     protected $messages = [
@@ -32,6 +34,8 @@ class User extends Component
         'cognome.required' => 'Il cognome è obbligatorio',
         'email.required' => 'La mail è obbligatoria',
         'password.required' => 'La password è obbligatoria',
+        'password_confirmation.required' => 'Ripeti la password inserita',
+        'password_confirmation.same' => 'Le password non coincidono',
     ];
 
     /**
@@ -329,6 +333,7 @@ class User extends Component
         $this->cognome = '';
         $this->email = '';
         $this->password = '';
+        $this->password_confirmation = '';
         $this->oldPassword = '';
         $this->level = 0;
         $this->enabled = true;
@@ -383,7 +388,8 @@ class User extends Component
             'name' => 'required',
             'cognome' => 'required',
             'email' => 'required|email|unique:users,email',
-            'password' => 'required|min:6'
+            'password' => 'required|min:6',
+            'password_confirmation' => 'required|same:password'
         ];
 
         $messages = [
@@ -393,7 +399,9 @@ class User extends Component
             'email.email' => 'La mail deve essere un indirizzo valido',
             'email.unique' => 'Questa mail è già stata utilizzata',
             'password.required' => 'La password è obbligatoria',
-            'password.min' => 'La password deve essere di almeno 6 caratteri'
+            'password.min' => 'La password deve essere di almeno 6 caratteri',
+            'password_confirmation.required' => 'Ripeti la password inserita',
+            'password_confirmation.same' => 'Le password non coincidono',
         ];
 
         $this->validate($rules, $messages);
@@ -546,7 +554,8 @@ class User extends Component
             'name' => 'required',
             'cognome' => 'required',
             'email' => 'required|email',
-            'password' => 'nullable|min:6'
+            'password' => 'nullable|min:6',
+            'password_confirmation' => 'required|same:password'
         ];
 
         $this->validate($rules, $this->messages);

+ 770 - 0
app/Http/Livewire/User.php.bak

@@ -0,0 +1,770 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Mail;
+use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+use Illuminate\Support\Facades\Auth;
+
+class User extends Component
+{
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+        $this->logCurrentDatabase('After tenant connection setup in boot()');
+    }
+
+    public $records, $name, $cognome, $email, $password, $oldPassword, $level, $enabled, $dataId, $update = false, $add = false, $oldEmail = null;
+    public $userExists = false;
+
+    protected $rules = [
+        'name' => 'required',
+        'cognome' => 'required',
+        'email' => 'required',
+        'password' => 'required'
+    ];
+
+    protected $messages = [
+        'name.required' => 'Il nome è obbligatorio',
+        'cognome.required' => 'Il cognome è obbligatorio',
+        'email.required' => 'La mail è obbligatoria',
+        'password.required' => 'La password è obbligatoria',
+    ];
+
+    /**
+     * Helper method to log current database information
+     */
+    private function logCurrentDatabase($context = '')
+    {
+        try {
+            $currentConnection = DB::getDefaultConnection();
+            $currentDatabase = DB::connection()->getDatabaseName();
+            $user = Auth::user();
+
+            Log::info('Database Connection Info', [
+                'context' => $context,
+                'current_connection' => $currentConnection,
+                'current_database' => $currentDatabase,
+                'user_id' => $user ? $user->id : null,
+                'user_tenant_database' => $user ? $user->tenant_database : null,
+                'user_tenant_username' => $user ? $user->tenant_username : null,
+            ]);
+        } catch (\Exception $e) {
+            Log::error('Failed to get database info', [
+                'context' => $context,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * Create or update user in master database
+     */
+    private function syncUserToMasterDatabase($userData, $action = 'create', $oldEmail = null)
+    {
+        try {
+            Log::info('Syncing user to master database', [
+                'action' => $action,
+                'email' => $userData['email'],
+                'old_email' => $oldEmail
+            ]);
+
+            $masterConfig = [
+                'driver' => 'mysql',
+                'host' => env('DB_HOST', '127.0.0.1'),
+                'port' => env('DB_PORT', '3306'),
+                'database' => env('DB_DATABASE'),
+                'username' => env('DB_USERNAME'),
+                'password' => env('DB_PASSWORD'),
+                'charset' => 'utf8mb4',
+                'collation' => 'utf8mb4_unicode_ci',
+                'prefix' => '',
+                'strict' => true,
+                'engine' => null,
+            ];
+
+            config(['database.connections.master_sync' => $masterConfig]);
+
+            $currentUser = Auth::user();
+
+            $masterData = [
+                'name' => $userData['name'],
+                'email' => $userData['email'],
+                'password' => $userData['password'],
+                'tenant_database' => $currentUser->tenant_database,
+                'tenant_username' => $currentUser->tenant_username,
+                'tenant_password' => $currentUser->tenant_password,
+                'tenant_host' => '127.0.0.1',
+                'created_at' => now(),
+                'updated_at' => now()
+            ];
+
+            if ($action === 'create') {
+                $inserted = DB::connection('master_sync')
+                    ->table('users')
+                    ->insert($masterData);
+
+                if ($inserted) {
+                    Log::info('Successfully created user in master database', [
+                        'email' => $userData['email'],
+                        'tenant_database' => $currentUser->tenant_database
+                    ]);
+                    return true;
+                } else {
+                    Log::warning('Failed to create user in master database', [
+                        'email' => $userData['email']
+                    ]);
+                    return false;
+                }
+            } elseif ($action === 'update') {
+                $searchEmail = $oldEmail ?: $userData['email'];
+
+                unset($masterData['created_at']);
+
+                $updated = DB::connection('master_sync')
+                    ->table('users')
+                    ->where('email', $searchEmail)
+                    ->update($masterData);
+
+                if ($updated) {
+                    Log::info('Successfully updated user in master database', [
+                        'old_email' => $searchEmail,
+                        'new_email' => $userData['email']
+                    ]);
+                    return true;
+                } else {
+                    Log::warning('No user found in master database to update', [
+                        'search_email' => $searchEmail
+                    ]);
+                    return false;
+                }
+            }
+        } catch (\Exception $e) {
+            Log::error('Failed to sync user to master database', [
+                'action' => $action,
+                'email' => $userData['email'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            return false;
+        } finally {
+            // Clean up the temporary connection
+            try {
+                DB::purge('master_sync');
+            } catch (\Exception $e) {
+                // Ignore cleanup errors
+            }
+        }
+    }
+
+    /**
+     * Delete user from master database
+     */
+    private function deleteUserFromMasterDatabase($email)
+    {
+        try {
+            Log::info('Deleting user from master database', [
+                'email' => $email
+            ]);
+
+            $masterConfig = [
+                'driver' => 'mysql',
+                'host' => env('DB_HOST', '127.0.0.1'),
+                'port' => env('DB_PORT', '3306'),
+                'database' => env('DB_DATABASE'),
+                'username' => env('DB_USERNAME'),
+                'password' => env('DB_PASSWORD'),
+                'charset' => 'utf8mb4',
+                'collation' => 'utf8mb4_unicode_ci',
+                'prefix' => '',
+                'strict' => true,
+                'engine' => null,
+            ];
+
+            config(['database.connections.master_delete' => $masterConfig]);
+
+            $deleted = DB::connection('master_delete')
+                ->table('users')
+                ->where('email', $email)
+                ->delete();
+
+            if ($deleted) {
+                Log::info('Successfully deleted user from master database', [
+                    'email' => $email,
+                    'rows_affected' => $deleted
+                ]);
+                return true;
+            } else {
+                Log::warning('No user found in master database to delete', [
+                    'email' => $email
+                ]);
+                return false;
+            }
+        } catch (\Exception $e) {
+            Log::error('Failed to delete user from master database', [
+                'email' => $email,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            return false;
+        } finally {
+            try {
+                DB::purge('master_delete');
+            } catch (\Exception $e) {
+            }
+        }
+    }
+
+    /**
+     * Send welcome email to new user
+     */
+    private function sendWelcomeEmail($userData, $plainPassword)
+    {
+        try {
+            $currentUser = Auth::user();
+            $companyName = 'Leezard';
+
+            Log::info('Preparing to send welcome email', [
+                'recipient' => $userData['email'],
+                'company' => $companyName,
+                'mail_from' => config('mail.from.address'),
+                'mail_host' => config('mail.mailers.smtp.host'),
+                'mail_port' => config('mail.mailers.smtp.port')
+            ]);
+
+            $emailData = [
+                'name' => $userData['name'],
+                'cognome' => $userData['cognome'],
+                'email' => $userData['email'],
+                'password' => $plainPassword,
+                'level' => $userData['level'],
+                'company' => $companyName,
+                'login_url' => url('/'),
+                'created_by' => $currentUser->name
+            ];
+
+            try {
+                $viewContent = view('emails.welcome-user', $emailData)->render();
+                Log::info('Email template rendered successfully', ['template_length' => strlen($viewContent)]);
+            } catch (\Exception $viewException) {
+                Log::error('Email template rendering failed', ['error' => $viewException->getMessage()]);
+                throw new \Exception('Email template error: ' . $viewException->getMessage());
+            }
+
+            Mail::send('emails.welcome-user', $emailData, function ($message) use ($userData, $companyName) {
+                $message->to($userData['email'], $userData['name'] . ' ' . $userData['cognome'])
+                    ->subject('Benvenuto su Leezard - Account Creato')
+                    ->from(config('mail.from.address'), config('mail.from.name'));
+
+                if (env('MAIL_CCN')) {
+                    $message->bcc(env('MAIL_CCN'));
+                }
+            });
+
+            Log::info('Welcome email sent successfully', [
+                'recipient' => $userData['email'],
+                'company' => $companyName,
+                'subject' => 'Benvenuto in ' . $companyName . ' - Account Creato'
+            ]);
+
+            return true;
+        } catch (\Exception $e) {
+            Log::error('SMTP Transport error when sending welcome email', [
+                'recipient' => $userData['email'],
+                'error' => $e->getMessage(),
+                'mail_config' => [
+                    'host' => config('mail.mailers.smtp.host'),
+                    'port' => config('mail.mailers.smtp.port'),
+                    'encryption' => config('mail.mailers.smtp.encryption'),
+                    'username' => config('mail.mailers.smtp.username')
+                ]
+            ]);
+            return false;
+        } catch (\Exception $e) {
+            Log::error('General error when sending welcome email', [
+                'recipient' => $userData['email'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            return false;
+        }
+    }
+    public function sendSimpleWelcomeEmail($email, $userData)
+    {
+        try {
+            $subject = "Benvenuto su " . $userData['company'] . " – Account Creato";
+
+            $message = "Ciao " . $userData['name'] . " " . $userData['cognome'] . ",\n\n";
+            $message .= "Il tuo account è stato creato con successo su " . $userData['company'] . ".\n\n";
+            $message .= "Le tue credenziali di accesso:\n";
+            $message .= "Email: " . $userData['email'] . "\n";
+            $message .= "Password: " . $userData['password'] . "\n\n";
+            $message .= "Per motivi di sicurezza, dovrai cambiare la password al primo accesso.\n\n";
+            $message .= "Accedi ora: " . $userData['login_url'] . "\n\n";
+            $message .= "Grazie e benvenuto!\n";
+            $message .= "Il team di " . $userData['company'];
+
+            // Send simple text email
+            mail($email, $subject, $message, [
+                'From' => config('mail.from.address'),
+                'Reply-To' => config('mail.from.address'),
+                'Content-Type' => 'text/plain; charset=UTF-8'
+            ]);
+
+            return true;
+        } catch (\Exception $e) {
+            Log::error('Simple email sending failed', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+    public function resetFields()
+    {
+        $this->name = '';
+        $this->cognome = '';
+        $this->email = '';
+        $this->password = '';
+        $this->oldPassword = '';
+        $this->level = 0;
+        $this->enabled = true;
+        $this->emit('load-data-table');
+    }
+
+    public function render()
+    {
+        $this->logCurrentDatabase('Before fetching users in render()');
+
+        $this->records = \App\Models\User::select('id', 'name', 'cognome', 'email', 'password', 'level', 'enabled')->get();
+
+        $this->logCurrentDatabase('After fetching users in render()');
+
+        return view('livewire.user');
+    }
+
+    public function add()
+    {
+        if (!$this->canAddUser()) {
+            session()->flash('error', 'Non hai i permessi per aggiungere utenti.');
+            return;
+        }
+
+        $this->logCurrentDatabase('In add() method');
+
+        $this->resetFields();
+        $this->add = true;
+        $this->update = false;
+        $this->enabled = true;
+        $this->userExists = false;
+    }
+
+    public function store()
+    {
+        if (!$this->canAddUser()) {
+            session()->flash('error', 'Non hai i permessi per aggiungere utenti.');
+            return;
+        }
+
+        $this->logCurrentDatabase('Start of store() method');
+
+        Log::info('User store', [
+            'name' => $this->name,
+            'cognome' => $this->cognome,
+            'email' => $this->email,
+            'level' => $this->level,
+            'enabled' => $this->enabled
+        ]);
+
+        $rules = [
+            'name' => 'required',
+            'cognome' => 'required',
+            'email' => 'required|email|unique:users,email',
+            'password' => 'required|min:6'
+        ];
+
+        $messages = [
+            'name.required' => 'Il nome è obbligatorio',
+            'cognome.required' => 'Il cognome è obbligatorio',
+            'email.required' => 'La mail è obbligatoria',
+            'email.email' => 'La mail deve essere un indirizzo valido',
+            'email.unique' => 'Questa mail è già stata utilizzata',
+            'password.required' => 'La password è obbligatoria',
+            'password.min' => 'La password deve essere di almeno 6 caratteri'
+        ];
+
+        $this->validate($rules, $messages);
+
+        $this->logCurrentDatabase('Before creating user in store()');
+
+        try {
+            $plainPassword = $this->password;
+
+            $hashedPassword = bcrypt($this->password);
+
+            $user = \App\Models\User::create([
+                'name' => $this->name,
+                'cognome' => $this->cognome,
+                'email' => $this->email,
+                'password' => $hashedPassword,
+                'level' => $this->level,
+                'enabled' => $this->enabled
+            ]);
+
+            $this->logCurrentDatabase('After creating user in tenant database');
+
+            Log::info('User created successfully in tenant database', [
+                'user_id' => $user->id,
+                'name' => $this->name,
+                'cognome' => $this->cognome,
+                'email' => $this->email,
+                'level' => $this->level,
+                'enabled' => $this->enabled,
+                'database' => DB::connection()->getDatabaseName()
+            ]);
+
+            $masterSyncSuccess = $this->syncUserToMasterDatabase([
+                'name' => $this->name,
+                'cognome' => $this->cognome,
+                'email' => $this->email,
+                'password' => $hashedPassword,
+                'level' => $this->level,
+                'enabled' => $this->enabled
+            ], 'create');
+
+            if ($masterSyncSuccess) {
+                $emailSent = $this->sendWelcomeEmail([
+                    'name' => $this->name,
+                    'cognome' => $this->cognome,
+                    'email' => $this->email,
+                    'level' => $this->level
+                ], $plainPassword);
+
+                if (!$emailSent) {
+                    Log::info('HTML email failed, trying simple email', ['email' => $this->email]);
+                    $emailSent = $this->sendSimpleWelcomeEmail($this->email, [
+                        'name' => $this->name,
+                        'cognome' => $this->cognome,
+                        'email' => $this->email,
+                        'password' => $plainPassword,
+                        'level' => $this->level,
+                        'company' => 'Leezard',
+                        'login_url' => url('/')
+                    ]);
+                }
+
+                if ($emailSent) {
+                    session()->flash('success', 'Utente creato e email di benvenuto inviata con successo');
+                } else {
+                    session()->flash('success', 'Utente creato ma errore nell\'invio dell\'email. Controlla i log per dettagli.');
+                }
+            } else {
+                session()->flash('success', 'Utente creato nel database tenant ma errore nella sincronizzazione master');
+            }
+
+            $this->resetFields();
+            $this->add = false;
+        } catch (\Exception $ex) {
+            $this->logCurrentDatabase('Error in store() method');
+
+            Log::error('User creation failed', [
+                'error' => $ex->getMessage(),
+                'database' => DB::connection()->getDatabaseName(),
+                'user_data' => [
+                    'name' => $this->name,
+                    'cognome' => $this->cognome,
+                    'email' => $this->email,
+                    'level' => $this->level,
+                    'enabled' => $this->enabled
+                ]
+            ]);
+
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function edit($id)
+    {
+        if (!$this->canEditUser($id)) {
+            session()->flash('error', 'Non hai i permessi per modificare questo utente.');
+            return;
+        }
+
+        $this->logCurrentDatabase('Start of edit() method');
+
+        try {
+            $user = \App\Models\User::findOrFail($id);
+
+            $this->logCurrentDatabase('After finding user in edit()');
+
+            if (!$user) {
+                session()->flash('error', 'Utente non trovato');
+            } else {
+                $this->name = $user->name;
+                $this->cognome = $user->cognome;
+                $this->email = $user->email;
+                $this->level = $user->level;
+                $this->dataId = $user->id;
+                $this->update = true;
+                $this->add = false;
+                $this->enabled = $user->enabled;
+                $this->userExists = true;
+                // Store old email for master database update
+                $this->oldEmail = $user->email;
+            }
+
+            Log::info('User edit loaded', [
+                'user_id' => $id,
+                'name' => $this->name,
+                'cognome' => $this->cognome,
+                'email' => $this->email,
+                'level' => $this->level,
+                'database' => DB::connection()->getDatabaseName()
+            ]);
+        } catch (\Exception $ex) {
+            $this->logCurrentDatabase('Error in edit() method');
+
+            Log::error('User edit failed', [
+                'user_id' => $id,
+                'error' => $ex->getMessage(),
+                'database' => DB::connection()->getDatabaseName()
+            ]);
+
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+
+    public function update()
+    {
+        $this->logCurrentDatabase('Start of update() method');
+
+        $rules = [
+            'name' => 'required',
+            'cognome' => 'required',
+            'email' => 'required|email',
+            'password' => 'nullable|min:6'
+        ];
+
+        $this->validate($rules, $this->messages);
+
+        try {
+            $currentUser = \App\Models\User::findOrFail($this->dataId);
+            $oldEmail = $currentUser->email;
+            $oldName = $currentUser->name;
+
+            $updateData = [
+                'name' => $this->name,
+                'cognome' => $this->cognome,
+                'email' => $this->email,
+                'level' => $this->level,
+                'enabled' => $this->enabled
+            ];
+
+            $passwordChanged = !empty($this->password);
+            if ($passwordChanged) {
+                $hashedPassword = bcrypt($this->password);
+                $updateData['password'] = $hashedPassword;
+            }
+
+            \App\Models\User::whereId($this->dataId)->update($updateData);
+
+            $this->logCurrentDatabase('After updating user');
+
+            Log::info('User updated successfully in tenant database', [
+                'user_id' => $this->dataId,
+                'name' => $this->name,
+                'cognome' => $this->cognome,
+                'email' => $this->email,
+                'level' => $this->level,
+                'enabled' => $this->enabled,
+                'password_changed' => $passwordChanged,
+                'database' => DB::connection()->getDatabaseName()
+            ]);
+
+            $emailChanged = $oldEmail !== $this->email;
+            $nameChanged = $oldName !== $this->name;
+
+            if ($emailChanged || $nameChanged || $passwordChanged) {
+                $masterData = [
+                    'name' => $this->name,
+                    'email' => $this->email
+                ];
+
+                if ($passwordChanged) {
+                    $masterData['password'] = $hashedPassword;
+                }
+
+                $this->syncUserToMasterDatabase($masterData, 'update', $oldEmail);
+            }
+
+            session()->flash('success', 'Dato aggiornato');
+            $this->resetFields();
+            $this->update = false;
+        } catch (\Exception $ex) {
+            $this->logCurrentDatabase('Error in update() method');
+
+            Log::error('User update failed', [
+                'user_id' => $this->dataId,
+                'error' => $ex->getMessage(),
+                'database' => DB::connection()->getDatabaseName()
+            ]);
+
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function cancel()
+    {
+        $this->logCurrentDatabase('In cancel() method');
+
+        $this->resetFields();
+        $this->add = false;
+        $this->update = false;
+        $this->userExists = false;
+        $this->enabled = false;
+    }
+
+    public function delete($id)
+    {
+        Log::info('Delete method called', [
+            'user_id_to_delete' => $id,
+            'current_user_id' => Auth::id(),
+            'current_user_level' => Auth::user()->level
+        ]);
+
+        if (!$this->canDeleteUser($id)) {
+            Log::warning('Delete permission denied', [
+                'user_id_to_delete' => $id,
+                'current_user_id' => Auth::id(),
+                'current_user_level' => Auth::user()->level
+            ]);
+            session()->flash('error', 'Non hai i permessi per eliminare questo utente.');
+            return;
+        }
+
+        $this->logCurrentDatabase('Start of delete() method');
+
+        try {
+            $user = \App\Models\User::find($id);
+
+            if (!$user) {
+                Log::error('User not found for deletion', ['user_id' => $id]);
+                session()->flash('error', 'Utente non trovato.');
+                return;
+            }
+
+            $userEmail = $user->email;
+            $userName = $user->name;
+            $userCognome = $user->cognome;
+
+            Log::info('Found user for deletion', [
+                'user_id' => $id,
+                'user_email' => $userEmail,
+                'user_name' => $userName . ' ' . $userCognome
+            ]);
+
+            $deleted = $user->delete();
+
+            if (!$deleted) {
+                throw new \Exception('Failed to delete user from tenant database');
+            }
+
+            $this->logCurrentDatabase('After deleting user from tenant');
+
+            Log::info('User deleted successfully from tenant database', [
+                'user_id' => $id,
+                'user_email' => $userEmail,
+                'database' => DB::connection()->getDatabaseName()
+            ]);
+
+            $masterDeleted = $this->deleteUserFromMasterDatabase($userEmail);
+
+            if ($masterDeleted) {
+                Log::info('User deleted from both databases successfully', [
+                    'user_id' => $id,
+                    'user_email' => $userEmail
+                ]);
+                session()->flash('success', "Utente {$userName} {$userCognome} eliminato con successo");
+            } else {
+                Log::warning('User deleted from tenant but failed to delete from master', [
+                    'user_id' => $id,
+                    'user_email' => $userEmail
+                ]);
+                session()->flash('success', "Utente eliminato dal database tenant, ma errore nella sincronizzazione master");
+            }
+
+            $this->emit('userDeleted');
+            $this->emit('load-data-table');
+        } catch (\Exception $e) {
+            $this->logCurrentDatabase('Error in delete() method');
+
+            Log::error('User deletion failed', [
+                'user_id' => $id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+                'database' => DB::connection()->getDatabaseName()
+            ]);
+
+            session()->flash('error', 'Errore durante l\'eliminazione: ' . $e->getMessage());
+        }
+    }
+
+
+    private function canEditUser($userId)
+    {
+        $currentUser = Auth::user();
+
+        if ($currentUser->level == 0) {
+            $targetUser = \App\Models\User::find($userId);
+            return $targetUser && $targetUser->email != 'admin@admin.com';
+        }
+
+        return $userId == $currentUser->id;
+    }
+
+    /**
+     * Check if current user can edit email and password of a specific user
+     */
+    private function canEditEmailAndPassword($userId)
+    {
+        $currentUser = Auth::user();
+
+        return $userId == $currentUser->id;
+    }
+
+    /**
+     * Check if current user can delete a specific user
+     */
+    private function canDeleteUser($userId)
+    {
+        $currentUser = Auth::user();
+
+        if ($currentUser->level != 0) {
+            return false;
+        }
+
+        if ($userId == $currentUser->id) {
+            return false;
+        }
+
+        $targetUser = \App\Models\User::find($userId);
+        if ($targetUser && $targetUser->email == 'admin@admin.com') {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check if current user can add users
+     */
+    private function canAddUser()
+    {
+        $currentUser = Auth::user();
+        return $currentUser->level == 0;
+    }
+}

+ 134 - 0
app/Jobs/SendSmsMessage.php

@@ -0,0 +1,134 @@
+<?php
+
+namespace App\Jobs;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use RuntimeException;
+
+class SendSmsMessage implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    public function __construct(public int $messageId) {}
+
+    public function handle()
+    {
+        $msg = \App\Models\SmsMessage::with(['recipients'])->findOrFail($this->messageId);
+        $msg->update(['status' => 'processing']);
+
+        foreach ($msg->recipients as $r) {
+            if (in_array($r->status, ['sent', 'failed'])) continue;
+
+            try {
+                $phone = $r->phone;
+                $message = $msg->content;
+
+                $params = array(
+                    'to'            => '+39' . $phone,
+                    'from'          => env('SMS_FROM', 'Leezard'),
+                    'message'       => $message,
+                    'format'        => 'json',
+                );
+                $this->sms_send($params);
+
+                $r->update(['status' => 'sent', 'sent_at' => now(), 'error_message' => null]);
+            } catch (\Throwable $e) {
+                $r->update(['status' => 'failed', 'error_message' => $e->getMessage()]);
+            }
+        }
+        $total = $msg->recipients()->count();
+        $sent = $msg->recipients()->where('status', 'sent')->count();
+        $failed = $msg->recipients()->where('status', 'failed')->count();
+
+        $newStatus = 'draft';
+        if ($total === 0) {
+            $newStatus = 'draft';
+        } elseif ($sent === $total) {
+            $newStatus = 'sent';
+        } elseif ($sent > 0 && $failed > 0) {
+            $newStatus = 'partial';
+        } else {
+            $newStatus = 'failed';
+        }
+
+        $msg->update([
+            'status' => $newStatus,
+            'sent_at' => $sent > 0 ? now() : $msg->sent_at,
+        ]);
+    }
+
+    public function sms_send(array $params, bool $backup = false)
+    {
+        if (!isset($params['format'])) {
+            $params['format'] = 'json';
+        }
+
+        $url = $backup
+            ? 'https://api2.smsapi.com/sms.do'
+            : 'https://api.smsapi.com/sms.do';
+
+        $ch = curl_init($url);
+
+        curl_setopt_array($ch, [
+            CURLOPT_POST => true,
+            CURLOPT_POSTFIELDS => $params,
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_HTTPHEADER => [
+                'Authorization: Bearer ' . env('SMS_TOKEN'),
+            ],
+            CURLOPT_TIMEOUT => 15,
+            CURLOPT_CONNECTTIMEOUT => 5,
+        ]);
+
+        $content = curl_exec($ch);
+        $httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        $curlError = curl_error($ch);
+
+        curl_close($ch);
+
+        if ($content === false) {
+            if (!$backup) {
+                return $this->sms_send($params, true);
+            }
+
+            throw new RuntimeException('SMS API cURL error: ' . $curlError);
+        }
+
+        if ($httpStatus !== 200) {
+            if (!$backup && $httpStatus >= 500 && $httpStatus < 600) {
+                return $this->sms_send($params, true);
+            }
+
+            throw new RuntimeException("SMS API HTTP {$httpStatus}: {$content}");
+        }
+
+        $data = json_decode($content, true);
+
+        if (json_last_error() !== JSON_ERROR_NONE) {
+            throw new RuntimeException('SMS API invalid JSON response: ' . $content);
+        }
+
+        if (isset($data['error'])) {
+            $msg = is_array($data['error'])
+                ? ($data['error']['message'] ?? json_encode($data['error']))
+                : $data['error'];
+
+            throw new RuntimeException('SMS API error: ' . $msg);
+        }
+
+        if (isset($data['list'][0]['status'])) {
+            $status = strtoupper($data['list'][0]['status']);
+
+            if (in_array($status, ['ERROR', 'FAILED', 'REJECTED'], true)) {
+                $errMsg = $data['list'][0]['error_message'] ?? 'Unknown SMS API error';
+                throw new RuntimeException("SMS API message error ({$status}): {$errMsg}");
+            }
+        }
+
+        return $data;
+    }
+}

+ 1 - 1
app/Models/Azienda.php

@@ -81,7 +81,7 @@ class Azienda extends Model
     }
 
     /**
-     * Restituisce true se l’azienda è valida (tutti i campi richiesti presenti).
+     * Restituisce true se l'azienda è valida (tutti i campi richiesti presenti).
      */
     public function isValid()
     {

+ 15 - 0
app/Models/Course.php

@@ -141,4 +141,19 @@ class Course extends Model
     {
         return ucfirst($this->attributes['type'] ?? 'No Type');
     }
+
+    public function getDetailsName() {
+        $courseName = $this->name ?? 'Corso Sconosciuto';
+        $levelName = is_object($this->level) ? $this->level->name : '';
+        $frequencyName = is_object($this->frequency) ? $this->frequency->name : '';
+        $typeName = $this->getFormattedTypeField() ?? '';
+
+        // Build display name with level and frequency
+        $displayNameParts = [$courseName];
+        if ($levelName) $displayNameParts[] = $levelName;
+        // if ($typeName) $displayNameParts[] = $typeName;
+        if ($frequencyName) $displayNameParts[] = $frequencyName;
+
+        return implode(' - ', $displayNameParts);
+    }
 }

+ 7 - 0
app/Models/Member.php

@@ -56,6 +56,8 @@ class Member extends Model
         'to_complete',
         'is_archived',
         'archived_date',
+        'is_deleted',
+        'deleted_date',
     ];
     public function nation()
     {
@@ -102,6 +104,11 @@ class Member extends Model
         return $this->hasMany(MemberCourse::class);
     }
 
+    public function subscriptions()
+    {
+        return $this->hasMany(MemberSubscription::class);
+    }
+
     public function certificates()
     {
         return $this->hasMany(MemberCertificate::class)->orderBy('expire_date', 'DESC');

+ 52 - 0
app/Models/MemberSubscription.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class MemberSubscription extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'member_id',
+        'subscription_id',
+        'date_from',
+        'date_to',
+        'price',
+        'course_subscription_id',
+        'subscription_price',
+        'notes'
+    ];
+
+    public function member()
+    {
+        return $this->belongsTo(Member::class);
+    }
+
+    public function subscription()
+    {
+        return $this->belongsTo(Subscription::class);
+    }
+
+    public function course_subscription()
+    {
+        return $this->belongsTo(CourseSubscription::class);
+    }
+
+    public function getStatus()
+    {
+        $status = 0;
+        $rates = \App\Models\Rate::where('member_subscription_id', $this->id)->orderBy('date')->get();
+        foreach($rates as $rate)
+        {
+            if ($rate->date > date("Y-m-d"))
+            {
+                break;
+            }
+            $status = $rate->status;
+        }
+        return $status;
+    }
+}

+ 6 - 1
app/Models/Rate.php

@@ -19,7 +19,8 @@ class Rate extends Model
         'note',
         'status',
         'record_id',
-        'is_subscription'
+        'is_subscription',
+        'member_subscription_id',
     ];
 
     public function member()
@@ -36,6 +37,10 @@ class Rate extends Model
     {
         return $this->belongsTo(MemberCourse::class, 'member_course_id');
     }
+    public function member_subscription()
+    {
+        return $this->belongsTo(MemberSubscription::class, 'member_subscription_id');
+    }
 
     public function course_subscription()
     {

+ 12 - 0
app/Models/ReceiptRow.php

@@ -14,6 +14,7 @@ class ReceiptRow extends Model
     protected $fillable = [
         'receip_id',
         'causal_id',
+        'course_id',
         'when',
         'amount',
         'note',
@@ -27,6 +28,7 @@ class ReceiptRow extends Model
         'quantita',
         'prediscount_amount',
         'sconto',
+        'subscription_id',
     ];
 
     protected $casts = [
@@ -47,4 +49,14 @@ class ReceiptRow extends Model
     {
         return $this->belongsTo(Receipt::class, 'receip_id', 'id');
     }
+
+    public function course()
+    {
+        return $this->belongsTo(Course::class);
+    }
+
+    public function subscription()
+    {
+        return $this->belongsTo(Subscription::class, 'subscription_id');
+    }
 }

+ 11 - 0
app/Models/RecordRow.php

@@ -14,6 +14,7 @@ class RecordRow extends Model
     protected $fillable = [
         'record_id',
         'causal_id',
+        'course_id',
         'when',
         'amount',
         'vat_id',
@@ -27,6 +28,7 @@ class RecordRow extends Model
         'numero_linea',
         'sconto',
         'prediscount_amount',
+        'subscription_id',
     ];
 
     public function causal()
@@ -34,5 +36,14 @@ class RecordRow extends Model
         return $this->belongsTo(Causal::class);
     }
 
+    public function course()
+    {
+        return $this->belongsTo(Course::class);
+    }
+
+    public function subscription()
+    {
+        return $this->belongsTo(Subscription::class);
+    }
 }
 

+ 69 - 0
app/Models/SmsMessage.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
+
+class SmsMessage extends Model
+{
+    protected $fillable = [
+        'subject',
+        'content',
+        'status',
+        'schedule_at',
+        'sent_at',
+        'created_by'
+    ];
+
+    protected $casts = [
+        'schedule_at' => 'datetime',
+        'sent_at' => 'datetime',
+    ];
+
+    public function recipients()
+    {
+        return $this->hasMany(SmsMessageRecipient::class);
+    }
+    public function creator()
+    {
+        return $this->belongsTo(User::class, 'created_by');
+    }
+
+    public function duplicate(bool $withRecipients = false)
+    {
+        return DB::transaction(function () use ($withRecipients) {
+            $copy = $this->replicate(['status', 'schedule_at', 'sent_at']);
+            $copy->status = 'draft';
+            $copy->schedule_at = null;
+            $copy->sent_at = null;
+            $copy->save();
+
+            foreach ($this->attachments as $a) {
+                $copy->attachments()->create($a->only(['disk', 'path', 'name', 'size']));
+            }
+
+            if ($withRecipients) {
+                $payload = $this->recipients()
+                    ->get(['member_id', 'phone'])
+                    ->map(fn($r) => [
+                        'member_id' => $r->member_id,
+                        'phone' => $r->phone,
+                        'status' => 'pending',
+                    ])
+                    ->all();
+
+                if ($payload) {
+                    $copy->recipients()->createMany($payload);
+                }
+            }
+
+            return $copy;
+        });
+    }
+
+    public function isLocked(): bool
+    {
+        return in_array($this->status, ['processing', 'sent']);
+    }
+}

+ 28 - 0
app/Models/SmsMessageRecipient.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class SmsMessageRecipient extends Model
+{
+    protected $fillable = [
+        'sms_message_id',
+        'member_id',
+        'phone',
+        'status',
+        'error_message',
+        'sent_at'
+    ];
+
+    protected $casts = ['sent_at' => 'datetime'];
+
+    public function message()
+    {
+        return $this->belongsTo(SmsMessage::class);
+    }
+    public function member()
+    {
+        return $this->belongsTo(Member::class);
+    }
+}

+ 35 - 0
app/Models/Subscription.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class Subscription extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'name',
+        'causal_id',
+        'sub_causal_id',
+        'subscription_price',
+        'prices',
+        'enabled'
+    ];
+
+    public function getCount()
+    {
+        return \App\Models\MemberSubscription::where('subscription_id', $this->id)->count();
+    }
+
+    public function getCausal()
+    {
+        return $this->belongsTo(CourseSubscription::class);
+    }
+
+    public function getSubscriptionCausal()
+    {
+        return $this->belongsTo(CourseSubscription::class, 'sub_causal_id');
+    }
+}

Разница между файлами не показана из-за своего большого размера
+ 181 - 177
composer.lock


+ 34 - 0
database/migrations/2025_11_12_164023_add_is_deleted_to_members.php

@@ -0,0 +1,34 @@
+<?php
+
+use App\Database\Migrations\TenantMigration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends TenantMigration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('members', function (Blueprint $table) {
+            $table->boolean('is_deleted')->nullable();
+            $table->timestamp('deleted_date')->nullable()->after('is_deleted');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('members', function (Blueprint $table) {
+            $table->dropColumn('is_deleted');
+            $table->dropColumn('deleted_date');
+        });
+    }
+};

+ 33 - 0
database/migrations/2025_11_14_145909_add_course_id_to_records_rows.php

@@ -0,0 +1,33 @@
+<?php
+
+use App\Database\Migrations\TenantMigration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends TenantMigration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('records_rows', function (Blueprint $table) {
+            $table->unsignedBigInteger('course_id')->nullable()->after('causal_id');
+            $table->foreign('course_id')->nullable()->references('id')->on('courses')->onUpdate('cascade')->onDelete('cascade');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('records_rows', function (Blueprint $table) {
+            $table->dropColumn('course_id');
+        });
+    }
+};

+ 33 - 0
database/migrations/2025_11_17_114817_add_course_id_to_receipts_rows.php

@@ -0,0 +1,33 @@
+<?php
+
+use App\Database\Migrations\TenantMigration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends TenantMigration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('receipts_rows', function (Blueprint $table) {
+            $table->unsignedBigInteger('course_id')->nullable()->after('causal_id');
+            $table->foreign('course_id')->nullable()->references('id')->on('courses')->onUpdate('cascade')->onDelete('cascade');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('receipts_rows', function (Blueprint $table) {
+            $table->dropColumn('course_id');
+        });
+    }
+};

+ 41 - 0
database/migrations/2025_11_21_144756_create_sms_messages.php

@@ -0,0 +1,41 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use App\Database\Migrations\TenantMigration;
+
+return new class extends TenantMigration
+{
+    public function up()
+    {
+        Schema::create('sms_messages', function (Blueprint $t) {
+            $t->id();
+            $t->string('subject');
+            $t->longText('content');
+            $t->enum('status', ['draft', 'scheduled', 'processing', 'sent', 'failed', 'canceled'])->default('draft')->index();
+            $t->dateTime('schedule_at')->nullable()->index();
+            $t->dateTime('sent_at')->nullable();
+            $t->unsignedBigInteger('created_by')->index();
+            $t->timestamps();
+        });
+
+        Schema::create('sms_message_recipients', function (Blueprint $t) {
+            $t->id();
+            $t->foreignId('sms_message_id')->constrained('sms_messages')->cascadeOnDelete();
+            $t->foreignId('member_id')->nullable()->constrained('members')->nullOnDelete();
+            $t->string('phone');
+            $t->enum('status', ['pending', 'sent', 'failed', 'bounced', 'skipped'])->default('pending')->index();
+            $t->text('error_message')->nullable();
+            $t->dateTime('sent_at')->nullable();
+            $t->timestamps();
+
+            $t->index(['sms_message_id', 'status']);
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists('sms_message_recipients');
+        Schema::dropIfExists('sms_messages');
+    }
+};

+ 87 - 0
database/migrations/2025_11_26_132239_create_subscriptions_tables.php

@@ -0,0 +1,87 @@
+<?php
+
+use App\Database\Migrations\TenantMigration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends TenantMigration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('subscriptions', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->unsignedBigInteger('causal_id')->nullable();
+            $table->unsignedBigInteger('sub_causal_id')->nullable();
+            $table->decimal('subscription_price', 8, 2);
+            $table->longText('prices')->nullable();
+            $table->boolean('enabled')->default(1);
+            $table->softDeletes();
+            $table->timestamps();
+
+            $table->foreign('causal_id')->nullable()->references('id')->on('causals')->onUpdate('cascade')->onDelete('cascade');
+            $table->foreign('sub_causal_id')->nullable()->references('id')->on('causals')->onUpdate('cascade')->onDelete('cascade');
+        });
+
+        Schema::table('records_rows', function (Blueprint $table) {
+            $table->unsignedBigInteger('subscription_id')->nullable();
+            $table->foreign('subscription_id')->nullable()->references('id')->on('subscriptions')->onUpdate('cascade')->onDelete('cascade');
+        });
+        Schema::table('receipts_rows', function (Blueprint $table) {
+            $table->unsignedBigInteger('subscription_id')->nullable();
+            $table->foreign('subscription_id')->nullable()->references('id')->on('subscriptions')->onUpdate('cascade')->onDelete('cascade');
+        });
+
+
+        Schema::create('member_subscriptions', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('member_id');
+            $table->unsignedBigInteger('subscription_id');
+            $table->date('date_from')->nullable();
+            $table->date('date_to')->nullable();
+            $table->decimal('price', 8, 2);
+            $table->unsignedBigInteger('course_subscription_id')->nullable();
+            $table->decimal('subscription_price', 8, 2);
+            $table->string('notes')->nullable();
+            $table->timestamps();
+
+            $table->foreign('member_id')->nullable()->references('id')->on('members')->onUpdate('cascade')->onDelete('cascade');
+            $table->foreign('subscription_id')->nullable()->references('id')->on('subscriptions')->onUpdate('cascade')->onDelete('cascade');
+            $table->foreign('course_subscription_id')->nullable()->references('id')->on('course_subscriptions')->onUpdate('cascade')->onDelete('cascade');
+        });
+
+        Schema::table('rates', function (Blueprint $table) {
+            $table->unsignedBigInteger('member_subscription_id')->nullable();
+            $table->foreign('member_subscription_id')->nullable()->references('id')->on('member_subscriptions')->onUpdate('cascade')->onDelete('cascade');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('rates', function (Blueprint $table) {
+            $table->dropColumn('member_subscription_id');
+        });
+
+        Schema::dropIfExists('member_subscriptions');
+
+
+        Schema::table('records_rows', function (Blueprint $table) {
+            $table->dropColumn('subscription_id');
+        });
+        Schema::table('receipts_rows', function (Blueprint $table) {
+            $table->dropColumn('subscription_id');
+        });
+
+        Schema::dropIfExists('subscriptions');
+    }
+};

+ 17 - 1
public/css/chart-reports.css

@@ -432,6 +432,7 @@
 
 .table-cell.net {
     font-weight: 600;
+    white-space: nowrap;
 }
 
 .table-row.positive .table-cell.net {
@@ -613,6 +614,11 @@
     font-weight: 600;
 }
 
+.course-table .table-cell.earned {
+    justify-content: flex-end;
+    font-weight: 600;
+}
+
 .course-table .table-cell.delta.positive {
     color: #10b981;
 }
@@ -629,9 +635,19 @@
     justify-content: center;
     font-weight: 600;
     font-size: 0.8rem;
+    color: #10b981;
+}
+
+.course-table .table-cell.earned.positive {
+    color: #10b981;
+}
+
+.course-table .table-cell.earned.neutral {
+    color: #6b7280;
 }
 
-/* .course-table .table-cell.percentage.good {
+/*
+.course-table .table-cell.percentage.good {
     color: #059669;
 }
 

+ 44 - 26
public/css/new_style.css

@@ -367,8 +367,8 @@ body label.form-label {
     color: black;
 }
 
-body .form-control,
-body form .form-control,
+body .form-control:not(textarea),
+body form .form-control:not(textarea),
 body .form-select,
 body form .form-select,
 body .select2-selection,
@@ -381,21 +381,25 @@ body .btn.dropdown-toggle {
     background-color: #f5f8fa !important;
 }
 
-body .form-check-input,
-body form .form-check-input {
+body textarea.form-control,
+body form textarea.form-control {
+    border-radius: 15px !important;
+    border: 1px solid #d3dce1 !important;
+    background-color: #f5f8fa !important;
+}
+
+body input.form-check-input,
+body form input.form-check-input {
     height: 20px !important;
     width: 20px !important;
     border-radius: 5px !important;
     padding: 0 !important;
 }
 
-body .form-check-input:checked[type="checkbox"],
-body
-    form
-    .form-check-input:checked[type="checkbox"]
-    body
-    .form-check-input:checked[type="radio"],
-body form .form-check-input:checked[type="radio"] {
+body input.form-check-input:checked[type="checkbox"],
+body form input.form-check-input:checked[type="checkbox"],
+body input.form-check-input:checked[type="radio"],
+body form input.form-check-input:checked[type="radio"] {
     background-color: var(--color-blu) !important;
 }
 
@@ -870,24 +874,9 @@ body .dashboard-container .participation-fill {
     height: 100%;
     transition: width 0.3s ease;
     border-radius: 10px;
-}
-
-body .dashboard-container .participation-fill.padel {
-    background: #ffd700;
-}
-
-body .dashboard-container .participation-fill.tennis {
     background: #8b4cf7;
 }
 
-body .dashboard-container .participation-fill.pallavolo {
-    background: #ff6b35;
-}
-
-body .dashboard-container .participation-fill.yoga {
-    background: var(--color-verde);
-}
-
 body .dashboard-container .chart-title {
     font-size: 16px;
     font-weight: 600;
@@ -1213,4 +1202,33 @@ a.member-file-url {
 
 .password-wrapper .password-eye.shown .password-shown {
     display: block;
+}
+
+.form-radio-wrapper {
+    display: inline-flex;
+    gap: 4px;
+    margin-right: 20px;
+    font-size: 1em;
+}
+
+.form-radio-wrapper:last-of-type {
+    margin-right: 0;
+}
+
+.table-actions button {
+    border-color: #d3dce1;
+    background-color: #f5f8fa !important;
+    width: 42px;
+    height: 40px;
+    display: inline-flex;
+    flex-direction: row;
+    flex-wrap: nowrap;
+    align-content: center;
+    justify-content: center;
+    align-items: center;
+}
+
+.table-actions button:hover {
+    color: white;
+    background-color: var(--color-blu) !important;
 }

+ 10 - 0
resources/views/first-login.blade.php

@@ -5,6 +5,16 @@
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>Primo Accesso - Leezard.cloud</title>
 
+  {{-- Favicon --}}
+    <link rel="icon" type="image/png" href="{{asset('favicon-96x96.png')}}" sizes="96x96" />
+    <link rel="icon" type="image/svg+xml" href="{{asset('favicon.svg')}}" />
+    <link rel="shortcut icon" href="{{asset('favicon.ico')}}"/>
+    <link rel="shortcut icon" href="{{asset('favicon-light.ico')}}" media="(prefers-color-scheme: light)"/>
+    <link rel="shortcut icon" href="{{asset('favicon-dark.ico')}}" media="(prefers-color-scheme: dark)"/>
+    <link rel="apple-touch-icon" sizes="180x180" href="{{asset('apple-touch-icon.png')}}" />
+    <link rel="manifest" href="{{asset('site.webmanifest')}}" />
+  {{-- end Favicon --}}
+
     <!-- Bootstrap CSS -->
     <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
     <!-- Font Awesome -->

+ 57 - 25
resources/views/layouts/app.blade.php

@@ -162,6 +162,8 @@
                 print "Dashboard";
             if (Request::is('members'))
                 print "Utenti";
+            if (Request::is('members_archive'))
+                print "Archivio utenti";
             if (Request::is('suppliers'))
                 print "Fornitori";
             if (Request::is('in'))
@@ -189,7 +191,7 @@
             if (Request::is('settings'))
                 print "Impostazioni";
             if (Request::is('categories'))
-                print "Gruppi di appartenenza";
+                print "Gruppi di interesse";
             if (Request::is('disciplines'))
                 print "Discipline";
             if (Request::is('cards'))
@@ -197,7 +199,7 @@
             if (Request::is('rates'))
                 print "Pagamenti";
             if (Request::is('course_subscriptions'))
-                print "Corsi - Abbonamenti";
+                print "Corsi - Frequenza di pagamenti";
             if (Request::is('courses'))
                 print "Corsi";
             if (Request::is('course_durations'))
@@ -206,10 +208,10 @@
                 print "Corsi - Frequenza";
             if (Request::is('course_levels'))
                 print "Corsi - Livelli";
-            if (Request::is('course_types'))
-                print "Corsi - Tipologie";
+            // if (Request::is('course_types'))
+            //     print "Corsi - Tipologie";
             if (Request::is('banks'))
-                print "Canali finanziari";
+                print "Banche";
             if (Request::is('causals'))
                 print "Causali";
             if (Request::is('vats'))
@@ -217,17 +219,21 @@
             if (Request::is('payment_methods'))
                 print "Metodi di pagamento";
             if (Request::is('users'))
-                print "Profilo utenti";
+                print "Utenze";
             if (Request::is('azienda'))
                 print "Società";
             if (Request::is('profile'))
                 print "Profilo Utente";
             if (Request::is('reports'))
                 print "Reports";
+            if (Request::is('subscriptions'))
+                print "Abbonamenti";
+            if (Request::is('subscription_member', 'subscription_member/*'))
+                print "Abbonamenti";
+            if (Request::is('mail_comunications'))
+                print "Email";
             if (Request::is('sms_comunications'))
-                print "Comunicazioni SMS";
-            if (Request::is('email_comunications'))
-                print "Comunicazioni Email";
+                print "Sms";
             @endphp
             </h3>
 
@@ -267,12 +273,12 @@
                 <div id="accordionExample" style="width:100%">
                     <div class="accordion-item">
                         <h2 class="accordion-header linkMenu" id="headingOne" style="margin-top:50px;">
-                            <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="{{Request::is('members') || Request::is('suppliers') ? 'true' : 'false'}}" aria-controls="collapseOne">
+                            <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="{{Request::is('members') || Request::is('suppliers') || Request::is('members_archive') ? 'true' : 'false'}}" aria-controls="collapseOne">
                                 <i class="fas fa-user"></i>
                                 <span>Anagrafiche</span>
                             </button>
                         </h2>
-                        <div id="collapseOne" class="accordion-collapse collapse {{Request::is('members') || Request::is('suppliers') ? 'show' : ''}}" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
+                        <div id="collapseOne" class="accordion-collapse collapse {{Request::is('members') || Request::is('suppliers') || Request::is('members_archive') ? 'show' : ''}}" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
                             <div class="accordion-body">
                                 <ul class="nav nav-pills flex-column align-items-center align-items-sm-start w-100" id="menu-anagrafica" style="margin-top:0px;">
                                     <li class="nav-item {{Request::is('members') ? "nav-item-active" : ""}}">
@@ -287,6 +293,11 @@
                                             </a>
                                         </li>
                                     @endif
+                                    <li class="nav-item {{Request::is('members_archive') ? "nav-item-active" : ""}}">
+                                        <a href="/members_archive" class="nav-link d-flex align-items-center linkMenu">
+                                            <span class="ms-3 d-md-inline">Archivio</span>
+                                        </a>
+                                    </li>
                                 </ul>
                             </div>
                         </div>
@@ -341,17 +352,17 @@
                     </div>
                     <div class="accordion-item">
                         <h2 class="accordion-header linkMenu" id="headingThree">
-                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="{{Request::is('course_member_one') || Request::is('course_member_two') || Request::is('course_list') || Request::is('course_member') ? 'true' : 'false'}}" aria-controls="collapseThree">
+                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="{{Request::is('course_member_one') || Request::is('course_member_two') || Request::is('subscription_member', 'subscription_member/*') || Request::is('course_list') || Request::is('course_member') ? 'true' : 'false'}}" aria-controls="collapseThree">
                                 <i class="fas fa-calendar"></i>
                                 <span>Corsi</span>
                             </button>
                         </h2>
                         @php
                         $iscritti_types = [];
-                        $iscritti_types[] = array('name' => 'Standard', 'type' => 'standard');
+                        $iscritti_types[] = array('name' => 'Singoli', 'type' => 'standard');
                         $iscritti_types[] = array('name' => 'Personalizzati', 'type' => 'custom');   
                         @endphp
-                        <div id="collapseThree" class="accordion-collapse collapse {{Request::is('course_member_one') || Request::is('course_member_two') || Request::is('course_list') || Request::is('course_member') || Request::is('rates') ? 'show' : ''}}" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
+                        <div id="collapseThree" class="accordion-collapse collapse {{Request::is('course_member_one') || Request::is('course_member_two') || Request::is('subscription_member', 'subscription_member/*') || Request::is('course_list') || Request::is('course_member') || Request::is('rates') ? 'show' : ''}}" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
                             <div class="accordion-body">
                                 <ul class="nav nav-pills flex-column align-items-center align-items-sm-start w-100" id="menu-contabilita" style="margin-top:0px;">
                                     {{-- <li class="nav-item {{Request::is('course_member_one') || Request::is('course_member_two') || Request::is('course_member') ? "nav-item-active" : ""}}">
@@ -377,6 +388,12 @@
                                     </li>
                                     @endforeach
 
+                                    <li class="nav-item {{Request::is('subscription_member', 'subscription_member/*') ? "nav-item-active" : ""}}">
+                                        <a href="/subscription_member" class="nav-link d-flex align-items-center linkMenu">
+                                            <span class="ms-3 d-md-inline">Abbonamenti</span>
+                                        </a>
+                                    </li>
+
                                     @if(false)
                                         <li class="nav-item {{Request::is('course_list') ? "nav-item-active" : ""}}">
                                             <a href="/course_list" class="nav-link d-flex align-items-center linkMenu">
@@ -399,7 +416,17 @@
                         </div>
                     @endif
                     @if(Auth::user()->level == env('LEVEL_ADMIN', 0))
-                        <div class="accordion-item {{Request::is('settings') || Request::is('categories') || Request::is('disciplines') || Request::is('cards') || Request::is('course_subscriptions') || Request::is('courses') || Request::is('course_durations') || Request::is('course_frequencies') || Request::is('course_levels') || Request::is('course_types') || Request::is('banks') || Request::is('causals') || Request::is('vats') || Request::is('payment_methods') || Request::is('users') ? "accordion-item-active" : ""}}">
+                        <div class="accordion-item {{Request::is('reports') ? "accordion-item-active" : ""}}">
+                            <h2 class="accordion-header linkMenu">
+                                <a class="accordion-button collapsed" href="/reports">
+                                    <i class="fas fa-chart-line"></i>
+                                    <span>Reports</span>
+                                </a>
+                            </h2>
+                        </div>
+                    @endif
+                    @if(Auth::user()->level == env('LEVEL_ADMIN', 0))
+                        <div class="accordion-item {{Request::is('settings') || Request::is('categories') || Request::is('disciplines') || Request::is('cards') || Request::is('course_subscriptions') || Request::is('courses') || Request::is('course_durations') || Request::is('course_frequencies') || Request::is('course_levels') {{-- || Request::is('course_types') --}} || Request::is('banks') || Request::is('causals') || Request::is('vats') || Request::is('payment_methods') || Request::is('users') ? "accordion-item-active" : ""}}">
                             <h2 class="accordion-header linkMenu">
                                 <a class="accordion-button collapsed" href="/settings">
                                     <i class="fas fa-gear"></i>
@@ -418,16 +445,6 @@
                             </div>
                         @endif
                     @endif
-                    @if(Auth::user()->level == env('LEVEL_ADMIN', 0))
-                        <div class="accordion-item {{Request::is('reports') ? "accordion-item-active" : ""}}">
-                            <h2 class="accordion-header linkMenu">
-                                <a class="accordion-button collapsed" href="/reports">
-                                    <i class="fas fa-chart-line"></i>
-                                    <span>Reports</span>
-                                </a>
-                            </h2>
-                        </div>
-                    @endif
                 </div>
                 </div>
             </div>
@@ -478,6 +495,21 @@
     @stack('scripts')
 
     <script>
+        function togglePassword(eye_icon) {
+            let wrapper = eye_icon.parentElement;
+            let password_html = wrapper.querySelector("input");
+
+            if (password_html) {
+                if (password_html.type == "password") {
+                    password_html.type = "text";
+                    eye_icon.classList.add("shown");
+                } else {
+                    password_html.type = "password";
+                    eye_icon.classList.remove("shown");
+                }
+            }
+        }
+
         function setToday(d)
         {
             $("#dateFrom").val(d);

+ 45 - 53
resources/views/livewire/category.blade.php

@@ -2,28 +2,24 @@
 
     @if(!$add && !$update)
 
-    <a class="btn--ui lightGrey" href="/settings?type=anagrafica"><i class="fa-solid fa-arrow-left"></i></a><br>
+        <a class="btn--ui lightGrey" href="/settings?type=anagrafica"><i class="fa-solid fa-arrow-left"></i></a>
 
-    <header id="title--section"  class="d-flex align-items-center justify-content-between">
-        <div class="title--section_name d-flex align-items-center justify-content-between" style="display:none !important" >
-            <i class="ico--ui title_section utenti me-2"></i>
-            <h2 class="primary">@if(!$add && !$update)Categorie @else Inserimento/modifica categoria @endif</h2>
-        </div>
+        <header id="title--section"  class="d-flex align-items-center justify-content-between" style="display:none !important">
+            <div class="title--section_name d-flex align-items-center justify-content-between">
+                <i class="ico--ui title_section utenti me-2"></i>
+                <h2 class="primary">@if(!$add && !$update)Categorie @else Inserimento/modifica categoria @endif</h2>
+            </div>
+        </header>
 
         @if(!$add && !$update)
-            <div class="title--section_addButton text-right "  wire:click="add()" style="cursor: pointer;margin-top:20px;">
-                <div class="btn--ui entrata d-flex justify-items-between">
+            <div class="title--section_addButton d-flex justify-content-end" >
+                {{-- <button type="button" class="btn--ui entrata" wire:click="openImportModal" onclick="openImportModal()">Importa</button> --}}
+                <div class="btn--ui entrata d-flex justify-items-between" wire:click="add()" style="cursor: pointer;">
                     <a href="#" wire:click="add()" style="color:white">AGGIUNGI</a>
                 </div>
-            </div>
-
+            </div><br>
         @endif
 
-    </header>
-
-
-
-
         <section id="resume-table">
             <div class="compare--chart_wrapper d-none"></div>
 
@@ -45,7 +41,7 @@
                             </td>
                         </tr>
                         @if(count($record->childs))
-                            @include('livewire/category_child',['records' => $record->childs, 'indentation' => 1])
+                            @include('livewire/category_child',['records' => $record->childs->sortBy('name'), 'indentation' => 1])
                         @endif
                     @endforeach
 
@@ -55,50 +51,46 @@
         </section>
 
     @else
+        <a class="btn--ui lightGrey" href="/categories"><i class="fa-solid fa-arrow-left"></i></a><br><br>
 
-        <div class="container">
-
-            <a class="btn--ui lightGrey" href="/categories"><i class="fa-solid fa-arrow-left"></i></a><br><br>
-
-            @if (session()->has('error'))
-                <div class="alert alert-danger" role="alert">
-                    {{ session()->get('error') }}
-                </div>
-            @endif
+        @if (session()->has('error'))
+            <div class="alert alert-danger" role="alert">
+                {{ session()->get('error') }}
+            </div>
+        @endif
 
-            <div class="row">
-                <div class="col">
+        <div class="row">
+            <div class="col">
 
-                    <form action="">
+                <form action="">
 
-                        @if($parent != '')
-                            <h3>Aggiungi livello a {{$parent}}</h3><br>
-                        @endif
+                    @if($parent != '')
+                        <h3>Aggiungi livello a {{$parent}}</h3><br>
+                    @endif
 
-                        <div class="row mb-3">
-                            <div class="col">
-                                <div class="form--item">
-                                    <label for="inputName" class="form-label">Nome</label>
-                                    <input class="form-control js-keyupTitle @error('name') is-invalid @enderror" type="text" id="name" placeholder="Nome" wire:model="name">
-                                    @error('name')
-                                        <div class="invalid-feedback">{{ $message }}</div>
-                                    @enderror
-                                </div>
+                    <div class="row mb-3">
+                        <div class="col">
+                            <div class="form--item">
+                                <label for="inputName" class="form-label">Nome</label>
+                                <input class="form-control js-keyupTitle @error('name') is-invalid @enderror" type="text" id="name" placeholder="Nome" wire:model="name">
+                                @error('name')
+                                    <div class="invalid-feedback">{{ $message }}</div>
+                                @enderror
                             </div>
                         </div>
-
-                        <div class="form--item">
-                            <button type="button" class="btn--ui lightGrey" wire:click="cancel()">Annulla</button>
-                        @if($add)
-                            <button type="submit" class="btn--ui" wire:click.prevent="store()">Salva</button>
-                        @endif
-                        @if($update)
-                            <button type="submit" class="btn--ui" wire:click.prevent="update()">Salva</button>
-                        @endif
-                        </div>
-
-                    </form>
-                </div>
+                    </div>
+
+                    <div class="form--item">
+                        <button type="button" class="btn--ui lightGrey" wire:click="cancel()">Annulla</button>
+                    @if($add)
+                        <button type="submit" class="btn--ui" wire:click.prevent="store()">Salva</button>
+                    @endif
+                    @if($update)
+                        <button type="submit" class="btn--ui" wire:click.prevent="update()">Salva</button>
+                    @endif
+                    </div>
+
+                </form>
             </div>
         </div>
 

+ 12 - 10
resources/views/livewire/course.blade.php

@@ -76,7 +76,7 @@
                         <tr>
                             <td style="text-align:center"><input type="checkbox" class="chkCourse" name="{{$record->id}}"></td>
                             <td>{{$record->year}}</td>
-                            <td>{{$record->type == 'standard' ? 'Standard' : 'Personalizzato'}}</td>
+                            <td>{{$record->type == 'standard' ? 'Singolo' : 'Personalizzato'}}</td>
                             <td>{{$record->name}}</td>
                             <td>{{$record->level->name ?? ""}}</td>
                             <td>{{$record->frequency->name ?? ""}}</td>
@@ -113,7 +113,7 @@
 
                         <div class="row mb-3 ">
                             <div class="col-12 mt-2">
-                                <input type="radio" name="chkType" value="standard" wire:model="type">&nbsp;Standard
+                                <input type="radio" name="chkType" value="standard" wire:model="type">&nbsp;Singolo
                                 &nbsp;&nbsp;&nbsp;
                                 <input type="radio" name="chkType" value="custom" wire:model="type">&nbsp;Personalizzato
                             </div>
@@ -127,7 +127,7 @@
                                 </div>
                             </div>
                             @if(false)
-                                <div class="col-6 mt-2">
+                                {{-- <div class="col-6 mt-2">
                                     <div class="form--item">
                                         <label for="inputName" class="form-label">Tipologia</label>
                                         <select class="form-select form-select-lg me-1 @error('course_type_id') is-invalid @enderror" wire:model="course_type_id">
@@ -137,7 +137,7 @@
                                             @endforeach
                                         </select>
                                     </div>
-                                </div>
+                                </div> --}}
                                 <div class="col-6 mt-2">
                                     <div class="form--item">
                                         <label for="inputName" class="form-label">Durata</label>
@@ -175,13 +175,13 @@
                             <div class="col-3 mt-2">
                                 <div class="form--item">
                                     <label for="inputName" class="form-label">Data inizio</label>
-                                    <input class="form-control @error('name') is-invalid @enderror" type="date" placeholder="Data inizio" wire:model="date_from">
+                                    <input class="form-control @error('date_from') is-invalid @enderror" type="date" placeholder="Data inizio" wire:model="date_from">
                                 </div>
                             </div>
                             <div class="col-3 mt-2">
                                 <div class="form--item">
                                     <label for="inputName" class="form-label">Data fine</label>
-                                    <input class="form-control @error('name') is-invalid @enderror" type="date" placeholder="Data fine" wire:model="date_to">
+                                    <input class="form-control @error('date_to') is-invalid @enderror" type="date" placeholder="Data fine" wire:model="date_to">
                                 </div>
                             </div>
                             @if(false)
@@ -258,7 +258,7 @@
                                     <select class="form-select form-select-lg" wire:model="instructor_id">
                                         <option value="">
                                         @foreach($instructors as $ins)
-                                            <option value="{{$ins["id"]}}">{{$ins["name"]}}
+                                            <option value="{{$ins["id"]}}">{{$ins["name"]}} {{$ins["cognome"]}}</option>
                                         @endforeach
                                     </select>
                                 </div>
@@ -266,7 +266,7 @@
 
                             <div class="col-6 mt-2">
                                 <div class="form--item">
-                                    <label for="inputName" class="form-label">Gruppo di appartenenza</label>
+                                    <label for="inputName" class="form-label">Gruppo di interesse</label>
                                     <select class="form-select form-select-lg me-1 @error('category_id') is-invalid @enderror" wire:model="category_id">
                                         <option value="">
                                         @foreach($categories as $category)
@@ -302,7 +302,7 @@
                                 @foreach($prices as $idP => $p)
                                     <div class="row mt-2 ">
                                         <div class="col-6">
-                                            <label for="abb" class="form-label">Abbonamento</label>
+                                            <label for="abb" class="form-label">Pagamento</label>
                                             <select class="form-control" wire:model="prices.{{$idP}}.course_subscription_id">
                                                 <option value=""></option>
                                                 @foreach($course_subscriptions as $s)
@@ -332,7 +332,9 @@
                             @if($type == 'standard')
 
                                 @foreach($when as $idW => $w)
-
+                                    @if ($idW > 0)
+                                    <div class="col-6"></div>
+                                    @endif
                                     <div class="col-6 mt-4">
                                         <div class="day durata--corso d-flex">
                                             <label for="" class="form-label">Giorno</label>

+ 2 - 2
resources/views/livewire/course_list.blade.php

@@ -118,7 +118,7 @@
                     </div>
                 </div>
             </div>
-            <div class="col-md-2">
+            {{-- <div class="col-md-2">
                 <div class="row">
                     <div class="col-md-12" style="margin-bottom:10px;">
                         <b>Tipologia</b>
@@ -131,7 +131,7 @@
                         </select>
                     </div>
                 </div>
-            </div>
+            </div> --}}
             <div class="col-md-2">
                 <div class="row">
                     <div class="col-md-12" style="margin-bottom:10px;">

+ 2 - 2
resources/views/livewire/course_list_original.blade.php

@@ -79,7 +79,7 @@
                     </div>
                 </div>
             </div>
-            <div class="col-md-2">
+            {{-- <div class="col-md-2">
                 <div class="row">
                     <div class="col-md-12" style="margin-bottom:10px;">
                         <b>Tipologia</b>
@@ -92,7 +92,7 @@
                         </select>
                     </div>
                 </div>
-            </div>
+            </div> --}}
             <div class="col-md-2">
                 <div class="row">
                     <div class="col-md-12" style="margin-bottom:10px;">

+ 23 - 5
resources/views/livewire/course_member.blade.php

@@ -12,6 +12,23 @@
 
     <a class="btn--ui lightGrey" href="/course_member_two?type={{$type}}"><i class="fa-solid fa-arrow-left"></i></a><br>
 
+    @if ($course)
+    @php
+        $courseName = $course->name ?? 'Corso Sconosciuto';
+        $levelName = is_object($course->level) ? $course->level->name : '';
+        $frequencyName = is_object($course->frequency) ? $course->frequency->name : '';
+
+        $courseInfoParts = [];
+        if ($levelName) $courseInfoParts[] = $levelName;
+        if ($frequencyName) $courseInfoParts[] = $frequencyName;
+
+        $courseInfo = implode(' - ', $courseInfoParts);
+    @endphp
+        <h3>{{$courseName}}</h3>
+        <p>{{$courseInfo}}</p>
+    @endif
+
+
     <div class="showFilter" style="display:none" wire:ignore.self>
         <hr size="1">
         <div class="row g-3">
@@ -57,7 +74,7 @@
                     </div>
                 </div>
             </div>
-            <div class="col-md-2">
+            {{-- <div class="col-md-2">
                 <div class="row">
                     <div class="col-md-12" style="margin-bottom:10px;">
                         <b>Tipologia</b>
@@ -70,7 +87,7 @@
                         </select>
                     </div>
                 </div>
-            </div>
+            </div> --}}
             <div class="col-md-2">
                 <div class="row">
                     <div class="col-md-12" style="margin-bottom:10px;">
@@ -272,7 +289,7 @@
             <thead>
                 <tr>
                     <th scope="col">#</th>
-                    <th scope="col">Corso</th>
+                    {{-- <th scope="col">Corso</th> --}}
                     <th scope="col">Cognome</th>
                     <th scope="col">Nome</th>
                     <th scope="col">Certificato</th>
@@ -653,7 +670,7 @@
                 },
                 columns: [
                     { data: 'column_0' },
-                    { data: 'column_8' },
+                    // { data: 'column_8' },
                     {
                         data: 'column_1',
                         render: function (data, type, row) {
@@ -693,7 +710,8 @@
                                 html += '<span style="float:right"></span>';
                             }else {
                                 console.log(status);
-                                html += 'Non Presente';
+                                html += '<i class="ico--ui check absent me-2"></i>';
+                                html += 'Non consegnato';
                             }
                             html += '</span>';
                             return html;

+ 6 - 4
resources/views/livewire/course_member_two.blade.php

@@ -9,7 +9,7 @@
 
     </header>
 
-    <a class="btn--ui lightGrey" href="/course_member_one"><i class="fa-solid fa-arrow-left"></i></a><br>
+    {{-- <a class="btn--ui lightGrey" href="/course_member_one"><i class="fa-solid fa-arrow-left"></i></a><br> --}}
 
     <section id="resume-table">
         <div class="compare--chart_wrapper d-none"></div>
@@ -20,11 +20,12 @@
                     <th scope="col">Nome</th>
                     <th scope="col">Frequenza</th>
                     <th scope="col">Livello</th>
-                    <th scope="col">Durata</th>
+                    {{-- <th scope="col">Durata</th> --}}
                     @if($_GET["type"] != "custom")
                         <th scope="col">Giorni</th>
                     @endif
                     <th scope="col">Istruttore</th>
+                    <th scope="col">N° partecipanti</th>
                     <th scope="col">...</th>
                 </tr>
             </thead>
@@ -34,7 +35,7 @@
                         <td>{{$record->name}}</td>
                         <td>{{$record->frequency->name ?? ""}}</td>
                         <td>{{$record->level->name ?? ""}}</td>
-                        <td>{{date("d/m/Y", strtotime($record->date_from))}} - {{date("d/m/Y", strtotime($record->date_to))}}                                                                </td>
+                        {{-- <td>{{date("d/m/Y", strtotime($record->date_from))}} - {{date("d/m/Y", strtotime($record->date_to))}}                                                         </td> --}}
                         @if($_GET["type"] != "custom")
                         <td>
                             @if($record->when != null)
@@ -50,7 +51,8 @@
                             @endif
                         </td>
                         @endif
-                        <td>{{$record->getins->name ?? ""}}</td>
+                        <td>{{$record->getins->name ?? ""}} {{$record->getins->cognome ?? ""}}</td>
+                        <td>{{$record->getCount()}}</td>
                         <td>
                             <a href="/course_member?id={{$record->id}}" type="button" class="btn" ><b> <i class="fa-solid fa-chevron-right"></i></b></button>
                         </td>

+ 2 - 2
resources/views/livewire/course_subscription.blade.php

@@ -49,10 +49,10 @@
 
     @else
 
+        <a class="btn--ui lightGrey" href="/course_subscriptions"><i class="fa-solid fa-arrow-left"></i></a><br><br>
+        
         <div class="container">
 
-            <a class="btn--ui lightGrey" href="/course_subscriptions"><i class="fa-solid fa-arrow-left"></i></a><br><br>
-
             @if (session()->has('error'))
                 <div class="alert alert-danger" role="alert">
                     {{ session()->get('error') }}

+ 2 - 14
resources/views/livewire/dashboard.blade.php

@@ -80,19 +80,6 @@
                         <div class="course-divider"></div>
                         <div class="course-name">{{$course['full_name'] ?? $course['course_name']}}</div>
                         <div class="course-days">{{$course['days']}}</div>
-                        {{-- @if(!empty($course['level_name']) || !empty($course['frequency_name']))
-                        <div style="font-size: 11px; color: #888; margin-top: 4px;">
-                            @if(!empty($course['level_name']))
-                            <span>{{$course['level_name']}}</span>
-                            @endif
-                            @if(!empty($course['level_name']) && !empty($course['frequency_name']))
-                            <span> • </span>
-                            @endif
-                            @if(!empty($course['frequency_name']))
-                            <span>{{$course['frequency_name']}}</span>
-                            @endif
-                        </div>
-                        @endif --}}
                     </div>
                     @endforeach
                 </div>
@@ -244,7 +231,8 @@
 
             <div class="dashboard-card dashboard-stat card-inverted card-topay">
                 <div class="dashboard-card-header">
-                    <div class="dashboard-card-title">Da pagare<br/><small><small><i>{{$current_month}}</i></small></small></div>
+                    {{-- <div class="dashboard-card-title">Da pagare<br/><small><small><i>{{$current_month}}</i></small></small></div> --}}
+                    <div class="dashboard-card-title">Da pagare<br/><small><small><i>&nbsp;</i></small></small></div>
                     <i class="dashboard-card-icon fa-solid fa-money-bill-transfer"></i>
                 </div>
                 <div class="dashboard-card-value">{{number_format($toPay, 2, ",", ".")}}</div>

+ 57 - 43
resources/views/livewire/email_comunications.blade.php

@@ -68,15 +68,15 @@
                             <td><span class="badge bg-{{$badgeMap[$state]}}">{{ $record->status }}</span></td>
                             <td>
                                 @if(!empty($record->schedule_at))
-                                    {{ optional($record->schedule_at)->setTimezone('Europe/Rome')->format('d/m/Y H:i') }}
+                                    {{ optional($record->schedule_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}
                                 @endif
                             </td>
                             <td>
                                 @if(!empty($record->sent_at))
-                                    {{ optional($record->sent_at)->setTimezone('Europe/Rome')->format('d/m/Y H:i') }}
+                                    {{ optional($record->sent_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}
                                 @endif
                             </td>
-                            <td>{{ optional($record->created_at)->setTimezone('Europe/Rome')->format('d/m/Y H:i') }}</td>
+                            <td>{{ optional($record->created_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}</td>
                             <td class="d-flex gap-2">
                                 <button type="button" class="btn" wire:click="edit({{ $record->id }})" data-bs-toggle="tooltip" data-bs-trigger="hover focus" data-bs-placement="bottom" title="Modifica">
                                     <i class="fa-regular fa-pen-to-square"></i>
@@ -196,7 +196,7 @@
                                             </div>
                                             <div class="col-md-3">
                                                 <div class="row">
-                                                    <div class="col-md-12 mb-2"><b>Gruppo di appartenenza</b></div>
+                                                    <div class="col-md-12 mb-2"><b>Gruppo di interesse</b></div>
                                                     <div class="col-12">
                                                         <select name="filterCategories" class="form-select filterCategories" multiple="multiple">
                                                             <option value="">Tutte</option>
@@ -444,6 +444,8 @@
             </div>
         </div>
     </section>
+
+    <input type="hidden" name="timezone" id="timezone" wire:model="timezone">
 </div>
 
 @if (session()->has('success'))
@@ -491,13 +493,18 @@
                 dt.row(rowEl).remove().draw(false);
             }
         });
-    });
-
 
+        const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
+        @this.set('timezone', tz);
+    });
 
     window.addEventListener('init-recipients-table', (e) => {
         const selected = e.detail?.selected || [];
         loadDataTable(selected);
+
+        
+        const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
+        @this.set('timezone', tz);
     });
 
     function loadArchiveDataTable(){
@@ -690,7 +697,7 @@
                 { data: "categories" },
             ],
             order: [
-                [1, 'desc']
+                [1, 'asc']
             ],
             fixedHeader: false,
             thead: {
@@ -738,45 +745,52 @@
 import { ClassicEditor } from "ckeditor5";
 import { editorConfig } from "/assets/libraries/ckeditor5/config.js";
 
-window.addEventListener("load-editor", (e) => {
-    let detail = e.detail || {};
-    let latestLocked = !!detail.locked;
-    let html = detail.html ?? '';
-
-    let el = document.querySelector('#message');
-    if (el.ckeditorInstance) el.ckeditorInstance.destroy();
-    
-    editorConfig.simpleUpload = {
-        uploadUrl: "{{ route('ckeditor.upload', ['_token' => csrf_token()]) }}"
-    };
+    window.addEventListener("load-editor", (e) => {
+        let detail = e.detail || {};
+        let latestLocked = !!detail.locked;
+        let html = detail.html ?? '';
+
+        let el = document.querySelector('#message');
+        if (el.ckeditorInstance) el.ckeditorInstance.destroy();
+        
+        editorConfig.simpleUpload = {
+            uploadUrl: "{{ route('ckeditor.upload', ['_token' => csrf_token()]) }}"
+        };
+
+        ClassicEditor
+            .create(el, editorConfig)
+            .then(editor => {
+                el.ckeditorInstance = editor;
+
+                editor.setData(html);
+
+                if (latestLocked) {
+                    editor.enableReadOnlyMode('locked');
+                    return;
+                }
+            })
+            .catch(console.error)
+    });
 
-    ClassicEditor
-        .create(el, editorConfig)
-        .then(editor => {
-            el.ckeditorInstance = editor;
+    window.submitEmail = function(action){
+        const ed = document.querySelector('#message')?.ckeditorInstance;
+        const html = ed ? ed.getData() : '';
 
-            editor.setData(html);
+        if (action === 'draft') {
+            @this.call('saveDraft', html);
+        } else if (action === 'send') {
+            @this.call('sendNow', html);
+        } else {
+            @this.call('scheduleMessage', html);
+        }
+    };
 
-            if (latestLocked) {
-                editor.enableReadOnlyMode('locked');
-                return;
-            }
-        })
-        .catch(console.error)
-});
-
-window.submitEmail = function(action){
-    const ed = document.querySelector('#message')?.ckeditorInstance;
-    const html = ed ? ed.getData() : '';
-
-    if (action === 'draft') {
-        @this.call('saveDraft', html);
-    } else if (action === 'send') {
-        @this.call('sendNow', html);
-    } else {
-        @this.call('scheduleMessage', html);
-    }
-};
+    window.addEventListener("scroll-top", (e) => {
+        let wrapper = document.querySelector('#card--dashboard');
+        if (wrapper) {
+            wrapper.scrollTo({top: 0, behavior: 'smooth'});
+        }
+    });
 </script>
 
 {{-- END CKEditor --}}

Разница между файлами не показана из-за своего большого размера
+ 523 - 291
resources/views/livewire/member.blade.php


+ 613 - 0
resources/views/livewire/member_archive.blade.php

@@ -0,0 +1,613 @@
+<div>
+    <div class="col card--ui" id="card--dashboard">
+        <header id="title--section" style="display:none !important"  class="d-flex align-items-center justify-content-between" style="display:none !important">
+            <div class="title--section_name d-flex align-items-center justify-content-between">
+                <i class="ico--ui title_section utenti me-2 "></i>
+                <h2 class="primary">Archivio utenti</h2>
+            </div>
+        </header>
+
+        <section id="subheader" class="d-flex align-items-center justify-content-between">
+            @if(false)
+            <form action="" class="search--form d-flex align-items-center">
+                <div class="input-group mb-3">
+                    <input type="text" class="form-control" placeholder="Cerca utente" aria-label="cerca utent" aria-describedby="button-addon2" wire:model="search">
+                    @if($showReset)
+                        <button class="btn--ui" type="button" id="button-addon2" onclick="reset()"><i class="ico--ui search"></i>Reset</button>
+                    @else
+                        <button class="btn--ui" type="button" id="button-addon2" wire:click="search()"><i class="ico--ui search"></i>Cerca</button>
+                    @endif
+                </div>
+            </form>
+            @endif
+        </section>
+
+        <div class="showFilter" style="display:none">
+            <hr size="1">
+            <div class="row g-3">
+                <div class="col-md-3">
+                    <div class="row">
+                        <div class="col-md-12" style="margin-bottom:10px;">
+                            <b>Età</b>
+                        </div>
+                        <div class="col-12">
+                            <div class="row mb-2">
+                                <div class="col-3"><label class="form-check-label ms-2" >Da</label></div>
+                                <div class="col-9"><input class="form-control " type="number" name="txtFromYear"></div>
+                            </div>
+                        </div>
+                        <div class="col-12">
+                            <div class="row">
+                                <div class="col-3"><label class="form-check-label ms-2" >A</label></div>
+                                <div class="col-9"><input class="form-control " type="number"  name="txtToYear"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="row">
+                        <div class="col-md-12" style="margin-bottom:10px;">
+                            <b>Tipologia di tesseramento</b>
+                        </div>
+                        <div class="col-12">
+                            <select name="filterCards" class="form-select filterCards">
+                                <option value="">Tutte
+                                @foreach(getCards() as $card)
+                                    <option value="{{$card->id}}">{{$card->name}}
+                                @endforeach
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="row">
+                        <div class="col-md-12" style="margin-bottom:10px;">
+                            <b>Stato tesseramento</b>
+                        </div>
+                        <div class="col-12">
+                            <select name="filterStatus" class="form-select filterStatus" multiple="multiple">
+                                <option value="2">Attivo
+                                <option value="1">Sospeso
+                                <option value="0">Non tesserato
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="row">
+                        <div class="col-md-12" style="margin-bottom:10px;">
+                            <b>Gruppo di interesse</b>
+                        </div>
+                        <div class="col-12">
+                            <select name="filterCategories" class="form-select filterCategories" multiple="multiple">
+                                <option value="">Tutte</option>
+                                @foreach($categories as $category)
+                                    <option value="{{$category["id"]}}">
+                                        {!! str_repeat('&bull; ', $category["indentation"] ?? 0) !!}{{$category["name"]}}
+                                    </option>
+                                @endforeach
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="row">
+                        <div class="col-md-12" style="margin-bottom:10px;">
+                            <b>Anno di nascita</b>
+                        </div>
+                        <div class="col-12">
+                            <div class="row mb-2">
+                                <div class="col-3"><label class="form-check-label ms-2" >Da</label></div>
+                                <div class="col-9"><input class="form-control " type="number" name="txtFromYearYear"></div>
+                            </div>
+                        </div>
+                        <div class="col-12">
+                            <div class="row">
+                                <div class="col-3"><label class="form-check-label ms-2" >A</label></div>
+                                <div class="col-9"><input class="form-control " type="number"  name="txtToYearYear"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="row">
+                        <div class="col-md-12" style="margin-bottom:10px;">
+                            <b>Scadenza certificato medico</b>
+                        </div>
+                        <div class="col-12">
+                            <select name="filterScadenza" class="form-select filterScadenza" multiple="multiple">
+                                <option value="1">Scaduti
+                                <option value="2">In scadenza
+                                <option value="3">Non consegnato
+                                <option value="4">Validi
+                            </select>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="row">
+                        <div class="col-md-12" style="margin-bottom:10px;">
+                            <b>Tipologia certificato medico</b>
+                        </div>
+                        <div class="col-12">
+                            <select name="filterCertificateType" class="form-select filterCertificateType" multiple="multiple">
+                                <option value="">Tutti
+                                <option value="N">Non agonistico
+                                <option value="A">Agonistico
+                            </select>
+                        </div>
+                    </div>
+                </div>
+
+            </div>
+            <div class="row g-3">
+                <div class="col-md-12" style="text-align:right">
+                    <button class="btn--ui lightGrey" onclick="reset()" onclick="destroyDataTable()">Reset</button>
+                    <button class="btn--ui" onclick="loadDataTable()">FILTRA</button>
+                </div>
+            </div>
+            <hr size="1">
+        </div>
+        <section id="anagrafiche--utenti">
+            <div class="compare--chart_wrapper d-none"></div>
+            <table class="table tablesaw tableHead tablesaw-stack table--lista_utenti tableHead" width="100%" id="tablesaw-350" width="100%">
+                <thead>
+                    <tr>
+                        <th scope="col" style="text-align: center">Cognome</th>
+                        <th scope="col">Nome</th>
+                        <th scope="col">Telefono</th>
+                        <th scope="col" style="text-align: center">Età</th>
+                        <th scope="col" style="text-align: center">Anno</th>
+                        <th scope="col">Tesseramento</th>
+                        <th scope="col">Certificato</th>
+                        <th scope="col">...</th>
+                    </tr>
+                </thead>
+                <tbody id="checkall-target"></tbody>
+            </table>
+        </section>
+    </div>
+</div>
+@push('scripts')
+    <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
+    <style>
+        table.tableHead thead
+        {
+            position: sticky;
+            z-index: 100;
+            top: 0;
+        }
+        .select2-container--default .select2-selection--single
+        {
+            background-color: #E9F0F5;
+            border: 0.0625rem solid #DFE5EB;
+            font-size: 0.75rem;
+        }
+        .select2-selection
+        {
+            height: 38px !important;
+        }
+        .select2-selection__rendered
+        {
+            padding-top:3px;
+        }
+        .select2
+        {
+            width:100% !important;
+        }
+        a.notSelected
+        {
+            display: flex;
+            margin-right:10px;
+            float:left;
+            height: 2.5rem !important;
+            width:50px;
+            align-items: center;
+            justify-content: center;
+            color: var(--color-blu);
+            background-color: #d3dce1 !important;
+            padding: 0 1.25rem;
+            font-size: 0.875rem;
+            font-family: greycliff-cf, sans-serif;
+            border-radius: 1.875rem !important;
+            -webkit-border-radius: 1.875rem !important;
+            -moz-border-radius: 1.875rem !important;
+            -webkit-transition: all 0.3s ease-in-out;
+            -moz-transition: all 0.3s ease-in-out;
+            -o-transition: all 0.3s ease-in-out;
+            transition: all 0.3s ease-in-out;
+            border: none;
+        }
+        a.selected
+        {
+            display: flex;
+            margin-right:10px;
+            float:left;
+            height: 2.5rem !important;
+            width:50px;
+            align-items: center;
+            justify-content: center;
+            color: #fff;
+            background-color: var(--color-blu) !important;
+            padding: 0 1.25rem;
+            font-size: 0.875rem;
+            font-family: greycliff-cf, sans-serif;
+            border-radius: 1.875rem !important;
+            -webkit-border-radius: 1.875rem !important;
+            -moz-border-radius: 1.875rem !important;
+            -webkit-transition: all 0.3s ease-in-out;
+            -moz-transition: all 0.3s ease-in-out;
+            -o-transition: all 0.3s ease-in-out;
+            transition: all 0.3s ease-in-out;
+            border: none;
+        }
+        div.day
+        {
+            margin-top:20px;
+            color: var(--color-blu);
+            background-color: #ffffff !important;
+            padding: 20px;
+            border-radius: 1.875rem !important;
+            -webkit-border-radius: 1.875rem !important;
+            -moz-border-radius: 1.875rem !important;
+            -webkit-transition: all 0.3s ease-in-out;
+            -moz-transition: all 0.3s ease-in-out;
+            -o-transition: all 0.3s ease-in-out;
+            transition: all 0.3s ease-in-out;
+            border: 2px solid #d3dce1;
+        }
+        .btn--ui.lightGrey, .btn--ui.extraLightGrey
+        {
+            -webkit-border-radius: 0.5rem !important;
+            background-color:#ffffff !important;
+            border: 1px solid grey;
+            color: black;
+            font-weight:normal;
+        }
+
+        .persistent-invalid
+        {
+            border-color: #dc3545 !important;
+            padding-right: calc(1.5em + 0.75rem) !important;
+            background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") !important;
+            background-repeat: no-repeat !important;
+            background-position: right calc(0.375em + 0.1875rem) center !important;
+            background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) !important;
+        }
+    </style>
+    <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
+@endpush
+
+@push('scripts')
+    <link href="/css/datatables.css" rel="stylesheet" />
+    <script src="/assets/js/datatables.js"></script>
+    <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
+@endpush
+
+@push('scripts')
+    <script>
+        function restoreData(id)
+        {
+            if (confirm('Sei sicuro? L\'utente verrà ripristinato.'))
+                @this.restore(id);
+        }
+
+        function archiveData(id)
+        {
+            if (confirm('Sei sicuro? Tutti i dati relativi a questo utente verranno archiviati e non saranno più visibili.'))
+                @this.archive(id);
+        }
+
+        var isFilter = false;
+        $(document).ready(function() {
+            $(document).on("click",".showHideFilter",function() {
+                if (isFilter)
+                {
+                    isFilter = false;
+                    $(".showFilter").hide();
+                }
+                else
+                {
+                    isFilter = true;
+                    $(".showFilter").show();
+                }
+            });
+        } );
+
+        $(document).ready(function() {
+            loadFilters();
+            loadDataTable();
+        } );
+
+        function destroyDataTable()
+        {
+            $('#tablesaw-350').DataTable().destroy();
+        }
+
+        $('.filterCards').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+        $('.filterStatus').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+        $('.filterScadenza').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+        $('.filterCertificateType').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+        $('.filterCategories').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+
+        function loadFilters() {
+            let show = false;
+            var filterStatus = localStorage.getItem("filterStatusMember");
+            if (filterStatus != null && filterStatus != "null" && filterStatus != "undefined" && filterStatus)
+            {
+                $('.filterStatus').val(filterStatus).trigger('change');
+                show = true;
+            }
+            var filterCards = localStorage.getItem("filterCardsMember");
+            if (filterCards != null && filterCards != "null" && filterCards != "undefined" && filterCards)
+            {
+                $('.filterCards').val(filterCards).trigger('change');
+                show = true;
+            }
+            var filterScadenza = localStorage.getItem("filterScadenzaMember");
+            if (filterScadenza != null && filterScadenza != "null" && filterScadenza != "undefined" && filterScadenza)
+            {
+                $('.filterScadenza').val(filterScadenza).trigger('change');
+                show = true;
+            }
+            var filterCertificateType = localStorage.getItem("filterCertificateTypeMember");
+            if (filterCertificateType != null && filterCertificateType != "null" && filterCertificateType != "undefined" && filterCertificateType)
+            {
+                $('.filterCertificateType').val(filterCertificateType).trigger('change');
+                show = true;
+            }
+            var filterCategories = localStorage.getItem("filterCategoriesMember");
+            if (filterCategories != null && filterCategories != "null" && filterCategories != "undefined" && filterCategories)
+            {
+                $('.filterCategories').val(filterCategories).trigger('change');
+                show = true;
+            }
+            var fromYear = localStorage.getItem("fromYearMember");
+            if (fromYear != null && fromYear != "null" && fromYear != "undefined" && fromYear)
+            {
+                $('input[name="txtFromYear"]').val(fromYear);
+                show = true;
+            }
+            var toYear = localStorage.getItem("toYearMember");
+            if (toYear != null && toYear != "null" && toYear != "undefined" && toYear)
+            {
+                $('input[name="txtToYear"]').val(toYear);
+                show = true;
+            }
+            var fromYearYear = localStorage.getItem("fromYearYearMember");
+            if (fromYearYear != null && fromYearYear != "null" && fromYearYear != "undefined" && fromYearYear)
+            {
+                $('input[name="txtFromYearYear"]').val(fromYearYear);
+                show = true;
+            }
+
+            var toYearYear = localStorage.getItem("toYearYearMember");
+            if (toYearYear != null && toYearYear != "null" && toYearYear != "undefined" && toYearYear)
+            {
+                $('input[name="txtToYearYear"]').val(toYearYear);
+                show = true;
+            }
+
+            if (show) {
+                $('.showHideFilter').trigger('click');
+            }
+        }
+
+        $(document).on("keypress", $('.filterCategories'), function (e) {
+
+            setTimeout(() => {
+                $(".select2-results__option").each(function(){
+                    var txt = $(this).html();
+                    var count = (txt.match(/-/g) || []).length;
+                    $(this).addClass('paddingLeftSelect' + count);
+                });
+            }, 100);
+        });
+
+        $('.filterCategories').on('select2:open', function (e) {
+            setTimeout(() => {
+                $(".select2-results__option").each(function(){
+                    var txt = $(this).html();
+                    var count = (txt.match(/-/g) || []).length;
+                    $(this).addClass('paddingLeftSelect' + count);
+                });
+            }, 100);
+        });
+
+        function reset()
+        {
+            $('.filterCards').val('').trigger('change');
+            $('.filterStatus').val('').trigger('change');
+            $('.filterScadenza').val('-1').trigger('change');
+            $('.filterCertificateType').val('-1').trigger('change');
+            $('.filterCategories').val('-1').trigger('change');
+
+
+            $('input[name="txtFromYear"]').val('');
+            $('input[name="txtToYear"]').val('');
+
+            $('input[name="txtFromYearYear"]').val('');
+            $('input[name="txtToYearYear"]').val('');
+            loadDataTable();
+        }
+
+        function loadDataTable() {
+            let date = new Date();
+            let date_export = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_`;
+
+            const url = '/get_members?cards=' + $('.filterCards').val() + "&filterCategories=" + $('.filterCategories').val() + "&filterCertificateType=" + $('.filterCertificateType').val() + "&filterScadenza=" + $('.filterScadenza').val() + "&filterStatus=" + $('.filterStatus').val() + "&fromYear=" + $('input[name="txtFromYear"]').val() + "&toYear=" + $('input[name="txtToYear"]').val() + "&fromYearYear=" + $('input[name="txtFromYearYear"]').val() + "&toYearYear=" + $('input[name="txtToYearYear"]').val() + "&archived=1";
+
+            localStorage.setItem("filterCardsMember", $('.filterCards').val());
+            localStorage.setItem("filterCategoriesMember", $('.filterCategories').val());
+            localStorage.setItem("filterCertificateTypeMember", $('.filterCertificateType').val());
+            localStorage.setItem("filterScadenzaMember", $('.filterScadenza').val());
+            localStorage.setItem("filterStatusMember", $('.filterStatus').val());
+            localStorage.setItem("fromYearMember", $('input[name="txtFromYear"]').val());
+            localStorage.setItem("toYearMember", $('input[name="txtToYear"]').val());
+            localStorage.setItem("fromYearYearMember", $('input[name="txtFromYearYear"]').val());
+            localStorage.setItem("toYearYearMember", $('input[name="txtToYearYear"]').val());
+
+            if ($.fn.dataTable.isDataTable('#tablesaw-350')) {
+                $('#tablesaw-350').DataTable().destroy();
+            }
+
+            const pageLength = 10;
+
+            const dataTable = $('#tablesaw-350').DataTable({
+                serverSide: true,
+                processing: true,
+                ajax: url,
+                columns: [
+                    {
+                        data: "last_name",
+                        render: function (data){
+                            const d = data.split("|");
+                            var ret = d[0];
+                            return ret;
+                        }
+                    },
+                    {
+                        data: "first_name",
+                        render: function (data){
+                            const d = data.split("|");
+                            var ret = d[0];
+                            return ret;
+                        }
+                    },
+                    { data: "phone"},
+                    { data: "age", "type": "num", className:"dt-type-numeric"},
+                    { data: "year", className:"dt-type-numeric"},
+                    {
+                        data: "status",
+                        render: function (data){
+                            const d = data.split("|");
+                            var ret = '<span class="tablesaw-cell-content"><span class="badge tessera-badge ' + d[0] + '">' + d[1] + '</span></span>';
+                            return ret;
+                        }
+                    },
+                    {
+                        data: "certificate",
+                        render: function (data){
+                            var ret = '';
+                            if (data != "") {
+                                const d = data.split("|");
+                                ret += '<span class="tablesaw-cell-content d-flex align-items-center">';
+                                if (d[0] == "0") {
+                                    ret += '<i class="ico--ui check suspended me-2"></i>';
+                                    ret += 'Scaduto : ';
+                                }
+                                if (d[0] == "1") {
+                                    ret += '<i class="ico--ui check due me-2"></i>';
+                                    ret += 'In scadenza : ';
+                                }
+                                if (d[0] == "2") {
+                                    ret += '<i class="ico--ui check active me-2"></i>';
+                                    ret += 'Scadenza : ';
+                                }
+                                ret += d[1];
+                                ret += '</span>';
+                            }
+                            if(data == ""){
+                                ret += '<span class="tablesaw-cell-content d-flex align-items-center">';
+                                ret += '<i class="ico--ui check absent me-2"></i>';
+                                ret += 'Non consegnato';
+                                ret += '</span>';
+                            }
+                            return ret;
+                        }
+                    },
+                    {
+                        data: "action",
+                        render: function (data) {
+                            var ret = '<button type="button" class="btn" onclick="restoreData(' + data + ')" data-bs-toggle="popover"  data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Ripristina"><i class="fa-solid fa-rotate-left"></i></button>&nbsp;';
+                            ret += '<button type="button" class="btn" onclick="archiveData(' + data + ')" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Elimina"><i class="fa-regular fa-trash-can"></i></button>';
+                            return ret;
+                        }
+                    },
+                ],
+                fixedHeader: false,
+                thead: {
+                    'th': {'background-color': 'blue'}
+                },
+                layout: {
+                    topStart : null,
+                    topEnd : null,
+                    top1A: {
+                        // buttons: [
+                        //     {
+                        //         extend: 'collection',
+                        //         text: 'ESPORTA',
+                                buttons: [
+                                    {
+                                        extend: 'excelHtml5',
+                                        text: '<i class="fa-solid fa-file-excel"></i>',
+                                        action: newexportaction,
+                                        title: date_export + 'Utenti archiviati',
+                                        exportOptions: {
+                                            columns: ":not(':last')",
+                                            page: 'all'
+                                        }
+                                    },
+                                    {
+                                        extend: 'pdfHtml5',
+                                        text: '<i class="fa-solid fa-file-pdf"></i>',
+                                        action: newexportaction,
+                                        title: date_export + 'Utenti archiviati',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        },
+                                        customize: function(doc) {
+                                            doc.styles.tableHeader.alignment = 'left';
+                                        }
+                                    },
+                                    {
+                                        extend: 'print',
+                                        action: newexportaction,
+                                        text: '<i class="fa-solid fa-print"></i>',
+                                        title: date_export + 'Utenti archiviati',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        }
+                                    }
+                                ],
+                        //         dropup: true
+                        //     }
+                        // ]
+                    },
+                    top1B : {
+                        pageLength: {
+                            menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
+                        }
+                    },
+                    top1C :'search',
+                },
+                pagingType: 'numbers',
+                "language": {
+                    "url": "/assets/js/Italian.json"
+                },
+                "fnInitComplete": function (oSettings, json) {
+                    var html = '&nbsp;<a style="cursor:pointer" class="showHideFilter btn--ui"><i class="fa-solid fa-sliders"></i></a>';
+                    html += '&nbsp;<a style="cursor:pointer" class="addData btn--ui"><i class="fa-solid fa-plus"></i></a>';
+                    $(".dt-search").append(html);
+                    
+                    loadFilters();
+                }
+            });
+
+            $('#tablesaw-350 thead tr th').addClass('col');
+            $('#tablesaw-350 thead tr th').css("background-color", "#f6f8fa");
+            $('#tablesaw-350').on('draw.dt', function() {
+                $('[data-bs-toggle="popover"]').popover()
+            });
+        }
+
+        Livewire.on('reload', (x) =>
+        {
+            location.reload();
+        });
+    </script>
+@endpush

+ 30 - 5
resources/views/livewire/profile.blade.php

@@ -48,18 +48,43 @@
                             <div class="col-md-6 mb-3">
                                 <label class="form-label">Email</label>
                                 <input type="email"
-                                    class="form-control @error('email') is-invalid @enderror"
-                                    wire:model="email" {{ $editMode ? '' : 'disabled' }}>
+                                class="form-control @error('email') is-invalid @enderror"
+                                wire:model="email" {{ $editMode ? '' : 'disabled' }}>
                                 @error('email') <span class="text-danger">{{ $message }}</span> @enderror
                             </div>
+                            <div class="col-md-6"></div>
                             <div class="col-md-6 mb-3">
                                 <label class="form-label">Password</label>
-                                <input type="password"
-                                    class="form-control @error('password') is-invalid @enderror"
-                                    wire:model="password" {{ $editMode ? '' : 'disabled' }}>
+                                @if ($editMode)
+                                <div class="password-wrapper">
+                                @endif
+                                    <input type="password" class="form-control @error('password') is-invalid @enderror" wire:model="password" {{ $editMode ? '' : 'disabled' }}>
+                                @if ($editMode)
+                                    <div class="password-eye" onclick="togglePassword(this)">
+                                        <i class="fas fa-eye password-hidden"></i>
+                                        <i class="fas fa-eye-slash password-shown"></i>
+                                    </div>
+                                </div>
+                                @endif
                                 @error('password') <span class="text-danger">{{ $message }}</span> @enderror
                                 <small class="form-text text-muted">Lasciare vuoto per non modificare</small>
                             </div>
+                            <div class="col-md-6 mb-3">
+                                <label class="form-label">Conferma password</label>
+                                @if ($editMode)
+                                <div class="password-wrapper">
+                                @endif
+                                    <input type="password" class="form-control @error('password_confirmation') is-invalid @enderror" wire:model="password_confirmation" {{ $editMode ? '' : 'disabled' }}>
+                                @if ($editMode)
+                                    <div class="password-eye" onclick="togglePassword(this)">
+                                        <i class="fas fa-eye password-hidden"></i>
+                                        <i class="fas fa-eye-slash password-shown"></i>
+                                    </div>
+                                </div>
+                                @endif
+                                @error('password_confirmation') <span class="text-danger">{{ $message }}</span> @enderror
+                                <small class="form-text text-muted">Lasciare vuoto per non modificare</small>
+                            </div>
                         </div>
                     </div>
 

+ 29 - 19
resources/views/livewire/rate.blade.php

@@ -8,7 +8,11 @@
         </div>
     </header>
 
+    @if ($rate_from == 'course')
     <a class="btn--ui lightGrey" href="/course_member?id={{$mc->course_id}}"><i class="fa-solid fa-arrow-left"></i></a><br>
+    @elseif($rate_from == 'subscription')
+    <a class="btn--ui lightGrey" href="/subscription_member/{{$ms->subscription_id}}"><i class="fa-solid fa-arrow-left"></i></a><br>
+    @endif
 
     <div class="showFilter" style="display:none" wire:ignore.self>
         <hr size="1">
@@ -79,16 +83,12 @@
 
         <div class="row">
             <div class="col-6">
-                <h3 class="mt-4">{{$detail}}</h3><br>
+                <h3>{!!$detail!!}</h3>
             </div>
-            <div class="col-2 right" style="margin-top:20px;text-align:right">
-                <button id="btPay" style="display:none;" class="btn--ui" >PAGA</button>
-            </div>
-            <div class="col-2 right" style="margin-top:20px;text-align:right">
-                <button id="btSuspend" style="display:none;" class="btn--ui" >SOSPENDI/ATTIVA SELEZIONATI</button>
-            </div>
-            <div class="col-2 right" style="margin-top:20px;text-align:right">
-                <button id="btRemove" style="display:none;" class="btn--ui" >ELIMINA SELEZIONATI</button>
+            <div class="col right table-actions" style="text-align: right">
+                <button id="btPay" style="display:none;" class="btn" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Crea tutte le entrate"><i class="fa-regular fa-file-text"></i></button>
+                <button id="btSuspend" style="display:none;" class="btn" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Sospendi/Riattiva tutto"><i class="fa-solid fa-ban"></i></button>
+                <button id="btRemove" style="display:none;" class="btn" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Elimina tutto"><i class="fa-regular fa-trash-can"></i></button>
             </div>
         </div>
         
@@ -140,21 +140,29 @@
                             @if($record->status == 0)
 
                                 <input type="hidden" id="memberId_{{$record->id}}" value="{{$record->member_id}}">
-                                <input type="hidden" id="causalId_{{$record->id}}" value="{{!$record->is_subscription ? $record->member_course->course->causal_id : ''}}">
-                                <input type="hidden" id="subCausalId_{{$record->id}}" value="{{$record->is_subscription ? $record->member_course->course->sub_causal_id : ''}}">
                                 <input type="hidden" id="createSubscription_{{$record->id}}" value="{{$record->is_subscription ? '1' : ''}}">
                                 <input type="hidden" id="months_{{$record->id}}" value="{{implode("|", json_decode($record->months))}}">
                                 <input type="hidden" id="price_{{$record->id}}" value="{{!$record->is_subscription ? $record->price : ''}}">
                                 <input type="hidden" id="subscription_price_{{$record->id}}" value="{{$record->is_subscription ? $record->price : ''}}">
-                                <input type="hidden" id="courseId_{{$record->id}}" value="{{$record->member_course_id}}">
                                 <input type="hidden" id="rateId_{{$record->id}}" value="{{$record->id}}">
 
-                                <a class="btn  u" href="/in?new=1&memberId={{$record->member_id}}&causalId={{!$record->is_subscription ? $record->member_course->course->causal_id : ''}}&subCausalId={{$record->is_subscription ? $record->member_course->course->sub_causal_id : ''}}&createSubscription={{$record->is_subscription ? '1' : ''}}&months={{implode("|", json_decode($record->months))}}&price={{!$record->is_subscription ? $record->price : ''}}&subscription_price={{$record->is_subscription ? $record->price : ''}}&courseId={{$record->member_course_id}}&rateId={{$record->id}}" data-bs-toggle="popover"
-                                data-bs-trigger="hover focus" data-bs-placement="bottom"
-                                data-bs-content="Crea entrata"><i class="fa-regular fa-file-lines"></i></a>
-                                <a class="btn u" onclick="confirmDelete({{$record->id}})" data-bs-toggle="popover"
-                                    data-bs-trigger="hover focus" data-bs-placement="bottom"
-                                    data-bs-content="Elimina"><i class="fa-regular fa-trash-can"></i></button>
+                                @if ($rate_from == 'course')
+                                <input type="hidden" id="courseId_{{$record->id}}" value="{{$record->member_course_id}}">
+                                <input type="hidden" id="subscriptionId_{{$record->id}}" value="0">
+                                <input type="hidden" id="causalId_{{$record->id}}" value="{{!$record->is_subscription ? $record->member_course->course->causal_id : ''}}">
+                                <input type="hidden" id="subCausalId_{{$record->id}}" value="{{$record->is_subscription ? $record->member_course->course->sub_causal_id : ''}}">
+
+                                <a class="btn  u" href="/in?new=1&memberId={{$record->member_id}}&causalId={{!$record->is_subscription ? $record->member_course->course->causal_id : ''}}&subCausalId={{$record->is_subscription ? $record->member_course->course->sub_causal_id : ''}}&createSubscription={{$record->is_subscription ? '1' : ''}}&months={{implode("|", json_decode($record->months))}}&price={{!$record->is_subscription ? $record->price : ''}}&subscription_price={{$record->is_subscription ? $record->price : ''}}&courseId={{$record->member_course_id}}&subscriptionId=0&rateId={{$record->id}}&backToRates" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Crea entrata"><i class="fa-regular fa-file-lines"></i></a>
+                                @elseif($rate_from == 'subscription')
+                                <input type="hidden" id="courseId_{{$record->id}}" value="0">
+                                <input type="hidden" id="subscriptionId_{{$record->id}}" value="{{$record->member_subscription_id}}">
+                                <input type="hidden" id="causalId_{{$record->id}}" value="{{!$record->is_subscription ? $record->member_subscription->subscription->causal_id : ''}}">
+                                <input type="hidden" id="subCausalId_{{$record->id}}" value="{{$record->is_subscription ? $record->member_subscription->subscription->sub_causal_id : ''}}">
+
+                                <a class="btn  u" href="/in?new=1&memberId={{$record->member_id}}&causalId={{!$record->is_subscription ? $record->member_subscription->subscription->causal_id : ''}}&subCausalId={{$record->is_subscription ? $record->member_subscription->subscription->sub_causal_id : ''}}&createSubscription={{$record->is_subscription ? '1' : ''}}&months={{implode("|", json_decode($record->months))}}&price={{!$record->is_subscription ? $record->price : ''}}&subscription_price={{$record->is_subscription ? $record->price : ''}}&courseId=0&subscriptionId={{$record->member_subscription_id}}&rateId={{$record->id}}&backToRates" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Crea entrata"><i class="fa-regular fa-file-lines"></i></a>
+                                @endif
+
+                                <a class="btn u" onclick="confirmDelete({{$record->id}})" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Elimina"><i class="fa-regular fa-trash-can"></i></button>
                             @endif
                         </td>
                     </tr>
@@ -565,6 +573,7 @@
                     var price = '0';
                     var subscription_price = '';
                     var courseId = 0;
+                    var subscriptionId = 0;
                     var rateId = 0;
 
                     $('.chkIds').each(function () {
@@ -588,6 +597,7 @@
                                 price = parseFloat(price) + parseFloat($("#price_" + id).val());                            
                             }
                             courseId = $("#courseId_" + id).val();;
+                            subscriptionId = $("#subscriptionId_" + id).val();;
                             if (rateId != '') 
                                 rateId += ",";
                             rateId += $("#rateId_" + id).val();;
@@ -595,7 +605,7 @@
                             //ids.push($(this).val());
                     });
 
-                    document.location.href = "/in?new=1&memberId=" + memberId + "&causalId=" + causalId + "&subCausalId=" + subCausalId + "&createSubscription=" + createSubscription + "&months=" + months + "&price=" + price + "&subscription_price=" + subscription_price + "&courseId=" + courseId + "&rateId=" + rateId;
+                    document.location.href = "/in?new=1&memberId=" + memberId + "&causalId=" + causalId + "&subCausalId=" + subCausalId + "&createSubscription=" + createSubscription + "&months=" + months + "&price=" + price + "&subscription_price=" + subscription_price + "&courseId=" + courseId + "&subscriptionId=" + subscriptionId + "&rateId=" + rateId;
 
                     /*if (confirm('Sei sicuro?'))
                     {

+ 165 - 60
resources/views/livewire/records_in.blade.php

@@ -57,60 +57,92 @@
                         </div>
                     </div>
                 </div>
-                <div class="col-md-2">
+                <div class="col-md-10">
                     <div class="row">
-                        <div class="col-md-12" style="margin-bottom:10px;">
-                            <b>Tipologia</b>
-                        </div>
-                        <div class="col-12">
-                            <select name="filterCommercial" class="form-select filterCommercial">
-                                <option value="">Entrambe
-                                <option value="1">Commerciale
-                                <option value="2">Non commerciale
-                            </select>
-                        </div>
-                    </div>
-                </div>
-                <div class="col-md-2">
-                    <div class="row">
-                        <div class="col-md-12" style="margin-bottom:10px;">
-                            <b>Utente</b>
+                        <div class="col-md-3 mt-2">
+                            <div class="row">
+                                <div class="col-md-12" style="margin-bottom:10px;">
+                                    <b>Tipologia</b>
+                                </div>
+                                <div class="col-12">
+                                    <select name="filterCommercial" class="form-select filterCommercial">
+                                        <option value="">Entrambe
+                                        <option value="1">Commerciale
+                                        <option value="2">Non commerciale
+                                    </select>
+                                </div>
+                            </div>
                         </div>
-                        <div class="col-12">
-                            <select name="search_member_id" class="form-select filterMember">
-                                <option value="">--Seleziona--
-                                @foreach($members as $member)
-                                    <option value="{{$member->id}}">{{$member->last_name}} {{$member->first_name}}
-                                @endforeach
-                            </select>
+                        <div class="col-md-3 mt-2">
+                            <div class="row">
+                                <div class="col-md-12" style="margin-bottom:10px;">
+                                    <b>Utente</b>
+                                </div>
+                                <div class="col-12">
+                                    <select name="search_member_id" class="form-select filterMember">
+                                        <option value="">--Seleziona--
+                                        @foreach($members as $member)
+                                            <option value="{{$member->id}}">{{$member->last_name}} {{$member->first_name}}
+                                        @endforeach
+                                    </select>
+                                </div>
+                            </div>
                         </div>
-                    </div>
-                </div>
-                <div class="col-md-2">
-                    <div class="row">
-                        <div class="col-md-12" style="margin-bottom:10px;">
-                            <b>Metodi di pagamento</b>
+                        <div class="col-md-3 mt-2">
+                            <div class="row">
+                                <div class="col-md-12" style="margin-bottom:10px;">
+                                    <b>Metodi di pagamento</b>
+                                </div>
+                                <div class="col-12">
+                                    <select name="search_payment_method_id" class="form-select filterPaymentMethod" multiple="multiple">
+                                        @foreach($payments as $payment)
+                                            <option value="{{$payment->id}}">{{$payment->name}}
+                                        @endforeach
+                                    </select>
+                                </div>
+                            </div>
                         </div>
-                        <div class="col-12">
-                            <select name="search_payment_method_id" class="form-select filterPaymentMethod" multiple="multiple">
-                                @foreach($payments as $payment)
-                                    <option value="{{$payment->id}}">{{$payment->name}}
-                                @endforeach
-                            </select>
+                        <div class="col-md-3 mt-2">
+                            <div class="row">
+                                <div class="col-md-12" style="margin-bottom:10px;">
+                                    <b>Causali</b>
+                                </div>
+                                <div class="col-12">
+                                    <select name="search_causal_id" class="form-select filterCausals" multiple="multiple">
+                                        @foreach($causals as $causal)
+                                            <option value="{{$causal["id"]}}">{!!$causal["name"]!!}
+                                        @endforeach
+                                    </select>
+                                </div>
+                            </div>
                         </div>
-                    </div>
-                </div>
-                <div class="col-md-2">
-                    <div class="row">
-                        <div class="col-md-12" style="margin-bottom:10px;">
-                            <b>Causali</b>
+                        <div class="col-md-3 mt-2">
+                            <div class="row">
+                                <div class="col-md-12" style="margin-bottom:10px;">
+                                    <b>Corsi</b>
+                                </div>
+                                <div class="col-12">
+                                    <select name="search_course_id" class="form-select filterCourses" multiple="multiple">
+                                        @foreach($courses as $course)
+                                            <option value="{{$course["id"]}}">{!!$course["name"]!!}
+                                        @endforeach
+                                    </select>
+                                </div>
+                            </div>
                         </div>
-                        <div class="col-12">
-                            <select name="search_causal_id" class="form-select filterCausals" multiple="multiple">
-                                @foreach($causals as $causal)
-                                    <option value="{{$causal["id"]}}">{!!$causal["name"]!!}
-                                @endforeach
-                            </select>
+                        <div class="col-md-3 mt-2">
+                            <div class="row">
+                                <div class="col-md-12" style="margin-bottom:10px;">
+                                    <b>Abbonamenti</b>
+                                </div>
+                                <div class="col-12">
+                                    <select name="search_subscription_id" class="form-select filterSubscriptions" multiple="multiple">
+                                        @foreach($subscriptions as $subscription)
+                                            <option value="{{$subscription["id"]}}">{!!$subscription["name"]!!}
+                                        @endforeach
+                                    </select>
+                                </div>
+                            </div>
                         </div>
                     </div>
                 </div>
@@ -137,6 +169,7 @@
                         <th scope="col">Nome</th>
                         <th scope="col">Commerciale</th>
                         <th scope="col">Causale</th>
+                        <th scope="col">Corso/Abbonamento</th>
                         <th scope="col">Pagamento</th>
 
                         <th scope="col">Stato</th>
@@ -362,22 +395,41 @@
                             </div>
 
                             @foreach($rows as $idx => $row)
-
                                 <div class="row gx-2 mt-3" >
-                                    <span class="title-form d-block w-100">Causale</span>
-                                    <div class="col-md-12">
-
-                                        @if($this->dataId > 0 && !$isDuplicate)
+                                    <div class="@if ($fromCourse || $row['course_id'] || $row['subscription_id'])col-md-6 @endif col-xs-12">
+                                        <span class="title-form d-block w-100">Causale</span>
+                                        @if ($fromCourse || $row['course_id'] || $row['subscription_id'])
                                             {{$this->getCausal($rows[$idx]['causal_id'])}}<br>
                                         @else
-                                            <livewire:causals :type="$typeIN" :idx="$idx" :show_hidden=0 :causal_id="$rows[$idx]['causal_id']" :wire:key="$idx"/>
-                                        @endif
+                                            @if($this->dataId > 0 && !$isDuplicate)
+                                                {{$this->getCausal($rows[$idx]['causal_id'])}}<br>
+                                            @else
+                                                <livewire:causals :type="$typeIN" :idx="$idx" :show_hidden=0 :show_course_causals=0 :causal_id="$rows[$idx]['causal_id']" :wire:key="$idx"/>
+                                            @endif
 
-                                        @error('rows.'. $idx . '.causal_id')
-                                            <span style="argin-top: 0.25rem; font-size: 0.875em; color: var(--bs-form-invalid-color);">{{ $message }}</span>
-                                        @enderror
+                                            @error('rows.'. $idx . '.causal_id')
+                                                <span style="argin-top: 0.25rem; font-size: 0.875em; color: var(--bs-form-invalid-color);">{{ $message }}</span>
+                                            @enderror
+                                        @endif
 
                                     </div>
+                                    @if ($fromCourse)
+                                        @if (isset($row['course']) && $row['course'] != null)
+                                            <div class="col-md-6 col-xs-12">
+                                                <span class="title-form d-block w-100">Corso</span>
+                                                @if (isset($row['course']))
+                                                    {{$row['course']->getDetailsName()}}
+                                                @endif
+                                            </div>
+                                        @elseif (isset($row['subscription']) && $row['subscription'] != null)
+                                            <div class="col-md-6 col-xs-12">
+                                                <span class="title-form d-block w-100">Abbonamento</span>
+                                                @if (isset($row['subscription']))
+                                                    {{$row['subscription']->name}}
+                                                @endif
+                                            </div>
+                                        @endif
+                                    @endif
                                 </div>
 
                                 <div class="row gx-2 mt-3">
@@ -578,7 +630,7 @@
 
                             @endforeach
 
-                            @if($this->dataId == 0)
+                            @if($this->dataId == 0 && !$fromCourse)
                             <br>
                             <button type="button" class="btn--ui primary" wire:click.prevent="addRow()">
                                 Aggiungi causale
@@ -1048,6 +1100,16 @@
                 //var data = $('.filterCausals').select2("val");
                 //@this.set('filterCausals', data);
             });
+            $('.filterCourses').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+            $('.filterCourses').on('change', function (e) {
+                //var data = $('.filterCourses').select2("val");
+                //@this.set('filterCourses', data);
+            });
+            $('.filterSubscriptions').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+            $('.filterSubscriptions').on('change', function (e) {
+                //var data = $('.filterSubscriptions').select2("val");
+                //@this.set('filterSubscriptions', data);
+            });
 
 
 
@@ -1067,6 +1129,16 @@
             //var data = $('.filterCausals').select2("val");
             //@this.set('filterCausals', data);
         });
+        $('.filterCourses').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+        $('.filterCourses').on('change', function (e) {
+            //var data = $('.filterCourses').select2("val");
+            //@this.set('filterCourses', data);
+        });
+        $('.filterSubscriptions').select2({"language": {"noResults": function(){return "Nessun risultato";}}});
+        $('.filterSubscriptions').on('change', function (e) {
+            //var data = $('.filterSubscriptions').select2("val");
+            //@this.set('filterSubscriptions', data);
+        });
 
         window.livewire.on('saved', () => {
             $('#userModal').modal('hide');
@@ -1140,6 +1212,18 @@
                 $('.filterCausals').val(filterCausals);
                 $('.filterCausals').trigger('change');
             }
+            var filterCourses = localStorage.getItem("filterCourses");
+            if (filterCourses)
+            {
+                $('.filterCourses').val(filterCourses);
+                $('.filterCourses').trigger('change');
+            }
+            var filterSubscriptions = localStorage.getItem("filterSubscriptions");
+            if (filterSubscriptions)
+            {
+                $('.filterSubscriptions').val(filterSubscriptions);
+                $('.filterSubscriptions').trigger('change');
+            }
             var filterFrom = localStorage.getItem("filterFrom");
             if (filterFrom)
             {
@@ -1181,6 +1265,18 @@
                 $('.filterCausals').val(filterCausals);
                 $('.filterCausals').trigger('change');
             }
+            var filterCourses = localStorage.getItem("filterCoursesRecordIn");
+            if (filterCourses && filterCourses != "null" && filterCourses != "undefined")
+            {
+                $('.filterCourses').val(filterCourses);
+                $('.filterCourses').trigger('change');
+            }
+            var filterSubscriptions = localStorage.getItem("filterSubscriptionsRecordIn");
+            if (filterSubscriptions && filterSubscriptions != "null" && filterSubscriptions != "undefined")
+            {
+                $('.filterSubscriptions').val(filterSubscriptions);
+                $('.filterSubscriptions').trigger('change');
+            }
             var filterFrom = localStorage.getItem("filterFromRecordIn");
             if (filterFrom && filterFrom != "null" && filterFrom != "undefined")
             {
@@ -1262,6 +1358,10 @@
             $('.filterPaymentMethod').trigger('change');
             $('.filterCausals').val('');
             $('.filterCausals').trigger('change');
+            $('.filterCourses').val('');
+            $('.filterCourses').trigger('change');
+            $('.filterSubscriptions').val('');
+            $('.filterSubscriptions').trigger('change');
             $('.filterFrom').val('');
             $('.filterTo').val('');
             //localStorage.clear();
@@ -1282,6 +1382,8 @@
             var filterMember = $('.filterMember').val();
             var filterPaymentMethod = $('.filterPaymentMethod').val();
             var filterCausals = $('.filterCausals').val();
+            var filterCourses = $('.filterCourses').val();
+            var filterSubscriptions = $('.filterSubscriptions').val();
             var filterFrom = $('.filterFrom').val();
             var filterTo = $('.filterTo').val();
 
@@ -1290,6 +1392,8 @@
             localStorage.setItem("filterMemberRecordIn", filterMember);
             localStorage.setItem("filterPaymentMethodRecordIn", filterPaymentMethod);
             localStorage.setItem("filterCausalsRecordIn", filterCausals);
+            localStorage.setItem("filterCoursesRecordIn", filterCourses);
+            localStorage.setItem("filterSubscriptionsRecordIn", filterSubscriptions);
             localStorage.setItem("filterFromRecordIn", filterFrom);
             localStorage.setItem("filterToRecordIn", filterTo);
 
@@ -1307,7 +1411,7 @@
                 serverSide: true,
                 processing: true,
                 ajax: {
-                    url : '/get_record_in?filterCommercial=' + filterCommercial + "&filterMember=" + filterMember + "&filterPaymentMethod=" + filterPaymentMethod + "&filterCausals=" + filterCausals + "&filterFrom=" + filterFrom + "&filterTo=" + filterTo,
+                    url : '/get_record_in?filterCommercial=' + filterCommercial + "&filterMember=" + filterMember + "&filterPaymentMethod=" + filterPaymentMethod + "&filterCausals=" + filterCausals + "&filterCourses=" + filterCourses + "&filterSubscriptions=" + filterSubscriptions + "&filterFrom=" + filterFrom + "&filterTo=" + filterTo,
                     dataSrc: function (json){
                         if(json.totals){
                             $(".totalDiv").html('Totale&nbsp;:&nbsp;<b>' + json.totals + '</b>');
@@ -1344,6 +1448,7 @@
                     { data: 'first_name' },
                     { data: 'commercial' },
                     { data: 'causals', "orderable": false, },
+                    { data: 'courses', "orderable": false, },
                     { data: 'payment', "orderable": false, },
                     //{ data: 'payment_date', "orderable": false, },
                     { data: 'status', "orderable": false, },

+ 24 - 3
resources/views/livewire/records_in_out.blade.php

@@ -15,14 +15,14 @@
 
         <form action="" class=" d-flex align-items-center ">
 
-            <select class="form-select form-select-lg me-1 " id="month" style="width:150px !important;">
-                <option value="x" {{$month == "x" ? 'selected' : ""}}>Anno completo</option>
+            <select class="form-select form-select-lg me-1 " id="month" style="width:150px !important;" onchange="updateYearSelect(this)">
+                <option value="x" {{$month == "x" ? 'selected' : ""}}>Stagione</option>
                 @foreach(getMonthList() as $mid => $mname)
                     <option value="{{$mid}}" {{$month == $mid ? 'selected' : ""}}>{{$mname}}</option>
                 @endforeach
             </select>
 
-            <select class="form-select " style="width:100px !important;" id="year">
+            <select class="form-select " {{-- style="width:100px !important;" --}} id="year">
                 @for($y=date("Y") + 5; $y>date("Y") - 2; $y--)
                     <option value="{{$y}}" {{$y == date("Y") ? 'selected' : ''}}>{{$y}}</option>
                 @endfor
@@ -503,6 +503,27 @@
             @this.exportYear(y);
         });
 
+        function updateYearSelect(month) {
+            let year = document.getElementById("year");
+
+            if (year) {
+                let options = Array.from(year.options);
+
+                if (month.value=='x') {
+                    options.forEach(option => {
+                        let y = Number(option.innerText);
+                        if (!isNaN(y)) {
+                            option.innerText = `${y}/${y+1}`;
+                        }
+                    });
+                } else {
+                    options.forEach(option => {
+                        let y = option.innerText;
+                        option.innerText = y.split('/')[0];
+                    });
+                }
+            }
+        }
 
     </script>
 

+ 22 - 21
resources/views/livewire/reports.blade.php

@@ -63,7 +63,7 @@
 
         <div class="chart-row">
             <div class="dashboard-card chart-card">
-                <div class="dashboard-card-title">Causali Performanti - <span id="causals-season-title">{{ $seasonFilter }}</span></div>
+                <div class="dashboard-card-title">Redditività per Causale - <span id="causals-season-title">{{ $seasonFilter }}</span></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">
@@ -95,7 +95,7 @@
 
     <div class="chart-row">
         <div class="dashboard-card chart-card modern-course-card">
-            <div class="dashboard-card-title">Analisi Corsi</div>
+            <div class="dashboard-card-title">Analisi incassi corsi</div>
             <div class="chart-body">
                 <div class="course-controls">
                     <div class="control-group">
@@ -135,8 +135,8 @@
     </div>
 </div>
 
-<script>
-    window.ReportsChartManager = window.ReportsChartManager || {
+    <script>
+        window.ReportsChartManager = window.ReportsChartManager || {
             charts: {},
             currentSeason: null,
 
@@ -288,7 +288,7 @@
                                 callbacks: {
                                     label: function (context) {
                                         return context.dataset.label + ': €' +
-                                            new Intl.NumberFormat('it-IT').format(context.parsed.y);
+                                            new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(context.parsed.y);
                                     }
                                 }
                             },
@@ -303,7 +303,7 @@
                                 grid: { color: 'rgba(0, 0, 0, 0.05)' },
                                 ticks: {
                                     callback: function (value) {
-                                        return '€' + new Intl.NumberFormat('it-IT').format(value);
+                                        return '€' + new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(value);
                                     }
                                 }
                             }
@@ -744,7 +744,7 @@
                                 },
                                 ticks: {
                                     callback: function (value) {
-                                        return '€' + new Intl.NumberFormat('it-IT').format(value);
+                                        return '€' + new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(value);
                                     }
                                 }
                             }
@@ -769,7 +769,7 @@
                                 callbacks: {
                                     label: function (context) {
                                         return context.dataset.label + ': €' +
-                                            new Intl.NumberFormat('it-IT').format(context.parsed.y);
+                                            new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(context.parsed.y);
                                     }
                                 }
                             }
@@ -886,11 +886,11 @@
                                         bottomLeft: 0,
                                         bottomRight: 0,
                                     },
-                                    borderSkipped: true,
+                                    // borderSkipped: true,
                                     data: earnedData,
                                     type: 'bar',
                                     barThickness: "flex",
-                                    barPercentage: 0.65,
+                                    barPercentage: 0.50,
                                     categoryPercentage: 0.25,
                                     order: 1,
                                     participantData: participantData,
@@ -908,11 +908,11 @@
                                         bottomLeft: 0,
                                         bottomRight: 0,
                                     },
-                                    borderSkipped: true,
+                                    // borderSkipped: true,
                                     data: totalData,
                                     type: 'bar',
                                     barThickness: "flex",
-                                    barPercentage: 0.65,
+                                    barPercentage: 0.50,
                                     categoryPercentage: 0.25,
                                     order: 2,
                                     participantData: participantData,
@@ -1026,14 +1026,14 @@
                                             let index = item.dataIndex;
                                             let monthNameExtended = item.dataset["monthNamesExtended"] ? item.dataset["monthNamesExtended"][index] : 0;
 
-                                            // return item.label + '\n' + 'TOTALE ATTESO: €' + new Intl.NumberFormat('it-IT').format(sum);
-                                            return monthNameExtended + '\n' + 'TOTALE ATTESO: €' + new Intl.NumberFormat('it-IT').format(sum);
+                                            // return item.label + '\n' + 'TOTALE ATTESO: €' + new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(sum);
+                                            return monthNameExtended + '\n' + 'TOTALE ATTESO: €' + new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(sum);
                                         },
                                         // labelTextColor: function(tooltipItems) {
                                         //     return tooltipItems.dataset.backgroundColor;
                                         // },
                                         label: function (tooltipItems) {
-                                            let label = tooltipItems.dataset.label + ': €' + new Intl.NumberFormat('it-IT').format(tooltipItems.parsed.y);
+                                            let label = tooltipItems.dataset.label + ': €' + new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(tooltipItems.parsed.y);
 
                                             return label;
                                         },
@@ -1082,7 +1082,7 @@
                 let tableHtml = `<div class="course-table">
                                     <div class="table-header">
                                         <div class="table-cell month">MESE</div>
-                                        <div class="table-cell percentage">%<br/>INCASSATO</div>
+                                        <div class="table-cell percentage">TOT. INCASSATO</div>
                                         <div class="table-cell delta">TOT. DA INCASSARE</div>
                                         <div class="table-cell suspended">SOSPESI</div>
                                     </div>`;
@@ -1111,14 +1111,15 @@
                     }
 
                     // 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';
+                    const deltaClass = (total > 0 && delta === 0) ? 'positive' : (delta > 0) ? 'negative' : 'neutral';
+                    const earnedClass = (earned > 0) ? 'positive' : 'neutral';
 
                     tableHtml += `
             <div class="table-row">
                 <div class="table-cell month">${row.month}</div>
-                <div class="table-cell percentage ${percentageClass}">${percentageDisplay}</div>
-                <div class="table-cell delta ${deltaClass}">€${new Intl.NumberFormat('it-IT').format(delta)}</div>
+                <!-- <div class="table-cell percentage ${percentageClass}">${percentageDisplay}</div> -->
+                <div class="table-cell earned ${earnedClass}">€${new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(earned)}</div>
+                <div class="table-cell delta ${deltaClass}">€${new Intl.NumberFormat('it-IT', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(delta)}</div>
                 <div class="table-cell suspended">${row.suspended}</div>
             </div>
         `;
@@ -1170,5 +1171,5 @@
                 }, 200);
             });
         });
-</script>
+    </script>
 </div>

+ 13 - 6
resources/views/livewire/settings.blade.php

@@ -16,14 +16,14 @@
             <h4 style="cursor:pointer;{{$type == 'corsi' ? 'border-bottom:2px solid var(--color-blu); color:var(--color-blu);' : ''}}" wire:click="change('corsi')">Corsi</h4>
             <h4 style="cursor:pointer;{{$type == 'contabilita' ? 'border-bottom:2px solid var(--color-blu); color:var(--color-blu);' : ''}}" wire:click="change('contabilita')">Contabilità</h4>
             <h4 style="cursor:pointer;{{$type == 'comunicazioni' ? 'border-bottom:2px solid var(--color-blu); color:var(--color-blu);' : ''}}" wire:click="change('comunicazioni')">Comunicazioni</h4>
-            <a href="/users"><h4>Profilo utenti</h4></a>
+            <a href="/users"><h4>Utenze</h4></a>
         </div>
 
         @if($type == 'anagrafica')
             <div id="anagrafica">
                 <a href="/categories">
                     <div class="row">
-                        <div class="col-md-11 p-2"><h5>Gruppi di appartenenza</h5></div>
+                        <div class="col-md-11 p-2"><h5>Gruppi di interesse</h5></div>
                         <div class="col-md-1 p-2"><i class="fa-solid fa-chevron-right"></i></div>
                     </div>
                 </a>
@@ -49,7 +49,7 @@
             <div id="corsi">
                 <a href="/course_subscriptions">
                     <div class="row">
-                        <div class="col-md-11 p-2"><h5>Abbonamenti</h5></div>
+                        <div class="col-md-11 p-2"><h5>Frequenza di pagamenti</h5></div>
                         <div class="col-md-1 p-2"><i class="fa-solid fa-chevron-right"></i></div>
                     </div>
                 </a>
@@ -61,6 +61,13 @@
                     </div>
                 </a>
                 <hr size="1">
+                <a href="/subscriptions">
+                    <div class="row">
+                        <div class="col-md-11 p-2"><h5>Abbonamenti</h5></div>
+                        <div class="col-md-1 p-2"><i class="fa-solid fa-chevron-right"></i></div>
+                    </div>
+                </a>
+                <hr size="1">
                 <a href="/course_durations">
                     <div class="row">
                         <div class="col-md-11 p-2"><h5>Durata</h5></div>
@@ -82,13 +89,13 @@
                     </div>
                 </a>
                 <hr size="1">
-                <a href="/course_types">
+                {{-- <a href="/course_types">
                     <div class="row">
                         <div class="col-md-11 p-2"><h5>Tipologie</h5></div>
                         <div class="col-md-1 p-2"><i class="fa-solid fa-chevron-right"></i></div>
                     </div>
                 </a>
-                <hr size="1">
+                <hr size="1"> --}}
             </div>
         @endif
 
@@ -96,7 +103,7 @@
             <div id="contabilita">
                 <a href="/banks">
                     <div class="row">
-                        <div class="col-md-11 p-2"><h5>Canali finanziari</h5></div>
+                        <div class="col-md-11 p-2"><h5>Banche</h5></div>
                         <div class="col-md-1 p-2"><i class="fa-solid fa-chevron-right"></i></div>
                     </div>
                 </a>

+ 571 - 232
resources/views/livewire/sms_comunications.blade.php

@@ -1,192 +1,367 @@
 <div class="col card--ui" id="card--dashboard">
 
-    @if(!$add && !$update)
-
-    <header id="title--section" style="display:none !important"  class="d-flex align-items-center justify-content-between">
+    <header id="title--section" style="display:none !important" class="d-flex align-items-center justify-content-between">
         <div class="title--section_name d-flex align-items-center justify-content-between">
             <i class="ico--ui title_section utenti me-2"></i>
-            <h2 class="primary">@if(!$add && !$update)SMS Templates @else Inserimento/modifica template SMS @endif</h2>
+            <h2 class="primary">Sms</h2>
         </div>
 
-        @if(!$add && !$update)
-            <div class="title--section_addButton" wire:click="add()" style="cursor: pointer;">
+        @if(!$showForm)
+            <div class="title--section_addButton" wire:click="add()" style="cursor: pointer;" wire:ignore>
                 <div class="btn--ui entrata d-flex justify-items-between">
-                    <a href="#" wire:click="add()" style="color:white">Aggiungi</a>
+                    <a href="#" style="color:white;">Aggiungi</a>
                 </div>
             </div>
         @endif
-
     </header>
 
-    <a class="btn--ui lightGrey" href="/settings?type=comunicazioni"><i class="fa-solid fa-arrow-left"></i></a><br>
-
-        <section id="resume-table">
-            <div class="compare--chart_wrapper d-none"></div>
+    <a class="btn--ui lightGrey" @if(!$showForm) href="/settings?type=comunicazioni" @else href="/sms_comunications" @endif><i class="fa-solid fa-arrow-left"></i></a><br/><br/>
 
+    {{-- LISTA MESSAGGI --}}
+    <section id="resume-table" @if($showForm) style="display:none" @endif>
+        <div wire:ignore>
             <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
                 <thead>
                     <tr>
-                        <th scope="col">Oggetto</th>
-                        <th scope="col">Messaggio</th>
-                        <th scope="col">Caratteri</th>
-                        <th scope="col">Data Creazione</th>
-                        <th scope="col">...</th>
+                        <th>ID</th>
+                        <th>Oggetto</th>
+                        <th># Destinatari</th>
+                        <th>Stato</th>
+                        <th>Programmato per</th>
+                        <th>Data invio</th>
+                        <th>Data creazione</th>
+                        <th>...</th>
                     </tr>
                 </thead>
                 <tbody id="checkall-target">
                     @foreach($records as $record)
-                        <tr>
+                        @php
+                            $state = $record->status;
+
+                            if (!$state) {
+                                if (($record->recipients_sent_count ?? 0) > 0 && ($record->recipients_failed_count ?? 0) > 0) {
+                                    $state = 'partial';
+                                } elseif (($record->recipients_sent_count ?? 0) > 0) {
+                                    $state = 'sent';
+                                } elseif (!empty($record->schedule_at)) {
+                                    $state = 'scheduled';
+                                } else {
+                                    $state = 'draft';
+                                }
+                            }
+
+                            $badgeMap = [
+                                'draft'      => 'secondary',
+                                'processing' => 'info',
+                                'scheduled'  => 'primary',
+                                'partial'    => 'warning',
+                                'sent'       => 'success',
+                                'failed'     => 'danger',
+                            ];
+                        @endphp
+                        <tr id="row_email_{{ $record->id }}">
+                            <td>{{ $record->id }}</td>
+                            <td><strong>{{ $record->subject }}</strong></td>
+                            <td style="padding-right: 20px">{{ $record->recipients_count ?? $record->recipients()->count() }}</td>
+                            <td><span class="badge bg-{{$badgeMap[$state]}}">{{ $record->status }}</span></td>
                             <td>
-                                <strong>{{$record->name}}</strong>
+                                @if(!empty($record->schedule_at))
+                                    {{ optional($record->schedule_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}
+                                @endif
                             </td>
                             <td>
-                                {{ Str::limit($record->content, 50) }}
+                                @if(!empty($record->sent_at))
+                                    {{ optional($record->sent_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}
+                                @endif
                             </td>
-                            <td>
-                                @php
-                                    $length = strlen($record->content);
-                                    $badgeClass = $length > 160 ? 'bg-danger' : ($length > 140 ? 'bg-warning' : 'bg-success');
-                                @endphp
-                                <span class="badge {{ $badgeClass }}">{{ $length }}/160</span>
-                            </td>
-                            <td>{{ $record->created_at->format('d/m/Y H:i') }}</td>
-                            <td>
-                                <button type="button" class="btn" wire:click="sendTemplate({{ $record->id }})" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Invia SMS"><i class="fa-solid fa-paper-plane"></i></button>
-                                <button type="button" class="btn" wire:click="edit({{ $record->id }})" data-bs-toggle="popover"  data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Modifica"><i class="fa-regular fa-pen-to-square"></i></button>
+                            <td>{{ optional($record->created_at)->setTimezone('Europe/Rome')->format('d M Y - H:i') }}</td>
+                            <td class="d-flex gap-2">
+                                <button type="button" class="btn" wire:click="edit({{ $record->id }})" data-bs-toggle="tooltip" data-bs-trigger="hover focus" data-bs-placement="bottom" title="Modifica">
+                                    <i class="fa-regular fa-pen-to-square"></i>
+                                </button>
+                                <button type="button" class="btn" wire:click="duplicate({{ $record->id }})" data-bs-toggle="tooltip" data-bs-trigger="hover focus" data-bs-placement="bottom" title="Duplica">
+                                    <i class="fa-solid fa-copy"></i>
+                                </button>
+                                
+                                @if(in_array($record->status, ['draft','failed']))
+                                    <button type="button" class="btn text-danger"
+                                            onclick="if (confirm('Eliminare definitivamente questo sms?')) { Livewire.find('{{ $this->id }}').call('deleteMessage', {{ $record->id }}); }"
+                                            data-bs-toggle="tooltip" title="Elimina">
+                                        <i class="fa-solid fa-trash"></i>
+                                    </button>
+                                @endif
                             </td>
                         </tr>
                     @endforeach
                 </tbody>
             </table>
-        </section>
-
-    @else
+        </div>
+    </section>
 
+    {{-- FORM MESSAGGIO --}}
+    <section wire:key="email-form" @if(!$showForm) style="display:none" @endif>
         <div class="container">
 
-            <a class="btn--ui lightGrey" href="/sms_comunications"><i class="fa-solid fa-arrow-left"></i></a><br><br>
-
-            @if (session()->has('error'))
-                <div class="alert alert-danger" role="alert">
-                    {{ session()->get('error') }}
-                </div>
+            @if ($error)
+                <div class="alert alert-danger" role="alert">{{ $error }}</div>
+            @endif
+            @if ($success)
+                <div class="alert alert-success" role="alert">{{ $success }}</div>
             @endif
 
             <div class="row">
                 <div class="col">
 
-                    <form action="">
+                    <form>
 
-                        <div class="row mb-3">
-                            <div class="col">
-                                <div class="form--item">
-                                    <label for="subject" class="form-label">Oggetto</label>
-                                    <input type="text" class="form-control @error('subject') is-invalid @enderror" id="subject" wire:model="subject" placeholder="Inserisci l'oggetto del template">
-                                    @error('subject')
-                                        <div class="invalid-feedback">{{ $message }}</div>
-                                    @enderror
+                        {{-- Destinatari (selezionati) --}}
+                        <div class="row mb-5">
+                            <div class="col-12 mb-2">
+                                <h4>Destinatari</h4>
+                                <div class="recipients">
+                                    @if (empty($recipients))
+                                        <span>Nessun destinatario selezionato</span>
+                                    @else
+                                        @foreach ($recipients as $r)
+                                            @php
+                                                $fullName = trim(($r['last_name'] ?? '').' '.($r['first_name'] ?? ''));
+                                            @endphp
+                                            <div class="recipient">
+                                                <span class="recipient-name">{{ $fullName !== '' ? $fullName : '—' }}</span>
+                                                <span class="recipient-email">({{ $r['phone'] }})</span>
+                                            </div>
+                                        @endforeach
+                                    @endif
+                                </div>
+                                @error('recipients') <div class="invalid-feedback d-block">{{ $message }}</div> @enderror
+                            </div>
+                            <div class="col"></div>
+                            <div class="col-auto">
+                                <a style="cursor:pointer" class="addRecipients btn--ui"><i class="fa-solid fa-plus"></i></a>
+                            </div>
+                        </div>
+
+                        {{-- FILTRI + TABELLA DESTINATARI --}}
+                        <div class="row mb-5" wire:ignore id="addRecipientsRow" style="display: none">
+                            <div class="col-xs-12">
+                                <div class="showFilter" style="display: none">
+                                    <hr size="1">
+                                    <div class="row g-3">
+                                        <div class="col-md-3">
+                                            <div class="row">
+                                                <div class="col-md-12 mb-2"><b>Età</b></div>
+                                                <div class="col-12">
+                                                    <div class="row mb-2">
+                                                        <div class="col-3"><label class="form-check-label ms-2">Da</label></div>
+                                                        <div class="col-9"><input class="form-control" type="number" name="txtFromYear"></div>
+                                                    </div>
+                                                </div>
+                                                <div class="col-12">
+                                                    <div class="row">
+                                                        <div class="col-3"><label class="form-check-label ms-2">A</label></div>
+                                                        <div class="col-9"><input class="form-control" type="number" name="txtToYear"></div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+
+                                        {{-- Altri filtri come da tua UI esistente --}}
+                                        <div class="col-md-3">
+                                            <div class="row">
+                                                <div class="col-md-12 mb-2"><b>Tipologia di tesseramento</b></div>
+                                                <div class="col-12">
+                                                    <select name="filterCards" class="form-select filterCards">
+                                                        <option value="">Tutte
+                                                        @foreach(getCards() as $card)
+                                                            <option value="{{$card->id}}">{{$card->name}}
+                                                        @endforeach
+                                                    </select>
+                                                </div>
+                                            </div>
+                                        </div>
+
+                                            <div class="col-md-3">
+                                                <div class="row">
+                                                    <div class="col-md-12 mb-2"><b>Stato tesseramento</b></div>
+                                                    <div class="col-12">
+                                                        <select name="filterStatus" class="form-select filterStatus" multiple="multiple">
+                                                            <option value="2">Attivo
+                                                            <option value="1">Sospeso
+                                                            <option value="0">Non tesserato
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="col-md-3">
+                                                <div class="row">
+                                                    <div class="col-md-12 mb-2"><b>Gruppo di interesse</b></div>
+                                                    <div class="col-12">
+                                                        <select name="filterCategories" class="form-select filterCategories" multiple="multiple">
+                                                            <option value="">Tutte</option>
+                                                            @foreach($categories as $category)
+                                                                <option value="{{$category["id"]}}">
+                                                                    {!! str_repeat('&bull; ', $category["indentation"] ?? 0) !!}{{$category["name"]}}
+                                                                </option>
+                                                            @endforeach
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="col-md-3">
+                                                <div class="row">
+                                                    <div class="col-md-12 mb-2"><b>Anno di nascita</b></div>
+                                                    <div class="col-12">
+                                                        <div class="row mb-2">
+                                                            <div class="col-3"><label class="form-check-label ms-2" >Da</label></div>
+                                                            <div class="col-9"><input class="form-control " type="number" name="txtFromYearYear"></div>
+                                                        </div>
+                                                    </div>
+                                                    <div class="col-12">
+                                                        <div class="row">
+                                                            <div class="col-3"><label class="form-check-label ms-2" >A</label></div>
+                                                            <div class="col-9"><input class="form-control " type="number"  name="txtToYearYear"></div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="col-md-3">
+                                                <div class="row">
+                                                    <div class="col-md-12 mb-2"><b>Corso</b></div>
+                                                    <div class="col-12">
+                                                        <select name="filterCourses" class="form-select filterCourses" multiple="multiple">
+                                                            <option value="">Tutti</option>
+                                                            @foreach($courses as $course)
+                                                                <option value="{{ $course['id'] }}">
+                                                                    {!! str_repeat('&bull; ', $course['indentation'] ?? 0) !!}{{ $course['name'] }}
+                                                                </option>
+                                                            @endforeach
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="col-md-3">
+                                                <div class="row">
+                                                    <div class="col-md-12 mb-2"><b>Scadenza certificato medico</b></div>
+                                                    <div class="col-12">
+                                                        <select name="filterScadenza" class="form-select filterScadenza" multiple="multiple">
+                                                            <option value="1">Scaduti
+                                                            <option value="2">In scadenza
+                                                            <option value="3">Non consegnato
+                                                            <option value="4">Validi
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="col-md-3">
+                                                <div class="row">
+                                                    <div class="col-md-12 mb-2"><b>Tipologia certificato medico</b></div>
+                                                    <div class="col-12">
+                                                        <select name="filterCertificateType" class="form-select filterCertificateType" multiple="multiple">
+                                                            <option value="">Tutti
+                                                            <option value="N">Non agonistico
+                                                            <option value="A">Agonistico
+                                                        </select>
+                                                    </div>
+                                                </div>
+                                            </div>
+
+                                    </div>
+                                    <div class="row g-3">
+                                        <div class="col-md-12" style="text-align:right">
+                                            <button class="btn--ui lightGrey" onclick="resetFilters(event)">Reset</button>
+                                            <button class="btn--ui" onclick="event.preventDefault();loadDataTable()">FILTRA</button>
+                                        </div>
+                                    </div>
+                                    <hr size="1">
                                 </div>
                             </div>
+
+                            <div class="col-xs-12">
+                                <table id="recipients-table" class="table tablesaw tableHead tablesaw-stack w-100">
+                                    <thead>
+                                        <tr>
+                                            <th></th>
+                                            <th>Cognome</th>
+                                            <th>Nome</th>
+                                            <th>Email</th>
+                                            <th>Telefono</th>
+                                            <th>Età</th>
+                                            <th>Anno</th>
+                                            <th>Stato</th>
+                                            <th>Certificato</th>
+                                            <th>Gruppi</th>
+                                            {{-- <th>Corsi</th> --}}
+                                        </tr>
+                                    </thead>
+                                    <tbody></tbody>
+                                </table>
+                            </div>
                         </div>
 
-                        <div class="row mb-3">
+                        {{-- Oggetto --}}
+                        <div class="row mb-5">
                             <div class="col">
                                 <div class="form--item">
-                                    <label for="message" class="form-label">Messaggio</label>
-                                    <textarea class="form-control @error('message') is-invalid @enderror" id="message" wire:model="message" rows="4" placeholder="Inserisci il contenuto del messaggio (max 160 caratteri)" maxlength="160"></textarea>
-                                    <div class="form-text">
-                                        Caratteri: <span class="fw-bold">{{ strlen($message) }}</span>/160
-                                        @if(strlen($message) > 160)
-                                            <span class="text-danger"> - Troppi caratteri!</span>
-                                        @endif
-                                    </div>
-                                    @error('message')
-                                        <div class="invalid-feedback">{{ $message }}</div>
+                                    <h4>Oggetto</h4>
+                                    <input type="text" class="form-control @error('subject') is-invalid @enderror" id="subject" wire:model.defer="subject" placeholder="Oggetto sms" @if($locked) disabled @endif>
+                                    @error('subject')
+                                    <div class="invalid-feedback">{{ $message }}</div>
                                     @enderror
                                 </div>
                             </div>
                         </div>
 
-                        @if($add)
-                        <div class="row mb-3">
+                        {{-- Messaggio (CKEditor → content_html) --}}
+                        <div class="row mb-5">
                             <div class="col">
-                                <label class="form-label">Destinatari</label>
-                                <div class="mb-2">
-                                    <button type="button" class="btn btn-outline-primary btn-sm" onclick="selectAllUsers()">
-                                        <i class="fas fa-users me-1"></i>Seleziona Tutti
-                                    </button>
-                                    <button type="button" class="btn btn-outline-secondary btn-sm ms-2" onclick="deselectAllUsers()">
-                                        <i class="fas fa-times me-1"></i>Deseleziona Tutti
-                                    </button>
-                                </div>
-                                <div style="max-height: 200px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 0.375rem; padding: 10px;">
-                                    @foreach($members as $member)
-                                        <div class="form-check mb-1">
-                                            <input class="form-check-input" type="checkbox" value="{{ $member->id }}" wire:model="selectedRecipients" id="recipient_{{ $member->id }}">
-                                            <label class="form-check-label" for="recipient_{{ $member->id }}">
-                                                <strong>{{ $member->last_name }}</strong>
-                                                @if($member->phone)
-                                                    <small class="text-muted">({{ $member->phone }})</small>
-                                                @else
-                                                    <small class="text-danger">(no phone)</small>
-                                                @endif
-                                            </label>
-                                        </div>
-                                    @endforeach
-                                </div>
-                                <div class="form-text">
-                                    <small class="text-muted">Selezionati: <span id="selectedCount">{{ count($selectedRecipients) }}</span> utenti</small>
+                                <div class="form--item">
+                                    <h4>Messaggio</h4>
+                                    <textarea class="form-control" id="message" wire:model="content"></textarea>
+                                    @error('content')
+                                        <div class="invalid-feedback d-block">{{ $message }}</div>
+                                    @enderror
                                 </div>
-                                @error('selectedRecipients')
-                                    <div class="text-danger">{{ $message }}</div>
-                                @enderror
                             </div>
                         </div>
 
-                        <div class="row mb-3">
+                        {{-- Opzioni invio --}}
+                        <div class="row mb-5">
                             <div class="col">
-                                <label class="form-label">Opzioni di Invio</label>
-                                <div class="form-check">
-                                    <input class="form-check-input" type="radio" name="sendOption" id="sendNow" wire:model="sendNow" value="true">
-                                    <label class="form-check-label" for="sendNow">
-                                        <i class="fas fa-paper-plane me-2"></i>Invia Immediatamente
+                                <h4>Opzioni di Invio</h4>
+                                <div class="d-flex gap-3 comunication-send-options">
+                                    <label class="form-check">
+                                        <input class="form-check-input" type="radio" wire:model="mode" value="now">
+                                        <i class="fas fa-envelope me-2"></i> <span>Invia subito</span>
                                     </label>
-                                </div>
-                                <div class="form-check mt-2">
-                                    <input class="form-check-input" type="radio" name="sendOption" id="scheduleFor" wire:model="sendNow" value="false">
-                                    <label class="form-check-label" for="scheduleFor">
-                                        <i class="fas fa-clock me-2"></i>Programma per dopo
+                                    <label class="form-check">
+                                        <input class="form-check-input" type="radio" wire:model="mode" value="schedule">
+                                        <i class="fas fa-clock me-2"></i> <span>Programma</span>
                                     </label>
                                 </div>
                             </div>
                         </div>
 
-                        @if(!$sendNow)
-                        <div class="row mb-3">
-                            <div class="col-md-6">
-                                <label for="scheduledDateTime" class="form-label">Data e Ora di Invio</label>
-                                <input type="datetime-local" class="form-control @error('scheduledDateTime') is-invalid @enderror" id="scheduledDateTime" wire:model="scheduledDateTime">
-                                @error('scheduledDateTime')
-                                    <div class="invalid-feedback">{{ $message }}</div>
-                                @enderror
+                        @if(!$locked && $mode === 'schedule')
+                            <div class="row mb-5">
+                                <div class="col-md-6">
+                                    <label for="scheduledDateTime" class="form-label">Data e Ora di Invio</label>
+                                    <input type="datetime-local" class="form-control @error('schedule_at') is-invalid @enderror" id="scheduledDateTime" wire:model="schedule_at">
+                                    @error('schedule_at') <div class="invalid-feedback">{{ $message }}</div> @enderror
+                                </div>
                             </div>
-                        </div>
-                        @endif
                         @endif
 
-                        <div class="form--item">
-                            <button type="button" class="btn--ui lightGrey" wire:click="cancel()">Annulla</button>
-                            @if($add)
-                                <button type="submit" class="btn--ui" wire:click.prevent="store()">
-                                    @if($sendNow)
-                                        Salva e Invia
-                                    @else
-                                        Salva e Programma
-                                    @endif
-                                </button>
-                            @endif
-                            @if($update)
-                                <button type="submit" class="btn--ui" wire:click.prevent="update()">Salva</button>
+                        <div class="form--item mt-5 mb-5 d-flex gap-2">
+                            @if(!$locked)
+                                <a class="btn--ui lightGrey" href="/sms_comunications">Annulla</a>
+                                <button type="button" class="btn--ui" onclick="submitSMS('draft')" style="margin-right: auto">Salva bozza</button>
+                                @if($mode==='now')
+                                    <button type="button" class="btn--ui" onclick="submitSMS('send')">Invia ora</button>
+                                @else
+                                    <button type="button" class="btn--ui" onclick="submitSMS('schedule')">Salva & Programma</button>
+                                @endif
+                            @else
+                                <a class="btn--ui lightGrey" href="/sms_comunications">Torna indietro</a>
                             @endif
                         </div>
 
@@ -194,147 +369,311 @@
                 </div>
             </div>
         </div>
+    </section>
 
-    @endif
+    <input type="hidden" name="timezone" id="timezone" wire:model="timezone">
 </div>
 
 @if (session()->has('success'))
-    <div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
-        {{ session()->get('success') }}
-        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
-    </div>
+<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
+    {{ session('success') }}
+    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+</div>
 @endif
 
 @if (session()->has('error'))
-    <div class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
-        {{ session()->get('error') }}
-        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
-    </div>
+<div class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
+    {{ session('error') }}
+    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+</div>
 @endif
 
 @push('scripts')
-    <link href="/css/datatables.css" rel="stylesheet" />
-    <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
-    <script src="/assets/js/datatables.js"></script>
-    <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
+<link href="/css/datatables.css" rel="stylesheet" />
+<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+<script src="/assets/js/datatables.js"></script>
+<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
+
+<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
+<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
 @endpush
 
 @push('scripts')
-    <script>
+<script type="text/javascript">
+    document.addEventListener('livewire:load', () => {
+        if (!$.fn.DataTable.isDataTable('#tablesaw-350')) {
+            loadArchiveDataTable();
+        }
 
-        $(document).ready(function() {
-            loadDataTable();
-        });
+        window.addEventListener('sms-deleted', (e) => {
+            const id = e.detail?.id;
+            const table = $('#tablesaw-350');
+            if (!id || !$.fn.DataTable.isDataTable(table)) return;
 
-        Livewire.on('load-data-table', () => {
-            loadDataTable();
+            const dt = table.DataTable();
+            const rowEl = document.getElementById('row_email_' + id);
+            if (rowEl) {
+                dt.row(rowEl).remove().draw(false);
+            }
         });
 
-        function loadDataTable(){
-            let date = new Date();
-            let date_export = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_`;
+        const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
+        @this.set('timezone', tz);
+    });
 
-            if ($.fn.DataTable.isDataTable('#tablesaw-350')) {
-                $('#tablesaw-350').DataTable().destroy();
-            }
+    window.addEventListener('init-recipients-table', (e) => {
+        const selected = e.detail?.selected || [];
+        loadDataTable(selected);
+
+        
+        const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
+        @this.set('timezone', tz);
+    });
+
+    function loadArchiveDataTable(){
+        let date = new Date();
+        let date_export = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_`;
 
-            $('#tablesaw-350').DataTable({
-                processing: true,
-                thead: {
+        let table = $('#tablesaw-350').DataTable();
+        if ( $.fn.DataTable.isDataTable('#tablesaw-350') ) {
+            table.destroy();
+        }
+
+        $('#tablesaw-350').DataTable({
+            processing: true,
+            thead: {
                 'th': {'background-color': 'blue'}
-                },
-                layout: {
-                    topStart : null,
-                    topEnd : null,
-                    top1A: {
-                        // buttons: [
-                        //     {
-                        //         extend: 'collection',
-                        //         text: 'ESPORTA',
-                                buttons: [
-                                    {
-                                        extend: 'excelHtml5',
-                                        text: '<i class="fa-solid fa-file-excel"></i>',
-                                        action: newexportaction,
-                                        title: date_export + 'Templates SMS',
-                                        exportOptions: {
-                                            columns: ":not(':last')"
-                                        }
-                                    },
-                                    {
-                                        extend: 'pdfHtml5',
-                                        text: '<i class="fa-solid fa-file-pdf"></i>',
-                                        action: newexportaction,
-                                        title: date_export + 'Templates SMS',
+            },
+            order: [[7, 'desc']],
+            layout: {
+                topStart : null,
+                topEnd : null,
+                top1A: {
+                    // buttons: [
+                    //     {
+                    //         extend: 'collection',
+                    //         text: 'ESPORTA',
+                            buttons: [
+                                {
+                                    extend: 'excelHtml5',
+                                    text: '<i class="fa-solid fa-file-excel"></i>',
+                                    action: newexportaction,
+                                    title: date_export + 'SMS',
+                                    exportOptions: {
+                                        columns: ":not(':last')"
+                                    }
+                                },
+                                {
+                                    extend: 'pdfHtml5',
+                                    text: '<i class="fa-solid fa-file-pdf"></i>',
+                                    action: newexportaction,
+                                    title: date_export + 'SMS',
                                         exportOptions: {
                                             columns: ":not(':last')"
                                         }
                                     },
-                                    {
-                                        extend: 'print',
-                                        action: newexportaction,
-                                        text: '<i class="fa-solid fa-print"></i>',
-                                        title: date_export + 'Templates SMS',
-                                        exportOptions: {
-                                            columns: ":not(':last')"
-                                        }
+                                {
+                                    extend: 'print',
+                                    action: newexportaction,
+                                    text: '<i class="fa-solid fa-print"></i>',
+                                    title: date_export + 'SMS',
+                                    exportOptions: {
+                                        columns: ":not(':last')"
                                     }
-                                ],
-                        //         dropup: true
-                        //     }
-                        // ]
-                    },
-                    top1B : {
-                        pageLength: {
-                            menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
-                        }
-                    },
-                    top1C :'search',
+                                }
+                            ],
+                    //         dropup: true
+                    //     }
+                    // ]
                 },
-                pagingType: 'numbers',
-                "language": {
-                    "url": "/assets/js/Italian.json"
+                top1B : {
+                    pageLength: {
+                        menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
+                    }
                 },
-                "fnInitComplete": function (oSettings, json) {
-                    var html = '&nbsp;<a href="#" class="addData btn--ui"><i class="fa-solid fa-plus"></i></a>';
-                    $(".dt-search").append(html);
-                }
-            });
-            $('#tablesaw-350 thead tr th').addClass('col');
-            $('#tablesaw-350 thead tr th').css("background-color", "#f6f8fa");
+                top1C :'search',
+            },
+            pagingType: 'numbers',
+            language: {
+                "url": "/assets/js/Italian.json"
+            },
+            fnInitComplete: function (oSettings, json) {
+                var html = '&nbsp;<a href="#" class="addData btn--ui"><i class="fa-solid fa-plus"></i></a>';
+                $(".dt-search").append(html);
+            }
+        });
+
+        $('#tablesaw-350 thead tr th').addClass('col').css("background-color", "#f6f8fa");
 
-            $(document).ready(function() {
-                $(document).on("click",".addData",function() {
-                    $(".title--section_addButton").trigger("click")
-                });
+        $(document).on("click",".addData",function() {
+            @this.add();
+        });
+    }
+
+    $(document).on("click",".showHideFilter",function() {
+        $(".showFilter").toggle();
+        $('.filterCards,.filterStatus,.filterScadenza,.filterCertificateType,.filterCategories,.filterCourses').each(function(){
+            $(this).select2({
+                language: { noResults: ()=>"Nessun risultato" }
             });
+        });
+    });
 
-        }
+    $(document).on("click", ".addRecipients", function() {
+        $("#addRecipientsRow").toggle();
+    });
 
-        function selectAllUsers() {
-            $('input[wire\\:model="selectedRecipients"]').prop('checked', true).trigger('change');
-            updateSelectedCount();
-        }
+    window.resetFilters = function(event){
+        if (event) event.preventDefault();
 
-        function deselectAllUsers() {
-            $('input[wire\\:model="selectedRecipients"]').prop('checked', false).trigger('change');
-            updateSelectedCount();
-        }
+        $('.filterCards').val('').trigger('change');
+        $('.filterStatus').val('').trigger('change');
+        $('.filterScadenza').val('-1').trigger('change');
+        $('.filterCertificateType').val('-1').trigger('change');
+        $('.filterCategories').val('-1').trigger('change');
+        $('.filterCourses').val('-1').trigger('change');
+
+
+        $('input[name="txtFromYear"]').val('');
+        $('input[name="txtToYear"]').val('');
 
-        function updateSelectedCount() {
-            setTimeout(function() {
-                let count = $('input[wire\\:model="selectedRecipients"]:checked').length;
-                $('#selectedCount').text(count);
-            }, 100);
+        $('input[name="txtFromYearYear"]').val('');
+        $('input[name="txtToYearYear"]').val('');
+        loadDataTable();
+    }
+
+    function loadDataTable(preselected = []) {
+        const selectedIds = new Set((preselected || []).map(x => parseInt(x, 10)).filter(Boolean));
+
+        if ($.fn.DataTable.isDataTable('#recipients-table')) {
+            $('#recipients-table').DataTable().destroy();
         }
 
-        // Update count when checkboxes change
-        $(document).on('change', 'input[wire\\:model="selectedRecipients"]', function() {
-            updateSelectedCount();
+        var fromYear = $('input[name="txtFromYear"]').val();
+        var toYear = $('input[name="txtToYear"]').val();
+        var fromYearYear = $('input[name="txtFromYearYear"]').val();
+        var toYearYear = $('input[name="txtToYearYear"]').val();
+        var filterCards = $('.filterCards').val();
+        var filterStatus = $('.filterStatus').val();
+        var filterScadenza = $('.filterScadenza').val();
+        var filterCertificateType = $('.filterCertificateType').val();
+        var filterCategories = $('.filterCategories').val();
+        var filterCourses = $('.filterCourses').val();
+
+        const dataTable = $('#recipients-table').DataTable({
+            serverSide: true,
+            ajax: '/get_recipients?cards=' + filterCards + "&filterCategories=" + filterCategories + "&filterCertificateType=" + filterCertificateType + "&filterScadenza=" + filterScadenza + "&filterStatus=" + filterStatus + "&fromYear=" + fromYear + "&toYear=" + toYear + "&fromYearYear=" + fromYearYear + "&toYearYear=" + toYearYear + "&filterCourses=" + (filterCourses || ""),
+            columns: [
+                {
+                    orderable: false,
+                    data: "id",
+                    render: function (data){
+                        const id = parseInt(data, 10);
+                        const checked = selectedIds.has(id) ? 'checked' : '';
+                        return `<input type="checkbox" value="${id}" ${checked} onclick="toggleRecipient(${id})" id="recipient_${id}"/>`;
+                    }
+                },
+                {
+                    data: "last_name",
+                    render: function (data){
+                        const d = data.split("|");
+                        const id = d[1], value = d[0];
+                        return `<label for="recipient_${id}">${value}</label>`;
+                    }
+                },
+                {
+                    data: "first_name",
+                    render: function (data){
+                        const d = data.split("|");
+                        const id = d[1], value = d[0];
+                        return `<label for="recipient_${id}">${value}</label>`;
+                    }
+                },
+                {
+                    data: "email",
+                    render: function (data){
+                        const d = data.split("|");
+                        const id = d[1], value = d[0];
+                        return `<label for="recipient_${id}">${value}</label>`;
+                    }
+                },
+                { data: "phone"},
+                { data: "age", "type": "num", className:"dt-type-numeric"},
+                { data: "year", className:"dt-type-numeric"},
+                {
+                    data: "status",
+                    render: function (data){
+                        const d = data.split("|");
+                        return '<span class="tablesaw-cell-content"><span class="badge tessera-badge ' + d[0] + '">' + d[1] + '</span></span>';
+                    }
+                },
+                {
+                    data: "certificate",
+                    render: function (data){
+                        if (!data) return '<span class="tablesaw-cell-content d-flex align-items-center"><i class="ico--ui check absent me-2"></i>Non consegnato</span>';
+                        const d = data.split("|");
+                        const icon = d[0] === "0" ? "suspended" : (d[0] === "1" ? "due" : "active");
+                        const label = d[0] === "0" ? "Scaduto" : (d[0] === "1" ? "In scadenza" : "Scadenza");
+                        return '<span class="tablesaw-cell-content d-flex align-items-center"><i class="ico--ui check '+icon+' me-2"></i>'+label+' : '+d[1]+'</span>';
+                    }
+                },
+                { data: "categories" },
+            ],
+            order: [
+                [1, 'asc']
+            ],
+            fixedHeader: false,
+            thead: {
+                'th': {'background-color': 'blue'}
+            },
+            layout: {
+                topStart : null,
+                topEnd : null,
+                top1A: null,
+                top1B : {
+                    pageLength: {
+                        menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
+                    }
+                },
+                top1C :'search',
+            },
+            pagingType: 'numbers',
+            language: {
+                "url": "/assets/js/Italian.json"
+            },
+            fnInitComplete: function (oSettings, json) {
+                var html = '&nbsp;<a style="cursor:pointer" class="showHideFilter btn--ui"><i class="fa-solid fa-sliders"></i></a>';
+                $(".dt-search").append(html);
+            }
         });
 
-    </script>
-@endpush
+        $('#recipients-table thead tr th').addClass('col').css("background-color", "#f6f8fa");
+        $('#recipients-table').on('draw.dt', function() { $('[data-bs-toggle="popover"]').popover() });
+    }
+
+    window.toggleRecipient = function(id) { @this.toggleRecipient(id); }
+    
+
+    window.submitSMS = function(action){
+        if (action === 'draft') {
+            @this.call('saveDraft');
+        } else if (action === 'send') {
+            @this.call('sendNow');
+        } else {
+            @this.call('scheduleMessage');
+        }
+    };
+
+    window.addEventListener("scroll-top", (e) => {
+        let wrapper = document.querySelector('#card--dashboard');
+        if (wrapper) {
+            wrapper.scrollTo({top: 0, behavior: 'smooth'});
+        }
+    });
+</script>
+
+{{-- END CKEditor --}}
+@endpush

+ 318 - 0
resources/views/livewire/subscription.blade.php

@@ -0,0 +1,318 @@
+<div class="col card--ui" id="card--dashboard">
+
+    <header id="title--section" style="display:none !important"  class="d-flex align-items-center justify-content-between">
+        <div class="title--section_name d-flex align-items-center justify-content-between">
+            <i class="ico--ui title_section utenti me-2"></i>
+            <h2 class="primary">@if(!$add && !$update)Abbonamenti @else Inserimento/modifica abbonamento @endif</h2>
+        </div>
+
+        @if(!$add && !$update)
+            <div class="title--section_addButton"  wire:click="add()" style="cursor: pointer;">
+                <div class="btn--ui entrata d-flex justify-items-between">
+                    <a href="#" wire:click="add()" style="color:white">Aggiungi</a>
+                </div>
+            </div>
+        @endif
+
+    </header>
+
+
+    @if(!$add && !$update)
+
+        <a class="btn--ui lightGrey" href="/settings?type=corsi"><i class="fa-solid fa-arrow-left"></i></a><br>
+
+        <section id="resume-table">
+            <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
+                <thead>
+                    <tr>
+                        <th  style="text-align:center" scope="col"><input type="checkbox" class="checkAll"></th>
+                        <th scope="col">Nome</th>
+                        <th scope="col">N° partecipanti</th>
+                        <th scope="col">...</th>
+                    </tr>
+                </thead>
+                <tbody id="checkall-target">
+                    @foreach($records as $record)
+                        <tr>
+                            <td style="text-align:center"><input type="checkbox" class="chkCourse" name="{{$record->id}}"></td>
+                            <td>{{$record->name}}</td>
+                            <td style="padding-right: 50px">{{$record->getCount()}}</td>
+                            <td>
+                                <button type="button" class="btn" wire:click="edit({{ $record->id }})" data-bs-toggle="popover"  data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Modifica"><i class="fa-regular fa-pen-to-square"></i></button>
+                                <button type="button" class="btn" onclick="confirm('Sei sicuro?') || event.stopImmediatePropagation()" wire:click="delete({{ $record->id }})" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Elimina"><i class="fa-regular fa-trash-can"></i></button>
+                                <button type="button" class="btn" wire:click="duplicate({{ $record->id }}, false)" data-bs-toggle="popover"  data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Duplica"><i class="fa-regular fa-copy"></i></button>
+                            </td>
+                        </tr>
+                    @endforeach
+                </tbody>
+            </table>
+        </section>
+
+    @else
+
+        <a class="btn--ui lightGrey" href="/subscriptions"><i class="fa-solid fa-arrow-left"></i></a><br>
+
+        <div class="container">
+
+            @if (session()->has('error'))
+                <div class="alert alert-danger" role="alert">
+                    {{ session()->get('error') }}
+                </div>
+            @endif
+
+            <div class="row">
+                <div class="col">
+
+                    <form action="">
+
+                        <div class="row mb-3 ">
+                            <div class="col-6 mt-2">
+                                <div class="form--item">
+                                    <label for="inputName" class="form-label">Nome</label>
+                                    <input class="form-control js-keyupTitle @error('name') is-invalid @enderror" type="text" id="name" placeholder="Nome" wire:model="name">
+                                    @error('name')
+                                        <div class="invalid-feedback">{{ $message }}</div>
+                                    @enderror
+                                </div>
+                            </div>
+
+                            <div class="col-6 mt-2">
+                                <div class="form--item">
+                                    <label for="enabled" class="form-label">Abilitato</label>
+                                    <input class="form-check-input form-control" style="width:22px; height:22px;" type="checkbox" id="enabled" wire:model="enabled">
+                                </div>
+                            </div>
+                            
+                            {{-- <div class="col-3 mt-2">
+                                <div class="form--item">
+                                    <label for="inputName" class="form-label">Data inizio</label>
+                                    <input class="form-control @error('date_from') is-invalid @enderror" type="date" placeholder="Data inizio" wire:model="date_from">
+                                </div>
+                            </div>
+                            <div class="col-3 mt-2">
+                                <div class="form--item">
+                                    <label for="inputName" class="form-label">Data fine</label>
+                                    <input class="form-control @error('date_to') is-invalid @enderror" type="date" placeholder="Data fine" wire:model="date_to">
+                                </div>
+                            </div> --}}
+
+                            <div class="col-6 mt-2">
+                                <div class="form--item">
+                                    <label for="inputName" class="form-label">Prezzo iscrizione</label>
+                                    <input class="form-control js-keyupTitle @error('subscription_price') is-invalid @enderror" type="text" id="subscription_price" onkeyup="onlyNumberAmount(this)" placeholder="€ 0,00" wire:model="subscription_price">
+                                    @error('subscription_price')
+                                        <div class="invalid-feedback">{{ $message }}</div>
+                                    @enderror
+                                </div>
+                            </div>
+
+                            <div class="col-6 mt-2">
+                                @foreach($prices as $idP => $p)
+                                    <div class="row mt-2 ">
+                                        <div class="col-6">
+                                            <label for="abb" class="form-label">Pagamento</label>
+                                            <select class="form-control" wire:model="prices.{{$idP}}.course_subscription_id">
+                                                <option value=""></option>
+                                                @foreach($course_subscriptions as $s)
+                                                    <option value="{{$s["id"]}}">{{$s["name"]}}
+                                                @endforeach
+                                            </select>
+                                        </div>
+                                        <div class="col-4">
+                                            <label for="price" class="form-label">Prezzo</label>
+                                            <input class="form-control " type="text" onkeyup="onlyNumberAmount(this)" placeholder="€ 0,00" wire:model="prices.{{$idP}}.price">
+                                        </div>
+                                        <div class="col-2">
+                                            @if($idP == 0)
+                                                <br><button class="btn--ui primary add--daye" wire:click.prevent="addPrice()"><i class="fa-solid fa-plus"></i></button>
+                                            @endif
+                                            @if($idP > 0)
+                                                <br><button class="btn--ui primary add--daye" wire:click.prevent="delPrice({{$idP}})"><i class="fa-solid fa-minus"></i></button>
+                                            @endif
+                                        </div>
+                                        <div class="col-12">
+                                            <span style="color:red">{{$msgPrices}}</span>
+                                        </div>
+                                    </div>
+                                @endforeach
+                            </div>
+                        </div>
+
+                        <div class="form--item mt-4">
+                            <button type="button" class="btn--ui lightGrey" onclick="annulla()">Annulla</button>
+                            @if($add)
+                                <button type="submit" class="btn--ui" wire:click.prevent="store()">Salva</button>
+                            @endif
+                            @if($update)
+                                <button type="submit" class="btn--ui" wire:click.prevent="update()">Salva</button>
+                            @endif
+                        </div>
+
+                    </form>
+                </div>
+            </div>
+        </div>
+
+    @endif
+</div>
+
+@push('scripts')
+    <link href="/css/datatables.css" rel="stylesheet" />
+    <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+    <script src="/assets/js/datatables.js"></script>
+    <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
+@endpush
+
+@push('scripts')
+    <script>
+        function onlyNumberAmount(input) {
+            let v = input.value.replace(/\D+/g, '');
+            if (v.length > 14) v = v.slice(0, 14);
+            input.value = "€ " + v.replace(/(\d)(\d\d)$/, "$1,$2").replace(/(^\d{1,3}|\d{3})(?=(?:\d{3})+(?:,|$))/g, '$1.');
+        }
+
+        var isEdit = false;
+
+        Livewire.on('setEdit', (x) =>
+        {
+            isEdit = x;
+            console.log(isEdit);
+        });
+
+        function annulla()
+        {
+            window.onbeforeunload = null;
+            document.location.href = '/courses';
+        }
+
+        window.onbeforeunload = function(){
+            if (isEdit)
+                return 'Cambiando pagina le eventuali modifiche andranno perse';
+
+        };
+
+        $(document).ready(function() {
+            loadDataTable();
+        } );
+
+        Livewire.on('load-data-table', () => {
+            loadDataTable();
+        });
+
+        function loadDataTable(){
+            let date = new Date();
+            let date_export = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_`;
+
+            if ( $.fn.DataTable.isDataTable('#tablesaw-350') ) {
+                $('#tablesaw-350').DataTable().destroy();
+            }
+
+            $('#tablesaw-350').DataTable({
+                processing: true,
+                thead: {
+                'th': {'background-color': 'blue'}
+                },
+                layout: {
+                    topStart : null,
+                    topEnd : null,
+                    top1A: {
+                        buttons: [
+                            {
+                                extend: 'excelHtml5',
+                                text: '<i class="fa-solid fa-file-excel"></i>',
+                                action: newexportaction,
+                                title: date_export + 'Abbonamenti',
+                                exportOptions: {
+                                    columns: ":not(':last')"
+                                }
+                            },
+                            {
+                                extend: 'pdfHtml5',
+                                text: '<i class="fa-solid fa-file-pdf"></i>',
+                                action: newexportaction,
+                                title: date_export + 'Abbonamenti',
+                                exportOptions: {
+                                    columns: ":not(':last')"
+                                }
+                            },
+                            {
+                                extend: 'print',
+                                action: newexportaction,
+                                text: '<i class="fa-solid fa-print"></i>',
+                                title: date_export + 'Abbonamenti',
+                                exportOptions: {
+                                    columns: ":not(':last')"
+                                }
+                            }
+                        ],
+                    },
+                    top1B : {
+                        pageLength: {
+                            menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
+                        }
+                    },
+                    top1C :'search',
+                },
+                columnDefs: [
+                    {
+                        targets: 0,
+                        orderable: false
+                    }
+                ],
+                pagingType: 'numbers',
+                language: {
+                    url: "/assets/js/Italian.json"
+                },
+                fnInitComplete: function (oSettings, json) {
+                    var html = '&nbsp;<a href="#" class="addData btn--ui"><i class="fa-solid fa-plus"></i></a>';
+                    $(".dt-search").append(html);
+                    html = '&nbsp;<a href="#" class="duplicateData btn--ui" style="display:none"><i class="fa-solid fa-copy fa-lg" style="height:5px;"></i></a>';
+                    $(".dt-buttons").after(html);
+                }
+            });
+            $('#tablesaw-350 thead tr th').addClass('col');
+            $('#tablesaw-350 thead tr th').css("background-color", "#f6f8fa");
+
+            var subscriptions = [];
+            $(".chkCourse").click(function(){
+                var id = $(this).attr('name');
+                if(!subscriptions.includes(id)){
+                    subscriptions.push(id);
+                }else{
+                    subscriptions.splice(subscriptions.indexOf(id), 1);  //deleting
+                }
+                if (subscriptions.length > 0)
+                    $(".duplicateData").show();
+                else
+                    $(".duplicateData").hide();
+            });
+
+            $(document).ready(function() {
+                $(document).on("click",".addData",function() {
+                    $(".title--section_addButton").trigger("click");
+                });
+                $(document).on("click",".duplicateData",function() {
+                    @this.duplicateMultiple(subscriptions);
+                });
+            } );
+
+            var all = false;
+            $(".checkAll").click(function(){
+                all = !all;
+                subscriptions = [];
+                $('.chkCourse').each(function(){
+                    $(this).prop('checked', all);
+                    if (all)
+                        subscriptions.push($(this).attr('name'));
+                });
+                if (subscriptions.length > 0)
+                    $(".duplicateData").show();
+                else
+                    $(".duplicateData").hide();
+            });
+        }
+    </script>
+@endpush

+ 503 - 0
resources/views/livewire/subscription_member.blade.php

@@ -0,0 +1,503 @@
+<div class="col card--ui" id="card--dashboard">
+
+
+    <header id="title--section" style="display:none !important"
+        class="d-flex align-items-center justify-content-between">
+        <div class="title--section_name d-flex align-items-center justify-content-between">
+            <i class="ico--ui title_section utenti me-2"></i>
+            <h2 class="primary">Iscritti agli abbonamenti</h2>
+        </div>
+
+    </header>
+
+    <a class="btn--ui lightGrey" href="/subscription_member"><i class="fa-solid fa-arrow-left"></i></a><br>
+
+    @if ($subscription)
+        <h3>{{$subscription->name ?? 'Abbonamento Sconosciuto'}}</h3>
+    @endif
+
+
+    <div class="showFilter" style="display:none" wire:ignore.self>
+        <hr size="1">
+        <div class="row g-3 mt-1">
+            <div class="col-md-3">
+                <div class="row">
+                    <div class="col-md-12" style="margin-bottom:10px;">
+                        <b>Tipologia di tesseramento</b>
+                    </div>
+                    <div class="col-12">
+                        <select class="form-select filterCards" multiple="multiple">
+                            @foreach(getCards() as $card)
+                                <option value="{{$card->id}}">{{$card->name}}
+                            @endforeach
+                        </select>
+                    </div>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="row">
+                    <div class="col-md-12" style="margin-bottom:10px;">
+                        <b>Stato tesseramento</b>
+                    </div>
+                    <div class="col-12">
+                        <select class="form-select filterStatus" multiple="multiple">
+                            <option value="2">Attivo
+                            <option value="1">Sospeso
+                            <option value="0">Non tesserato
+                        </select>
+                    </div>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="row">
+                    <div class="col-md-12" style="margin-bottom:10px;">
+                        <b>Certificato medico</b>
+                    </div>
+                    <div class="col-12">
+                        <select class="form-select filterCertificateScadenza" multiple="multiple">
+                            <option value="1">Scaduti
+                            <option value="2">In scadenza
+                            <option value="3">Non presenti
+                            <option value="4">Validi
+                        </select>
+                    </div>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="row">
+                    <div class="col-md-12" style="margin-bottom:10px;">
+                        <b>Tipologia certificato medico</b>
+                    </div>
+                    <div class="col-12">
+                        <select class="form-select filterCertificateType" multiple="multiple">
+                            <option value="N">Non agonistico
+                            <option value="A">Agonistico
+                        </select>
+                    </div>
+                </div>
+            </div>
+
+        </div>
+        <div class="row g-3 mt-1">
+            <div class="col-md-3">
+                <div class="row">
+                    <div class="col-md-12" style="margin-bottom:10px;">
+                        <b>Età</b>
+                    </div>
+                    <div class="col-md-6">
+                        <div class="row align-items-center">
+                            <div class="col-auto"><label class="form-check-label ms-2">Da</label></div>
+                            <div class="col"><input class="form-control txt fromYear" type="number" min="0" step="1" name="fromYear"></div>
+                        </div>
+                    </div>
+                    <div class="col-md-6">
+                        <div class="row align-items-center">
+                            <div class="col-auto"><label class="form-check-label ms-2">A</label></div>
+                            <div class="col"><input class="form-control txt toYear" type="number" min="0" step="1" name="toYear"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="row">
+                    <div class="col-md-12" style="margin-bottom:10px;">
+                        <b>Anno di nascita</b>
+                    </div>
+                    <div class="col-md-6">
+                        <div class="row align-items-center">
+                            <div class="col-auto"><label class="form-check-label ms-2">Da</label></div>
+                            <div class="col"><input class="form-control txt fromFromYear" name="fromYearYear" type="number" min="0" step="1" name="fromFromYear"></div>
+                        </div>
+                    </div>
+                    <div class="col-md-6">
+                        <div class="row align-items-center">
+                            <div class="col-auto"><label class="form-check-label ms-2">A</label></div>
+                            <div class="col"><input class="form-control txt toToYear" type="number" min="0" step="1" name="toToYear"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="row g-3 mt-2">
+            <div class="col-md-12" style="text-align:right">
+                <button class="btn--ui_outline light" onclick="reset()">RESET</button>
+                <button class="btn--ui" onclick="loadDataTable()">FILTRA</button>
+            </div>
+        </div>
+        <hr size="1">
+    </div>
+
+    <section id="resume-table">
+        <div class="compare--chart_wrapper d-none"></div>
+
+        <div id="filter">{{$filter}}</div>
+
+        <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
+            <thead>
+                <tr>
+                    <th scope="col">#</th>
+                    <th scope="col">Cognome</th>
+                    <th scope="col">Nome</th>
+                    <th scope="col">Certificato</th>
+                    <th scope="col">Stato</th>
+                    <th scope="col">...</th>
+                </tr>
+            </thead>
+            <tbody id="checkall-target">
+
+            </tbody>
+        </table>
+
+    </section>
+</div>
+
+@push('scripts')
+    <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
+    <style>
+        .select2-container--default .select2-selection--single {
+            background-color: #E9F0F5;
+            border: 0.0625rem solid #DFE5EB;
+            font-size: 0.75rem;
+        }
+
+        .select2-selection {
+            height: 38px !important;
+        }
+
+        .select2-selection__rendered {
+            padding-top: 3px;
+        }
+
+        .select2 {
+            width: 100% !important;
+        }
+    </style>
+    <script src="https://code.jquery.com/jquery-2.2.4.min.js"
+        integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
+@endpush
+
+@push('scripts')
+    <link href="/css/datatables.css" rel="stylesheet" />
+    <script src="https://code.jquery.com/jquery-2.2.4.min.js"
+        integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+
+
+    <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
+    <style>
+        .select2-container--default .select2-selection--single {
+            background-color: #E9F0F5;
+            border: 0.0625rem solid #DFE5EB;
+            font-size: 0.75rem;
+        }
+
+        .select2-selection {
+            height: 38px !important;
+        }
+
+        .select2-selection__rendered {
+            padding-top: 3px;
+        }
+
+        .select2 {
+            width: 100% !important;
+        }
+
+        .select2-selection--multiple {
+            overflow: hidden !important;
+            height: auto !important;
+        }
+
+        .select2-container {
+            box-sizing: border-box;
+            display: inline-block;
+            margin: 0;
+            position: relative;
+            vertical-align: middle;
+        }
+
+        .select2-container .select2-selection--single {
+            box-sizing: border-box;
+            cursor: pointer;
+            display: block;
+            height: 38px;
+            user-select: none;
+            -webkit-user-select: none;
+        }
+
+        .select2-container .select2-selection--single .select2-selection__rendered {
+            display: block;
+            padding-left: 8px;
+            padding-right: 20px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+        }
+
+        .select2-selection__choice__display {
+            color: #000000 !important;
+        }
+    </style>
+    <script src="https://code.jquery.com/jquery-2.2.4.min.js"
+        integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
+    <script src="/assets/js/datatables.js"></script>
+    <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
+@endpush
+
+@push('scripts')
+    <script>
+        $(document).ready(function () {
+            loadDataTable();
+        });
+
+        $('.filterStatus').select2({ "language": { "noResults": function () { return "Nessun risultato"; } } });
+        $('.filterCertificateScadenza').select2({ "language": { "noResults": function () { return "Nessun risultato"; } } });
+        $('.filterCertificateType').select2({ "language": { "noResults": function () { return "Nessun risultato"; } } });
+        $('.filterCards').select2({ "language": { "noResults": function () { return "Nessun risultato"; } } });
+
+        Livewire.on('load-data-table', () => {
+            $('.filterStatus').select2({ "language": { "noResults": function () { return "Nessun risultato"; } } });
+            $('.filterCards').select2({ "language": { "noResults": function () { return "Nessun risultato"; } } });
+            $('.filterCertificateScadenza').select2({ "language": { "noResults": function () { return "Nessun risultato"; } } });
+            $('.filterCertificateType').select2({ "language": { "noResults": function () { return "Nessun risultato"; } } });
+            loadDataTable();
+        });
+
+        Livewire.on('destroy-data-table', () => {
+            $('#tablesaw-350').DataTable().destroy();
+        });
+
+        @if(isset($_GET["showFilters"]))
+            var filterStatus = localStorage.getItem("filterStatusSubscriptionMember");
+            if (filterStatus) {
+                $('.filterStatus').val(filterStatus).trigger('change');
+            }
+
+            var filterCards = localStorage.getItem("filterCardsSubscriptionMember");
+            if (filterCards) {
+                $('.filterCards').val(filterCards).trigger('change');
+            }
+
+            var filterCertificateScadenza = localStorage.getItem("filterCertificateScadenzaSubscriptionMember");
+            if (filterCertificateScadenza) {
+                $('.filterCertificateScadenza').val(filterCertificateScadenza).trigger('change');
+            }
+
+            var filterCertificateType = localStorage.getItem("filterCertificateTypeSubscriptionMember");
+            if (filterCertificateType) {
+                $('.filterCertificateType').val(filterCertificateType).trigger('change');
+            }
+
+            var fromYear = localStorage.getItem("fromYearSubscriptionMember");
+            if (fromYear) {
+                $('input[name="fromYear"]').val(fromYear);
+            }
+            var toYear = localStorage.getItem("toYearSubscriptionMember");
+            if (toYear) {
+                $('input[name="toYear"]').val(toYear);
+            }
+            var fromFromYear = localStorage.getItem("fromFromYearSubscriptionMember");
+            if (fromFromYear) {
+                $('input[name="fromYearYear"]').val(fromFromYear);
+            }
+
+            var toToYear = localStorage.getItem("toToYearSubscriptionMember");
+            if (toToYear) {
+                $('input[name="toToYear"]').val(toToYear);
+            }
+        @endif
+
+        function reset() {
+            $('.filterStatus').val(null).trigger("change");
+            $('.filterCards').val(null).trigger("change");
+            $('.filterCertificateScadenza').val(null).trigger("change");
+            $('.filterCertificateType').val(null).trigger("change");
+            $('.fromYear').val('');
+            $('.toYear').val('');
+            $('.fromFromYear').val('');
+            $('.toToYear').val('');
+            destroyDataTable();
+            loadDataTable();
+        }
+
+        function destroyDataTable() {
+            $('#tablesaw-350').DataTable().destroy();
+        }
+
+        var isFilter = false;
+        $(document).ready(function () {
+            $(document).on("click", ".showHideFilter", function () {
+                if (isFilter) {
+                    isFilter = false;
+                    $(".showFilter").hide();
+                }
+                else {
+                    isFilter = true;
+                    $(".showFilter").show();
+                }
+            });
+        });
+
+        function loadDataTable() {
+            if ($.fn.DataTable.isDataTable('#tablesaw-350')) {
+                $('#tablesaw-350').DataTable().destroy();
+            }
+
+            var filterStatus = $('.filterStatus').val();
+            localStorage.setItem("filterStatusSubscriptionMember", filterStatus);
+
+            var filterCards = $('.filterCards').val();
+            localStorage.setItem("filterCardsSubscriptionMember", filterCards);
+
+            var filterCertificateScadenza = $('.filterCertificateScadenza').val();
+            localStorage.setItem("filterCertificateScadenzaSubscriptionMember", filterCertificateScadenza);
+            console.log(filterCertificateScadenza);
+
+            var filterCertificateType = $('.filterCertificateType').val();
+            localStorage.setItem("filterCertificateTypeSubscriptionMember", filterCertificateType);
+
+            var fromYear = $('.fromYear').val();
+            localStorage.setItem("fromYearSubscriptionMember", fromYear);
+
+            var toYear = $('.toYear').val();
+            localStorage.setItem("toYearSubscriptionMember", toYear);
+
+            var fromFromYear = $('.fromFromYear').val();
+            localStorage.setItem("fromFromYearSubscriptionMember", fromFromYear);
+
+            var toToYear = $('.toToYear').val();
+            localStorage.setItem("toToYearSubscriptionMember", toToYear);
+
+            let date = new Date();
+            let date_export = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_`;
+
+
+            $('#tablesaw-350').DataTable({
+                serverSide: true,
+                processing: true,
+                ajax: {
+                    url: '/get_subscription_members?subscription_id=' + @this.subscription_id + '&filterStatus=' + filterStatus + '&filterCards=' + filterCards + '&filterCertificateScadenza=' + filterCertificateScadenza + '&filterCertificateType=' + filterCertificateType + '&fromYear=' + fromYear + '&toYear=' + toYear + '&fromFromYear=' + fromFromYear + '&toToYear=' + toToYear,
+                },
+                thead: {
+                    'th': { 'background-color': 'blue' }
+                },
+                columns: [
+                    { data: 'column_0' },
+                    {
+                        data: 'column_1',
+                        render: function (data, type, row) {
+                            var ret = '<a href="/members?member_detail=' + row["column_7"] + '&from=subscription_member">' + data + '</a>';
+                            return ret;
+                        }
+                    },
+                    {
+                        data: 'column_2',
+                        render: function (data, type, row) {
+                            var ret = '<a href="/members?member_detail=' + row["column_7"] + '&from=subscription_member">' + data + '</a>';
+                            return ret;
+                        }
+                    },
+                    {
+                        data: 'column_3',
+                        render: function (data, type, row) {
+                            var certInfo = data.split("|");
+                            var status = certInfo[0];
+                            var expireDate = certInfo[1] || "";
+                            var html = '<span class="tablesaw-cell-content d-flex align-items-center">';
+                            if (status === "0") {
+                                html += '<i class="ico--ui check suspended me-2"></i> ';
+                                html += 'Scaduto: ' + expireDate;
+                            } else if (status === "1") {
+                                html += '<i class="ico--ui check due me-2"></i>';
+                                html += 'In scadenza: ' + expireDate;
+                            } else if (status === "2") {
+                                html += '<i class="ico--ui check active me-2"></i>';
+                                html += 'Scadenza: ' + expireDate;
+                                html += '<span style="float:right"></span>';
+                            }else {
+                                console.log(status);
+                                html += '<i class="ico--ui check absent me-2"></i>';
+                                html += 'Non consegnato';
+                            }
+                            html += '</span>';
+                            return html;
+                        }
+                    },
+                    {
+                        data: 'column_4',
+                        render: function (data, type, row) {
+                            var html = '<span class="badge tessera-badge ' + (data == 0 ? 'due' : (data == 1 ? 'active' : 'suspended')) + '">' + (data == 0 ? 'Da pagare' : (data == 1 ? 'Pagato' : 'Sospeso')) + '</span>';
+                            return html;
+                        }
+                    },
+                    { 
+                        data: 'column_5',
+                        render: function (data, type, row) {
+                            var html = '<a href="/rates?member_id=' + row["column_7"] + '&member_subscription_id=' + data + '"><b> <i class="fa-solid fa-edit"></i></b></a>';
+                            return html;
+                        }
+                    }],
+                layout: {
+                    topStart: null,
+                    topEnd: null,
+                    top1A: {
+                        buttons: [
+                            {
+                                extend: 'excelHtml5',
+                                text: '<i class="fa-solid fa-file-excel"></i>',
+                                action: newexportaction,
+                                title: date_export + 'Iscritti abbonamenti',
+                                exportOptions: {
+                                    columns: ":not(':last')"
+                                }
+                            },
+                            {
+                                extend: 'pdfHtml5',
+                                text: '<i class="fa-solid fa-file-pdf"></i>',
+                                action: newexportaction,
+                                title: date_export + 'Iscritti abbonamenti',
+                                exportOptions: {
+                                    columns: ":not(':last')"
+                                }
+                            },
+                            {
+                                extend: 'print',
+                                action: newexportaction,
+                                text: '<i class="fa-solid fa-print"></i>',
+                                title: date_export + 'Iscritti abbonamenti',
+                                exportOptions: {
+                                    columns: ":not(':last')"
+                                }
+                            }
+                        ],
+                    },
+                    top1B: {
+                        pageLength: {
+                            menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
+                        }
+                    },
+                    top1C: 'search',
+                },
+                order: [[0, 'asc'], [1, 'asc']],
+                pagingType: 'numbers',
+                language: {
+                    url: "/assets/js/Italian.json"
+                },
+                fnInitComplete: function (oSettings, json) {
+                    var html = '&nbsp;<a href="#" class="showHideFilter btn--ui"><i class="fa-solid fa-sliders"></i></a>';
+                    $(".dt-search").append(html);
+                }
+            });
+
+            $('#tablesaw-350 thead tr th').addClass('col');
+            $('#tablesaw-350 thead tr th').css("background-color", "#f6f8fa");
+            $('#tablesaw-350').on('draw.dt', function () {
+                $('[data-bs-toggle="popover"]').popover()
+            });
+        }
+    </script>
+@endpush

+ 141 - 0
resources/views/livewire/subscription_members.blade.php

@@ -0,0 +1,141 @@
+<div class="col card--ui" id="card--dashboard">
+
+    <header id="title--section" style="display:none !important"  class="d-flex align-items-center justify-content-between">
+        <div class="title--section_name d-flex align-items-center justify-content-between">
+            <i class="ico--ui title_section utenti me-2"></i>
+            <h2 class="primary">Durata corsi</h2>
+        </div>
+
+
+    </header>
+
+    <section id="resume-table">
+        <div class="compare--chart_wrapper d-none"></div>
+
+        <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
+            <thead>
+                <tr>
+                    <th scope="col">Nome</th>
+                    <th scope="col">N° partecipanti</th>
+                    <th scope="col">...</th>
+                </tr>
+            </thead>
+            <tbody id="checkall-target">
+                @foreach($records as $record)
+                    <tr>
+                        <td>{{$record->name}}</td>
+                        <td>{{$record->getCount()}}</td>
+                        <td>
+                            <a href="/subscription_member/{{$record->id}}" type="button" class="btn" ><b> <i class="fa-solid fa-chevron-right"></i></b></button>
+                        </td>
+                    </tr>
+                @endforeach
+            </tbody>
+        </table>
+    </section>
+</div>
+
+@push('scripts')
+    <link href="/css/datatables.css" rel="stylesheet" />
+    <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+    <script src="/assets/js/datatables.js"></script>
+    <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
+@endpush
+
+@push('scripts')
+    <script>
+
+        $(document).ready(function() {
+            loadDataTable();
+        } );
+
+        Livewire.on('load-data-table', () => {
+            loadDataTable();
+        });
+
+        function loadDataTable(){
+            let date = new Date();
+            let date_export = `${date.getFullYear()}${date.getMonth()}${date.getDate()}_`;
+
+            if ( $.fn.DataTable.isDataTable('#tablesaw-350') ) {
+                $('#tablesaw-350').DataTable().destroy();
+            }
+            $('#tablesaw-350').DataTable({
+                processing: true,
+                thead: {
+                'th': {'background-color': 'blue'}
+                },
+                layout: {
+                    topStart : null,
+                    topEnd : null,
+                    top1A: {
+                        // buttons: [
+                        //     {
+                        //         extend: 'collection',
+                        //         text: 'ESPORTA',
+                                buttons: [
+                                    {
+                                        extend: 'excelHtml5',
+                                        text: '<i class="fa-solid fa-file-excel"></i>',
+                                        action: newexportaction,
+                                        title: date_export + 'Abbonamenti',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        }
+                                    },
+                                    {
+                                        extend: 'pdfHtml5',
+                                        text: '<i class="fa-solid fa-file-pdf"></i>',
+                                        action: newexportaction,
+                                        title: date_export + 'Abbonamenti',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        }
+                                    },
+                                    {
+                                        extend: 'print',
+                                        action: newexportaction,
+                                        text: '<i class="fa-solid fa-print"></i>',
+                                        title: date_export + 'Abbonamenti',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        }
+                                    }
+                                ],
+                        //         dropup: true
+                        //     }
+                        // ]
+                    },
+                    top1B : {
+                        pageLength: {
+                            menu: [[10, 25, 50, 100, 100000], [10, 25, 50, 100, "Tutti"]]
+                        }
+                    },
+                    top1C :'search',
+                },
+                pagingType: 'numbers',
+                "language": {
+                    "url": "/assets/js/Italian.json"
+                }/*,
+                "fnInitComplete": function (oSettings, json) {
+                    var html = '&nbsp;<a href="#" class="addData btn--ui"><i class="fa-solid fa-plus"></i></a>';
+                    $(".dt-search").append(html);
+                }*/
+            });
+            $('#tablesaw-350 thead tr th').addClass('col');
+            $('#tablesaw-350 thead tr th').css("background-color", "#f6f8fa");
+
+            $(document).ready(function() {
+                $(document).on("click",".addData",function() {
+                    $(".title--section_addButton").trigger("click")
+                });
+            } );
+
+        }
+
+    </script>
+@endpush
+

+ 55 - 15
resources/views/livewire/user.blade.php

@@ -142,27 +142,80 @@
                                 </div>
                             @endif
 
+                            <div class="col-md-6 mt-3">
+                                <div class="form--item">
+                                    <label for="enabled" class="form-label">Livello</label>
+                                    <select class="form-control" id="level" wire:model="level">
+                                        <option value="0">Admin
+                                        <option value="1">Worker
+                                        <option value="2">Istruttore
+                                    </select>
+                                </div>
+                            </div>
+
                             @if ($add)
                                 <div class="col-md-6 mt-3">
                                     <div class="form--item">
                                         <label for="password" class="form-label">Password</label>
-                                        <input class="form-control js-keyupTitle @error('password') is-invalid @enderror" type="password" id="password" placeholder="Password" wire:model="password">
+                                        <div class="password-wrapper">
+                                            <input class="form-control js-keyupTitle @error('password') is-invalid @enderror" type="password" id="password" placeholder="Password" wire:model="password">
+                                            <div class="password-eye" onclick="togglePassword(this)">
+                                                <i class="fas fa-eye password-hidden"></i>
+                                                <i class="fas fa-eye-slash password-shown"></i>
+                                            </div>
+                                        </div>
                                         @error('password')
                                             <div class="invalid-feedback">{{ $message }}</div>
                                         @enderror
                                     </div>
                                 </div>
+                                <div class="col-md-6 mt-3">
+                                    <div class="form--item">
+                                        <label for="password_confirmation" class="form-label">Conferma password</label>
+                                        <div class="password-wrapper">
+                                            <input class="form-control js-keyupTitle @error('password_confirmation') is-invalid @enderror" type="password" id="password_confirmation" placeholder="Ripeti password" wire:model="password_confirmation">
+                                            <div class="password-eye" onclick="togglePassword(this)">
+                                                <i class="fas fa-eye password-hidden"></i>
+                                                <i class="fas fa-eye-slash password-shown"></i>
+                                            </div>
+                                        </div>
+                                        @error('password_confirmation')
+                                            <div class="invalid-feedback">{{ $message }}</div>
+                                        @enderror
+                                    </div>
+                                </div>
                             @elseif($canEditEmailAndPassword)
                                 <div class="col-md-6 mt-3">
                                     <div class="form--item">
                                         <label for="password" class="form-label">Password</label>
-                                        <input class="form-control js-keyupTitle @error('password') is-invalid @enderror" type="password" id="password" placeholder="Password" wire:model="password">
+                                        <div class="password-wrapper">
+                                            <input class="form-control js-keyupTitle @error('password') is-invalid @enderror" type="password" id="password" placeholder="Password" wire:model="password">
+                                            <div class="password-eye" onclick="togglePassword(this)">
+                                                <i class="fas fa-eye password-hidden"></i>
+                                                <i class="fas fa-eye-slash password-shown"></i>
+                                            </div>
+                                        </div>
                                         <small>Lasciare vuota per NON modificarla</small>
                                         @error('password')
                                             <div class="invalid-feedback">{{ $message }}</div>
                                         @enderror
                                     </div>
                                 </div>
+                                <div class="col-md-6 mt-3">
+                                    <div class="form--item">
+                                        <label for="password_confirmation" class="form-label">Conferma password</label>
+                                        <div class="password-wrapper">
+                                            <input class="form-control js-keyupTitle @error('password_confirmation') is-invalid @enderror" type="password" id="password_confirmation" placeholder="Ripeti password" wire:model="password_confirmation">
+                                            <div class="password-eye" onclick="togglePassword(this)">
+                                                <i class="fas fa-eye password-hidden"></i>
+                                                <i class="fas fa-eye-slash password-shown"></i>
+                                            </div>
+                                        </div>
+                                        @error('password_confirmation')
+                                            <div class="invalid-feedback">{{ $message }}</div>
+                                        @enderror
+                                    </div>
+                                </div>
                             @else
                                 <div class="col-md-6 mt-3">
                                     <div class="form--item">
@@ -174,19 +227,6 @@
                             @endif
 
                         </div>
-                        <div class="row mt-3">
-
-                            <div class="col-md-6">
-                                <div class="form--item">
-                                    <label for="enabled" class="form-label">Livello</label>
-                                    <select class="form-control" id="level" wire:model="level">
-                                        <option value="0">Admin
-                                        <option value="1">Worker
-                                        <option value="2">Istruttore
-                                    </select>
-                                </div>
-                            </div>
-                        </div>
                         <div class="row mt-3 mb-3">
                             <div class="col-md-6">
                                 <div class="form--item">

+ 37 - 1
resources/views/login.blade.php

@@ -6,6 +6,19 @@
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Dashboard</title>
+
+  {{-- Favicon --}}
+    <link rel="icon" type="image/png" href="{{asset('favicon-96x96.png')}}" sizes="96x96" />
+    <link rel="icon" type="image/svg+xml" href="{{asset('favicon.svg')}}" />
+    <link rel="shortcut icon" href="{{asset('favicon.ico')}}"/>
+    <link rel="shortcut icon" href="{{asset('favicon-light.ico')}}" media="(prefers-color-scheme: light)"/>
+    <link rel="shortcut icon" href="{{asset('favicon-dark.ico')}}" media="(prefers-color-scheme: dark)"/>
+    <link rel="apple-touch-icon" sizes="180x180" href="{{asset('apple-touch-icon.png')}}" />
+    <link rel="manifest" href="{{asset('site.webmanifest')}}" />
+  {{-- end Favicon --}}
+
+  <!-- Font Awesome -->
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
   <link rel="stylesheet" href="https://use.typekit.net/dit2bgs.css">
   <link rel="stylesheet" href="/css/style.css">
   <link rel="stylesheet" href="/css/new_style.css">
@@ -33,7 +46,13 @@
                 </div>
                 <div class="form--item input-group mb-3">
                     <label for="password">Password</label>
-                    <input type="password" class="form-control" placeholder="Password" id="password" name="password">
+                    <div class="password-wrapper">
+                        <input type="password" class="form-control" placeholder="Password" id="password" name="password">
+                        <div class="password-eye" onclick="togglePassword(this)">
+                            <i class="fas fa-eye password-hidden"></i>
+                            <i class="fas fa-eye-slash password-shown"></i>
+                        </div>
+                    </div>
                     <span class="input-group-text" id="basic-addon2"><i class="ico--ui lock"></i></span>
                 </div>
                 <div class="form--item input-group d-flex align-items-center justify-content-center">
@@ -52,6 +71,23 @@
 
   <script src="/assets/js/bootstrap.bundle.js"></script>
   <script src="/assets/js/app.js"></script>
+
+  <script>
+    function togglePassword(eye_icon) {
+        let wrapper = eye_icon.parentElement;
+        let password_html = wrapper.querySelector("input");
+
+        if (password_html) {
+            if (password_html.type == "password") {
+                password_html.type = "text";
+                eye_icon.classList.add("shown");
+            } else {
+                password_html.type = "password";
+                eye_icon.classList.remove("shown");
+            }
+        }
+    }
+  </script>
 </body>
 
 </html>

+ 6 - 0
resources/views/receipt.blade.php

@@ -140,6 +140,12 @@
 
     @foreach($receipt->rows as $row)
         <b>Causale</b>: {{@$row->causal->getTree()}}<br><br>
+        @if ($row->course)
+            <b>Corso</b>: {{$row->course->getDetailsName()}}<br><br>
+        @endif
+        @if ($row->subscription)
+            <b>Abbonamento</b>: {{$row->subscription->name}}<br><br>
+        @endif
         @if ($row->note != '')
             <b>Dettaglio causale</b>: {{$row->note}}<br><br>
         @endif

+ 444 - 43
routes/web.php

@@ -97,7 +97,7 @@ Route::group(['middleware' => 'tenant'], function () {
     Route::get('/banks', \App\Http\Livewire\Bank::class);
     Route::get('/vats', \App\Http\Livewire\Vat::class);
     Route::get('/disciplines', \App\Http\Livewire\Discipline::class);
-    Route::get('/course_types', \App\Http\Livewire\CourseType::class);
+    // Route::get('/course_types', \App\Http\Livewire\CourseType::class);
     Route::get('/course_subscriptions', \App\Http\Livewire\CourseSubscription::class);
     Route::get('/course_durations', \App\Http\Livewire\CourseDuration::class);
     Route::get('/course_levels', \App\Http\Livewire\CourseLevel::class);
@@ -126,6 +126,10 @@ Route::group(['middleware' => 'tenant'], function () {
     Route::get('/azienda', \App\Http\Livewire\Azienda::class);
     Route::get('/sms_comunications', \App\Http\Livewire\SmsComunications::class);
     Route::get('/mail_comunications', \App\Http\Livewire\EmailComunications::class);
+    Route::get('/members_archive', \App\Http\Livewire\MemberArchive::class);
+    Route::get('/subscriptions', \App\Http\Livewire\Subscription::class);
+    Route::get('/subscription_member', \App\Http\Livewire\SubscriptionMembers::class);
+    Route::get('/subscription_member/{id}', \App\Http\Livewire\SubscriptionMember::class);
 
     Route::get('/calendar', \App\Http\Livewire\Calendar::class);
     Route::get('/presences', \App\Http\Livewire\Presence::class);
@@ -257,10 +261,17 @@ Route::group(['middleware' => 'tenant'], function () {
 
         $datas = [];
 
+        $archived = (isset($_GET['archived']) && $_GET['archived'] == 1) ? true : false;
+
         $x = \App\Models\Member::select('id', 'first_name', 'last_name', 'phone', 'birth_date', 'to_complete', 'current_status', 'certificate', 'certificate_date')
+            ->where(function($query) use($archived) {
+                $query->where('is_archived', $archived);
+                if (!$archived)
+                    $query->orWhereNull('is_archived');
+            })
             ->where(function($query) {
-                $query->where('is_archived', false)
-                    ->orWhereNull('is_archived');
+                $query->where('is_deleted', false)
+                    ->orWhereNull('is_deleted');
             });
 
         if (isset($_GET["search"]["value"]) && $_GET["search"]["value"] != "") {
@@ -361,9 +372,14 @@ Route::group(['middleware' => 'tenant'], function () {
                 } else if ($filterValue == "3") {
                     // Only non-archived members without certificates
                     $scadIds = \App\Models\Member::whereNotIn('id', \App\Models\MemberCertificate::pluck('member_id'))
+                        ->where(function($query) use($archived) {
+                            $query->where('is_archived', $archived);
+                            if (!$archived)
+                                $query->orWhereNull('is_archived');
+                        })
                         ->where(function($query) {
-                            $query->where('is_archived', false)
-                                ->orWhereNull('is_archived');
+                            $query->where('is_deleted', false)
+                                ->orWhereNull('is_deleted');
                         })
                         ->pluck('id')->toArray();
                 } else if ($filterValue == "4") {
@@ -394,9 +410,16 @@ Route::group(['middleware' => 'tenant'], function () {
         if ($_GET["filterStatus"] != "null") {
             $status = explode(",", $_GET["filterStatus"]);
             // Only get non-archived members for status filtering
-            $members = \App\Models\Member::where(function($query) {
-                $query->where('is_archived', false)
-                    ->orWhereNull('is_archived');
+            $members = \App\Models\Member::where(function($query) use($archived) {
+                $query->where(function($q) use($archived) {
+                    $q->where('is_archived', $archived);
+                    if (!$archived)
+                        $q->orWhereNull('is_archived');
+                })
+                ->where(function($q) {
+                    $q->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                });
             })->get();
 
             foreach ($status as $s) {
@@ -426,7 +449,7 @@ Route::group(['middleware' => 'tenant'], function () {
             if ($_GET["order"][0]["column"] == 2)
                 $column = 'phone';
             if ($_GET["order"][0]["column"] == 3)
-                $column = 'birth_date';
+                $column = 'years';
             if ($_GET["order"][0]["column"] == 4)
                 $column = 'birth_date';
             if ($_GET["order"][0]["column"] == 5)
@@ -443,6 +466,14 @@ Route::group(['middleware' => 'tenant'], function () {
                 elseif ($column == 'current_status') {
                     $x = $x->orderBy('to_complete', 'DESC');
                     $x = $x->orderBy($column, $_GET["order"][0]["dir"]);
+                } elseif ($column == 'years') {
+                    $column = 'birth_date';
+                    $dirs = ['asc' => 'desc', 'desc' => 'asc'];
+                    $x = $x->orderByRaw($column . ' IS NULL ' . $dirs[$_GET["order"][0]["dir"]])
+                        ->orderBy($column, $dirs[$_GET["order"][0]["dir"]]);
+                } elseif ($column == 'birth_date') {
+                    $x = $x->orderByRaw($column . ' IS NULL ' . $_GET["order"][0]["dir"])
+                        ->orderBy($column, $_GET["order"][0]["dir"]);
                 } else {
                     $x = $x->orderBy($column, $_GET["order"][0]["dir"]);
                 }
@@ -532,11 +563,14 @@ Route::group(['middleware' => 'tenant'], function () {
 
         $datas = [];
 
-        // BASE IDENTICA ALLA TUA get_members
         $x = \App\Models\Member::select('id', 'first_name', 'last_name', 'email', 'phone', 'birth_date', 'to_complete', 'current_status', 'certificate', 'certificate_date')
             ->where(function($query) {
                 $query->where('is_archived', false)
                     ->orWhereNull('is_archived');
+            })
+            ->where(function($query) {
+                $query->where('is_deleted', false)
+                    ->orWhereNull('is_deleted');
             });
 
         if (isset($_GET["search"]["value"]) && $_GET["search"]["value"] != "") {
@@ -640,6 +674,10 @@ Route::group(['middleware' => 'tenant'], function () {
                             $query->where('is_archived', false)
                                 ->orWhereNull('is_archived');
                         })
+                        ->where(function($query) {
+                            $query->where('is_deleted', false)
+                                ->orWhereNull('is_deleted');
+                        })
                         ->pluck('id')->toArray();
                 } else if ($filterValue == "4") {
                     $memberLatestCerts = DB::table('member_certificates')
@@ -669,8 +707,14 @@ Route::group(['middleware' => 'tenant'], function () {
         if ($_GET["filterStatus"] != "null") {
             $status = explode(",", $_GET["filterStatus"]);
             $members = \App\Models\Member::where(function($query) {
-                $query->where('is_archived', false)
-                    ->orWhereNull('is_archived');
+                $query->where(function($q) {
+                    $q->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($q) {
+                    $q->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                });
             })->get();
 
             foreach ($status as $s) {
@@ -722,41 +766,35 @@ Route::group(['middleware' => 'tenant'], function () {
 
         if (isset($_GET["order"])) {
             $column = '';
-            if ($_GET["order"][0]["column"] == 0)
-                $column = 'last_name';
             if ($_GET["order"][0]["column"] == 1)
-                $column = 'first_name';
+                $column = 'last_name';
             if ($_GET["order"][0]["column"] == 2)
-                $column = 'email';
+                $column = 'first_name';
             if ($_GET["order"][0]["column"] == 3)
-                $column = 'phone';
+                $column = 'email';
             if ($_GET["order"][0]["column"] == 4)
-                $column = 'birth_date';
+                $column = 'phone';
             if ($_GET["order"][0]["column"] == 5)
                 $column = 'birth_date';
             if ($_GET["order"][0]["column"] == 6)
-                $column = 'current_status';
+                $column = 'birth_date';
             if ($_GET["order"][0]["column"] == 7)
+                $column = 'current_status';
+            if ($_GET["order"][0]["column"] == 8)
                 $column = 'certificate';
 
             if ($column != '') {
-                if ($column == 'last_name') {
-                    $x = $x->orderBy('to_complete', 'DESC');
-                }
                 if ($column == 'certificate')
                     $x = $x->orderBy('certificate_date', $_GET["order"][0]["dir"]);
                 elseif ($column == 'current_status') {
-                    $x = $x->orderBy('to_complete', 'DESC');
                     $x = $x->orderBy($column, $_GET["order"][0]["dir"]);
                 } else {
                     $x = $x->orderBy($column, $_GET["order"][0]["dir"]);
                 }
             } else {
-                $x = $x->orderBy('to_complete', 'DESC');
                 $x = $x->orderBy('last_name', 'ASC')->orderBy('first_name', 'ASC');
             }
         } else {
-            $x = $x->orderBy('to_complete', 'DESC');
             $x = $x->orderBy('last_name', 'ASC')->orderBy('first_name', 'ASC');
         }
 
@@ -864,7 +902,7 @@ Route::group(['middleware' => 'tenant'], function () {
             ->leftJoin('receipts', 'records.id', '=', 'receipts.record_id')
             ->where('records.type', 'IN');
 
-        $y = \App\Models\Record::select('records_rows.amount', 'records.member_id', 'records.corrispettivo_fiscale', 'records.deleted', 'records.financial_movement', 'records_rows.causal_id', DB::raw('members.first_name as first_name'), DB::raw('members.last_name as last_name')) // , \DB::raw('SUM(records.id) As total'))
+        $y = \App\Models\Record::select('records_rows.amount', 'records.member_id', 'records.corrispettivo_fiscale', 'records.deleted', 'records.financial_movement', 'records_rows.causal_id', 'records_rows.course_id', 'records_rows.subscription_id', DB::raw('members.first_name as first_name'), DB::raw('members.last_name as last_name')) // , \DB::raw('SUM(records.id) As total'))
             ->leftJoin('members', 'records.member_id', '=', 'members.id')
             ->leftJoin('records_rows', 'records.id', '=', 'records_rows.record_id')
             //->leftJoin('receipts', 'records.id', '=', 'receipts.record_id')
@@ -947,6 +985,35 @@ Route::group(['middleware' => 'tenant'], function () {
             $x = $x->whereIn('records.id', $causals);
             $y = $y->whereIn('records.id', $causals);
         }
+        if ($_GET["filterCourses"] != "null") {
+            $hasFilter = true;
+            $courses = explode(",", $_GET["filterCourses"]);
+
+            // Per ogni corso, se ha dei figli allora aggiungo i corsi figli
+            foreach ($courses as $c) {
+                $childs = \App\Models\Course::where('parent_id', $c)->get();
+                foreach ($childs as $cc) {
+                    $courses[] = $cc->id;
+                    $childss = \App\Models\Course::where('parent_id', $cc->id)->get();
+                    foreach ($childss as $ccc) {
+                        $courses[] = $ccc->id;
+                    }
+                }
+            }
+
+            //$courses = \App\Models\RecordRow::where('course_id', $_GET["filtercourses"])->pluck('record_id');
+            $courses = \App\Models\RecordRow::whereIn('course_id', $courses)->pluck('record_id');
+            $x = $x->whereIn('records.id', $courses);
+            $y = $y->whereIn('records.id', $courses);
+        }
+        if ($_GET["filterSubscriptions"] != "null") {
+            $hasFilter = true;
+            $subscriptions = explode(",", $_GET["filterSubscriptions"]);
+
+            $subscriptions = \App\Models\RecordRow::whereIn('subscription_id', $subscriptions)->pluck('record_id');
+            $x = $x->whereIn('records.id', $subscriptions);
+            $y = $y->whereIn('records.id', $subscriptions);
+        }
         if ($_GET["filterFrom"] != '') {
             $hasFilter = true;
             $x = $x->where('records.date', '>=', $_GET["filterFrom"]);
@@ -981,7 +1048,15 @@ Route::group(['middleware' => 'tenant'], function () {
                 $excludeCausals[] = $e->id;
             }*/
 
-        $exclude_from_records = \App\Models\Member::where('exclude_from_records', true)->pluck('id')->toArray();
+        $exclude_from_records = \App\Models\Member::where('exclude_from_records', true)
+            ->where(function($query) {
+                $query->where('is_archived', false)
+                    ->orWhereNull('is_archived');
+            })
+            ->where(function($query) {
+                $query->where('is_deleted', false)
+                    ->orWhereNull('is_deleted');
+            })->pluck('id')->toArray();
 
         // Pagamento money
         $moneys = \App\Models\PaymentMethod::where('money', true)->pluck('id')->toArray();
@@ -1036,8 +1111,15 @@ Route::group(['middleware' => 'tenant'], function () {
         foreach ($x as $idx => $r) {
 
             $causals = '';
+            $courses = '';
+            $subscriptions = '';
             foreach ($r->rows as $row) {
                 $causals .= $row->causal->getTree() . "<br>";
+
+                if ($row->course)
+                    $courses .= $row->course->getDetailsName() . "<br>";
+                if ($row->subscription)
+                    $subscriptions .= $row->subscription->name . "<br>";
             }
 
             $datas[] = array(
@@ -1049,6 +1131,7 @@ Route::group(['middleware' => 'tenant'], function () {
                 'last_name' => $r->last_name,
                 'commercial' => $r->financial_movement ? 'Movimento finanziario' : ($r->commercial ? 'SI' : 'NO'),
                 'causals' => $causals,
+                'courses' => $subscriptions . $courses,
                 'payment' => $r->payment_method->name,
                 //'payment_date' => date("d/m/Y", strtotime($r->date)),
                 'status' => $r->deleted ? 'Annullato' : '',
@@ -1172,9 +1255,17 @@ Route::group(['middleware' => 'tenant'], function () {
             if ($_GET["search"]["value"] != '') {
                 $v = str_replace("'", "\'", stripcslashes($_GET["search"]["value"]));
                 $member_ids = \App\Models\Member::where(function ($query) use ($v) {
-                    $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $v . "%'")
-                        ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $v . "%'");
-                })->pluck('id');
+                        $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $v . "%'")
+                            ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $v . "%'");
+                    })
+                    ->where(function($query) {
+                        $query->where('is_archived', false)
+                            ->orWhereNull('is_archived');
+                    })
+                    ->where(function($query) {
+                        $query->where('is_deleted', false)
+                            ->orWhereNull('is_deleted');
+                    })->pluck('id');
                 /*
                     $v = str_replace("'", "\'", stripcslashes($_GET["search"]["value"]));
                     $member_ids = \App\Models\Member::where(function ($query) use ($v) {
@@ -1212,7 +1303,7 @@ Route::group(['middleware' => 'tenant'], function () {
             $course_ids = \App\Models\Course::whereIn('course_frequency_id', $frequencies)->pluck('id');
             $member_course = $member_course->whereIn('course_id', $course_ids);
         }
-        if ($_GET["filterType"] != "null") {
+        if ($_GET["filterType"] != "null" && $_GET["filterType"] != "undefined") {
             $types = explode(",", $_GET["filterType"]);
             $course_ids = \App\Models\Course::whereIn('course_type_id', $types)->pluck('id');
             $member_course = $member_course->whereIn('course_id', $course_ids);
@@ -1543,13 +1634,21 @@ Route::group(['middleware' => 'tenant'], function () {
             ->leftJoin('courses', 'member_courses.course_id', '=', 'courses.id')
             ->leftJoin('members', 'member_courses.member_id', '=', 'members.id');
 
-        if (isset($_GET["search"]["value"])) {
+        if (isset($_GET["search"]["value"]) && trim($_GET["search"]["value"]) != "") {
             $v = str_replace("'", "\'", stripcslashes($_GET["search"]["value"]));
 
             $member_ids = \App\Models\Member::where(function ($query) use ($v) {
-                $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $v . "%'")
-                    ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $v . "%'");
-            })->pluck('id');
+                    $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $v . "%'")
+                        ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $v . "%'");
+                })
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
 
             /*$member_ids = \App\Models\Member::where(function ($query) use ($v) {
                     $query->where('first_name', 'like', '%' . $v . '%')
@@ -1583,7 +1682,7 @@ Route::group(['middleware' => 'tenant'], function () {
             $course_ids = \App\Models\Course::whereIn('course_frequency_id', $frequencies)->pluck('id');
             $datas = $datas->whereIn('course_id', $course_ids);
         }
-        if ($_GET["filterType"] != "null") {
+        if ($_GET["filterType"] != "null" && $_GET["filterType"] != "undefined") {
             $types = explode(",", $_GET["filterType"]);
             $course_ids = \App\Models\Course::whereIn('course_type_id', $types)->pluck('id');
             $datas = $datas->whereIn('course_id', $course_ids);
@@ -1662,6 +1761,14 @@ Route::group(['middleware' => 'tenant'], function () {
                     $allScadIds = array_merge($allScadIds, $expiringMemberIds);
                 } else if ($filterValue == "3") {
                     $scadIds = \App\Models\Member::whereNotIn('id', \App\Models\MemberCertificate::pluck('member_id'))
+                        ->where(function($query) {
+                            $query->where('is_archived', false)
+                                ->orWhereNull('is_archived');
+                        })
+                        ->where(function($query) {
+                            $query->where('is_deleted', false)
+                                ->orWhereNull('is_deleted');
+                        })
                         ->pluck('id')
                         ->toArray();
                     $allScadIds = array_merge($allScadIds, $scadIds);
@@ -1693,21 +1800,53 @@ Route::group(['middleware' => 'tenant'], function () {
         }
 
         if ($_GET["fromYear"] != "") {
-            $m_ids = \App\Models\Member::where('birth_date', '<', date("Y-m-d", strtotime("-" . $_GET["fromYear"] . " year", time())))->pluck('id');
+            $m_ids = \App\Models\Member::where('birth_date', '<', date("Y-m-d", strtotime("-" . $_GET["fromYear"] . " year", time())))
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
             $datas = $datas->whereIn('member_id', $m_ids);
         }
 
         if ($_GET["toYear"] != "") {
-            $m_ids = \App\Models\Member::where('birth_date', '>', date("Y-m-d", strtotime("-" . $_GET["toYear"] . " year", time())))->pluck('id');
+            $m_ids = \App\Models\Member::where('birth_date', '>', date("Y-m-d", strtotime("-" . $_GET["toYear"] . " year", time())))
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
             $datas = $datas->whereIn('member_id', $m_ids);
         }
 
         if ($_GET["fromFromYear"] != "") {
-            $m_ids = \App\Models\Member::whereYear('birth_date', '>=', $_GET["fromFromYear"])->pluck('id');
+            $m_ids = \App\Models\Member::whereYear('birth_date', '>=', $_GET["fromFromYear"])
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
             $datas = $datas->whereIn('member_id', $m_ids);
         }
         if ($_GET["toToYear"] != "") {
-            $m_ids = \App\Models\Member::whereYear('birth_date', '<=', $_GET["toToYear"])->pluck('id');
+            $m_ids = \App\Models\Member::whereYear('birth_date', '<=', $_GET["toToYear"])
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
             $datas = $datas->whereIn('member_id', $m_ids);
         }
         if ($_GET["filterCards"] != "null") {
@@ -1844,6 +1983,260 @@ Route::group(['middleware' => 'tenant'], function () {
         return json_encode(array("data" => $ret, "recordsTotal" => sizeof($aRet), "recordsFiltered" => sizeof($aRet)));
     });
 
+    Route::get('/get_subscription_members', function () {
+        $subscription_id = 0;
+        if (!isset($_GET['subscription_id']) || is_null($_GET['subscription_id']) || $_GET['subscription_id'] <= 0) {
+            return json_encode(array("data" => [], "recordsTotal" => 0, "recordsFiltered" => 0));
+        }
+        $subscription_id = $_GET['subscription_id'];
+
+        $datas = \App\Models\MemberSubscription::select('member_subscriptions.*', 'subscriptions.name as course_name', 'members.first_name', 'members.last_name', 'members.birth_date')
+            ->leftJoin('subscriptions', 'member_subscriptions.subscription_id', '=', 'subscriptions.id')
+            ->leftJoin('members', 'member_subscriptions.member_id', '=', 'members.id')
+            ->where('member_subscriptions.subscription_id', $subscription_id);
+
+        if (isset($_GET["search"]["value"]) && trim($_GET["search"]["value"]) != "") {
+            $v = str_replace("'", "\'", stripcslashes($_GET["search"]["value"]));
+
+            $member_ids = \App\Models\Member::where(function ($query) use ($v) {
+                    $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $v . "%'")
+                        ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $v . "%'");
+                })
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
+
+            $datas = $datas->whereIn('member_id', $member_ids);
+        }
+
+        if ($_GET["filterCertificateScadenza"] != "null") {
+            $scadenzaValues = explode(",", $_GET["filterCertificateScadenza"]);
+            $allScadIds = [];
+
+            foreach ($scadenzaValues as $filterValue) {
+                if ($filterValue == "1") {
+                    $memberLatestCerts = DB::table('member_certificates')
+                        ->select('member_id', DB::raw('MAX(expire_date) as latest_expire_date'))
+                        ->groupBy('member_id')
+                        ->get();
+
+                    $expiredMemberIds = [];
+                    foreach ($memberLatestCerts as $cert) {
+                        if (date('Y-m-d', strtotime($cert->latest_expire_date)) < date('Y-m-d')) {
+                            $expiredMemberIds[] = $cert->member_id;
+                        }
+                    }
+                    $allScadIds = array_merge($allScadIds, $expiredMemberIds);
+
+                    Log::info("Course members - Expired certificates filter - member count: " . count($expiredMemberIds));
+                } else if ($filterValue == "2") {
+                    $memberLatestCerts = DB::table('member_certificates')
+                        ->select('member_id', DB::raw('MAX(expire_date) as latest_expire_date'))
+                        ->groupBy('member_id')
+                        ->get();
+
+                    $expiringMemberIds = [];
+                    foreach ($memberLatestCerts as $cert) {
+                        $expireDate = date('Y-m-d', strtotime($cert->latest_expire_date));
+                        $today = date('Y-m-d');
+                        $oneMonthLater = date('Y-m-d', strtotime("+1 month"));
+
+                        if ($expireDate >= $today && $expireDate <= $oneMonthLater) {
+                            $expiringMemberIds[] = $cert->member_id;
+                        }
+                    }
+                    $allScadIds = array_merge($allScadIds, $expiringMemberIds);
+                } else if ($filterValue == "3") {
+                    $scadIds = \App\Models\Member::whereNotIn('id', \App\Models\MemberCertificate::pluck('member_id'))
+                        ->where(function($query) {
+                            $query->where('is_archived', false)
+                                ->orWhereNull('is_archived');
+                        })
+                        ->where(function($query) {
+                            $query->where('is_deleted', false)
+                                ->orWhereNull('is_deleted');
+                        })
+                        ->pluck('id')
+                        ->toArray();
+                    $allScadIds = array_merge($allScadIds, $scadIds);
+                } else if ($filterValue == "4") {
+                    $memberLatestCerts = DB::table('member_certificates')
+                        ->select('member_id', DB::raw('MAX(expire_date) as latest_expire_date'))
+                        ->groupBy('member_id')
+                        ->get();
+
+                    $validMemberIds = [];
+                    foreach ($memberLatestCerts as $cert) {
+                        $expireDate = date('Y-m-d', strtotime($cert->latest_expire_date));
+                        $oneMonthLater = date('Y-m-d', strtotime("+1 month"));
+
+                        if ($expireDate > $oneMonthLater) {
+                            $validMemberIds[] = $cert->member_id;
+                        }
+                    }
+                    $allScadIds = array_merge($allScadIds, $validMemberIds);
+                }
+            }
+
+            // Remove duplicates
+            $allScadIds = array_unique($allScadIds);
+            Log::info("Course members - Total members after filterCertificateScadenza: " . count($allScadIds));
+
+            // Apply filter using the collected IDs
+            $datas = $datas->whereIn('member_id', $allScadIds);
+        }
+
+        if ($_GET["fromYear"] != "") {
+            $m_ids = \App\Models\Member::where('birth_date', '<', date("Y-m-d", strtotime("-" . $_GET["fromYear"] . " year", time())))
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+
+        if ($_GET["toYear"] != "") {
+            $m_ids = \App\Models\Member::where('birth_date', '>', date("Y-m-d", strtotime("-" . $_GET["toYear"] . " year", time())))
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+
+        if ($_GET["fromFromYear"] != "") {
+            $m_ids = \App\Models\Member::whereYear('birth_date', '>=', $_GET["fromFromYear"])
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+
+        if ($_GET["toToYear"] != "") {
+            $m_ids = \App\Models\Member::whereYear('birth_date', '<=', $_GET["toToYear"])
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
+            $datas = $datas->whereIn('member_id', $m_ids);
+        }
+
+        if ($_GET["filterCards"] != "null") {
+            $cards = explode(",", $_GET["filterCards"]);
+            $card_ids = \App\Models\MemberCard::whereIn('card_id', $cards)->pluck('member_id');
+            $datas = $datas->whereIn('member_id', $card_ids);
+        }
+
+        $aRet = [];
+        if (isset($_GET["order"])) {
+            $column = '';
+            if ($_GET["order"][0]["column"] == 1)
+                $column = 'last_name';
+            if ($_GET["order"][0]["column"] == 2)
+                $column = 'first_name';
+            if ($_GET["order"][0]["column"] == 3)
+                $column = 'certificate_expire_date';
+            // if ($_GET["order"][0]["column"] == 4)
+            //     $column = 'gender';
+            if ($column != '')
+                $datas = $datas->orderBy($column, $_GET["order"][0]["dir"]);
+            else
+                $datas = $datas->orderBy('last_name', 'ASC')->orderBy('first_name', 'ASC');
+        } else
+            $datas = $datas->orderBy('last_name', 'ASC')->orderBy('first_name', 'ASC');
+
+        if ($_GET["filterStatus"] != "null") {
+            $status = explode(",", $_GET["filterStatus"]);
+            foreach ($status as $s) {
+                foreach ($datas->get() as $aaa) {
+
+                    $state = \App\Models\Member::findOrFail($aaa->member_id)->isActive();
+                    if ($state["status"] == $s)
+                        $aRet[] = $aaa;
+                }
+            }
+        } else
+            $aRet = $datas->get();
+
+        $ret = [];
+        foreach ($aRet as $idx => $r) {
+            $certificate = \App\Models\MemberCertificate::where('member_id', $r->member_id)
+                ->orderBy('expire_date', 'desc')
+                ->first();
+
+            $certificateInfo = "";
+            if ($certificate) {
+                // Format: status|expire_date|type
+                // Status: 0 = expired, 1 = expiring soon, 2 = valid
+                $today = new DateTime();
+                $expireDate = new DateTime($certificate->expire_date);
+                $oneMonthFromNow = (new DateTime())->modify('+1 month');
+
+                $status = "2"; // Default to valid
+                if ($expireDate < $today) {
+                    $status = "0"; // Expired
+                } elseif ($expireDate < $oneMonthFromNow) {
+                    $status = "1"; // Expiring soon
+                }
+
+                $certificateInfo = $status . "|" . date("d/m/Y", strtotime($certificate->expire_date)) . "|" . $certificate->type;
+            }
+
+            $status = 0;
+            $payed = 0;
+            $rates = \App\Models\Rate::where('member_course_id', $r->id)->where('date', '<', date("Y-m-d"))->get();
+            foreach($rates as $rate)
+            {
+                if ($rate->date > date("Y-m-d"))
+                {
+                    break;
+                }
+                $status = $rate->status;
+                if ($rate->status == 1)
+                    $payed += 1;
+            }
+            if ($status == 1 && $payed != sizeof($rates))
+                $status = 0;
+
+            $ret[] = array(
+                "column_0" => $idx + 1,
+                "column_1" => $r->last_name,
+                "column_2" => $r->first_name,
+                "column_3" => $certificateInfo,
+                "column_4" => $status,
+                "column_5" => $r->id,
+                "column_7" => $r->member_id,
+            );
+        }
+
+        if (isset($_GET["start"]))
+            $ret = array_slice($ret, $_GET["start"], $_GET["length"]);
+
+        return json_encode(array("data" => $ret, "recordsTotal" => sizeof($aRet), "recordsFiltered" => sizeof($aRet)));
+    });
+
     Route::get('/get_receipts', function () {
         $baseQuery = \App\Models\Receipt::select('receipts.id')
             ->leftJoin('members', 'receipts.member_id', '=', 'members.id');
@@ -1851,9 +2244,17 @@ Route::group(['middleware' => 'tenant'], function () {
         if (isset($_GET["search"]["value"]) && !empty($_GET["search"]["value"])) {
             $v = str_replace("'", "\'", stripcslashes($_GET["search"]["value"]));
             $member_ids = \App\Models\Member::where(function ($query) use ($v) {
-                $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $v . "%'")
-                    ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $v . "%'");
-            })->pluck('id');
+                    $query->whereRaw("CONCAT(first_name, ' ', last_name) like '%" . $v . "%'")
+                        ->orWhereRaw("CONCAT(last_name, ' ', first_name) like '%" . $v . "%'");
+                })
+                ->where(function($query) {
+                    $query->where('is_archived', false)
+                        ->orWhereNull('is_archived');
+                })
+                ->where(function($query) {
+                    $query->where('is_deleted', false)
+                        ->orWhereNull('is_deleted');
+                })->pluck('id');
             $baseQuery = $baseQuery->whereIn('receipts.member_id', $member_ids);
         }
 

Некоторые файлы не были показаны из-за большого количества измененных файлов