++ Prima versione del designer piantine + Skill grafica
This commit is contained in:
@@ -143,6 +143,11 @@
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A2 2 0 013 12V7a4 4 0 014-4z"/></svg>
|
||||
Tipologie
|
||||
</a>
|
||||
<a href="{{ route('piantine.index') }}"
|
||||
class="flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs('piantine.*') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-100' }}">
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 6.75V15m6-6v8.25m.503 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z"/></svg>
|
||||
Piantine
|
||||
</a>
|
||||
<a href="{{ route('xml.exchange') }}"
|
||||
class="flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-lg {{ request()->routeIs('xml.*', 'backup.*') ? 'bg-indigo-50 text-indigo-700' : 'text-gray-700 hover:bg-gray-100' }}">
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 16v4m0 0l-3-3m3 3l3-3M4 12a8 8 0 1116 0v1a3 3 0 01-3 3h-1M7 16H6a2 2 0 01-2-2v-2"/></svg>
|
||||
|
||||
229
resources/views/livewire/piantine/piantine-manager.blade.php
Normal file
229
resources/views/livewire/piantine/piantine-manager.blade.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<div>
|
||||
@vite('resources/js/piantine.js')
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">Piantine dei territori</h1>
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
Carica il KML dell'area della città, disegna i poligoni dei territori sulla mappa e genera
|
||||
immagini ad alta definizione e PDF sul modulo S-12. I PDF sostituiti finiscono nello storico
|
||||
versioni, sempre ripristinabili.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- ── 1. Area della città (KML) ─────────────────────────────────── --}}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-4">
|
||||
<h2 class="text-base font-semibold text-gray-900 mb-3">1 · Area della città</h2>
|
||||
<div class="grid gap-4 lg:grid-cols-2">
|
||||
<form wire:submit="caricaKml">
|
||||
<label for="kmlFile" class="block text-sm font-medium text-gray-700">File .kml dell'area</label>
|
||||
<div class="mt-1 flex items-center gap-3">
|
||||
<input type="file" id="kmlFile" wire:model="kmlFile" accept=".kml,.xml"
|
||||
class="block w-full text-sm text-gray-600 file:mr-3 file:rounded-lg file:border-0 file:bg-indigo-50 file:px-3 file:py-2 file:text-sm file:font-medium file:text-indigo-700 hover:file:bg-indigo-100">
|
||||
<x-btn variante="primary" type="submit" wire:loading.attr="disabled" wire:target="kmlFile,caricaKml">
|
||||
Carica
|
||||
</x-btn>
|
||||
</div>
|
||||
<div wire:loading wire:target="kmlFile" class="mt-1 text-xs text-gray-500">Caricamento del file…</div>
|
||||
@error('kmlFile') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||
@if($config['kml_nome'])
|
||||
<p class="mt-2 text-xs text-gray-500">
|
||||
Area attiva: <span class="font-medium text-gray-700">{{ $config['kml_nome'] }}</span>
|
||||
(aggiornata il {{ $config['aggiornato_il'] }}).
|
||||
Caricare un nuovo KML sostituisce l'area; i poligoni già disegnati restano.
|
||||
</p>
|
||||
@else
|
||||
<p class="mt-2 text-xs text-amber-600">Nessuna area caricata: la mappa parte senza perimetro di riferimento.</p>
|
||||
@endif
|
||||
</form>
|
||||
<form wire:submit="salvaLocalita">
|
||||
<label for="localita" class="block text-sm font-medium text-gray-700">
|
||||
Località (intestazione del modulo S-12)
|
||||
</label>
|
||||
<div class="mt-1 flex items-center gap-3">
|
||||
<input type="text" id="localita" wire:model="localita" placeholder="Es. Palermo — Congregazione …"
|
||||
class="block w-full rounded-lg border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm">
|
||||
<x-btn variante="secondary" type="submit">Salva</x-btn>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500">Se vuota, viene usato il nome della congregazione dalle impostazioni.</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── 2. Editor della mappa ─────────────────────────────────────── --}}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-4">
|
||||
<h2 class="text-base font-semibold text-gray-900 mb-3">2 · Disegno dei territori</h2>
|
||||
<div class="grid gap-4 lg:grid-cols-3">
|
||||
<div class="lg:col-span-2">
|
||||
<div wire:ignore id="piantine-map" class="w-full rounded-xl border border-gray-300 z-0" style="height: 65vh; min-height: 420px;"></div>
|
||||
<p class="mt-2 text-xs text-gray-500">
|
||||
Usa lo strumento poligono (in alto a sinistra sulla mappa) per disegnare il territorio.
|
||||
Il tratteggio blu è l'area del KML; i poligoni grigi sono i territori già salvati.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label for="territorioSelezionato" class="block text-sm font-medium text-gray-700">Territorio da associare</label>
|
||||
<select id="territorioSelezionato" wire:model.live="territorioSelezionato"
|
||||
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm">
|
||||
<option value="">— scegli il territorio —</option>
|
||||
@foreach($territori as $territorio)
|
||||
<option value="{{ $territorio->id }}">
|
||||
{{ $territorio->numero }}{{ $mappe->has($territorio->id) ? ' · poligono presente' : '' }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
@if($geometriaInAttesa)
|
||||
<div class="mt-3 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800">
|
||||
C'è un poligono disegnato non ancora salvato.
|
||||
</div>
|
||||
<div class="mt-2 flex gap-2">
|
||||
<x-btn variante="primary" wire:click="salvaPoligono" wire:loading.attr="disabled">
|
||||
Salva poligono
|
||||
</x-btn>
|
||||
<x-btn variante="ghost" wire:click="scartaPoligono">Scarta</x-btn>
|
||||
</div>
|
||||
@else
|
||||
<p class="mt-3 text-xs text-gray-500">
|
||||
Disegna un poligono e poi salvalo sul territorio selezionato. Ridisegnare e salvare
|
||||
sostituisce il poligono esistente.
|
||||
</p>
|
||||
@endif
|
||||
|
||||
{{-- Territori con poligono --}}
|
||||
<div class="mt-4 border-t border-gray-200 pt-3">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-2">Territori con poligono ({{ $mappe->count() }})</h3>
|
||||
<ul class="divide-y divide-gray-100 max-h-72 overflow-y-auto pr-1">
|
||||
@forelse($mappe->sortBy(fn ($m) => [strlen($m->territorio?->numero ?? ''), $m->territorio?->numero]) as $mappa)
|
||||
<li class="py-2 text-sm" wire:key="mappa-{{ $mappa->id }}">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<button type="button" wire:click="$set('territorioSelezionato', {{ $mappa->territorio_id }})"
|
||||
class="font-medium {{ $territorioSelezionato === $mappa->territorio_id ? 'text-indigo-700' : 'text-gray-800' }} hover:underline">
|
||||
Terr. {{ $mappa->territorio?->numero ?? '?' }}
|
||||
</button>
|
||||
<span class="text-xs {{ $mappa->image_path ? 'text-green-600' : 'text-gray-400' }}">
|
||||
{{ $mappa->image_path ? 'immagine del ' . $mappa->rendered_at?->format('d/m/Y H:i') : 'immagine da generare' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
|
||||
<button type="button" wire:click="generaImmagine({{ $mappa->id }})"
|
||||
wire:loading.attr="disabled" wire:target="generaImmagine({{ $mappa->id }})"
|
||||
class="text-indigo-600 hover:underline disabled:opacity-50">
|
||||
<span wire:loading.remove wire:target="generaImmagine({{ $mappa->id }})">Genera immagine</span>
|
||||
<span wire:loading wire:target="generaImmagine({{ $mappa->id }})">Rendering in corso…</span>
|
||||
</button>
|
||||
@if($mappa->image_path)
|
||||
<a href="{{ asset('storage/' . $mappa->image_path) }}" target="_blank" class="text-indigo-600 hover:underline">Scarica immagine</a>
|
||||
<button type="button" wire:click="generaPdf({{ $mappa->id }})"
|
||||
wire:loading.attr="disabled" wire:target="generaPdf({{ $mappa->id }})"
|
||||
wire:confirm="Sostituire il PDF del territorio {{ $mappa->territorio?->numero }}? Quello attuale finisce nello storico versioni."
|
||||
class="text-indigo-600 hover:underline disabled:opacity-50">
|
||||
<span wire:loading.remove wire:target="generaPdf({{ $mappa->id }})">Genera PDF e sostituisci</span>
|
||||
<span wire:loading wire:target="generaPdf({{ $mappa->id }})">Composizione…</span>
|
||||
</button>
|
||||
@endif
|
||||
<button type="button" wire:click="eliminaPoligono({{ $mappa->id }})"
|
||||
wire:confirm="Eliminare poligono e immagine del territorio {{ $mappa->territorio?->numero }}? Il PDF del territorio resta com'è."
|
||||
class="text-red-600 hover:underline">Elimina</button>
|
||||
</div>
|
||||
</li>
|
||||
@empty
|
||||
<li class="py-2 text-sm text-gray-500">Nessun poligono ancora disegnato.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── 3. Rigenerazione massiva ──────────────────────────────────── --}}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-4"
|
||||
@if($stato && in_array($stato['status'], ['queued', 'running'], true)) wire:poll.2s @endif>
|
||||
<h2 class="text-base font-semibold text-gray-900 mb-3">3 · Rigenerazione massiva</h2>
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
Rigenera in coda le immagini di tutti i territori con poligono (utile dopo aggiornamenti della
|
||||
cartografia). Con la seconda opzione vengono ricomposti e sostituiti anche i PDF.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<x-btn variante="secondary" wire:click="rigeneraTutto(false)"
|
||||
wire:confirm="Rigenerare le immagini di tutti i territori con poligono?"
|
||||
wire:loading.attr="disabled" :disabled="$stato && in_array($stato['status'], ['queued', 'running'], true)">
|
||||
Rigenera tutte le immagini
|
||||
</x-btn>
|
||||
<x-btn variante="secondary" wire:click="rigeneraTutto(true)"
|
||||
wire:confirm="Rigenerare immagini E sostituire tutti i PDF? Le versioni attuali finiscono nello storico."
|
||||
wire:loading.attr="disabled" :disabled="$stato && in_array($stato['status'], ['queued', 'running'], true)">
|
||||
Rigenera immagini + PDF
|
||||
</x-btn>
|
||||
</div>
|
||||
|
||||
@if($stato)
|
||||
<div class="mt-4">
|
||||
@php
|
||||
$percento = ($stato['total'] ?? 0) > 0 ? (int) round(($stato['processed'] / $stato['total']) * 100) : 0;
|
||||
@endphp
|
||||
<div class="flex items-center justify-between text-xs text-gray-600 mb-1">
|
||||
<span>
|
||||
@if($stato['status'] === 'queued') In coda…
|
||||
@elseif($stato['status'] === 'running') In corso: {{ $stato['processed'] }}/{{ $stato['total'] }}
|
||||
@elseif($stato['status'] === 'completed') Completata: {{ $stato['processed'] }}/{{ $stato['total'] }} ({{ $stato['falliti'] }} fallite)
|
||||
@else Fallita: {{ $stato['error'] }}
|
||||
@endif
|
||||
</span>
|
||||
<span>{{ $percento }}%</span>
|
||||
</div>
|
||||
<div class="h-2 w-full rounded-full bg-gray-100 overflow-hidden">
|
||||
<div class="h-2 rounded-full transition-all duration-500 {{ $stato['status'] === 'failed' ? 'bg-red-500' : 'bg-indigo-500' }}"
|
||||
style="width: {{ $percento }}%"></div>
|
||||
</div>
|
||||
@if(!empty($stato['logs']))
|
||||
<div class="mt-3 max-h-48 overflow-y-auto rounded-lg bg-gray-900 p-3 font-mono text-xs text-gray-100 space-y-0.5">
|
||||
@foreach($stato['logs'] as $riga)
|
||||
<div>{{ $riga }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- ── 4. Storico versioni PDF ───────────────────────────────────── --}}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
||||
<h2 class="text-base font-semibold text-gray-900 mb-3">4 · Storico versioni PDF</h2>
|
||||
@if($versioni->isEmpty())
|
||||
<p class="text-sm text-gray-500">Nessun PDF è ancora stato sostituito.</p>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-xs uppercase tracking-wider text-gray-500 border-b border-gray-200">
|
||||
<th class="py-2 pr-4">Territorio</th>
|
||||
<th class="py-2 pr-4">Sostituito il</th>
|
||||
<th class="py-2 pr-4">Origine</th>
|
||||
<th class="py-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
@foreach($versioni as $versione)
|
||||
<tr wire:key="versione-{{ $versione->id }}">
|
||||
<td class="py-2 pr-4 font-medium text-gray-800">{{ $versione->territorio?->numero ?? '?' }}</td>
|
||||
<td class="py-2 pr-4 text-gray-600">{{ $versione->sostituito_il?->format('d/m/Y H:i') }}</td>
|
||||
<td class="py-2 pr-4 text-gray-600">{{ $versione->origine }}</td>
|
||||
<td class="py-2 text-right space-x-3 whitespace-nowrap">
|
||||
<a href="{{ asset('storage/' . $versione->pdf_path) }}" target="_blank" class="text-indigo-600 hover:underline text-xs">Apri</a>
|
||||
<button type="button" wire:click="ripristinaVersione({{ $versione->id }})"
|
||||
wire:confirm="Ripristinare questa versione per il territorio {{ $versione->territorio?->numero }}? Il PDF attuale finisce a sua volta nello storico."
|
||||
class="text-indigo-600 hover:underline text-xs">Ripristina</button>
|
||||
<button type="button" wire:click="eliminaVersione({{ $versione->id }})"
|
||||
wire:confirm="Eliminare definitivamente questa versione archiviata?"
|
||||
class="text-red-600 hover:underline text-xs">Elimina</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<script type="application/json" id="piantine-dati">@json($datiMappaIniziali)</script>
|
||||
</div>
|
||||
Reference in New Issue
Block a user