Переглянути джерело

Merge branch 'multi_db' of http://host.webmagistri.biz:3000/parisio/iao_team into ferrari

ferrari 6 місяців тому
батько
коміт
fdc468af92
100 змінених файлів з 7128 додано та 685 видалено
  1. 125 0
      app/Console/Commands/CleanupPasswordResets.php
  2. 26 0
      app/Console/Commands/MigrateMaster.php
  3. 30 0
      app/Console/Commands/MigrateTenants.php
  4. 102 0
      app/Console/Commands/ProcessScheduledEmailCommand.php
  5. 1 1
      app/Console/Kernel.php
  6. 17 0
      app/Database/Migrations/MasterMigration.php
  7. 17 0
      app/Database/Migrations/TenantMigration.php
  8. 222 0
      app/Http/Controllers/FirstLoginController.php
  9. 482 0
      app/Http/Controllers/PasswordResetController.php
  10. 138 0
      app/Http/Controllers/SmsTemplateController.php
  11. 2 0
      app/Http/Kernel.php
  12. 5 1
      app/Http/Livewire/Azienda.php
  13. 8 4
      app/Http/Livewire/Bank.php
  14. 8 3
      app/Http/Livewire/Card.php
  15. 7 0
      app/Http/Livewire/Categories.php
  16. 8 2
      app/Http/Livewire/Category.php
  17. 104 33
      app/Http/Livewire/Causal.php
  18. 60 27
      app/Http/Livewire/Causals.php
  19. 6 3
      app/Http/Livewire/City.php
  20. 11 8
      app/Http/Livewire/Course.php
  21. 8 4
      app/Http/Livewire/CourseDuration.php
  22. 8 4
      app/Http/Livewire/CourseFrequency.php
  23. 8 3
      app/Http/Livewire/CourseLevel.php
  24. 6 2
      app/Http/Livewire/CourseList.php
  25. 6 1
      app/Http/Livewire/CourseMember.php
  26. 7 2
      app/Http/Livewire/CourseSubscription.php
  27. 9 4
      app/Http/Livewire/CourseType.php
  28. 7 1
      app/Http/Livewire/Courses.php
  29. 736 98
      app/Http/Livewire/Dashboard.php
  30. 8 3
      app/Http/Livewire/Discipline.php
  31. 206 0
      app/Http/Livewire/EmailComunications.php
  32. 189 0
      app/Http/Livewire/FirstLogin.php
  33. 76 127
      app/Http/Livewire/Member.php
  34. 46 24
      app/Http/Livewire/PaymentMethod.php
  35. 179 15
      app/Http/Livewire/Profile.php
  36. 1 1
      app/Http/Livewire/Province.php
  37. 3 3
      app/Http/Livewire/Rate.php
  38. 5 0
      app/Http/Livewire/Record.php
  39. 87 5
      app/Http/Livewire/RecordIN.php
  40. 7 0
      app/Http/Livewire/RecordINOUT.php
  41. 3 2
      app/Http/Livewire/RecordOUT.php
  42. 0 32
      app/Http/Livewire/Reminder.php
  43. 25 0
      app/Http/Livewire/Reports.php
  44. 200 0
      app/Http/Livewire/SmsComunications.php
  45. 3 2
      app/Http/Livewire/Sponsor.php
  46. 46 9
      app/Http/Livewire/Supplier.php
  47. 624 36
      app/Http/Livewire/User.php
  48. 5 1
      app/Http/Livewire/Vat.php
  49. 28 0
      app/Http/Middleware/CheckFirstLoginCompleted.php
  50. 25 0
      app/Http/Middleware/PasswordResetThrottle.php
  51. 60 0
      app/Http/Middleware/TenantMiddleware.php
  52. 6 0
      app/Jobs/ProcessRecordAttachment.php
  53. 32 0
      app/Mail/CustomEmail.php
  54. 1 0
      app/Models/Causal.php
  55. 55 0
      app/Models/DashboardNote.php
  56. 96 0
      app/Models/EmailScheduled.php
  57. 59 0
      app/Models/EmailScheduledRecipient.php
  58. 41 0
      app/Models/EmailTemplate.php
  59. 3 1
      app/Models/Member.php
  60. 1 0
      app/Models/PaymentMethod.php
  61. 41 0
      app/Models/SmsScheduled.php
  62. 31 0
      app/Models/SmsTemplate.php
  63. 2 1
      app/Models/Supplier.php
  64. 8 3
      app/Models/User.php
  65. 24 0
      app/Providers/MigrationServiceProvider.php
  66. 252 0
      app/Services/MigrationService.php
  67. 72 0
      app/Services/MultiTenantAuthService.php
  68. 2 2
      app/helpers.php
  69. 34 0
      database/migrations/2025_06_06_153500_create_courts_table.php
  70. 42 0
      database/migrations/2025_06_06_153700_create_calendars_table.php
  71. 39 0
      database/migrations/2025_06_06_154000_create_presences_table.php
  72. 21 0
      database/migrations/2025_06_20_084530_create_password_resets_table.php
  73. 24 0
      database/migrations/2025_06_20_122026_add_first_login_to_users_table_master.php
  74. 23 0
      database/migrations/2025_06_20_122038_add_first_login_to_users_table_master_tenant.php
  75. 32 0
      database/migrations/2025_06_23_075223_is_archived_field_member_table.php
  76. 32 0
      database/migrations/2025_06_23_080139_archived_date_field_member_table.php
  77. 34 0
      database/migrations/2025_07_02_155307_add_is_hidden_to_causals_table.php
  78. 34 0
      database/migrations/2025_07_02_161922_add_is_hidden_to_payment_methods_table.php
  79. 33 0
      database/migrations/2025_07_04_075532_add_archived_to_suppliers_table.php
  80. 37 0
      database/migrations/2025_07_04_102429_add_dashboard_note_table.php
  81. 26 0
      database/migrations/2025_07_11_090151_create_mail_templates_table.php
  82. 26 0
      database/migrations/2025_07_11_090151_create_sms_templates_table.php
  83. 31 0
      database/migrations/2025_07_11_090201_create_mail_scheduled_table.php
  84. 29 0
      database/migrations/2025_07_11_090201_create_sms_scheduled_table.php
  85. 31 0
      database/migrations/2025_07_11_090208_create_mail_scheduled_recipients_table.php
  86. 27 0
      database/migrations/2025_07_11_090208_create_sms_scheduled_recipients_table.php
  87. 71 0
      resources/views/auth/password-reset-form.blade.php
  88. 53 0
      resources/views/auth/password-reset-request.blade.php
  89. 94 0
      resources/views/emails/account-activated.blade.php
  90. 51 0
      resources/views/emails/custom.blade.php
  91. 80 0
      resources/views/emails/password-changed.blade.php
  92. 103 0
      resources/views/emails/password-reset.blade.php
  93. 139 0
      resources/views/emails/welcome-user.blade.php
  94. 231 0
      resources/views/first-login.blade.php
  95. 4 0
      resources/views/layouts/app.blade.php
  96. 29 5
      resources/views/livewire/causal.blade.php
  97. 26 27
      resources/views/livewire/causal_child.blade.php
  98. 640 120
      resources/views/livewire/dashboard.blade.php
  99. 306 0
      resources/views/livewire/email_comunications.blade.php
  100. 20 65
      resources/views/livewire/member.blade.php

+ 125 - 0
app/Console/Commands/CleanupPasswordResets.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace App\Console\Commands;
+
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\Log;
+
+class CleanupPasswordResets extends Command
+{
+    protected $signature = 'password:cleanup';
+    protected $description = 'Clean up expired password reset tokens';
+
+    public function handle()
+    {
+        try {
+            $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_cleanup' => $masterConfig]);
+
+            // Delete tokens older than 24 hours
+            $deletedCount = DB::connection('master_cleanup')
+                ->table('password_resets')
+                ->where('created_at', '<', Carbon::now()->subHours(24))
+                ->delete();
+
+            DB::purge('master_cleanup');
+
+            $this->info("Cleaned up {$deletedCount} expired password reset tokens.");
+
+        } catch (\Exception $e) {
+            $this->error("Failed to cleanup password reset tokens: " . $e->getMessage());
+        }
+    }
+}
+
+// ENHANCED AUTHENTICATION FUNCTION
+// Update your existing authentication function to handle password resets
+
+function authenticateUser($email, $password)
+{
+    try {
+        // Step 1: Get user from master database (current default connection)
+        $masterUser = DB::table('users')->where('email', $email)->first();
+
+        if (!$masterUser) {
+            Log::info('User not found in master database', ['email' => $email]);
+            return false;
+        }
+
+        // Step 2: Check password in master database
+        if (!Hash::check($password, $masterUser->password)) {
+            Log::info('Password incorrect in master database', ['email' => $email]);
+            return false;
+        }
+
+        // Step 3: Set up tenant connection
+        $tenantConfig = [
+            'driver' => 'mysql',
+            'host' => env('DB_HOST', '127.0.0.1'),
+            'port' => env('DB_PORT', '3306'),
+            'database' => $masterUser->tenant_database,
+            'username' => $masterUser->tenant_username,
+            'password' => $masterUser->tenant_password,
+            'charset' => 'utf8mb4',
+            'collation' => 'utf8mb4_unicode_ci',
+            'prefix' => '',
+            'strict' => true,
+            'engine' => null,
+        ];
+
+        // Add tenant connection to config
+        config(['database.connections.tenant_check' => $tenantConfig]);
+
+        // Step 4: Check user in tenant database
+        $tenantUser = DB::connection('tenant_check')->table('users')->where('email', $email)->first();
+
+        if (!$tenantUser) {
+            Log::info('User not found in tenant database', [
+                'email' => $email,
+                'tenant_db' => $masterUser->tenant_database
+            ]);
+            return false;
+        }
+
+        // Step 5: Check password in tenant database
+        if (!Hash::check($password, $tenantUser->password)) {
+            Log::info('Password incorrect in tenant database', [
+                'email' => $email,
+                'tenant_db' => $masterUser->tenant_database
+            ]);
+            return false;
+        }
+
+        Log::info('Authentication successful in both databases', [
+            'email' => $email,
+            'tenant_db' => $masterUser->tenant_database
+        ]);
+
+        return $masterUser;
+
+    } catch (\Exception $e) {
+        Log::error('Authentication error', [
+            'email' => $email,
+            'error' => $e->getMessage(),
+            'trace' => $e->getTraceAsString()
+        ]);
+        return false;
+    }
+}

+ 26 - 0
app/Console/Commands/MigrateMaster.php

@@ -0,0 +1,26 @@
+<?php
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Services\MigrationService;
+
+class MigrateMaster extends Command
+{
+    protected $signature = 'migrate:master {--rollback} {--step=1}';
+    protected $description = 'Run migrations on master database only';
+
+    public function handle(MigrationService $migrationService)
+    {
+        if ($this->option('rollback')) {
+            $result = $migrationService->rollbackMasterMigrations($this->option('step'));
+            $this->info($result['message']);
+            $this->line($result['output']);
+        } else {
+            $result = $migrationService->runMasterMigrations();
+            $this->info($result['message']);
+            if (!empty($result['migrations'])) {
+                $this->table(['Master Migrations'], array_map(fn($m) => [$m], $result['migrations']));
+            }
+        }
+    }
+}

+ 30 - 0
app/Console/Commands/MigrateTenants.php

@@ -0,0 +1,30 @@
+<?php
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Services\MigrationService;
+
+class MigrateTenants extends Command
+{
+    protected $signature = 'migrate:tenants {--rollback} {--step=1}';
+    protected $description = 'Run migrations on all tenant databases';
+
+    public function handle(MigrationService $migrationService)
+    {
+        if ($this->option('rollback')) {
+            $result = $migrationService->rollbackTenantMigrations($this->option('step'));
+        } else {
+            $result = $migrationService->runTenantMigrations();
+        }
+
+        $this->info($result['message']);
+
+        if (!empty($result['results'])) {
+            $tableData = [];
+            foreach ($result['results'] as $r) {
+                $tableData[] = [$r['tenant'], $r['status'], $r['message']];
+            }
+            $this->table(['Tenant Database', 'Status', 'Message'], $tableData);
+        }
+    }
+}

+ 102 - 0
app/Console/Commands/ProcessScheduledEmailCommand.php

@@ -0,0 +1,102 @@
+<?php
+namespace App\Console\Commands;
+
+use App\Models\EmailScheduled;
+use App\Models\EmailScheduledRecipient;
+use App\Mail\CustomEmail;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Mail;
+use Carbon\Carbon;
+
+class ProcessScheduledEmailCommand extends Command
+{
+    protected $signature = 'email:process-scheduled';
+    protected $description = 'Process scheduled email messages that are due to be sent';
+
+    public function handle()
+    {
+        $this->info('🔍 Checking for scheduled Email messages...');
+
+        $dueMessages = EmailScheduled::where('status', 'scheduled')
+            ->where('scheduled_at', '<=', now())
+            ->with('recipients')
+            ->get();
+
+        if ($dueMessages->isEmpty()) {
+            $this->info('✅ No scheduled emails due for sending.');
+            return 0;
+        }
+
+        $this->info("📧 Found {$dueMessages->count()} email batches due for sending.");
+
+        foreach ($dueMessages as $message) {
+            $this->info("📤 Processing email batch ID: {$message->id}");
+
+            try {
+                $message->update(['status' => 'sending']);
+
+                $successCount = 0;
+                $failCount = 0;
+
+                foreach ($message->recipients as $recipient) {
+                    if ($recipient->email) {
+                        $recipientRecord = EmailScheduledRecipient::create([
+                            'email_scheduled_id' => $message->id,
+                            'user_id' => $recipient->id,
+                            'email_address' => $recipient->email,
+                            'status' => 'pending'
+                        ]);
+
+                        try {
+                            Mail::to($recipient->email)->send(
+                                new CustomEmail($message->subject, $message->content, $recipient->name)
+                            );
+
+                            $recipientRecord->markAsSent();
+                            $successCount++;
+                            $this->line("   ✅ Sent to {$recipient->name} ({$recipient->email})");
+
+                            usleep(100000);
+
+                        } catch (\Exception $e) {
+                            $recipientRecord->markAsFailed($e->getMessage());
+                            $failCount++;
+                            $this->line("   ❌ Failed to {$recipient->name} ({$recipient->email}): " . $e->getMessage());
+                        }
+                    } else {
+                        $failCount++;
+                        $this->line("   ⚠️  No email address for {$recipient->name}");
+                    }
+                }
+
+                // Update final status
+                $finalStatus = $failCount === 0 ? 'sent' : ($successCount === 0 ? 'failed' : 'sent');
+
+                $message->update([
+                    'status' => $finalStatus,
+                    'delivery_report' => [
+                        'total' => $successCount + $failCount,
+                        'successful' => $successCount,
+                        'failed' => $failCount,
+                        'completed_at' => now()
+                    ]
+                ]);
+
+                $this->info("✅ Email batch {$message->id} completed: {$successCount} sent, {$failCount} failed");
+
+            } catch (\Exception $e) {
+                $message->update([
+                    'status' => 'failed',
+                    'delivery_report' => [
+                        'error' => $e->getMessage(),
+                        'failed_at' => now()
+                    ]
+                ]);
+                $this->error("❌ Failed to process email batch {$message->id}: " . $e->getMessage());
+            }
+        }
+
+        $this->info('🎉 Scheduled email processing completed!');
+        return 0;
+    }
+}

+ 1 - 1
app/Console/Kernel.php

@@ -15,7 +15,7 @@ class Kernel extends ConsoleKernel
      */
      */
     protected function schedule(Schedule $schedule)
     protected function schedule(Schedule $schedule)
     {
     {
-        // $schedule->command('inspire')->hourly();
+        $schedule->command('password:cleanup')->dailyAt('02:00');
     }
     }
 
 
     /**
     /**

+ 17 - 0
app/Database/Migrations/MasterMigration.php

@@ -0,0 +1,17 @@
+<?php
+namespace App\Database\Migrations;
+
+use Illuminate\Database\Migrations\Migration as BaseMigration;
+
+abstract class MasterMigration extends BaseMigration
+{
+    public function getConnection()
+    {
+        return 'master';
+    }
+
+    public function isMasterMigration(): bool
+    {
+        return true;
+    }
+}

+ 17 - 0
app/Database/Migrations/TenantMigration.php

@@ -0,0 +1,17 @@
+<?php
+namespace App\Database\Migrations;
+
+use Illuminate\Database\Migrations\Migration as BaseMigration;
+
+abstract class TenantMigration extends BaseMigration
+{
+    public function getConnection()
+    {
+        return 'tenant';
+    }
+
+    public function isTenantMigration(): bool
+    {
+        return true;
+    }
+}

+ 222 - 0
app/Http/Controllers/FirstLoginController.php

@@ -0,0 +1,222 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Mail;
+use App\Http\Middleware\TenantMiddleware;
+
+class FirstLoginController extends Controller
+{
+    public function __construct()
+    {
+        $this->middleware('auth');
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function show()
+    {
+        $user = Auth::user();
+
+        // If already completed first login, redirect to dashboard
+        if ($user->first_login_completed) {
+            return redirect('/dashboard');
+        }
+
+        return view('first-login');
+    }
+
+    public function complete(Request $request)
+    {
+        // Validate only password
+        $request->validate([
+            'password' => 'required|min:6|confirmed',
+        ], [
+            'password.required' => 'La password è obbligatoria',
+            'password.min' => 'La password deve essere di almeno 6 caratteri',
+            'password.confirmed' => 'Le password non coincidono',
+        ]);
+
+        $currentUser = Auth::user();
+
+        Log::info('Starting first login completion', [
+            'user_id' => $currentUser->id,
+            'email' => $currentUser->email
+        ]);
+
+        try {
+            $this->updateTenantDatabase($currentUser, $request->password);
+
+            $this->updateMasterDatabaseSafely($currentUser, $request->password);
+
+            $this->sendAccountActivationEmailSafely($currentUser);
+
+            return redirect('/dashboard')->with('success', 'Password impostata con successo! Benvenuto in Leezard.cloud.');
+
+        } catch (\Exception $e) {
+            Log::error('First login completion failed', [
+                'user_id' => $currentUser->id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+
+            return back()
+                ->withErrors(['error' => 'Errore durante l\'impostazione della password. Riprova.']);
+        }
+    }
+
+    private function updateTenantDatabase($currentUser, $password)
+    {
+        try {
+            Log::info('Updating tenant database', ['user_id' => $currentUser->id]);
+
+            $user = \App\Models\User::findOrFail($currentUser->id);
+            $user->password = Hash::make($password);
+            $user->first_login_completed = true;
+            $user->email_verified_at = now();
+            $user->save();
+
+            Log::info('Tenant database updated successfully', ['user_id' => $currentUser->id]);
+
+        } catch (\Exception $e) {
+            Log::error('Failed to update tenant database', [
+                'user_id' => $currentUser->id,
+                'error' => $e->getMessage()
+            ]);
+            throw new \Exception('Errore durante l\'aggiornamento del database locale: ' . $e->getMessage());
+        }
+    }
+
+    private function updateMasterDatabaseSafely($currentUser, $password)
+    {
+        try {
+            Log::info('Attempting to update master database', ['user_id' => $currentUser->id]);
+
+            if (!env('DB_DATABASE')) {
+                Log::warning('No master database configured, skipping master update');
+                return;
+            }
+
+            $this->updateMasterDatabaseWithTimeout($currentUser, $password);
+
+        } catch (\Exception $e) {
+            Log::error('Master database update failed (non-critical)', [
+                'user_id' => $currentUser->id,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+
+    private function updateMasterDatabaseWithTimeout($currentUser, $password)
+    {
+        $originalTimeout = ini_get('max_execution_time');
+        set_time_limit(10);
+        try {
+            $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,
+                'options' => [
+                    \PDO::ATTR_TIMEOUT => 5,
+                ],
+            ];
+
+            config(['database.connections.master_temp' => $masterConfig]);
+
+            $connection = DB::connection('master_temp');
+            $connection->getPdo();
+
+            Log::info('Master database connection established');
+
+            $updateData = [
+                'password' => Hash::make($password),
+                'first_login_completed' => true,
+                'email_verified_at' => now(),
+                'updated_at' => now()
+            ];
+
+            $updated = $connection->table('users')
+                ->where('email', $currentUser->email)
+                ->update($updateData);
+
+            if ($updated) {
+                Log::info('Master database updated successfully', [
+                    'email' => $currentUser->email,
+                    'rows_updated' => $updated
+                ]);
+            } else {
+                Log::warning('No rows updated in master database', [
+                    'email' => $currentUser->email
+                ]);
+            }
+
+            DB::purge('master_temp');
+
+        } catch (\Exception $e) {
+            Log::error('Master database update timeout or error', [
+                'error' => $e->getMessage(),
+                'user_email' => $currentUser->email
+            ]);
+
+            try {
+                DB::purge('master_temp');
+            } catch (\Exception $cleanupError) {
+                // Ignore cleanup errors
+            }
+
+            throw $e;
+        } finally {
+            set_time_limit($originalTimeout);
+        }
+    }
+
+    private function sendAccountActivationEmailSafely($user)
+    {
+        try {
+            Log::info('Attempting to send activation email', ['user_id' => $user->id]);
+
+            $emailData = [
+                'name' => $user->name,
+                'email' => $user->email,
+                'login_url' => url('/'),
+                'activation_time' => now()->format('d/m/Y H:i'),
+                'platform_name' => 'Leezard.cloud'
+            ];
+
+            $originalTimeout = ini_get('max_execution_time');
+            set_time_limit(15);
+
+            Mail::send('emails.account-activated', $emailData, function ($message) use ($user) {
+                $message->to($user->email, $user->name)
+                        ->subject('Il tuo account è attivo – Benvenuto in Leezard.cloud')
+                        ->from(config('mail.from.address'), config('mail.from.name'));
+            });
+
+            set_time_limit($originalTimeout);
+
+            Log::info('Activation email sent successfully', [
+                'user_id' => $user->id,
+                'email' => $user->email
+            ]);
+
+        } catch (\Exception $e) {
+            Log::error('Failed to send activation email (non-critical)', [
+                'user_id' => $user->id,
+                'email' => $user->email,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+}

+ 482 - 0
app/Http/Controllers/PasswordResetController.php

@@ -0,0 +1,482 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Str;
+use Carbon\Carbon;
+
+class PasswordResetController extends Controller
+{
+    /**
+     * Show password reset request form
+     */
+    public function showResetRequestForm()
+    {
+        return view('auth.password-reset-request');
+    }
+
+    /**
+     * Send password reset email
+     */
+    public function sendResetEmail(Request $request)
+    {
+        $request->validate([
+            'email' => 'required|email'
+        ], [
+            'email.required' => 'La mail è obbligatoria',
+            'email.email' => 'Inserisci un indirizzo email valido'
+        ]);
+
+        $this->logSecurityEvent('reset_requested', $request->email);
+
+        try {
+            $user = $this->findUserInMasterDatabase($request->email);
+
+            if (!$user) {
+                $this->logSecurityEvent('reset_requested_invalid_email', $request->email);
+                return back()->with('success', 'Se l\'email esiste nel sistema, riceverai le istruzioni per il reset della password.');
+            }
+
+            if ($this->hasRecentResetRequest($request->email)) {
+                $this->logSecurityEvent('reset_requested_too_soon', $request->email);
+                return back()->with('error', 'È già stata inviata una richiesta di reset per questa email. Controlla la tua casella di posta o riprova più tardi.');
+            }
+
+            $token = Str::random(64);
+
+            $this->storeResetToken($request->email, $token);
+
+            $this->sendPasswordResetEmail($request->email, $token, $user);
+
+            $this->logSecurityEvent('reset_email_sent', $request->email);
+
+            return back()->with('success', 'Se l\'email esiste nel sistema, riceverai le istruzioni per il reset della password.');
+        } catch (\Exception $e) {
+            $this->logSecurityEvent('reset_request_failed', $request->email, ['error' => $e->getMessage()]);
+            return back()->with('error', 'Errore durante l\'invio dell\'email. Riprova più tardi.');
+        }
+    }
+
+    /**
+     * Show password reset form
+     */
+    public function showResetForm($token)
+    {
+        return view('auth.password-reset-form', ['token' => $token]);
+    }
+
+    /**
+     * Reset password
+     */
+    public function resetPassword(Request $request)
+    {
+        $request->validate([
+            'token' => 'required',
+            'email' => 'required|email',
+            'password' => 'required|min:6|confirmed'
+        ], [
+            'email.required' => 'La mail è obbligatoria',
+            'email.email' => 'Inserisci un indirizzo email valido',
+            'password.required' => 'La password è obbligatoria',
+            'password.min' => 'La password deve essere di almeno 6 caratteri',
+            'password.confirmed' => 'Le password non coincidono'
+        ]);
+
+        try {
+            // Verify reset token
+            $resetRecord = $this->verifyResetToken($request->email, $request->token);
+
+            if (!$resetRecord) {
+                return back()->with('error', 'Token non valido o scaduto.');
+            }
+
+            // Get user from master database
+            $user = $this->findUserInMasterDatabase($request->email);
+
+            if (!$user) {
+                return back()->with('error', 'Utente non trovato.');
+            }
+
+            // Update password in both databases
+            $this->updatePasswordInBothDatabases($request->email, $request->password, $user);
+
+            // Delete reset token
+            $this->deleteResetToken($request->email);
+
+            // Send password change notification
+            $this->sendPasswordChangeNotification($request->email, $user->name);
+
+            Log::info('Password reset completed with notification', [
+                'email' => $request->email
+            ]);
+
+            return redirect('/')->with('success', 'Password aggiornata con successo. Ti abbiamo inviato una email di conferma.');
+        } catch (\Exception $e) {
+            Log::error('Password reset failed', [
+                'email' => $request->email,
+                'error' => $e->getMessage()
+            ]);
+
+            return back()->with('error', 'Errore durante il reset della password. Riprova più tardi.');
+        }
+    }
+
+    /**
+     * Find user in master database
+     */
+    private function findUserInMasterDatabase($email)
+    {
+        try {
+            $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_reset' => $masterConfig]);
+
+            $user = DB::connection('master_reset')
+                ->table('users')
+                ->where('email', $email)
+                ->first();
+
+            DB::purge('master_reset');
+
+            return $user;
+        } catch (\Exception $e) {
+            Log::error('Failed to find user in master database', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
+    }
+
+    /**
+     * Store reset token in master database
+     */
+    private function storeResetToken($email, $token)
+    {
+        try {
+            $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_reset_token' => $masterConfig]);
+
+            DB::connection('master_reset_token')
+                ->table('password_resets')
+                ->where('email', $email)
+                ->delete();
+
+            DB::connection('master_reset_token')
+                ->table('password_resets')
+                ->insert([
+                    'email' => $email,
+                    'token' => Hash::make($token),
+                    'created_at' => Carbon::now()
+                ]);
+
+            DB::purge('master_reset_token');
+        } catch (\Exception $e) {
+            Log::error('Failed to store reset token', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            throw $e;
+        }
+    }
+
+    /**
+     * Verify reset token
+     */
+    private function verifyResetToken($email, $token)
+    {
+        try {
+            $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_verify' => $masterConfig]);
+
+            $resetRecord = DB::connection('master_verify')
+                ->table('password_resets')
+                ->where('email', $email)
+                ->first();
+
+            DB::purge('master_verify');
+
+            if (!$resetRecord) {
+                return null;
+            }
+
+            $isValidToken = Hash::check($token, $resetRecord->token);
+            $isNotExpired = Carbon::parse($resetRecord->created_at)->addHours(24)->isFuture();
+
+            if ($isValidToken && $isNotExpired) {
+                return $resetRecord;
+            }
+
+            return null;
+        } catch (\Exception $e) {
+            Log::error('Failed to verify reset token', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
+    }
+
+    /**
+     * Update password in both master and tenant databases
+     */
+    private function updatePasswordInBothDatabases($email, $newPassword, $masterUser)
+    {
+        $hashedPassword = Hash::make($newPassword);
+
+        try {
+            $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_update' => $masterConfig]);
+
+            DB::connection('master_update')
+                ->table('users')
+                ->where('email', $email)
+                ->update(['password' => $hashedPassword]);
+
+            DB::purge('master_update');
+
+            Log::info('Password updated in master database', ['email' => $email]);
+
+            if ($masterUser->tenant_database) {
+                $tenantConfig = [
+                    'driver' => 'mysql',
+                    'host' => env('DB_HOST', '127.0.0.1'),
+                    'port' => env('DB_PORT', '3306'),
+                    'database' => $masterUser->tenant_database,
+                    'username' => $masterUser->tenant_username,
+                    'password' => $masterUser->tenant_password,
+                    'charset' => 'utf8mb4',
+                    'collation' => 'utf8mb4_unicode_ci',
+                    'prefix' => '',
+                    'strict' => true,
+                    'engine' => null,
+                ];
+
+                config(['database.connections.tenant_update' => $tenantConfig]);
+
+                DB::connection('tenant_update')
+                    ->table('users')
+                    ->where('email', $email)
+                    ->update(['password' => $hashedPassword]);
+
+                DB::purge('tenant_update');
+
+                Log::info('Password updated in tenant database', [
+                    'email' => $email,
+                    'tenant_database' => $masterUser->tenant_database
+                ]);
+            }
+        } catch (\Exception $e) {
+            Log::error('Failed to update password in databases', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            throw $e;
+        }
+    }
+
+    /**
+     * Delete reset token
+     */
+    private function deleteResetToken($email)
+    {
+        try {
+            $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_token' => $masterConfig]);
+
+            DB::connection('master_delete_token')
+                ->table('password_resets')
+                ->where('email', $email)
+                ->delete();
+
+            DB::purge('master_delete_token');
+        } catch (\Exception $e) {
+            Log::error('Failed to delete reset token', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+
+    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', [
+                'email' => $email,
+                'name' => $name
+            ]);
+
+            return true;
+        } catch (\Exception $e) {
+            Log::error('Failed to send password change notification', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+
+
+    /**
+     * Send password reset email
+     */
+    private function sendPasswordResetEmail($email, $token, $user)
+    {
+        try {
+            $resetUrl = url('/password-reset/' . $token . '?email=' . urlencode($email));
+            $companyName = 'Leezard';
+
+            $emailData = [
+                'name' => $user->name,
+                'email' => $email,
+                'reset_url' => $resetUrl,
+                'company' => $companyName,
+                'expires_at' => Carbon::now()->addHours(24)->format('d/m/Y H:i')
+            ];
+
+            Mail::send('emails.password-reset', $emailData, function ($message) use ($email, $companyName, $user) {
+                $message->to($email, $user->name)
+                    ->subject('Reset Password -  Leezard')
+                    ->from(config('mail.from.address'), config('mail.from.name'));
+            });
+
+            Log::info('Password reset email sent', ['email' => $email]);
+        } catch (\Exception $e) {
+            Log::error('Failed to send password reset email', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            throw $e;
+        }
+    }
+
+    private function logSecurityEvent($event, $email, $additionalData = [])
+    {
+        Log::info('Password Reset Security Event', array_merge([
+            'event' => $event,
+            'email' => $email,
+            'ip' => request()->ip(),
+            'user_agent' => request()->userAgent(),
+            'timestamp' => now()
+        ], $additionalData));
+    }
+
+    private function hasRecentResetRequest($email)
+    {
+        try {
+            $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_check_recent' => $masterConfig]);
+
+            $recentRequest = DB::connection('master_check_recent')
+                ->table('password_resets')
+                ->where('email', $email)
+                ->where('created_at', '>', Carbon::now()->subMinutes(5)) // 5 minutes cooldown
+                ->first();
+
+            DB::purge('master_check_recent');
+
+            return $recentRequest !== null;
+        } catch (\Exception $e) {
+            Log::error('Failed to check recent reset requests', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+}

+ 138 - 0
app/Http/Controllers/SmsTemplateController.php

@@ -0,0 +1,138 @@
+<?php
+
+// Controller: SmsTemplateController.php
+namespace App\Http\Controllers;
+
+use App\Models\SmsTemplate;
+use App\Models\SmsScheduled;
+use App\Models\Category;
+use App\Models\User;
+use Illuminate\Http\Request;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Log;
+class SmsTemplateController extends Controller
+{
+    public function index()
+    {
+        return view('sms.index');
+    }
+
+    public function getTemplatesJson(Request $request)
+    {
+        $templates = SmsTemplate::with('creator:id,name')->latest()->get();
+
+        $data = $templates->map(function($template) {
+            return [
+                'id' => $template->id,
+                'name' => $template->name,
+                'content' => $template->content,
+                'character_count' => strlen($template->content),
+                'created_at' => $template->created_at->format('d/m/Y H:i'),
+                'creator' => $template->creator->name ?? 'N/A'
+            ];
+        });
+
+        return response()->json(['data' => $data]);
+    }
+
+    public function store(Request $request)
+    {
+        $request->validate([
+            'subject' => 'required|string|max:255',
+            'message' => 'required|string|max:160',
+            'recipients' => 'required|array|min:1',
+            'send_now' => 'required|boolean',
+            'scheduled_at' => 'required_if:send_now,false|date|after:now',
+        ]);
+
+        $template = SmsTemplate::create([
+            'name' => $request->subject,
+            'content' => $request->message,
+            'created_by' => Auth::id(),
+        ]);
+
+        $recipients = User::whereHas('categories', function ($query) use ($request) {
+            $query->whereIn('categories.id', $request->recipients);
+        })->get();
+
+        if ($request->send_now) {
+            $this->sendSmsNow($template, $recipients);
+            $message = 'Template creato e SMS inviato a ' . $recipients->count() . ' destinatari!';
+        } else {
+            $this->scheduleSms($template, $recipients, $request->scheduled_at);
+            $scheduledDate = Carbon::parse($request->scheduled_at)->format('d/m/Y H:i');
+            $message = 'Template creato e SMS programmato per ' . $scheduledDate;
+        }
+
+        return response()->json([
+            'success' => true,
+            'message' => $message,
+            'template' => [
+                'id' => $template->id,
+                'name' => $template->name,
+                'content' => $template->content,
+                'character_count' => strlen($template->content),
+                'created_at' => $template->created_at->format('d/m/Y H:i'),
+                'creator' => Auth::user()->name
+            ]
+        ]);
+    }
+
+    public function destroy($id)
+    {
+        $template = SmsTemplate::findOrFail($id);
+        $template->delete();
+
+        return response()->json([
+            'success' => true,
+            'message' => 'Template eliminato con successo!'
+        ]);
+    }
+
+    public function getCategories()
+    {
+        $categories = Category::with('users')->get()->map(function($category) {
+            return [
+                'id' => $category->id,
+                'name' => $category->name,
+                'users_count' => $category->users->count()
+            ];
+        });
+
+        return response()->json($categories);
+    }
+
+    public function getTemplate($id)
+    {
+        $template = SmsTemplate::findOrFail($id);
+
+        return response()->json([
+            'id' => $template->id,
+            'name' => $template->name,
+            'content' => $template->content
+        ]);
+    }
+
+    private function sendSmsNow($template, $recipients)
+    {
+        foreach ($recipients as $recipient) {
+            if ($recipient->phone) {
+                Log::info("SMS sent to {$recipient->name} ({$recipient->phone}): {$template->content}");
+            }
+        }
+    }
+
+    private function scheduleSms($template, $recipients, $scheduledAt)
+    {
+        $scheduled = SmsScheduled::create([
+            'template_id' => $template->id,
+            'content' => $template->content,
+            'scheduled_at' => Carbon::parse($scheduledAt),
+            'status' => 'scheduled',
+            'created_by' => Auth::id(),
+        ]);
+
+        $scheduled->recipients()->attach($recipients->pluck('id'));
+    }
+}

+ 2 - 0
app/Http/Kernel.php

@@ -63,5 +63,7 @@ class Kernel extends HttpKernel
         'signed' => \App\Http\Middleware\ValidateSignature::class,
         'signed' => \App\Http\Middleware\ValidateSignature::class,
         'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
         'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
         'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
         'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+        'tenant' => \App\Http\Middleware\TenantMiddleware::class,
+        'first.login.completed' => \App\Http\Middleware\CheckFirstLoginCompleted::class,
     ];
     ];
 }
 }

+ 5 - 1
app/Http/Livewire/Azienda.php

@@ -9,7 +9,7 @@ use Illuminate\Support\Str;
 use App\Models\Azienda as AziendaModel;
 use App\Models\Azienda as AziendaModel;
 use App\Services\LogoUploadServices;
 use App\Services\LogoUploadServices;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
-use Illuminate\Support\Facades\Storage;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class Azienda extends Component
 class Azienda extends Component
 {
 {
@@ -87,6 +87,10 @@ class Azienda extends Component
         $this->codice_fiscale = null;
         $this->codice_fiscale = null;
         $this->codice_sdi = null;
         $this->codice_sdi = null;
     }
     }
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
 
 
     public function mount()
     public function mount()
     {
     {

+ 8 - 4
app/Http/Livewire/Bank.php

@@ -3,7 +3,8 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
-
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 class Bank extends Component
 class Bank extends Component
 {
 {
     public $records, $name, $enabled, $dataId, $update = false, $add = false;
     public $records, $name, $enabled, $dataId, $update = false, $add = false;
@@ -18,10 +19,13 @@ class Bank extends Component
 
 
     public $sortField ='name';
     public $sortField ='name';
     public $sortAsc = true;
     public $sortAsc = true;
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
     }
     }
@@ -121,7 +125,7 @@ class Bank extends Component
             session()->flash('success',"Città eliminata");
             session()->flash('success',"Città eliminata");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 8 - 3
app/Http/Livewire/Card.php

@@ -1,8 +1,9 @@
 <?php
 <?php
 
 
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
-
+use Illuminate\Support\Facades\Auth;
 use Livewire\Component;
 use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class Card extends Component
 class Card extends Component
 {
 {
@@ -19,9 +20,13 @@ class Card extends Component
     public $sortField ='name';
     public $sortField ='name';
     public $sortAsc = true;
     public $sortAsc = true;
 
 
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
     }
     }
@@ -146,7 +151,7 @@ class Card extends Component
             session()->flash('success',"Tessera eliminata");
             session()->flash('success',"Tessera eliminata");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 7 - 0
app/Http/Livewire/Categories.php

@@ -1,6 +1,8 @@
 <?php
 <?php
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 use Livewire\Component;
 use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+
 class Categories extends Component
 class Categories extends Component
 {
 {
     public $level_1 = [];
     public $level_1 = [];
@@ -10,6 +12,11 @@ class Categories extends Component
     public $level_1_id = 0;
     public $level_1_id = 0;
     public $level_2_id = 0;
     public $level_2_id = 0;
     public $level_3_id = 0;
     public $level_3_id = 0;
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
 
 
     public function render()
     public function render()
     {
     {

+ 8 - 2
app/Http/Livewire/Category.php

@@ -3,6 +3,8 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+use Illuminate\Support\Facades\Auth;
 
 
 class Category extends Component
 class Category extends Component
 {
 {
@@ -18,9 +20,13 @@ class Category extends Component
         'name.required' => 'Il nome è obbligatorio'
         'name.required' => 'Il nome è obbligatorio'
     ];
     ];
 
 
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
     }
     }
@@ -120,7 +126,7 @@ class Category extends Component
             session()->flash('success',"Categoria eliminata");
             session()->flash('success',"Categoria eliminata");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 104 - 33
app/Http/Livewire/Causal.php

@@ -2,7 +2,9 @@
 
 
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
+use Illuminate\Support\Facades\Auth;
 use Livewire\Component;
 use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class Causal extends Component
 class Causal extends Component
 {
 {
@@ -11,6 +13,7 @@ class Causal extends Component
     public $corrispettivo_causal_id = 0;
     public $corrispettivo_causal_id = 0;
 
 
     public $parent = '';
     public $parent = '';
+    public $showHidden = false;
 
 
     protected $rules = [
     protected $rules = [
         'name' => 'required',
         'name' => 'required',
@@ -21,18 +24,91 @@ class Causal extends Component
         'name.required' => 'Il nome è obbligatorio'
         'name.required' => 'Il nome è obbligatorio'
     ];
     ];
 
 
-    public function mount(){
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
-            return redirect()->to('/dashboard');
+    public function mount()
+    {
+        $this->loadRecords();
+    }
 
 
-        $fisc = \App\Models\Causal::where('corrispettivo_fiscale', true)->first();
-        if ($fisc)
-            $this->corrispettivo_causal_id = $fisc->id;
+    public function hide($id)
+    {
+        try {
+            \App\Models\Causal::whereId($id)->update(['hidden' => true]);
+            session()->flash('success', 'Causale nascosta');
+            $this->loadRecords(); // Refresh the data
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
 
 
+    public function show($id)
+    {
+        try {
+            \App\Models\Causal::whereId($id)->update(['hidden' => false]);
+            session()->flash('success', 'Causale ripristinata');
+            $this->loadRecords(); // Refresh the data
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
     }
     }
 
 
-    public function resetFields(){
+    public function toggleHidden()
+    {
+        $this->showHidden = !$this->showHidden;
+        $this->loadRecords();
+    }
+
+    public function loadRecords()
+    {
+        if ($this->showHidden) {
+            // Show all records including hidden ones
+            $this->recordsIn = \App\Models\Causal::where('type', 'IN')
+                ->where('parent_id', null)
+                ->with(['childs'])
+                ->orderBy('name')
+                ->get();
+
+            $this->recordsOut = \App\Models\Causal::where('type', 'OUT')
+                ->where('parent_id', null)
+                ->with(['childs'])
+                ->orderBy('name')
+                ->get();
+        } else {
+            // Show only non-hidden records
+            $this->recordsIn = \App\Models\Causal::where('type', 'IN')
+                ->where('parent_id', null)
+                ->where(function ($query) {
+                    $query->where('hidden', false)->orWhereNull('hidden');
+                })
+                ->with(['childs' => function ($query) {
+                    $query->where(function ($q) {
+                        $q->where('hidden', false)->orWhereNull('hidden');
+                    });
+                }])
+                ->orderBy('name')
+                ->get();
+
+            $this->recordsOut = \App\Models\Causal::where('type', 'OUT')
+                ->where('parent_id', null)
+                ->where(function ($query) {
+                    $query->where('hidden', false)->orWhereNull('hidden');
+                })
+                ->with(['childs' => function ($query) {
+                    $query->where(function ($q) {
+                        $q->where('hidden', false)->orWhereNull('hidden');
+                    });
+                }])
+                ->orderBy('name')
+                ->get();
+        }
+    }
+
+    public function resetFields()
+    {
         $this->name = '';
         $this->name = '';
         $this->parent_id = null;
         $this->parent_id = null;
         $this->parent = '';
         $this->parent = '';
@@ -48,8 +124,12 @@ class Causal extends Component
 
 
     public function render()
     public function render()
     {
     {
-        $this->recordsIn = \App\Models\Causal::where('parent_id', null)->where('type', 'IN')->get();
-        $this->recordsOut = \App\Models\Causal::where('parent_id', null)->where('type', 'OUT')->get();
+        // Remove the duplicate queries - loadRecords() already handles this
+        // and make sure it respects the showHidden state
+        if (!isset($this->recordsIn) || !isset($this->recordsOut)) {
+            $this->loadRecords();
+        }
+
         return view('livewire.causal');
         return view('livewire.causal');
     }
     }
 
 
@@ -87,19 +167,21 @@ class Causal extends Component
                 'no_records' => $this->no_records,
                 'no_records' => $this->no_records,
                 'enabled' => $this->enabled
                 'enabled' => $this->enabled
             ]);
             ]);
-            session()->flash('success','Causale creata');
+            session()->flash('success', 'Causale creata');
             $this->resetFields();
             $this->resetFields();
             $this->add = false;
             $this->add = false;
+            $this->loadRecords();
         } catch (\Exception $ex) {
         } catch (\Exception $ex) {
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
         }
         }
     }
     }
 
 
-    public function edit($id){
+    public function edit($id)
+    {
         try {
         try {
             $causal = \App\Models\Causal::findOrFail($id);
             $causal = \App\Models\Causal::findOrFail($id);
-            if( !$causal) {
-                session()->flash('error','Causale non trovata');
+            if (!$causal) {
+                session()->flash('error', 'Causale non trovata');
             } else {
             } else {
                 $this->name = $causal->name;
                 $this->name = $causal->name;
                 $this->money = $causal->money;
                 $this->money = $causal->money;
@@ -108,6 +190,7 @@ class Causal extends Component
                 $this->no_first = $causal->no_first;
                 $this->no_first = $causal->no_first;
                 $this->no_records = $causal->no_records;
                 $this->no_records = $causal->no_records;
                 $this->enabled = $causal->enabled;
                 $this->enabled = $causal->enabled;
+                $this->corrispettivo_fiscale = $causal->corrispettivo_fiscale;
                 $this->type = $causal->type;
                 $this->type = $causal->type;
                 $this->parent_id = $causal->parent_id;
                 $this->parent_id = $causal->parent_id;
                 $this->dataId = $causal->id;
                 $this->dataId = $causal->id;
@@ -115,7 +198,7 @@ class Causal extends Component
                 $this->add = false;
                 $this->add = false;
             }
             }
         } catch (\Exception $ex) {
         } catch (\Exception $ex) {
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
         }
         }
     }
     }
 
 
@@ -135,11 +218,12 @@ class Causal extends Component
                 'corrispettivo_fiscale' => $this->corrispettivo_fiscale,
                 'corrispettivo_fiscale' => $this->corrispettivo_fiscale,
                 'enabled' => $this->enabled
                 'enabled' => $this->enabled
             ]);
             ]);
-            session()->flash('success','Tessera aggiornata');
+            session()->flash('success', 'Causale aggiornata');
             $this->resetFields();
             $this->resetFields();
             $this->update = false;
             $this->update = false;
+            $this->loadRecords();
         } catch (\Exception $ex) {
         } catch (\Exception $ex) {
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
         }
         }
     }
     }
 
 
@@ -150,17 +234,6 @@ class Causal extends Component
         $this->resetFields();
         $this->resetFields();
     }
     }
 
 
-    public function delete($id)
-    {
-        try{
-            \App\Models\Causal::find($id)->delete();
-            session()->flash('success',"Tessera eliminata");
-            return redirect(request()->header('Referer'));
-        }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
-        }
-    }
-
     public function duplicate($id)
     public function duplicate($id)
     {
     {
         $old = \App\Models\Causal::find($id);
         $old = \App\Models\Causal::find($id);
@@ -169,25 +242,23 @@ class Causal extends Component
         $new->save();
         $new->save();
 
 
         $this->duplicateRecursive($old, $new);
         $this->duplicateRecursive($old, $new);
+        $this->loadRecords(); // Refresh after duplicate
     }
     }
 
 
     public function duplicateRecursive($old, $new)
     public function duplicateRecursive($old, $new)
     {
     {
-        foreach($old->childs as $c)
-        {
+        foreach ($old->childs as $c) {
             $old1 = \App\Models\Causal::find($c->id);
             $old1 = \App\Models\Causal::find($c->id);
             $new1 = $old1->replicate();
             $new1 = $old1->replicate();
             $new1->parent_id = $new->id;
             $new1->parent_id = $new->id;
             $new1->save();
             $new1->save();
 
 
             $this->duplicateRecursive($old1, $new1);
             $this->duplicateRecursive($old1, $new1);
-
         }
         }
     }
     }
 
 
     public function reorder()
     public function reorder()
     {
     {
-
+        $this->loadRecords(); // Refresh after reorder
     }
     }
-
 }
 }

+ 60 - 27
app/Http/Livewire/Causals.php

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

+ 6 - 3
app/Http/Livewire/City.php

@@ -3,7 +3,7 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
-
+use App\Http\Middleware\TenantMiddleware;
 use Livewire\WithPagination;
 use Livewire\WithPagination;
 
 
 class City extends Component
 class City extends Component
@@ -45,7 +45,10 @@ class City extends Component
         $this->enabled = true;
         $this->enabled = true;
         $this->province_id = null;
         $this->province_id = null;
     }
     }
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount()
     public function mount()
     {
     {
         $this->provinces = \App\Models\Province::select('id', 'name')->get();
         $this->provinces = \App\Models\Province::select('id', 'name')->get();
@@ -133,7 +136,7 @@ class City extends Component
             \App\Models\City::find($id)->delete();
             \App\Models\City::find($id)->delete();
             session()->flash('success',"Città eliminata");
             session()->flash('success',"Città eliminata");
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 11 - 8
app/Http/Livewire/Course.php

@@ -1,9 +1,9 @@
 <?php
 <?php
 
 
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
-
+use App\Http\Middleware\TenantMiddleware;
 use Livewire\Component;
 use Livewire\Component;
-
+use Illuminate\Support\Facades\Auth;
 class Course extends Component
 class Course extends Component
 {
 {
 
 
@@ -62,7 +62,7 @@ class Course extends Component
         'subscription_price' => 'required|min:0|not_in:0',
         'subscription_price' => 'required|min:0|not_in:0',
         /*'course_type_id' => 'required',
         /*'course_type_id' => 'required',
         'course_duration_id' => 'required',
         'course_duration_id' => 'required',
-        
+
         'causal_id' => 'required',
         'causal_id' => 'required',
         'sub_causal_id' => 'required',*/
         'sub_causal_id' => 'required',*/
     ];
     ];
@@ -74,7 +74,10 @@ class Course extends Component
         /*'causal_id' => 'Campo obbligatorio',
         /*'causal_id' => 'Campo obbligatorio',
         'sub_causal_id' => 'Campo obbligatorio',*/
         'sub_causal_id' => 'Campo obbligatorio',*/
     ];
     ];
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function resetFields(){
     public function resetFields(){
         $this->name = '';
         $this->name = '';
         $this->parent_id = null;
         $this->parent_id = null;
@@ -115,7 +118,7 @@ class Course extends Component
 
 
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
         $this->categories = array();
         $this->categories = array();
@@ -272,7 +275,7 @@ class Course extends Component
                 $course->sub_causal_id = $ci->id;
                 $course->sub_causal_id = $ci->id;
 
 
                 $course->save();
                 $course->save();
-                
+
                 session()->flash('success','Corso creato');
                 session()->flash('success','Corso creato');
                 $this->resetFields();
                 $this->resetFields();
                 $this->add = false;
                 $this->add = false;
@@ -320,7 +323,7 @@ class Course extends Component
                 {
                 {
                     $this->when[] = array('day' => array(), 'from' => '', 'to' => '');
                     $this->when[] = array('day' => array(), 'from' => '', 'to' => '');
                 }
                 }
-        
+
                 $this->prices = array();
                 $this->prices = array();
                 if ($course->prices != null)
                 if ($course->prices != null)
                 {
                 {
@@ -395,7 +398,7 @@ class Course extends Component
             session()->flash('success',"Corso eliminato");
             session()->flash('success',"Corso eliminato");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 
 

+ 8 - 4
app/Http/Livewire/CourseDuration.php

@@ -3,7 +3,8 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
-
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 class CourseDuration extends Component
 class CourseDuration extends Component
 {
 {
     public $records, $name, $duration, $enabled, $dataId, $update = false, $add = false;
     public $records, $name, $duration, $enabled, $dataId, $update = false, $add = false;
@@ -20,10 +21,13 @@ class CourseDuration extends Component
 
 
     public $sortField ='name';
     public $sortField ='name';
     public $sortAsc = true;
     public $sortAsc = true;
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
     }
     }
@@ -126,7 +130,7 @@ class CourseDuration extends Component
             session()->flash('success',"Dato eliminato");
             session()->flash('success',"Dato eliminato");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 8 - 4
app/Http/Livewire/CourseFrequency.php

@@ -3,7 +3,8 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
-
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 class CourseFrequency extends Component
 class CourseFrequency extends Component
 {
 {
     public $records, $name, $enabled, $dataId, $update = false, $add = false;
     public $records, $name, $enabled, $dataId, $update = false, $add = false;
@@ -18,10 +19,13 @@ class CourseFrequency extends Component
 
 
     public $sortField ='name';
     public $sortField ='name';
     public $sortAsc = true;
     public $sortAsc = true;
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
     }
     }
@@ -120,7 +124,7 @@ class CourseFrequency extends Component
             session()->flash('success',"Dato eliminato");
             session()->flash('success',"Dato eliminato");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 8 - 3
app/Http/Livewire/CourseLevel.php

@@ -1,7 +1,8 @@
 <?php
 <?php
 
 
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
-
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 use Livewire\Component;
 use Livewire\Component;
 
 
 class CourseLevel extends Component
 class CourseLevel extends Component
@@ -15,10 +16,14 @@ class CourseLevel extends Component
     protected $messages = [
     protected $messages = [
         'name.required' => 'Il nome è obbligatorio'
         'name.required' => 'Il nome è obbligatorio'
     ];
     ];
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
 
 
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
     }
     }
@@ -121,7 +126,7 @@ class CourseLevel extends Component
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
             //return redirect()->to(\Illuminate\Http\Request::url());
             //return redirect()->to(\Illuminate\Http\Request::url());
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

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

@@ -9,7 +9,8 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
 use App\Models\MemberCourse;
 use App\Models\MemberCourse;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\Storage;
-
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 class CourseList extends Component
 class CourseList extends Component
 {
 {
 
 
@@ -53,7 +54,10 @@ class CourseList extends Component
     public $aaa;
     public $aaa;
 
 
     public $months = array('Set', 'Ott', 'Nov', 'Dic', 'Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago');
     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()
     public function mount()
     {
     {
 
 

+ 6 - 1
app/Http/Livewire/CourseMember.php

@@ -2,6 +2,8 @@
 
 
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 use Livewire\Component;
 use Livewire\Component;
 
 
 class CourseMember extends Component
 class CourseMember extends Component
@@ -44,7 +46,10 @@ class CourseMember extends Component
     public $type = '';
     public $type = '';
 
 
     public $filterFromPrevious = '';
     public $filterFromPrevious = '';
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount()
     public function mount()
     {
     {
 
 

+ 7 - 2
app/Http/Livewire/CourseSubscription.php

@@ -3,6 +3,8 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class CourseSubscription extends Component
 class CourseSubscription extends Component
 {
 {
@@ -18,7 +20,10 @@ class CourseSubscription extends Component
 
 
     public $sortField ='name';
     public $sortField ='name';
     public $sortAsc = true;
     public $sortAsc = true;
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function sortBy($field)
     public function sortBy($field)
     {
     {
         if($this->sortField === $field)
         if($this->sortField === $field)
@@ -124,7 +129,7 @@ class CourseSubscription extends Component
             \App\Models\CourseSubscription::find($id)->delete();
             \App\Models\CourseSubscription::find($id)->delete();
             session()->flash('success',"Dato eliminato");
             session()->flash('success',"Dato eliminato");
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 9 - 4
app/Http/Livewire/CourseType.php

@@ -3,7 +3,9 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
-
+use App\Http\Middleware\TenantMiddleware;
+use Livewire\WithPagination;
+use Illuminate\Support\Facades\Auth;
 class CourseType extends Component
 class CourseType extends Component
 {
 {
     public $records, $name, $duration, $enabled, $dataId, $update = false, $add = false;
     public $records, $name, $duration, $enabled, $dataId, $update = false, $add = false;
@@ -15,10 +17,13 @@ class CourseType extends Component
     protected $messages = [
     protected $messages = [
         'name.required' => 'Il nome è obbligatorio'
         'name.required' => 'Il nome è obbligatorio'
     ];
     ];
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
     }
     }
@@ -124,7 +129,7 @@ class CourseType extends Component
             session()->flash('success',"Dato eliminato");
             session()->flash('success',"Dato eliminato");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 7 - 1
app/Http/Livewire/Courses.php

@@ -1,6 +1,9 @@
 <?php
 <?php
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 use Livewire\Component;
 use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+use Livewire\WithPagination;
+use Illuminate\Support\Facades\Auth;
 class Courses extends Component
 class Courses extends Component
 {
 {
     public $level_1 = [];
     public $level_1 = [];
@@ -12,7 +15,10 @@ class Courses extends Component
     public $level_3_id = 0;
     public $level_3_id = 0;
 
 
     public $course_id = null;
     public $course_id = null;
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount($course_id)
     public function mount($course_id)
     {
     {
         $this->course_id = $course_id;
         $this->course_id = $course_id;

+ 736 - 98
app/Http/Livewire/Dashboard.php

@@ -4,10 +4,11 @@ namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
 use Carbon\Carbon;
 use Carbon\Carbon;
+use Illuminate\Support\Facades\Log;
 
 
 class Dashboard extends Component
 class Dashboard extends Component
 {
 {
-
+    // Existing properties
     public $totMembers = 0;
     public $totMembers = 0;
     public $totSuppliers = 0;
     public $totSuppliers = 0;
     public $totTodayIn = 0;
     public $totTodayIn = 0;
@@ -16,165 +17,802 @@ class Dashboard extends Component
     public $in;
     public $in;
     public $out;
     public $out;
     public $members;
     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 $toReceive = 0;
+    public $toPay = 0;
 
 
+    public $courses = [];
+    public $fields = [];
+    public $recentUsers = [];
+    public $recentTransactions = [];
+    public $coursesParticipation = [];
+    public $notes = '';
+    public $savedNotes = [];
     public array $membersDatas = [];
     public array $membersDatas = [];
     public array $recordDatas = [];
     public array $recordDatas = [];
     public array $labels = [];
     public array $labels = [];
+    public array $monthlyLabels = [];
+    public array $monthlyIncomeData = [];
+    public array $monthlyExpenseData = [];
 
 
     public function render()
     public function render()
     {
     {
+        Log::info('Dashboard render method called');
         return view('livewire.dashboard');
         return view('livewire.dashboard');
     }
     }
 
 
     public function mount()
     public function mount()
     {
     {
+        Log::info('Dashboard mount started');
+        $startTime = microtime(true);
 
 
         $this->dayName = Carbon::now()->locale('it_IT')->dayName;
         $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();
 
 
-        $this->totMembers = \App\Models\Member::count();
-        $this->totSuppliers = \App\Models\Supplier::count();
-        $this->totTodayIn = 0;
-        $tmp = \App\Models\Record::where('type', 'IN')->where('date', date("Y-m-d"))->get();
-        foreach($tmp as $t)
-        {
-            foreach($t->rows as $r)
-            {
-                $this->totTodayIn += $r->amount;
+            foreach ($todayRecordsIn as $record) {
+                foreach ($record->rows as $row) {
+                    $this->totTodayIn += $row->amount;
+                }
             }
             }
-        }
-        //$this->totTodayIn = \App\Models\Record::where('type', 'IN')->where('date', date("Y-m-d"))->rows->sum('amount');
-        $tmp = \App\Models\Record::where('type', 'OUT')->where('date', date("Y-m-d"))->get();
-        foreach($tmp as $t)
-        {
-            foreach($t->rows as $r)
-            {
-                $this->totTodayOut += $r->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()
+            ]);
         }
         }
-        //$this->totTodayOut = \App\Models\Record::where('type', 'OUT')->where('date', date("Y-m-d"))->rows->sum('amount');
+    }
 
 
-        $this->members = \App\Models\Member::whereBetween('created_at', [date("y-m-d", strtotime('-7 days')), date("Y-m-d 23:59:59")])->select(\DB::raw("COUNT(*) as total, created_at"))->groupBy('created_at')->get();
+    private function loadUserStats()
+    {
+        Log::info('Loading user stats');
+        $startTime = microtime(true);
 
 
-        $this->in = \App\Models\Record::where('type', 'IN')->whereBetween('date', [date("y-m-d", strtotime('-7 days')), date("Y-m-d 23:59:59")])->select(\DB::raw("SUM(amount) as total, date"))->groupBy('date')->get();
-        $this->out = \App\Models\Record::where('type', 'OUT')->whereBetween('date', [date("y-m-d", strtotime('-7 days')), date("Y-m-d 23:59:59")])->select(\DB::raw("SUM(amount) as total, date"))->groupBy('date')->get();
+        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();
 
 
-        $this->labels = $this->getLabels();
+            Log::info('User counts loaded', [
+                'active_users' => $this->activeUsers,
+                'registered_users' => $this->registeredUsers,
+                'suspended_subscriptions' => $this->suspendedSubscriptions
+            ]);
 
 
-        $this->memberDatas = [
-            [
-                'label' => 'Utenti',
-                'backgroundColor' => 'blue',
-                'borderColor' => 'blue',
-                // 'data' => $this->getRandomData(),
-                'data' => $this->getMemberData(),
-            ]
-        ];
+            $this->expiredCertificates = \App\Models\Member::whereHas('certificates', function ($query) {
+                $query->where('expire_date', '<', now());
+            })->whereDoesntHave('certificates', function ($query) {
+                $query->where('expire_date', '>=', now());
+            })->count();
 
 
-        $this->recordDatas = [
-            [
-                'label' => 'Entrate',
-                'backgroundColor' => 'green',
-                'borderColor' => 'green',
-                // 'data' => $this->getRandomData(),
-                'data' => $this->getRecordData('IN'),
-            ],
-            [
-                'label' => 'Uscite',
-                'backgroundColor' => 'red',
-                'borderColor' => 'red',
-                'data' => $this->getRecordData('OUT'),
-            ]
-        ];
+            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 getLabels()
+    private function loadFinancialStats()
     {
     {
-        $labels = array();
-        for($i=0; $i<=7; $i++)
-        {
-            $labels[] = date("d/M", strtotime('-' . $i . ' days'));
+        Log::info('Loading financial stats');
+        $startTime = microtime(true);
+
+        try {
+            $currentMonth = now()->format('Y-m');
+            Log::info('Calculating financial stats for month', ['month' => $currentMonth]);
+
+            $this->toReceive = \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->toPay = \App\Models\Record::where('type', 'OUT')
+                ->whereRaw('DATE_FORMAT(date, "%Y-%m") = ?', [$currentMonth])
+                ->where(function ($query) {
+                    $query->where('deleted', false)->orWhere('deleted', null);
+                })
+                ->sum('amount') ?? 0;
+
+            $endTime = microtime(true);
+            Log::info('Financial stats loaded successfully', [
+                'to_receive' => $this->toReceive,
+                '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()
+            ]);
         }
         }
-        return array_reverse($labels);
     }
     }
 
 
-    private function getRecordData($type)
+    private function loadRecentData()
     {
     {
-        $data = [];
-        for($i=0; $i<=7; $i++)
-        {
-            if ($type == 'IN')
-            {
-                $found = false;
-                foreach($this->in as $in)
-                {
-                    if (date("Y-m-d", strtotime($in->date)) == date("Y-m-d", strtotime('-' . $i . ' days')))
-                    {
-                        $data[] = $in->total;
-                        $found = true;
+        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()
+                    ]);
                 }
                 }
-                if (!$found)
-                    $data[] = 0;
-            }
-            if ($type == 'OUT')
-            {
-                $found = false;
-                foreach($this->out as $out)
-                {
-                    if (date("Y-m-d", strtotime($out->date)) == date("Y-m-d", strtotime('-' . $i . ' days')))
-                    {
-                        $data[] = $out->total;
-                        $found = true;
+                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 (!$found)
-                    $data[] = 0;
-            }
+
+                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);
 
 
-        return array_reverse($data);
+        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 getMemberData()
+    private function getCourseColor($courseName)
     {
     {
-        $data = [];
-        for($i=0; $i<=7; $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;
-                }
-            }
-            if (!$found)
-                $data[] = 0;
+        $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()->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]);
 
 
-        return array_reverse($data);
+        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()
     public function addMember()
     {
     {
+        Log::info('Redirecting to add member');
         return redirect()->to('/members?new=1');
         return redirect()->to('/members?new=1');
     }
     }
 
 
     public function addSupplier()
     public function addSupplier()
     {
     {
+        Log::info('Redirecting to add supplier');
         return redirect()->to('/suppliers?new=1');
         return redirect()->to('/suppliers?new=1');
     }
     }
 
 
     public function addIn()
     public function addIn()
     {
     {
+        Log::info('Redirecting to add income record');
         return redirect()->to('/in?new=1');
         return redirect()->to('/in?new=1');
     }
     }
 
 
     public function addOut()
     public function addOut()
     {
     {
+        Log::info('Redirecting to add expense record');
         return redirect()->to('/out?new=1');
         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;
+    }
 }
 }

+ 8 - 3
app/Http/Livewire/Discipline.php

@@ -3,7 +3,8 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
-
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 class Discipline extends Component
 class Discipline extends Component
 {
 {
     public $records, $name, $enabled, $dataId, $update = false, $add = false;
     public $records, $name, $enabled, $dataId, $update = false, $add = false;
@@ -18,10 +19,14 @@ class Discipline extends Component
 
 
     public $sortField ='name';
     public $sortField ='name';
     public $sortAsc = true;
     public $sortAsc = true;
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
 
 
     public function mount(){
     public function mount(){
 
 
-        if(\Auth::user()->level != env('LEVEL_ADMIN', 0))
+        if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
     }
     }
@@ -120,7 +125,7 @@ class Discipline extends Component
             session()->flash('success',"Disciplina eliminata");
             session()->flash('success',"Disciplina eliminata");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 206 - 0
app/Http/Livewire/EmailComunications.php

@@ -0,0 +1,206 @@
+<?php
+
+namespace App\Http\Livewire;
+
+use Livewire\Component;
+use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
+use App\Models\EmailTemplate;
+use App\Models\EmailScheduled;
+use App\Models\Category;
+use App\Models\Member;
+use App\Models\User;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Facades\Log;
+
+class EmailComunications extends Component
+{
+    public $records, $subject, $message, $selectedRecipients = [], $scheduledDateTime, $sendNow = true;
+    public $dataId, $update = false, $add = false;
+    public $users = [];
+
+    protected $rules = [
+        'subject' => 'required|string|max:255',
+        'message' => 'required|string',
+        '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',
+        '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->users = Member::select('id', 'last_name', 'email')->get();
+        $this->scheduledDateTime = now()->addHour()->format('Y-m-d\TH:i');
+    }
+
+    public function render()
+    {
+        $this->records = EmailTemplate::orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')->get();
+        return view('livewire.email_comunications');
+    }
+
+    public function add()
+    {
+        $this->resetFields();
+        $this->add = true;
+        $this->update = false;
+    }
+
+    public function store()
+    {
+        $this->validate();
+
+        try {
+            $template = EmailTemplate::create([
+                'name' => $this->subject,
+                'content' => $this->message,
+                'created_by' => Auth::id(),
+            ]);
+
+            $recipients = User::whereIn('id', $this->selectedRecipients)->get();
+
+            if ($this->sendNow) {
+                $this->sendEmailNow($template, $recipients);
+                session()->flash('success', 'Template creato e Email inviate a ' . $recipients->count() . ' destinatari!');
+            } else {
+                $this->scheduleEmail($template, $recipients);
+                $scheduledDate = Carbon::parse($this->scheduledDateTime)->format('d/m/Y H:i');
+                session()->flash('success', 'Template creato e Email programmate per ' . $scheduledDate);
+            }
+
+            $this->resetFields();
+            $this->add = false;
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function edit($id)
+    {
+        try {
+            $template = EmailTemplate::findOrFail($id);
+            if (!$template) {
+                session()->flash('error', 'Template Email 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',
+        ]);
+
+        try {
+            EmailTemplate::whereId($this->dataId)->update([
+                'name' => $this->subject,
+                'content' => $this->message,
+            ]);
+            session()->flash('success', 'Template Email 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 = EmailTemplate::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 sendEmailNow($template, $recipients)
+    {
+        foreach ($recipients as $recipient) {
+            if ($recipient->email) {
+                try {
+                    Log::info("Email sent to {$recipient->name} ({$recipient->email}): Subject: {$template->name}");
+                } catch (\Exception $e) {
+                    Log::error("Failed to send email to {$recipient->email}: " . $e->getMessage());
+                }
+            } else {
+                Log::warning("User {$recipient->id} has no email address");
+            }
+        }
+    }
+
+    private function scheduleEmail($template, $recipients)
+    {
+        $scheduled = EmailScheduled::create([
+            'template_id' => $template->id,
+            'subject' => $template->name,
+            'content' => $template->content,
+            'scheduled_at' => Carbon::parse($this->scheduledDateTime),
+            'status' => 'scheduled',
+            'created_by' => Auth::id(),
+        ]);
+
+        $scheduled->recipients()->attach($recipients->pluck('id'));
+
+        Log::info("Email scheduled for {$this->scheduledDateTime} to {$recipients->count()} recipients: {$template->name}");
+    }
+}

+ 189 - 0
app/Http/Livewire/FirstLogin.php

@@ -0,0 +1,189 @@
+<?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 FirstLogin extends Component
+{
+    // Component properties
+    public $name;
+    public $cognome;
+    public $email;
+    public $telefono;
+    public $cellulare;
+    public $password;
+    public $password_confirmation;
+    public $isLoading = false; // Make sure this is defined
+
+    protected $rules = [
+        'password' => 'required|min:6|confirmed',
+        'password_confirmation' => 'required|min:6',
+    ];
+
+    protected $messages = [
+        'password.required' => 'La password è obbligatoria',
+        'password.min' => 'La password deve essere di almeno 6 caratteri',
+        'password.confirmed' => 'Le password non coincidono',
+        'password_confirmation.required' => 'Conferma la password',
+    ];
+
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
+    public function mount()
+    {
+        $currentUser = Auth::user();
+
+        if ($currentUser->first_login_completed) {
+            return redirect('/dashboard');
+        }
+
+        $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 completeProfile()
+    {
+        $this->isLoading = true;
+
+        $this->validate();
+
+        $currentUser = Auth::user();
+
+        try {
+            DB::beginTransaction();
+
+            $user = \App\Models\User::findOrFail($currentUser->id);
+            $oldEmail = $user->email;
+
+            $user->password = Hash::make($this->password);
+            $user->first_login_completed = true;
+            $user->email_verified_at = now();
+            $user->save();
+
+            $this->updateMasterDatabase($currentUser, $oldEmail);
+
+            DB::commit();
+
+            $this->sendAccountActivationEmail($user);
+
+            session()->flash('success', 'Profilo completato con successo! Benvenuto in Leezard.cloud.');
+
+            $this->isLoading = false;
+
+            return redirect('/dashboard');
+
+        } catch (\Exception $e) {
+            DB::rollback();
+
+            Log::error('First login profile completion failed', [
+                'user_id' => $currentUser->id,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+
+            $this->isLoading = false;
+            session()->flash('error', 'Errore durante il completamento del profilo: ' . $e->getMessage());
+        }
+    }
+
+    private function updateMasterDatabase($currentUser, $oldEmail)
+    {
+        try {
+            $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 = [
+                'password' => Hash::make($this->password),
+                'first_login_completed' => true,
+                'email_verified_at' => now(),
+                'updated_at' => now()
+            ];
+
+            $updated = DB::connection('master_temp')
+                ->table('users')
+                ->where('email', $oldEmail)
+                ->update($updateData);
+
+            if ($updated) {
+                Log::info('Successfully updated user in master database during first login', [
+                    'old_email' => $oldEmail,
+                    'new_email' => $this->email
+                ]);
+            }
+
+            DB::purge('master_temp');
+        } catch (\Exception $e) {
+            Log::error('Failed to update master database during first login', [
+                'error' => $e->getMessage(),
+                'user_id' => $currentUser->id,
+                'old_email' => $oldEmail,
+                'new_email' => $this->email
+            ]);
+            throw $e;
+        }
+    }
+
+    private function sendAccountActivationEmail($user)
+    {
+        try {
+            $emailData = [
+                'name' => $user->name,
+                'email' => $user->email,
+                'login_url' => url('/'),
+                'activation_time' => now()->format('d/m/Y H:i'),
+                'platform_name' => 'Leezard.cloud'
+            ];
+
+            Mail::send('emails.account-activated', $emailData, function ($message) use ($user) {
+                $message->to($user->email, $user->name)
+                        ->subject('Il tuo account è attivo – Benvenuto in Leezard.cloud')
+                        ->from(config('mail.from.address'), config('mail.from.name'));
+            });
+
+            Log::info('Account activation email sent after first login completion', [
+                'user_id' => $user->id,
+                'email' => $user->email
+            ]);
+
+        } catch (\Exception $e) {
+            Log::error('Failed to send account activation email', [
+                'user_id' => $user->id,
+                'email' => $user->email,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+
+    public function render()
+    {
+        return view('livewire.first-login');
+    }
+}

+ 76 - 127
app/Http/Livewire/Member.php

@@ -7,6 +7,7 @@ use Livewire\Attributes\Url;
 use Livewire\WithFileUploads;
 use Livewire\WithFileUploads;
 use Livewire\WithPagination;
 use Livewire\WithPagination;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
+use App\Http\Middleware\TenantMiddleware;
 
 
 use DateTime;
 use DateTime;
 
 
@@ -31,7 +32,6 @@ class Member extends Component
 
 
     public $selectedCourseMember = 0;
     public $selectedCourseMember = 0;
 
 
-    public $hasCertificate = false;
     protected $messages = [
     protected $messages = [
         'birth_nation_id.required' => 'birth_nation_id',
         'birth_nation_id.required' => 'birth_nation_id',
         'birth_province_id.required' => 'birth_province_id',
         'birth_province_id.required' => 'birth_province_id',
@@ -154,6 +154,8 @@ class Member extends Component
     public $already_existing = false;
     public $already_existing = false;
     private $fileService;
     private $fileService;
 
 
+    public $hasCertificate = false;
+
     protected $rules = [
     protected $rules = [
         'first_name' => 'required',
         'first_name' => 'required',
         'last_name' => 'required',
         'last_name' => 'required',
@@ -374,19 +376,6 @@ class Member extends Component
 
 
             $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
             $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
 
 
-            /*
-
-            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);
-                // 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;
-            } else {
-                $this->course_price = 0;
-                $this->course_subscription_price = 0;
-                $this->course_exist = false;
-            }*/
         } else {
         } else {
             $this->course_price = 0;
             $this->course_price = 0;
             $this->course_subscription_price = 0;
             $this->course_subscription_price = 0;
@@ -557,14 +546,6 @@ class Member extends Component
 
 
         $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
         $this->getCategories(\App\Models\Category::select('id', 'name')->where('parent_id', null)->orderBy('name')->get(), 0);
 
 
-
-
-        // $this->getCourses(\App\Models\Course::select('id', 'name')->where('parent_id', null)->get(), 0);
-
-        /*$this->nations = \App\Models\Nation::select('id', 'name')->orderBy('name')->get();
-        $this->provinces = \App\Models\Province::where('nation_id', 1)->orderBy('name')->get();
-        $this->cities = \App\Models\City::where('province_id', 178)->orderBy('name')->orderBy('name')->get();*/
-
         $c = \App\Models\Causal::where('type', 'IN')->where('money', true)->first();
         $c = \App\Models\Causal::where('type', 'IN')->where('money', true)->first();
         if ($c)
         if ($c)
             $this->causalId = $c->id;
             $this->causalId = $c->id;
@@ -573,13 +554,11 @@ class Member extends Component
             $this->showDetailF($_GET["member_detail"]);
             $this->showDetailF($_GET["member_detail"]);
             $this->refreshAfter = 1;
             $this->refreshAfter = 1;
         }
         }
-        /*$this->birthNations = \App\Models\Nation::select('id', 'name')->orderBy('name')->get();
-        $this->birthProvinces = \App\Models\Province::where('nation_id', 1)->orderBy('name')->get();
-        $this->birthCities = \App\Models\City::where('province_id', 178)->orderBy('name')->orderBy('name')->get();*/
     }
     }
 
 
     public function boot(){
     public function boot(){
         $this->fileService = app(\App\Services\MemberFileService::class);
         $this->fileService = app(\App\Services\MemberFileService::class);
+        app(abstract: TenantMiddleware::class)->setupTenantConnection();
 
 
     }
     }
 
 
@@ -653,37 +632,6 @@ class Member extends Component
         } else
         } else
             $this->isBirthItaly = false;
             $this->isBirthItaly = false;
     }
     }
-
-    /*public function loadProvinces()
-    {
-        $n = \App\Models\Nation::findOrFail($this->nation_id);
-        $this->isItaly = $n->is_italy;
-        $this->provinces = \App\Models\Province::where('nation_id', $this->nation_id)->orderBy('name')->get();
-        $this->cities = array();
-        $this->selectId++;
-    }
-
-    public function loadCities()
-    {
-        $this->cities = \App\Models\City::where('province_id', $this->province_id)->orderBy('name')->orderBy('name')->get();
-        $this->selectId++;
-    }
-
-    public function loadBirthProvinces()
-    {
-        $n = \App\Models\Nation::findOrFail($this->birth_nation_id);
-        $this->isBirthItaly = $n->is_italy;
-        $this->birthProvinces = \App\Models\Province::where('nation_id', $this->birth_nation_id)->orderBy('name')->get();
-        $this->birthCities = array();
-        $this->selectId++;
-    }
-
-    public function loadBirthCities()
-    {
-        $this->birthCities = \App\Models\City::where('province_id', $this->birth_province_id)->get();
-        $this->selectId++;
-    }
-    */
     public function search()
     public function search()
     {
     {
         if ($this->searchTxt != '') {
         if ($this->searchTxt != '') {
@@ -724,61 +672,43 @@ class Member extends Component
             if (!in_array($cN, $this->course_names))
             if (!in_array($cN, $this->course_names))
                 $this->course_names[] = $cN;
                 $this->course_names[] = $cN;
         }
         }
-        //$this->course_names = \App\Models\Course::orderBy('name')->groupBy('name')->pluck('name');
 
 
 
 
         $datas = [];
         $datas = [];
-        // $this->emit('destroy-data-table');
         if (false) {
         if (false) {
 
 
             if (!$this->advanced) {
             if (!$this->advanced) {
-                /*if ($this->search != '')
-                    $datas = \App\Models\Member::select('members.*') // , \DB::raw('SUM(records.id) As total'))
-                        ->where('first_name', 'LIKE', '%' . $this->search . '%')
-                        ->orWhere('last_name', 'LIKE', '%' . $this->search . '%')
-                        ->orWhere('email', 'LIKE', '%' . $this->search . '%');
-                    $this->records = \App\Models\Member::where('first_name', 'LIKE', '%' . $this->search . '%')->orWhere('last_name', 'LIKE', '%' . $this->search . '%')->orWhere('email', 'LIKE', '%' . $this->search . '%')->get();
-                else*/
                 $this->records = \App\Models\Member::select('id', 'first_name', 'last_name', 'phone')->get();
                 $this->records = \App\Models\Member::select('id', 'first_name', 'last_name', 'phone')->get();
-                //$datas = \App\Models\Member::select('members.*');
             } else {
             } else {
-                //$this->records = \App\Models\Member::where('id', '>', 0);
                 $datas = \App\Models\Member::select('members.*')->where('id', '>', 0);
                 $datas = \App\Models\Member::select('members.*')->where('id', '>', 0);
                 if (sizeof($this->filterCard) > 0) {
                 if (sizeof($this->filterCard) > 0) {
                     $card_ids = \App\Models\MemberCard::whereIn('card_id', $this->filterCard)->pluck('member_id');
                     $card_ids = \App\Models\MemberCard::whereIn('card_id', $this->filterCard)->pluck('member_id');
-                    //$this->records->whereIn('id', $card_ids);
                     $datas = $datas->whereIn('id', $card_ids);
                     $datas = $datas->whereIn('id', $card_ids);
                 }
                 }
                 if (sizeof($this->filterCategory) > 0) {
                 if (sizeof($this->filterCategory) > 0) {
                     $cats_ids = \App\Models\MemberCategory::whereIn('category_id', $this->filterCategory)->pluck('member_id');
                     $cats_ids = \App\Models\MemberCategory::whereIn('category_id', $this->filterCategory)->pluck('member_id');
-                    //$this->records->whereIn('id', $cats_ids);
                     $datas = $datas->whereIn('id', $cats_ids);
                     $datas = $datas->whereIn('id', $cats_ids);
                 }
                 }
                 $certs = [];
                 $certs = [];
 
 
                 if ($this->filterCertNormal > 0) {
                 if ($this->filterCertNormal > 0) {
                     $normal = \App\Models\MemberCertificate::where('type', 'N')->pluck('member_id');
                     $normal = \App\Models\MemberCertificate::where('type', 'N')->pluck('member_id');
-                    //$this->records->whereIn('id', $normal);
                     $datas = $datas->whereIn('id', $normal);;
                     $datas = $datas->whereIn('id', $normal);;
                 }
                 }
                 if ($this->filterCertAgonistic > 0) {
                 if ($this->filterCertAgonistic > 0) {
                     $agonistic = \App\Models\MemberCertificate::where('type', 'A')->pluck('member_id');
                     $agonistic = \App\Models\MemberCertificate::where('type', 'A')->pluck('member_id');
-                    //$this->records->whereIn('id', $agonistic);
                     $datas = $datas->whereIn('id', $agonistic);
                     $datas = $datas->whereIn('id', $agonistic);
                 }
                 }
                 if ($this->filterCertScaduto > 0) {
                 if ($this->filterCertScaduto > 0) {
                     $scaduto = \App\Models\MemberCertificate::where('expire_date', '<', date("Y-m-d"))->pluck('member_id');
                     $scaduto = \App\Models\MemberCertificate::where('expire_date', '<', date("Y-m-d"))->pluck('member_id');
-                    //$this->records->whereIn('id', $scaduto);
                     $datas = $datas->whereIn('id', $scaduto);
                     $datas = $datas->whereIn('id', $scaduto);
                 }
                 }
                 if ($this->filterCertInScadenza > 0) {
                 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');
                     $scaduto = \App\Models\MemberCertificate::whereBetween('expire_date', [date("Y-m-d"), date("Y-m-d", strtotime("+1 month"))])->pluck('member_id');
-                    //$this->records->whereIn('id', $scaduto);
                     $datas = $datas->whereIn('id', $scaduto);
                     $datas = $datas->whereIn('id', $scaduto);
                 }
                 }
                 if (sizeof($certs) > 0) {
                 if (sizeof($certs) > 0) {
                     $datas = $datas->whereIn('id', $certs);
                     $datas = $datas->whereIn('id', $certs);
-                    //$this->records->whereIn('id', $certs);
                 }
                 }
                 $this->records = $datas->get();
                 $this->records = $datas->get();
             }
             }
@@ -791,16 +721,6 @@ class Member extends Component
                 $r->certificate = $r->hasCertificate()["date"];
                 $r->certificate = $r->hasCertificate()["date"];
                 $r->state = $r->getStatus()["status"];
                 $r->state = $r->getStatus()["status"];
             }
             }
-            /*
-            if ($this->sortAsc)
-                $this->records = $this->records->sortBy($this->sortField);
-            else
-                $this->records = $this->records->sortByDesc($this->sortField);
-            */
-
-            //$datas = $datas->get(); // ->orderBy($this->sortField, $this->sortAsc ? 'ASC' : 'DESC')->paginate(10);
-
-            //$this->records = $this->records->get();
 
 
 
 
             $this->emit('load-data-table');
             $this->emit('load-data-table');
@@ -812,7 +732,6 @@ class Member extends Component
         $this->loadMemberCertificates();
         $this->loadMemberCertificates();
 
 
         $this->courses = \App\Models\Course::select('id', 'name')->where('type', $this->course_course_type)->get();
         $this->courses = \App\Models\Course::select('id', 'name')->where('type', $this->course_course_type)->get();
-        // $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->where('enabled', true)->get();
 
 
         return view('livewire.member', ['datas' => $datas]);
         return view('livewire.member', ['datas' => $datas]);
     }
     }
@@ -856,25 +775,21 @@ class Member extends Component
     public function loadMemberCards()
     public function loadMemberCards()
     {
     {
         $this->member_cards = \App\Models\MemberCard::where('member_id', $this->dataId)->get();
         $this->member_cards = \App\Models\MemberCard::where('member_id', $this->dataId)->get();
-        // return view('livewire.member');
     }
     }
 
 
     public function loadMemberCourses()
     public function loadMemberCourses()
     {
     {
         $this->member_courses = \App\Models\MemberCourse::where('member_id', $this->dataId)->get();
         $this->member_courses = \App\Models\MemberCourse::where('member_id', $this->dataId)->get();
-        // return view('livewire.member');
     }
     }
 
 
     public function loadMemberCategories()
     public function loadMemberCategories()
     {
     {
         $this->member_categories = \App\Models\MemberCategory::where('member_id', $this->dataId)->get();
         $this->member_categories = \App\Models\MemberCategory::where('member_id', $this->dataId)->get();
-        // return view('livewire.member');
     }
     }
 
 
     public function loadMemberCertificates()
     public function loadMemberCertificates()
     {
     {
         $this->member_certificates = \App\Models\MemberCertificate::where('member_id', $this->dataId)->orderBy('expire_date', 'DESC')->get();
         $this->member_certificates = \App\Models\MemberCertificate::where('member_id', $this->dataId)->orderBy('expire_date', 'DESC')->get();
-        // return view('livewire.member');
     }
     }
 
 
     public function showDetailF($id)
     public function showDetailF($id)
@@ -1245,10 +1160,6 @@ class Member extends Component
                 $interval = $date1->diff($date2);
                 $interval = $date1->diff($date2);
                 $this->age = $interval->y . " anni";
                 $this->age = $interval->y . " anni";
                 $this->under18 = $interval->y < 18;
                 $this->under18 = $interval->y < 18;
-                //$this->provinces = \App\Models\Province::where('nation_id', $this->nation_id)->get();
-                //$this->cities = \App\Models\City::where('province_id', $this->province_id)->get();
-                //$this->birthProvinces = \App\Models\Province::where('nation_id', $this->birth_nation_id)->get();
-                //$this->birthCities = \App\Models\City::where('province_id', $this->birth_province_id)->get();
                 $this->update = true;
                 $this->update = true;
                 $this->add = false;
                 $this->add = false;
 
 
@@ -2128,17 +2039,6 @@ class Member extends Component
         } else {
         } else {
             $this->course_months[] = array("m" => $m, "status" => "");
             $this->course_months[] = array("m" => $m, "status" => "");
         }
         }
-
-        /*
-        if (in_array($m, $this->course_months))
-        {
-            $i = array_search($m, $this->course_months);
-            array_splice($this->course_months, $i, 1);
-        }
-        else
-        {
-            $this->course_months[] = $m;
-        }*/
     }
     }
 
 
     public function newPayment($course)
     public function newPayment($course)
@@ -2313,7 +2213,6 @@ class Member extends Component
                     }
                     }
                     if ($payed < $price)
                     if ($payed < $price)
                         $class = 'orange half';
                         $class = 'orange half';
-                    //$class .= $extraC;
                 }
                 }
             }
             }
         }
         }
@@ -2422,31 +2321,82 @@ class Member extends Component
         }
         }
         return $ret;
         return $ret;
     }
     }
-
-    private function checkCourseAvailability()
+    public function archive($id)
     {
     {
-        $this->active = $this->getActiveStatus();
-        $this->hasCertificate = $this->checkHasCertificate();
+        try {
+            $member = \App\Models\Member::findOrFail($id);
 
 
-        $this->emit('course-availability-updated');
-    }
+            $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
+            ]);
 
 
-    private function getActiveStatus()
-    {
-        if ($this->dataId > 0) {
-            $member = \App\Models\Member::find($this->dataId);
-            if ($member) {
-                return $member->isActive();
-            }
-        }
-        return ["status" => 0, "date" => null];
-    }
-    private function checkHasCertificate()
-    {
-        if ($this->dataId > 0) {
-            return $this->member_certificates->where('expire_date', '>=', date('Y-m-d'))->count() > 0;
+            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()
+            ]);
         }
         }
-        return false;
     }
     }
 }
 }
 
 
@@ -2872,7 +2822,6 @@ class codicefiscale
         $codice .= $this->_calcolaCifraControllo($codice);
         $codice .= $this->_calcolaCifraControllo($codice);
 
 
         if (strlen($codice) != 16) {
         if (strlen($codice) != 16) {
-            //$this->_setError(self::ERR_GENERIC);
             return 'ERROR';
             return 'ERROR';
         }
         }
 
 

+ 46 - 24
app/Http/Livewire/PaymentMethod.php

@@ -3,12 +3,14 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 use Livewire\Component;
 use Livewire\Component;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class PaymentMethod extends Component
 class PaymentMethod extends Component
 {
 {
     public $records, $name, $enabled, $money, $type, $corrispettivo_fiscale, $dataId, $bank_id, $update = false, $add = false;
     public $records, $name, $enabled, $money, $type, $corrispettivo_fiscale, $dataId, $bank_id, $update = false, $add = false;
     public $paymentMethods = [];
     public $paymentMethods = [];
     public $banks = array();
     public $banks = array();
+    public $showHidden = false;
 
 
     protected $rules = [
     protected $rules = [
         'name' => 'required'
         'name' => 'required'
@@ -21,10 +23,14 @@ class PaymentMethod extends Component
     public $sortField ='name';
     public $sortField ='name';
     public $sortAsc = true;
     public $sortAsc = true;
 
 
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
     public function sortBy($field)
     public function sortBy($field)
     {
     {
-        if($this->sortField === $field)
-        {
+        if($this->sortField === $field) {
             $this->sortAsc = ! $this->sortAsc;
             $this->sortAsc = ! $this->sortAsc;
         } else {
         } else {
             $this->sortAsc = true;
             $this->sortAsc = true;
@@ -43,26 +49,53 @@ class PaymentMethod extends Component
     }
     }
 
 
     public function mount(){
     public function mount(){
-
         if(Auth::user()->level != env('LEVEL_ADMIN', 0))
         if(Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
-            $this->banks = \App\Models\Bank::select('id', 'name')->get();
+        $this->banks = \App\Models\Bank::select('id', 'name')->get();
+
+        $this->loadPaymentMethodOptions();
+    }
+
+    public function hide($id)
+    {
+        try {
+            \App\Models\PaymentMethod::whereId($id)->update(['hidden' => true]);
+            session()->flash('success', 'Metodo pagamento nascosto');
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
+
+    public function show($id)
+    {
+        try {
+            \App\Models\PaymentMethod::whereId($id)->update(['hidden' => false]);
+            session()->flash('success', 'Metodo pagamento ripristinato');
+        } catch (\Exception $ex) {
+            session()->flash('error', 'Errore (' . $ex->getMessage() . ')');
+        }
+    }
 
 
-            // Load predefined payment methods from database
-            $this->loadPaymentMethodOptions();    }
+    public function toggleShowHidden()
+    {
+        $this->showHidden = !$this->showHidden;
+    }
 
 
     public function render()
     public function render()
     {
     {
-        $this->records = \App\Models\PaymentMethod::all();
-        foreach($this->records as $r)
-        {
+        if ($this->showHidden) {
+            $this->records = \App\Models\PaymentMethod::all();
+        } else {
+            $this->records = \App\Models\PaymentMethod::where(function ($query) {
+                $query->where('hidden', false)->orWhereNull('hidden');
+            })->get();
+        }
+
+        foreach($this->records as $r) {
             $r->bank = $r->bank ? $r->bank->name : '';
             $r->bank = $r->bank ? $r->bank->name : '';
         }
         }
-        /*if ($this->sortAsc)
-            $this->records = $this->records->sortBy($this->sortField);
-        else
-            $this->records = $this->records->sortByDesc($this->sortField);*/
+
         return view('livewire.payment_method');
         return view('livewire.payment_method');
     }
     }
 
 
@@ -141,17 +174,6 @@ class PaymentMethod extends Component
         $this->resetFields();
         $this->resetFields();
     }
     }
 
 
-    public function delete($id)
-    {
-        try{
-            \App\Models\PaymentMethod::find($id)->delete();
-            session()->flash('success',"Metodo pagamento eliminato");
-            return redirect(request()->header('Referer'));
-        }catch(\Exception $e){
-            session()->flash('error','Errore (' . $e->getMessage() . ')');
-        }
-    }
-
     protected function loadPaymentMethodOptions()
     protected function loadPaymentMethodOptions()
     {
     {
         $this->paymentMethods = [
         $this->paymentMethods = [

+ 179 - 15
app/Http/Livewire/Profile.php

@@ -1,9 +1,14 @@
 <?php
 <?php
 
 
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
+
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 use Livewire\Component;
 use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+use Illuminate\Support\Facades\Mail;
 
 
 class Profile extends Component
 class Profile extends Component
 {
 {
@@ -16,9 +21,15 @@ class Profile extends Component
     public $cellulare;
     public $cellulare;
     public $password;
     public $password;
 
 
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
+
     public function mount()
     public function mount()
     {
     {
-        $user = Auth::user();
+        $currentUser = Auth::user();
+        $user = \App\Models\User::findOrFail($currentUser->id);
         $this->name = $user->name;
         $this->name = $user->name;
         $this->cognome = $user->cognome;
         $this->cognome = $user->cognome;
         $this->email = $user->email;
         $this->email = $user->email;
@@ -26,7 +37,6 @@ class Profile extends Component
         $this->cellulare = $user->cellulare;
         $this->cellulare = $user->cellulare;
     }
     }
 
 
-
     public function enableEditMode()
     public function enableEditMode()
     {
     {
         $this->editMode = true;
         $this->editMode = true;
@@ -41,31 +51,150 @@ class Profile extends Component
             'password' => 'nullable|min:6',
             'password' => 'nullable|min:6',
         ]);
         ]);
 
 
-        $user = Auth::user();
-        $user->name = $this->name;
-        $user->cognome = $this->cognome;
-        $user->email = $this->email;
-        $user->telefono = $this->telefono;
-        $user->cellulare = $this->cellulare;
+        $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
+            ]);
 
 
-        if (!empty($this->password)) {
-            $user->password = Hash::make($this->password);
+            // 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();
 
 
-        $user->save();
+            Log::info('Updating master database', [
+                'current_connection' => $currentConnection,
+                'current_database' => $currentDatabase,
+                'user_id' => $currentUser->id,
+                'old_email' => $oldEmail,
+                'new_email' => $this->email
+            ]);
 
 
-        session()->flash('message', 'Profilo aggiornato con successo!');
+            $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,
+            ];
 
 
-        $this->editMode = false;
+            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()
     public function cancel()
     {
     {
         $this->editMode = false;
         $this->editMode = false;
         $this->password = '';
         $this->password = '';
+
+        $this->mount();
     }
     }
 
 
-    private function resetInputFields(){
+    private function resetInputFields()
+    {
         $this->name = '';
         $this->name = '';
         $this->cognome = '';
         $this->cognome = '';
         $this->email = '';
         $this->email = '';
@@ -73,5 +202,40 @@ class Profile extends Component
         $this->cellulare = '';
         $this->cellulare = '';
         $this->password = '';
         $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;
+        }
+    }
+}

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

@@ -136,7 +136,7 @@ class Province extends Component
             \App\Models\Province::find($id)->delete();
             \App\Models\Province::find($id)->delete();
             session()->flash('success',"Provincia eliminata");
             session()->flash('success',"Provincia eliminata");
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 }
 }

+ 3 - 3
app/Http/Livewire/Rate.php

@@ -66,7 +66,7 @@ class Rate extends Component
             $this->course_subscriptions = \App\Models\CourseSubscription::select('*')->whereIn('id', $course_subscription_ids)->where('enabled', true)->get();
             $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();
         //$this->course_subscriptions = \App\Models\CourseSubscription::select('*')->where('enabled', true)->get();
 
 
 
 
@@ -243,8 +243,8 @@ class Rate extends Component
             $this->months[] = $m;
             $this->months[] = $m;
         }
         }
         else
         else
-        {            
-            if (($key = array_search($m, $this->months)) !== false) 
+        {
+            if (($key = array_search($m, $this->months)) !== false)
             {
             {
                 $this->months = array_slice($this->months, $key, 1);
                 $this->months = array_slice($this->months, $key, 1);
             }
             }

+ 5 - 0
app/Http/Livewire/Record.php

@@ -14,6 +14,7 @@ use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Mail;
 use Illuminate\Support\Facades\Mail;
 use App\Mail\ExportNotification;
 use App\Mail\ExportNotification;
 use App\Jobs\ExportPrimaNota;
 use App\Jobs\ExportPrimaNota;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class Record extends Component
 class Record extends Component
 {
 {
@@ -52,7 +53,11 @@ class Record extends Component
         'exportEmailSubject.required_if' => 'L\'oggetto dell\'email è obbligatorio.',
         'exportEmailSubject.required_if' => 'L\'oggetto dell\'email è obbligatorio.',
         'exportEmailSubject.max' => 'L\'oggetto dell\'email non può superare i 255 caratteri.',
         'exportEmailSubject.max' => 'L\'oggetto dell\'email non può superare i 255 caratteri.',
     ];
     ];
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
 
 
+    }
     public function hydrate()
     public function hydrate()
     {
     {
         $this->emit('load-select');
         $this->emit('load-select');

+ 87 - 5
app/Http/Livewire/RecordIN.php

@@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\Mail;
 use Illuminate\Support\Facades\Mail;
 use Illuminate\Contracts\Validation\Validator;
 use Illuminate\Contracts\Validation\Validator;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class RecordIN extends Component
 class RecordIN extends Component
 {
 {
@@ -119,6 +120,12 @@ class RecordIN extends Component
         'rows.*.amount.required' => 'L\'importo è obbligatorio',
         'rows.*.amount.required' => 'L\'importo è obbligatorio',
     ];
     ];
 
 
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+
+    }
+
     public function updatedMemberId()
     public function updatedMemberId()
     {
     {
         $this->emit('refresh');
         $this->emit('refresh');
@@ -667,7 +674,8 @@ class RecordIN extends Component
         $this->isDuplicate = true;
         $this->isDuplicate = true;
     }
     }
 
 
-    public function edit($id)
+
+public function edit($id)
     {
     {
         if (!isset($_GET["from"]) && $this->fromPage == '')
         if (!isset($_GET["from"]) && $this->fromPage == '')
             $this->fromPage = 'in';
             $this->fromPage = 'in';
@@ -675,15 +683,36 @@ class RecordIN extends Component
         //if ($this->hasFilter)
         //if ($this->hasFilter)
         $this->emit('hide-search');
         $this->emit('hide-search');
         $this->emit('load-select');
         $this->emit('load-select');
+
         try {
         try {
             $record = \App\Models\Record::findOrFail($id);
             $record = \App\Models\Record::findOrFail($id);
             if (!$record) {
             if (!$record) {
                 session()->flash('error', 'Movimento non trovato');
                 session()->flash('error', 'Movimento non trovato');
             } else {
             } else {
-                $this->member_id = $record->member_id;
+                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->supplier_id = $record->supplier_id;
                 $this->supplier_id = $record->supplier_id;
-                $this->payment_method_id = $record->payment_method_id;
-                $this->amount = $record->amount; // This is the net amount in DB
+                $this->amount = $record->amount;
                 $this->commercial = $record->commercial;
                 $this->commercial = $record->commercial;
                 $this->corrispettivo_fiscale = $record->corrispettivo_fiscale;
                 $this->corrispettivo_fiscale = $record->corrispettivo_fiscale;
                 $this->date = date("Y-m-d", strtotime($record->date));
                 $this->date = date("Y-m-d", strtotime($record->date));
@@ -702,8 +731,16 @@ class RecordIN extends Component
                 foreach ($rows as $i => $r) {
                 foreach ($rows as $i => $r) {
                     $grossAmount = $r->prediscount_amount ?? ($r->amount + $r->sconto);
                     $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[] = [
                     $this->rows[] = [
-                        'causal_id' => $r->causal_id,
+                        'causal_id' => $causalId,
                         'note' => $r->note,
                         'note' => $r->note,
                         'commercial' => $r->commercial,
                         'commercial' => $r->commercial,
                         'when' => json_decode($r->when),
                         'when' => json_decode($r->when),
@@ -718,6 +755,8 @@ class RecordIN extends Component
                     $this->currentReceip = $exist;
                     $this->currentReceip = $exist;
                     $this->parent = $this->currentReceip->parent;
                     $this->parent = $this->currentReceip->parent;
                 }
                 }
+
+                $this->refreshEditOptions();
             }
             }
         } catch (\Exception $ex) {
         } catch (\Exception $ex) {
             Log::error("Error in edit method: " . $ex->getMessage());
             Log::error("Error in edit method: " . $ex->getMessage());
@@ -725,6 +764,49 @@ class RecordIN extends Component
         }
         }
     }
     }
 
 
+    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)
     public function update($generate = false)
     {
     {
         Log::info("Starting update method");
         Log::info("Starting update method");

+ 7 - 0
app/Http/Livewire/RecordINOUT.php

@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Log;
 use SimpleXMLElement;
 use SimpleXMLElement;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\Storage;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class RecordINOUT extends Component
 class RecordINOUT extends Component
 {
 {
@@ -52,6 +53,12 @@ class RecordINOUT extends Component
     public $filterCausalsOut = null;
     public $filterCausalsOut = null;
 
 
     public $excludeCausals = array();
     public $excludeCausals = array();
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+
+    }
+
 
 
     public function mount()
     public function mount()
     {
     {

+ 3 - 2
app/Http/Livewire/RecordOUT.php

@@ -2,6 +2,7 @@
 
 
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
+use App\Http\Middleware\TenantMiddleware;
 use Livewire\Component;
 use Livewire\Component;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
@@ -115,6 +116,8 @@ class RecordOUT extends Component
     public function boot()
     public function boot()
     {
     {
         $this->recordFileService = app(RecordFileService::class);
         $this->recordFileService = app(RecordFileService::class);
+        app(TenantMiddleware::class)->setupTenantConnection();
+
     }
     }
 
 
     public function getSupplierProperty()
     public function getSupplierProperty()
@@ -376,7 +379,6 @@ class RecordOUT extends Component
     }
     }
 
 
 
 
-
     public function store()
     public function store()
     {
     {
         $this->emit('start-loading', 'Validazione dati...');
         $this->emit('start-loading', 'Validazione dati...');
@@ -520,7 +522,6 @@ class RecordOUT extends Component
                 } catch (\Exception $ex) {
                 } catch (\Exception $ex) {
                     Log::error("Failed to process file: " . $ex->getMessage());
                     Log::error("Failed to process file: " . $ex->getMessage());
                     Log::error("Stack trace: " . $ex->getTraceAsString());
                     Log::error("Stack trace: " . $ex->getTraceAsString());
-
                     DB::table('records')
                     DB::table('records')
                         ->where('id', $record->id)
                         ->where('id', $record->id)
                         ->update(['attachment_status' => 'failed']);
                         ->update(['attachment_status' => 'failed']);

+ 0 - 32
app/Http/Livewire/Reminder.php

@@ -39,17 +39,6 @@ class Reminder extends Component
     {
     {
 
 
         $data = [];
         $data = [];
-
-        /*
-        if ($this->filterT)
-        {
-
-            foreach($member_cards as $member_card)
-            {
-                $data[] = array('type' => 'Tessera', 'first_name' => $member_card->member->first_name, "last_name" => $member_card->member->last_name, 'date' => $member_card->expire_date);
-            }
-        }*/
-
         $member_cards = \App\Models\MemberCard::with('member')->where('expire_date', '>', date("y-m-d"));
         $member_cards = \App\Models\MemberCard::with('member')->where('expire_date', '>', date("y-m-d"));
         if ($this->filterFrom != '')
         if ($this->filterFrom != '')
             $member_cards = $member_cards->where('expire_date', '>=', $this->filterFrom);
             $member_cards = $member_cards->where('expire_date', '>=', $this->filterFrom);
@@ -97,21 +86,6 @@ class Reminder extends Component
                 $data[] = array('type' => 'Tessera ' . $member_card->card->name, 'first_name' => $member_card->member->first_name, "last_name" => $member_card->member->last_name, 'date' => $member_card->expire_date);
                 $data[] = array('type' => 'Tessera ' . $member_card->card->name, 'first_name' => $member_card->member->first_name, "last_name" => $member_card->member->last_name, 'date' => $member_card->expire_date);
             }
             }
         }
         }
-        /*
-        if ($this->filterC)
-        {
-            $member_certificates = \App\Models\MemberCertificate::with('member')->where('expire_date', '>', date("y-m-d"));
-            if ($this->filterFrom != '')
-                $member_certificates = $member_certificates->where('expire_date', '>=', $this->filterFrom);
-            if ($this->filterTo != '')
-                $member_certificates = $member_certificates->where('expire_date', '<=', $this->filterTo);
-            $member_certificates = $member_certificates->orderBy('expire_date')->get();
-            foreach($member_certificates as $member_certificate)
-            {
-                $data[] = array('type' => 'Certificato', 'first_name' => $member_certificate->member->first_name, "last_name" => $member_certificate->member->last_name, 'date' => $member_certificate->expire_date);
-            }
-        }
-        */
         if ($data)
         if ($data)
         {
         {
             $key_values = array_column($data, 'date');
             $key_values = array_column($data, 'date');
@@ -122,12 +96,6 @@ class Reminder extends Component
         {
         {
             $this->records = [];
             $this->records = [];
         }
         }
-
-        /*if ($this->sortAsc)
-            usort($this->records, function ($a, $b) {return $a[$this->sortField] > $b[$this->sortField];});
-        else
-            usort($this->records, function ($a, $b) {return $a[$this->sortField] < $b[$this->sortField];});*/
-
         $this->emit('load-data-table');
         $this->emit('load-data-table');
 
 
         return view('livewire.reminders');
         return view('livewire.reminders');

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

@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
 use App\Models\Course;
 use App\Models\Course;
 use App\Models\MemberCard;
 use App\Models\MemberCard;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class Reports extends Component
 class Reports extends Component
 {
 {
@@ -19,6 +20,12 @@ class Reports extends Component
     public $courses = [];
     public $courses = [];
     public $selectedCourse = null;
     public $selectedCourse = null;
 
 
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+
+    }
+
     public function mount()
     public function mount()
     {
     {
         if (Auth::user()->level != env('LEVEL_ADMIN', 0))
         if (Auth::user()->level != env('LEVEL_ADMIN', 0))
@@ -127,6 +134,23 @@ class Reports extends Component
         $this->seasonFilter = $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()
     public function getMonthlyTotals()
     {
     {
         Log::info('=== getMonthlyTotals called ===');
         Log::info('=== getMonthlyTotals called ===');
@@ -141,6 +165,7 @@ class Reports extends Component
 
 
         $incomeData = array_fill(0, 12, 0);
         $incomeData = array_fill(0, 12, 0);
         $expenseData = array_fill(0, 12, 0);
         $expenseData = array_fill(0, 12, 0);
+        $this->setupTenantConnection();
 
 
         $incomeRecords = DB::table('records')
         $incomeRecords = DB::table('records')
             ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
             ->join('records_rows', 'records.id', '=', 'records_rows.record_id')

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

@@ -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}");
+    }
+}

+ 3 - 2
app/Http/Livewire/Sponsor.php

@@ -19,6 +19,7 @@ class Sponsor extends Component
 
 
     public $contracts = array();
     public $contracts = array();
 
 
+    public $showReset = false;
     protected $rules = [
     protected $rules = [
         'name' => 'required'
         'name' => 'required'
     ];
     ];
@@ -227,7 +228,7 @@ class Sponsor extends Component
             \App\Models\Sponsor::find($id)->delete();
             \App\Models\Sponsor::find($id)->delete();
             session()->flash('success',"Sponsor eliminato");
             session()->flash('success',"Sponsor eliminato");
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 
 
@@ -352,7 +353,7 @@ class Sponsor extends Component
             \App\Models\SponsorContract::find($id)->delete();
             \App\Models\SponsorContract::find($id)->delete();
             session()->flash('success',"Contratto eliminato");
             session()->flash('success',"Contratto eliminato");
         }catch(\Exception $e){
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
         }
     }
     }
 
 

+ 46 - 9
app/Http/Livewire/Supplier.php

@@ -5,7 +5,7 @@ namespace App\Http\Livewire;
 use Livewire\Component;
 use Livewire\Component;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
-
+use App\Http\Middleware\TenantMiddleware;
 class Supplier extends Component
 class Supplier extends Component
 {
 {
     public $records, $name, $fiscal_code, $vat, $address, $zip_code, $nation_id, $province_id, $city_id, $referent, $website, $phone, $email, $enabled, $dataId, $update = false, $add = false;
     public $records, $name, $fiscal_code, $vat, $address, $zip_code, $nation_id, $province_id, $city_id, $referent, $website, $phone, $email, $enabled, $dataId, $update = false, $add = false;
@@ -14,7 +14,7 @@ class Supplier extends Component
 
 
     public $searchTxt;
     public $searchTxt;
     public $search;
     public $search;
-
+    public $showArchived = false;
     protected $rules = [
     protected $rules = [
         'name' => 'required',
         'name' => 'required',
         'vat' => 'nullable|unique:suppliers,vat',
         'vat' => 'nullable|unique:suppliers,vat',
@@ -26,6 +26,10 @@ class Supplier extends Component
         'vat.unique' => 'Un fornitore con questa Partita IVA esiste già',
         'vat.unique' => 'Un fornitore con questa Partita IVA esiste già',
         'fiscal_code.unique' => 'Un fornitore con questo Codice Fiscale esiste già'
         'fiscal_code.unique' => 'Un fornitore con questo Codice Fiscale esiste già'
     ];
     ];
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
 
 
 
 
     public function resetFields()
     public function resetFields()
@@ -67,7 +71,6 @@ class Supplier extends Component
 
 
     public function mount()
     public function mount()
     {
     {
-
         if (Auth::user()->level != env('LEVEL_ADMIN', 0))
         if (Auth::user()->level != env('LEVEL_ADMIN', 0))
             return redirect()->to('/dashboard');
             return redirect()->to('/dashboard');
 
 
@@ -82,6 +85,7 @@ class Supplier extends Component
             $this->showReset = true;
             $this->showReset = true;
         }
         }
     }
     }
+
     public function resetSearch()
     public function resetSearch()
     {
     {
         $this->showReset = false;
         $this->showReset = false;
@@ -91,10 +95,18 @@ class Supplier extends Component
 
 
     public function render()
     public function render()
     {
     {
-        $this->records = \App\Models\Supplier::with('nation')->get();
+        if ($this->showArchived) {
+            // Show all suppliers including archived ones
+            $this->records = \App\Models\Supplier::orderBy('name')->get();
+        } else {
+            // Show only non-archived suppliers
+            $this->records = \App\Models\Supplier::where(function($query) {
+                $query->where('archived', 0)->orWhereNull('archived');
+            })->orderBy('name')->get();
+        }
+
         return view('livewire.supplier');
         return view('livewire.supplier');
     }
     }
-
     public function hydrate()
     public function hydrate()
     {
     {
         $this->emit('load-select');
         $this->emit('load-select');
@@ -119,6 +131,7 @@ class Supplier extends Component
         $this->vat = trim($this->vat) === '' ? null : trim($this->vat);
         $this->vat = trim($this->vat) === '' ? null : trim($this->vat);
         $this->fiscal_code = trim($this->fiscal_code) === '' ? null : trim($this->fiscal_code);
         $this->fiscal_code = trim($this->fiscal_code) === '' ? null : trim($this->fiscal_code);
     }
     }
+
     public function store()
     public function store()
     {
     {
         $this->validate();
         $this->validate();
@@ -229,11 +242,36 @@ class Supplier extends Component
         $this->resetFields();
         $this->resetFields();
     }
     }
 
 
-    public function delete($id)
+    // Replace delete method with anonymize method
+    public function anonymize($id)
     {
     {
         try {
         try {
-            \App\Models\Supplier::find($id)->delete();
-            session()->flash('success', "Fornitore eliminato");
+            $supplier = \App\Models\Supplier::findOrFail($id);
+
+            // Anonymize all fields
+            $supplier->update([
+                'name' => 'Fornitore Anonimizzato',
+                'fiscal_code' => null,
+                'vat' => null,
+                'address' => null,
+                'zip_code' => null,
+                'nation_id' => null,
+                'province_id' => null,
+                'city_id' => null,
+                'referent' => null,
+                'website' => null,
+                'phone' => null,
+                'email' => null,
+                'referent_first_name' => null,
+                'referent_last_name' => null,
+                'referent_email' => null,
+                'referent_phone' => null,
+                'referent_mobile' => null,
+                'archived' => true, // Mark as archived
+                'enabled' => false // Also disable the supplier
+            ]);
+
+            session()->flash('success', "Fornitore anonimizzato");
             return redirect(request()->header('Referer'));
             return redirect(request()->header('Referer'));
         } catch (\Exception $e) {
         } catch (\Exception $e) {
             session()->flash('error', 'Errore (' . $e->getMessage() . ')');
             session()->flash('error', 'Errore (' . $e->getMessage() . ')');
@@ -276,7 +314,6 @@ class Supplier extends Component
         ];
         ];
     }
     }
 
 
-    // Updated update method
     public function update()
     public function update()
     {
     {
         // Clean empty fields first
         // Clean empty fields first

+ 624 - 36
app/Http/Livewire/User.php

@@ -3,12 +3,23 @@
 namespace App\Http\Livewire;
 namespace App\Http\Livewire;
 
 
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Mail;
 use Livewire\Component;
 use Livewire\Component;
+use App\Http\Middleware\TenantMiddleware;
+use Illuminate\Support\Facades\Auth;
 
 
 class User extends Component
 class User extends Component
 {
 {
-    public $records, $name,$cognome, $email, $password, $oldPassword, $level, $enabled, $dataId, $update = false, $add = false;
+    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;
     public $userExists = false;
+
     protected $rules = [
     protected $rules = [
         'name' => 'required',
         'name' => 'required',
         'cognome' => 'required',
         'cognome' => 'required',
@@ -23,7 +34,297 @@ class User extends Component
         'password.required' => 'La password è obbligatoria',
         'password.required' => 'La password è obbligatoria',
     ];
     ];
 
 
-    public function resetFields(){
+    /**
+     * 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->name = '';
         $this->cognome = '';
         $this->cognome = '';
         $this->email = '';
         $this->email = '';
@@ -36,12 +337,24 @@ class User extends Component
 
 
     public function render()
     public function render()
     {
     {
-        $this->records = \App\Models\User::select('id', 'name','cognome' ,'email', 'password', 'level', 'enabled')->get();
+        $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');
         return view('livewire.user');
     }
     }
 
 
     public function add()
     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->resetFields();
         $this->add = true;
         $this->add = true;
         $this->update = false;
         $this->update = false;
@@ -51,6 +364,13 @@ class User extends Component
 
 
     public function store()
     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', [
         Log::info('User store', [
             'name' => $this->name,
             'name' => $this->name,
             'cognome' => $this->cognome,
             'cognome' => $this->cognome,
@@ -58,6 +378,7 @@ class User extends Component
             'level' => $this->level,
             'level' => $this->level,
             'enabled' => $this->enabled
             'enabled' => $this->enabled
         ]);
         ]);
+
         $rules = [
         $rules = [
             'name' => 'required',
             'name' => 'required',
             'cognome' => 'required',
             'cognome' => 'required',
@@ -74,43 +395,113 @@ class User extends Component
             'password.required' => 'La password è obbligatoria',
             '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'
         ];
         ];
+
         $this->validate($rules, $messages);
         $this->validate($rules, $messages);
-        Log::info('User store', [
-            'name' => $this->name,
-            'cognome' => $this->cognome,
-            'email' => $this->email,
-            'level' => $this->level,
-            'enabled' => $this->enabled
-        ]);
+
+        $this->logCurrentDatabase('Before creating user in store()');
+
         try {
         try {
-            \App\Models\User::create([
+            $plainPassword = $this->password;
+
+            $hashedPassword = bcrypt($this->password);
+
+            $user = \App\Models\User::create([
                 'name' => $this->name,
                 'name' => $this->name,
                 'cognome' => $this->cognome,
                 'cognome' => $this->cognome,
                 'email' => $this->email,
                 'email' => $this->email,
-                'password' => bcrypt($this->password),
+                'password' => $hashedPassword,
                 'level' => $this->level,
                 'level' => $this->level,
                 'enabled' => $this->enabled
                 'enabled' => $this->enabled
             ]);
             ]);
-            Log::info('User created', [
+
+            $this->logCurrentDatabase('After creating user in tenant database');
+
+            Log::info('User created successfully in tenant database', [
+                'user_id' => $user->id,
                 'name' => $this->name,
                 'name' => $this->name,
                 'cognome' => $this->cognome,
                 'cognome' => $this->cognome,
                 'email' => $this->email,
                 'email' => $this->email,
                 'level' => $this->level,
                 'level' => $this->level,
-                'enabled' => $this->enabled
+                'enabled' => $this->enabled,
+                'database' => DB::connection()->getDatabaseName()
             ]);
             ]);
-            session()->flash('success','Utente creato');
+
+            $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->resetFields();
             $this->add = false;
             $this->add = false;
         } catch (\Exception $ex) {
         } catch (\Exception $ex) {
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            $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){
+    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 {
         try {
             $user = \App\Models\User::findOrFail($id);
             $user = \App\Models\User::findOrFail($id);
-            if( !$user) {
-                session()->flash('error','Dato non trovato');
+
+            $this->logCurrentDatabase('After finding user in edit()');
+
+            if (!$user) {
+                session()->flash('error', 'Utente non trovato');
             } else {
             } else {
                 $this->name = $user->name;
                 $this->name = $user->name;
                 $this->cognome = $user->cognome;
                 $this->cognome = $user->cognome;
@@ -121,48 +512,115 @@ class User extends Component
                 $this->add = false;
                 $this->add = false;
                 $this->enabled = $user->enabled;
                 $this->enabled = $user->enabled;
                 $this->userExists = true;
                 $this->userExists = true;
+                // Store old email for master database update
+                $this->oldEmail = $user->email;
             }
             }
-            Log::info('User edit', [
+
+            Log::info('User edit loaded', [
+                'user_id' => $id,
                 'name' => $this->name,
                 'name' => $this->name,
                 'cognome' => $this->cognome,
                 'cognome' => $this->cognome,
                 'email' => $this->email,
                 'email' => $this->email,
-                'level' => $this->level
+                'level' => $this->level,
+                'database' => DB::connection()->getDatabaseName()
             ]);
             ]);
         } catch (\Exception $ex) {
         } catch (\Exception $ex) {
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            $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()
     public function update()
     {
     {
-        $this->validate();
+        $this->logCurrentDatabase('Start of update() method');
+
+        $rules = [
+            'name' => 'required',
+            'cognome' => 'required',
+            'email' => 'required|email',
+            'password' => 'nullable|min:6'
+        ];
+
+        $this->validate($rules, $this->messages);
+
         try {
         try {
-            if ($this->pa)
-            \App\Models\User::whereId($this->dataId)->update([
+            $currentUser = \App\Models\User::findOrFail($this->dataId);
+            $oldEmail = $currentUser->email;
+            $oldName = $currentUser->name;
+
+            $updateData = [
                 'name' => $this->name,
                 'name' => $this->name,
                 'cognome' => $this->cognome,
                 'cognome' => $this->cognome,
                 'email' => $this->email,
                 'email' => $this->email,
-                'password' => bcrypt($this->password),
                 'level' => $this->level,
                 'level' => $this->level,
                 'enabled' => $this->enabled
                 'enabled' => $this->enabled
-            ]);
-            Log::info('User updated', [
+            ];
+
+            $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,
                 'name' => $this->name,
                 'cognome' => $this->cognome,
                 'cognome' => $this->cognome,
                 'email' => $this->email,
                 'email' => $this->email,
                 'level' => $this->level,
                 'level' => $this->level,
-                'enabled' => $this->enabled
+                'enabled' => $this->enabled,
+                'password_changed' => $passwordChanged,
+                'database' => DB::connection()->getDatabaseName()
             ]);
             ]);
-            session()->flash('success','Dato aggiornato');
+
+            $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->resetFields();
             $this->update = false;
             $this->update = false;
         } catch (\Exception $ex) {
         } catch (\Exception $ex) {
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            $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()
     public function cancel()
     {
     {
+        $this->logCurrentDatabase('In cancel() method');
+
         $this->resetFields();
         $this->resetFields();
         $this->add = false;
         $this->add = false;
         $this->update = false;
         $this->update = false;
@@ -172,11 +630,141 @@ class User extends Component
 
 
     public function delete($id)
     public function delete($id)
     {
     {
-        try{
-            \App\Models\User::find($id)->delete();
-            session()->flash('success',"Dato eliminato");
-        }catch(\Exception $e){
-            session()->flash('error','Errore (' . $e->getMessage() . ')');
+        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;
     }
     }
 }
 }

+ 5 - 1
app/Http/Livewire/Vat.php

@@ -4,6 +4,7 @@ namespace App\Http\Livewire;
 
 
 use Livewire\Component;
 use Livewire\Component;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Auth;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class Vat extends Component
 class Vat extends Component
 {
 {
@@ -21,7 +22,10 @@ class Vat extends Component
 
 
     public $sortField ='name';
     public $sortField ='name';
     public $sortAsc = true;
     public $sortAsc = true;
-
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+    }
     public function mount(){
     public function mount(){
 
 
         if(Auth::user()->level != env('LEVEL_ADMIN', 0))
         if(Auth::user()->level != env('LEVEL_ADMIN', 0))

+ 28 - 0
app/Http/Middleware/CheckFirstLoginCompleted.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+
+class CheckFirstLoginCompleted
+{
+    public function handle(Request $request, Closure $next)
+    {
+        $user = Auth::user();
+
+        if (!$user) {
+            return $next($request);
+        }
+
+        if (!$user->first_login_completed) {
+            if (!$request->routeIs(['first-login', 'logout', 'login'])) {
+                return redirect('/first-login')
+                    ->with('warning', 'Devi completare il tuo profilo prima di accedere alla piattaforma.');
+            }
+        }
+
+        return $next($request);
+    }
+}

+ 25 - 0
app/Http/Middleware/PasswordResetThrottle.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\RateLimiter;
+use Symfony\Component\HttpFoundation\Response;
+
+class PasswordResetThrottle
+{
+    public function handle(Request $request, Closure $next): Response
+    {
+        $key = 'password-reset:' . $request->ip();
+
+        if (RateLimiter::tooManyAttempts($key, 5)) {
+            $seconds = RateLimiter::availableIn($key);
+            return back()->with('error', "Troppi tentativi. Riprova tra {$seconds} secondi.");
+        }
+
+        RateLimiter::hit($key, 3600); // 1 hour
+
+        return $next($request);
+    }
+}

+ 60 - 0
app/Http/Middleware/TenantMiddleware.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class TenantMiddleware
+{
+    /**
+     * Handle an incoming request.
+     */
+    public function handle(Request $request, Closure $next): Response
+    {
+        if (! $request->user()) {
+            return redirect('/');
+        }
+
+        $this->setupTenantConnection($request->user());
+
+        return $next($request);
+    }
+
+    /**
+     * Set up the tenant database connection
+     */
+    public function setupTenantConnection($user = null)
+    {
+        $user = $user ?: auth()->user();
+
+        if ($user) {
+            Log::info('Setting database connection', [
+                'database' => $user->tenant_database,
+                'username' => $user->tenant_username,
+                'password' => $user->tenant_password
+            ]);
+
+            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');
+
+            session(['currentClient' => $user->tenant_database]);
+
+            Log::info('Current database after setup: ' . DB::connection()->getDatabaseName());
+            Log::info('Current default connection: ' . DB::getDefaultConnection());
+        }
+    }
+}

+ 6 - 0
app/Jobs/ProcessRecordAttachment.php

@@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
 use App\Services\RecordFileService;
 use App\Services\RecordFileService;
+use App\Http\Middleware\TenantMiddleware;
 
 
 class ProcessRecordAttachment implements ShouldQueue
 class ProcessRecordAttachment implements ShouldQueue
 {
 {
@@ -27,6 +28,11 @@ class ProcessRecordAttachment implements ShouldQueue
     public $tries = 3;
     public $tries = 3;
     public $backoff = [10, 30, 60];
     public $backoff = [10, 30, 60];
 
 
+    public function boot()
+    {
+        app(TenantMiddleware::class)->setupTenantConnection();
+
+    }
     public function __construct($recordId, $tempFilePath, $originalFileName, $type = 'OUT', $clientName = null)
     public function __construct($recordId, $tempFilePath, $originalFileName, $type = 'OUT', $clientName = null)
     {
     {
         $this->recordId = $recordId;
         $this->recordId = $recordId;

+ 32 - 0
app/Mail/CustomEmail.php

@@ -0,0 +1,32 @@
+<?php
+namespace App\Mail;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+
+class CustomEmail extends Mailable
+{
+    use Queueable, SerializesModels;
+
+    public $emailSubject;
+    public $emailContent;
+    public $recipientName;
+
+    public function __construct($subject, $content, $recipientName = null)
+    {
+        $this->emailSubject = $subject;
+        $this->emailContent = $content;
+        $this->recipientName = $recipientName;
+    }
+
+    public function build()
+    {
+        return $this->subject($this->emailSubject)
+                    ->view('emails.custom')
+                    ->with([
+                        'content' => $this->emailContent,
+                        'recipientName' => $this->recipientName
+                    ]);
+    }
+}

+ 1 - 0
app/Models/Causal.php

@@ -13,6 +13,7 @@ class Causal extends Model
         'parent_id',
         'parent_id',
         'name',
         'name',
         'type',
         'type',
+        'hidden',
         'money',
         'money',
         'corrispettivo_fiscale',
         'corrispettivo_fiscale',
         'no_receipt',
         'no_receipt',

+ 55 - 0
app/Models/DashboardNote.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Str;
+
+class DashboardNote extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'unique_id',
+        'text',
+        'completed',
+        'user_id'
+    ];
+
+    protected $casts = [
+        'completed' => 'boolean',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime'
+    ];
+
+    protected static function boot()
+    {
+        parent::boot();
+
+        static::creating(function ($note) {
+            if (empty($note->unique_id)) {
+                $note->unique_id = Str::uuid();
+            }
+        });
+    }
+
+    public function user()
+    {
+        return $this->belongsTo(User::class);
+    }
+
+    public function scopeActive($query)
+    {
+        return $query->where('completed', false);
+    }
+
+    public function scopeForUser($query, $userId = null)
+    {
+        if ($userId) {
+            return $query->where('user_id', $userId);
+        }
+
+        return $query->whereNull('user_id');
+    }
+}

+ 96 - 0
app/Models/EmailScheduled.php

@@ -0,0 +1,96 @@
+<?php
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class EmailScheduled extends Model
+{
+    use HasFactory;
+
+    protected $table = 'email_scheduled';
+
+    protected $fillable = [
+        'template_id',
+        'subject',
+        'content',
+        'scheduled_at',
+        'status',
+        'created_by',
+        'delivery_report'
+    ];
+
+    protected $casts = [
+        'scheduled_at' => 'datetime',
+        'delivery_report' => 'array',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    public function template()
+    {
+        return $this->belongsTo(EmailTemplate::class, 'template_id');
+    }
+
+    public function creator()
+    {
+        return $this->belongsTo(User::class, 'created_by');
+    }
+
+    public function recipients()
+    {
+        return $this->belongsToMany(User::class, 'email_scheduled_recipients', 'email_scheduled_id', 'user_id')
+                    ->withPivot(['email_address', 'status', 'error_message', 'sent_at'])
+                    ->withTimestamps();
+    }
+
+    public function recipientDetails()
+    {
+        return $this->hasMany(EmailScheduledRecipient::class, 'email_scheduled_id');
+    }
+
+    public function scopeScheduled($query)
+    {
+        return $query->where('status', 'scheduled');
+    }
+
+    public function scopeSent($query)
+    {
+        return $query->where('status', 'sent');
+    }
+
+    public function scopeFailed($query)
+    {
+        return $query->where('status', 'failed');
+    }
+
+    public function getIsScheduledAttribute()
+    {
+        return $this->status === 'scheduled';
+    }
+
+    public function getIsSentAttribute()
+    {
+        return $this->status === 'sent';
+    }
+
+    public function getCanBeCancelledAttribute()
+    {
+        return $this->status === 'scheduled' && $this->scheduled_at->isFuture();
+    }
+
+    public function getTotalRecipientsAttribute()
+    {
+        return $this->recipients()->count();
+    }
+
+    public function getSuccessfulSendsAttribute()
+    {
+        return $this->recipientDetails()->where('status', 'sent')->count();
+    }
+
+    public function getFailedSendsAttribute()
+    {
+        return $this->recipientDetails()->where('status', 'failed')->count();
+    }
+}

+ 59 - 0
app/Models/EmailScheduledRecipient.php

@@ -0,0 +1,59 @@
+<?php
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class EmailScheduledRecipient extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'email_scheduled_id',
+        'user_id',
+        'email_address',
+        'status',
+        'error_message',
+        'sent_at'
+    ];
+
+    protected $casts = [
+        'sent_at' => 'datetime',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    public function scheduledEmail()
+    {
+        return $this->belongsTo(EmailScheduled::class, 'email_scheduled_id');
+    }
+
+    public function user()
+    {
+        return $this->belongsTo(User::class);
+    }
+
+    public function markAsSent()
+    {
+        $this->update([
+            'status' => 'sent',
+            'sent_at' => now()
+        ]);
+    }
+
+    public function markAsFailed($errorMessage = null)
+    {
+        $this->update([
+            'status' => 'failed',
+            'error_message' => $errorMessage
+        ]);
+    }
+
+    public function markAsBounced($errorMessage = null)
+    {
+        $this->update([
+            'status' => 'bounced',
+            'error_message' => $errorMessage
+        ]);
+    }
+}

+ 41 - 0
app/Models/EmailTemplate.php

@@ -0,0 +1,41 @@
+<?php
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Str;
+class EmailTemplate extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'name',
+        'content',
+        'created_by'
+    ];
+
+    protected $casts = [
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    public function creator()
+    {
+        return $this->belongsTo(User::class, 'created_by');
+    }
+
+    public function scheduledEmails()
+    {
+        return $this->hasMany(EmailScheduled::class, 'template_id');
+    }
+
+    public function getWordCountAttribute()
+    {
+        return str_word_count(strip_tags($this->content));
+    }
+
+    public function getPreviewAttribute()
+    {
+        return Str::limit(strip_tags($this->content), 100);
+    }
+}

+ 3 - 1
app/Models/Member.php

@@ -53,7 +53,9 @@ class Member extends Model
         'current_status',
         'current_status',
         'certificate',
         'certificate',
         'certificate_date',
         'certificate_date',
-        'to_complete'
+        'to_complete',
+        'is_archived',
+        'archived_date',
     ];
     ];
     public function nation()
     public function nation()
     {
     {

+ 1 - 0
app/Models/PaymentMethod.php

@@ -13,6 +13,7 @@ class PaymentMethod extends Model
         'name',
         'name',
         'money',
         'money',
         'type',
         'type',
+        'hidden',
         'corrispettivo_fiscale',
         'corrispettivo_fiscale',
         'enabled',
         'enabled',
         'bank_id'
         'bank_id'

+ 41 - 0
app/Models/SmsScheduled.php

@@ -0,0 +1,41 @@
+<?php
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class SmsScheduled extends Model
+{
+    use HasFactory;
+
+    protected $table = 'sms_scheduled';
+
+    protected $fillable = [
+        'template_id',
+        'content',
+        'scheduled_at',
+        'status',
+        'created_by'
+    ];
+
+    protected $casts = [
+        'scheduled_at' => 'datetime',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    public function template()
+    {
+        return $this->belongsTo(SmsTemplate::class, 'template_id');
+    }
+
+    public function creator()
+    {
+        return $this->belongsTo(User::class, 'created_by');
+    }
+
+    public function recipients()
+    {
+        return $this->belongsToMany(User::class, 'sms_scheduled_recipients', 'sms_scheduled_id', 'user_id');
+    }
+}

+ 31 - 0
app/Models/SmsTemplate.php

@@ -0,0 +1,31 @@
+<?php
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class SmsTemplate extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'name',
+        'content',
+        'created_by'
+    ];
+
+    protected $casts = [
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    public function creator()
+    {
+        return $this->belongsTo(User::class, 'created_by');
+    }
+
+    public function getCharacterCountAttribute()
+    {
+        return strlen($this->content);
+    }
+}

+ 2 - 1
app/Models/Supplier.php

@@ -27,7 +27,8 @@ class Supplier extends Model
         'referent_email',
         'referent_email',
         'referent_phone',
         'referent_phone',
         'referent_mobile',
         'referent_mobile',
-        'enabled'
+        'enabled',
+        'archived',
     ];
     ];
 
 
     public function nation()
     public function nation()

+ 8 - 3
app/Models/User.php

@@ -24,7 +24,10 @@ class User extends Authenticatable
         'cellulare',
         'cellulare',
         'email',
         'email',
         'password',
         'password',
-        'level'
+        'level',
+        'enabled',
+        'first_login_completed',
+        'first_login_at',
     ];
     ];
 
 
     /**
     /**
@@ -43,6 +46,8 @@ class User extends Authenticatable
      * @var array<string, string>
      * @var array<string, string>
      */
      */
     protected $casts = [
     protected $casts = [
-        'email_verified_at' => 'datetime',
-    ];
+    'email_verified_at' => 'datetime',
+    'first_login_at' => 'datetime',
+    'first_login_completed' => 'boolean',
+    'enabled' => 'boolean',];
 }
 }

+ 24 - 0
app/Providers/MigrationServiceProvider.php

@@ -0,0 +1,24 @@
+<?php
+namespace App\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use App\Console\Commands\MigrateMaster;
+use App\Console\Commands\MigrateTenants;
+
+class MigrationServiceProvider extends ServiceProvider
+{
+    public function register()
+    {
+        $this->app->singleton(\App\Services\MigrationService::class);
+    }
+
+    public function boot()
+    {
+        if ($this->app->runningInConsole()) {
+            $this->commands([
+                MigrateMaster::class,
+                MigrateTenants::class,
+            ]);
+        }
+    }
+}

+ 252 - 0
app/Services/MigrationService.php

@@ -0,0 +1,252 @@
+<?php
+namespace App\Services;
+
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Artisan;
+use App\Models\User;
+use App\Database\Migrations\MasterMigration;
+use App\Database\Migrations\TenantMigration;
+
+class MigrationService
+{
+    protected $masterDatabase = 'leezard_master';
+
+    public function runMasterMigrations()
+    {
+        $this->setupMasterConnection();
+
+        $masterMigrations = $this->getMasterMigrationFiles();
+
+        if (empty($masterMigrations)) {
+            return ['message' => 'No master migrations found.', 'migrations' => []];
+        }
+
+        // Create a temporary directory for master migrations only
+        $tempPath = storage_path('app/temp_master_migrations');
+        if (!File::exists($tempPath)) {
+            File::makeDirectory($tempPath, 0755, true);
+        }
+
+        // Copy only master migrations to temp directory
+        foreach ($masterMigrations as $migration) {
+            $source = database_path('migrations/' . $migration);
+            $destination = $tempPath . '/' . $migration;
+            File::copy($source, $destination);
+        }
+
+        try {
+            // Run migrations from temp directory
+            Artisan::call('migrate', [
+                '--database' => 'master',
+                '--path' => 'storage/app/temp_master_migrations'
+            ]);
+
+            $output = Artisan::output();
+        } finally {
+            // Clean up temp directory
+            File::deleteDirectory($tempPath);
+        }
+
+        return [
+            'message' => 'Master migrations completed successfully!',
+            'database' => $this->masterDatabase,
+            'migrations' => $masterMigrations,
+            'output' => $output
+        ];
+    }
+
+    public function runTenantMigrations()
+    {
+        $tenants = $this->getTenants();
+        $results = [];
+
+        if ($tenants->isEmpty()) {
+            return ['message' => 'No tenants found.', 'results' => []];
+        }
+
+        $tenantMigrations = $this->getTenantMigrationFiles();
+
+        if (empty($tenantMigrations)) {
+            return ['message' => 'No tenant migrations found.', 'results' => []];
+        }
+
+        // Create a temporary directory for tenant migrations only
+        $tempPath = storage_path('app/temp_tenant_migrations');
+        if (!File::exists($tempPath)) {
+            File::makeDirectory($tempPath, 0755, true);
+        }
+
+        // Copy only tenant migrations to temp directory
+        foreach ($tenantMigrations as $migration) {
+            $source = database_path('migrations/' . $migration);
+            $destination = $tempPath . '/' . $migration;
+            File::copy($source, $destination);
+        }
+
+        foreach ($tenants as $tenant) {
+            try {
+                $this->setupTenantConnection($tenant);
+
+                Artisan::call('migrate', [
+                    '--database' => 'tenant',
+                    '--path' => 'storage/app/temp_tenant_migrations'
+                ]);
+
+                $results[] = [
+                    'tenant' => $tenant->tenant_database,
+                    'status' => 'success',
+                    'message' => 'Migrations completed successfully'
+                ];
+
+            } catch (\Exception $e) {
+                $results[] = [
+                    'tenant' => $tenant->tenant_database,
+                    'status' => 'error',
+                    'message' => $e->getMessage()
+                ];
+            }
+        }
+
+        // Clean up temp directory
+        File::deleteDirectory($tempPath);
+
+        return [
+            'message' => 'Tenant migrations process completed',
+            'results' => $results
+        ];
+    }
+
+    public function rollbackMasterMigrations($steps = 1)
+    {
+        $this->setupMasterConnection();
+
+        Artisan::call('migrate:rollback', [
+            '--database' => 'master',
+            '--step' => $steps
+        ]);
+
+        return [
+            'message' => "Master database rolled back {$steps} step(s)",
+            'database' => $this->masterDatabase,
+            'output' => Artisan::output()
+        ];
+    }
+
+    public function rollbackTenantMigrations($steps = 1)
+    {
+        $tenants = $this->getTenants();
+        $results = [];
+
+        foreach ($tenants as $tenant) {
+            try {
+                $this->setupTenantConnection($tenant);
+
+                Artisan::call('migrate:rollback', [
+                    '--database' => 'tenant',
+                    '--step' => $steps
+                ]);
+
+                $results[] = [
+                    'tenant' => $tenant->tenant_database,
+                    'status' => 'success',
+                    'message' => "Rolled back {$steps} step(s)"
+                ];
+
+            } catch (\Exception $e) {
+                $results[] = [
+                    'tenant' => $tenant->tenant_database,
+                    'status' => 'error',
+                    'message' => $e->getMessage()
+                ];
+            }
+        }
+
+        return [
+            'message' => 'Tenant rollback process completed',
+            'results' => $results
+        ];
+    }
+
+    protected function setupMasterConnection()
+    {
+        config(['database.connections.master' => [
+            'driver' => 'mysql',
+            'host' => env('DB_HOST', '127.0.0.1'),
+            'port' => env('DB_PORT', '3306'),
+            'database' => $this->masterDatabase,
+            'username' => env('DB_USERNAME'),
+            'password' => env('DB_PASSWORD'),
+            'charset' => 'utf8mb4',
+            'collation' => 'utf8mb4_unicode_ci',
+            'prefix' => '',
+            'strict' => true,
+            'engine' => null,
+        ]]);
+
+        config(['database.default' => 'master']);
+        DB::purge('master');
+        DB::reconnect('master');
+    }
+
+    protected function setupTenantConnection($tenant)
+    {
+        config(['database.connections.tenant' => [
+            'driver' => 'mysql',
+            'host' => env('DB_HOST', '127.0.0.1'),
+            'port' => env('DB_PORT', '3306'),
+            'database' => $tenant->tenant_database,
+            'username' => $tenant->tenant_username,
+            'password' => $tenant->tenant_password,
+            'charset' => 'utf8mb4',
+            'collation' => 'utf8mb4_unicode_ci',
+            'prefix' => '',
+            'strict' => true,
+            'engine' => null,
+        ]]);
+
+        config(['database.default' => 'tenant']);
+        DB::purge('tenant');
+        DB::reconnect('tenant');
+    }
+
+    protected function getTenants()
+    {
+        return User::select('tenant_database', 'tenant_username', 'tenant_password')
+            ->whereNotNull('tenant_database')
+            ->distinct()
+            ->get();
+    }
+
+    protected function getMasterMigrationFiles()
+    {
+        $migrationPath = database_path('migrations');
+        $files = File::glob($migrationPath . '/*.php');
+        $masterMigrations = [];
+
+        foreach ($files as $file) {
+            $content = File::get($file);
+            if (strpos($content, 'extends MasterMigration') !== false) {
+                $masterMigrations[] = basename($file);
+            }
+        }
+
+        return $masterMigrations;
+    }
+
+    protected function getTenantMigrationFiles()
+    {
+        $migrationPath = database_path('migrations');
+        $files = File::glob($migrationPath . '/*.php');
+        $tenantMigrations = [];
+
+        foreach ($files as $file) {
+            $content = File::get($file);
+            if (strpos($content, 'extends TenantMigration') !== false) {
+                $tenantMigrations[] = basename($file);
+            }
+        }
+
+        return $tenantMigrations;
+    }
+}

+ 72 - 0
app/Services/MultiTenantAuthService.php

@@ -0,0 +1,72 @@
+<?php
+namespace App\Services;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Hash;
+class MultiTenantAuthService
+{
+    /**
+     * Authenticate user against both master and tenant databases
+     */
+    public static function authenticate($email, $password)
+    {
+        try {
+            $masterUser = DB::connection('mysql')->table('users')
+                ->where('email', $email)
+                ->first();
+
+            if (!$masterUser) {
+                Log::info('User not found in master database', ['email' => $email]);
+                return false;
+            }
+
+            if (!Hash::check($password, $masterUser->password)) {
+                Log::info('Password mismatch in master database', ['email' => $email]);
+                return false;
+            }
+
+            config(['database.connections.temp_tenant' => [
+                'driver' => 'mysql',
+                'host' => '127.0.0.1',
+                'port' => '3306',
+                'database' => $masterUser->tenant_database,
+                'username' => $masterUser->tenant_username,
+                'password' => $masterUser->tenant_password,
+            ]]);
+
+            $tenantUser = DB::connection('temp_tenant')->table('users')
+                ->where('email', $email)
+                ->first();
+
+            if (!$tenantUser) {
+                Log::info('User not found in tenant database', [
+                    'email' => $email,
+                    'tenant_db' => $masterUser->tenant_database
+                ]);
+                return false;
+            }
+
+            if (!Hash::check($password, $tenantUser->password)) {
+                Log::info('Password mismatch in tenant database', [
+                    'email' => $email,
+                    'tenant_db' => $masterUser->tenant_database
+                ]);
+                return false;
+            }
+
+            Log::info('Authentication successful for both databases', [
+                'email' => $email,
+                'tenant_db' => $masterUser->tenant_database
+            ]);
+
+            return $masterUser;
+
+        } catch (\Exception $e) {
+            Log::error('Authentication error', [
+                'email' => $email,
+                'error' => $e->getMessage()
+            ]);
+            return false;
+        }
+    }
+}

+ 2 - 2
app/helpers.php

@@ -63,7 +63,7 @@ function currencyToDouble($val)
 function getConfiguration($field)
 function getConfiguration($field)
 {
 {
     $ret = "";
     $ret = "";
-    $conf = \App\Models\Configurration::first();
+    $conf = \App\Models\Configuration::first();
     if ($conf)
     if ($conf)
     {
     {
         $ret = $conf[$field];
         $ret = $conf[$field];
@@ -281,4 +281,4 @@ function sms_send($params, $backup = false)
 
 
     curl_close($c);
     curl_close($c);
     return $content;
     return $content;
-}
+}

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

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('courts', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->integer('enabled')->default(1);
+            $table->softDeletes();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('courts');
+    }
+};

+ 42 - 0
database/migrations/2025_06_06_153700_create_calendars_table.php

@@ -0,0 +1,42 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('calendars', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('course_id')->nullable();
+            $table->foreign('course_id')->nullable()->references('id')->on('courses')->onUpdate('cascade')->onDelete('cascade');
+            $table->unsignedBigInteger('court_id')->nullable();
+            $table->foreign('court_id')->nullable()->references('id')->on('courtes')->onUpdate('cascade')->onDelete('cascade');
+            $table->unsignedBigInteger('instructor_id')->nullable();
+            $table->foreign('instructor_id')->nullable()->references('id')->on('users')->onUpdate('cascade')->onDelete('cascade');
+            $table->datetime('date')->nullable();
+            $table->string('from')->nullable();
+            $table->string('to')->nullable();
+            $table->string('note')->nullable();
+            $table->softDeletes();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('calendars');
+    }
+};

+ 39 - 0
database/migrations/2025_06_06_154000_create_presences_table.php

@@ -0,0 +1,39 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('presences', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('calendar_id')->nullable();
+            $table->foreign('calendar_id')->nullable()->references('id')->on('calendars')->onUpdate('cascade')->onDelete('cascade');
+            $table->unsignedBigInteger('member_id')->nullable();
+            $table->foreign('member_id')->nullable()->references('id')->on('members')->onUpdate('cascade')->onDelete('cascade');
+            $table->unsignedBigInteger('member_course_id')->nullable();
+            $table->foreign('member_course_id')->nullable()->references('id')->on('member_courses')->onUpdate('cascade')->onDelete('cascade');
+
+            $table->softDeletes();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('presences');
+    }
+};

+ 21 - 0
database/migrations/2025_06_20_084530_create_password_resets_table.php

@@ -0,0 +1,21 @@
+<?php
+use App\Database\Migrations\MasterMigration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends MasterMigration
+{
+    public function up()
+    {
+        Schema::create('password_resets', function (Blueprint $table) {
+            $table->string('email')->index();
+            $table->string('token');
+            $table->timestamp('created_at')->nullable();
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists('password_resets');
+    }
+};

+ 24 - 0
database/migrations/2025_06_20_122026_add_first_login_to_users_table_master.php

@@ -0,0 +1,24 @@
+<?php
+
+use App\Database\Migrations\MasterMigration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends MasterMigration
+{
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->boolean('first_login_completed')->default(false);
+            $table->timestamp('first_login_at')->nullable()->after('first_login_completed');
+        });
+    }
+
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn(['first_login_completed', 'first_login_at']);
+        });
+    }
+};
+

+ 23 - 0
database/migrations/2025_06_20_122038_add_first_login_to_users_table_master_tenant.php

@@ -0,0 +1,23 @@
+<?php
+
+use App\Database\Migrations\TenantMigration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends TenantMigration
+{
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->boolean('first_login_completed')->default(false);
+            $table->timestamp('first_login_at')->nullable()->after('first_login_completed');
+        });
+    }
+
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn(['first_login_completed', 'first_login_at']);
+        });
+    }
+};

+ 32 - 0
database/migrations/2025_06_23_075223_is_archived_field_member_table.php

@@ -0,0 +1,32 @@
+<?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_archived')->nullable();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('members', function (Blueprint $table) {
+            $table->dropColumn('is_archived');
+        });
+    }
+};

+ 32 - 0
database/migrations/2025_06_23_080139_archived_date_field_member_table.php

@@ -0,0 +1,32 @@
+<?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->timestamp('archived_date')->nullable()->after('is_archived');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('members', function (Blueprint $table) {
+            $table->dropColumn('archived_date');
+        });
+    }
+};

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

@@ -0,0 +1,34 @@
+<?php
+
+use App\Database\Migrations\TenantMigration;
+use Illuminate\Database\Migrations\Migration;
+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('causals', function (Blueprint $table) {
+            $table->boolean('hidden')->default(false)->after('type');
+            $table->index('hidden'); // Add index for better performance
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('causals', function (Blueprint $table) {
+            $table->dropColumn('hidden');
+        });
+    }
+};

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

@@ -0,0 +1,34 @@
+<?php
+
+use App\Database\Migrations\TenantMigration;
+use Illuminate\Database\Migrations\Migration;
+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('payment_methods', function (Blueprint $table) {
+            $table->boolean('hidden')->default(false)->after('type');
+            $table->index('hidden');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('payment_methods', function (Blueprint $table) {
+            $table->dropColumn('hidden');
+        });
+    }
+};

+ 33 - 0
database/migrations/2025_07_04_075532_add_archived_to_suppliers_table.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('suppliers', function (Blueprint $table) {
+            $table->boolean('archived')->default(false);
+            $table->index('archived');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('suppliers', function (Blueprint $table) {
+            //
+        });
+    }
+};

+ 37 - 0
database/migrations/2025_07_04_102429_add_dashboard_note_table.php

@@ -0,0 +1,37 @@
+
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use App\Database\Migrations\TenantMigration;
+return new class extends TenantMigration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('dashboard_notes', function (Blueprint $table) {
+            $table->id();
+            $table->string('unique_id')->unique();
+            $table->text('text');
+            $table->boolean('completed')->default(false);
+            $table->unsignedBigInteger('user_id')->nullable();
+            $table->timestamps();
+
+            $table->index(['user_id', 'completed', 'created_at']);
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('dashboard_notes');
+    }
+};

+ 26 - 0
database/migrations/2025_07_11_090151_create_mail_templates_table.php

@@ -0,0 +1,26 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+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('email_templates', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->longText('content');
+            $table->unsignedBigInteger('created_by');
+            $table->timestamps();
+
+            $table->foreign('created_by')->references('id')->on('users');
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists('email_templates');
+    }
+};

+ 26 - 0
database/migrations/2025_07_11_090151_create_sms_templates_table.php

@@ -0,0 +1,26 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+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_templates', function (Blueprint $table) {
+            $table->id();
+            $table->string('name');
+            $table->text('content');
+            $table->unsignedBigInteger('created_by');
+            $table->timestamps();
+
+            $table->foreign('created_by')->references('id')->on('users');
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists('sms_templates');
+    }
+};

+ 31 - 0
database/migrations/2025_07_11_090201_create_mail_scheduled_table.php

@@ -0,0 +1,31 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+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('email_scheduled', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('template_id')->nullable();
+            $table->string('subject');
+            $table->longText('content');
+            $table->timestamp('scheduled_at');
+            $table->enum('status', ['scheduled', 'sending', 'sent', 'failed'])->default('scheduled');
+            $table->unsignedBigInteger('created_by');
+            $table->json('delivery_report')->nullable();
+            $table->timestamps();
+
+            $table->foreign('template_id')->references('id')->on('email_templates')->onDelete('set null');
+            $table->foreign('created_by')->references('id')->on('users');
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists('email_scheduled');
+    }
+};

+ 29 - 0
database/migrations/2025_07_11_090201_create_sms_scheduled_table.php

@@ -0,0 +1,29 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+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_scheduled', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('template_id')->nullable();
+            $table->text('content');
+            $table->timestamp('scheduled_at');
+            $table->enum('status', ['scheduled', 'sent', 'failed'])->default('scheduled');
+            $table->unsignedBigInteger('created_by');
+            $table->timestamps();
+
+            $table->foreign('template_id')->references('id')->on('sms_templates')->onDelete('set null');
+            $table->foreign('created_by')->references('id')->on('users');
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists('sms_scheduled');
+    }
+};

+ 31 - 0
database/migrations/2025_07_11_090208_create_mail_scheduled_recipients_table.php

@@ -0,0 +1,31 @@
+<?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('email_scheduled_recipients', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('email_scheduled_id');
+            $table->unsignedBigInteger('user_id');
+            $table->string('email_address');
+            $table->enum('status', ['pending', 'sent', 'failed', 'bounced'])->default('pending');
+            $table->text('error_message')->nullable();
+            $table->timestamp('sent_at')->nullable();
+            $table->timestamps();
+
+            $table->foreign('email_scheduled_id')->references('id')->on('email_scheduled')->onDelete('cascade');
+            $table->foreign('user_id')->references('id')->on('users');
+            $table->unique(['email_scheduled_id', 'user_id']);
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists('email_scheduled_recipients');
+    }
+};

+ 27 - 0
database/migrations/2025_07_11_090208_create_sms_scheduled_recipients_table.php

@@ -0,0 +1,27 @@
+<?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_scheduled_recipients', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('sms_scheduled_id');
+            $table->unsignedBigInteger('user_id');
+            $table->timestamps();
+
+            $table->foreign('sms_scheduled_id')->references('id')->on('sms_scheduled')->onDelete('cascade');
+            $table->foreign('user_id')->references('id')->on('users');
+            $table->unique(['sms_scheduled_id', 'user_id']);
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists('sms_scheduled_recipients');
+    }
+};

+ 71 - 0
resources/views/auth/password-reset-form.blade.php

@@ -0,0 +1,71 @@
+<!-- resources/views/auth/password-reset-form.blade.php -->
+<!DOCTYPE html>
+<html lang="it">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Nuova Password</title>
+    <link rel="stylesheet" href="https://use.typekit.net/dit2bgs.css">
+    <link rel="stylesheet" href="/css/style.css">
+</head>
+<body>
+    <div class="login--box d-flex flex-column min-vh-100 justify-content-center align-items-center">
+        <img src="{{ env('LOGO', '') }}" alt="" class="img-fluid" id="login--logo" style="max-width:400px">
+        <div class="card--ui mt-5" style="width: 400px; height: 350px;">
+            <p class="form--title text-center mb-4">Imposta Nuova Password</p>
+
+            @if(session('success'))
+                <div class="alert alert-success mb-3">{{ session('success') }}</div>
+            @endif
+
+            @if(session('error'))
+                <div class="alert alert-danger mb-3">{{ session('error') }}</div>
+            @endif
+
+            <form action="/password-reset" method="POST">
+                @csrf
+                <input type="hidden" name="token" value="{{ $token }}">
+
+                <div class="form--item input-group mb-3">
+                    <input type="email" class="form-control @error('email') is-invalid @enderror"
+                           placeholder="Email" name="email" value="{{ request('email') }}" required>
+                    <span class="input-group-text"><i class="ico--ui mail"></i></span>
+                    @error('email')
+                        <div class="invalid-feedback">{{ $message }}</div>
+                    @enderror
+                </div>
+
+                <div class="form--item input-group mb-3">
+                    <input type="password" class="form-control @error('password') is-invalid @enderror"
+                           placeholder="Nuova Password" name="password" required>
+                    <span class="input-group-text"><i class="ico--ui lock"></i></span>
+                    @error('password')
+                        <div class="invalid-feedback">{{ $message }}</div>
+                    @enderror
+                </div>
+
+                <div class="form--item input-group mb-3">
+                    <input type="password" class="form-control @error('password_confirmation') is-invalid @enderror"
+                           placeholder="Conferma Password" name="password_confirmation" required>
+                    <span class="input-group-text"><i class="ico--ui lock"></i></span>
+                    @error('password_confirmation')
+                        <div class="invalid-feedback">{{ $message }}</div>
+                    @enderror
+                </div>
+
+                <div class="form--item input-group d-flex align-items-center justify-content-between">
+                    <button type="submit" class="btn--ui">Aggiorna Password</button>
+                </div>
+            </form>
+
+            <div class="credential--recovery d-flex flex-column mt-4">
+                <a href="/">Torna al Login</a>
+            </div>
+        </div>
+    </div>
+
+    <script src="/assets/js/bootstrap.bundle.js"></script>
+    <script src="/assets/js/app.js"></script>
+</body>
+</html>

+ 53 - 0
resources/views/auth/password-reset-request.blade.php

@@ -0,0 +1,53 @@
+<!-- resources/views/password-reset-request.blade.php -->
+<!DOCTYPE html>
+<html lang="it">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Reset Password</title>
+    <link rel="stylesheet" href="https://use.typekit.net/dit2bgs.css">
+    <link rel="stylesheet" href="/css/style.css">
+</head>
+<body>
+    <div class="login--box d-flex flex-column min-vh-100 justify-content-center align-items-center">
+        <img src="{{ env('LOGO', '') }}" alt="" class="img-fluid" id="login--logo" style="max-width:400px">
+        <div class="card--ui mt-5" style="width: 400px; height: 350px;">
+            <p class="form--title text-center mb-4">Reset Password</p>
+            <p class="text-center mb-4">Inserisci la tua email per ricevere le istruzioni di reset</p>
+
+            @if(session('success'))
+                <div class="alert alert-success mb-3">{{ session('success') }}</div>
+            @endif
+
+            @if(session('error'))
+                <div class="alert alert-danger mb-3">{{ session('error') }}</div>
+            @endif
+
+            <form action="/password-reset-request" method="POST">
+                @csrf
+
+                <div class="form--item input-group mb-3">
+                    <input type="email" class="form-control @error('email') is-invalid @enderror"
+                           placeholder="Email" name="email" value="{{ old('email') }}" required>
+                    <span class="input-group-text"><i class="ico--ui mail"></i></span>
+                    @error('email')
+                        <div class="invalid-feedback">{{ $message }}</div>
+                    @enderror
+                </div>
+
+                <div class="form--item input-group d-flex align-items-center justify-content-between">
+                    <button type="submit" class="btn--ui">Invia Reset</button>
+                </div>
+            </form>
+
+            <div class="credential--recovery d-flex flex-column mt-4">
+                <a href="/">Torna al Login</a>
+            </div>
+        </div>
+    </div>
+
+    <script src="/assets/js/bootstrap.bundle.js"></script>
+    <script src="/assets/js/app.js"></script>
+</body>
+</html>

+ 94 - 0
resources/views/emails/account-activated.blade.php

@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html lang="it">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Il tuo account è attivo</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 600px;
+            margin: 0 auto;
+            padding: 20px;
+        }
+        .header {
+            background-color: #28a745;
+            color: white;
+            padding: 20px;
+            text-align: center;
+            border-radius: 8px 8px 0 0;
+        }
+        .content {
+            background-color: #f8f9fa;
+            padding: 30px;
+            border-radius: 0 0 8px 8px;
+        }
+        .login-button {
+            display: inline-block;
+            background-color: #0C6197;
+            color: white;
+            padding: 12px 30px;
+            text-decoration: none;
+            border-radius: 5px;
+            margin: 20px 0;
+            font-weight: bold;
+        }
+        .brand {
+            color: #0C6197;
+            font-weight: bold;
+        }
+        .footer {
+            margin-top: 30px;
+            padding-top: 20px;
+            border-top: 1px solid #dee2e6;
+            font-size: 14px;
+            color: #6c757d;
+        }
+        .login-url {
+            background-color: white;
+            border: 2px solid #0C6197;
+            border-radius: 8px;
+            padding: 15px;
+            margin: 20px 0;
+            word-break: break-all;
+            font-family: monospace;
+            font-size: 14px;
+        }
+    </style>
+</head>
+<body>
+    <div class="header">
+        <h1>🎉 Account Attivato!</h1>
+    </div>
+
+    <div class="content">
+        <p>Ciao <strong>{{ $name }}</strong>,</p>
+
+        <p>la tua registrazione su <span class="brand">Leezard.cloud</span> è andata a buon fine e il tuo account è stato attivato con successo.</p>
+
+        <p>Ora puoi accedere alla piattaforma utilizzando le tue credenziali.</p>
+
+        <div style="text-align: center;">
+            <a href="{{ $login_url }}" class="login-button" style="color: #FFF!important">🚀 Accedi alla piattaforma</a>
+        </div>
+
+        <p>Oppure copia e incolla questo link nel tuo browser:</p>
+
+        <div class="login-url">
+            {{ $login_url }}
+        </div>
+
+        <p>Per qualsiasi dubbio o supporto, non esitare a contattarci.</p>
+
+        <p>A presto!</p>
+
+        <div class="footer">
+            <p>Il team di <span class="brand">Leezard.cloud</span></p>
+            <p>Data attivazione: {{ date('d/m/Y H:i') }} ({{ date('T') }})</p>
+            <p>Questa email è stata generata automaticamente. Per favore, non rispondere a questo indirizzo.</p>
+        </div>
+    </div>
+</body>
+</html>

+ 51 - 0
resources/views/emails/custom.blade.php

@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>{{ $emailSubject ?? 'Email' }}</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 600px;
+            margin: 0 auto;
+            padding: 20px;
+        }
+        .header {
+            border-bottom: 2px solid #0C6197;
+            padding-bottom: 20px;
+            margin-bottom: 30px;
+        }
+        .content {
+            margin-bottom: 30px;
+        }
+        .footer {
+            border-top: 1px solid #ddd;
+            padding-top: 20px;
+            font-size: 12px;
+            color: #666;
+            text-align: center;
+        }
+    </style>
+</head>
+<body>
+    <div class="header">
+        <h2 style="color: #0C6197; margin: 0;">{{ config('app.name', 'La Tua App') }}</h2>
+    </div>
+
+    <div class="content">
+        @if($recipientName)
+            <p>Ciao {{ $recipientName }},</p>
+        @endif
+
+        {!! $content !!}
+    </div>
+
+    <div class="footer">
+        <p>Questa email è stata inviata automaticamente. Per favore non rispondere a questo indirizzo.</p>
+        <p>&copy; {{ date('Y') }} {{ config('app.name') }}. Tutti i diritti riservati.</p>
+    </div>
+</body>
+</html>

+ 80 - 0
resources/views/emails/password-changed.blade.php

@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html lang="it">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Password Modificata</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 600px;
+            margin: 0 auto;
+            padding: 20px;
+        }
+        .header {
+            background-color: #28a745;
+            color: white;
+            padding: 20px;
+            text-align: center;
+            border-radius: 8px 8px 0 0;
+        }
+        .content {
+            background-color: #f8f9fa;
+            padding: 30px;
+            border-radius: 0 0 8px 8px;
+        }
+        .warning {
+            background-color: #fff3cd;
+            border: 1px solid #ffeaa7;
+            color: #856404;
+            padding: 15px;
+            border-radius: 5px;
+            margin: 20px 0;
+        }
+        .footer {
+            margin-top: 30px;
+            padding-top: 20px;
+            border-top: 1px solid #dee2e6;
+            font-size: 14px;
+            color: #6c757d;
+        }
+        .brand {
+            color: #0C6197;
+            font-weight: bold;
+        }
+    </style>
+</head>
+<body>
+    <div class="header">
+        <h1>🔒 Password Modificata</h1>
+    </div>
+
+    <div class="content">
+        <h2>Ciao {{ $name }},</h2>
+
+        <p>ti confermiamo che la tua password su <span class="brand">Leezard.cloud</span> è stata modificata con successo.</p>
+
+        <div class="warning">
+            <strong>⚠️ Importante:</strong> Se non sei stato tu a effettuare questa operazione, ti invitiamo a contattarci immediatamente.
+        </div>
+
+        <p>Se hai modificato la password tu stesso, puoi ignorare questa email.</p>
+
+        <p>Per maggiore sicurezza, assicurati sempre di:</p>
+        <ul>
+            <li>Utilizzare password uniche e complesse</li>
+            <li>Non condividere le tue credenziali con nessuno</li>
+            <li>Effettuare il logout dai dispositivi condivisi</li>
+        </ul>
+
+        <div class="footer">
+            <p>Grazie,<br>
+            Il team di <span class="brand">Leezard.cloud</span></p>
+            <p>Data modifica: {{ date('d/m/Y H:i') }} ({{ date('T') }})</p>
+            <p>Questa email è stata generata automaticamente. Per favore, non rispondere a questo indirizzo.</p>
+        </div>
+    </div>
+</body>
+</html>

+ 103 - 0
resources/views/emails/password-reset.blade.php

@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html lang="it">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Reset Password</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 600px;
+            margin: 0 auto;
+            padding: 20px;
+        }
+        .header {
+            background-color: #0C6197;
+            color: white;
+            padding: 20px;
+            text-align: center;
+            border-radius: 8px 8px 0 0;
+        }
+        .content {
+            background-color: #f8f9fa;
+            padding: 30px;
+            border-radius: 0 0 8px 8px;
+        }
+        .reset-button {
+            display: inline-block;
+            background-color: #0C6197;
+            color: white;
+            padding: 15px 30px;
+            text-decoration: none;
+            border-radius: 5px;
+            margin: 20px 0;
+            font-weight: bold;
+            text-align: center;
+        }
+        .reset-url {
+            background-color: white;
+            border: 2px solid #0C6197;
+            border-radius: 8px;
+            padding: 15px;
+            margin: 20px 0;
+            word-break: break-all;
+            font-family: monospace;
+            font-size: 14px;
+        }
+        .warning {
+            background-color: #fff3cd;
+            border: 1px solid #ffeaa7;
+            color: #856404;
+            padding: 15px;
+            border-radius: 5px;
+            margin: 20px 0;
+        }
+        .footer {
+            margin-top: 30px;
+            padding-top: 20px;
+            border-top: 1px solid #dee2e6;
+            font-size: 14px;
+            color: #6c757d;
+        }
+    </style>
+</head>
+<body>
+    <div class="header">
+        <h1>🔒 Reimposta la tua password – Leezard.cloud</h1>
+    </div>
+
+    <div class="content">
+        <h2>Ciao {{ $name }},</h2>
+
+        <p>abbiamo ricevuto una richiesta di reset della password per il tuo account su <strong>Leezard.cloud</strong>.</p>
+
+        <p>Per impostare una nuova password, clicca sul pulsante qui sotto:</p>
+
+        <div style="text-align: center;">
+            <a href="{{ $reset_url }}" class="reset-button" style="color: #FFF!important">🔑 Reimposta Password</a>
+        </div>
+
+        <p><strong>Oppure copia e incolla questo link nel tuo browser:</strong></p>
+
+        <div class="reset-url">
+            {{ $reset_url }}
+        </div>
+
+        <div class="warning">
+            <strong>⚠️ Importante:</strong> Questo link è valido per <strong>{{ $expires_at }}</strong> e può essere utilizzato una sola volta.
+        </div>
+
+        <p>Se non hai richiesto questa operazione, puoi ignorare questa email: nessuna modifica verrà applicata.</p>
+
+        <div class="footer">
+            <p><strong>Grazie,<br>
+            Il team di Leezard.cloud</strong></p>
+            <p>Questa email è stata generata automaticamente. Per favore, non rispondere a questo indirizzo.</p>
+            <p>Data richiesta: {{ date('d/m/Y H:i') }} ({{ date('T') }})</p>
+            <p>Indirizzo email: {{ $email }}</p>
+        </div>
+    </div>
+</body>
+</html>

+ 139 - 0
resources/views/emails/welcome-user.blade.php

@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html lang="it">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Benvenuto - Account Creato</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            line-height: 1.6;
+            color: #333;
+            max-width: 600px;
+            margin: 0 auto;
+            padding: 20px;
+        }
+        .header {
+            background-color: #0C6197;
+            color: white;
+            padding: 20px;
+            text-align: center;
+            border-radius: 8px 8px 0 0;
+        }
+        .content {
+            background-color: #f8f9fa;
+            padding: 30px;
+            border-radius: 0 0 8px 8px;
+        }
+        .credentials-box {
+            background-color: white;
+            border: 2px solid #0C6197;
+            border-radius: 8px;
+            padding: 20px;
+            margin: 20px 0;
+        }
+        .credential-item {
+            margin: 10px 0;
+            font-size: 16px;
+        }
+        .credential-label {
+            font-weight: bold;
+            color: #007bff;
+        }
+        .credential-value {
+            background-color: #e9ecef;
+            padding: 5px 10px;
+            border-radius: 4px;
+            font-family: monospace;
+            margin-left: 10px;
+        }
+        .login-button {
+            display: inline-block;
+            background-color: #0C6197;
+            color: white;
+            padding: 12px 30px;
+            text-decoration: none;
+            border-radius: 5px;
+            margin: 20px 0;
+            font-weight: bold;
+        }
+        .footer {
+            margin-top: 30px;
+            padding-top: 20px;
+            border-top: 1px solid #dee2e6;
+            font-size: 14px;
+            color: #6c757d;
+        }
+        .warning {
+            background-color: #fff3cd;
+            border: 1px solid #ffeaa7;
+            color: #856404;
+            padding: 15px;
+            border-radius: 5px;
+            margin: 20px 0;
+        }
+    </style>
+</head>
+<body>
+    <div class="header">
+        <h1>🎉 Benvenuto in {{ $company }}!</h1>
+    </div>
+
+    <div class="content">
+        <h2>Ciao {{ $name }} {{ $cognome }},</h2>
+
+        <p>Il tuo account è stato creato con successo da <strong>{{ $created_by }}</strong>. Ora puoi accedere alla piattaforma con le credenziali fornite di seguito.</p>
+
+        <div class="credentials-box">
+            <h3>🔐 Le tue credenziali di accesso:</h3>
+
+            <div class="credential-item">
+                <span class="credential-label">Email:</span>
+                <span class="credential-value">{{ $email }}</span>
+            </div>
+
+            <div class="credential-item">
+                <span class="credential-label">Password:</span>
+                <span class="credential-value">{{ $password }}</span>
+            </div>
+
+            <div class="credential-item">
+                <span class="credential-label">Livello Account:</span>
+                <span class="credential-value">
+                    @if($level == 0)
+                        Amministratore
+                    @elseif($level == 1)
+                        Worker
+                    @else
+                        Istruttore
+                    @endif
+                </span>
+            </div>
+        </div>
+
+        <div class="warning">
+            <strong>⚠️ Importante:</strong> Per motivi di sicurezza, dovrai cambiare la password al primo accesso. Sarai reindirizzato dopo aver effettuato il login.
+        </div>
+
+        <div style="text-align: center;">
+            <a href="{{ $login_url }}" class="login-button" style="color: #FFF!important">🚀 Accedi Ora</a>
+        </div>
+
+        <h3>📋 Cosa puoi fare ora:</h3>
+        <ul>
+            <li>Accedi alla piattaforma usando le credenziali sopra</li>
+            <li>Cambia la password per maggiore sicurezza</li>
+            <li>Completa il tuo profilo nella sezione "Profilo"</li>
+
+        </ul>
+
+        <p>Se hai domande o problemi nell'accesso, contatta l'amministratore del sistema.</p>
+
+        <div class="footer">
+            <p><strong>{{ $company }}</strong></p>
+            <p>Questa email è stata generata automaticamente. Per favore, non rispondere a questo indirizzo.</p>
+            <p>Data creazione account: {{ date('d/m/Y H:i') }} ({{ date('T') }})</p>
+        </div>
+    </div>
+</body>
+</html>

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

@@ -0,0 +1,231 @@
+<!DOCTYPE html>
+<html lang="it">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Primo Accesso - Leezard.cloud</title>
+
+    <!-- Bootstrap CSS -->
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
+    <!-- Font Awesome -->
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
+
+    <style>
+        body {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+        }
+
+        .first-login-container {
+            padding: 2rem 0;
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+        }
+
+        .card {
+            box-shadow: 0 15px 35px rgba(0,0,0,0.1);
+            border: none;
+            border-radius: 15px;
+            overflow: hidden;
+        }
+
+        .card-header {
+            background: linear-gradient(135deg, #0C6197 0%, #094a79 100%);
+            color: white;
+            border: none;
+            padding: 2rem;
+            text-align: center;
+        }
+
+        .card-header h3 {
+            margin: 0;
+            font-weight: 300;
+        }
+
+        .text-primary {
+            color: #0C6197 !important;
+        }
+
+        .btn-primary {
+            background-color: #0C6197;
+            border-color: #0C6197;
+            padding: 12px 30px;
+            font-weight: 600;
+        }
+
+        .btn-primary:hover {
+            background-color: #094a79;
+            border-color: #094a79;
+        }
+
+        .form-control:focus {
+            border-color: #0C6197;
+            box-shadow: 0 0 0 0.2rem rgba(12, 97, 151, 0.25);
+        }
+
+        .alert-warning {
+            background-color: rgba(255, 193, 7, 0.1);
+            border-color: #ffc107;
+            color: #856404;
+        }
+
+        .section-header {
+            border-bottom: 2px solid #f8f9fa;
+            padding-bottom: 0.5rem;
+            margin-bottom: 1.5rem;
+        }
+
+        .logout-link {
+            position: absolute;
+            top: 20px;
+            right: 20px;
+            color: white;
+            text-decoration: none;
+            background: rgba(255,255,255,0.2);
+            padding: 8px 15px;
+            border-radius: 5px;
+            transition: background 0.3s;
+        }
+
+        .logout-link:hover {
+            background: rgba(255,255,255,0.3);
+            color: white;
+            text-decoration: none;
+        }
+    </style>
+</head>
+<body>
+    <a href="/logout" class="logout-link">
+        <i class="fas fa-sign-out-alt"></i> Esci
+    </a>
+
+    <div class="first-login-container">
+        <div class="container">
+            <div class="row justify-content-center">
+                <div class="col-md-8 col-lg-6">
+
+                    <div class="card">
+                        <div class="card-header">
+                            <h3><i class="fas fa-user-cog"></i> Completa il tuo Profilo</h3>
+                            <p class="mb-0">Primo accesso - Configurazione obbligatoria</p>
+                        </div>
+
+                        <div class="card-body p-4">
+
+                            {{-- Welcome Message --}}
+                            <div class="alert alert-warning mb-4">
+                                <h6><i class="fas fa-exclamation-triangle"></i> <strong>Primo Accesso</strong></h6>
+                                <p class="mb-2">Per motivi di sicurezza, devi <strong>impostare una nuova password</strong> prima di accedere alla piattaforma.</p>
+                                <p class="mb-0">I tuoi dati sono già stati configurati dall'amministratore.</p>
+                            </div>
+
+                            {{-- Success/Error Messages --}}
+                            @if (session('success'))
+                                <div class="alert alert-success alert-dismissible fade show" role="alert">
+                                    <i class="fas fa-check-circle"></i> {{ session('success') }}
+                                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
+                                </div>
+                            @endif
+
+                            <form method="POST" action="{{ route('first-login.complete') }}">
+                                @csrf
+
+                                <div class="section-header">
+                                    <h6 class="text-primary mb-0">
+                                        <i class="fas fa-lock"></i> Imposta la Tua Password
+                                    </h6>
+                                </div>
+
+                                <div class="alert alert-info mb-3">
+                                    <i class="fas fa-info-circle"></i>
+                                    <strong>Cambia Password:</strong> Sostituisci la password temporanea con una tua password sicura.
+                                </div>
+
+                                <div class="row mb-4">
+                                    <div class="col-md-6">
+                                        <label for="password" class="form-label">Nuova Password <span class="text-danger">*</span></label>
+                                        <input type="password"
+                                               class="form-control @error('password') is-invalid @enderror"
+                                               id="password"
+                                               name="password"
+                                               placeholder="Inserisci una password sicura"
+                                               required>
+                                        <small class="form-text text-muted">
+                                            <i class="fas fa-key"></i> Minimo 6 caratteri
+                                        </small>
+                                        @error('password')
+                                            <div class="invalid-feedback">{{ $message }}</div>
+                                        @enderror
+                                    </div>
+
+                                    <div class="col-md-6">
+                                        <label for="password_confirmation" class="form-label">Conferma Password <span class="text-danger">*</span></label>
+                                        <input type="password"
+                                               class="form-control @error('password_confirmation') is-invalid @enderror"
+                                               id="password_confirmation"
+                                               name="password_confirmation"
+                                               placeholder="Ripeti la password"
+                                               required>
+                                        @error('password_confirmation')
+                                            <div class="invalid-feedback">{{ $message }}</div>
+                                        @enderror
+                                    </div>
+                                </div>
+
+                                {{-- Action Buttons --}}
+                                <div class="d-grid gap-2 d-md-block text-center">
+                                    <button type="submit" class="btn btn-primary btn-lg">
+                                        <i class="fas fa-key"></i> Imposta Password e Accedi
+                                    </button>
+                                </div>
+
+                                <div class="text-center mt-3">
+                                    <small class="text-muted">
+                                        <i class="fas fa-shield-alt"></i>
+                                        Dopo aver impostato la password, riceverai un'email di conferma dell'attivazione.
+                                    </small>
+                                </div>
+
+                            </form>
+                        </div>
+                    </div>
+
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- Bootstrap JS -->
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
+
+    <script>
+        // Password strength indicator
+        document.getElementById('password').addEventListener('input', function() {
+            const password = this.value;
+            const strength = document.getElementById('password-strength');
+
+            if (password.length >= 8) {
+                this.classList.remove('is-invalid');
+                this.classList.add('is-valid');
+            } else {
+                this.classList.remove('is-valid');
+            }
+        });
+
+        // Password confirmation check
+        document.getElementById('password_confirmation').addEventListener('input', function() {
+            const password = document.getElementById('password').value;
+            const confirmation = this.value;
+
+            if (password === confirmation && confirmation.length >= 8) {
+                this.classList.remove('is-invalid');
+                this.classList.add('is-valid');
+            } else {
+                this.classList.remove('is-valid');
+            }
+        });
+    </script>
+</body>
+</html>

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

@@ -206,6 +206,10 @@
                 print "Profilo Utente";
                 print "Profilo Utente";
             if (Request::is('reports'))
             if (Request::is('reports'))
                 print "Reports";
                 print "Reports";
+            if (Request::is('sms_comunications'))
+                print "Comunicazioni SMS";
+            if (Request::is('email_comunications'))
+                print "Comunicazioni Email";
             @endphp
             @endphp
             </h3>
             </h3>
 
 

+ 29 - 5
resources/views/livewire/causal.blade.php

@@ -25,7 +25,15 @@
             <div class="compare--chart_wrapper d-none"></div>
             <div class="compare--chart_wrapper d-none"></div>
 
 
             <h1>Entrata</h1>
             <h1>Entrata</h1>
-
+            <div class="mb-3">
+                <button type="button" class="btn btn-outline-secondary btn-sm" wire:click="toggleHidden()">
+                    @if($showHidden)
+                        <i class="fa-regular fa-eye-slash"></i> Nascondi elementi nascosti
+                    @else
+                        <i class="fa-regular fa-eye"></i> Mostra elementi nascosti
+                    @endif
+                </button>
+            </div>
             <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
             <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
                 <thead>
                 <thead>
                     <tr>
                     <tr>
@@ -45,7 +53,11 @@
                                 <button type="button" class="btn btn-outline-success btn-sm" wire:click="addLevel({{ $record->id }})">Aggiungi livello</button>
                                 <button type="button" class="btn btn-outline-success btn-sm" wire:click="addLevel({{ $record->id }})">Aggiungi livello</button>
                                 <button type="button" class="btn btn-outline-success btn-sm" wire:click="duplicate({{ $record->id }})">Duplica</button>
                                 <button type="button" class="btn btn-outline-success btn-sm" wire:click="duplicate({{ $record->id }})">Duplica</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>
                                 <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>
+                                @if($record->hidden)
+                                    <button type="button" class="btn btn-success" wire:click="show({{ $record->id }})" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Ripristina"><i class="fa-regular fa-eye"></i></button>
+                                @else
+                                    <button type="button" class="btn btn-warning" onclick="confirm('Sei sicuro di voler nascondere questo elemento?') || event.stopImmediatePropagation()" wire:click="hide({{ $record->id }})" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Nascondi"><i class="fa-regular fa-eye-slash"></i></button>
+                                @endif
                             </td>
                             </td>
                         </tr>
                         </tr>
                         @if(count($record->childs))
                         @if(count($record->childs))
@@ -58,7 +70,15 @@
 
 
             <br>
             <br>
             <h1>Uscita</h1>
             <h1>Uscita</h1>
-
+            <div class="mb-3">
+                <button type="button" class="btn btn-outline-secondary btn-sm" wire:click="toggleHidden()">
+                    @if($showHidden)
+                        <i class="fa-regular fa-eye-slash"></i> Nascondi elementi nascosti
+                    @else
+                        <i class="fa-regular fa-eye"></i> Mostra elementi nascosti
+                    @endif
+                </button>
+            </div>
             <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
             <table class="table tablesaw tableHead tablesaw-stack" id="tablesaw-350" width="100%">
                 <thead>
                 <thead>
                     <tr>
                     <tr>
@@ -86,8 +106,12 @@
                                 <button type="button" class="btn btn-outline-success btn-sm" wire:click="addLevel({{ $record->id }})">Aggiungi livello</button>
                                 <button type="button" class="btn btn-outline-success btn-sm" wire:click="addLevel({{ $record->id }})">Aggiungi livello</button>
                                 <button type="button" class="btn btn-outline-success btn-sm" wire:click="duplicate({{ $record->id }})">Duplica</button>
                                 <button type="button" class="btn btn-outline-success btn-sm" wire:click="duplicate({{ $record->id }})">Duplica</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>
                                 <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>
-                            </td>
+                                @if($record->hidden)
+                                    <button type="button" class="btn btn-success" wire:click="show({{ $record->id }})" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Ripristina"><i class="fa-regular fa-eye"></i></button>
+                                @else
+                                    <button type="button" class="btn btn-warning" onclick="confirm('Sei sicuro di voler nascondere questo elemento?') || event.stopImmediatePropagation()" wire:click="hide({{ $record->id }})" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Nascondi"><i class="fa-regular fa-eye-slash"></i></button>
+                                @endif
+                                </td>
                         </tr>
                         </tr>
                         @if(count($record->childs))
                         @if(count($record->childs))
                             @include('livewire/causal_child',['records' => $record->childs, 'indentation' => 1])
                             @include('livewire/causal_child',['records' => $record->childs, 'indentation' => 1])

+ 26 - 27
resources/views/livewire/causal_child.blade.php

@@ -1,31 +1,30 @@
 @foreach($records as $record)
 @foreach($records as $record)
-    <tr class="record-level-{{$indentation}}">
-        <td>
-            <div class="level-wrapper level-{{$indentation}}">
-                @for ($i = 0; $i < $indentation; $i++)
-                    <span class="level-dot"></span>
-                @endfor
-                <span class="level-indicator">
-                    {{$record->name}}
+    @if($showHidden || (!$record->hidden && $record->hidden !== null))
+        <tr class="record-level-{{ $indentation }}" style="padding-left: {{ $indentation * 20 }}px;">
+            <td style="padding-left: {{ $indentation * 20 }}px;">
+                {{ str_repeat('— ', $indentation) }}{{ $record->name }}
+            </td>
+            <td>{{ $record->type == 'IN' ? 'Entrata' : 'Uscita' }}</td>
+            <td>
+                <span class="badge tessera-badge {{ $record->enabled ? 'active' : 'suspended' }}">
+                    {{ $record->enabled ? 'attivo' : 'disattivo' }}
                 </span>
                 </span>
-            </div>
-        </td>
-        <td>{{$record->type == 'IN' ? 'Entrata' : 'Uscita'}}</td>
-        <td>
-            <span class="tablesaw-cell-content">
-                <span class="badge tessera-badge {{$record->enabled ? 'active' : 'suspended'}}">
-                    {{$record->enabled ? 'attivo' : 'disattivo'}}
-                </span>
-            </span>
-        </td>
-        <td>
-            <button type="button" class="btn btn-outline-success btn-sm" wire:click="addLevel({{ $record->id }})">Aggiungi livello</button>
-            <button type="button" class="btn btn-outline-success btn-sm" wire:click="duplicate({{ $record->id }})">Duplica</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>
-            <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>
-        </td>
-    </tr>
-    @if(count($record->childs))
-        @include('livewire/causal_child',['records' => $record->childs, 'indentation' => $indentation + 1])
+            </td>
+            <td>
+                <button type="button" class="btn btn-outline-success btn-sm" wire:click="addLevel({{ $record->id }})">Aggiungi livello</button>
+                <button type="button" class="btn btn-outline-success btn-sm" wire:click="duplicate({{ $record->id }})">Duplica</button>
+                <button type="button" class="btn btn-sm" wire:click="edit({{ $record->id }})"><i class="fa-regular fa-pen-to-square"></i></button>
+
+                @if($record->hidden)
+                    <button type="button" class="btn btn-success btn-sm" wire:click="show({{ $record->id }})"><i class="fa-regular fa-eye"></i></button>
+                @else
+                    <button type="button" class="btn btn-warning btn-sm" onclick="confirm('Sei sicuro di voler nascondere questo elemento?') || event.stopImmediatePropagation()" wire:click="hide({{ $record->id }})"><i class="fa-regular fa-eye-slash"></i></button>
+                @endif
+            </td>
+        </tr>
+
+        @if(count($record->childs))
+            @include('livewire/causal_child', ['records' => $record->childs, 'indentation' => $indentation + 1])
+        @endif
     @endif
     @endif
 @endforeach
 @endforeach

+ 640 - 120
resources/views/livewire/dashboard.blade.php

@@ -1,137 +1,657 @@
-<div class="col card--ui" id="card--dashboard">
-
-    <main id="dashboard" class="d-flex">
-    <section id="dash_anagrafica">
-        <div class="anagrafica--btn">
-            <h1>Anagrafica</h1>
-            <div class="anagrafica--btn_wrapper w-100 d-flex">
-
-                <div class="card--ui card--ui_btnDash utente d-flex justify-content-between me-3" wire:click="addMember()"  style="cursor: pointer;">
-                    <header class="d-flex justify-content-between">
-                        <div class="card-title d-flex align-items-start"><i class="ico--ui medium user me-2"></i><h2><small>Nuovo</small><br>Utente</h2></div><a href="/members?new=1"><i class="ico--ui big add"></i></a>
-                    </header>
-                    <footer class="d-flex justify-content-between align-items-end">
-                        <div class="card-footer_percentage d-flex">
-                            <!--<i class="ico--ui small uscite ico me-1"></i><span>10%</span>-->
-                        </div>
-                        <div class="card-footer_info text-end">
-                            <span>{{$totMembers}} </span> persone registrate
-                        </div>
-                    </footer>
-                </div>
-                <div class="card--ui card--ui_btnDash fornitore d-flex justify-items-between"  wire:click="addSupplier()"  style="cursor: pointer;">
-                    <header class="d-flex justify-content-between"><div class="card-title d-flex align-items-start"><i class="ico--ui medium fornitore me-2"></i><h2><small>Nuovo</small><br>Fornitore</h2></div><a href="/suppliers?new=1"><i class="ico--ui big add"></i></a>
-                    </header>
-                    <footer class="d-flex justify-content-between align-items-end">
-                        <div class="card-footer_percentage d-flex">
-                            <!--<i class="ico--ui small uscite ico me-1"></i><span>10%</span>-->
-                        </div>
-                        <div class="card-footer_info text-end">
-                            <span>{{$totSuppliers}} </span> fornitori registrati
-                        </div>
-                    </footer>
-                </div>
+<div class="dashboard-container">
+    <style>
+        .dashboard-container {
+            background: #F6F7FF;
+            min-height: 100vh;
+            padding: 20px;
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+        }
+
+        .dashboard-grid {
+            display: grid;
+            grid-template-columns: 1fr 1fr 1fr;
+            gap: 20px;
+            margin-bottom: 20px;
+        }
+
+        .dashboard-section {
+            background: white;
+            border-radius: 12px;
+            padding: 20px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
+        }
+
+        .stat-card {
+            background: white;
+            border-radius: 12px;
+            padding: 20px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
+            position: relative;
+        }
+
+        .stat-card-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 10px;
+        }
+
+        .stat-card-title {
+            font-size: 14px;
+            color: #666;
+            font-weight: 500;
+        }
+
+        .stat-card-value {
+            font-size: 48px;
+            font-weight: bold;
+            color: #333;
+            margin: 10px 0;
+        }
+
+        .stat-card-change {
+            font-size: 12px;
+            color: #666;
+        }
+
+        .stat-card-change.positive {
+            color: #339E8E;
+        }
+
+        .stat-card-change.negative {
+            color: #D6234F;
+        }
+
+        .stat-icon {
+            width: 24px;
+            height: 24px;
+            border-radius: 4px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: white;
+            font-size: 12px;
+        }
+
+        .courses-list {
+            max-height: 300px;
+            overflow-y: auto;
+        }
+
+        .course-item {
+            padding: 12px;
+            border-left: 4px solid #7438FA;
+            margin-bottom: 8px;
+            background: #F6F7FF;
+            border-radius: 0 8px 8px 0;
+        }
+
+        .course-time {
+            font-weight: bold;
+            color: #333;
+            font-size: 14px;
+        }
+
+        .course-name {
+            font-size: 13px;
+            color: #7438FA;
+            font-weight: 600;
+        }
+
+        .course-days {
+            font-size: 12px;
+            color: #666;
+        }
+
+        .fields-grid {
+            display: grid;
+            grid-template-columns: 1fr 1fr;
+            gap: 10px;
+        }
+
+        .field-item {
+            padding: 12px;
+            border: 2px solid #339E8E;
+            border-radius: 8px;
+            text-align: center;
+            background: white;
+        }
+
+        .field-item.occupied {
+            border-color: #D6234F;
+            background: #FFF5F7;
+        }
+
+        .field-name {
+            font-weight: bold;
+            color: #333;
+        }
+
+        .field-time {
+            color: #666;
+            font-size: 12px;
+        }
+
+        .notes-section {
+            background: #F6F7FF;
+            border-radius: 8px;
+            padding: 15px;
+            margin-top: 15px;
+        }
+
+        .notes-input {
+            width: 100%;
+            border: none;
+            background: transparent;
+            outline: none;
+            font-size: 14px;
+            min-height: 60px;
+            resize: vertical;
+        }
+
+        .save-btn {
+            background: #7438FA;
+            color: white;
+            border: none;
+            padding: 8px 16px;
+            border-radius: 6px;
+            font-size: 12px;
+            cursor: pointer;
+            margin-top: 10px;
+        }
+
+        .save-btn:hover {
+            background: #6028E0;
+        }
+
+        .notes-list {
+            max-height: 200px;
+            overflow-y: auto;
+            margin-bottom: 15px;
+        }
+
+        .note-item {
+            display: flex;
+            align-items: flex-start;
+            gap: 10px;
+            padding: 10px;
+            background: white;
+            border-radius: 8px;
+            margin-bottom: 8px;
+            border-left: 3px solid #7438FA;
+        }
+
+        .note-checkbox {
+            width: 18px;
+            height: 18px;
+            border: 2px solid #7438FA;
+            border-radius: 4px;
+            background: white;
+            cursor: pointer;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: white;
+            font-size: 12px;
+            font-weight: bold;
+            transition: all 0.2s ease;
+            flex-shrink: 0;
+            margin-top: 2px;
+        }
+
+        .note-checkbox:hover {
+            background: #7438FA;
+            transform: scale(1.1);
+        }
+
+        .note-content {
+            flex: 1;
+        }
+
+        .note-text {
+            color: #333;
+            font-size: 14px;
+            line-height: 1.4;
+            margin-bottom: 5px;
+        }
+
+        .note-date {
+            color: #666;
+            font-size: 11px;
+        }
+
+        .empty-notes {
+            text-align: center;
+            color: #666;
+            font-size: 13px;
+            padding: 20px;
+            font-style: italic;
+        }
+
+        .chart-container {
+            background: white;
+            border-radius: 12px;
+            padding: 20px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
+            grid-column: span 3;
+        }
+
+        .financial-cards {
+            display: grid;
+            grid-template-columns: 1fr 1fr;
+            gap: 20px;
+            margin-bottom: 20px;
+        }
+
+        .financial-card {
+            background: white;
+            border-radius: 12px;
+            padding: 20px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
+            text-align: center;
+        }
+
+        .financial-card.income {
+            border-left: 4px solid #339E8E;
+        }
+
+        .financial-card.expense {
+            border-left: 4px solid #D6234F;
+        }
+
+        .financial-amount {
+            font-size: 36px;
+            font-weight: bold;
+            margin: 10px 0;
+        }
+
+        .financial-amount.income {
+            color: #339E8E;
+        }
+
+        .financial-amount.expense {
+            color: #D6234F;
+        }
+
+        .recent-users-table {
+            width: 100%;
+            border-collapse: collapse;
+        }
+
+        .recent-users-table th,
+        .recent-users-table td {
+            text-align: left;
+            padding: 12px 8px;
+            border-bottom: 1px solid #f0f0f0;
+        }
+
+        .recent-users-table th {
+            background: #F6F7FF;
+            font-weight: 600;
+            color: #333;
+            font-size: 12px;
+        }
+
+        .recent-users-table td {
+            font-size: 14px;
+            color: #333;
+        }
+
+        .transaction-list {
+            max-height: 300px;
+            overflow-y: auto;
+        }
+
+        .transaction-item {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            padding: 12px;
+            border-bottom: 1px solid #f0f0f0;
+        }
+
+        .transaction-name {
+            font-weight: 500;
+            color: #333;
+        }
+
+        .transaction-amount {
+            font-weight: bold;
+        }
+
+        .transaction-type {
+            font-size: 10px;
+            padding: 4px 8px;
+            border-radius: 12px;
+            color: white;
+            font-weight: bold;
+        }
+
+        .transaction-type.income {
+            background: #339E8E;
+        }
+
+        .transaction-type.expense {
+            background: #D6234F;
+        }
+
+        .participation-bar {
+            height: 20px;
+            background: #f0f0f0;
+            border-radius: 10px;
+            overflow: hidden;
+            margin: 8px 0;
+        }
+
+        .participation-fill {
+            height: 100%;
+            transition: width 0.3s ease;
+        }
+
+        .participation-fill.padel {
+            background: #FFD700;
+        }
+
+        .participation-fill.tennis {
+            background: #8B4CF7;
+        }
+
+        .participation-fill.pallavolo {
+            background: #FF6B35;
+        }
+
+        .participation-fill.yoga {
+            background: #339E8E;
+        }
+
+        .section-title {
+            font-size: 18px;
+            font-weight: bold;
+            color: #333;
+            margin-bottom: 20px;
+        }
+
+        .chart-title {
+            font-size: 16px;
+            font-weight: 600;
+            color: #333;
+            margin-bottom: 15px;
+        }
+
+        .grid-4 {
+            display: grid;
+            grid-template-columns: repeat(4, 1fr);
+            gap: 15px;
+            margin-bottom: 20px;
+        }
+
+        .grid-3 {
+            display: grid;
+            grid-template-columns: repeat(3, 1fr);
+            gap: 20px;
+            margin-bottom: 20px;
+        }
+
+        .grid-2 {
+            display: grid;
+            grid-template-columns: repeat(2, 1fr);
+            gap: 20px;
+            margin-bottom: 20px;
+        }
+
+        .full-width {
+            grid-column: span 3;
+        }
+
+        .half-width {
+            grid-column: span 2;
+        }
+
+        .fade-out {
+            opacity: 0;
+            transform: translateX(100%);
+            transition: all 0.3s ease;
+        }
+    </style>
+
+    <!-- User Statistics Cards -->
+    <div class="grid-4">
+        <div class="stat-card">
+            <div class="stat-card-header">
+                <div class="stat-card-title">Utenti Totali</div>
+                <div class="stat-icon">👥</div>
             </div>
             </div>
+            <div class="stat-card-value">{{$activeUsers}}</div>
         </div>
         </div>
-        <div class="anagrafica_chart">
-            <h1>Utenti iscritti ultimi 7 giorni</h1>
-            <canvas id="userChart"></canvas>
+
+        <div class="stat-card">
+            <div class="stat-card-header">
+                <div class="stat-card-title">Tesserati</div>
+                <div class="stat-icon">📋</div>
+            </div>
+            <div class="stat-card-value">{{$registeredUsers}}</div>
         </div>
         </div>
-        </section>
-        <section id="dash_contabilita">
-        <div class="contabilita--btn">
-            <h1>Contabilità</h1>
-            <div class="contabilita--btn_wrapper w-100 d-flex">
-                <div class="card--ui card--ui_btnDash entrata d-flex justify-items-between me-3"  wire:click="addIn()"  style="cursor: pointer;">
-                    <header class="d-flex justify-content-between"><div class="card-title d-flex align-items-start"><h2><small>Registra</small><br>Nuova Entrata</h2></div><a href="/in?new=1"><i class="ico--ui big add primary"></i></a>
-                    </header>
-                    <footer class="d-flex justify-content-between align-items-end">
-                        <div class="card-footer_percentage d-flex">
-                            <!--<i class="ico--ui small entrate ico me-1"></i><span>10%</span>-->
-                        </div>
-                        <div class="card-footer_info text-end">
-                            <span><strong>+ € {{$totTodayIn}}</strong></span> entrate oggi
-                        </div>
-                    </footer>
+
+        <div class="stat-card">
+            <div class="stat-card-header">
+                <div class="stat-card-title">Certificati medici scaduti</div>
+                <div class="stat-icon">📄</div>
+            </div>
+            <div class="stat-card-value">{{$expiredCertificates}}</div>
+        </div>
+
+        <div class="stat-card">
+            <div class="stat-card-header">
+                <div class="stat-card-title">Abbonamenti sospesi</div>
+                <div class="stat-icon">⏸️</div>
+            </div>
+            <div class="stat-card-value">{{$suspendedSubscriptions}}</div>
+        </div>
+    </div>
+
+    <div class="grid-3">
+        <div class="dashboard-section">
+            <div class="section-title">Ultimi utenti registrati</div>
+            @if(count($recentUsers) > 0)
+            <table class="recent-users-table">
+                <thead>
+                    <tr>
+                        <th>Cognome</th>
+                        <th>Nome</th>
+                        <th>Telefono</th>
+                        <th>Email</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    @foreach($recentUsers as $user)
+                    <tr>
+                        <td>{{$user['surname']}}</td>
+                        <td>{{$user['name']}}</td>
+                        <td>{{$user['phone']}}</td>
+                        <td>{{$user['email']}}</td>
+                    </tr>
+                    @endforeach
+                </tbody>
+            </table>
+            @else
+            <div style="text-align: center; color: #666; padding: 40px;">
+                Nessun utente registrato di recente
+            </div>
+            @endif
+        </div>
+
+        <!-- Corsi Section -->
+        <div class="dashboard-section">
+            <div class="section-title">
+                Corsi
+            </div>
+            @if(count($courses) > 0)
+            <div class="courses-list">
+                @foreach($courses as $course)
+                <div class="course-item">
+                    <div class="course-time">{{$course['time']}}</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>
                 </div>
-                <div class="card--ui card--ui_btnDash uscite d-flex justify-items-between"  wire:click="addOut()"  style="cursor: pointer;">
-                    <header class="d-flex justify-content-between"><div class="card-title d-flex align-items-start"><h2><small>Registra</small><br>Nuova Uscita</h2></div><a href="/out?new=1"><i class="ico--ui big add secondary"></i></a>
-                    </header>
-                    <footer class="d-flex justify-content-between align-items-end">
-                        <div class="card-footer_percentage d-flex">
-                            <!--<i class="ico--ui small uscite ico me-1"></i><span>10%</span>-->
-                        </div>
-                        <div class="card-footer_info text-end">
-                            <span><strong>- € {{$totTodayOut}}</strong></span> uscite oggi
-                        </div>
-                    </footer>
+                @endforeach
+            </div>
+            @else
+            <div style="text-align: center; color: #666; padding: 40px;">
+                Nessun corso programmato per oggi
+                <div style="font-size: 12px; margin-top: 10px; color: #999;">
+                    Oggi è: <strong>{{$dayName ?? 'N/A'}}</strong>
                 </div>
                 </div>
             </div>
             </div>
+            @endif
         </div>
         </div>
-        <div class="contabilita--chart">
-            <h1>Entrate/Uscite ultimi 7 giorni</h1>
-            <canvas id="recordChart"></canvas>
+
+        <!-- To Do Section -->
+        <div class="dashboard-section">
+            <div class="section-title">To do</div>
+            <div style="font-size: 14px; color: #666; margin-bottom: 10px;">{{date('d/m/Y')}}</div>
+
+            <!-- Notes List -->
+            @if(count($savedNotes) > 0)
+            <div class="notes-list">
+                @foreach($savedNotes as $note)
+                <div class="note-item">
+                    <div class="note-checkbox" wire:click="completeNote('{{$note['id']}}')" title="Segna come completato">
+                        ✓
+                    </div>
+                    <div class="note-content">
+                        <div class="note-text">{{$note['text']}}</div>
+                        <div class="note-date">{{$note['created_at']}}</div>
+                    </div>
+                </div>
+                @endforeach
+            </div>
+            @else
+            <div class="empty-notes">
+                Nessuna nota salvata
+            </div>
+            @endif
+
+            <!-- Add Note Section -->
+            <div class="notes-section">
+                <textarea class="notes-input" placeholder="Inserisci una nuova nota..." wire:model="notes"></textarea>
+                <button class="save-btn" wire:click="saveNote">SALVA</button>
+            </div>
+        </div>
+    </div>
+
+    <div class="grid-2">
+        <!-- Courses Participation Section -->
+        <div class="dashboard-section">
+            <div class="section-title">Corsi con più partecipazioni</div>
+            @if(count($coursesParticipation) > 0)
+            <div style="padding: 10px 0;">
+                @foreach($coursesParticipation as $course)
+                <div style="margin-bottom: 20px;">
+                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
+                        <span style="font-weight: 600; color: #333; font-size: 14px;">{{$course['course_name']}}</span>
+                        <span style="font-size: 12px; color: #666;">{{$course['participants']}} partecipanti</span>
+                    </div>
+                    <div class="participation-bar">
+                        <div class="participation-fill {{$course['color']}}" style="width: {{$course['percentage']}}%"></div>
+                    </div>
+                </div>
+                @endforeach
+            </div>
+            @else
+            <div style="text-align: center; color: #666; padding: 40px;">
+                Nessun corso con partecipazioni
+            </div>
+            @endif
         </div>
         </div>
-        </section>
 
 
-    </main>
+        <!-- Financial Cards moved here -->
+        <div>
+            <div class="financial-cards" style="margin-bottom: 0;">
+                <div class="financial-card income">
+                    <div style="font-size: 14px; color: #666; margin-bottom: 10px;">Incassato mese corrente</div>
+                    <div class="financial-amount income">€ {{number_format($toReceive, 2)}}</div>
+                </div>
 
 
+                <div class="financial-card expense">
+                    <div style="font-size: 14px; color: #666; margin-bottom: 10px;">Pagato mese corrente</div>
+                    <div class="financial-amount expense">€ {{number_format($toPay, 2)}}</div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="dashboard-section">
+        <div class="section-title">Ultimi movimenti finanziari</div>
+        @if(count($recentTransactions) > 0)
+        <div class="transaction-list">
+            @foreach($recentTransactions as $transaction)
+            <div class="transaction-item">
+                <div class="transaction-name">{{$transaction['name']}}</div>
+                <div style="display: flex; align-items: center; gap: 10px;">
+                    <div class="transaction-amount">€ {{number_format($transaction['amount'], 2)}}</div>
+                    <div class="transaction-type {{$transaction['type'] == 'ENTRATA' ? 'income' : 'expense'}}">
+                        {{$transaction['type']}}
+                    </div>
+                </div>
+            </div>
+            @endforeach
+        </div>
+        @else
+        <div style="text-align: center; color: #666; padding: 40px;">
+            Nessun movimento finanziario recente
+        </div>
+        @endif
+    </div>
 </div>
 </div>
 
 
 @push('scripts')
 @push('scripts')
-    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
-@endpush
+<script>
+    window.addEventListener('note-saved', event => {
+        // Show success message
+        if (typeof toastr !== 'undefined') {
+            toastr.success('Nota salvata con successo!');
+        } else {
+            alert('Nota salvata con successo!');
+        }
+    });
 
 
+    window.addEventListener('courses-activated', event => {
+        if (typeof toastr !== 'undefined') {
+            toastr.success(event.detail.message);
+        } else {
+            alert(event.detail.message);
+        }
+    });
 
 
-@push('scripts')
-    <script>
-        const chart = new Chart(
-            document.getElementById('recordChart'), {
-                type: 'line',
-                data: {
-                    labels: @json($labels),
-                    datasets: @json($recordDatas)
-                },
-                options: {
-                    plugins: {
-                        legend: {
-                            position: 'bottom'
-                        }
-                    },
-                    responsive: true,
-                    elements: {
-                        line: {
-                            tension : 0.4  // smooth lines
-                        },
-                    },
-                }
-            }
-        );
-        const chartX = new Chart(
-            document.getElementById('userChart'), {
-                type: 'line',
-                data: {
-                    labels: @json($labels),
-                    datasets: @json($memberDatas)
-                },
-                options: {
-                    plugins: {
-                        legend: {
-                            position: 'bottom'
-                        }
-                    },
-                    responsive: true,
-                    elements: {
-                        line: {
-                            tension : 0.4  // smooth lines
-                        },
-                    },
-                }
+    window.addEventListener('debug-completed', event => {
+        if (typeof toastr !== 'undefined') {
+            toastr.info('Debug completato - controlla i log');
+        } else {
+            alert('Debug completato - controlla i log');
+        }
+    });
+
+    // Optional: Add keyboard shortcut to save note
+    document.addEventListener('keydown', function(e) {
+        if (e.ctrlKey && e.key === 'Enter') {
+            let textarea = document.querySelector('.notes-input');
+            if (textarea === document.activeElement) {
+                Livewire.emit('saveNote');
             }
             }
-        );
-        Livewire.on('updateChart', data => {
-            chart.data = data;
-            chart.update();
-        });
-    </script>
+        }
+    });
+</script>
 @endpush
 @endpush

+ 306 - 0
resources/views/livewire/email_comunications.blade.php

@@ -0,0 +1,306 @@
+<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">
+        <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)Email Templates @else Inserimento/modifica template Email @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>
+
+    <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>
+
+            <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">Parole</th>
+                        <th scope="col">Data Creazione</th>
+                        <th scope="col">...</th>
+                    </tr>
+                </thead>
+                <tbody id="checkall-target">
+                    @foreach($records as $record)
+                        <tr>
+                            <td>
+                                <strong>{{$record->name}}</strong>
+                            </td>
+                            <td>
+                                {{ Str::limit(strip_tags($record->content), 80) }}
+                            </td>
+                            <td>
+                                @php
+                                    $wordCount = str_word_count(strip_tags($record->content));
+                                    $badgeClass = $wordCount > 500 ? 'bg-warning' : 'bg-info';
+                                @endphp
+                                <span class="badge {{ $badgeClass }}">{{ $wordCount }} parole</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 Email"><i class="fa-solid fa-envelope"></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>
+                        </tr>
+                    @endforeach
+                </tbody>
+            </table>
+        </section>
+
+    @else
+
+        <div class="container">
+
+            <a class="btn--ui lightGrey" href="/email_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>
+            @endif
+
+            <div class="row">
+                <div class="col">
+
+                    <form action="">
+
+                        <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
+                                </div>
+                            </div>
+                        </div>
+
+                        <div class="row mb-3">
+                            <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="10" placeholder="Inserisci il contenuto del messaggio (supporta HTML)"></textarea>
+                                    <div class="form-text">
+                                        <small class="text-muted">Puoi utilizzare HTML per formattare il messaggio. Parole: <span class="fw-bold">{{ str_word_count(strip_tags($message)) }}</span></small>
+                                    </div>
+                                    @error('message')
+                                        <div class="invalid-feedback">{{ $message }}</div>
+                                    @enderror
+                                </div>
+                            </div>
+                        </div>
+
+                        @if($add)
+                        <div class="row mb-3">
+                            <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($users as $user)
+                                        <div class="form-check mb-1">
+                                            <input class="form-check-input" type="checkbox" value="{{ $user->id }}" wire:model="selectedRecipients" id="recipient_{{ $user->id }}">
+                                            <label class="form-check-label" for="recipient_{{ $user->id }}">
+                                                <strong>{{ $user->name }}</strong>
+                                                @if($user->email)
+                                                    <small class="text-muted">({{ $user->email }})</small>
+                                                @else
+                                                    <small class="text-danger">(no email)</small>
+                                                @endif
+                                            </label>
+                                        </div>
+                                    @endforeach
+                                </div>
+                                <div class="form-text">
+                                    <small class="text-muted">Selezionati: <span id="selectedCount">{{ count($selectedRecipients) }}</span> utenti</small>
+                                </div>
+                                @error('selectedRecipients')
+                                    <div class="text-danger">{{ $message }}</div>
+                                @enderror
+                            </div>
+                        </div>
+
+                        <div class="row mb-3">
+                            <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-envelope me-2"></i>Invia Immediatamente
+                                    </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>
+                                </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
+                            </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>
+                            @endif
+                        </div>
+
+                    </form>
+                </div>
+            </div>
+        </div>
+
+    @endif
+</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>
+@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>
+@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>
+@endpush
+
+@push('scripts')
+    <script>
+
+        $(document).ready(function() {
+            loadDataTable();
+        });
+
+        Livewire.on('load-data-table', () => {
+            loadDataTable();
+        });
+
+        function loadDataTable(){
+            if ($.fn.DataTable.isDataTable('#tablesaw-350')) {
+                $('#tablesaw-350').DataTable().destroy();
+            }
+
+            $('#tablesaw-350').DataTable({
+                thead: {
+                'th': {'background-color': 'blue'}
+                },
+                layout: {
+                    topStart : null,
+                    topEnd : null,
+                    top1A: {
+                        buttons: [
+                            {
+                                extend: 'collection',
+                                text: 'ESPORTA',
+                                buttons: [
+                                    {
+                                    extend: 'excelHtml5',
+                                        title: 'Templates Email',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        }
+                                    },
+                                    {
+                                        extend: 'pdfHtml5',
+                                        title: 'Templates Email',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        }
+                                    },
+                                    {
+                                        extend: 'print',
+                                        text: 'Stampa',
+                                        title: 'Templates Email',
+                                        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

+ 20 - 65
resources/views/livewire/member.blade.php

@@ -1639,7 +1639,6 @@
         }
         }
         var nation_id = {{$nation_id > 0 ? $nation_id : 0}};
         var nation_id = {{$nation_id > 0 ? $nation_id : 0}};
         var province_id = 0;
         var province_id = 0;
-
         var nation_birth_id = 0;
         var nation_birth_id = 0;
         var province_birth_id = 0;
         var province_birth_id = 0;
 
 
@@ -1867,6 +1866,11 @@
                 @this.delete(id);
                 @this.delete(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;
         var isFilter = false;
         $(document).ready(function() {
         $(document).ready(function() {
@@ -1988,6 +1992,7 @@
             $('.filterCertificateType').val('-1').trigger('change');
             $('.filterCertificateType').val('-1').trigger('change');
             $('.filterCategories').val('-1').trigger('change');
             $('.filterCategories').val('-1').trigger('change');
 
 
+
             $('input[name="txtFromYear"]').val('');
             $('input[name="txtFromYear"]').val('');
             $('input[name="txtToYear"]').val('');
             $('input[name="txtToYear"]').val('');
 
 
@@ -2097,7 +2102,7 @@
                         data: "action",
                         data: "action",
                         render: function (data){
                         render: function (data){
                             var ret = '<button type="button" class="btn" onclick="editData(' + data + ')" 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>&nbsp;';
                             var ret = '<button type="button" class="btn" onclick="editData(' + data + ')" 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>&nbsp;';
-                            ret += '<button type="button" class="btn" onclick="deleteData(' + 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>';
+                            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="Archivia"><i class="fa-regular fa-folder-closed"></i></button>';
                             return ret;
                             return ret;
                         }
                         }
                     },
                     },
@@ -2202,6 +2207,7 @@
                     $("span").find("[aria-controls='select2-" + myArray[0] + "-container']").css('border', 'solid 3px #dc3545');
                     $("span").find("[aria-controls='select2-" + myArray[0] + "-container']").css('border', 'solid 3px #dc3545');
             }
             }
             console.log(errors);
             console.log(errors);
+
         });
         });
 
 
         Livewire.on('reload', (x) =>
         Livewire.on('reload', (x) =>
@@ -2281,6 +2287,7 @@
             if (window.history && window.history.pushState) {
             if (window.history && window.history.pushState) {
                 window.history.pushState('', null, './');
                 window.history.pushState('', null, './');
                 $(window).on('popstate', function() {
                 $(window).on('popstate', function() {
+
                     document.location.href = '/members';
                     document.location.href = '/members';
                 });
                 });
             }
             }
@@ -2303,13 +2310,13 @@
                 console.log('Validation errors received:', fieldId);
                 console.log('Validation errors received:', fieldId);
                 let element;
                 let element;
 
 
-                if (typeof fieldId === 'string') {
-                    element = document.getElementById(fieldId);
-                } else if (Array.isArray(fieldId) && fieldId.length > 0) {
-                    const firstFieldId = fieldId[0];
-                    element = document.getElementById(firstFieldId);
-                    fieldId = firstFieldId;
-                }
+        if (typeof fieldId === 'string') {
+            element = document.getElementById(fieldId);
+        } else if (Array.isArray(fieldId) && fieldId.length > 0) {
+            const firstFieldId = fieldId[0];
+            element = document.getElementById(firstFieldId);
+            fieldId = firstFieldId; // Use the first fieldId for highlighting
+        }
 
 
                 if (element) {
                 if (element) {
                     element.focus();
                     element.focus();
@@ -2333,67 +2340,15 @@
 
 
                     const intervalId = setInterval(preserveInvalidClass, 100);
                     const intervalId = setInterval(preserveInvalidClass, 100);
 
 
-                    element.addEventListener('input', () => {
-                        clearInterval(intervalId);
-                    });
+            // Stop preserving after user interaction
+            element.addEventListener('input', () => {
+                clearInterval(intervalId);
+            });
 
 
                 } else {
                 } else {
                     console.warn('Element with id ' + fieldId + ' not found');
                     console.warn('Element with id ' + fieldId + ' not found');
                 }
                 }
             });
             });
         });
         });
-
-        function getNextTab(currentTab) {
-            const tabs = ['dati', 'tesseramento', 'corsi', 'gruppi'];
-            const currentIndex = tabs.indexOf(currentTab);
-            return currentIndex < tabs.length - 1 ? tabs[currentIndex + 1] : currentTab;
-        }
-
-        function scrollToFormTop() {
-            const formSection = document.querySelector('.section--tab, .form--wrapper, #card--resume');
-            if (formSection) {
-                formSection.scrollIntoView({
-                    behavior: 'instant',
-                    block: 'start',
-                    inline: 'nearest'
-                });
-                return true;
-            }
-            return false;
-        }
-
-        Livewire.on('saved-and-continue', (currentTab) => {
-            const nextTab = getNextTab(currentTab);
-            if (nextTab !== currentTab) {
-                @this.change(nextTab);
-            }
-
-            setTimeout(() => {
-                if (!scrollToFormTop()) {
-                    window.scrollTo({ top: 0, behavior: 'instant' });
-                    document.body.scrollTop = 0;
-                    document.documentElement.scrollTop = 0;
-                }
-                const firstInput = document.querySelector('#first_name');
-                if (firstInput) {
-                    firstInput.focus();
-                }
-            }, 200);
-        });
-        Livewire.on('course-availability-updated', () => {
-            console.log('Course availability updated');
-            // You can add any additional UI updates here if needed
-        });
-
-        // Update the existing card/certificate save handlers
-        window.addEventListener('card-saved', event => {
-            console.log('Card saved, checking course availability');
-            // The course availability will be automatically updated by the Livewire component
-        });
-
-        window.addEventListener('certificate-saved', event => {
-            console.log('Certificate saved, checking course availability');
-            // The course availability will be automatically updated by the Livewire component
-        });
     </script>
     </script>
 @endpush
 @endpush

Деякі файли не було показано, через те що забагато файлів було змінено