++ fix prioritario display and add link sent toggle

This commit is contained in:
2026-04-13 15:40:35 +00:00
parent 0553d4ef74
commit 6a65087449
10 changed files with 459 additions and 108 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Models\Assegnazione; use App\Models\Assegnazione;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -14,6 +15,10 @@ class AssignmentPdfController extends Controller
{ {
$this->validateAccess($assignment, $code); $this->validateAccess($assignment, $code);
if ($expired = $this->linkScaduto($assignment)) {
return $expired;
}
$pdfUrl = route('assignments.pdf.file', [ $pdfUrl = route('assignments.pdf.file', [
'assignment' => $assignment->id, 'assignment' => $assignment->id,
'code' => $code, 'code' => $code,
@@ -25,10 +30,14 @@ class AssignmentPdfController extends Controller
]); ]);
} }
public function file(Request $request, Assegnazione $assignment, string $code): StreamedResponse public function file(Request $request, Assegnazione $assignment, string $code): StreamedResponse|View
{ {
$this->validateAccess($assignment, $code); $this->validateAccess($assignment, $code);
if ($expired = $this->linkScaduto($assignment)) {
return $expired;
}
$pdfPath = $assignment->territorio?->pdf_path; $pdfPath = $assignment->territorio?->pdf_path;
abort_unless($pdfPath && Storage::disk('public')->exists($pdfPath), 404); abort_unless($pdfPath && Storage::disk('public')->exists($pdfPath), 404);
@@ -48,4 +57,22 @@ class AssignmentPdfController extends Controller
abort_unless($assignment->is_aperta, 403); abort_unless($assignment->is_aperta, 403);
abort_unless($assignment->territorio?->pdf_path, 404); abort_unless($assignment->territorio?->pdf_path, 404);
} }
protected function linkScaduto(Assegnazione $assignment): ?View
{
if (auth()->check()) {
return null;
} }
$ttlMonths = max(1, (int) \App\Models\Setting::getValue('assignment_link_ttl_hours', 1));
if ($assignment->assigned_at->copy()->addMonths($ttlMonths)->isPast()) {
return view('assignments.link-scaduto', [
'numero' => $assignment->territorio?->numero,
]);
}
return null;
}
}

View File

@@ -3,20 +3,32 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Assegnazione; use App\Models\Assegnazione;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
class ShortPdfLinkController extends Controller class ShortPdfLinkController extends Controller
{ {
public function __invoke(string $code): RedirectResponse public function __invoke(string $code): RedirectResponse|View
{ {
$assignment = Assegnazione::where('pdf_access_code', $code)->firstOrFail(); $assignment = Assegnazione::where('pdf_access_code', $code)->firstOrFail();
abort_unless($assignment->is_aperta, 403); abort_unless($assignment->is_aperta, 403);
abort_unless($assignment->territorio?->pdf_path, 404); abort_unless($assignment->territorio?->pdf_path, 404);
// Unauthenticated users (proclamatori) are subject to link TTL
if (! auth()->check()) {
$ttlMonths = max(1, (int) \App\Models\Setting::getValue('assignment_link_ttl_hours', 1));
if ($assignment->assigned_at->copy()->addMonths($ttlMonths)->isPast()) {
return view('assignments.link-scaduto', [
'numero' => $assignment->territorio?->numero,
]);
}
}
return redirect()->route('assignments.pdf.viewer', [ return redirect()->route('assignments.pdf.viewer', [
'assignment' => $assignment->id, 'assignment' => $assignment->id,
'code' => $code, 'code' => $code,
]); ]);
} }
} }

View File

@@ -83,6 +83,13 @@ class Assegna extends Component
return $this->redirect(route('territori.show', $territorio), navigate: true); return $this->redirect(route('territori.show', $territorio), navigate: true);
} }
public function toggleLinkSent(int $assegnazioneId): void
{
$this->authorize('territori.assign');
$assegnazione = Assegnazione::findOrFail($assegnazioneId);
$assegnazione->forceFill(['link_sent' => ! $assegnazione->link_sent])->saveQuietly();
}
#[Computed] #[Computed]
public function selectedThumbnailUrl(): ?string public function selectedThumbnailUrl(): ?string
{ {
@@ -122,10 +129,13 @@ class Assegna extends Component
->get() ->get()
->sortBy(fn($a) => (int) $a->territorio?->numero); ->sortBy(fn($a) => (int) $a->territorio?->numero);
$linkTtlMonths = max(1, (int) \App\Models\Setting::getValue('assignment_link_ttl_hours', 1));
return view('livewire.assegnazioni.assegna', [ return view('livewire.assegnazioni.assegna', [
'territoriDisponibili' => $territoriDisponibili, 'territoriDisponibili' => $territoriDisponibili,
'proclamatoriAttivi' => $proclamatoriAttivi, 'proclamatoriAttivi' => $proclamatoriAttivi,
'assegnazioniAperte' => $assegnazioniAperte, 'assegnazioniAperte' => $assegnazioniAperte,
'linkTtlMonths' => $linkTtlMonths,
]); ]);
} }
} }

View File

@@ -18,9 +18,12 @@ class CampagnaShow extends Component
public function render() public function render()
{ {
// All assignments with returned_at in campaign range that were counted // Assignments counted for this campaign:
$conteggiate = Assegnazione::where('campagna_id', $this->campagna->id) // - assigned on or after campaign start
// - linked to this campaign (campaign_id), regardless of returned_at (retroactive returns allowed)
$conteggiate = Assegnazione::where('campaign_id', $this->campagna->id)
->where('counted_in_campaign', true) ->where('counted_in_campaign', true)
->where('assigned_at', '>=', $this->campagna->start_date)
->with(['territorio', 'proclamatore']) ->with(['territorio', 'proclamatore'])
->orderBy('returned_at') ->orderBy('returned_at')
->get(); ->get();

View File

@@ -19,6 +19,7 @@ class Assegnazione extends Model
'counted_in_campaign', 'counted_in_campaign',
'campaign_id', 'campaign_id',
'pdf_access_code', 'pdf_access_code',
'link_sent',
'note', 'note',
'created_by', 'created_by',
'returned_by', 'returned_by',
@@ -30,6 +31,7 @@ class Assegnazione extends Model
'assigned_at' => 'date', 'assigned_at' => 'date',
'returned_at' => 'date', 'returned_at' => 'date',
'counted_in_campaign' => 'boolean', 'counted_in_campaign' => 'boolean',
'link_sent' => 'boolean',
]; ];
} }
@@ -117,6 +119,11 @@ class Assegnazione extends Model
return route('assignments.pdf.short', ['code' => $this->ensurePdfAccessCode()]); return route('assignments.pdf.short', ['code' => $this->ensurePdfAccessCode()]);
} }
public function markLinkSent(): void
{
$this->forceFill(['link_sent' => true])->saveQuietly();
}
// ─── Scopes ───────────────────────────────────────────────── // ─── Scopes ─────────────────────────────────────────────────
public function scopeAperte($query) public function scopeAperte($query)

View File

@@ -72,19 +72,19 @@ class Campagna extends Model
/** /**
* Campaign coverage percentage. * Campaign coverage percentage.
* Numerator: assignments counted for campaign * Numerator: assignments counted for campaign
* Denominator: ALL assignments with assigned_at in campaign range (returned or not) * Denominator: total active territories
*/ */
public function getPercentualePercorrenzaAttribute(): float public function getPercentualePercorrenzaAttribute(): float
{ {
$totaleNelRange = $this->assegnazioniNelRange()->count(); $totaleAttivi = Territorio::where('attivo', true)->count();
if ($totaleNelRange === 0) { if ($totaleAttivi === 0) {
return 0.0; return 0.0;
} }
$conteggiate = $this->assegnazioniConteggiate()->count(); $conteggiate = $this->assegnazioniConteggiate()->count();
return round(($conteggiate / $totaleNelRange) * 100, 1); return round(($conteggiate / $totaleAttivi) * 100, 1);
} }
public function scopeCompletate($query) public function scopeCompletate($query)

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('assegnazioni', function (Blueprint $table) {
$table->boolean('link_sent')->default(false)->after('pdf_access_code');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('assegnazioni', function (Blueprint $table) {
$table->dropColumn('link_sent');
});
}
};

View File

@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Link scaduto TerManager2</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f1f5f9;
min-height: 100dvh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
.card {
background: #fff;
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0,0,0,.08);
padding: 40px 32px;
max-width: 420px;
width: 100%;
text-align: center;
}
.icon {
width: 56px;
height: 56px;
background: #fef2f2;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
}
h1 {
font-size: 20px;
font-weight: 700;
color: #111827;
margin-bottom: 10px;
}
p {
font-size: 14px;
color: #6b7280;
line-height: 1.6;
}
.territory {
display: inline-block;
margin-top: 16px;
background: #f3f4f6;
border-radius: 8px;
padding: 8px 16px;
font-size: 13px;
color: #374151;
font-weight: 600;
}
.footer {
margin-top: 28px;
font-size: 12px;
color: #9ca3af;
}
</style>
</head>
<body>
<div class="card">
<div class="icon">
<svg width="26" height="26" fill="none" viewBox="0 0 24 24" stroke="#ef4444" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<h1>Link scaduto</h1>
<p>Il link per questo territorio non è più valido.<br>Contatta il responsabile dei territori per ricevere un nuovo link.</p>
@if($numero)
<span class="territory">Territorio {{ $numero }}</span>
@endif
<p class="footer">TerManager2</p>
</div>
</body>
</html>

View File

@@ -3,42 +3,178 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>PDF territorio {{ $assignment->territorio?->numero }}</title> <title>Territorio {{ $assignment->territorio?->numero }}</title>
<style> <style>
html, body { *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
margin: 0;
height: 100%; body {
background: #111827; font-family: system-ui, -apple-system, sans-serif;
background: #f1f5f9;
min-height: 100dvh;
display: flex;
flex-direction: column;
} }
.viewer { header {
width: 100%; background: #fff;
height: 100%; border-bottom: 1px solid #e2e8f0;
border: 0; padding: 14px 20px;
display: block; display: flex;
background: #111827; flex-wrap: wrap;
align-items: center;
gap: 12px 24px;
} }
.fallback { .logo {
position: fixed; font-size: 15px;
right: 12px; font-weight: 700;
bottom: 12px; color: #4f46e5;
z-index: 10; letter-spacing: -.3px;
flex: none;
}
.info {
display: flex;
flex-wrap: wrap;
gap: 6px 20px;
flex: 1;
}
.chip {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; gap: 5px;
padding: 10px 14px; background: #f1f5f9;
border-radius: 999px; border: 1px solid #e2e8f0;
background: rgba(17, 24, 39, 0.9); border-radius: 8px;
padding: 4px 10px;
font-size: 13px;
color: #374151;
}
.chip .label {
font-size: 11px;
color: #94a3b8;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .4px;
}
.chip .value {
font-weight: 600;
color: #1e293b;
}
.open-btn {
display: inline-flex;
align-items: center;
gap: 6px;
background: #4f46e5;
color: #fff; color: #fff;
text-decoration: none; text-decoration: none;
font: 600 14px/1 system-ui, sans-serif; font-size: 13px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25); font-weight: 600;
padding: 7px 14px;
border-radius: 8px;
flex: none;
transition: background .15s;
}
.open-btn:hover { background: #4338ca; }
.pdf-area {
flex: 1;
display: flex;
flex-direction: column;
padding: 16px;
gap: 12px;
}
.pdf-embed {
flex: 1;
min-height: 60vh;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 24px rgba(0,0,0,.08);
background: #fff;
}
.pdf-embed embed,
.pdf-embed iframe {
width: 100%;
height: 100%;
min-height: 60vh;
border: 0;
display: block;
}
.fallback-notice {
background: #fef9c3;
border: 1px solid #fde047;
border-radius: 10px;
padding: 12px 16px;
font-size: 13px;
color: #854d0e;
display: none;
}
@media (max-width: 480px) {
header { padding: 10px 14px; }
.chip { font-size: 12px; padding: 3px 8px; }
} }
</style> </style>
</head> </head>
<body> <body>
<iframe class="viewer" src="{{ $pdfUrl }}#toolbar=0&navpanes=0&scrollbar=0" title="PDF territorio {{ $assignment->territorio?->numero }}"></iframe> <header>
<a class="fallback" href="{{ $pdfUrl }}" target="_blank" rel="noopener noreferrer">Apri PDF</a> <span class="logo">TerManager2</span>
<div class="info">
<div class="chip">
<span class="label">Territorio</span>
<span class="value"> {{ $assignment->territorio?->numero }}</span>
</div>
<div class="chip">
<span class="label">Assegnato a</span>
<span class="value">{{ $assignment->proclamatore?->nome_completo ?? '—' }}</span>
</div>
<div class="chip">
<span class="label">Data</span>
<span class="value">{{ $assignment->assigned_at->format('d/m/Y') }}</span>
</div>
</div>
<a href="{{ $pdfUrl }}" download class="open-btn">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5m0 0l5-5m-5 5V4"/></svg>
Scarica PDF
</a>
</header>
<div class="pdf-area">
<div class="pdf-embed" id="pdfContainer">
<embed src="{{ $pdfUrl }}" type="application/pdf" id="pdfEmbed">
</div>
<div class="fallback-notice" id="fallbackNotice">
Il tuo dispositivo non supporta la visualizzazione del PDF nel browser.
<a href="{{ $pdfUrl }}" download style="color:#4f46e5;font-weight:600;margin-left:6px;">Scarica il file PDF</a>
</div>
</div>
<script>
// Detect if embed loaded - fallback for mobile devices that don't support PDF embed
var embed = document.getElementById('pdfEmbed');
var notice = document.getElementById('fallbackNotice');
embed.onerror = function() {
notice.style.display = 'block';
};
// iOS Safari: embed always renders but PDF viewer doesn't work - show download link anyway
var ua = navigator.userAgent;
if (/iP(hone|ad|od)/i.test(ua) || (/Macintosh/i.test(ua) && 'ontouchend' in document)) {
document.getElementById('pdfContainer').innerHTML =
'<div style="display:flex;align-items:center;justify-content:center;height:100%;min-height:60vh;flex-direction:column;gap:16px;padding:24px;text-align:center;">' +
'<svg width="48" height="48" fill="none" viewBox="0 0 24 24" stroke="#94a3b8" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"/></svg>' +
'<p style="font-size:14px;color:#64748b;max-width:240px">Safari su iOS non supporta la visualizzazione PDF nel browser.</p>' +
'<a href="{{ $pdfUrl }}" download style="background:#4f46e5;color:#fff;text-decoration:none;font-weight:600;font-size:14px;padding:10px 20px;border-radius:8px;">Scarica PDF</a>' +
'</div>';
}
</script>
</body> </body>
</html> </html>

View File

@@ -8,7 +8,21 @@
<p class="text-sm text-gray-500 mt-1">Questa funzione consente un'assegnazione arbitraria, indipendente dalle tre schede della Home.</p> <p class="text-sm text-gray-500 mt-1">Questa funzione consente un'assegnazione arbitraria, indipendente dalle tre schede della Home.</p>
</div> </div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6 max-w-3xl"> <div x-data="{ formOpen: true }">
{{-- Toggle form --}}
<button type="button"
x-on:click="formOpen = !formOpen"
class="mb-4 inline-flex items-center gap-2 text-sm font-medium text-indigo-600 hover:text-indigo-800 transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path x-show="formOpen" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"/>
<path x-show="!formOpen" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
<span x-text="formOpen ? 'Nascondi form' : 'Nuova assegnazione'"></span>
</button>
{{-- Form --}}
<div x-show="formOpen" x-cloak class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6 mb-6 max-w-3xl">
<form wire:submit="save" class="space-y-4"> <form wire:submit="save" class="space-y-4">
<div> <div>
@if(!$preselectedTerritorioId) @if(!$preselectedTerritorioId)
@@ -41,7 +55,6 @@
alt="Thumbnail territorio selezionato" alt="Thumbnail territorio selezionato"
class="block w-full h-auto max-h-[70vh] object-contain bg-white"> class="block w-full h-auto max-h-[70vh] object-contain bg-white">
</div> </div>
<p class="mt-2 text-xs text-gray-500">Miniatura del territorio ottimizzata per consultazione rapida anche da mobile.</p>
@else @else
<div class="rounded-lg border border-dashed border-gray-300 bg-gray-50 px-4 py-6 text-sm text-gray-500"> <div class="rounded-lg border border-dashed border-gray-300 bg-gray-50 px-4 py-6 text-sm text-gray-500">
Nessuna thumbnail disponibile per questo territorio. Nessuna thumbnail disponibile per questo territorio.
@@ -70,50 +83,85 @@
<div class="flex flex-col-reverse gap-3 pt-4 sm:flex-row sm:items-center"> <div class="flex flex-col-reverse gap-3 pt-4 sm:flex-row sm:items-center">
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 transition">Assegna</button> <button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 transition">Assegna</button>
<a href="{{ route('territori.index') }}" class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition">Annulla</a> <button type="button" x-on:click="formOpen = false" class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition">Annulla</button>
</div> </div>
</form> </form>
</div> </div>
{{-- Elenco territori attualmente assegnati con link --}} </div>
{{-- Elenco territori attualmente assegnati --}}
@if($assegnazioniAperte->count()) @if($assegnazioniAperte->count())
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden mt-10"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden mt-2">
<div class="px-5 py-4 border-b flex items-center gap-3" style="background:linear-gradient(135deg,#eef2ff,#e0e7ff);border-color:#c7d2fe"> <div class="px-5 py-4 border-b flex items-center gap-3" style="background:linear-gradient(135deg,#eef2ff,#e0e7ff);border-color:#c7d2fe">
<div class="h-8 w-8 rounded-lg flex items-center justify-center" style="background:#6366f1"> <div class="h-8 w-8 rounded-lg flex items-center justify-center" style="background:#6366f1">
<svg class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg> <svg class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>
</div> </div>
<div> <div>
<h3 class="text-sm font-semibold" style="color:#3730a3">Territori Assegnati ({{ $assegnazioniAperte->count() }})</h3> <h3 class="text-sm font-semibold" style="color:#3730a3">Territori Assegnati ({{ $assegnazioniAperte->count() }})</h3>
<p class="text-xs" style="color:#4f46e5">Per ogni territorio è visibile il link da condividere</p> <p class="text-xs" style="color:#4f46e5">Link PDF · stato invio · link valido {{ $linkTtlMonths }} {{ $linkTtlMonths === 1 ? 'mese' : 'mesi' }}</p>
</div> </div>
</div> </div>
<div class="divide-y divide-gray-100"> <div class="divide-y divide-gray-100">
@foreach($assegnazioniAperte as $a) @foreach($assegnazioniAperte as $a)
@php($pdfUrl = $a->shortPdfUrl()) @php
$pdfUrl = $a->shortPdfUrl();
$linkScaduto = ! auth()->check() && $a->assigned_at->copy()->addMonths($linkTtlMonths)->isPast();
@endphp
<div class="px-5 py-4 hover:bg-indigo-50/30 transition-colors"> <div class="px-5 py-4 hover:bg-indigo-50/30 transition-colors">
<div class="flex items-center justify-between gap-4"> {{-- Testata riga --}}
<div class="flex items-center gap-4"> <div class="flex flex-wrap items-start justify-between gap-x-6 gap-y-2">
<a href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline font-semibold text-sm">N° {{ $a->territorio?->numero }}</a> <div class="flex items-center gap-2">
<span class="text-xs text-gray-400">{{ $a->territorio?->zona?->nome }}</span> <a href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline font-bold text-sm">N° {{ $a->territorio?->numero }}</a>
@if($a->territorio?->zona?->nome)
<span class="text-xs text-gray-400 bg-gray-100 rounded px-1.5 py-0.5">{{ $a->territorio?->zona?->nome }}</span>
@endif
</div> </div>
<div class="flex items-center gap-5 text-sm"> <div class="flex items-center gap-3 text-sm flex-wrap">
<span class="text-gray-700">{{ $a->proclamatore?->nome_completo ?? 'N/A' }}</span> <span class="font-medium text-gray-800">{{ $a->proclamatore?->nome_completo ?? 'N/A' }}</span>
<span class="text-gray-400">{{ $a->assigned_at->format('d/m/Y') }}</span> <span class="text-gray-300">·</span>
<span class="text-xs font-medium {{ $a->giorni > 120 ? 'text-red-600' : ($a->giorni > 90 ? 'text-amber-600' : 'text-gray-500') }}">{{ $a->giorni }}g</span> <span class="text-gray-500 text-xs">{{ $a->assigned_at->format('d/m/Y') }}</span>
<span class="text-gray-300">·</span>
<span class="text-xs font-semibold px-2 py-0.5 rounded-full
{{ $a->giorni > 120 ? 'bg-red-50 text-red-600' : ($a->giorni > 90 ? 'bg-amber-50 text-amber-600' : 'bg-gray-100 text-gray-500') }}">
{{ $a->giorni }}g
</span>
</div> </div>
</div> </div>
@if($pdfUrl)
<div x-data="{ copied: false }" class="mt-2 flex items-center gap-2"> {{-- Avviso link scaduto (solo per non loggati) --}}
@if($linkScaduto)
<div class="mt-3 inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-semibold bg-red-50 border border-red-200 text-red-700">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Link scaduto — rigenerare dal dettaglio territorio
</div>
{{-- Link attivo --}}
@elseif($pdfUrl)
<div x-data="{ copied: false }" class="mt-3 flex flex-wrap items-center gap-2">
<code class="flex-1 min-w-0 text-xs bg-gray-50 border border-gray-200 rounded-md px-3 py-2 text-gray-600 break-all select-all cursor-text" x-on:click="window.getSelection().selectAllChildren($el)">{{ $pdfUrl }}</code> <code class="flex-1 min-w-0 text-xs bg-gray-50 border border-gray-200 rounded-md px-3 py-2 text-gray-600 break-all select-all cursor-text" x-on:click="window.getSelection().selectAllChildren($el)">{{ $pdfUrl }}</code>
<button type="button" <button type="button"
x-on:click=" x-on:click="navigator.clipboard.writeText($el.closest('div').querySelector('code').textContent).then(() => { copied = true; setTimeout(() => copied = false, 2000) })"
const text = $el.closest('div').querySelector('code').textContent;
navigator.clipboard.writeText(text).then(() => { copied = true; setTimeout(() => copied = false, 2000) });
"
class="flex-none btn-action btn-action-indigo" title="Copia link" style="padding:6px 10px"> class="flex-none btn-action btn-action-indigo" title="Copia link" style="padding:6px 10px">
<svg x-show="!copied" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg x-show="!copied" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
<svg x-show="copied" x-cloak class="h-4 w-4 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> <svg x-show="copied" x-cloak class="h-4 w-4 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
</button> </button>
{{-- Flag Link Inviato --}}
<button type="button"
wire:click="toggleLinkSent({{ $a->id }})"
title="{{ $a->link_sent ? 'Link inviato clicca per annullare' : 'Segna come inviato' }}"
class="flex-none inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-semibold border transition
{{ $a->link_sent ? 'bg-green-50 border-green-200 text-green-700 hover:bg-green-100' : 'bg-gray-50 border-gray-200 text-gray-500 hover:bg-gray-100' }}">
@if($a->link_sent)
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
Link inviato
@else
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
Non inviato
@endif
</button>
<span x-show="copied" x-cloak class="flex-none text-xs text-green-600 font-medium">Copiato!</span> <span x-show="copied" x-cloak class="flex-none text-xs text-green-600 font-medium">Copiato!</span>
</div> </div>
@else @else