Checkpoint Google supplier workflow cleanup

This commit is contained in:
michele 2026-04-12 08:06:52 +00:00
parent ce5451fe1a
commit c3efa1754b
30 changed files with 636 additions and 315 deletions

View File

@ -1,5 +1,4 @@
<?php
namespace App\Console\Commands;
use App\Models\RubricaContattoCanale;

View File

@ -1,5 +1,4 @@
<?php
namespace App\Console\Commands;
use App\Models\Stabile;

View File

@ -1,5 +1,4 @@
<?php
namespace App\Console\Commands;
use App\Models\Amministratore;
@ -27,13 +26,13 @@ public function handle(): int
$dryRun = (bool) $this->option('dry-run');
$limit = max(1, (int) $this->option('limit'));
$categorie = collect((array) $this->option('categoria'))
->map(fn ($v) => trim((string) $v))
->filter(fn (string $v) => $v !== '')
->map(fn($v) => trim((string) $v))
->filter(fn(string $v) => $v !== '')
->values()
->all();
$filterAdminIds = collect((array) $this->option('admin-id'))
->map(fn ($v) => (int) $v)
->filter(fn (int $v) => $v > 0)
->map(fn($v) => (int) $v)
->filter(fn(int $v) => $v > 0)
->values()
->all();

View File

@ -333,8 +333,8 @@ public function handle(): int
'repo_path' => $repoPath,
'deploy_path' => $deployPath,
'mode' => $deploySync ? 'external-repo-rsync' : 'in-place-git',
'source_repo_aligned'=> $sourceRepoAligned,
'source_repo_message'=> $sourceRepoMessage,
'source_repo_aligned' => $sourceRepoAligned,
'source_repo_message' => $sourceRepoMessage,
'rubrica_legami_qa' => $rubricaQaExit === 0 ? 'completed' : 'warning',
]);

View File

@ -162,6 +162,15 @@ public function getProdottiUrl(): string
return ProdottiCatalogo::getUrl(panel: 'admin-filament');
}
public function getImpostazioniUrl(): string
{
if ($this->fornitoreId) {
return ImpostazioniArchivio::getUrl(['fornitore' => $this->fornitoreId], panel: 'admin-filament');
}
return ImpostazioniArchivio::getUrl(panel: 'admin-filament');
}
protected function buildBaseQuery(Fornitore $fornitore, ?FornitoreDipendente $dipendente): Builder
{
$query = TicketIntervento::query()

View File

@ -2,6 +2,7 @@
namespace App\Filament\Pages\Fornitore;
use App\Filament\Pages\Fornitore\Concerns\ResolvesOperatoreContext;
use App\Models\AssistenzaTecnorepairScheda;
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\TicketAttachment;
@ -95,13 +96,22 @@ public function refreshData(): void
$query = $this->buildBaseQuery($fornitore, $dipendente);
$this->applyStatusFilter($query, $this->status);
$this->rows = $query
$ticketRows = $query
->limit(100)
->get()
->map(function (TicketIntervento $intervento): array {
$base = $this->buildInterventoRow($intervento);
return array_merge($base, [
'row_type' => 'ticket',
'row_class' => $this->resolveTicketRowClass((string) $intervento->stato),
'badge_class' => $this->resolveTicketBadgeClass((string) $intervento->stato),
'numero' => (string) $intervento->ticket_id,
'cliente' => $base['contatto'],
'difetto' => $base['problema'],
'modello' => $base['apparato'],
'seriale' => '-',
'cod_prodotto' => '-',
'id' => (int) $intervento->id,
'ticket_id' => (int) $intervento->ticket_id,
'titolo' => (string) ($intervento->ticket->titolo ?? '-'),
@ -110,17 +120,34 @@ public function refreshData(): void
'operatore' => (string) $intervento->operatore_assegnato_label,
'tempo_minuti' => (int) ($intervento->tempo_minuti ?? 0),
'updated_at' => optional($intervento->updated_at)->format('d/m/Y H:i') ?: '-',
'sort_at' => optional($intervento->created_at)->format('Y-m-d H:i:s') ?: '1970-01-01 00:00:00',
'origine' => 'Ticket amministratore',
'url' => TicketInterventoScheda::getUrl(['record' => $intervento->id], panel: 'admin-filament'),
]);
})
->all();
$tecnorepairRows = $this->buildTecnorepairRows($fornitore);
$rows = array_merge($ticketRows, $tecnorepairRows);
usort($rows, function (array $left, array $right): int {
return strcmp((string) ($right['sort_at'] ?? ''), (string) ($left['sort_at'] ?? ''));
});
$this->rows = array_slice($rows, 0, 150);
$this->totals = [
'aperti' => (clone $this->buildBaseQuery($fornitore, $dipendente))
->whereNotIn('stato', ['chiuso'])
->count() + AssistenzaTecnorepairScheda::query()
->where('fornitore_id', (int) $fornitore->id)
->whereIn('status_bucket', ['open', 'waiting'])
->count(),
'chiusi' => (clone $this->buildBaseQuery($fornitore, $dipendente))
->whereIn('stato', ['chiuso', 'fatturato'])
->count() + AssistenzaTecnorepairScheda::query()
->where('fornitore_id', (int) $fornitore->id)
->where('status_bucket', 'closed')
->count(),
'fatturabili' => (clone $this->buildBaseQuery($fornitore, $dipendente))
->whereIn('stato', ['fatturabile', 'fatturato'])
@ -152,6 +179,15 @@ public function getContabilitaUrl(): string
return Contabilita::getUrl(panel: 'admin-filament');
}
public function getImpostazioniUrl(): string
{
if ($this->fornitoreId && Auth::user()?->hasAnyRole(['super-admin', 'admin', 'amministratore'])) {
return ImpostazioniArchivio::getUrl(['fornitore' => $this->fornitoreId], panel: 'admin-filament');
}
return ImpostazioniArchivio::getUrl(panel: 'admin-filament');
}
public function openInterventoModal(int $interventoId): void
{
if (! $this->fornitoreId) {
@ -248,4 +284,79 @@ protected function applyStatusFilter(Builder $query, string $status): void
$query->whereNotIn('stato', ['chiuso']);
}
/**
* @return array<int, array<string, mixed>>
*/
protected function buildTecnorepairRows(Fornitore $fornitore): array
{
$query = AssistenzaTecnorepairScheda::query()
->where('fornitore_id', (int) $fornitore->id)
->orderByDesc('date_received')
->orderByDesc('updated_at');
if ($this->status === 'chiusi') {
$query->where('status_bucket', 'closed');
} elseif ($this->status === 'aperti') {
$query->whereIn('status_bucket', ['open', 'waiting']);
} elseif ($this->status === 'fatturabili') {
return [];
}
return $query
->limit(120)
->get()
->map(function (AssistenzaTecnorepairScheda $scheda): array {
return [
'row_type' => 'tecnorepair',
'row_class' => (string) $scheda->row_classes,
'badge_class' => (string) $scheda->status_badge_classes,
'numero' => (string) ($scheda->legacy_numero_scheda ?: $scheda->legacy_id),
'ingresso' => optional($scheda->date_received)->format('d/m/Y') ?: '-',
'cliente' => (string) ($scheda->customer_name ?: 'Cliente TecnoRepair'),
'contatto' => (string) ($scheda->customer_name ?: 'Cliente TecnoRepair'),
'telefono' => (string) ($scheda->customer_phone ?: $scheda->customer_phone_alt ?: ''),
'difetto' => (string) ($scheda->defect_reported ?: '-'),
'problema' => (string) ($scheda->defect_reported ?: '-'),
'apparato' => (string) ($scheda->product_model ?: '-'),
'modello' => (string) ($scheda->product_model ?: '-'),
'seriale' => (string) $scheda->serial_label,
'cod_prodotto' => (string) ($scheda->product_code ?: '-'),
'id' => (int) $scheda->id,
'ticket_id' => null,
'titolo' => (string) $scheda->display_title,
'stato' => (string) ($scheda->status_label ?: $scheda->status_bucket ?: '-'),
'stabile' => 'Archivio TecnoRepair',
'operatore' => (string) ($scheda->technician_name ?: $scheda->operator_name ?: '-'),
'tempo_minuti' => 0,
'updated_at' => optional($scheda->updated_at)->format('d/m/Y H:i') ?: '-',
'sort_at' => optional($scheda->date_received ?? $scheda->updated_at)->format('Y-m-d H:i:s') ?: '1970-01-01 00:00:00',
'origine' => 'TecnoRepair MDB',
'url' => null,
];
})
->all();
}
protected function resolveTicketRowClass(string $status): string
{
$status = strtolower(trim($status));
return match (true) {
in_array($status, ['chiuso', 'fatturato'], true) => 'bg-emerald-50/70 hover:bg-emerald-50',
in_array($status, ['fatturabile', 'verifica'], true) => 'bg-amber-50/70 hover:bg-amber-50',
default => 'bg-sky-50/60 hover:bg-sky-50',
};
}
protected function resolveTicketBadgeClass(string $status): string
{
$status = strtolower(trim($status));
return match (true) {
in_array($status, ['chiuso', 'fatturato'], true) => 'border-emerald-200 bg-emerald-50 text-emerald-800',
in_array($status, ['fatturabile', 'verifica'], true) => 'border-amber-200 bg-amber-50 text-amber-800',
default => 'border-sky-200 bg-sky-50 text-sky-800',
};
}
}

View File

@ -4,10 +4,10 @@
use App\Models\ChiamataPostIt;
use App\Models\Fornitore;
use App\Models\RubricaUniversale;
use App\Services\Anagrafiche\RubricaPhoneLookupService;
use App\Models\Stabile;
use App\Models\Ticket;
use App\Models\User;
use App\Services\Anagrafiche\RubricaPhoneLookupService;
use App\Support\PhoneNumber;
use App\Support\StabileContext;
use BackedEnum;

View File

@ -1131,7 +1131,7 @@ private function selectCallerRepresentative(Collection $group, int $selectedCall
$representative = $selected instanceof RubricaUniversale
? $selected
: $group
->sortByDesc(fn(RubricaUniversale $match): array => [
->sortByDesc(fn(RubricaUniversale $match): array=> [
(int) (! empty($match->amministratore_id)),
(int) (! empty($match->riferimento_stabile)),
(int) (! empty($match->riferimento_unita)),
@ -1274,7 +1274,6 @@ private function resolveCallerStabile(RubricaUniversale $caller, ?int $fallbackS
return $fallbackStabileId ? Stabile::query()->find($fallbackStabileId) : null;
}
private function resolveCallerSoggetto(RubricaUniversale $caller, ?int $stabileId = null): ?Soggetto
{
$queries = [];

View File

@ -23,10 +23,12 @@
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Schema;
use Illuminate\Support\Facades\Auth;
@ -56,6 +58,8 @@ public static function canAccess(): bool
public ?array $unitaPickerData = [];
public string $unitaVisibilita = 'attive';
/**
* @var array<int, string>
*/
@ -139,6 +143,8 @@ public function mount(): void
}
$this->stabileId = (int) $stabileId;
$this->unitaVisibilita = $this->normalizeUnitaVisibilita(request()->query('visibilita'));
$this->refreshUnitaOptions();
$this->unitaId = $this->pickUnitaId(request()->integer('unita_id'));
@ -240,6 +246,60 @@ protected function getHeaderActions(): array
$nextId = $this->getNextUnitaId();
return [
Action::make('modifica_unita')
->label('Modifica unità')
->icon('heroicon-o-pencil-square')
->modalWidth('5xl')
->visible(fn(): bool => (bool) $this->unitaId && $this->unita instanceof UnitaImmobiliare)
->fillForm(fn(): array=> $this->getEditUnitaFormData())
->form([
TextInput::make('codice_unita')
->label('Codice unità')
->maxLength(100)
->columnSpan(3),
TextInput::make('denominazione')
->label('Denominazione')
->maxLength(255)
->columnSpan(5),
TextInput::make('tipo_unita')
->label('Tipologia')
->maxLength(100)
->columnSpan(2),
TextInput::make('categoria_catastale')
->label('Categoria catastale')
->maxLength(20)
->columnSpan(2),
TextInput::make('scala')
->label('Scala')
->maxLength(50)
->columnSpan(2),
TextInput::make('piano')
->label('Piano')
->maxLength(50)
->columnSpan(2),
TextInput::make('interno')
->label('Interno')
->maxLength(50)
->columnSpan(2),
TextInput::make('stato_occupazione')
->label('Stato occupazione')
->maxLength(100)
->columnSpan(3),
TextInput::make('utilizzo_attuale')
->label('Utilizzo attuale')
->maxLength(100)
->columnSpan(3),
Toggle::make('attiva')
->label('Unità attiva')
->columnSpan(2),
Textarea::make('note')
->label('Note')
->rows(4)
->columnSpanFull(),
])
->action(function (array $data): void {
$this->saveEditedUnita($data);
}),
Action::make('prev_unita')
->label('Precedente')
->icon('heroicon-o-arrow-left')
@ -529,15 +589,32 @@ public function setTab(string $tab): void
$this->tab = $tab;
}
public function setUnitaVisibilita(string $visibilita): void
{
$this->unitaVisibilita = $this->normalizeUnitaVisibilita($visibilita);
$this->refreshUnitaOptions();
$this->unitaId = $this->pickUnitaId($this->unitaId);
$this->getSchema('unitaPicker')?->fill([
'unita_id' => $this->unitaId,
]);
if ($this->unitaId) {
$this->redirect($this->makeSelfUrl($this->unitaId));
return;
}
$this->loadUnita();
}
protected function refreshUnitaOptions(): void
{
$unita = UnitaImmobiliare::query()
->where('stabile_id', $this->stabileId)
$unita = $this->buildUnitaIndexQuery()
->orderBy('palazzina_id')
->orderBy('scala')
->orderBy('piano')
->orderBy('id')
->get(['id', 'palazzina_id', 'codice_unita', 'denominazione', 'scala', 'piano', 'interno']);
->get(['id', 'palazzina_id', 'codice_unita', 'denominazione', 'scala', 'piano', 'interno', 'deleted_at']);
$unita = $unita->filter(function (UnitaImmobiliare $u): bool {
$denominazione = trim((string) ($u->denominazione ?? ''));
@ -649,6 +726,9 @@ protected function refreshUnitaOptions(): void
if (is_string($owner) && trim($owner) !== '') {
$label .= ' — ' . trim($owner);
}
if ($u->trashed()) {
$label .= ' [archiviata]';
}
return [(int) $u->id => $label];
})
->all();
@ -730,7 +810,8 @@ protected function loadUnita(): void
return;
}
$this->unita = UnitaImmobiliare::with([
$this->unita = UnitaImmobiliare::withTrashed()
->with([
'palazzinaObj',
'soggetti',
'stabile',
@ -758,6 +839,79 @@ protected function loadUnita(): void
$this->tab = 'riepilogo';
}
protected function buildUnitaIndexQuery()
{
$query = UnitaImmobiliare::query()
->where('stabile_id', $this->stabileId);
return match ($this->unitaVisibilita) {
'tutte' => $query->withTrashed(),
'archiviate' => $query->onlyTrashed(),
default => $query,
};
}
protected function normalizeUnitaVisibilita(mixed $value): string
{
$candidate = strtolower(trim((string) $value));
return in_array($candidate, ['attive', 'tutte', 'archiviate'], true)
? $candidate
: 'attive';
}
protected function getEditUnitaFormData(): array
{
if (! $this->unita) {
return [];
}
return [
'codice_unita' => (string) ($this->unita->codice_unita ?? ''),
'denominazione' => (string) ($this->unita->denominazione ?? ''),
'tipo_unita' => (string) ($this->unita->tipo_unita ?? ''),
'categoria_catastale' => (string) ($this->unita->categoria_catastale ?? ''),
'scala' => (string) ($this->unita->scala ?? ''),
'piano' => (string) ($this->unita->piano ?? ''),
'interno' => (string) ($this->unita->interno ?? ''),
'stato_occupazione' => (string) ($this->unita->stato_occupazione ?? ''),
'utilizzo_attuale' => (string) ($this->unita->utilizzo_attuale ?? ''),
'attiva' => (bool) ($this->unita->attiva ?? true),
'note' => (string) ($this->unita->note ?? ''),
];
}
protected function saveEditedUnita(array $data): void
{
if (! $this->unita) {
return;
}
$this->unita->fill([
'codice_unita' => $this->normalizeOptionalString($data['codice_unita'] ?? null),
'denominazione' => $this->normalizeOptionalString($data['denominazione'] ?? null),
'tipo_unita' => $this->normalizeOptionalString($data['tipo_unita'] ?? null),
'categoria_catastale' => $this->normalizeOptionalString($data['categoria_catastale'] ?? null),
'scala' => $this->normalizeOptionalString($data['scala'] ?? null),
'piano' => $this->normalizeOptionalString($data['piano'] ?? null),
'interno' => $this->normalizeOptionalString($data['interno'] ?? null),
'stato_occupazione' => $this->normalizeOptionalString($data['stato_occupazione'] ?? null),
'utilizzo_attuale' => $this->normalizeOptionalString($data['utilizzo_attuale'] ?? null),
'attiva' => (bool) ($data['attiva'] ?? false),
'note' => $this->normalizeOptionalString($data['note'] ?? null),
]);
$this->unita->updated_by = Auth::id();
$this->unita->save();
$this->refreshUnitaOptions();
$this->loadUnita();
Notification::make()
->title('Unità aggiornata')
->success()
->send();
}
public function hydrateNominativiStorici(): void
{
$this->nominativiStorici = [];

View File

@ -6,8 +6,8 @@
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\Stabile;
use App\Support\GoogleAccountStore;
use App\Models\User;
use App\Support\GoogleAccountStore;
use App\Support\StabileContext;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr;

View File

@ -102,7 +102,18 @@ protected static function booted(): void
return;
}
foreach (['documenti', 'temp/upload', 'temp/processing', 'logs'] as $folder) {
foreach ([
'documenti',
'rubrica',
'fe',
'prodotti',
'comunicazioni',
'tecnorepair/imports',
'tecnorepair/allegati',
'temp/upload',
'temp/processing',
'logs',
] as $folder) {
Storage::disk('local')->makeDirectory($base . '/' . $folder);
}
} catch (\Throwable) {

View File

@ -1,5 +1,4 @@
<?php
namespace App\Services\Anagrafiche;
use App\Models\RubricaUniversale;

View File

@ -1,5 +1,4 @@
<?php
namespace App\Services\Support;
use App\Models\CommunicationMessage;
@ -51,7 +50,7 @@ public function importMailbox(Stabile $stabile, array $mailbox, int $maxMessages
->get('https://gmail.googleapis.com/gmail/v1/users/me/messages', array_filter([
'maxResults' => max(1, min($maxMessages, 50)),
'q' => $query !== '' ? $query : null,
], static fn ($value) => $value !== null));
], static fn($value) => $value !== null));
if (! $response->successful()) {
throw new RuntimeException('Gmail API non disponibile: ' . $response->body());

View File

@ -1,5 +1,4 @@
<?php
namespace App\Support;
use App\Models\Amministratore;
@ -249,13 +248,13 @@ public function resolveAccessToken(Amministratore $amministratore, ?string $acco
'label' => $account['label'] ?? null,
'email' => $account['email'] ?? null,
'name' => $account['name'] ?? null,
'google_user_id'=> $account['google_user_id'] ?? null,
'google_user_id' => $account['google_user_id'] ?? null,
'access_token' => $newAccessToken,
'refresh_token' => trim((string) Arr::get($payload, 'refresh_token', '')) ?: $refreshToken,
'expires_in' => (int) Arr::get($payload, 'expires_in', $account['expires_in'] ?? 3600),
'refreshed_at' => now()->toDateTimeString(),
'scopes' => $account['scopes'] ?? self::SCOPES,
], static fn ($value) => $value !== null), ! empty($account['is_default']));
], static fn($value) => $value !== null), ! empty($account['is_default']));
return $newAccessToken;
}
@ -280,7 +279,7 @@ private function normalizeAccountPayload(array $account, string $key): array
'expires_in' => (int) ($account['expires_in'] ?? 0),
'connected_at' => $account['connected_at'] ?? null,
'refreshed_at' => $account['refreshed_at'] ?? null,
'disconnected_at'=> $account['disconnected_at'] ?? null,
'disconnected_at' => $account['disconnected_at'] ?? null,
'scopes' => array_values(array_filter((array) ($account['scopes'] ?? []))),
];
}

View File

@ -1,11 +1,11 @@
{
"resources/css/app.css": {
"file": "assets/app-BGt-S40c.css",
"file": "assets/app-tgbzBtFO.css",
"src": "resources/css/app.css",
"isEntry": true
},
"resources/css/filament/admin/theme.css": {
"file": "assets/theme-D-E95I9r.css",
"file": "assets/theme-EkdZtrh_.css",
"src": "resources/css/filament/admin/theme.css",
"isEntry": true
},

View File

@ -16,6 +16,7 @@
<a href="{{ $this->getTicketsUrl() }}" class="inline-flex items-center rounded-md bg-gray-800 px-3 py-1.5 text-xs font-medium text-white hover:bg-gray-700">Ticket operativi</a>
<a href="{{ $this->getCollaboratoriUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Collaboratori</a>
<a href="{{ $this->getRubricaUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Rubrica clienti</a>
<a href="{{ $this->getImpostazioniUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Impostazioni</a>
<a href="{{ $this->getProdottiUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Prodotti</a>
</div>
</div>

View File

@ -15,6 +15,7 @@
<div class="flex flex-wrap gap-2">
<a href="{{ \App\Filament\Pages\Fornitore\LavorazioniOperative::getUrl(['fornitore' => $this->fornitoreId], panel: 'admin-filament') }}" class="inline-flex items-center rounded-md bg-primary-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-primary-500">Lavorazioni</a>
<a href="{{ $this->getCollaboratoriUrl() }}" class="inline-flex items-center rounded-md bg-gray-800 px-3 py-1.5 text-xs font-medium text-white hover:bg-gray-700">Collaboratori</a>
<a href="{{ $this->getImpostazioniUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Impostazioni</a>
<a href="{{ $this->getContabilitaUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">Contabilita</a>
</div>
</div>
@ -48,10 +49,14 @@
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left font-medium text-gray-600">Ticket</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Contatto</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Problema</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Operatore</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Num</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Ingresso</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Cliente</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Tel</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Difetto</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Modello</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Seriale</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Cod. prod</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Stato</th>
<th class="px-4 py-3 text-left font-medium text-gray-600">Aggiornato</th>
<th class="px-4 py-3"></th>
@ -59,34 +64,46 @@
</thead>
<tbody class="divide-y divide-gray-100 bg-white">
@forelse($rows as $row)
<tr>
<td class="px-4 py-3 align-top">
<div class="font-medium">#{{ $row['ticket_id'] }} - {{ $row['titolo'] }}</div>
<div class="text-xs text-gray-500">{{ $row['stabile'] }} · ingresso {{ $row['ingresso'] }}</div>
<tr class="{{ $row['row_class'] }}">
<td class="px-4 py-3 align-top font-medium">
{{ $row['numero'] }}
<div class="mt-1 text-[11px] text-slate-500">{{ $row['origine'] }}</div>
</td>
<td class="px-4 py-3 align-top text-gray-700">{{ $row['ingresso'] }}</td>
<td class="px-4 py-3 align-top">
<div>{{ $row['contatto'] }}</div>
<div class="text-xs text-gray-500">{{ $row['telefono'] !== '' ? $row['telefono'] : 'Telefono non disponibile' }}</div>
<div class="font-medium">{{ $row['cliente'] }}</div>
<div class="text-[11px] text-slate-500">{{ $row['stabile'] }}</div>
</td>
<td class="px-4 py-3 align-top">{{ $row['telefono'] !== '' ? $row['telefono'] : '-' }}</td>
<td class="px-4 py-3 align-top">{{ $row['difetto'] }}</td>
<td class="px-4 py-3 align-top">
<div>{{ $row['problema'] }}</div>
<div class="text-xs text-gray-500">Apparato: {{ $row['apparato'] }}</div>
<div>{{ $row['modello'] }}</div>
@if(($row['titolo'] ?? '') !== '')
<div class="text-[11px] text-slate-500">{{ $row['titolo'] }}</div>
@endif
</td>
<td class="px-4 py-3 align-top">{{ $row['operatore'] }}</td>
<td class="px-4 py-3 align-top">{{ $row['seriale'] }}</td>
<td class="px-4 py-3 align-top">{{ $row['cod_prodotto'] }}</td>
<td class="px-4 py-3 align-top">
<span class="inline-flex rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700">{{ $row['stato'] }}</span>
<span class="inline-flex rounded-full border px-2 py-1 text-xs font-medium {{ $row['badge_class'] }}">{{ $row['stato'] }}</span>
</td>
<td class="px-4 py-3 align-top text-gray-600">{{ $row['updated_at'] }}</td>
<td class="px-4 py-3 text-right align-top">
<div class="flex justify-end gap-2">
@if($row['row_type'] === 'ticket')
<button type="button" wire:click="openInterventoModal({{ (int) $row['id'] }})" class="inline-flex items-center rounded-md bg-gray-100 px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-200">Apri modal</button>
@endif
@if($row['url'])
<a href="{{ $row['url'] }}" class="inline-flex items-center rounded-md bg-primary-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-primary-500">Apri scheda</a>
@else
<a href="{{ $this->getImpostazioniUrl() }}" class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-300 hover:bg-slate-50">Gestisci import</a>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-10 text-center text-sm text-gray-500">Nessun intervento trovato per questo filtro.</td>
<td colspan="11" class="px-4 py-10 text-center text-sm text-gray-500">Nessun intervento trovato per questo filtro.</td>
</tr>
@endforelse
</tbody>

View File

@ -29,26 +29,17 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s
@php
$unitaOptions = $this->getUnitaOptions();
@endphp
@if(!$unita)
<div class="rounded-xl border border-dashed border-gray-200 bg-white p-6 text-center text-gray-500">Nessuna unità immobiliare trovata.</div>
@else
{{-- Header compatto --}}
<x-filament::section class="p-4!">
@php
$prevUrl = $this->getPrevUnitaUrl();
$nextUrl = $this->getNextUnitaUrl();
$visibilityOptions = [
'attive' => 'Attive',
'tutte' => 'Tutte',
'archiviate' => 'Archiviate',
];
@endphp
<div class="flex items-start justify-between gap-4 flex-wrap">
<div class="space-y-1">
<div class="space-y-1">
<div class="flex flex-wrap items-center gap-3 text-lg font-semibold text-gray-900">
<x-filament::icon icon="heroicon-o-home-modern" class="h-5 w-5 text-primary-600" />
<span class="text-primary-700">Unità immobiliare</span>
<x-filament::section class="p-4!">
<div class="flex flex-wrap items-end justify-between gap-4">
<div class="flex items-end gap-2">
<div class="flex flex-col items-center gap-1">
<span class="text-[10px] text-gray-500">Prec.</span>
@ -59,7 +50,7 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s
@endif
</div>
<div class="min-w-72 max-w-xl flex flex-col items-center gap-1">
<div class="min-w-72 max-w-xl flex flex-col items-start gap-1">
<span class="text-[10px] text-gray-500">Unità</span>
<div class="w-full">{{ $this->getSchema('unitaPicker') }}</div>
</div>
@ -73,6 +64,40 @@ class="inline-flex items-center gap-2 rounded-lg border px-3 py-2 text-sm font-s
@endif
</div>
</div>
<div class="flex flex-wrap items-center gap-2">
<span class="text-xs font-medium text-gray-500">Visibilità:</span>
@foreach($visibilityOptions as $key => $label)
<button
type="button"
wire:click="setUnitaVisibilita('{{ $key }}')"
class="rounded-lg border px-3 py-2 text-xs font-semibold transition {{ $this->unitaVisibilita === $key ? 'border-primary-300 bg-primary-50 text-primary-700 shadow-sm' : 'border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50' }}">
{{ $label }}
</button>
@endforeach
</div>
</div>
</x-filament::section>
@if(!$unita)
<div class="rounded-xl border border-dashed border-gray-200 bg-white p-6 text-center text-gray-500">Nessuna unità immobiliare trovata.</div>
@else
{{-- Header compatto --}}
<x-filament::section class="p-4!">
@php
$isArchivedUnita = method_exists($unita, 'trashed') && $unita->trashed();
@endphp
<div class="flex items-start justify-between gap-4 flex-wrap">
<div class="space-y-1">
<div class="space-y-1">
<div class="flex flex-wrap items-center gap-3 text-lg font-semibold text-gray-900">
<x-filament::icon icon="heroicon-o-home-modern" class="h-5 w-5 text-primary-600" />
<span class="text-primary-700">Unità immobiliare</span>
@if($isArchivedUnita)
<span class="rounded-full border border-amber-300 bg-amber-50 px-2 py-0.5 text-xs font-semibold text-amber-700">Archiviata</span>
@endif
</div>
</div>
@php