feat(tickets): desktop mobile-upload UX, max10 files, attachment cues

This commit is contained in:
michele 2026-03-12 15:05:02 +00:00
parent 2b7dcb6ab7
commit 62f8613f9e
4 changed files with 53 additions and 29 deletions

View File

@ -387,7 +387,8 @@ private function loadTickets(): void
}
$query = Ticket::query()
->with(['categoriaTicket', 'assegnatoAFornitore:id,ragione_sociale,nome,cognome'])
->with(['categoriaTicket', 'assegnatoAFornitore:id,ragione_sociale,nome,cognome', 'assegnatoAUser:id,name'])
->withCount('attachments')
->where('stabile_id', $stabileId);
if ($this->status === 'open') {

View File

@ -290,21 +290,32 @@ public function selezionaCaller(int $rubricaId): void
public function creaTicketRapido(): void
{
$this->validate([
'newTicketTitolo' => ['required', 'string', 'max:255'],
'newTicketDescrizione' => ['required', 'string'],
'newTicketLuogo' => ['nullable', 'string', 'max:255'],
'newTicketPriorita' => ['required', 'in:Bassa,Media,Alta,Urgente'],
'newTicketCategoriaId' => ['nullable', 'integer', 'exists:categorie_ticket,id'],
'newTicketTipoIntervento' => ['nullable', 'string', 'max:80'],
'newTicketFotoNote' => ['nullable', 'string', 'max:2000'],
'newTicketCameraShots' => ['nullable', 'array', 'max:8'],
'newTicketCameraShots.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp'],
'newTicketAttachments' => ['nullable', 'array', 'max:8'],
'newTicketAttachments.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp,pdf,doc,docx,xls,xlsx,txt'],
'newTicketTitolo' => ['required', 'string', 'max:255'],
'newTicketDescrizione' => ['required', 'string'],
'newTicketLuogo' => ['nullable', 'string', 'max:255'],
'newTicketPriorita' => ['required', 'in:Bassa,Media,Alta,Urgente'],
'newTicketCategoriaId' => ['nullable', 'integer', 'exists:categorie_ticket,id'],
'newTicketTipoIntervento' => ['nullable', 'string', 'max:80'],
'newTicketFotoNote' => ['nullable', 'string', 'max:2000'],
'newTicketCameraShots' => ['nullable', 'array', 'max:10'],
'newTicketCameraShots.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp'],
'newTicketAttachments' => ['nullable', 'array', 'max:10'],
'newTicketAttachments.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp,pdf,doc,docx,xls,xlsx,txt'],
'newTicketAttachmentDescriptions' => ['nullable', 'array'],
'newTicketAttachmentDescriptions.*' => ['nullable', 'string', 'max:255'],
]);
$totalUploads = count($this->newTicketCameraShots) + count($this->newTicketAttachments);
if ($totalUploads > 10) {
Notification::make()
->title('Troppi allegati selezionati')
->body('Puoi caricare al massimo 10 file totali (foto + documenti) per ticket.')
->warning()
->send();
return;
}
$user = Auth::user();
if (! $user instanceof User) {
return;
@ -375,16 +386,16 @@ public function creaTicketRapido(): void
->success()
->send();
$this->newTicketTitolo = null;
$this->newTicketDescrizione = null;
$this->newTicketLuogo = null;
$this->newTicketPriorita = 'Media';
$this->newTicketCategoriaId = null;
$this->newTicketTipoIntervento = 'altro';
$this->newTicketCameraShots = [];
$this->newTicketAttachments = [];
$this->newTicketTitolo = null;
$this->newTicketDescrizione = null;
$this->newTicketLuogo = null;
$this->newTicketPriorita = 'Media';
$this->newTicketCategoriaId = null;
$this->newTicketTipoIntervento = 'altro';
$this->newTicketCameraShots = [];
$this->newTicketAttachments = [];
$this->newTicketAttachmentDescriptions = [];
$this->newTicketFotoNote = null;
$this->newTicketFotoNote = null;
$this->refreshData();
}
@ -419,7 +430,7 @@ public function removeNewTicketFile(int $index): void
*/
public function getSelectedUploadsProperty(): array
{
$rows = [];
$rows = [];
$files = array_merge($this->newTicketCameraShots, $this->newTicketAttachments);
foreach ($files as $index => $file) {
@ -427,8 +438,8 @@ public function getSelectedUploadsProperty(): array
continue;
}
$name = (string) (method_exists($file, 'getClientOriginalName') ? $file->getClientOriginalName() : ('file-' . $index));
$mime = (string) (method_exists($file, 'getClientMimeType') ? $file->getClientMimeType() : 'application/octet-stream');
$name = (string) (method_exists($file, 'getClientOriginalName') ? $file->getClientOriginalName() : ('file-' . $index));
$mime = (string) (method_exists($file, 'getClientMimeType') ? $file->getClientMimeType() : 'application/octet-stream');
$isImage = str_starts_with($mime, 'image/');
$previewUrl = null;
@ -510,7 +521,7 @@ private function salvaAllegatiTicket(Ticket $ticket, int $userId): int
private function syncAttachmentDescriptionSlots(): void
{
$files = array_merge($this->newTicketCameraShots, $this->newTicketAttachments);
$next = [];
$next = [];
foreach ($files as $index => $_file) {
$next[$index] = (string) ($this->newTicketAttachmentDescriptions[$index] ?? '');

View File

@ -40,6 +40,7 @@
<th class="border px-2 py-2 text-left">ID</th>
<th class="border px-2 py-2 text-left">Titolo</th>
<th class="border px-2 py-2 text-left">Categoria</th>
<th class="border px-2 py-2 text-left">Assegnato a</th>
<th class="border px-2 py-2 text-left">Priorita</th>
<th class="border px-2 py-2 text-left">Stato</th>
<th class="border px-2 py-2 text-left">Apertura</th>
@ -50,8 +51,18 @@
@forelse($tickets as $ticket)
<tr id="ticket-{{ (int) $ticket->id }}" class="hover:bg-slate-50">
<td class="border px-2 py-2">#{{ (int) $ticket->id }}</td>
<td class="border px-2 py-2">{{ $ticket->titolo }}</td>
<td class="border px-2 py-2">
<div class="flex items-center gap-1">
<span>{{ $ticket->titolo }}</span>
@if(((int) ($ticket->attachments_count ?? 0)) > 0)
<span title="Presenza allegati" class="inline-flex items-center rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold text-emerald-800">
ATT {{ (int) $ticket->attachments_count }}
</span>
@endif
</div>
</td>
<td class="border px-2 py-2">{{ optional($ticket->categoriaTicket)->nome ?: '-' }}</td>
<td class="border px-2 py-2">{{ optional($ticket->assegnatoAUser)->name ?: '-' }}</td>
<td class="border px-2 py-2">{{ $ticket->priorita }}</td>
<td class="border px-2 py-2">{{ $ticket->stato }}</td>
<td class="border px-2 py-2">{{ optional($ticket->data_apertura)->format('d/m/Y H:i') }}</td>
@ -80,7 +91,7 @@
</tr>
@empty
<tr>
<td colspan="7" class="border px-2 py-4 text-center text-gray-500">Nessun ticket per il filtro selezionato.</td>
<td colspan="8" class="border px-2 py-4 text-center text-gray-500">Nessun ticket per il filtro selezionato.</td>
</tr>
@endforelse
</tbody>

View File

@ -140,10 +140,11 @@
<label class="inline-flex cursor-pointer items-center rounded-md bg-indigo-600 px-3 py-2 text-xs font-medium text-white hover:bg-indigo-500">
Aggiungi allegati
<input type="file" wire:model="newTicketAttachments" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt" class="hidden" />
<input type="file" wire:model="newTicketAttachments" multiple accept=".pdf,.doc,.docx,.xls,.xlsx,.txt,image/*" class="hidden" />
</label>
</div>
<div class="mt-1 text-xs text-gray-500">Dopo ogni scatto puoi aggiungere subito la descrizione. Se servono altre foto usa di nuovo "Scatta foto".</div>
<div class="mt-1 text-xs text-gray-500">Puoi caricare fino a 10 file totali (foto + documenti). Ogni file ha descrizione separata.</div>
<div class="mt-1 text-xs text-gray-500">iPhone: su "Aggiungi allegati" scegli "Sfoglia" per aprire l'app File (iCloud Drive/On My iPhone).</div>
</label>
@if(count($this->selectedUploads) > 0)