318 lines
11 KiB
PHP
318 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Piantine;
|
|
|
|
use App\Jobs\RigeneraPiantine;
|
|
use App\Models\Territorio;
|
|
use App\Models\TerritorioMappa;
|
|
use App\Models\TerritorioPdfVersione;
|
|
use App\Services\Piantine\PiantinaPdfService;
|
|
use App\Services\Piantine\PiantinaRenderer;
|
|
use App\Services\Piantine\PiantineArea;
|
|
use App\Services\Piantine\PiantineOperationState;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Livewire\Attributes\On;
|
|
use Livewire\Component;
|
|
use Livewire\WithFileUploads;
|
|
|
|
/**
|
|
* Sotto-app "Piantine" (solo amministratore).
|
|
*
|
|
* Dal KML dell'area città si disegnano i poligoni dei territori su una mappa
|
|
* OpenStreetMap; da ogni poligono si genera l'immagine ad alta definizione
|
|
* (container map-renderer) e, a scelta, il PDF sul modulo S-12 che sostituisce
|
|
* quello del territorio, con storico versioni.
|
|
*/
|
|
class PiantineManager extends Component
|
|
{
|
|
use WithFileUploads;
|
|
|
|
public $kmlFile = null;
|
|
|
|
public string $localita = '';
|
|
|
|
public ?int $territorioSelezionato = null;
|
|
|
|
/** Geometria (JSON) appena disegnata sulla mappa, in attesa di salvataggio. */
|
|
public ?string $geometriaInAttesa = null;
|
|
|
|
/** Id dell'operazione di rigenerazione massiva seguita dalla pagina. */
|
|
public ?string $operazioneId = null;
|
|
|
|
public function mount(PiantineArea $area, PiantineOperationState $state): void
|
|
{
|
|
$this->localita = $area->localita();
|
|
$this->operazioneId = $state->corrente();
|
|
}
|
|
|
|
// ─── Area città (KML) ───────────────────────────────────────────────────
|
|
|
|
public function caricaKml(PiantineArea $area)
|
|
{
|
|
$this->validate(
|
|
['kmlFile' => 'required|file|max:10240'],
|
|
[],
|
|
['kmlFile' => 'file KML'],
|
|
);
|
|
|
|
$estensione = strtolower($this->kmlFile->getClientOriginalExtension());
|
|
if (! in_array($estensione, ['kml', 'xml'], true)) {
|
|
$this->addError('kmlFile', 'Il file deve essere un .kml (o .xml in formato KML).');
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$quante = $area->salvaKml(
|
|
file_get_contents($this->kmlFile->getRealPath()),
|
|
$this->kmlFile->getClientOriginalName(),
|
|
);
|
|
} catch (\Throwable $e) {
|
|
$this->addError('kmlFile', $e->getMessage());
|
|
|
|
return;
|
|
}
|
|
|
|
$this->kmlFile = null;
|
|
|
|
activity()->causedBy(auth()->user())->log('piantine_kml_caricato');
|
|
session()->flash('success', "Area della città aggiornata ({$quante} geometrie riconosciute nel KML).");
|
|
|
|
$this->aggiornaMappaJs();
|
|
}
|
|
|
|
public function salvaLocalita(PiantineArea $area): void
|
|
{
|
|
$area->impostaLocalita($this->localita);
|
|
session()->flash('success', 'Località aggiornata: comparirà sull\'intestazione dei prossimi PDF.');
|
|
}
|
|
|
|
// ─── Editor dei poligoni ────────────────────────────────────────────────
|
|
|
|
#[On('poligono-disegnato')]
|
|
public function poligonoDisegnato($geometry): void
|
|
{
|
|
if (! is_array($geometry)
|
|
|| ! in_array($geometry['type'] ?? null, ['Polygon', 'MultiPolygon'], true)
|
|
|| ! is_array($geometry['coordinates'] ?? null)) {
|
|
return;
|
|
}
|
|
|
|
$this->geometriaInAttesa = json_encode($geometry);
|
|
}
|
|
|
|
#[On('territorio-selezionato-mappa')]
|
|
public function territorioSelezionatoDaMappa($territorioId): void
|
|
{
|
|
$this->territorioSelezionato = is_numeric($territorioId) ? (int) $territorioId : null;
|
|
$this->aggiornaMappaJs();
|
|
}
|
|
|
|
public function updatedTerritorioSelezionato(): void
|
|
{
|
|
$this->territorioSelezionato = $this->territorioSelezionato ?: null;
|
|
$this->aggiornaMappaJs();
|
|
}
|
|
|
|
public function salvaPoligono()
|
|
{
|
|
if (! $this->territorioSelezionato) {
|
|
session()->flash('error', 'Scegli prima a quale territorio associare il poligono.');
|
|
|
|
return;
|
|
}
|
|
|
|
if (! $this->geometriaInAttesa) {
|
|
session()->flash('error', 'Disegna prima il poligono sulla mappa.');
|
|
|
|
return;
|
|
}
|
|
|
|
$territorio = Territorio::findOrFail($this->territorioSelezionato);
|
|
|
|
TerritorioMappa::updateOrCreate(
|
|
['territorio_id' => $territorio->id],
|
|
['geojson' => $this->geometriaInAttesa],
|
|
);
|
|
|
|
$this->geometriaInAttesa = null;
|
|
|
|
activity()->causedBy(auth()->user())->performedOn($territorio)->log('piantina_poligono_salvato');
|
|
session()->flash('success', "Poligono salvato per il territorio {$territorio->numero}. Ora puoi generare l'immagine.");
|
|
|
|
$this->aggiornaMappaJs();
|
|
}
|
|
|
|
public function scartaPoligono(): void
|
|
{
|
|
$this->geometriaInAttesa = null;
|
|
$this->aggiornaMappaJs();
|
|
}
|
|
|
|
public function eliminaPoligono(int $mappaId)
|
|
{
|
|
$mappa = TerritorioMappa::with('territorio')->findOrFail($mappaId);
|
|
$numero = $mappa->territorio?->numero ?? '?';
|
|
|
|
if ($mappa->image_path) {
|
|
Storage::disk('public')->delete($mappa->image_path);
|
|
}
|
|
$mappa->delete();
|
|
|
|
session()->flash('success', "Poligono e immagine del territorio {$numero} eliminati (il PDF resta com'è).");
|
|
$this->aggiornaMappaJs();
|
|
}
|
|
|
|
// ─── Generazione ────────────────────────────────────────────────────────
|
|
|
|
public function generaImmagine(int $mappaId, PiantinaRenderer $renderer)
|
|
{
|
|
$mappa = TerritorioMappa::with('territorio')->findOrFail($mappaId);
|
|
|
|
try {
|
|
$renderer->render($mappa);
|
|
} catch (\Throwable $e) {
|
|
session()->flash('error', $e->getMessage());
|
|
|
|
return;
|
|
}
|
|
|
|
session()->flash('success', "Immagine generata per il territorio {$mappa->territorio?->numero}.");
|
|
}
|
|
|
|
public function generaPdf(int $mappaId, PiantinaPdfService $pdfService)
|
|
{
|
|
$mappa = TerritorioMappa::with('territorio')->findOrFail($mappaId);
|
|
$territorio = $mappa->territorio;
|
|
|
|
if (! $territorio) {
|
|
session()->flash('error', 'Il territorio associato non esiste più.');
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$pdfService->sostituisci($territorio, $mappa);
|
|
} catch (\Throwable $e) {
|
|
session()->flash('error', $e->getMessage());
|
|
|
|
return;
|
|
}
|
|
|
|
session()->flash('success', "PDF del territorio {$territorio->numero} sostituito. Il precedente è nello storico versioni.");
|
|
}
|
|
|
|
// ─── Rigenerazione massiva ──────────────────────────────────────────────
|
|
|
|
public function rigeneraTutto(bool $anchePdf, PiantineOperationState $state)
|
|
{
|
|
if ($state->corrente() !== null) {
|
|
session()->flash('error', 'C\'è già una rigenerazione in corso: attendi che finisca.');
|
|
|
|
return;
|
|
}
|
|
|
|
$totale = TerritorioMappa::count();
|
|
if ($totale === 0) {
|
|
session()->flash('error', 'Nessun territorio ha ancora un poligono disegnato.');
|
|
|
|
return;
|
|
}
|
|
|
|
$id = (string) Str::uuid();
|
|
$state->initialize($id, $totale, $anchePdf);
|
|
RigeneraPiantine::dispatch($id, $anchePdf);
|
|
|
|
$this->operazioneId = $id;
|
|
|
|
activity()->causedBy(auth()->user())->log($anchePdf ? 'piantine_rigenerazione_pdf' : 'piantine_rigenerazione');
|
|
}
|
|
|
|
// ─── Storico versioni ───────────────────────────────────────────────────
|
|
|
|
public function ripristinaVersione(int $versioneId, PiantinaPdfService $pdfService)
|
|
{
|
|
$versione = TerritorioPdfVersione::with('territorio')->findOrFail($versioneId);
|
|
$numero = $versione->territorio?->numero ?? '?';
|
|
|
|
try {
|
|
$pdfService->ripristina($versione);
|
|
} catch (\Throwable $e) {
|
|
session()->flash('error', $e->getMessage());
|
|
|
|
return;
|
|
}
|
|
|
|
session()->flash('success', "Versione precedente ripristinata per il territorio {$numero}.");
|
|
}
|
|
|
|
public function eliminaVersione(int $versioneId)
|
|
{
|
|
$versione = TerritorioPdfVersione::findOrFail($versioneId);
|
|
|
|
if (Storage::disk('public')->exists($versione->pdf_path)) {
|
|
Storage::disk('public')->delete($versione->pdf_path);
|
|
}
|
|
$versione->delete();
|
|
|
|
session()->flash('success', 'Versione eliminata definitivamente.');
|
|
}
|
|
|
|
// ─── Dati per la mappa e rendering ──────────────────────────────────────
|
|
|
|
/**
|
|
* Il payload che il JavaScript della mappa usa per disegnare: area della
|
|
* città, poligoni salvati, selezione corrente.
|
|
*/
|
|
public function datiMappa(?PiantineArea $area = null): array
|
|
{
|
|
$area ??= app(PiantineArea::class);
|
|
|
|
$poligoni = TerritorioMappa::with('territorio')->get()
|
|
->filter(fn ($mappa) => $mappa->territorio !== null && $mappa->geometria() !== null)
|
|
->map(fn ($mappa) => [
|
|
'territorioId' => $mappa->territorio_id,
|
|
'numero' => $mappa->territorio->numero,
|
|
'geometry' => $mappa->geometria(),
|
|
])
|
|
->values()
|
|
->all();
|
|
|
|
return [
|
|
'area' => $area->area(),
|
|
'poligoni' => $poligoni,
|
|
'selezionato' => $this->territorioSelezionato,
|
|
];
|
|
}
|
|
|
|
private function aggiornaMappaJs(): void
|
|
{
|
|
$this->dispatch('piantine-dati-aggiornati', dati: $this->datiMappa());
|
|
}
|
|
|
|
public function render(PiantineArea $area, PiantineOperationState $state)
|
|
{
|
|
$mappe = TerritorioMappa::with('territorio')->get()->keyBy('territorio_id');
|
|
|
|
$territori = Territorio::attivi()
|
|
->orderByRaw('LENGTH(numero), numero')
|
|
->get(['id', 'numero', 'pdf_path']);
|
|
|
|
$stato = $this->operazioneId ? $state->get($this->operazioneId) : null;
|
|
|
|
return view('livewire.piantine.piantine-manager', [
|
|
'config' => $area->config(),
|
|
'datiMappaIniziali' => $this->datiMappa($area),
|
|
'territori' => $territori,
|
|
'mappe' => $mappe,
|
|
'versioni' => TerritorioPdfVersione::with('territorio')
|
|
->orderByDesc('sostituito_il')
|
|
->limit(30)
|
|
->get(),
|
|
'stato' => $stato,
|
|
'title' => 'Piantine dei territori',
|
|
]);
|
|
}
|
|
}
|