*/ public array $nuoviAllegati = []; /** @var array */ public array $nuoveFoto = []; public ?string $descrizioneAllegati = null; public ?string $lastGeneratedPassword = null; public ?int $selectedCategoriaId = null; public ?string $categoriaNome = null; public ?string $categoriaDescrizione = null; /** @var array */ public array $categorieOptions = []; /** @var array */ public array $fornitoriOptions = []; /** @var array> */ public array $fornitoriAttiviRows = []; /** @var array|null */ public ?array $attachmentPreview = null; /** @var \Illuminate\Support\Collection */ public $tickets; /** @var array */ public array $ticketCounters = [ 'open' => 0, 'urgent' => 0, 'closed' => 0, 'all' => 0, ]; public static function canAccess(): bool { $user = Auth::user(); return $user instanceof User && $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']); } public function mount(): void { $this->tickets = collect(); $this->selectedTicketId = (int) request()->query('ticket', 0) ?: null; $queryTab = (string) request()->query('tab', ''); if ($queryTab === 'scheda') { $this->activeTab = 'scheda'; } if (request()->query('status') === 'all') { $this->status = 'all'; } $this->loadFornitoriOptions(); $this->loadCategorieTicketOptions(); $this->refreshFornitoriAttiviRows(); if ($this->selectedTicket) { $this->fornitoreId = (int) ($this->selectedTicket->assegnato_a_fornitore_id ?? 0) ?: null; } $this->refreshData(); } public function updatedSelectedCategoriaId($value): void { $id = (int) $value; if ($id <= 0) { $this->categoriaNome = null; $this->categoriaDescrizione = null; return; } $categoria = CategoriaTicket::query()->find($id); if (! $categoria instanceof CategoriaTicket) { return; } $this->categoriaNome = (string) $categoria->nome; $this->categoriaDescrizione = (string) ($categoria->descrizione ?? ''); } public function creaCategoriaTicket(): void { $this->validate([ 'categoriaNome' => ['required', 'string', 'max:255', Rule::unique('categorie_ticket', 'nome')], 'categoriaDescrizione' => ['nullable', 'string', 'max:2000'], ]); $categoria = CategoriaTicket::query()->create([ 'nome' => trim((string) $this->categoriaNome), 'descrizione' => filled($this->categoriaDescrizione) ? trim((string) $this->categoriaDescrizione) : null, ]); $this->loadCategorieTicketOptions(); $this->selectedCategoriaId = (int) $categoria->id; $this->updatedSelectedCategoriaId($this->selectedCategoriaId); Notification::make()->title('Categoria creata')->success()->send(); } public function aggiornaCategoriaTicket(): void { $id = (int) ($this->selectedCategoriaId ?? 0); if ($id <= 0) { Notification::make()->title('Seleziona una categoria da modificare')->warning()->send(); return; } $this->validate([ 'categoriaNome' => ['required', 'string', 'max:255', Rule::unique('categorie_ticket', 'nome')->ignore($id)], 'categoriaDescrizione' => ['nullable', 'string', 'max:2000'], ]); $categoria = CategoriaTicket::query()->find($id); if (! $categoria instanceof CategoriaTicket) { Notification::make()->title('Categoria non trovata')->danger()->send(); return; } $categoria->update([ 'nome' => trim((string) $this->categoriaNome), 'descrizione' => filled($this->categoriaDescrizione) ? trim((string) $this->categoriaDescrizione) : null, ]); $this->loadCategorieTicketOptions(); Notification::make()->title('Categoria aggiornata')->success()->send(); $this->refreshData(); } public function updatedStatus(): void { $this->refreshData(); } public function refreshData(): void { $this->loadCounters(); $this->loadTickets(); $this->refreshFornitoriAttiviRows(); } public function getTicketInserimentoUrl(): string { return TicketMobile::getUrl(panel: 'admin-filament'); } public function apriScheda(int $ticketId): void { $this->selectedTicketId = $ticketId; $this->activeTab = 'scheda'; $ticket = $this->selectedTicket; $this->fornitoreId = $ticket ? ((int) ($ticket->assegnato_a_fornitore_id ?? 0) ?: null): null; } public function apriElenco(): void { $this->activeTab = 'elenco'; } public function getFornitoreOperativoUrl(?int $fornitoreId = null): string { $base = route('fornitore.tickets.index'); if ($fornitoreId) { return $base . '?fornitore=' . $fornitoreId; } return $base; } public function getFornitoreAnagraficaUrl(int $fornitoreId): string { return \App\Filament\Pages\Gescon\FornitoreScheda::getUrl(['record' => $fornitoreId], panel: 'admin-filament'); } public function getAttachmentPublicUrl(TicketAttachment $attachment): string { return route('filament.tickets.attachments.view', ['attachment' => (int) $attachment->id]); } public function openAttachmentPreview(int $attachmentId): void { $ticket = $this->selectedTicket; if (! $ticket) { return; } $attachment = $ticket->attachments->firstWhere('id', $attachmentId); if (! $attachment instanceof TicketAttachment) { return; } $name = (string) ($attachment->original_file_name ?? 'allegato'); $mime = strtolower((string) ($attachment->mime_type ?? '')); $ext = strtolower((string) pathinfo($name, PATHINFO_EXTENSION)); $isImage = str_starts_with($mime, 'image/') || in_array($ext, ['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp'], true); $isPdf = $mime === 'application/pdf' || $ext === 'pdf'; $this->attachmentPreview = [ 'id' => (int) $attachment->id, 'name' => $name, 'description' => (string) ($attachment->description ?? ''), 'url' => $this->getAttachmentPublicUrl($attachment), 'is_image' => $isImage, 'is_pdf' => $isPdf, ]; } public function closeAttachmentPreview(): void { $this->attachmentPreview = null; } public function getSelectedTicketProperty(): ?Ticket { if (! $this->selectedTicketId) { return null; } $user = Auth::user(); if (! $user instanceof User) { return null; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return null; } return Ticket::query() ->with([ 'stabile:id,denominazione', 'categoriaTicket:id,nome', 'assegnatoAFornitore:id,ragione_sociale,nome,cognome', 'attachments.user:id,name', 'messages.user:id,name', 'interventi.fornitore:id,ragione_sociale,nome,cognome', 'interventi.creatoDaUser:id,name', ]) ->where('stabile_id', $stabileId) ->find($this->selectedTicketId); } public function assegnaFornitore(): void { $ticket = $this->selectedTicket; if (! $ticket) { Notification::make()->title('Ticket non trovato')->danger()->send(); return; } $this->validate([ 'fornitoreId' => ['required', 'integer', 'exists:fornitori,id'], 'noteAssegnazione' => ['nullable', 'string', 'max:4000'], ]); $ticket->update([ 'assegnato_a_fornitore_id' => $this->fornitoreId, 'stato' => in_array($ticket->stato, ['Aperto'], true) ? 'Preso in Carico' : $ticket->stato, ]); if (Schema::hasTable('ticket_interventi')) { $ultimo = TicketIntervento::query() ->where('ticket_id', $ticket->id) ->where('fornitore_id', $this->fornitoreId) ->latest('id') ->first(); if (! $ultimo) { TicketIntervento::query()->create([ 'ticket_id' => $ticket->id, 'fornitore_id' => $this->fornitoreId, 'creato_da_user_id' => Auth::id(), 'stato' => 'assegnato', 'note_amministratore' => $this->noteAssegnazione, ]); } } if (filled($this->noteAssegnazione)) { $ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => 'Assegnazione fornitore: ' . trim((string) $this->noteAssegnazione), ]); } $this->noteAssegnazione = null; Notification::make()->title('Fornitore assegnato')->success()->send(); $this->refreshData(); } public function aggiungiNotaInterna(): void { $ticket = $this->selectedTicket; if (! $ticket) { Notification::make()->title('Ticket non trovato')->danger()->send(); return; } $this->validate([ 'notaInterna' => ['required', 'string', 'max:5000'], ]); $stamp = now()->format('d/m/Y H:i'); $ticket->messages()->create([ 'user_id' => Auth::id(), 'messaggio' => '[' . $stamp . '] ' . trim((string) $this->notaInterna), ]); $this->notaInterna = null; // Force refresh of selected ticket relations so the new note is visible immediately. $this->refreshData(); Notification::make()->title('Nota aggiunta')->success()->send(); } public function caricaAllegati(): void { $ticket = $this->selectedTicket; if (! $ticket) { Notification::make()->title('Ticket non trovato')->danger()->send(); return; } if (! Schema::hasTable('ticket_attachments')) { Notification::make()->title('Tabella allegati non pronta')->danger()->send(); return; } $this->validate([ 'nuoviAllegati' => ['nullable', 'array', 'max:8'], 'nuoviAllegati.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp,pdf,doc,docx,xls,xlsx,txt'], 'nuoveFoto' => ['nullable', 'array', 'max:8'], 'nuoveFoto.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp'], 'descrizioneAllegati' => ['nullable', 'string', 'max:500'], ]); $files = array_merge($this->nuoveFoto, $this->nuoviAllegati); if (count($files) === 0) { Notification::make()->title('Seleziona almeno un file o una foto')->warning()->send(); return; } $saved = 0; foreach ($files as $file) { if (! is_object($file) || ! method_exists($file, 'store')) { continue; } $path = $file->store('ticket-gestione/' . $ticket->id, 'public'); TicketAttachment::query()->create([ 'ticket_id' => $ticket->id, 'ticket_update_id' => null, 'user_id' => Auth::id(), 'file_path' => $path, 'original_file_name' => (string) $file->getClientOriginalName(), 'mime_type' => (string) $file->getClientMimeType(), 'size' => (int) $file->getSize(), 'description' => $this->descrizioneAllegati ?: 'Allegato da gestione ticket', ]); $saved++; } $this->nuoveFoto = []; $this->nuoviAllegati = []; $this->descrizioneAllegati = null; $this->refreshData(); Notification::make() ->title('Allegati caricati') ->body('Caricati ' . $saved . ' allegati.') ->success() ->send(); } public function prendiInCarico(int $ticketId): void { $this->aggiornaStatoTicket($ticketId, 'Preso in Carico', true); } public function avviaLavorazione(int $ticketId): void { $this->aggiornaStatoTicket($ticketId, 'In Lavorazione'); } public function risolviTicket(int $ticketId): void { $this->aggiornaStatoTicket($ticketId, 'Risolto'); } public function chiudiTicket(int $ticketId): void { $this->aggiornaStatoTicket($ticketId, 'Chiuso'); } public function abilitaAccessoFornitoreDaTicket(int $fornitoreId): void { $user = Auth::user(); if (! $user instanceof User) { return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { Notification::make()->title('Stabile attivo non disponibile')->warning()->send(); return; } $stabile = \App\Models\Stabile::query()->find($stabileId); $adminId = (int) ($stabile->amministratore_id ?? 0); $fornitore = Fornitore::query()->find($fornitoreId); if (! $fornitore instanceof Fornitore) { Notification::make()->title('Fornitore non trovato')->danger()->send(); return; } if ($adminId > 0 && (int) ($fornitore->amministratore_id ?? 0) > 0 && (int) ($fornitore->amministratore_id ?? 0) !== $adminId) { Notification::make()->title('Fornitore non valido per questo stabile')->danger()->send(); return; } $email = trim((string) ($fornitore->email ?? '')); if ($email === '') { Notification::make()->title('Email fornitore mancante')->warning()->send(); return; } $dipendente = FornitoreDipendente::query() ->where('fornitore_id', (int) $fornitore->id) ->whereRaw('LOWER(email) = ?', [mb_strtolower($email)]) ->first(); if (! $dipendente instanceof FornitoreDipendente) { $dipendente = FornitoreDipendente::query()->create([ 'fornitore_id' => (int) $fornitore->id, 'nome' => (string) ($fornitore->ragione_sociale ?: ($fornitore->nome ?: 'Referente fornitore')), 'cognome' => $fornitore->ragione_sociale ? null : (($fornitore->cognome ?? null) ?: null), 'email' => $email, 'telefono' => $fornitore->telefono ?: $fornitore->cellulare, 'attivo' => true, 'created_by_user_id' => Auth::id(), 'updated_by_user_id' => Auth::id(), ]); } $this->abilitaAccessoDipendente($dipendente); $this->refreshData(); } public function resetPasswordFornitoreUser(int $userId): void { $user = Auth::user(); if (! $user instanceof User) { return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { Notification::make()->title('Stabile attivo non disponibile')->warning()->send(); return; } $stabile = \App\Models\Stabile::query()->find($stabileId); $adminId = (int) ($stabile->amministratore_id ?? 0); $canReset = FornitoreDipendente::query() ->where('user_id', $userId) ->whereHas('fornitore', function ($q) use ($adminId): void { if ($adminId > 0) { $q->where('amministratore_id', $adminId); } }) ->exists(); if (! $canReset) { Notification::make()->title('Utente non autorizzato per reset')->danger()->send(); return; } $target = User::query()->find($userId); if (! $target instanceof User) { Notification::make()->title('Utente non trovato')->danger()->send(); return; } $newPassword = Str::random(12); $target->password = Hash::make($newPassword); $target->save(); $this->lastGeneratedPassword = $newPassword; Notification::make() ->title('Password fornitore resettata') ->body('Nuova password temporanea: ' . $newPassword) ->success() ->send(); } private function loadTickets(): void { $user = Auth::user(); if (! $user instanceof User) { $this->tickets = collect(); return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { $this->tickets = collect(); return; } $query = Ticket::query() ->with(['categoriaTicket', 'assegnatoAFornitore:id,ragione_sociale,nome,cognome', 'assegnatoAUser:id,name']) ->withCount('attachments') ->where('stabile_id', $stabileId); if ($this->status === 'open') { $query->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione']); } elseif ($this->status === 'urgent') { $query->where('priorita', 'Urgente') ->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione']); } elseif ($this->status === 'closed') { $query->whereIn('stato', ['Risolto', 'Chiuso']); } $this->tickets = $query->latest('data_apertura')->limit(50)->get(); } private function refreshFornitoriAttiviRows(): void { $user = Auth::user(); if (! $user instanceof User) { $this->fornitoriAttiviRows = []; return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { $this->fornitoriAttiviRows = []; return; } $ticketApertiStati = ['Aperto', 'Preso in Carico', 'In Lavorazione', 'In Attesa Approvazione', 'In Attesa Ricambi']; $assignedTickets = Ticket::query() ->where('stabile_id', $stabileId) ->whereIn('stato', $ticketApertiStati) ->whereNotNull('assegnato_a_fornitore_id') ->get(['id', 'assegnato_a_fornitore_id', 'stato', 'updated_at']); $assignedByFornitore = $assignedTickets ->groupBy('assegnato_a_fornitore_id') ->map(fn($rows) => $rows->count()); $ticketSummaries = $assignedTickets ->groupBy('assegnato_a_fornitore_id') ->map(function ($rows) { return $rows->sortByDesc('updated_at')->take(6)->map(function ($ticket): string { return '#' . ((int) $ticket->id) . ' (' . (string) $ticket->stato . ')'; })->values()->all(); }); $openInterventiByFornitore = collect(); $interventoStatiByFornitore = collect(); $lastInterventoAtByFornitore = collect(); if (Schema::hasTable('ticket_interventi')) { $openInterventi = TicketIntervento::query() ->whereIn('stato', ['assegnato', 'in_corso', 'in_verifica']) ->whereHas('ticket', function ($q) use ($stabileId, $ticketApertiStati): void { $q->where('stabile_id', $stabileId)->whereIn('stato', $ticketApertiStati); }) ->orderByDesc('updated_at') ->get(['fornitore_id', 'stato', 'updated_at']); $openInterventiByFornitore = $openInterventi ->groupBy('fornitore_id') ->map(fn($rows) => $rows->count()); $interventoStatiByFornitore = $openInterventi ->groupBy('fornitore_id') ->map(function ($rows): string { return $rows->pluck('stato') ->map(fn($stato) => str_replace('_', ' ', (string) $stato)) ->unique() ->take(3) ->implode(', '); }); $lastInterventoAtByFornitore = $openInterventi ->groupBy('fornitore_id') ->map(function ($rows): ?string { $last = $rows->sortByDesc('updated_at')->first(); return $last && $last->updated_at ? $last->updated_at->format('d/m/Y H:i') : null; }); } $fornitoriIds = collect($assignedByFornitore->keys()) ->merge($openInterventiByFornitore->keys()) ->map(fn($v) => (int) $v) ->filter(fn(int $v) => $v > 0) ->unique() ->values(); if ($fornitoriIds->isEmpty()) { $this->fornitoriAttiviRows = []; return; } $rows = []; $accessRows = FornitoreDipendente::query() ->whereIn('fornitore_id', $fornitoriIds->all()) ->get(['fornitore_id', 'user_id']); $dipCountByFornitore = $accessRows ->groupBy('fornitore_id') ->map(fn($r) => $r->count()); $linkedUserByFornitore = $accessRows ->filter(fn($r) => (int) ($r->user_id ?? 0) > 0) ->groupBy('fornitore_id') ->map(fn($r) => (int) ($r->first()->user_id ?? 0)); foreach (Fornitore::query()->whereIn('id', $fornitoriIds->all())->orderBy('ragione_sociale')->orderBy('cognome')->orderBy('nome')->get() as $fornitore) { $rows[] = [ 'id' => (int) $fornitore->id, 'nome' => trim((string) ($fornitore->ragione_sociale ?: trim(($fornitore->nome ?? '') . ' ' . ($fornitore->cognome ?? '')))), 'email' => (string) ($fornitore->email ?? ''), 'telefono' => (string) ($fornitore->telefono ?: $fornitore->cellulare), 'ticket_attivi' => (int) ($assignedByFornitore->get((string) $fornitore->id) ?? $assignedByFornitore->get((int) $fornitore->id) ?? 0), 'interventi_aperti' => (int) ($openInterventiByFornitore->get((string) $fornitore->id) ?? $openInterventiByFornitore->get((int) $fornitore->id) ?? 0), 'ticket_focus' => (array) ($ticketSummaries->get((string) $fornitore->id) ?? $ticketSummaries->get((int) $fornitore->id) ?? []), 'intervento_stati' => (string) ($interventoStatiByFornitore->get((string) $fornitore->id) ?? $interventoStatiByFornitore->get((int) $fornitore->id) ?? ''), 'ultimo_aggiornamento' => (string) ($lastInterventoAtByFornitore->get((string) $fornitore->id) ?? $lastInterventoAtByFornitore->get((int) $fornitore->id) ?? ''), 'dipendenti_count' => (int) ($dipCountByFornitore->get((string) $fornitore->id) ?? $dipCountByFornitore->get((int) $fornitore->id) ?? 0), 'linked_user_id' => (int) ($linkedUserByFornitore->get((string) $fornitore->id) ?? $linkedUserByFornitore->get((int) $fornitore->id) ?? 0), ]; } usort($rows, function (array $a, array $b): int { $lhs = ((int) $a['interventi_aperti']) * 1000 + (int) $a['ticket_attivi']; $rhs = ((int) $b['interventi_aperti']) * 1000 + (int) $b['ticket_attivi']; return $rhs <=> $lhs; }); $this->fornitoriAttiviRows = $rows; } private function abilitaAccessoDipendente(FornitoreDipendente $dipendente): void { $email = trim((string) ($dipendente->email ?? '')); if ($email === '') { Notification::make()->title('Email dipendente mancante')->warning()->send(); return; } $existingUser = User::query()->whereRaw('LOWER(email) = ?', [mb_strtolower($email)])->first(); $created = false; if (! $existingUser instanceof User) { $generatedPassword = Str::random(12); $existingUser = User::query()->create([ 'name' => trim((string) ($dipendente->nome_completo ?: 'Utente fornitore')), 'email' => $email, 'password' => Hash::make($generatedPassword), 'email_verified_at' => now(), 'is_active' => true, ]); $this->lastGeneratedPassword = $generatedPassword; $created = true; } $existingUser->assignRole('fornitore'); $dipendente->user_id = (int) $existingUser->id; $dipendente->updated_by_user_id = Auth::id(); $dipendente->save(); $body = $created ? 'Accesso creato. Password temporanea: ' . ($this->lastGeneratedPassword ?? '(non disponibile)') : 'Accesso attivato/aggiornato per utente esistente.'; Notification::make()->title('Accesso fornitore abilitato')->body($body)->success()->send(); } private function loadFornitoriOptions(): void { $user = Auth::user(); if (! $user instanceof User) { $this->fornitoriOptions = []; return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { $this->fornitoriOptions = []; return; } $stabile = \App\Models\Stabile::query()->find($stabileId); $adminId = (int) ($stabile->amministratore_id ?? 0); $query = Fornitore::query()->orderBy('ragione_sociale')->orderBy('cognome')->orderBy('nome'); if ($adminId > 0) { $query->where(function ($q) use ($adminId): void { $q->where('amministratore_id', $adminId)->orWhereNull('amministratore_id'); }); } $this->fornitoriOptions = $query->limit(400)->get()->map(function (Fornitore $f): array { $label = trim((string) ($f->ragione_sociale ?: trim(($f->nome ?? '') . ' ' . ($f->cognome ?? '')))); return [ 'id' => (int) $f->id, 'nome' => $label !== '' ? $label : ('Fornitore #' . $f->id), ]; })->all(); } private function loadCategorieTicketOptions(): void { if (! Schema::hasTable('categorie_ticket')) { $this->categorieOptions = []; return; } $this->categorieOptions = CategoriaTicket::query() ->orderBy('nome') ->get(['id', 'nome', 'descrizione']) ->map(fn(CategoriaTicket $c): array=> [ 'id' => (int) $c->id, 'nome' => (string) $c->nome, 'descrizione' => (string) ($c->descrizione ?? ''), ]) ->all(); } public function getTipoInterventoLabel(Ticket $ticket): string { $categoria = trim((string) optional($ticket->categoriaTicket)->nome); $titolo = trim((string) ($ticket->titolo ?? '')); if ($categoria !== '' && $titolo !== '') { return $categoria . ' - ' . $titolo; } if ($categoria !== '') { return $categoria; } if ($titolo !== '') { return $titolo; } return 'N/D'; } private function loadCounters(): void { $user = Auth::user(); if (! $user instanceof User) { $this->ticketCounters = ['open' => 0, 'urgent' => 0, 'closed' => 0, 'all' => 0]; return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { $this->ticketCounters = ['open' => 0, 'urgent' => 0, 'closed' => 0, 'all' => 0]; return; } $base = Ticket::query()->where('stabile_id', $stabileId); $this->ticketCounters = [ 'open' => (clone $base)->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(), 'urgent' => (clone $base)->where('priorita', 'Urgente')->whereIn('stato', ['Aperto', 'Preso in Carico', 'In Lavorazione'])->count(), 'closed' => (clone $base)->whereIn('stato', ['Risolto', 'Chiuso'])->count(), 'all' => (clone $base)->count(), ]; } private function aggiornaStatoTicket(int $ticketId, string $nuovoStato, bool $assegnaAUtente = false): void { $user = Auth::user(); if (! $user instanceof User) { return; } $stabileId = StabileContext::resolveActiveStabileId($user); if (! $stabileId) { return; } $ticket = Ticket::query() ->where('id', $ticketId) ->where('stabile_id', $stabileId) ->first(); if (! $ticket) { Notification::make() ->title('Ticket non trovato o non accessibile') ->danger() ->send(); return; } $payload = ['stato' => $nuovoStato]; if ($assegnaAUtente) { $payload['assegnato_a_user_id'] = $user->id; } if ($nuovoStato === 'Risolto' && ! $ticket->data_risoluzione_effettiva) { $payload['data_risoluzione_effettiva'] = now()->toDateString(); } if ($nuovoStato === 'Chiuso' && ! $ticket->data_chiusura_effettiva) { $payload['data_chiusura_effettiva'] = now()->toDateString(); } $ticket->update($payload); Notification::make() ->title('Ticket #' . $ticket->id . ' aggiornato a ' . $nuovoStato) ->success() ->send(); $this->refreshData(); } }