Browse Source

comunicazioni v1

FabioFratini 6 months ago
parent
commit
f7d10f412a

+ 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;
+    }
+}

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

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

+ 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 $showReset = false;
     protected $rules = [
         'name' => 'required'
     ];
@@ -227,7 +228,7 @@ class Sponsor extends Component
             \App\Models\Sponsor::find($id)->delete();
             session()->flash('success',"Sponsor eliminato");
         }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();
             session()->flash('success',"Contratto eliminato");
         }catch(\Exception $e){
-            session()->flash('error','Errore (' . $ex->getMessage() . ')');
+            session()->flash('error','Errore (' . $e->getMessage() . ')');
         }
     }
 

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

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

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

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

+ 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>

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

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

+ 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

+ 0 - 0
resources/views/livewire/mail_comunications.blade.php


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

@@ -11,12 +11,6 @@
 
     <section id="setting">
 
-        <!-- <div class="row mb-3">
-            <div class="col-md-3 p-3"><h4 style="cursor:pointer;{{$type == 'anagrafica' ? 'text-decoration:underline;' : ''}}" wire:click="change('anagrafica')">Anagrafica</h4></div>
-            <div class="col-md-3 p-3"><h4 style="cursor:pointer;{{$type == 'corsi' ? 'text-decoration:underline;' : ''}}" wire:click="change('corsi')">Corsi</h4></div>
-            <div class="col-md-3 p-3"><h4 style="cursor:pointer;{{$type == 'contabilita' ? 'text-decoration:underline;' : ''}}" wire:click="change('contabilita')">Contabilità</h4></div>
-        </div> -->
-
         <div class="section--tab d-flex">
             <h4 style="cursor:pointer;{{$type == 'anagrafica' ? 'border-bottom:2px solid #0C6197; color:#0C6197;' : ''}}" wire:click="change('anagrafica')">Anagrafica</h4>
             <h4 style="cursor:pointer;{{$type == 'corsi' ? 'border-bottom:2px solid #0C6197; color:#0C6197;' : ''}}" wire:click="change('corsi')">Corsi</h4>

+ 0 - 0
resources/views/livewire/sms_comunications.blad.php


+ 331 - 0
resources/views/livewire/sms_comunications.blade.php

@@ -0,0 +1,331 @@
+<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)SMS Templates @else Inserimento/modifica template SMS @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">Caratteri</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($record->content, 50) }}
+                            </td>
+                            <td>
+                                @php
+                                    $length = strlen($record->content);
+                                    $badgeClass = $length > 160 ? 'bg-danger' : ($length > 140 ? 'bg-warning' : 'bg-success');
+                                @endphp
+                                <span class="badge {{ $badgeClass }}">{{ $length }}/160</span>
+                            </td>
+                            <td>{{ $record->created_at->format('d/m/Y H:i') }}</td>
+                            <td>
+                                <button type="button" class="btn" wire:click="sendTemplate({{ $record->id }})" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Invia SMS"><i class="fa-solid fa-paper-plane"></i></button>
+                                <button type="button" class="btn" wire:click="edit({{ $record->id }})" data-bs-toggle="popover"  data-bs-trigger="hover focus" data-bs-placement="bottom" data-bs-content="Modifica"><i class="fa-regular fa-pen-to-square"></i></button>
+                            </td>
+                        </tr>
+                    @endforeach
+                </tbody>
+            </table>
+        </section>
+
+    @else
+
+        <div class="container">
+
+            <a class="btn--ui lightGrey" href="/sms_comunications"><i class="fa-solid fa-arrow-left"></i></a><br><br>
+
+            @if (session()->has('error'))
+                <div class="alert alert-danger" role="alert">
+                    {{ session()->get('error') }}
+                </div>
+            @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="4" placeholder="Inserisci il contenuto del messaggio (max 160 caratteri)" maxlength="160"></textarea>
+                                    <div class="form-text">
+                                        Caratteri: <span class="fw-bold">{{ strlen($message) }}</span>/160
+                                        @if(strlen($message) > 160)
+                                            <span class="text-danger"> - Troppi caratteri!</span>
+                                        @endif
+                                    </div>
+                                    @error('message')
+                                        <div class="invalid-feedback">{{ $message }}</div>
+                                    @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($members as $member)
+                                        <div class="form-check mb-1">
+                                            <input class="form-check-input" type="checkbox" value="{{ $member->id }}" wire:model="selectedRecipients" id="recipient_{{ $member->id }}">
+                                            <label class="form-check-label" for="recipient_{{ $member->id }}">
+                                                <strong>{{ $member->last_name }}</strong>
+                                                @if($member->phone)
+                                                    <small class="text-muted">({{ $member->phone }})</small>
+                                                @else
+                                                    <small class="text-danger">(no phone)</small>
+                                                @endif
+                                            </label>
+                                        </div>
+                                    @endforeach
+                                </div>
+                                <div class="form-text">
+                                    <small class="text-muted">Selezionati: <span id="selectedCount">{{ count($selectedRecipients) }}</span> utenti</small>
+                                </div>
+                                @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-paper-plane 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 SMS',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        }
+                                    },
+                                    {
+                                        extend: 'pdfHtml5',
+                                        title: 'Templates SMS',
+                                        exportOptions: {
+                                            columns: ":not(':last')"
+                                        }
+                                    },
+                                    {
+                                        extend: 'print',
+                                        text: 'Stampa',
+                                        title: 'Templates SMS',
+                                        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")
+                });
+            });
+
+        }
+
+        function selectAllUsers() {
+            $('input[wire\\:model="selectedRecipients"]').prop('checked', true).trigger('change');
+            updateSelectedCount();
+        }
+
+        function deselectAllUsers() {
+            $('input[wire\\:model="selectedRecipients"]').prop('checked', false).trigger('change');
+            updateSelectedCount();
+        }
+
+        function updateSelectedCount() {
+            setTimeout(function() {
+                let count = $('input[wire\\:model="selectedRecipients"]:checked').length;
+                $('#selectedCount').text(count);
+            }, 100);
+        }
+
+        // Update count when checkboxes change
+        $(document).on('change', 'input[wire\\:model="selectedRecipients"]', function() {
+            updateSelectedCount();
+        });
+
+    </script>
+@endpush

+ 11 - 1
routes/web.php

@@ -123,7 +123,8 @@ Route::group(['middleware' => 'tenant'], function () {
     Route::get('/rates', \App\Http\Livewire\Rate::class);
     Route::get('/reports', \App\Http\Livewire\Reports::class);
     Route::get('/azienda', \App\Http\Livewire\Azienda::class);
-
+    Route::get('/sms_comunications', \App\Http\Livewire\SMSComunications::class);
+    Route::get('/mail_comunications', \App\Http\Livewire\EmailComunications::class);
 
 
 Route::get('/receipt/{id}', function ($id) {
@@ -1728,3 +1729,12 @@ Route::get('/test_mail', function(){
     }
 
 });
+Route::middleware(['auth'])->group(function () {
+    Route::get('/sms', [App\Http\Controllers\SmsTemplateController::class, 'index'])->name('sms.index');
+    Route::get('/sms/templates/json', [App\Http\Controllers\SmsTemplateController::class, 'getTemplatesJson']);
+    Route::get('/sms/categories', [App\Http\Controllers\SmsTemplateController::class, 'getCategories']);
+    Route::get('/sms/templates/{id}', [App\Http\Controllers\SmsTemplateController::class, 'getTemplate']);
+    Route::post('/sms/templates', [App\Http\Controllers\SmsTemplateController::class, 'store']);
+    Route::delete('/sms/templates/{id}', [App\Http\Controllers\SmsTemplateController::class, 'destroy']);
+    Route::get('/email_comunications', \App\Http\Livewire\EmailComunications::class)->name('email.communications');
+});