netgescon-day0/resources/views/filament/pages/fornitore/ticket-intervento-scheda.blade.php

648 lines
50 KiB
PHP

<x-filament-panels::page>
@php
$ticket = $this->intervento->ticket;
@endphp
<div class="space-y-4"
x-data="{
localFotoPreviews: [],
localDocumentoPreviews: [],
geoBusy: false,
revoke(items) { (items || []).forEach(item => { if (item && item.url) { URL.revokeObjectURL(item.url); } }); },
mapFiles(fileList) {
return Array.from(fileList || []).map(file => {
const mime = file.type || '';
const isImage = mime.startsWith('image/');
const isPdf = mime === 'application/pdf' || (file.name || '').toLowerCase().endsWith('.pdf');
return {
name: file.name,
size: file.size || 0,
isImage,
isPdf,
url: (isImage || isPdf) ? URL.createObjectURL(file) : null,
};
});
},
setPreviews(event, type) { const key = type === 'foto' ? 'localFotoPreviews' : 'localDocumentoPreviews'; this.revoke(this[key]); this[key] = this.mapFiles(event.target.files); },
clearPreviews(type = null) { if (type === null || type === 'foto') { this.revoke(this.localFotoPreviews); this.localFotoPreviews = []; } if (type === null || type === 'documenti') { this.revoke(this.localDocumentoPreviews); this.localDocumentoPreviews = []; } },
formatSize(bytes) { if (!bytes) return '0 KB'; const units = ['B', 'KB', 'MB', 'GB']; let value = bytes; let unitIndex = 0; while (value >= 1024 && unitIndex < units.length - 1) { value /= 1024; unitIndex++; } return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`; },
captureGeo(action) {
return new Promise((resolve) => {
if (!navigator.geolocation) {
resolve({ source: 'browser', error: `geolocation_not_supported_${action}` });
return;
}
navigator.geolocation.getCurrentPosition(
(position) => resolve({
source: 'browser',
lat: position.coords?.latitude ?? null,
lng: position.coords?.longitude ?? null,
accuracy: position.coords?.accuracy ?? null,
action,
}),
(error) => resolve({ source: 'browser', error: error?.message || `geolocation_failed_${action}`, action }),
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 60000 },
);
});
},
async runGeoAction(action) {
this.geoBusy = true;
const payload = await this.captureGeo(action);
if (action === 'start') {
await $wire.startInterventoWithGeo(payload);
} else if (action === 'stop') {
await $wire.stopInterventoWithGeo(payload);
} else if (action === 'checkpoint') {
await $wire.addPresenceCheckpoint(payload);
}
this.geoBusy = false;
}
}"
x-on:fornitore-local-previews-clear.window="clearPreviews()">
<div class="rounded-xl border bg-white p-4">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<div class="text-lg font-semibold">Intervento ticket #{{ $this->intervento->ticket_id }}</div>
<div class="mt-1 text-sm text-gray-600">{{ $ticket->titolo ?? '-' }} · {{ $ticket->stabile->denominazione ?? '-' }}</div>
<div class="mt-2 text-xs text-gray-500">Fornitore: {{ $this->fornitore->ragione_sociale ?: trim(($this->fornitore->nome ?? '') . ' ' . ($this->fornitore->cognome ?? '')) }}</div>
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ $this->getTicketsUrl() }}" class="inline-flex items-center rounded-md bg-gray-800 px-3 py-1.5 text-xs font-medium text-white hover:bg-gray-700">Torna ai ticket</a>
<a href="{{ \App\Filament\Pages\Fornitore\LavorazioniOperative::getUrl(['fornitore' => (int) $this->fornitore->id], panel: 'admin-filament') }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Lavorazioni</a>
<a href="{{ $this->getCollaboratoriUrl() }}" class="inline-flex items-center rounded-md bg-primary-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-primary-500">Collaboratori</a>
<a href="{{ $this->getRubricaUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Rubrica clienti</a>
<a href="{{ \App\Filament\Pages\Fornitore\ImpostazioniArchivio::getUrl(['fornitore' => (int) $this->fornitore->id], panel: 'admin-filament') }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Impostazioni</a>
<a href="{{ $this->getProdottiUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Prodotti</a>
</div>
</div>
</div>
<div class="grid gap-4 xl:grid-cols-3">
<div class="space-y-4 xl:col-span-2">
<div class="rounded-xl border bg-white p-4">
<div class="text-sm font-semibold">Scheda segnalazione</div>
<div class="mt-3 grid gap-3 md:grid-cols-2">
<div>
<div class="text-xs uppercase tracking-wide text-gray-500">Richiedente</div>
<div class="mt-1 text-sm">
@if(!empty($caller['rubrica_url']))
<a href="{{ $caller['rubrica_url'] }}" class="text-primary-600 hover:underline">{{ $caller['contatto'] ?: '-' }}</a>
@else
{{ $caller['contatto'] ?: '-' }}
@endif
</div>
</div>
<div>
<div class="text-xs uppercase tracking-wide text-gray-500">Telefono</div>
<div class="mt-1 text-sm">
@if($caller['telefono'] !== '')
<a href="tel:{{ preg_replace('/\s+/', '', (string) $caller['telefono']) }}" class="text-primary-600 hover:underline">{{ $caller['telefono'] }}</a>
@else
-
@endif
</div>
</div>
<div>
<div class="text-xs uppercase tracking-wide text-gray-500">Email</div>
<div class="mt-1 text-sm">
@if($caller['email'] !== '')
<a href="mailto:{{ $caller['email'] }}" class="text-primary-600 hover:underline">{{ $caller['email'] }}</a>
@else
-
@endif
</div>
</div>
<div>
<div class="text-xs uppercase tracking-wide text-gray-500">Origine dato</div>
<div class="mt-1 text-sm">{{ $caller['sorgente'] !== '' ? $caller['sorgente'] : '-' }}</div>
</div>
<div>
<div class="text-xs uppercase tracking-wide text-gray-500">Stato intervento</div>
<div class="mt-1 text-sm">{{ $this->intervento->stato }}</div>
</div>
<div>
<div class="text-xs uppercase tracking-wide text-gray-500">Operatore</div>
<div class="mt-1 text-sm">{{ $this->intervento->operatore_assegnato_label }}</div>
</div>
<div class="md:col-span-2">
<div class="text-xs uppercase tracking-wide text-gray-500">Riferimento stabile</div>
<div class="mt-1 text-sm">
@if(!empty($caller['stabile_url']) && $caller['riferimento'] !== '')
<a href="{{ $caller['stabile_url'] }}" class="text-primary-600 hover:underline">{{ $caller['riferimento'] }}</a>
@else
{{ $caller['riferimento'] !== '' ? $caller['riferimento'] : '-' }}
@endif
</div>
@if($caller['unita_label'] !== '')
<div class="mt-1 text-xs text-gray-500">Unità collegata: {{ $caller['unita_label'] }}</div>
@endif
</div>
@if(!empty($caller['rubrica_url']) || !empty($caller['estratto_url']) || !empty($caller['stabile_url']))
<div class="md:col-span-2">
<div class="mt-2 flex flex-wrap gap-2">
@if(!empty($caller['rubrica_url']))
<a href="{{ $caller['rubrica_url'] }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Apri scheda contatto</a>
@endif
@if(!empty($caller['estratto_url']))
<a href="{{ $caller['estratto_url'] }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Apri estratto conto</a>
@endif
@if(!empty($caller['stabile_url']))
<a href="{{ $caller['stabile_url'] }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Apri stabile</a>
@endif
</div>
</div>
@endif
</div>
<div class="mt-4 whitespace-pre-wrap rounded-lg bg-gray-50 p-3 text-sm text-gray-700">{{ $ticket->descrizione }}</div>
</div>
<div class="rounded-xl border bg-white p-4">
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm font-semibold">Operativo e rapportino</div>
<div class="flex flex-wrap gap-2">
@if($this->intervento->stato === 'assegnato' || ! $this->intervento->iniziato_at || $this->intervento->terminato_at)
<x-filament::button size="sm" x-on:click.prevent="runGeoAction('start')" x-bind:disabled="geoBusy">Start intervento</x-filament::button>
@endif
@if($this->intervento->iniziato_at && ! $this->intervento->terminato_at)
<x-filament::button size="sm" color="gray" x-on:click.prevent="runGeoAction('stop')" x-bind:disabled="geoBusy">Stop intervento</x-filament::button>
@endif
<x-filament::button size="sm" color="info" x-on:click.prevent="runGeoAction('checkpoint')" x-bind:disabled="geoBusy">Checkpoint posizione</x-filament::button>
</div>
</div>
<div class="mt-2 text-[11px] text-slate-500">Start, stop e checkpoint provano a salvare la posizione del browser. Se il GPS non e disponibile o negato, l'evento viene comunque registrato senza coordinate.</div>
<div class="mt-3 grid gap-3 rounded-xl border border-slate-200 bg-slate-50 p-3 text-xs text-slate-700 md:grid-cols-3">
<div>
<div class="font-semibold uppercase tracking-wide text-slate-500">Timer avvio</div>
<div class="mt-1">{{ optional($this->intervento->iniziato_at)->format('d/m/Y H:i') ?: 'Non avviato' }}</div>
</div>
<div>
<div class="font-semibold uppercase tracking-wide text-slate-500">Timer stop</div>
<div class="mt-1">{{ optional($this->intervento->terminato_at)->format('d/m/Y H:i') ?: 'In corso' }}</div>
</div>
<div>
<div class="font-semibold uppercase tracking-wide text-slate-500">Durata rilevata</div>
<div class="mt-1">{{ $tempoMinuti ? ($tempoMinuti . ' minuti') : 'Da calcolare' }}</div>
</div>
</div>
@if($this->canAssignDipendente())
<div class="mt-4 rounded-lg border border-dashed p-3">
<div class="text-xs uppercase tracking-wide text-gray-500">Assegnazione operatore</div>
<div class="mt-2 flex flex-wrap items-end gap-3">
<label class="block min-w-72 flex-1 text-sm">
<span class="mb-1 block font-medium">Collaboratore incaricato</span>
<select wire:model="dipendenteId" class="w-full rounded-lg border-gray-300">
<option value="">Responsabile fornitore / non assegnato</option>
@foreach($dipendentiOptions as $option)
<option value="{{ $option['id'] }}">{{ $option['label'] }}</option>
@endforeach
</select>
</label>
<x-filament::button color="gray" wire:click="assignDipendente">Salva assegnazione</x-filament::button>
</div>
</div>
@endif
<div class="mt-4 grid gap-3 md:grid-cols-3">
<label class="block text-sm">
<span class="mb-1 block font-medium">Marca apparato</span>
<input type="text" wire:model.defer="apparatoMarca" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block text-sm">
<span class="mb-1 block font-medium">Modello</span>
<input type="text" wire:model.defer="apparatoModello" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block text-sm">
<span class="mb-1 block font-medium">Seriale</span>
<input type="text" wire:model.defer="apparatoSeriale" class="w-full rounded-lg border-gray-300" />
</label>
</div>
<label class="mt-4 block text-sm">
<span class="mb-1 block font-medium">Rapporto intervento</span>
<textarea wire:model.defer="rapportoFornitore" rows="6" class="w-full rounded-lg border-gray-300"></textarea>
</label>
<div class="mt-4 grid gap-3 md:grid-cols-2">
<label class="block text-sm">
<span class="mb-1 block font-medium">Tempo impiegato (minuti)</span>
<input type="number" min="1" max="1440" wire:model.defer="tempoMinuti" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block text-sm">
<span class="mb-1 block font-medium">QR token</span>
<input type="text" wire:model.defer="qrToken" class="w-full rounded-lg border-gray-300" placeholder="Inserisci il codice se scansionato" />
</label>
</div>
<div class="mt-4 rounded-xl border border-emerald-200 bg-emerald-50 p-4">
<div class="flex flex-wrap items-center justify-between gap-2">
<div>
<div class="text-sm font-semibold text-emerald-900">Materiali e prodotti</div>
<div class="mt-1 text-xs text-emerald-800">Puoi cercare per nome, codice interno o barcode. I lettori Bluetooth che si comportano come tastiera funzionano gia su questo campo.</div>
</div>
<a href="{{ $this->getProdottiUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-emerald-800 ring-1 ring-inset ring-emerald-300 hover:bg-emerald-100">Apri catalogo prodotti</a>
</div>
<div class="mt-3 flex flex-col gap-3 md:flex-row">
<label class="block flex-1 text-sm">
<span class="mb-1 block font-medium">Cerca prodotto o scansiona barcode</span>
<input type="text" wire:model.live.debounce.300ms="materialSearch" inputmode="search" class="w-full rounded-lg border-emerald-300" placeholder="Es. lampadina E27 10W oppure barcode" />
</label>
<div class="flex items-end gap-2">
<x-filament::button color="success" wire:click="addManualMaterial">Aggiungi manuale</x-filament::button>
</div>
</div>
@if(count($materialSuggestions) > 0)
<div class="mt-3 grid gap-3 lg:grid-cols-2">
@foreach($materialSuggestions as $suggestion)
<div class="rounded-xl border bg-white p-3 text-sm shadow-sm">
<div class="font-medium">{{ $suggestion['label'] }}</div>
<div class="mt-1 text-xs text-gray-500">Barcode: {{ $suggestion['barcode'] !== '' ? $suggestion['barcode'] : 'non presente' }}</div>
<div class="mt-1 text-xs text-gray-500">Prezzo: {{ $suggestion['price'] ?: 'non disponibile' }} · Fonte: {{ $suggestion['source'] }}</div>
<div class="mt-3 flex flex-wrap gap-2">
<button type="button" wire:click="addSuggestedMaterial({{ (int) $suggestion['id'] }})" class="inline-flex items-center rounded-md bg-emerald-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-emerald-500">Aggiungi al rapportino</button>
@if(!empty($suggestion['external_url']))
<a href="{{ $suggestion['external_url'] }}" target="_blank" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Apri fonte</a>
@endif
</div>
</div>
@endforeach
</div>
@elseif($materialSearch !== '')
<div class="mt-3 rounded-lg border border-dashed border-amber-300 bg-amber-50 p-3 text-xs text-amber-800">
Nessun prodotto trovato nel catalogo corrente. Puoi inserirlo manualmente adesso e usare il link Amazon di ricerca nel rapportino, poi completare l'anagrafica prodotto dal catalogo.
</div>
@endif
@if(count($materialiUtilizzati) > 0)
<div class="mt-4 space-y-3">
@foreach($materialiUtilizzati as $index => $materiale)
<div class="rounded-xl border bg-white p-3 text-sm shadow-sm">
<div class="grid gap-3 md:grid-cols-4">
<label class="block md:col-span-2">
<span class="mb-1 block text-xs font-medium uppercase tracking-wide text-gray-500">Prodotto/materiale</span>
<input type="text" wire:model.defer="materialiUtilizzati.{{ $index }}.descrizione" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block">
<span class="mb-1 block text-xs font-medium uppercase tracking-wide text-gray-500">Barcode</span>
<input type="text" wire:model.defer="materialiUtilizzati.{{ $index }}.barcode" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block">
<span class="mb-1 block text-xs font-medium uppercase tracking-wide text-gray-500">Quantita</span>
<input type="number" step="0.01" min="0.01" wire:model.defer="materialiUtilizzati.{{ $index }}.qty" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block">
<span class="mb-1 block text-xs font-medium uppercase tracking-wide text-gray-500">Prezzo nostro</span>
<input type="number" step="0.01" min="0" wire:model.defer="materialiUtilizzati.{{ $index }}.unit_price" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block">
<span class="mb-1 block text-xs font-medium uppercase tracking-wide text-gray-500">Valuta</span>
<input type="text" wire:model.defer="materialiUtilizzati.{{ $index }}.currency" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block md:col-span-2">
<span class="mb-1 block text-xs font-medium uppercase tracking-wide text-gray-500">Fonte</span>
<input type="text" wire:model.defer="materialiUtilizzati.{{ $index }}.source_label" class="w-full rounded-lg border-gray-300" />
</label>
</div>
<div class="mt-3 flex flex-wrap items-center justify-between gap-2">
@if(!empty($materiale['external_url']))
<a href="{{ $materiale['external_url'] }}" target="_blank" class="text-xs font-medium text-primary-600 hover:underline">Apri riferimento esterno</a>
@else
<span class="text-xs text-gray-400">Nessun riferimento esterno</span>
@endif
<button type="button" wire:click="removeMateriale({{ (int) $index }})" class="inline-flex items-center rounded-md bg-rose-600 px-2 py-1 text-[11px] font-medium text-white hover:bg-rose-500">Rimuovi</button>
</div>
</div>
@endforeach
</div>
@endif
</div>
<div class="mt-4 grid gap-2 md:grid-cols-3">
@foreach($articoliUtilizzati as $index => $item)
<label class="block text-sm">
<span class="mb-1 block font-medium">Ricambio/attivita {{ $index + 1 }}</span>
<input type="text" wire:model.defer="articoliUtilizzati.{{ $index }}" class="w-full rounded-lg border-gray-300" />
</label>
@endforeach
</div>
<div class="mt-4 grid gap-3 md:grid-cols-2">
<label class="block text-sm">
<span class="mb-1 block font-medium">Foto lavoro</span>
<input type="file" wire:model="fotoLavoro" multiple accept="image/*" x-on:change="setPreviews($event, 'foto')" class="w-full rounded-lg border-gray-300 text-sm" />
</label>
<label class="block text-sm">
<span class="mb-1 block font-medium">Documenti lavoro</span>
<input type="file" wire:model="documentiLavoro" multiple accept=".pdf,.doc,.docx,.xls,.xlsx,.txt,image/*" x-on:change="setPreviews($event, 'documenti')" class="w-full rounded-lg border-gray-300 text-sm" />
</label>
</div>
<div class="mt-4 rounded-xl border border-dashed border-sky-200 bg-sky-50/60 p-4" x-show="localFotoPreviews.length || localDocumentoPreviews.length" x-cloak>
<div class="text-sm font-semibold text-sky-900">Anteprima immediata allegati fornitore</div>
<div class="mt-1 text-xs text-sky-700">Le immagini e i documenti selezionati sono visibili subito prima del salvataggio del rapportino.</div>
<div class="mt-4" x-show="localFotoPreviews.length" x-cloak>
<div class="text-xs font-semibold uppercase tracking-wide text-sky-800">Foto lavoro</div>
<div class="mt-2 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
<template x-for="item in localFotoPreviews" :key="item.name + item.size">
<div class="rounded-xl border bg-white p-3 text-xs shadow-sm">
<div class="aspect-[4/3] overflow-hidden rounded-lg border bg-slate-50">
<template x-if="item.url">
<img :src="item.url" :alt="item.name" class="h-full w-full object-cover" />
</template>
</div>
<div class="mt-2 truncate font-medium" x-text="item.name"></div>
<div class="text-gray-500" x-text="formatSize(item.size)"></div>
</div>
</template>
</div>
</div>
<div class="mt-4" x-show="localDocumentoPreviews.length" x-cloak>
<div class="text-xs font-semibold uppercase tracking-wide text-sky-800">Documenti lavoro</div>
<div class="mt-2 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
<template x-for="item in localDocumentoPreviews" :key="item.name + item.size">
<div class="rounded-xl border bg-white p-3 text-xs shadow-sm">
<div class="flex aspect-[4/3] items-center justify-center rounded-lg border bg-slate-50">
<template x-if="item.isImage && item.url">
<img :src="item.url" :alt="item.name" class="h-full w-full rounded object-cover" />
</template>
<template x-if="item.isPdf && item.url">
<iframe :src="item.url + '#toolbar=0&navpanes=0&scrollbar=0&page=1&view=FitH'" class="h-full w-full rounded border-0 bg-white"></iframe>
</template>
<template x-if="!item.isImage && !item.isPdf">
<div class="text-center text-gray-500">
<div class="text-sm font-semibold">FILE</div>
<div class="mt-1 text-[11px]">Anteprima pronta</div>
</div>
</template>
</div>
<div class="mt-2 truncate font-medium" x-text="item.name"></div>
<div class="text-gray-500" x-text="formatSize(item.size)"></div>
</div>
</template>
</div>
</div>
</div>
@if(count($this->rapportinoUploads) > 0)
<div class="mt-4 rounded-xl border bg-white p-4">
<div class="text-sm font-semibold">File selezionati per il rapportino</div>
<div class="mt-1 text-xs text-gray-500">Ogni foto o documento puo avere la sua descrizione, come nella gestione ticket.</div>
<div class="mt-3 grid gap-3 sm:grid-cols-2">
@foreach($this->rapportinoUploads as $upload)
<div class="rounded-xl border bg-slate-50 p-3 text-xs shadow-sm">
<div class="aspect-[4/3] rounded-lg border bg-white p-2">
@if($upload['is_image'] && $upload['preview_url'])
<img src="{{ $upload['preview_url'] }}" alt="{{ $upload['name'] }}" class="h-full w-full rounded object-cover" />
@elseif($upload['is_pdf'])
<div class="flex h-full items-center justify-center text-center text-gray-500">
<div>
<div class="text-sm font-semibold">PDF</div>
<div class="mt-1 text-[11px]">Anteprima immediata disponibile sopra durante la selezione</div>
</div>
</div>
@else
<div class="flex h-full items-center justify-center text-center text-gray-500">
<div>
<div class="text-sm font-semibold">FILE</div>
<div class="mt-1 text-[11px]">{{ strtoupper(pathinfo((string) $upload['name'], PATHINFO_EXTENSION) ?: 'DOC') }}</div>
</div>
</div>
@endif
</div>
<div class="mt-2 font-medium">{{ $upload['name'] }}</div>
<div class="mt-1 text-gray-500">{{ number_format(((int) $upload['size']) / 1024, 1, ',', '.') }} KB</div>
<label class="mt-3 block text-sm">
<span class="mb-1 block font-medium">Descrizione allegato</span>
<input type="text" wire:model.defer="descrizioneFile.{{ $upload['index'] }}" class="w-full rounded-lg border-gray-300" placeholder="Es. foto finale, documento tecnico, rapporto PDF" />
</label>
</div>
@endforeach
</div>
</div>
@endif
<div class="mt-4 grid gap-3 md:grid-cols-2">
<label class="block text-sm">
<span class="mb-1 block font-medium">Messaggio veloce allo studio</span>
<textarea wire:model.defer="messaggioVeloce" rows="3" class="w-full rounded-lg border-gray-300"></textarea>
</label>
<div class="grid gap-3">
<label class="block text-sm">
<span class="mb-1 block font-medium">Codice proforma</span>
<input type="text" wire:model.defer="proformaCodice" class="w-full rounded-lg border-gray-300" />
</label>
<label class="block text-sm">
<span class="mb-1 block font-medium">Note proforma</span>
<textarea wire:model.defer="proformaNote" rows="3" class="w-full rounded-lg border-gray-300"></textarea>
</label>
</div>
</div>
<div class="mt-4 flex flex-wrap gap-4 text-sm">
<label class="inline-flex items-center gap-2">
<input type="checkbox" wire:model="richiestaProforma" class="rounded border-gray-300" />
<span>Richiesta proforma</span>
</label>
<label class="inline-flex items-center gap-2">
<input type="checkbox" wire:model="lavoroStandard" class="rounded border-gray-300" />
<span>Lavoro standard gia autorizzato</span>
</label>
</div>
<div class="mt-4 flex flex-wrap gap-2">
<x-filament::button wire:click="saveRapporto">Salva avanzamento</x-filament::button>
<x-filament::button color="gray" wire:click="inviaSollecito">Invia sollecito</x-filament::button>
</div>
</div>
<div class="rounded-xl border bg-white p-4">
<div class="text-sm font-semibold">Comunicazioni ticket</div>
<div class="mt-3 space-y-2">
@forelse($ticket->messages as $msg)
<div class="rounded-lg border p-3 text-sm">
<div class="font-medium">{{ $msg->user->name ?? 'Sistema' }}</div>
<div class="mt-1 whitespace-pre-wrap text-gray-700">{{ $msg->messaggio }}</div>
<div class="mt-1 text-xs text-gray-500">{{ optional($msg->created_at)->format('d/m/Y H:i') }}</div>
</div>
@empty
<div class="text-sm text-gray-500">Nessuna comunicazione disponibile.</div>
@endforelse
</div>
</div>
</div>
<div class="space-y-4">
<div class="rounded-xl border bg-white p-4">
<div class="text-sm font-semibold">Stato operativo</div>
<div class="mt-3 space-y-2 text-sm">
<div><span class="font-medium">Stato:</span> {{ $this->intervento->stato }}</div>
<div><span class="font-medium">Iniziato:</span> {{ optional($this->intervento->iniziato_at)->format('d/m/Y H:i') ?: '-' }}</div>
<div><span class="font-medium">Terminato:</span> {{ optional($this->intervento->terminato_at)->format('d/m/Y H:i') ?: '-' }}</div>
<div><span class="font-medium">Rif. chiusura:</span> {{ $this->intervento->riferimento_chiusura ?: '-' }}</div>
<div><span class="font-medium">QR registrato:</span> {{ $this->intervento->qr_token ?: '-' }}</div>
</div>
</div>
<div class="rounded-xl border bg-white p-4">
<div class="flex items-center justify-between gap-2">
<div class="text-sm font-semibold">Sessioni e presenza</div>
<div class="text-xs text-gray-500">{{ count($sessionRows) }} eventi</div>
</div>
@if($lastGeoSnapshot)
<div class="mt-3 rounded-lg border border-sky-200 bg-sky-50 p-3 text-xs text-sky-900">
<div class="font-semibold">{{ $lastGeoSnapshot['label'] }}</div>
<div class="mt-1">Start: {{ $lastGeoSnapshot['start_geo'] }}</div>
<div class="mt-1">Stop: {{ $lastGeoSnapshot['end_geo'] }}</div>
<div class="mt-1 text-sky-700">Aggiornato: {{ $lastGeoSnapshot['updated_at'] }}</div>
</div>
@endif
<div class="mt-3 space-y-2">
@forelse($sessionRows as $row)
<div class="rounded-lg border p-3 text-xs">
<div class="flex items-center justify-between gap-2">
<span class="inline-flex rounded-full bg-slate-100 px-2 py-1 font-semibold text-slate-700">{{ $row['type'] === 'checkpoint' ? 'Checkpoint' : 'Sessione lavoro' }}</span>
<span class="text-gray-500">{{ $row['duration_minutes'] !== null ? ($row['duration_minutes'] . ' min') : '-' }}</span>
</div>
<div class="mt-2 text-gray-700">{{ $row['note'] !== '' ? $row['note'] : 'Nessuna nota' }}</div>
<div class="mt-2 text-gray-500">Start: {{ $row['started_at'] }} · {{ $row['start_geo'] }}</div>
<div class="mt-1 text-gray-500">Stop: {{ $row['ended_at'] }} · {{ $row['end_geo'] }}</div>
</div>
@empty
<div class="text-sm text-gray-500">Nessuna sessione registrata.</div>
@endforelse
</div>
</div>
<div class="rounded-xl border bg-white p-4">
<div class="text-sm font-semibold">Google workspace</div>
@if($googleWorkspaceStatus)
<div class="mt-3 space-y-2 text-sm">
<div><span class="font-medium">Stato:</span> {{ $googleWorkspaceStatus['connected'] ? 'Collegato' : 'Non collegato' }}</div>
<div><span class="font-medium">Account:</span> {{ $googleWorkspaceStatus['email'] !== '' ? $googleWorkspaceStatus['email'] : '-' }}</div>
<div><span class="font-medium">Nome:</span> {{ $googleWorkspaceStatus['name'] !== '' ? $googleWorkspaceStatus['name'] : '-' }}</div>
<div><span class="font-medium">Ultimo collegamento:</span> {{ $googleWorkspaceStatus['connected_at'] !== '' ? $googleWorkspaceStatus['connected_at'] : '-' }}</div>
</div>
<div class="mt-3 flex flex-wrap gap-2">
<a href="{{ $this->getGoogleConnectUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Collega / aggiorna Google</a>
@if($googleWorkspaceStatus['connected'])
<form method="POST" action="{{ $this->getGoogleDisconnectUrl() }}">
@csrf
<button type="submit" class="inline-flex items-center rounded-md bg-rose-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-rose-500">Scollega</button>
</form>
@endif
</div>
<div class="mt-2 text-xs text-gray-500">Questo e il primo aggancio operativo per riusare contatti, calendario e Drive nello spazio fornitore con lo stesso account Google gia gestito dal backend.</div>
@else
<div class="mt-3 text-sm text-gray-500">Nessun contesto amministratore collegato al fornitore: collegamento Google non disponibile.</div>
@endif
</div>
<div class="rounded-xl border bg-white p-4">
<div class="text-sm font-semibold">Allegati ticket</div>
<div class="mt-3 grid gap-3 sm:grid-cols-2">
@forelse($ticket->attachments as $attachment)
<div class="rounded-xl border bg-white p-3 text-xs shadow-sm">
<div class="aspect-[4/3] rounded-lg border bg-gray-50 p-2">
@if($attachment->isImage())
<img src="{{ $this->getAttachmentPublicUrl($attachment) }}" alt="{{ $attachment->original_file_name }}" class="h-full w-full rounded object-cover" />
@elseif($attachment->isPdf())
<iframe src="{{ $this->getAttachmentPublicUrl($attachment) }}#toolbar=0&navpanes=0&scrollbar=0&page=1&view=FitH" class="h-full w-full rounded border-0 bg-white"></iframe>
@else
<div class="flex h-full items-center justify-center text-center text-gray-500">
<div>
<div class="text-sm font-semibold">{{ strtoupper(pathinfo((string) $attachment->original_file_name, PATHINFO_EXTENSION) ?: ($attachment->isPdf() ? 'PDF' : 'FILE')) }}</div>
<div class="mt-1 text-[11px]">Anteprima disponibile in modal</div>
</div>
</div>
@endif
</div>
<div class="mt-2 font-medium">{{ $attachment->original_file_name }}</div>
<div class="mt-1 text-gray-600">{{ $attachment->description ?: 'Nessuna descrizione' }}</div>
<div class="mt-2 text-[11px] text-gray-500">Caricato da {{ $attachment->user->name ?? 'Sistema' }} il {{ optional($attachment->created_at)->format('d/m/Y H:i') ?: '-' }}</div>
<button type="button" wire:click="openAttachmentPreview({{ (int) $attachment->id }})" class="mt-2 inline-flex items-center rounded-md bg-indigo-600 px-2 py-1 text-[11px] font-medium text-white hover:bg-indigo-500">Visualizza in modal</button>
</div>
@empty
<div class="text-sm text-gray-500">Nessun allegato disponibile. Se il ticket e stato creato senza file persistiti, qui non verra mostrato nulla finche non vengono caricati allegati reali.</div>
@endforelse
</div>
</div>
<div class="rounded-xl border bg-white p-4">
<div class="text-sm font-semibold">Storico correlato</div>
<div class="mt-3 space-y-2">
@forelse($storicoRows as $row)
<div class="rounded-lg border p-3 text-sm">
<div class="font-medium">#{{ $row['ticket_id'] }} - {{ $row['titolo'] }}</div>
<div class="text-xs text-gray-500">{{ $row['stato'] }} · {{ $row['updated_at'] }}</div>
@if($row['note'] !== '')
<div class="mt-1 text-xs text-gray-600">{{ \Illuminate\Support\Str::limit($row['note'], 90) }}</div>
@endif
</div>
@empty
<div class="text-sm text-gray-500">Nessuno storico correlato.</div>
@endforelse
</div>
</div>
</div>
</div>
@if($attachmentPreview)
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" x-data="{ zoom: 1, pdfPage: 1, iframeKey: 0, refreshPdf() { this.iframeKey++; }, zoomIn() { this.zoom = Math.min(this.zoom + 0.2, 4); }, zoomOut() { this.zoom = Math.max(this.zoom - 0.2, 0.5); }, resetZoom() { this.zoom = 1; }, nextPage() { this.pdfPage = this.pdfPage + 1; this.refreshPdf(); }, prevPage() { this.pdfPage = Math.max(this.pdfPage - 1, 1); this.refreshPdf(); }, printPdf() { const frame = this.$refs.pdfFrame; if (frame?.contentWindow) { frame.contentWindow.focus(); frame.contentWindow.print(); } else { window.open(@js($attachmentPreview['url']), '_blank'); } } }">
<div class="h-[92vh] w-full max-w-[96vw] rounded-xl bg-white shadow-2xl">
<div class="flex items-center justify-between border-b px-4 py-3">
<div>
<div class="text-sm font-semibold">{{ $attachmentPreview['name'] ?? 'Allegato' }}</div>
@if(!empty($attachmentPreview['description']))
<div class="text-xs text-gray-500">{{ $attachmentPreview['description'] }}</div>
@endif
</div>
<div class="flex items-center gap-2">
@if($attachmentPreview['is_image'] ?? false)
<button type="button" x-on:click="zoomOut()" class="rounded-md bg-slate-100 px-2 py-1 text-xs hover:bg-slate-200">-</button>
<button type="button" x-on:click="resetZoom()" class="rounded-md bg-slate-100 px-2 py-1 text-xs hover:bg-slate-200">100%</button>
<button type="button" x-on:click="zoomIn()" class="rounded-md bg-slate-100 px-2 py-1 text-xs hover:bg-slate-200">+</button>
@elseif($attachmentPreview['is_pdf'] ?? false)
<button type="button" x-on:click="prevPage()" class="rounded-md bg-slate-100 px-2 py-1 text-xs hover:bg-slate-200">Pagina precedente</button>
<span class="text-xs text-slate-500">Pagina <span x-text="pdfPage"></span></span>
<button type="button" x-on:click="nextPage()" class="rounded-md bg-slate-100 px-2 py-1 text-xs hover:bg-slate-200">Pagina successiva</button>
<button type="button" x-on:click="printPdf()" class="rounded-md bg-slate-100 px-2 py-1 text-xs hover:bg-slate-200">Stampa PDF</button>
@endif
<a href="{{ $attachmentPreview['url'] }}" target="_blank" class="rounded-md bg-slate-100 px-2 py-1 text-xs hover:bg-slate-200">Apri in nuova scheda</a>
<button type="button" wire:click="closeAttachmentPreview" class="rounded-md bg-gray-100 px-2 py-1 text-xs hover:bg-gray-200">Chiudi</button>
</div>
</div>
<div class="h-[calc(92vh-64px)] overflow-auto p-4">
@if($attachmentPreview['is_image'] ?? false)
<div class="h-full overflow-auto rounded-lg bg-slate-50 p-4">
<div class="flex min-h-full min-w-full items-start justify-center">
<img src="{{ $attachmentPreview['url'] }}" alt="Anteprima allegato" class="max-w-none rounded border bg-white shadow" x-bind:style="`width: ${zoom * 100}%; height: auto;`" />
</div>
</div>
@elseif($attachmentPreview['is_pdf'] ?? false)
<iframe x-ref="pdfFrame" x-bind:key="iframeKey" x-bind:src="@js($attachmentPreview['url']) + '#toolbar=1&navpanes=0&scrollbar=1&view=FitH&zoom=page-width&page=' + pdfPage" class="h-[84vh] w-full rounded border"></iframe>
@else
<div class="rounded-md border bg-gray-50 p-4 text-sm text-gray-700">
Anteprima non disponibile per questo formato. Scarica o apri il file:
<a href="{{ $attachmentPreview['url'] }}" class="ml-1 text-primary-600 hover:underline">Apri file</a>
</div>
@endif
</div>
</div>
</div>
@endif
</div>
</x-filament-panels::page>