++ fix temporary PDF viewer URLs, which were causing issues with caching and expiring links. Instead, we now generate short-lived URLs that redirect to the PDF viewer route, ensuring that users can access the PDFs without running into expired links. This change affects the Assegnazione model, the ShortPdfLinkController, and the relevant Blade views for assignments and records. Additionally, I've updated the Home Livewire component to calculate and display the average duration of assignments in months, providing more insight into assignment durations on the dashboard.
This commit is contained in:
@@ -3,35 +3,31 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Assegnazione;
|
use App\Models\Assegnazione;
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\URL;
|
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
class AssignmentPdfController extends Controller
|
class AssignmentPdfController extends Controller
|
||||||
{
|
{
|
||||||
public function viewer(Request $request, Assegnazione $assignment, string $code): View
|
public function viewer(Request $request, Assegnazione $assignment, string $code): View
|
||||||
{
|
{
|
||||||
$this->validateAccess($request, $assignment, $code);
|
$this->validateAccess($assignment, $code);
|
||||||
|
|
||||||
$expiresAt = Carbon::createFromTimestamp((int) $request->query('expires'));
|
$pdfUrl = route('assignments.pdf.file', [
|
||||||
$pdfUrl = URL::temporarySignedRoute(
|
'assignment' => $assignment->id,
|
||||||
'assignments.pdf.file',
|
'code' => $code,
|
||||||
$expiresAt,
|
]);
|
||||||
['assignment' => $assignment->id, 'code' => $code]
|
|
||||||
);
|
|
||||||
|
|
||||||
return view('assignments.pdf-viewer', [
|
return view('assignments.pdf-viewer', [
|
||||||
'assignment' => $assignment,
|
'assignment' => $assignment,
|
||||||
'pdfUrl' => $pdfUrl,
|
'pdfUrl' => $pdfUrl,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function file(Request $request, Assegnazione $assignment, string $code): StreamedResponse
|
public function file(Request $request, Assegnazione $assignment, string $code): StreamedResponse
|
||||||
{
|
{
|
||||||
$this->validateAccess($request, $assignment, $code);
|
$this->validateAccess($assignment, $code);
|
||||||
|
|
||||||
$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);
|
||||||
@@ -40,15 +36,14 @@ class AssignmentPdfController extends Controller
|
|||||||
$pdfPath,
|
$pdfPath,
|
||||||
'territorio-' . $assignment->territorio?->numero . '.pdf',
|
'territorio-' . $assignment->territorio?->numero . '.pdf',
|
||||||
[
|
[
|
||||||
'Content-Type' => 'application/pdf',
|
'Content-Type' => 'application/pdf',
|
||||||
'Content-Disposition' => 'inline; filename="territorio-' . $assignment->territorio?->numero . '.pdf"',
|
'Content-Disposition' => 'inline; filename="territorio-' . $assignment->territorio?->numero . '.pdf"',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function validateAccess(Request $request, Assegnazione $assignment, string $code): void
|
protected function validateAccess(Assegnazione $assignment, string $code): void
|
||||||
{
|
{
|
||||||
abort_unless($request->hasValidSignature(), 403);
|
|
||||||
abort_unless($assignment->pdf_access_code && hash_equals($assignment->pdf_access_code, $code), 404);
|
abort_unless($assignment->pdf_access_code && hash_equals($assignment->pdf_access_code, $code), 404);
|
||||||
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);
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Livewire\Settings\XmlExchange;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class XmlExchangeUploadController extends Controller
|
||||||
|
{
|
||||||
|
public function convertSqlToXml(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'sqlDump' => ['required', 'file', 'max:256000'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$file = $request->file('sqlDump');
|
||||||
|
$ext = strtolower($file->getClientOriginalExtension());
|
||||||
|
if (! in_array($ext, ['sql', 'txt'])) {
|
||||||
|
return back()->withErrors(['sqlDump' => 'Il file deve essere .sql o .txt']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($file->getRealPath());
|
||||||
|
if (! $content) {
|
||||||
|
return back()->withErrors(['sqlDump' => 'File vuoto o non leggibile.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$exchange = app(XmlExchange::class);
|
||||||
|
$dataset = $exchange->legacySqlToDatasetPublic($content);
|
||||||
|
$xml = $exchange->datasetToXmlPublic($dataset, 'legacy-sql-conversion');
|
||||||
|
|
||||||
|
return response()->streamDownload(function () use ($xml) {
|
||||||
|
echo $xml;
|
||||||
|
}, 'termanager-conversion.xml', ['Content-Type' => 'application/xml; charset=UTF-8']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importXml(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'xmlImport' => ['required', 'file', 'max:256000'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$file = $request->file('xmlImport');
|
||||||
|
$ext = strtolower($file->getClientOriginalExtension());
|
||||||
|
if (! in_array($ext, ['xml', 'txt'])) {
|
||||||
|
return back()->withErrors(['xmlImport' => 'Il file deve essere .xml o .txt']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($file->getRealPath());
|
||||||
|
if (! $content) {
|
||||||
|
return back()->withErrors(['xmlImport' => 'File vuoto o non leggibile.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$exchange = new XmlExchange();
|
||||||
|
$result = $exchange->importXmlFromContent($content);
|
||||||
|
|
||||||
|
if (isset($result['error'])) {
|
||||||
|
return back()->withErrors(['xmlImport' => $result['error']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stats = $result['stats'];
|
||||||
|
$issues = $result['issues'];
|
||||||
|
|
||||||
|
$message = 'Import XML completato con successo.';
|
||||||
|
if (($stats['duplicate_territori'] ?? 0) > 0 || ($stats['assegnazioni_saltate'] ?? 0) > 0) {
|
||||||
|
$message .= ' Territori duplicati saltati: ' . $stats['duplicate_territori'] . '. Assegnazioni saltate: ' . $stats['assegnazioni_saltate'] . '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('xml.exchange')
|
||||||
|
->with('success', $message)
|
||||||
|
->with('importStats', $stats)
|
||||||
|
->with('importIssues', $issues);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Http/Controllers/ShortPdfLinkController.php
Normal file
22
app/Http/Controllers/ShortPdfLinkController.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Assegnazione;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
|
||||||
|
class ShortPdfLinkController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke(string $code): RedirectResponse
|
||||||
|
{
|
||||||
|
$assignment = Assegnazione::where('pdf_access_code', $code)->firstOrFail();
|
||||||
|
|
||||||
|
abort_unless($assignment->is_aperta, 403);
|
||||||
|
abort_unless($assignment->territorio?->pdf_path, 404);
|
||||||
|
|
||||||
|
return redirect()->route('assignments.pdf.viewer', [
|
||||||
|
'assignment' => $assignment->id,
|
||||||
|
'code' => $code,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,12 +108,24 @@ class Home extends Component
|
|||||||
->count('territorio_id');
|
->count('territorio_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monthly average
|
// Monthly average (territories/month this year)
|
||||||
$mediaPercorrenzaMensile = 0;
|
$mediaPercorrenzaMensile = 0;
|
||||||
if ($annoCorrente && $annoCorrente->mesi_trascorsi > 0) {
|
if ($annoCorrente && $annoCorrente->mesi_trascorsi > 0) {
|
||||||
$mediaPercorrenzaMensile = round($territoriPercorsi / $annoCorrente->mesi_trascorsi, 1);
|
$mediaPercorrenzaMensile = round($territoriPercorsi / $annoCorrente->mesi_trascorsi, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Average assignment duration in months (current year only, matches old app "Media Percorrenza Congregazione")
|
||||||
|
$avgGiorni = null;
|
||||||
|
if ($annoCorrente) {
|
||||||
|
$avgGiorni = Assegnazione::where('anno_teocratico_id', $annoCorrente->id)
|
||||||
|
->whereNotNull('returned_at')
|
||||||
|
->whereRaw('YEAR(assigned_at) >= 1900')
|
||||||
|
->whereRaw('DATEDIFF(returned_at, assigned_at) > 0')
|
||||||
|
->selectRaw('AVG(DATEDIFF(returned_at, assigned_at)) as media_giorni')
|
||||||
|
->value('media_giorni');
|
||||||
|
}
|
||||||
|
$mediaDurataPercorrenzaMesi = $avgGiorni ? round($avgGiorni / 30.44, 1) : 0;
|
||||||
|
|
||||||
// Campaign stats
|
// Campaign stats
|
||||||
$campagnaStats = null;
|
$campagnaStats = null;
|
||||||
if ($campagnaAttiva) {
|
if ($campagnaAttiva) {
|
||||||
@@ -178,6 +190,7 @@ class Home extends Component
|
|||||||
'totInReparto' => $totInReparto,
|
'totInReparto' => $totInReparto,
|
||||||
'territoriPercorsi' => $territoriPercorsi,
|
'territoriPercorsi' => $territoriPercorsi,
|
||||||
'mediaPercorrenzaMensile' => $mediaPercorrenzaMensile,
|
'mediaPercorrenzaMensile' => $mediaPercorrenzaMensile,
|
||||||
|
'mediaDurataPercorrenzaMesi' => $mediaDurataPercorrenzaMesi,
|
||||||
'campagnaStats' => $campagnaStats,
|
'campagnaStats' => $campagnaStats,
|
||||||
'homeLimit' => $homeLimit,
|
'homeLimit' => $homeLimit,
|
||||||
'territoriDaAssegnare' => $territoriDaAssegnare,
|
'territoriDaAssegnare' => $territoriDaAssegnare,
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ class XmlExchange extends Component
|
|||||||
{
|
{
|
||||||
use WithFileUploads;
|
use WithFileUploads;
|
||||||
|
|
||||||
public $sqlDump;
|
|
||||||
public $xmlImport;
|
|
||||||
public array $importStats = [];
|
public array $importStats = [];
|
||||||
public array $importIssues = [];
|
public array $importIssues = [];
|
||||||
public array $pdfFolder = [];
|
public array $pdfFolder = [];
|
||||||
@@ -46,46 +44,135 @@ class XmlExchange extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function convertLegacySqlToXml()
|
public function importTerritoryPdfFolder(): void
|
||||||
{
|
{
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'sqlDump' => ['required', 'file', 'mimes:sql,txt'],
|
'pdfFolder' => ['required', 'array', 'min:1'],
|
||||||
|
'pdfFolder.*' => ['file', 'mimes:pdf', 'max:10240'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$content = file_get_contents($this->sqlDump->getRealPath());
|
$importId = (string) Str::uuid();
|
||||||
$dataset = $this->legacySqlToDataset($content ?: '');
|
$storedFiles = [];
|
||||||
$xml = $this->datasetToXml($dataset, 'legacy-sql-conversion');
|
|
||||||
|
foreach ($this->pdfFolder as $index => $file) {
|
||||||
|
$originalName = $file->getClientOriginalName();
|
||||||
|
$safeName = Str::slug(pathinfo($originalName, PATHINFO_FILENAME));
|
||||||
|
$extension = strtolower($file->getClientOriginalExtension() ?: 'pdf');
|
||||||
|
$storedPath = $file->storeAs(
|
||||||
|
'bulk-territori-imports/' . $importId,
|
||||||
|
str_pad((string) $index, 4, '0', STR_PAD_LEFT) . '-' . $safeName . '.' . $extension,
|
||||||
|
'local'
|
||||||
|
);
|
||||||
|
|
||||||
|
$storedFiles[] = [
|
||||||
|
'original_name' => $originalName,
|
||||||
|
'stored_path' => $storedPath,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdfFolder = [];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshPdfImportStatus(): void
|
||||||
|
{
|
||||||
|
if (! $this->currentPdfImportId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = app(TerritorioPdfImportState::class)->get($this->currentPdfImportId);
|
||||||
|
|
||||||
|
if (! $state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdfImportStatus = $state['status'] ?? 'idle';
|
||||||
|
$this->pdfImportStats = $state['stats'] ?? [];
|
||||||
|
$this->pdfImportLogs = $state['logs'] ?? [];
|
||||||
|
$this->pdfImportIssues = $state['issues'] ?? [];
|
||||||
|
$this->pdfImportLogText = implode(PHP_EOL, $this->pdfImportLogs);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function dispatchPdfImport(string $importId, array $storedFiles, string $initialLog): void
|
||||||
|
{
|
||||||
|
$state = app(TerritorioPdfImportDispatcher::class)
|
||||||
|
->dispatchStoredFiles($importId, $storedFiles, auth()->id(), $initialLog);
|
||||||
|
|
||||||
|
$this->currentPdfImportId = $importId;
|
||||||
|
$this->pdfImportStatus = $state['status'] ?? 'queued';
|
||||||
|
$this->pdfImportStats = $state['stats'] ?? [];
|
||||||
|
$this->pdfImportLogs = $state['logs'] ?? [];
|
||||||
|
$this->pdfImportIssues = $state['issues'] ?? [];
|
||||||
|
$this->refreshPdfImportStatus();
|
||||||
|
|
||||||
|
session()->flash('success', 'Import PDF avviato in background. I log si aggiorneranno automaticamente.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function downloadImportLogPdf()
|
||||||
|
{
|
||||||
|
if (empty($this->importStats)) {
|
||||||
|
session()->flash('error', 'Nessun log da scaricare. Esegui prima un import XML.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stats = $this->importStats;
|
||||||
|
$issues = $this->importIssues;
|
||||||
|
$generatedAt = now()->format('d/m/Y H:i:s');
|
||||||
|
|
||||||
|
$html = view('pdf.import-log', compact('stats', 'issues', 'generatedAt'))->render();
|
||||||
|
|
||||||
|
return response()->streamDownload(function () use ($html) {
|
||||||
|
echo Pdf::loadHTML($html)
|
||||||
|
->setPaper('a4', 'portrait')
|
||||||
|
->output();
|
||||||
|
}, 'import-log-' . now()->format('Ymd-His') . '.pdf', ['Content-Type' => 'application/pdf']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportCurrentAsXml()
|
||||||
|
{
|
||||||
|
$dataset = $this->currentDataset();
|
||||||
|
$xml = $this->datasetToXml($dataset, 'current-app-export');
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($xml) {
|
return response()->streamDownload(function () use ($xml) {
|
||||||
echo $xml;
|
echo $xml;
|
||||||
}, 'termanager-conversion.xml', ['Content-Type' => 'application/xml; charset=UTF-8']);
|
}, 'termanager-export.xml', ['Content-Type' => 'application/xml; charset=UTF-8']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function importXmlIntoApp(): void
|
public function render()
|
||||||
|
{
|
||||||
|
if (session()->has('importStats')) {
|
||||||
|
$this->importStats = session('importStats');
|
||||||
|
}
|
||||||
|
if (session()->has('importIssues')) {
|
||||||
|
$this->importIssues = session('importIssues');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('livewire.settings.xml-exchange');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function legacySqlToDatasetPublic(string $sql): array
|
||||||
|
{
|
||||||
|
return $this->legacySqlToDataset($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function datasetToXmlPublic(array $dataset, string $source): string
|
||||||
|
{
|
||||||
|
return $this->datasetToXml($dataset, $source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importXmlFromContent(string $content): array
|
||||||
{
|
{
|
||||||
$this->importStats = [];
|
$this->importStats = [];
|
||||||
$this->importIssues = [];
|
$this->importIssues = [];
|
||||||
|
|
||||||
$this->validate([
|
|
||||||
'xmlImport' => ['required', 'file', 'mimes:xml,txt'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$content = file_get_contents($this->xmlImport->getRealPath());
|
|
||||||
if (! $content) {
|
|
||||||
$this->addError('xmlImport', 'File XML non valido.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$xml = @simplexml_load_string($content);
|
$xml = @simplexml_load_string($content);
|
||||||
if (! $xml) {
|
if (! $xml) {
|
||||||
$this->addError('xmlImport', 'Impossibile leggere il file XML.');
|
return ['error' => 'Impossibile leggere il file XML.'];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$actorId = auth()->id() ?? User::query()->value('id');
|
$actorId = auth()->id() ?? User::query()->value('id');
|
||||||
if (! $actorId) {
|
if (! $actorId) {
|
||||||
$this->addError('xmlImport', 'Serve almeno un utente nel sistema per importare i dati.');
|
return ['error' => 'Serve almeno un utente nel sistema per importare i dati.'];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$stats = [
|
$stats = [
|
||||||
@@ -268,113 +355,10 @@ class XmlExchange extends Component
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->importStats = $stats;
|
return [
|
||||||
|
'stats' => $stats,
|
||||||
$message = 'Import XML completato con successo.';
|
'issues' => $this->importIssues,
|
||||||
if ($stats['duplicate_territori'] > 0 || $stats['assegnazioni_saltate'] > 0) {
|
];
|
||||||
$message .= ' Territori duplicati saltati: ' . $stats['duplicate_territori'] . '. Assegnazioni saltate: ' . $stats['assegnazioni_saltate'] . '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
session()->flash('success', $message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function importTerritoryPdfFolder(): void
|
|
||||||
{
|
|
||||||
$this->validate([
|
|
||||||
'pdfFolder' => ['required', 'array', 'min:1'],
|
|
||||||
'pdfFolder.*' => ['file', 'mimes:pdf', 'max:10240'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$importId = (string) Str::uuid();
|
|
||||||
$storedFiles = [];
|
|
||||||
|
|
||||||
foreach ($this->pdfFolder as $index => $file) {
|
|
||||||
$originalName = $file->getClientOriginalName();
|
|
||||||
$safeName = Str::slug(pathinfo($originalName, PATHINFO_FILENAME));
|
|
||||||
$extension = strtolower($file->getClientOriginalExtension() ?: 'pdf');
|
|
||||||
$storedPath = $file->storeAs(
|
|
||||||
'bulk-territori-imports/' . $importId,
|
|
||||||
str_pad((string) $index, 4, '0', STR_PAD_LEFT) . '-' . $safeName . '.' . $extension,
|
|
||||||
'local'
|
|
||||||
);
|
|
||||||
|
|
||||||
$storedFiles[] = [
|
|
||||||
'original_name' => $originalName,
|
|
||||||
'stored_path' => $storedPath,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->pdfFolder = [];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function refreshPdfImportStatus(): void
|
|
||||||
{
|
|
||||||
if (! $this->currentPdfImportId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$state = app(TerritorioPdfImportState::class)->get($this->currentPdfImportId);
|
|
||||||
|
|
||||||
if (! $state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->pdfImportStatus = $state['status'] ?? 'idle';
|
|
||||||
$this->pdfImportStats = $state['stats'] ?? [];
|
|
||||||
$this->pdfImportLogs = $state['logs'] ?? [];
|
|
||||||
$this->pdfImportIssues = $state['issues'] ?? [];
|
|
||||||
$this->pdfImportLogText = implode(PHP_EOL, $this->pdfImportLogs);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function dispatchPdfImport(string $importId, array $storedFiles, string $initialLog): void
|
|
||||||
{
|
|
||||||
$state = app(TerritorioPdfImportDispatcher::class)
|
|
||||||
->dispatchStoredFiles($importId, $storedFiles, auth()->id(), $initialLog);
|
|
||||||
|
|
||||||
$this->currentPdfImportId = $importId;
|
|
||||||
$this->pdfImportStatus = $state['status'] ?? 'queued';
|
|
||||||
$this->pdfImportStats = $state['stats'] ?? [];
|
|
||||||
$this->pdfImportLogs = $state['logs'] ?? [];
|
|
||||||
$this->pdfImportIssues = $state['issues'] ?? [];
|
|
||||||
$this->refreshPdfImportStatus();
|
|
||||||
|
|
||||||
session()->flash('success', 'Import PDF avviato in background. I log si aggiorneranno automaticamente.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function downloadImportLogPdf()
|
|
||||||
{
|
|
||||||
if (empty($this->importStats)) {
|
|
||||||
session()->flash('error', 'Nessun log da scaricare. Esegui prima un import XML.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stats = $this->importStats;
|
|
||||||
$issues = $this->importIssues;
|
|
||||||
$generatedAt = now()->format('d/m/Y H:i:s');
|
|
||||||
|
|
||||||
$html = view('pdf.import-log', compact('stats', 'issues', 'generatedAt'))->render();
|
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($html) {
|
|
||||||
echo Pdf::loadHTML($html)
|
|
||||||
->setPaper('a4', 'portrait')
|
|
||||||
->output();
|
|
||||||
}, 'import-log-' . now()->format('Ymd-His') . '.pdf', ['Content-Type' => 'application/pdf']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function exportCurrentAsXml()
|
|
||||||
{
|
|
||||||
$dataset = $this->currentDataset();
|
|
||||||
$xml = $this->datasetToXml($dataset, 'current-app-export');
|
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($xml) {
|
|
||||||
echo $xml;
|
|
||||||
}, 'termanager-export.xml', ['Content-Type' => 'application/xml; charset=UTF-8']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.settings.xml-exchange');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function currentDataset(): array
|
private function currentDataset(): array
|
||||||
@@ -512,7 +496,7 @@ class XmlExchange extends Component
|
|||||||
private function extractInsertRows(string $sql): array
|
private function extractInsertRows(string $sql): array
|
||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
preg_match_all('/INSERT INTO `([^`]+)` VALUES\s*(.+?);/s', $sql, $matches, PREG_SET_ORDER);
|
preg_match_all('/INSERT INTO `([^`]+)` VALUES\s*(.+);/m', $sql, $matches, PREG_SET_ORDER);
|
||||||
|
|
||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
$table = $match[1];
|
$table = $match[1];
|
||||||
@@ -545,7 +529,7 @@ class XmlExchange extends Component
|
|||||||
$escape = false;
|
$escape = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($ch === '\\\\') {
|
if ($ch === '\\') {
|
||||||
$escape = true;
|
$escape = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -603,7 +587,7 @@ class XmlExchange extends Component
|
|||||||
$escape = false;
|
$escape = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($ch === '\\\\') {
|
if ($ch === '\\') {
|
||||||
$escape = true;
|
$escape = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -645,7 +629,8 @@ class XmlExchange extends Component
|
|||||||
if (str_starts_with($raw, "'") && str_ends_with($raw, "'")) {
|
if (str_starts_with($raw, "'") && str_ends_with($raw, "'")) {
|
||||||
$v = substr($raw, 1, -1);
|
$v = substr($raw, 1, -1);
|
||||||
$v = str_replace(["\\\\", "\\'", '\\"', '\\n', '\\r', '\\t'], ['\\', "'", '"', "\n", "\r", "\t"], $v);
|
$v = str_replace(["\\\\", "\\'", '\\"', '\\n', '\\r', '\\t'], ['\\', "'", '"', "\n", "\r", "\t"], $v);
|
||||||
return html_entity_decode($v, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
$v = html_entity_decode($v, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
|
return $this->normalizeUnicodeQuotes($v);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_numeric($raw)) {
|
if (is_numeric($raw)) {
|
||||||
@@ -655,6 +640,15 @@ class XmlExchange extends Component
|
|||||||
return $raw;
|
return $raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function normalizeUnicodeQuotes(string $value): string
|
||||||
|
{
|
||||||
|
return str_replace(
|
||||||
|
["\u{2018}", "\u{2019}", "\u{2032}", "\u{2035}", "\u{201C}", "\u{201D}", "\u{201E}", "\u{2033}", "\u{2036}"],
|
||||||
|
["'", "'", "'", "'", '"', '"', '"', '"', '"'],
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function datesFromLegacyAnnoLabel(string $label): array
|
private function datesFromLegacyAnnoLabel(string $label): array
|
||||||
{
|
{
|
||||||
if (preg_match('/(\d{4})\s*-\s*(\d{4})/', $label, $m)) {
|
if (preg_match('/(\d{4})\s*-\s*(\d{4})/', $label, $m)) {
|
||||||
@@ -709,7 +703,7 @@ class XmlExchange extends Component
|
|||||||
|
|
||||||
$settings = $xml->addChild('settings');
|
$settings = $xml->addChild('settings');
|
||||||
foreach ($dataset['settings'] as $key => $value) {
|
foreach ($dataset['settings'] as $key => $value) {
|
||||||
$settings->addChild($key, htmlspecialchars((string) $value, ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($settings, $key, (string) $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
$zonesNode = $xml->addChild('zones');
|
$zonesNode = $xml->addChild('zones');
|
||||||
@@ -718,7 +712,7 @@ class XmlExchange extends Component
|
|||||||
if (isset($zone['legacy_id'])) {
|
if (isset($zone['legacy_id'])) {
|
||||||
$node->addAttribute('legacy_id', (string) $zone['legacy_id']);
|
$node->addAttribute('legacy_id', (string) $zone['legacy_id']);
|
||||||
}
|
}
|
||||||
$node->addChild('nome', htmlspecialchars((string) ($zone['nome'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'nome', (string) ($zone['nome'] ?? ''));
|
||||||
$node->addChild('attivo', (string) ((int) ($zone['attivo'] ?? 1)));
|
$node->addChild('attivo', (string) ((int) ($zone['attivo'] ?? 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,7 +722,7 @@ class XmlExchange extends Component
|
|||||||
if (isset($tipologia['legacy_id'])) {
|
if (isset($tipologia['legacy_id'])) {
|
||||||
$node->addAttribute('legacy_id', (string) $tipologia['legacy_id']);
|
$node->addAttribute('legacy_id', (string) $tipologia['legacy_id']);
|
||||||
}
|
}
|
||||||
$node->addChild('nome', htmlspecialchars((string) ($tipologia['nome'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'nome', (string) ($tipologia['nome'] ?? ''));
|
||||||
$node->addChild('attivo', (string) ((int) ($tipologia['attivo'] ?? 1)));
|
$node->addChild('attivo', (string) ((int) ($tipologia['attivo'] ?? 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -738,8 +732,8 @@ class XmlExchange extends Component
|
|||||||
if (isset($proclamatore['legacy_id'])) {
|
if (isset($proclamatore['legacy_id'])) {
|
||||||
$node->addAttribute('legacy_id', (string) $proclamatore['legacy_id']);
|
$node->addAttribute('legacy_id', (string) $proclamatore['legacy_id']);
|
||||||
}
|
}
|
||||||
$node->addChild('nome', htmlspecialchars((string) ($proclamatore['nome'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'nome', (string) ($proclamatore['nome'] ?? ''));
|
||||||
$node->addChild('cognome', htmlspecialchars((string) ($proclamatore['cognome'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'cognome', (string) ($proclamatore['cognome'] ?? ''));
|
||||||
$node->addChild('attivo', (string) ((int) ($proclamatore['attivo'] ?? 1)));
|
$node->addChild('attivo', (string) ((int) ($proclamatore['attivo'] ?? 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,11 +743,11 @@ class XmlExchange extends Component
|
|||||||
if (isset($territorio['legacy_id'])) {
|
if (isset($territorio['legacy_id'])) {
|
||||||
$node->addAttribute('legacy_id', (string) $territorio['legacy_id']);
|
$node->addAttribute('legacy_id', (string) $territorio['legacy_id']);
|
||||||
}
|
}
|
||||||
$node->addChild('numero', htmlspecialchars((string) ($territorio['numero'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'numero', (string) ($territorio['numero'] ?? ''));
|
||||||
$node->addChild('legacy_zona_id', (string) ($territorio['legacy_zona_id'] ?? ($territorio['zona_id'] ?? '')));
|
$node->addChild('legacy_zona_id', (string) ($territorio['legacy_zona_id'] ?? ($territorio['zona_id'] ?? '')));
|
||||||
$node->addChild('legacy_tipologia_id', (string) ($territorio['legacy_tipologia_id'] ?? ($territorio['tipologia_id'] ?? '')));
|
$node->addChild('legacy_tipologia_id', (string) ($territorio['legacy_tipologia_id'] ?? ($territorio['tipologia_id'] ?? '')));
|
||||||
$node->addChild('confini', htmlspecialchars((string) ($territorio['confini'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'confini', (string) ($territorio['confini'] ?? ''));
|
||||||
$node->addChild('note', htmlspecialchars((string) ($territorio['note'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'note', (string) ($territorio['note'] ?? ''));
|
||||||
$node->addChild('attivo', (string) ((int) ($territorio['attivo'] ?? 1)));
|
$node->addChild('attivo', (string) ((int) ($territorio['attivo'] ?? 1)));
|
||||||
$node->addChild('prioritario', (string) ((int) ($territorio['prioritario'] ?? 0)));
|
$node->addChild('prioritario', (string) ((int) ($territorio['prioritario'] ?? 0)));
|
||||||
}
|
}
|
||||||
@@ -764,7 +758,7 @@ class XmlExchange extends Component
|
|||||||
if (isset($anno['legacy_id'])) {
|
if (isset($anno['legacy_id'])) {
|
||||||
$node->addAttribute('legacy_id', (string) $anno['legacy_id']);
|
$node->addAttribute('legacy_id', (string) $anno['legacy_id']);
|
||||||
}
|
}
|
||||||
$node->addChild('label', htmlspecialchars((string) ($anno['label'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'label', (string) ($anno['label'] ?? ''));
|
||||||
$node->addChild('start_date', (string) ($anno['start_date'] ?? ''));
|
$node->addChild('start_date', (string) ($anno['start_date'] ?? ''));
|
||||||
$node->addChild('end_date', (string) ($anno['end_date'] ?? ''));
|
$node->addChild('end_date', (string) ($anno['end_date'] ?? ''));
|
||||||
}
|
}
|
||||||
@@ -775,7 +769,7 @@ class XmlExchange extends Component
|
|||||||
if (isset($campagna['legacy_id'])) {
|
if (isset($campagna['legacy_id'])) {
|
||||||
$node->addAttribute('legacy_id', (string) $campagna['legacy_id']);
|
$node->addAttribute('legacy_id', (string) $campagna['legacy_id']);
|
||||||
}
|
}
|
||||||
$node->addChild('descrizione', htmlspecialchars((string) ($campagna['descrizione'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'descrizione', (string) ($campagna['descrizione'] ?? ''));
|
||||||
$node->addChild('start_date', (string) ($campagna['start_date'] ?? ''));
|
$node->addChild('start_date', (string) ($campagna['start_date'] ?? ''));
|
||||||
$node->addChild('end_date', (string) ($campagna['end_date'] ?? ''));
|
$node->addChild('end_date', (string) ($campagna['end_date'] ?? ''));
|
||||||
}
|
}
|
||||||
@@ -793,9 +787,18 @@ class XmlExchange extends Component
|
|||||||
$node->addChild('assigned_at', (string) ($assegnazione['assigned_at'] ?? ''));
|
$node->addChild('assigned_at', (string) ($assegnazione['assigned_at'] ?? ''));
|
||||||
$node->addChild('returned_at', (string) ($assegnazione['returned_at'] ?? ''));
|
$node->addChild('returned_at', (string) ($assegnazione['returned_at'] ?? ''));
|
||||||
$node->addChild('counted_in_campaign', (string) ((int) ($assegnazione['counted_in_campaign'] ?? 0)));
|
$node->addChild('counted_in_campaign', (string) ((int) ($assegnazione['counted_in_campaign'] ?? 0)));
|
||||||
$node->addChild('note', htmlspecialchars((string) ($assegnazione['note'] ?? ''), ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
$this->addXmlText($node, 'note', (string) ($assegnazione['note'] ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $xml->asXML() ?: '';
|
return $xml->asXML() ?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function addXmlText(\SimpleXMLElement $parent, string $name, string $value): \SimpleXMLElement
|
||||||
|
{
|
||||||
|
$child = $parent->addChild($name);
|
||||||
|
$dom = dom_import_simplexml($child);
|
||||||
|
$dom->textContent = $value;
|
||||||
|
|
||||||
|
return $child;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\URL;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Assegnazione extends Model
|
class Assegnazione extends Model
|
||||||
@@ -103,18 +102,19 @@ class Assegnazione extends Model
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$months = max(1, (int) Setting::getValue('assignment_link_ttl_hours', 1));
|
return route('assignments.pdf.viewer', [
|
||||||
|
'assignment' => $this->id,
|
||||||
|
'code' => $this->ensurePdfAccessCode(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$url = URL::temporarySignedRoute(
|
public function shortPdfUrl(): ?string
|
||||||
'assignments.pdf.viewer',
|
{
|
||||||
now()->addMonths($months),
|
if (! $this->is_aperta || ! $this->territorio?->pdf_path) {
|
||||||
[
|
return null;
|
||||||
'assignment' => $this->id,
|
}
|
||||||
'code' => $this->ensurePdfAccessCode(),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return $url;
|
return route('assignments.pdf.short', ['code' => $this->ensurePdfAccessCode()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Scopes ─────────────────────────────────────────────────
|
// ─── Scopes ─────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -21,6 +21,18 @@ server {
|
|||||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_hide_header X-Powered-By;
|
fastcgi_hide_header X-Powered-By;
|
||||||
|
|
||||||
|
# Forward proxy headers so Laravel can validate signed URLs behind reverse proxy
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_HOST $http_host;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_PORT $http_x_forwarded_port;
|
||||||
|
|
||||||
|
# Trust external proxy proto, or default to https for signed URL validation
|
||||||
|
set $fwd_proto $http_x_forwarded_proto;
|
||||||
|
if ($fwd_proto = "") {
|
||||||
|
set $fwd_proto "https";
|
||||||
|
}
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_PROTO $fwd_proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ /\.(?!well-known).* {
|
location ~ /\.(?!well-known).* {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
</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->temporaryPdfViewerUrl())
|
@php($pdfUrl = $a->shortPdfUrl())
|
||||||
<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">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-medium text-gray-500 uppercase">Percorsi (anno)</p>
|
<p class="text-xs font-medium text-gray-500 uppercase">Percorsi (anno)</p>
|
||||||
<p class="mt-1 text-3xl font-bold" style="color:#22c55e">{{ $territoriPercorsi }}</p>
|
<p class="mt-1 text-3xl font-bold" style="color:#22c55e">{{ $territoriPercorsi }}</p>
|
||||||
<p class="text-xs text-gray-500">media {{ $mediaPercorrenzaMensile }}/mese</p>
|
<p class="text-xs text-gray-500">durata media {{ $mediaDurataPercorrenzaMesi }} mesi</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-10 w-10 rounded-lg flex items-center justify-center" style="background:#dcfce7">
|
<div class="h-10 w-10 rounded-lg flex items-center justify-center" style="background:#dcfce7">
|
||||||
<svg class="h-5 w-5" style="color:#22c55e" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
<svg class="h-5 w-5" style="color:#22c55e" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<tbody class="divide-y divide-gray-100">
|
<tbody class="divide-y divide-gray-100">
|
||||||
@forelse($assegnazioni as $a)
|
@forelse($assegnazioni as $a)
|
||||||
<tr class="hover:bg-indigo-50/30 transition-colors">
|
<tr class="hover:bg-indigo-50/30 transition-colors">
|
||||||
@php($temporaryPdfUrl = !$a->returned_at ? $a->temporaryPdfViewerUrl() : null)
|
@php($temporaryPdfUrl = !$a->returned_at ? $a->shortPdfUrl() : null)
|
||||||
<td class="px-3 py-2">
|
<td class="px-3 py-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@if($a->territorio?->thumbnail_path)
|
@if($a->territorio?->thumbnail_path)
|
||||||
|
|||||||
@@ -246,47 +246,46 @@
|
|||||||
<h2 class="text-lg font-semibold text-gray-900">Conversione dump SQL legacy</h2>
|
<h2 class="text-lg font-semibold text-gray-900">Conversione dump SQL legacy</h2>
|
||||||
<p class="text-xs text-gray-500">Carica il file SQL (es. dump-termanager-202604071526.sql) e genera un XML compatibile con l'import dell'app.</p>
|
<p class="text-xs text-gray-500">Carica il file SQL (es. dump-termanager-202604071526.sql) e genera un XML compatibile con l'import dell'app.</p>
|
||||||
|
|
||||||
<div>
|
<form action="{{ route('xml.convert-sql') }}" method="POST" enctype="multipart/form-data">
|
||||||
<input wire:model="sqlDump" type="file" accept=".sql,.txt,.SQL,.TXT" class="block w-full text-sm text-gray-600 file:mr-3 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
|
@csrf
|
||||||
@error('sqlDump') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
<div>
|
||||||
</div>
|
<input name="sqlDump" type="file" accept=".sql,.txt,.SQL,.TXT" class="block w-full text-sm text-gray-600 file:mr-3 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
|
||||||
|
@error('sqlDump') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2 mt-4">
|
||||||
<button
|
<button
|
||||||
wire:click="convertLegacySqlToXml"
|
type="submit"
|
||||||
type="button"
|
class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 transition border border-indigo-700"
|
||||||
class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 transition border border-indigo-700"
|
style="display:inline-flex;align-items:center;justify-content:center;min-height:40px;background:#4f46e5;color:#fff;border:1px solid #3730a3;border-radius:10px;padding:10px 14px;"
|
||||||
style="display:inline-flex;align-items:center;justify-content:center;min-height:40px;background:#4f46e5;color:#fff;border:1px solid #3730a3;border-radius:10px;padding:10px 14px;"
|
>
|
||||||
>
|
Converti in XML
|
||||||
Converti in XML
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-4">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900">Import XML nell'app</h2>
|
<h2 class="text-lg font-semibold text-gray-900">Import XML nell'app</h2>
|
||||||
<p class="text-xs text-gray-500">Importa un XML nel formato TerManager2. L'import sostituisce i dati gestionali (zone, tipologie, proclamatori, territori, anni, campagne, assegnazioni e impostazioni).</p>
|
<p class="text-xs text-gray-500">Importa un XML nel formato TerManager2. L'import sostituisce i dati gestionali (zone, tipologie, proclamatori, territori, anni, campagne, assegnazioni e impostazioni).</p>
|
||||||
|
|
||||||
<div wire:loading wire:target="importXmlIntoApp" style="padding:10px 12px;border-radius:10px;background:#fffbeb;border:1px solid #f59e0b;color:#92400e;font-size:13px;">
|
<form action="{{ route('xml.import-xml') }}" method="POST" enctype="multipart/form-data" onsubmit="return confirm('Confermi l\'import XML? I dati gestionali correnti verranno sostituiti.');">
|
||||||
Importazione in corso... attendi il completamento.
|
@csrf
|
||||||
</div>
|
<div>
|
||||||
|
<input name="xmlImport" type="file" accept=".xml,.txt,.XML,.TXT" class="block w-full text-sm text-gray-600 file:mr-3 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
|
||||||
|
@error('xmlImport') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="flex gap-2 mt-4">
|
||||||
<input wire:model="xmlImport" type="file" accept=".xml,.txt,.XML,.TXT" class="block w-full text-sm text-gray-600 file:mr-3 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
|
<button
|
||||||
@error('xmlImport') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
|
type="submit"
|
||||||
</div>
|
class="px-4 py-2 text-sm font-medium text-white bg-amber-600 rounded-lg hover:bg-amber-700 transition border border-amber-700"
|
||||||
|
style="display:inline-flex;align-items:center;justify-content:center;min-height:40px;background:#d97706;color:#fff;border:1px solid #92400e;border-radius:10px;padding:10px 14px;"
|
||||||
<div class="flex gap-2">
|
>
|
||||||
<button
|
Importa XML
|
||||||
wire:click="importXmlIntoApp"
|
</button>
|
||||||
type="button"
|
</div>
|
||||||
onclick="if(!confirm('Confermi l\'import XML? I dati gestionali correnti verranno sostituiti.')) event.stopImmediatePropagation();"
|
</form>
|
||||||
class="px-4 py-2 text-sm font-medium text-white bg-amber-600 rounded-lg hover:bg-amber-700 transition border border-amber-700"
|
|
||||||
style="display:inline-flex;align-items:center;justify-content:center;min-height:40px;background:#d97706;color:#fff;border:1px solid #92400e;border-radius:10px;padding:10px 14px;"
|
|
||||||
>
|
|
||||||
Importa XML
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(!empty($importStats))
|
@if(!empty($importStats))
|
||||||
|
|||||||
@@ -128,9 +128,8 @@
|
|||||||
{{ str_replace('_', ' ', ucfirst($stato)) }}
|
{{ str_replace('_', ' ', ucfirst($stato)) }}
|
||||||
</span>
|
</span>
|
||||||
@if($territorio->is_prioritario)
|
@if($territorio->is_prioritario)
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-800 ml-1"
|
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-800 ml-1">
|
||||||
title="{{ $territorio->prioritario ? 'Prioritario (manuale)' : 'Prioritario (giacenza)' }}">
|
★ Prioritario
|
||||||
★ {{ $territorio->prioritario ? 'Man.' : 'Auto' }}
|
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($activeAssignment)
|
@if($activeAssignment)
|
||||||
@php($temporaryPdfUrl = $activeAssignment->temporaryPdfViewerUrl())
|
@php($temporaryPdfUrl = $activeAssignment->shortPdfUrl())
|
||||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6" x-data="{ copied: false }">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6" x-data="{ copied: false }">
|
||||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
46
resources/views/vendor/pagination/bootstrap-4.blade.php
vendored
Normal file
46
resources/views/vendor/pagination/bootstrap-4.blade.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav>
|
||||||
|
<ul class="pagination">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
|
||||||
|
<span class="page-link" aria-hidden="true">‹</span>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">‹</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Pagination Elements --}}
|
||||||
|
@foreach ($elements as $element)
|
||||||
|
{{-- "Three Dots" Separator --}}
|
||||||
|
@if (is_string($element))
|
||||||
|
<li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Array Of Links --}}
|
||||||
|
@if (is_array($element))
|
||||||
|
@foreach ($element as $page => $url)
|
||||||
|
@if ($page == $paginator->currentPage())
|
||||||
|
<li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
|
||||||
|
@else
|
||||||
|
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">›</a>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
|
||||||
|
<span class="page-link" aria-hidden="true">›</span>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
88
resources/views/vendor/pagination/bootstrap-5.blade.php
vendored
Normal file
88
resources/views/vendor/pagination/bootstrap-5.blade.php
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav class="d-flex justify-items-center justify-content-between">
|
||||||
|
<div class="d-flex justify-content-between flex-fill d-sm-none">
|
||||||
|
<ul class="pagination">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<li class="page-item disabled" aria-disabled="true">
|
||||||
|
<span class="page-link">@lang('pagination.previous')</span>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item disabled" aria-disabled="true">
|
||||||
|
<span class="page-link">@lang('pagination.next')</span>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-none flex-sm-fill d-sm-flex align-items-sm-center justify-content-sm-between">
|
||||||
|
<div>
|
||||||
|
<p class="small text-muted">
|
||||||
|
{!! __('Showing') !!}
|
||||||
|
<span class="fw-semibold">{{ $paginator->firstItem() }}</span>
|
||||||
|
{!! __('to') !!}
|
||||||
|
<span class="fw-semibold">{{ $paginator->lastItem() }}</span>
|
||||||
|
{!! __('of') !!}
|
||||||
|
<span class="fw-semibold">{{ $paginator->total() }}</span>
|
||||||
|
{!! __('results') !!}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ul class="pagination">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
|
||||||
|
<span class="page-link" aria-hidden="true">‹</span>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">‹</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Pagination Elements --}}
|
||||||
|
@foreach ($elements as $element)
|
||||||
|
{{-- "Three Dots" Separator --}}
|
||||||
|
@if (is_string($element))
|
||||||
|
<li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Array Of Links --}}
|
||||||
|
@if (is_array($element))
|
||||||
|
@foreach ($element as $page => $url)
|
||||||
|
@if ($page == $paginator->currentPage())
|
||||||
|
<li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
|
||||||
|
@else
|
||||||
|
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">›</a>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
|
||||||
|
<span class="page-link" aria-hidden="true">›</span>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
46
resources/views/vendor/pagination/default.blade.php
vendored
Normal file
46
resources/views/vendor/pagination/default.blade.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav>
|
||||||
|
<ul class="pagination">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<li class="disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
|
||||||
|
<span aria-hidden="true">‹</span>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li>
|
||||||
|
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">‹</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Pagination Elements --}}
|
||||||
|
@foreach ($elements as $element)
|
||||||
|
{{-- "Three Dots" Separator --}}
|
||||||
|
@if (is_string($element))
|
||||||
|
<li class="disabled" aria-disabled="true"><span>{{ $element }}</span></li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Array Of Links --}}
|
||||||
|
@if (is_array($element))
|
||||||
|
@foreach ($element as $page => $url)
|
||||||
|
@if ($page == $paginator->currentPage())
|
||||||
|
<li class="active" aria-current="page"><span>{{ $page }}</span></li>
|
||||||
|
@else
|
||||||
|
<li><a href="{{ $url }}">{{ $page }}</a></li>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<li>
|
||||||
|
<a href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">›</a>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
|
||||||
|
<span aria-hidden="true">›</span>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
36
resources/views/vendor/pagination/semantic-ui.blade.php
vendored
Normal file
36
resources/views/vendor/pagination/semantic-ui.blade.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<div class="ui pagination menu" role="navigation">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<a class="icon item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')"> <i class="left chevron icon"></i> </a>
|
||||||
|
@else
|
||||||
|
<a class="icon item" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')"> <i class="left chevron icon"></i> </a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Pagination Elements --}}
|
||||||
|
@foreach ($elements as $element)
|
||||||
|
{{-- "Three Dots" Separator --}}
|
||||||
|
@if (is_string($element))
|
||||||
|
<a class="icon item disabled" aria-disabled="true">{{ $element }}</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Array Of Links --}}
|
||||||
|
@if (is_array($element))
|
||||||
|
@foreach ($element as $page => $url)
|
||||||
|
@if ($page == $paginator->currentPage())
|
||||||
|
<a class="item active" href="{{ $url }}" aria-current="page">{{ $page }}</a>
|
||||||
|
@else
|
||||||
|
<a class="item" href="{{ $url }}">{{ $page }}</a>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<a class="icon item" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')"> <i class="right chevron icon"></i> </a>
|
||||||
|
@else
|
||||||
|
<a class="icon item disabled" aria-disabled="true" aria-label="@lang('pagination.next')"> <i class="right chevron icon"></i> </a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
27
resources/views/vendor/pagination/simple-bootstrap-4.blade.php
vendored
Normal file
27
resources/views/vendor/pagination/simple-bootstrap-4.blade.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav>
|
||||||
|
<ul class="pagination">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<li class="page-item disabled" aria-disabled="true">
|
||||||
|
<span class="page-link">@lang('pagination.previous')</span>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item disabled" aria-disabled="true">
|
||||||
|
<span class="page-link">@lang('pagination.next')</span>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
29
resources/views/vendor/pagination/simple-bootstrap-5.blade.php
vendored
Normal file
29
resources/views/vendor/pagination/simple-bootstrap-5.blade.php
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav role="navigation" aria-label="Pagination Navigation">
|
||||||
|
<ul class="pagination">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<li class="page-item disabled" aria-disabled="true">
|
||||||
|
<span class="page-link">{!! __('pagination.previous') !!}</span>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">
|
||||||
|
{!! __('pagination.previous') !!}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">{!! __('pagination.next') !!}</a>
|
||||||
|
</li>
|
||||||
|
@else
|
||||||
|
<li class="page-item disabled" aria-disabled="true">
|
||||||
|
<span class="page-link">{!! __('pagination.next') !!}</span>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
19
resources/views/vendor/pagination/simple-default.blade.php
vendored
Normal file
19
resources/views/vendor/pagination/simple-default.blade.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav>
|
||||||
|
<ul class="pagination">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<li class="disabled" aria-disabled="true"><span>@lang('pagination.previous')</span></li>
|
||||||
|
@else
|
||||||
|
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a></li>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a></li>
|
||||||
|
@else
|
||||||
|
<li class="disabled" aria-disabled="true"><span>@lang('pagination.next')</span></li>
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
25
resources/views/vendor/pagination/simple-tailwind.blade.php
vendored
Normal file
25
resources/views/vendor/pagination/simple-tailwind.blade.php
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav role="navigation" aria-label="Pagination Navigation" class="flex justify-between">
|
||||||
|
{{-- Previous Page Link --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600">
|
||||||
|
{!! __('pagination.previous') !!}
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:focus:border-blue-700 dark:active:bg-gray-700 dark:active:text-gray-300">
|
||||||
|
{!! __('pagination.previous') !!}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Next Page Link --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<a href="{{ $paginator->nextPageUrl() }}" rel="next" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:focus:border-blue-700 dark:active:bg-gray-700 dark:active:text-gray-300">
|
||||||
|
{!! __('pagination.next') !!}
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600">
|
||||||
|
{!! __('pagination.next') !!}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
131
resources/views/vendor/pagination/tailwind.blade.php
vendored
Normal file
131
resources/views/vendor/pagination/tailwind.blade.php
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
@if ($paginator->hasPages())
|
||||||
|
<nav role="navigation" aria-label="Navigazione pagine"
|
||||||
|
class="flex flex-col sm:flex-row items-center justify-between gap-4 py-2">
|
||||||
|
|
||||||
|
{{-- Info testo --}}
|
||||||
|
<p class="text-sm text-gray-500 order-2 sm:order-1 shrink-0">
|
||||||
|
@if ($paginator->firstItem())
|
||||||
|
Risultati <span class="font-semibold text-gray-700">{{ $paginator->firstItem() }}–{{ $paginator->lastItem() }}</span>
|
||||||
|
di <span class="font-semibold text-gray-700">{{ $paginator->total() }}</span>
|
||||||
|
@else
|
||||||
|
{{ $paginator->count() }} risultati
|
||||||
|
@endif
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{-- Controlli --}}
|
||||||
|
<div class="flex items-center gap-1.5 order-1 sm:order-2">
|
||||||
|
|
||||||
|
{{-- Prev --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<span class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-200 text-gray-300 cursor-default select-none">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $paginator->previousPageUrl() }}" rel="prev"
|
||||||
|
class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-200 bg-white text-gray-500 hover:bg-indigo-50 hover:border-indigo-300 hover:text-indigo-600 transition"
|
||||||
|
aria-label="Pagina precedente">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Numeri pagina (solo desktop) --}}
|
||||||
|
<div class="hidden sm:flex items-center gap-1.5">
|
||||||
|
@foreach ($elements as $element)
|
||||||
|
@if (is_string($element))
|
||||||
|
<span class="inline-flex items-center justify-center w-9 h-9 text-sm text-gray-400 select-none">…</span>
|
||||||
|
@endif
|
||||||
|
@if (is_array($element))
|
||||||
|
@foreach ($element as $page => $url)
|
||||||
|
@if ($page == $paginator->currentPage())
|
||||||
|
<span aria-current="page"
|
||||||
|
class="inline-flex items-center justify-center w-9 h-9 rounded-lg text-sm font-semibold text-white select-none"
|
||||||
|
style="background:#4f46e5">{{ $page }}</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $url }}"
|
||||||
|
class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-200 bg-white text-sm font-medium text-gray-600 hover:bg-indigo-50 hover:border-indigo-300 hover:text-indigo-600 transition"
|
||||||
|
aria-label="Vai a pagina {{ $page }}">{{ $page }}</a>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Indicatore pagina corrente (solo mobile) --}}
|
||||||
|
<span class="sm:hidden inline-flex items-center justify-center px-4 h-9 rounded-lg border border-gray-200 bg-white text-sm font-medium text-gray-600 select-none">
|
||||||
|
{{ $paginator->currentPage() }} / {{ $paginator->lastPage() }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{-- Next --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<a href="{{ $paginator->nextPageUrl() }}" rel="next"
|
||||||
|
class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-200 bg-white text-gray-500 hover:bg-indigo-50 hover:border-indigo-300 hover:text-indigo-600 transition"
|
||||||
|
aria-label="Pagina successiva">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<span class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-200 text-gray-300 cursor-default select-none">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
|
{{-- Prev --}}
|
||||||
|
@if ($paginator->onFirstPage())
|
||||||
|
<span class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-200 text-gray-300 cursor-default select-none">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $paginator->previousPageUrl() }}" rel="prev"
|
||||||
|
class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-200 bg-white text-gray-500 hover:bg-indigo-50 hover:border-indigo-300 hover:text-indigo-600 transition"
|
||||||
|
aria-label="{{ __('pagination.previous') }}">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Numeri pagina: nascosti su mobile, visibili da sm in su --}}
|
||||||
|
<div class="hidden sm:flex items-center gap-1">
|
||||||
|
@foreach ($elements as $element)
|
||||||
|
@if (is_string($element))
|
||||||
|
<span class="inline-flex items-center justify-center w-8 h-8 text-xs text-gray-400 select-none">…</span>
|
||||||
|
@endif
|
||||||
|
@if (is_array($element))
|
||||||
|
@foreach ($element as $page => $url)
|
||||||
|
@if ($page == $paginator->currentPage())
|
||||||
|
<span aria-current="page"
|
||||||
|
class="inline-flex items-center justify-center w-8 h-8 rounded-lg text-xs font-semibold text-white select-none"
|
||||||
|
style="background:#4f46e5">{{ $page }}</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $url }}"
|
||||||
|
class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-200 bg-white text-xs font-medium text-gray-600 hover:bg-indigo-50 hover:border-indigo-300 hover:text-indigo-600 transition"
|
||||||
|
aria-label="{{ __('Go to page :page', ['page' => $page]) }}">{{ $page }}</a>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Indicatore pagina mobile --}}
|
||||||
|
<span class="sm:hidden inline-flex items-center justify-center px-3 h-8 rounded-lg border border-gray-200 bg-white text-xs font-medium text-gray-600 select-none">
|
||||||
|
{{ $paginator->currentPage() }} / {{ $paginator->lastPage() }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{-- Next --}}
|
||||||
|
@if ($paginator->hasMorePages())
|
||||||
|
<a href="{{ $paginator->nextPageUrl() }}" rel="next"
|
||||||
|
class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-200 bg-white text-gray-500 hover:bg-indigo-50 hover:border-indigo-300 hover:text-indigo-600 transition"
|
||||||
|
aria-label="{{ __('pagination.next') }}">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<span class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-200 text-gray-300 cursor-default select-none">
|
||||||
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
@endif
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use App\Http\Controllers\Settings\TerritoryPdfImportController;
|
use App\Http\Controllers\Settings\TerritoryPdfImportController;
|
||||||
|
use App\Http\Controllers\Settings\XmlExchangeUploadController;
|
||||||
use App\Http\Controllers\AssignmentPdfController;
|
use App\Http\Controllers\AssignmentPdfController;
|
||||||
|
use App\Http\Controllers\ShortPdfLinkController;
|
||||||
use App\Http\Controllers\Auth\LoginController;
|
use App\Http\Controllers\Auth\LoginController;
|
||||||
use App\Livewire\Home;
|
use App\Livewire\Home;
|
||||||
use App\Livewire\Territori\TerritorioIndex;
|
use App\Livewire\Territori\TerritorioIndex;
|
||||||
@@ -48,6 +50,7 @@ Route::post('logout', function () {
|
|||||||
return redirect('/login');
|
return redirect('/login');
|
||||||
})->middleware('auth')->name('logout');
|
})->middleware('auth')->name('logout');
|
||||||
|
|
||||||
|
Route::get('p/{code}', ShortPdfLinkController::class)->name('assignments.pdf.short');
|
||||||
Route::get('assegnazioni/pdf/{assignment}/{code}', [AssignmentPdfController::class, 'viewer'])
|
Route::get('assegnazioni/pdf/{assignment}/{code}', [AssignmentPdfController::class, 'viewer'])
|
||||||
->name('assignments.pdf.viewer');
|
->name('assignments.pdf.viewer');
|
||||||
Route::get('assegnazioni/pdf/{assignment}/{code}/file', [AssignmentPdfController::class, 'file'])
|
Route::get('assegnazioni/pdf/{assignment}/{code}/file', [AssignmentPdfController::class, 'file'])
|
||||||
@@ -114,6 +117,8 @@ Route::middleware('auth')->group(function () {
|
|||||||
Route::get('zone', ZoneIndex::class)->name('zone.index');
|
Route::get('zone', ZoneIndex::class)->name('zone.index');
|
||||||
Route::get('tipologie', TipologieIndex::class)->name('tipologie.index');
|
Route::get('tipologie', TipologieIndex::class)->name('tipologie.index');
|
||||||
Route::get('xml-exchange', XmlExchange::class)->name('xml.exchange');
|
Route::get('xml-exchange', XmlExchange::class)->name('xml.exchange');
|
||||||
|
Route::post('xml-exchange/convert-sql', [XmlExchangeUploadController::class, 'convertSqlToXml'])->name('xml.convert-sql');
|
||||||
|
Route::post('xml-exchange/import-xml', [XmlExchangeUploadController::class, 'importXml'])->name('xml.import-xml');
|
||||||
Route::post('imports/territori/pdf-zip', [TerritoryPdfImportController::class, 'storeZip'])->name('imports.territori.pdf-zip');
|
Route::post('imports/territori/pdf-zip', [TerritoryPdfImportController::class, 'storeZip'])->name('imports.territori.pdf-zip');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user