++ Prima versione del designer piantine + Skill grafica
This commit is contained in:
317
app/Livewire/Piantine/PiantineManager.php
Normal file
317
app/Livewire/Piantine/PiantineManager.php
Normal file
@@ -0,0 +1,317 @@
|
||||
<?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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user