Refine letture UI and auto-link FE water readings
This commit is contained in:
parent
2639c0a0e3
commit
fe9e8adb72
|
|
@ -20,11 +20,13 @@
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Pages\Page;
|
use Filament\Pages\Page;
|
||||||
|
use Filament\Tables\Columns\ImageColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Concerns\InteractsWithTable;
|
use Filament\Tables\Concerns\InteractsWithTable;
|
||||||
use Filament\Tables\Contracts\HasTable;
|
use Filament\Tables\Contracts\HasTable;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
@ -224,8 +226,17 @@ public function table(Table $table): Table
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('periodo_dal')->label('Dal')->date('d/m/Y')->sortable()->toggleable(),
|
ImageColumn::make('miniatura_foto')
|
||||||
TextColumn::make('periodo_al')->label('Al')->date('d/m/Y')->sortable(),
|
->label('Foto')
|
||||||
|
->getStateUsing(fn(StabileServizioLettura $record): ?string => $this->resolveReadingPrimaryPhotoPath($record))
|
||||||
|
->disk('public')
|
||||||
|
->square()
|
||||||
|
->size(42)
|
||||||
|
->toggleable()
|
||||||
|
->extraAttributes(['class' => 'py-2']),
|
||||||
|
|
||||||
|
TextColumn::make('periodo_dal')->label('Dal')->date('d/m/Y')->sortable()->toggleable()->extraAttributes(['class' => 'text-[11px] leading-tight']),
|
||||||
|
TextColumn::make('periodo_al')->label('Al')->date('d/m/Y')->sortable()->extraAttributes(['class' => 'text-[11px] leading-tight']),
|
||||||
|
|
||||||
TextColumn::make('stabile.denominazione')
|
TextColumn::make('stabile.denominazione')
|
||||||
->label('Stabile')
|
->label('Stabile')
|
||||||
|
|
@ -240,7 +251,19 @@ public function table(Table $table): Table
|
||||||
|
|
||||||
return trim($codice . ($nome !== '' ? (' - ' . $nome) : '')) ?: ('Stabile #' . $stabile->id);
|
return trim($codice . ($nome !== '' ? (' - ' . $nome) : '')) ?: ('Stabile #' . $stabile->id);
|
||||||
})
|
})
|
||||||
->wrap()
|
->limit(36)
|
||||||
|
->tooltip(function (StabileServizioLettura $record): string {
|
||||||
|
$stabile = $record->stabile;
|
||||||
|
if (! $stabile instanceof Stabile) {
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
$codice = trim((string) ($stabile->codice_stabile ?? ''));
|
||||||
|
$nome = trim((string) ($stabile->denominazione ?? ''));
|
||||||
|
|
||||||
|
return trim($codice . ($nome !== '' ? (' - ' . $nome) : '')) ?: ('Stabile #' . $stabile->id);
|
||||||
|
})
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: $this->archivioScope !== 'all'),
|
->toggleable(isToggledHiddenByDefault: $this->archivioScope !== 'all'),
|
||||||
|
|
||||||
TextColumn::make('servizio.nome')
|
TextColumn::make('servizio.nome')
|
||||||
|
|
@ -254,23 +277,37 @@ public function table(Table $table): Table
|
||||||
$tipo = trim((string) ($record->servizio?->tipo ?? ''));
|
$tipo = trim((string) ($record->servizio?->tipo ?? ''));
|
||||||
return $tipo !== '' ? strtoupper($tipo) : '—';
|
return $tipo !== '' ? strtoupper($tipo) : '—';
|
||||||
})
|
})
|
||||||
->wrap()
|
->limit(30)
|
||||||
|
->tooltip(function (StabileServizioLettura $record): string {
|
||||||
|
$nome = trim((string) ($record->servizio?->nome ?? ''));
|
||||||
|
if ($nome !== '') {
|
||||||
|
return $nome;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tipo = trim((string) ($record->servizio?->tipo ?? ''));
|
||||||
|
return $tipo !== '' ? strtoupper($tipo) : '—';
|
||||||
|
})
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('servizio.contatore_matricola')->label('Matricola')->toggleable(isToggledHiddenByDefault: true),
|
TextColumn::make('servizio.contatore_matricola')->label('Matricola')->toggleable(isToggledHiddenByDefault: true)->extraAttributes(['class' => 'text-[11px] leading-tight']),
|
||||||
|
|
||||||
TextColumn::make('unitaImmobiliare.denominazione')
|
TextColumn::make('unitaImmobiliare.denominazione')
|
||||||
->label('Attribuzione')
|
->label('Attribuzione')
|
||||||
->formatStateUsing(function ($state, StabileServizioLettura $record): string {
|
->formatStateUsing(function ($state, StabileServizioLettura $record): string {
|
||||||
return $this->resolveReadingAssignmentLabel($record);
|
return $this->resolveReadingAssignmentLabel($record);
|
||||||
})
|
})
|
||||||
->wrap()
|
->limit(34)
|
||||||
|
->tooltip(fn(StabileServizioLettura $record): string => $this->resolveReadingAssignmentLabel($record))
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('rilevatore_nome')
|
TextColumn::make('rilevatore_nome')
|
||||||
->label('Letturista / nominativo')
|
->label('Letturista / nominativo')
|
||||||
->formatStateUsing(fn($state): string => trim((string) $state) !== '' ? trim((string) $state) : '—')
|
->formatStateUsing(fn($state): string => trim((string) $state) !== '' ? trim((string) $state) : '—')
|
||||||
->wrap()
|
->limit(28)
|
||||||
|
->tooltip(fn($state): string => trim((string) $state) !== '' ? trim((string) $state) : '—')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('consumo_valore')
|
TextColumn::make('consumo_valore')
|
||||||
|
|
@ -284,13 +321,15 @@ public function table(Table $table): Table
|
||||||
$unit = trim((string) ($record->consumo_unita ?? ''));
|
$unit = trim((string) ($record->consumo_unita ?? ''));
|
||||||
return trim((string) $val) . ($unit !== '' ? (' ' . $unit) : '');
|
return trim((string) $val) . ($unit !== '' ? (' ' . $unit) : '');
|
||||||
})
|
})
|
||||||
->sortable(),
|
->sortable()
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight font-medium']),
|
||||||
|
|
||||||
TextColumn::make('importo_totale')->label('Importo')->money('EUR')->sortable()->toggleable(),
|
TextColumn::make('importo_totale')->label('Importo')->money('EUR')->sortable()->toggleable()->extraAttributes(['class' => 'text-[11px] leading-tight']),
|
||||||
|
|
||||||
TextColumn::make('tipologia_lettura')
|
TextColumn::make('tipologia_lettura')
|
||||||
->label('Tipo lettura')
|
->label('Tipo lettura')
|
||||||
->formatStateUsing(fn($state) => $tipologiaOptions[strtolower(trim((string) $state))] ?? (string) ($state ?? '—'))
|
->formatStateUsing(fn($state) => $tipologiaOptions[strtolower(trim((string) $state))] ?? (string) ($state ?? '—'))
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('canale_acquisizione')
|
TextColumn::make('canale_acquisizione')
|
||||||
|
|
@ -307,12 +346,14 @@ public function table(Table $table): Table
|
||||||
default => $v !== '' ? strtoupper($v) : '—',
|
default => $v !== '' ? strtoupper($v) : '—',
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('workflow_stato')
|
TextColumn::make('workflow_stato')
|
||||||
->label('Workflow')
|
->label('Workflow')
|
||||||
->formatStateUsing(fn($state): string => trim((string) $state) !== '' ? (string) $state : 'Acquisita')
|
->formatStateUsing(fn($state): string => trim((string) $state) !== '' ? (string) $state : 'Acquisita')
|
||||||
->badge()
|
->badge()
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('raw.consistency_check.summary')
|
TextColumn::make('raw.consistency_check.summary')
|
||||||
|
|
@ -329,7 +370,12 @@ public function table(Table $table): Table
|
||||||
default => 'gray',
|
default => 'gray',
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
->wrap()
|
->limit(28)
|
||||||
|
->tooltip(function (StabileServizioLettura $record): string {
|
||||||
|
$summary = trim((string) data_get($record->raw, 'consistency_check.summary', ''));
|
||||||
|
return $summary !== '' ? $summary : '—';
|
||||||
|
})
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('lettura_foto_original_name')
|
TextColumn::make('lettura_foto_original_name')
|
||||||
|
|
@ -342,6 +388,18 @@ public function table(Table $table): Table
|
||||||
|
|
||||||
return filled($record->lettura_foto_path) ? '1 foto' : '—';
|
return filled($record->lettura_foto_path) ? '1 foto' : '—';
|
||||||
})
|
})
|
||||||
|
->tooltip(function (StabileServizioLettura $record): string {
|
||||||
|
$photos = $this->getReadingPhotoEntries($record);
|
||||||
|
if ($photos === []) {
|
||||||
|
return 'Nessuna foto';
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode("\n", array_map(
|
||||||
|
fn(array $photo): string => (string) ($photo['name'] ?? basename((string) ($photo['path'] ?? 'foto'))),
|
||||||
|
$photos
|
||||||
|
));
|
||||||
|
})
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('protocollo_numero')
|
TextColumn::make('protocollo_numero')
|
||||||
|
|
@ -356,55 +414,72 @@ public function table(Table $table): Table
|
||||||
|
|
||||||
return trim($categoria . ($numero !== '' ? (' #' . $numero) : ''));
|
return trim($categoria . ($numero !== '' ? (' #' . $numero) : ''));
|
||||||
})
|
})
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('prossima_lettura_scadenza_at')
|
TextColumn::make('prossima_lettura_scadenza_at')
|
||||||
->label('Scadenza prossima lettura')
|
->label('Scadenza prossima lettura')
|
||||||
->dateTime('d/m/Y H:i')
|
->dateTime('d/m/Y H:i')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('deadline_lettura_at')
|
TextColumn::make('deadline_lettura_at')
|
||||||
->label('Deadline campagna')
|
->label('Deadline campagna')
|
||||||
->dateTime('d/m/Y H:i')
|
->dateTime('d/m/Y H:i')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('sollecito_inviato_at')
|
TextColumn::make('sollecito_inviato_at')
|
||||||
->label('Sollecito')
|
->label('Sollecito')
|
||||||
->dateTime('d/m/Y H:i')
|
->dateTime('d/m/Y H:i')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('rilevatore_tipo')
|
TextColumn::make('rilevatore_tipo')
|
||||||
->label('Rilevatore')
|
->label('Rilevatore')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('lettura_precedente_valore')
|
TextColumn::make('lettura_precedente_valore')
|
||||||
->label('Lettura precedente')
|
->label('Lettura precedente')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('riferimento_acquisizione')->label('Riferimento')->toggleable(isToggledHiddenByDefault: true),
|
TextColumn::make('riferimento_acquisizione')->label('Riferimento')->toggleable(isToggledHiddenByDefault: true)->extraAttributes(['class' => 'text-[11px] leading-tight']),
|
||||||
|
|
||||||
TextColumn::make('archivio_documentale_path')
|
TextColumn::make('archivio_documentale_path')
|
||||||
->label('Archivio documentale')
|
->label('Archivio documentale')
|
||||||
->wrap()
|
->limit(36)
|
||||||
|
->tooltip(fn($state): string => trim((string) ($state ?? '')) !== '' ? trim((string) $state) : '—')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('fatturaElettronica.numero_fattura')
|
TextColumn::make('fatturaElettronica.numero_fattura')
|
||||||
->label('Fattura FE')
|
->label('Fattura FE')
|
||||||
->url(fn(StabileServizioLettura $record): ?string => $record->fattura_elettronica_id
|
->formatStateUsing(fn($state): string => trim((string) ($state ?? '')) !== '' ? trim((string) $state) : '—')
|
||||||
? FatturaElettronicaScheda::getUrl(['id' => (int) $record->fattura_elettronica_id], panel : 'admin-filament')
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
: null)
|
|
||||||
->openUrlInNewTab()
|
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('voceSpesa.descrizione')->label('Voce spesa')->wrap()->toggleable(isToggledHiddenByDefault: true),
|
TextColumn::make('voceSpesa.descrizione')
|
||||||
|
->label('Voce spesa')
|
||||||
|
->limit(30)
|
||||||
|
->tooltip(fn($state): string => trim((string) ($state ?? '')) !== '' ? trim((string) $state) : '—')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
TextColumn::make('voceSpesa.tipo_gestione')
|
TextColumn::make('voceSpesa.tipo_gestione')
|
||||||
->label('Gestione')
|
->label('Gestione')
|
||||||
->formatStateUsing(fn($state) => $gestioneOptions[strtolower(trim((string) $state))] ?? (string) ($state ?? '—'))
|
->formatStateUsing(fn($state) => $gestioneOptions[strtolower(trim((string) $state))] ?? (string) ($state ?? '—'))
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('fornitore.ragione_sociale')->label('Fornitore')->wrap()->toggleable(isToggledHiddenByDefault: true),
|
TextColumn::make('fornitore.ragione_sociale')
|
||||||
|
->label('Fornitore')
|
||||||
|
->limit(30)
|
||||||
|
->tooltip(fn($state): string => trim((string) ($state ?? '')) !== '' ? trim((string) $state) : '—')
|
||||||
|
->extraAttributes(['class' => 'text-[11px] leading-tight'])
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
|
||||||
TextColumn::make('created_at')->label('Creato')->dateTime('d/m/Y H:i')->toggleable(isToggledHiddenByDefault: true),
|
TextColumn::make('created_at')->label('Creato')->dateTime('d/m/Y H:i')->toggleable(isToggledHiddenByDefault: true)->extraAttributes(['class' => 'text-[11px] leading-tight']),
|
||||||
])
|
])
|
||||||
->headerActions([
|
->headerActions([
|
||||||
Action::make('toggleScope')
|
Action::make('toggleScope')
|
||||||
|
|
@ -574,20 +649,47 @@ public function table(Table $table): Table
|
||||||
Action::make('apriFoto')
|
Action::make('apriFoto')
|
||||||
->label('Foto')
|
->label('Foto')
|
||||||
->icon('heroicon-o-photo')
|
->icon('heroicon-o-photo')
|
||||||
->url(fn(StabileServizioLettura $record): ?string => $this->resolveReadingPrimaryPhotoUrl($record))
|
->button()
|
||||||
->openUrlInNewTab()
|
->color('gray')
|
||||||
->visible(fn(StabileServizioLettura $record): bool => $this->resolveReadingPrimaryPhotoUrl($record) !== null),
|
->modalHeading('Foto contatore')
|
||||||
|
->modalWidth('5xl')
|
||||||
|
->modalSubmitAction(false)
|
||||||
|
->modalCancelActionLabel('Chiudi')
|
||||||
|
->modalContent(function (StabileServizioLettura $record): View {
|
||||||
|
return view('filament.pages.condomini.partials.lettura-servizio-foto-modal', [
|
||||||
|
'record' => $record,
|
||||||
|
'photos' => $this->getReadingPhotoEntries($record),
|
||||||
|
'mapUrl' => $this->resolveReadingMapUrl($record),
|
||||||
|
'assigned' => $this->resolveReadingAssignmentLabel($record),
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
->visible(fn(StabileServizioLettura $record): bool => $this->getReadingPhotoEntries($record) !== []),
|
||||||
|
|
||||||
Action::make('mappaFoto')
|
Action::make('mappaFoto')
|
||||||
->label('Mappa')
|
->label('Mappa')
|
||||||
->icon('heroicon-o-map')
|
->icon('heroicon-o-map')
|
||||||
|
->button()
|
||||||
|
->color('gray')
|
||||||
->url(fn(StabileServizioLettura $record): ?string => $this->resolveReadingMapUrl($record))
|
->url(fn(StabileServizioLettura $record): ?string => $this->resolveReadingMapUrl($record))
|
||||||
->openUrlInNewTab()
|
->openUrlInNewTab()
|
||||||
->visible(fn(StabileServizioLettura $record): bool => $this->resolveReadingMapUrl($record) !== null),
|
->visible(fn(StabileServizioLettura $record): bool => $this->resolveReadingMapUrl($record) !== null),
|
||||||
|
|
||||||
|
Action::make('apriFatturaFe')
|
||||||
|
->label('FE')
|
||||||
|
->icon('heroicon-o-receipt-percent')
|
||||||
|
->button()
|
||||||
|
->color('primary')
|
||||||
|
->url(fn(StabileServizioLettura $record): ?string => $record->fattura_elettronica_id
|
||||||
|
? FatturaElettronicaScheda::getUrl(['record' => (int) $record->fattura_elettronica_id], panel: 'admin-filament')
|
||||||
|
: null)
|
||||||
|
->openUrlInNewTab()
|
||||||
|
->visible(fn(StabileServizioLettura $record): bool => (int) ($record->fattura_elettronica_id ?? 0) > 0),
|
||||||
|
|
||||||
Action::make('riattribuisci')
|
Action::make('riattribuisci')
|
||||||
->label('Riattribuisci')
|
->label('Riattribuisci')
|
||||||
->icon('heroicon-o-user-circle')
|
->icon('heroicon-o-user-circle')
|
||||||
|
->button()
|
||||||
|
->color('gray')
|
||||||
->form([
|
->form([
|
||||||
Select::make('unita_immobiliare_id')
|
Select::make('unita_immobiliare_id')
|
||||||
->label('Unità / nominativo')
|
->label('Unità / nominativo')
|
||||||
|
|
@ -646,6 +748,7 @@ public function table(Table $table): Table
|
||||||
Action::make('spostaContatoreComune')
|
Action::make('spostaContatoreComune')
|
||||||
->label('Sposta a contatore comune')
|
->label('Sposta a contatore comune')
|
||||||
->icon('heroicon-o-arrow-path-rounded-square')
|
->icon('heroicon-o-arrow-path-rounded-square')
|
||||||
|
->button()
|
||||||
->color('warning')
|
->color('warning')
|
||||||
->form([
|
->form([
|
||||||
Select::make('stabile_servizio_id')
|
Select::make('stabile_servizio_id')
|
||||||
|
|
@ -728,6 +831,8 @@ public function table(Table $table): Table
|
||||||
Action::make('edit')
|
Action::make('edit')
|
||||||
->label('Modifica')
|
->label('Modifica')
|
||||||
->icon('heroicon-o-pencil-square')
|
->icon('heroicon-o-pencil-square')
|
||||||
|
->button()
|
||||||
|
->color('gray')
|
||||||
->form([
|
->form([
|
||||||
Select::make('stabile_servizio_id')
|
Select::make('stabile_servizio_id')
|
||||||
->label('Servizio')
|
->label('Servizio')
|
||||||
|
|
@ -847,6 +952,7 @@ public function table(Table $table): Table
|
||||||
Action::make('delete')
|
Action::make('delete')
|
||||||
->label('Elimina')
|
->label('Elimina')
|
||||||
->icon('heroicon-o-trash')
|
->icon('heroicon-o-trash')
|
||||||
|
->button()
|
||||||
->color('danger')
|
->color('danger')
|
||||||
->requiresConfirmation()
|
->requiresConfirmation()
|
||||||
->action(fn(StabileServizioLettura $record) => $record->delete()),
|
->action(fn(StabileServizioLettura $record) => $record->delete()),
|
||||||
|
|
@ -1345,21 +1451,55 @@ private function resolveReadingAssignmentLabel(StabileServizioLettura $record):
|
||||||
return $parts !== [] ? implode(' - ', $parts) : 'Contatore generale / bene comune';
|
return $parts !== [] ? implode(' - ', $parts) : 'Contatore generale / bene comune';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveReadingPrimaryPhotoPath(StabileServizioLettura $record): ?string
|
/**
|
||||||
|
* @return array<int, array{name: string, path: string, url: string, latitude: float|null, longitude: float|null}>
|
||||||
|
*/
|
||||||
|
private function getReadingPhotoEntries(StabileServizioLettura $record): array
|
||||||
{
|
{
|
||||||
|
$entries = [];
|
||||||
|
|
||||||
|
$append = function (string $path, ?string $name = null, $lat = null, $lng = null) use (&$entries): void {
|
||||||
|
$path = trim($path);
|
||||||
|
if ($path === '' || isset($entries[$path]) || ! Storage::disk('public')->exists($path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries[$path] = [
|
||||||
|
'name' => trim((string) ($name ?? '')) !== '' ? trim((string) $name) : basename($path),
|
||||||
|
'path' => $path,
|
||||||
|
'url' => Storage::disk('public')->url($path),
|
||||||
|
'latitude' => is_numeric($lat) ? (float) $lat : null,
|
||||||
|
'longitude' => is_numeric($lng) ? (float) $lng : null,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
if (filled($record->lettura_foto_path)) {
|
if (filled($record->lettura_foto_path)) {
|
||||||
return (string) $record->lettura_foto_path;
|
$append(
|
||||||
|
(string) $record->lettura_foto_path,
|
||||||
|
(string) ($record->lettura_foto_original_name ?? ''),
|
||||||
|
data_get($record->lettura_foto_metadata, 'gps_decimal.lat'),
|
||||||
|
data_get($record->lettura_foto_metadata, 'gps_decimal.lng')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$photos = is_array(data_get($record->lettura_foto_metadata, 'photos')) ? data_get($record->lettura_foto_metadata, 'photos') : [];
|
$photos = is_array(data_get($record->lettura_foto_metadata, 'photos')) ? data_get($record->lettura_foto_metadata, 'photos') : [];
|
||||||
foreach ($photos as $photo) {
|
foreach ($photos as $photo) {
|
||||||
$path = trim((string) ($photo['path'] ?? ''));
|
$append(
|
||||||
if ($path !== '') {
|
(string) ($photo['path'] ?? ''),
|
||||||
return $path;
|
(string) ($photo['original_name'] ?? $photo['stored_basename'] ?? ''),
|
||||||
}
|
data_get($photo, 'metadata.gps_decimal.lat'),
|
||||||
|
data_get($photo, 'metadata.gps_decimal.lng')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return array_values($entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveReadingPrimaryPhotoPath(StabileServizioLettura $record): ?string
|
||||||
|
{
|
||||||
|
$photos = $this->getReadingPhotoEntries($record);
|
||||||
|
|
||||||
|
return $photos !== [] ? (string) ($photos[0]['path'] ?? '') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveReadingPrimaryPhotoUrl(StabileServizioLettura $record): ?string
|
private function resolveReadingPrimaryPhotoUrl(StabileServizioLettura $record): ?string
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,15 @@
|
||||||
namespace App\Filament\Pages\Contabilita;
|
namespace App\Filament\Pages\Contabilita;
|
||||||
|
|
||||||
use App\Filament\Pages\Contabilita\FatturaElettronicaScheda;
|
use App\Filament\Pages\Contabilita\FatturaElettronicaScheda;
|
||||||
|
use App\Models\Documento;
|
||||||
use App\Jobs\RunCassettoFiscaleDownload;
|
use App\Jobs\RunCassettoFiscaleDownload;
|
||||||
use App\Models\FatturaElettronica;
|
use App\Models\FatturaElettronica;
|
||||||
use App\Models\Stabile;
|
use App\Models\Stabile;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\Consumi\AcquaPdfTextParser;
|
||||||
|
use App\Services\Consumi\ConsumiAcquaIngestionService;
|
||||||
|
use App\Services\Consumi\ConsumiAcquaTariffeIngestionService;
|
||||||
|
use App\Services\Documenti\PdfTextExtractionService;
|
||||||
use App\Services\FattureElettroniche\CassettoFiscaleDownloadService;
|
use App\Services\FattureElettroniche\CassettoFiscaleDownloadService;
|
||||||
use App\Services\FattureElettroniche\FatturaElettronicaImporter;
|
use App\Services\FattureElettroniche\FatturaElettronicaImporter;
|
||||||
use App\Services\FattureElettroniche\FatturaElettronicaProtocolloService;
|
use App\Services\FattureElettroniche\FatturaElettronicaProtocolloService;
|
||||||
|
|
@ -110,6 +115,169 @@ private function safeUtf8(string $value): string
|
||||||
return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $value) ?? $value;
|
return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $value) ?? $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{status: string, created_letture?: int, updated_letture?: int, tickets?: int, message?: string}
|
||||||
|
*/
|
||||||
|
private function autoLinkWaterReadingsForInvoice(int $fatturaId, int $userId): array
|
||||||
|
{
|
||||||
|
$fattura = FatturaElettronica::query()->find($fatturaId);
|
||||||
|
if (! $fattura instanceof FatturaElettronica) {
|
||||||
|
return ['status' => 'skipped', 'message' => 'Fattura non trovata'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasPdf = is_string($fattura->allegato_pdf_path ?? null) && trim((string) $fattura->allegato_pdf_path) !== '';
|
||||||
|
$hasStoredPayload = is_string($fattura->consumo_raw ?? null) && trim((string) $fattura->consumo_raw) !== '';
|
||||||
|
if (! $hasPdf && ! $hasStoredPayload) {
|
||||||
|
return ['status' => 'skipped', 'message' => 'Nessun PDF/payload acqua disponibile'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($userId > 0) {
|
||||||
|
app(FatturaElettronicaProtocolloService::class)->ensureDocumentoProtocollato($fattura, $userId);
|
||||||
|
$fattura->refresh();
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
$documento = Documento::query()
|
||||||
|
->where('documentable_type', FatturaElettronica::class)
|
||||||
|
->where('documentable_id', (int) $fattura->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$parsed = [];
|
||||||
|
if ($documento instanceof Documento) {
|
||||||
|
$text = is_string($documento->contenuto_ocr) ? trim((string) $documento->contenuto_ocr) : '';
|
||||||
|
if ($text === '') {
|
||||||
|
try {
|
||||||
|
$result = app(PdfTextExtractionService::class)->extractAndStore($documento, false);
|
||||||
|
if (($result['status'] ?? null) === 'ok') {
|
||||||
|
$documento->refresh();
|
||||||
|
$text = is_string($documento->contenuto_ocr) ? trim((string) $documento->contenuto_ocr) : '';
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$text = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($text !== '') {
|
||||||
|
$parsed = app(AcquaPdfTextParser::class)->parse($text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$storedRaw = is_string($fattura->consumo_raw ?? null) ? trim((string) $fattura->consumo_raw) : '';
|
||||||
|
if ($storedRaw !== '' && $parsed === []) {
|
||||||
|
$decoded = json_decode($storedRaw, true);
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
$parsed = $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$codici = is_array($parsed['codici'] ?? null) ? $parsed['codici'] : [];
|
||||||
|
$contatore = is_array($parsed['contatore'] ?? null) ? $parsed['contatore'] : [];
|
||||||
|
$consumi = is_array($parsed['consumi'] ?? null) ? $parsed['consumi'] : [];
|
||||||
|
$generale = is_array($parsed['generale'] ?? null) ? $parsed['generale'] : [];
|
||||||
|
$finestra = is_array($parsed['finestra_autolettura'] ?? null) ? $parsed['finestra_autolettura'] : [];
|
||||||
|
$letture = is_array($parsed['riepilogo_letture'] ?? null) ? $parsed['riepilogo_letture'] : [];
|
||||||
|
$quadro = is_array($parsed['quadro_dettaglio'] ?? null) ? $parsed['quadro_dettaglio'] : [];
|
||||||
|
$tariffe = is_array($parsed['tariffe'] ?? null) ? $parsed['tariffe'] : [];
|
||||||
|
$ivaInfo = is_array($parsed['iva'] ?? null) ? $parsed['iva'] : [];
|
||||||
|
|
||||||
|
$hasAny = false;
|
||||||
|
foreach ([$codici['utenza'] ?? null, $codici['cliente'] ?? null, $codici['contratto'] ?? null, $contatore['matricola'] ?? null] as $value) {
|
||||||
|
if (is_string($value) && trim($value) !== '') {
|
||||||
|
$hasAny = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasTariffData = count(array_filter([
|
||||||
|
$generale['periodicita_fatturazione'] ?? null,
|
||||||
|
$tariffe['profilo'] ?? null,
|
||||||
|
$ivaInfo['codice'] ?? null,
|
||||||
|
$quadro['quota_fissa'] ?? null,
|
||||||
|
], static fn($value): bool => $value !== null && $value !== '')) > 0;
|
||||||
|
|
||||||
|
if (! $hasAny && count($consumi) < 1 && ! $hasTariffData) {
|
||||||
|
return ['status' => 'skipped', 'message' => 'Nessun dato acqua utile rilevato'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'type' => 'acqua',
|
||||||
|
'source' => 'pdf_ocr',
|
||||||
|
'codici' => [
|
||||||
|
'utenza' => $codici['utenza'] ?? null,
|
||||||
|
'cliente' => $codici['cliente'] ?? null,
|
||||||
|
'contratto' => $codici['contratto'] ?? null,
|
||||||
|
],
|
||||||
|
'contatore' => [
|
||||||
|
'matricola' => $contatore['matricola'] ?? null,
|
||||||
|
],
|
||||||
|
'consumi' => array_values($consumi),
|
||||||
|
'generale' => $generale,
|
||||||
|
'finestra_autolettura' => $finestra,
|
||||||
|
'riepilogo_letture' => array_values($letture),
|
||||||
|
'quadro_dettaglio' => $quadro,
|
||||||
|
'tariffe' => $tariffe,
|
||||||
|
'iva' => [
|
||||||
|
'codice' => $ivaInfo['codice'] ?? null,
|
||||||
|
'aliquota_percentuale' => $ivaInfo['aliquota_percentuale'] ?? null,
|
||||||
|
'descrizione' => $ivaInfo['descrizione'] ?? null,
|
||||||
|
],
|
||||||
|
'parsed_at' => now()->toISOString(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$totalMc = null;
|
||||||
|
$reference = null;
|
||||||
|
if ($consumi !== []) {
|
||||||
|
$sum = 0.0;
|
||||||
|
$hasNumeric = false;
|
||||||
|
foreach ($consumi as $consumo) {
|
||||||
|
if (isset($consumo['valore']) && is_numeric($consumo['valore'])) {
|
||||||
|
$sum += (float) $consumo['valore'];
|
||||||
|
$hasNumeric = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($hasNumeric) {
|
||||||
|
$totalMc = $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = $consumi[0] ?? [];
|
||||||
|
$dal = $first['dal'] ?? null;
|
||||||
|
$al = $first['al'] ?? null;
|
||||||
|
if (is_string($dal) && is_string($al) && $dal !== '' && $al !== '') {
|
||||||
|
$reference = $dal . ' - ' . $al;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fattura->consumo_unita = 'mc';
|
||||||
|
$fattura->consumo_valore = $totalMc;
|
||||||
|
$fattura->consumo_riferimento = $reference;
|
||||||
|
$fattura->consumo_raw = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
$fattura->save();
|
||||||
|
|
||||||
|
try {
|
||||||
|
app(ConsumiAcquaTariffeIngestionService::class)->ingest($fattura, $parsed, null);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// best-effort
|
||||||
|
}
|
||||||
|
|
||||||
|
$ingestion = app(ConsumiAcquaIngestionService::class)->ingest($fattura, $parsed, null, $userId, false, null);
|
||||||
|
if (($ingestion['status'] ?? null) === 'ok') {
|
||||||
|
try {
|
||||||
|
app(ConsumiAcquaTariffeIngestionService::class)->ingest(
|
||||||
|
$fattura,
|
||||||
|
$parsed,
|
||||||
|
isset($ingestion['servizio_id']) && is_numeric($ingestion['servizio_id']) ? (int) $ingestion['servizio_id'] : null
|
||||||
|
);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// best-effort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ingestion;
|
||||||
|
}
|
||||||
|
|
||||||
public int $cassettoAnno;
|
public int $cassettoAnno;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1006,6 +1174,8 @@ protected function getHeaderActions(): array
|
||||||
$imported = 0;
|
$imported = 0;
|
||||||
$duplicates = 0;
|
$duplicates = 0;
|
||||||
$errors = 0;
|
$errors = 0;
|
||||||
|
$waterCreated = 0;
|
||||||
|
$waterUpdated = 0;
|
||||||
|
|
||||||
$duplicateDetails = [];
|
$duplicateDetails = [];
|
||||||
$errorDetails = [];
|
$errorDetails = [];
|
||||||
|
|
@ -1125,6 +1295,14 @@ protected function getHeaderActions(): array
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (in_array((string) ($result['status'] ?? ''), ['imported', 'duplicate'], true) && is_numeric($result['id'] ?? null)) {
|
||||||
|
$waterResult = $this->autoLinkWaterReadingsForInvoice((int) $result['id'], (int) $user->id);
|
||||||
|
if (($waterResult['status'] ?? null) === 'ok') {
|
||||||
|
$waterCreated += (int) ($waterResult['created_letture'] ?? 0);
|
||||||
|
$waterUpdated += (int) ($waterResult['updated_letture'] ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (($result['status'] ?? null) === 'imported') {
|
if (($result['status'] ?? null) === 'imported') {
|
||||||
$imported++;
|
$imported++;
|
||||||
} elseif (($result['status'] ?? null) === 'duplicate') {
|
} elseif (($result['status'] ?? null) === 'duplicate') {
|
||||||
|
|
@ -1228,6 +1406,14 @@ protected function getHeaderActions(): array
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (in_array((string) ($result['status'] ?? ''), ['imported', 'duplicate'], true) && is_numeric($result['id'] ?? null)) {
|
||||||
|
$waterResult = $this->autoLinkWaterReadingsForInvoice((int) $result['id'], (int) $user->id);
|
||||||
|
if (($waterResult['status'] ?? null) === 'ok') {
|
||||||
|
$waterCreated += (int) ($waterResult['created_letture'] ?? 0);
|
||||||
|
$waterUpdated += (int) ($waterResult['updated_letture'] ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (($result['status'] ?? null) === 'imported') {
|
if (($result['status'] ?? null) === 'imported') {
|
||||||
$imported++;
|
$imported++;
|
||||||
} elseif (($result['status'] ?? null) === 'duplicate') {
|
} elseif (($result['status'] ?? null) === 'duplicate') {
|
||||||
|
|
@ -1314,7 +1500,7 @@ protected function getHeaderActions(): array
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Import completato')
|
->title('Import completato')
|
||||||
->body($this->safeUtf8("Importate: {$imported} · Duplicate: {$duplicates} · Errori: {$errors}" . $logNote))
|
->body($this->safeUtf8("Importate: {$imported} · Duplicate: {$duplicates} · Errori: {$errors} · Letture acqua create: {$waterCreated} · aggiornate: {$waterUpdated}" . $logNote))
|
||||||
->success()
|
->success()
|
||||||
->send();
|
->send();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ ## Stato operativo reale alla chiusura sessione
|
||||||
- rimuovere il footer Filament invasivo su mobile
|
- rimuovere il footer Filament invasivo su mobile
|
||||||
- riallineare pagina `tabelle-millesimali` a `nord`, `tipo`, `calcolo`
|
- riallineare pagina `tabelle-millesimali` a `nord`, `tipo`, `calcolo`
|
||||||
- eliminare i blocchi Blade fragili nelle viste `ticket-acqua` e `ticket-acqua-generale`
|
- eliminare i blocchi Blade fragili nelle viste `ticket-acqua` e `ticket-acqua-generale`
|
||||||
|
- compattare la UI di `letture-servizi` con pulsanti, tooltip, miniatura e modal foto
|
||||||
|
- agganciare automaticamente il parsing acqua alle FE ricevute importate quando esiste PDF/payload utilizzabile
|
||||||
|
|
||||||
## Verifiche fatte e confermate
|
## Verifiche fatte e confermate
|
||||||
|
|
||||||
|
|
@ -21,9 +23,28 @@ ## Verifiche fatte e confermate
|
||||||
- Il problema operativo dello scarico FE nasceva dal contesto dello stabile attivo in topbar/sessione, non dalla chiamata HTTP finale.
|
- Il problema operativo dello scarico FE nasceva dal contesto dello stabile attivo in topbar/sessione, non dalla chiamata HTTP finale.
|
||||||
- Nella pagina FE è stato aggiunto un riepilogo esplicito del `Codice fiscale soggetto usato per lo scarico`.
|
- Nella pagina FE è stato aggiunto un riepilogo esplicito del `Codice fiscale soggetto usato per lo scarico`.
|
||||||
- Le due Blade acqua sono state semplificate spostando il rendering metadati foto in partial condivisa, per evitare parse error residui in staging dovuti a blocchi Blade annidati.
|
- Le due Blade acqua sono state semplificate spostando il rendering metadati foto in partial condivisa, per evitare parse error residui in staging dovuti a blocchi Blade annidati.
|
||||||
|
- La pagina `fatture-ricevute` continua a filtrare per `stabile_id` attivo; nel DB locale per `stabile_id=25` esistono FE 2025 ricevute.
|
||||||
|
- Per `stabile_id=25` esistono FE 2025 con `allegato_pdf_path` e in diversi casi `consumo_raw`, quindi il nuovo aggancio acqua post-import ha input reali da elaborare.
|
||||||
|
- `php artisan view:clear && php artisan view:cache` continua a passare dopo i ritocchi UI e il nuovo post-processing FE.
|
||||||
|
- `php artisan route:list` continua a caricare `755` route.
|
||||||
|
|
||||||
## File toccati in questa fase
|
## File toccati in questa fase
|
||||||
|
|
||||||
|
- `app/Filament/Pages/Condomini/LettureServiziArchivio.php`
|
||||||
|
- colonna miniatura foto
|
||||||
|
- testi piu compatti (`text-[11px]`)
|
||||||
|
- tooltip al posto di testo lungo per servizio/attribuzione/check/voce/fornitore
|
||||||
|
- azioni riga rese pulsanti
|
||||||
|
- modal foto invece dell'apertura diretta del file
|
||||||
|
- pulsante FE dedicato in riga
|
||||||
|
- `resources/views/filament/pages/condomini/partials/lettura-servizio-foto-modal.blade.php`
|
||||||
|
- nuova vista modal per anteprima foto contatore
|
||||||
|
- `app/Filament/Pages/Contabilita/FattureElettronicheP7mRicevute.php`
|
||||||
|
- nuovo post-processing `autoLinkWaterReadingsForInvoice()` dopo import FE
|
||||||
|
- sincronizzazione `consumo_raw`/consumi FE
|
||||||
|
- chiamata a `ConsumiAcquaIngestionService` e `ConsumiAcquaTariffeIngestionService` in best-effort
|
||||||
|
- notifica finale estesa con conteggio letture acqua create/aggiornate
|
||||||
|
|
||||||
- `app/Providers/Filament/AdminFilamentPanelProvider.php`
|
- `app/Providers/Filament/AdminFilamentPanelProvider.php`
|
||||||
- chain Filament riparata
|
- chain Filament riparata
|
||||||
- footer hook rimosso dal panel
|
- footer hook rimosso dal panel
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
@php
|
||||||
|
$photos = is_array($photos ?? null) ? $photos : [];
|
||||||
|
$primary = $photos[0] ?? null;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div class="text-sm font-semibold text-slate-900">{{ $assigned ?? 'Contatore' }}</div>
|
||||||
|
<div class="text-xs text-slate-500">
|
||||||
|
Riga #{{ (int) ($record->id ?? 0) }}
|
||||||
|
@if(! empty($record->periodo_dal) || ! empty($record->periodo_al))
|
||||||
|
· Periodo {{ optional($record->periodo_dal)->format('d/m/Y') ?: '—' }} → {{ optional($record->periodo_al)->format('d/m/Y') ?: '—' }}
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if(is_string($mapUrl ?? null) && $mapUrl !== '')
|
||||||
|
<a href="{{ $mapUrl }}" target="_blank" rel="noreferrer" class="inline-flex items-center rounded-md bg-slate-800 px-3 py-2 text-xs font-medium text-white hover:bg-slate-700">
|
||||||
|
Apri mappa
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($primary)
|
||||||
|
<div class="overflow-hidden rounded-xl border border-slate-200 bg-slate-50">
|
||||||
|
<img src="{{ $primary['url'] }}" alt="{{ $primary['name'] }}" class="max-h-[60vh] w-full object-contain bg-white" />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(count($photos) > 1)
|
||||||
|
<div class="grid grid-cols-2 gap-3 md:grid-cols-4">
|
||||||
|
@foreach($photos as $photo)
|
||||||
|
<a href="{{ $photo['url'] }}" target="_blank" rel="noreferrer" class="overflow-hidden rounded-lg border border-slate-200 bg-white transition hover:border-slate-400">
|
||||||
|
<img src="{{ $photo['url'] }}" alt="{{ $photo['name'] }}" class="h-28 w-full object-cover" />
|
||||||
|
<div class="border-t border-slate-100 px-2 py-2 text-[11px] text-slate-600">{{ $photo['name'] }}</div>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($photos === [])
|
||||||
|
<div class="rounded-lg border border-dashed border-slate-300 bg-slate-50 px-4 py-6 text-sm text-slate-500">
|
||||||
|
Nessuna foto disponibile per questa lettura.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
Loading…
Reference in New Issue
Block a user