*/ public array $nuoviAllegati = []; /** @var array */ public array $nuoveFoto = []; public ?string $descrizioneAllegati = null; /** @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->refreshFornitoriAttiviRows(); if ($this->selectedTicket) { $this->fornitoreId = (int) ($this->selectedTicket->assegnato_a_fornitore_id ?? 0) ?: null; } $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('admin.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; 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; 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'); } 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 = []; 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) ?? ''), ]; } 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 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 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(); } }