910 lines
90 KiB
PHP
Executable File
910 lines
90 KiB
PHP
Executable File
@php
|
|
$palazzine = $stabile->palazzine()
|
|
->with(['unitaImmobiliari' => function ($q) {
|
|
$q->with('soggetti')->orderBy('scala')->orderBy('piano')->orderBy('interno');
|
|
}])
|
|
->orderBy('codice_palazzina')
|
|
->get();
|
|
|
|
$unitaAll = $stabile->unitaImmobiliari()->with('soggetti')->get();
|
|
$fallbackGroups = collect();
|
|
if ($palazzine->isEmpty() && $unitaAll->isNotEmpty()) {
|
|
$fallbackGroups = $unitaAll->groupBy(fn($u) => strtoupper(trim($u->palazzina ?? '')))->sortKeys();
|
|
}
|
|
$unitaByPalCode = $unitaAll->groupBy(fn($u) => strtoupper(trim($u->palazzina ?? '')));
|
|
|
|
$formatSoggettoNome = static function ($soggetto) {
|
|
$nome = trim(($soggetto->nome ?? '') . ' ' . ($soggetto->cognome ?? ''));
|
|
return $nome !== '' ? $nome : ($soggetto->ragione_sociale ?? '—');
|
|
};
|
|
$ruoliProprietario = ['proprieta','proprietario','piena_proprieta','comproprietario','comproprieta','nudo_proprietario','nuda_proprieta','usufruttuario','usufrutto','diritto_abitazione','diritto_di_abitazione'];
|
|
$ruoliInquilino = ['locazione','inquilino','conduttore','locatario'];
|
|
$rolePriority = ['proprietario' => 0, 'inquilino' => 1, 'altro' => 2];
|
|
$badgePalette = [
|
|
'proprietario' => ['bg' => 'bg-emerald-50', 'text' => 'text-emerald-700', 'border' => 'border-emerald-200', 'icon' => 'fa-user-tie'],
|
|
'inquilino' => ['bg' => 'bg-sky-50', 'text' => 'text-sky-700', 'border' => 'border-sky-200', 'icon' => 'fa-user'],
|
|
'altro' => ['bg' => 'bg-slate-50', 'text' => 'text-slate-600', 'border' => 'border-slate-200', 'icon' => 'fa-user-circle'],
|
|
];
|
|
$categoriaColor = [
|
|
'A' => ['bg' => 'bg-emerald-50', 'text' => 'text-emerald-700', 'border' => 'border-emerald-200', 'icon' => 'fa-building'],
|
|
'B' => ['bg' => 'bg-sky-50', 'text' => 'text-sky-700', 'border' => 'border-sky-200', 'icon' => 'fa-warehouse'],
|
|
'C' => ['bg' => 'bg-amber-50', 'text' => 'text-amber-700', 'border' => 'border-amber-200', 'icon' => 'fa-store'],
|
|
'D' => ['bg' => 'bg-fuchsia-50', 'text' => 'text-fuchsia-700', 'border' => 'border-fuchsia-200', 'icon' => 'fa-industry'],
|
|
'E' => ['bg' => 'bg-rose-50', 'text' => 'text-rose-700', 'border' => 'border-rose-200', 'icon' => 'fa-landmark'],
|
|
'F' => ['bg' => 'bg-purple-50', 'text' => 'text-purple-700', 'border' => 'border-purple-200', 'icon' => 'fa-drafting-compass'],
|
|
'G' => ['bg' => 'bg-lime-50', 'text' => 'text-lime-700', 'border' => 'border-lime-200', 'icon' => 'fa-sitemap'],
|
|
'S' => ['bg' => 'bg-stone-50', 'text' => 'text-stone-600', 'border' => 'border-stone-200', 'icon' => 'fa-tree'],
|
|
];
|
|
$categoriaLegenda = [
|
|
'A' => ['label' => 'Gruppo A', 'descrizione' => 'Uso abitativo'],
|
|
'B' => ['label' => 'Gruppo B', 'descrizione' => 'Servizi collettivi'],
|
|
'C' => ['label' => 'Gruppo C', 'descrizione' => 'Depositi, box, negozi'],
|
|
'D' => ['label' => 'Gruppo D', 'descrizione' => 'Immobili a uso speciale'],
|
|
'E' => ['label' => 'Gruppo E', 'descrizione' => 'Immobili a destinazione particolare'],
|
|
'F' => ['label' => 'Gruppo F', 'descrizione' => 'Entità urbane particolari'],
|
|
'G' => ['label' => 'Gruppo G', 'descrizione' => 'Opere speciali e servizi'],
|
|
'S' => ['label' => 'Terreni', 'descrizione' => 'Terreni e aree scoperte'],
|
|
];
|
|
$legacyTipologie = [
|
|
'A' => 'Appartamento',
|
|
'B' => 'Autorimessa / Box',
|
|
'C' => 'Cantina / Deposito',
|
|
'D' => 'Locale commerciale',
|
|
'M' => 'Magazzino',
|
|
'S' => 'Soffitta / Sottotetto',
|
|
'L' => 'Locale tecnico',
|
|
];
|
|
|
|
$labelPiano = static function (int $piano): string {
|
|
return match ($piano) {
|
|
9 => 'Soffitte (+1)',
|
|
8 => 'Piano 8',
|
|
7 => 'Piano 7',
|
|
6 => 'Piano 6',
|
|
5 => 'Piano 5',
|
|
4 => 'Piano 4',
|
|
3 => 'Piano 3',
|
|
2 => 'Piano 2',
|
|
1 => 'Piano 1',
|
|
0 => 'Piano Terra',
|
|
-1 => 'Cantina (-1)',
|
|
default => 'Piano '.$piano,
|
|
};
|
|
};
|
|
|
|
$highlightPalazzinaId = isset($highlightPalazzina) ? (string) $highlightPalazzina : '';
|
|
$highlightPalazzinaCode = isset($highlightPalazzinaCode) ? strtoupper((string) $highlightPalazzinaCode) : '';
|
|
$highlightScala = isset($highlightScala) ? strtoupper((string) $highlightScala) : '';
|
|
$highlightUnita = isset($highlightUnita) ? (string) $highlightUnita : '';
|
|
$highlightInterno = isset($highlightInterno) ? strtoupper((string) $highlightInterno) : '';
|
|
@endphp
|
|
|
|
<div class="rounded-2xl border bg-white p-4">
|
|
<div class="space-y-6" id="palazzine-wrapper"
|
|
data-highlight-palazzina-id="{{ $highlightPalazzinaId }}"
|
|
data-highlight-palazzina-code="{{ $highlightPalazzinaCode }}"
|
|
data-highlight-scala="{{ $highlightScala }}"
|
|
data-highlight-unita="{{ $highlightUnita }}"
|
|
data-highlight-interno="{{ $highlightInterno }}">
|
|
<style>
|
|
.palazzine-layout { align-items: start; }
|
|
.palazzine-layout .left-col { position: static; top: auto; align-self: start; }
|
|
.unit-card:hover { box-shadow: 0 10px 15px -3px rgb(15 23 42 / 0.12); }
|
|
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
</style>
|
|
|
|
<!-- KPI palazzine -->
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
|
|
<div class="bg-blue-50 border border-blue-200 shadow-sm p-4 rounded-lg">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-blue-100 rounded"><i class="fas fa-building text-blue-600"></i></div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-blue-600">Palazzine</p>
|
|
<p class="text-2xl font-bold text-blue-900">{{ $palazzine->isNotEmpty() ? $palazzine->count() : $fallbackGroups->count() }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-indigo-50 border border-indigo-200 shadow-sm p-4 rounded-lg">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-indigo-100 rounded"><i class="fas fa-layer-group text-indigo-600"></i></div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-indigo-600">Unità immobiliari</p>
|
|
<p class="text-2xl font-bold text-indigo-900">{{ $palazzine->isNotEmpty() ? $palazzine->flatMap->unitaImmobiliari->count() : $unitaAll->count() }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-green-50 border border-green-200 shadow-sm p-4 rounded-lg">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-green-100 rounded"><i class="fas fa-users text-green-600"></i></div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-green-600">Unità con soggetti</p>
|
|
@php $withSubjectsKpi = $palazzine->isNotEmpty()
|
|
? $palazzine->flatMap->unitaImmobiliari->filter(fn($u) => $u->soggetti->isNotEmpty())->count()
|
|
: $unitaAll->filter(fn($u) => $u->soggetti->isNotEmpty())->count(); @endphp
|
|
<p class="text-2xl font-bold text-green-900">{{ $withSubjectsKpi }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-amber-50 border border-amber-200 shadow-sm p-4 rounded-lg">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-amber-100 rounded"><i class="fas fa-wrench text-amber-600"></i></div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-amber-600">Locali tecnici</p>
|
|
@php $localiTecniciKpi = ($palazzine->isNotEmpty() ? $palazzine->flatMap->unitaImmobiliari : $unitaAll)
|
|
->filter(fn($u) => in_array($u->tipo_unita, ['locale_tecnico','centrale_termica','autorimessa','cantina','box','solaio']))
|
|
->count(); @endphp
|
|
<p class="text-2xl font-bold text-amber-900">{{ $localiTecniciKpi }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-6 lg:grid-cols-[18rem_minmax(0,1fr)] xl:grid-cols-[19rem_minmax(0,1fr)] 2xl:grid-cols-[20rem_minmax(0,1fr)] palazzine-layout">
|
|
<!-- SX elenco palazzine -->
|
|
<div class="left-col w-full">
|
|
<div class="bg-white rounded-lg border border-gray-200">
|
|
<div class="px-4 py-3 border-b">
|
|
<h4 class="text-md font-semibold text-gray-800"><i class="fas fa-city mr-2 text-blue-600"></i>Palazzine/Scale</h4>
|
|
</div>
|
|
<div class="p-2 space-y-1" id="palazzine-list">
|
|
@if($palazzine->isNotEmpty())
|
|
@foreach($palazzine as $pal)
|
|
@php
|
|
$pid = 'pal_'.$pal->id;
|
|
$label = $pal->denominazione ?: ($pal->codice_palazzina ?: '—');
|
|
$scalaCode = strtoupper(trim($pal->codice_palazzina ?? ''));
|
|
$unitaPal = $pal->unitaImmobiliari;
|
|
if ($unitaPal->isEmpty() && $scalaCode !== '') {
|
|
$unitaPal = $unitaByPalCode[$scalaCode] ?? collect();
|
|
}
|
|
$cnt = $unitaPal->count();
|
|
$isFirst = $loop->first;
|
|
@endphp
|
|
<button class="w-full text-left px-3 py-2 rounded hover:bg-blue-50 border flex items-center justify-between {{ $isFirst ? 'bg-blue-100 border-blue-300' : 'border-transparent' }}"
|
|
data-target="#{{ $pid }}" data-palazzina-id="{{ $pal->id }}" data-palazzina-code="{{ $scalaCode }}"
|
|
onclick="selectPalazzina(this)">
|
|
<span class="text-sm text-gray-800">{{ $label }} @if($pal->codice_palazzina && $pal->denominazione)<span class="text-xs text-gray-400 ml-1">(Cod {{ $pal->codice_palazzina }})</span>@endif</span>
|
|
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded">{{ $cnt }}</span>
|
|
</button>
|
|
@endforeach
|
|
@elseif($fallbackGroups->isNotEmpty())
|
|
@foreach($fallbackGroups as $codicePalazzina => $group)
|
|
@php $pid = 'fb_'.str_replace(' ','_', $codicePalazzina ?: 'ND'); @endphp
|
|
<button class="w-full text-left px-3 py-2 rounded hover:bg-blue-50 border flex items-center justify-between {{ $loop->first ? 'bg-blue-100 border-blue-300' : 'border-transparent' }}"
|
|
data-target="#{{ $pid }}" data-palazzina-code="{{ strtoupper($codicePalazzina ?: '') }}"
|
|
onclick="selectPalazzina(this)">
|
|
<span class="text-sm text-gray-800">{{ $codicePalazzina ?: '—' }} <span class="text-xs text-gray-400">(fallback)</span></span>
|
|
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded">{{ $group->count() }}</span>
|
|
</button>
|
|
@endforeach
|
|
@else
|
|
<div class="p-4 text-center text-gray-500">Nessuna palazzina. <a class="text-blue-600" href="{{ route('admin.palazzine.create', ['stabile_id' => $stabile->id]) }}">Crea</a></div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DX pannelli palazzine -->
|
|
<div class="flex-1 min-w-0 space-y-6">
|
|
@if($palazzine->isNotEmpty())
|
|
@foreach($palazzine as $pal)
|
|
@php
|
|
$pid = 'pal_'.$pal->id;
|
|
$panelClasses = 'palazzina-panel ' . ($loop->first ? '' : 'hidden');
|
|
$unitaPal = $pal->unitaImmobiliari;
|
|
if ($unitaPal->isEmpty()) {
|
|
$code = strtoupper(trim($pal->codice_palazzina ?? ''));
|
|
$unitaPal = $code !== '' ? ($unitaByPalCode[$code] ?? collect()) : collect();
|
|
}
|
|
$unitCount = $unitaPal->count();
|
|
$withSubjectsCount = $unitaPal->filter(fn($u) => $u->soggetti->isNotEmpty())->count();
|
|
$localiTecniciPalCount = $unitaPal->filter(fn($u) => in_array($u->tipo_unita, ['locale_tecnico','centrale_termica','autorimessa','cantina','box','solaio']))->count();
|
|
$libereCount = $unitaPal->where('stato_occupazione', 'libera')->count();
|
|
$occupateProp = $unitaPal->where('stato_occupazione', 'occupata_proprietario')->count();
|
|
$occupateInq = $unitaPal->where('stato_occupazione', 'occupata_inquilino')->count();
|
|
@endphp
|
|
<div id="{{ $pid }}" class="{{ $panelClasses }}">
|
|
<div class="bg-white rounded-lg border border-gray-200">
|
|
<div class="px-6 py-4 border-b flex flex-col md:flex-row md:items-center md:justify-between gap-3 palazzina-header">
|
|
<div>
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fas fa-city text-blue-600"></i>
|
|
<h3 class="text-lg font-semibold text-gray-900">Palazzina {{ $pal->denominazione ?: ($pal->codice_palazzina ?: '—') }}</h3>
|
|
@if($pal->codice_palazzina && $pal->denominazione)
|
|
<span class="text-xs text-gray-500">(Cod {{ $pal->codice_palazzina }})</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-2 text-xs md:text-sm">
|
|
<span class="px-2 py-0.5 rounded border border-blue-200 bg-blue-50 text-blue-800"><i class="fas fa-layer-group mr-1"></i>{{ $unitCount }} unità</span>
|
|
<span class="px-2 py-0.5 rounded border border-purple-200 bg-purple-50 text-purple-800"><i class="fas fa-users mr-1"></i>{{ $withSubjectsCount }} con soggetti</span>
|
|
<span class="px-2 py-0.5 rounded border border-amber-200 bg-amber-50 text-amber-700"><i class="fas fa-wrench mr-1"></i>{{ $localiTecniciPalCount }} locali tecnici</span>
|
|
<span class="px-2 py-0.5 rounded border border-emerald-200 bg-emerald-50 text-emerald-700"><i class="fas fa-user-check mr-1"></i>{{ $occupateProp }} proprietario</span>
|
|
<span class="px-2 py-0.5 rounded border border-sky-200 bg-sky-50 text-sky-700"><i class="fas fa-user mr-1"></i>{{ $occupateInq }} inquilino</span>
|
|
<span class="px-2 py-0.5 rounded border border-stone-200 bg-stone-50 text-stone-600"><i class="fas fa-door-open mr-1"></i>{{ $libereCount }} libere</span>
|
|
</div>
|
|
</div>
|
|
|
|
@php $perScala = $unitaPal->groupBy('scala'); @endphp
|
|
<div class="p-6 space-y-8">
|
|
@foreach($perScala as $scala => $unitaScala)
|
|
@php $pianiGrouped = $unitaScala->groupBy(fn($u) => (int) ($u->piano ?? 0))->sortKeysDesc(); @endphp
|
|
<div>
|
|
<h4 class="text-md font-medium text-gray-700 mb-3"><i class="fas fa-stairs mr-2"></i>Scala {{ $scala ?? '-' }}</h4>
|
|
<div class="flex flex-col gap-6">
|
|
@foreach($pianiGrouped as $piano => $unitaPiano)
|
|
<div class="border border-gray-200 rounded-lg">
|
|
<div class="bg-gray-50 px-3 py-2 border-b text-sm text-gray-700 font-semibold">{{ $labelPiano((int) $piano) }}</div>
|
|
<div class="p-4">
|
|
@if($unitaPiano->isNotEmpty())
|
|
<div class="relative group/floor">
|
|
<button type="button"
|
|
onclick="scrollFloor(this, 'left')"
|
|
class="absolute left-0 top-1/2 -translate-y-1/2 -ml-2 z-20 w-8 h-8 rounded-full bg-white/95 shadow-md border border-gray-200 flex items-center justify-center text-gray-700 hover:bg-gray-50 focus:outline-none opacity-0 group-hover/floor:opacity-100 transition-opacity duration-200">
|
|
<i class="fas fa-chevron-left text-xs"></i>
|
|
</button>
|
|
<div class="overflow-x-auto pb-3 -mx-4 px-4 no-scrollbar scroll-smooth snap-x snap-mandatory">
|
|
<div class="flex flex-row flex-nowrap gap-5" style="width: max-content; min-width: 100%;">
|
|
@foreach($unitaPiano->sortBy(fn ($u) => \App\Support\InternoSort::key($u->interno !== null ? (string) $u->interno : null)) as $u)
|
|
@php
|
|
$categoriaCompleta = strtoupper(trim($u->categoria_catastale ?? ''));
|
|
$categoriaGruppo = $categoriaCompleta !== '' ? substr($categoriaCompleta, 0, 1) : null;
|
|
$categoriaStyle = $categoriaColor[$categoriaGruppo] ?? ['bg' => 'bg-slate-50', 'text' => 'text-slate-600', 'border' => 'border-slate-200', 'icon' => 'fa-tags'];
|
|
$categoriaDescrizione = $categoriaLegenda[$categoriaGruppo]['descrizione'] ?? null;
|
|
$tipoUnitaLabel = $legacyTipologie[strtoupper(substr($u->tipo_unita ?? '', 0, 1))] ?? ucfirst(str_replace('_', ' ', $u->tipo_unita ?? 'ND'));
|
|
$utilizzo = $u->utilizzo_attuale ? ucfirst($u->utilizzo_attuale) : null;
|
|
$consistenza = $u->consistenza ? number_format((float) $u->consistenza, 2, ',', '.') : null;
|
|
$rendita = $u->rendita_catastale ? number_format((float) $u->rendita_catastale, 2, ',', '.') : null;
|
|
$occupanti = $u->soggetti->map(function ($soggetto) use ($formatSoggettoNome, $ruoliProprietario, $ruoliInquilino, $badgePalette, $rolePriority) {
|
|
$tipoPivot = strtolower(trim($soggetto->pivot?->tipo_diritto ?? ''));
|
|
$ruoloKey = in_array($tipoPivot, $ruoliProprietario, true)
|
|
? 'proprietario'
|
|
: (in_array($tipoPivot, $ruoliInquilino, true) ? 'inquilino' : 'altro');
|
|
$palette = $badgePalette[$ruoloKey] ?? $badgePalette['altro'];
|
|
return [
|
|
'nome' => $formatSoggettoNome($soggetto),
|
|
'ruolo_label' => $tipoPivot !== '' ? ucfirst(str_replace('_', ' ', $tipoPivot)) : ($ruoloKey === 'altro' ? 'Altro' : ucfirst($ruoloKey)),
|
|
'ruolo_key' => $ruoloKey,
|
|
'palette' => $palette,
|
|
'percentuale' => $soggetto->pivot?->percentuale_possesso,
|
|
'inizio' => optional(\Illuminate\Support\Carbon::make($soggetto->pivot?->data_inizio))?->format('d/m/Y'),
|
|
'fine' => optional(\Illuminate\Support\Carbon::make($soggetto->pivot?->data_fine))?->format('d/m/Y'),
|
|
];
|
|
})->sortBy(fn($item) => $rolePriority[$item['ruolo_key']] ?? 99)->values();
|
|
$statoLabel = match($u->stato_occupazione) {
|
|
'occupata_proprietario' => 'Proprietà',
|
|
'occupata_inquilino' => 'Locata',
|
|
'libera' => 'Libera',
|
|
default => ucfirst(str_replace('_', ' ', $u->stato_occupazione ?? 'Sconosciuta')),
|
|
};
|
|
$statoColor = match($u->stato_occupazione) {
|
|
'occupata_proprietario' => 'bg-emerald-100 text-emerald-700 border-emerald-200',
|
|
'occupata_inquilino' => 'bg-sky-100 text-sky-700 border-sky-200',
|
|
'libera' => 'bg-stone-100 text-stone-600 border-stone-200',
|
|
default => 'bg-slate-100 text-slate-600 border-slate-200',
|
|
};
|
|
@endphp
|
|
<div class="w-[320px] flex-shrink-0 border border-gray-200 rounded-lg bg-white shadow-sm p-4 space-y-4 unit-card snap-start"
|
|
data-unita-id="{{ $u->id }}"
|
|
data-scala="{{ strtoupper($scala ?? '') }}"
|
|
data-interno="{{ strtoupper($u->interno ?? '') }}">
|
|
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
|
|
<div class="flex items-start space-x-3">
|
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center border border-blue-200">
|
|
<span class="text-sm font-bold text-blue-700">{{ $u->interno ?: '—' }}</span>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-semibold text-gray-900">{{ $u->codice_unita ?: 'Unità '.$u->id }}</p>
|
|
<div class="mt-1 flex flex-wrap gap-2 text-xxs text-gray-600">
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-stairs mr-1"></i>Scala {{ $scala ?? '—' }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-layer-group mr-1"></i>{{ $labelPiano((int) ($u->piano ?? 0)) }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 {{ $categoriaStyle['bg'] }} {{ $categoriaStyle['text'] }} border {{ $categoriaStyle['border'] }} rounded-full" title="{{ $categoriaDescrizione }}"><i class="fas {{ $categoriaStyle['icon'] ?? 'fa-tags' }} mr-1"></i>{{ $categoriaCompleta ?: 'Cat. ND' }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-home mr-1"></i>{{ $tipoUnitaLabel }}</span>
|
|
@if($utilizzo)
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-briefcase mr-1"></i>{{ $utilizzo }}</span>
|
|
@endif
|
|
</div>
|
|
<div class="mt-2 flex flex-wrap items-center gap-2 text-xs">
|
|
<a href="{{ route('admin.unita-immobiliari.show', $u) }}" class="inline-flex items-center px-2 py-1 bg-blue-50 text-blue-700 border border-blue-200 rounded hover:bg-blue-100 transition"><i class="fas fa-eye mr-1"></i>Apri</a>
|
|
<a href="{{ route('admin.unita-immobiliari.edit', $u) }}" class="inline-flex items-center px-2 py-1 bg-emerald-50 text-emerald-700 border border-emerald-200 rounded hover:bg-emerald-100 transition"><i class="fas fa-pen mr-1"></i>Modifica</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col items-end space-y-2">
|
|
<span class="inline-flex items-center px-2 py-0.5 text-xxs font-semibold rounded border {{ $statoColor }}"><i class="fas fa-circle mr-1"></i>{{ $statoLabel }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-2 text-xxs text-gray-600">
|
|
@if($u->subalterno)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Subalterno:</span> {{ $u->subalterno }}</div>
|
|
@endif
|
|
@if($consistenza)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Consistenza:</span> {{ $consistenza }}</div>
|
|
@endif
|
|
@if($rendita)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Rendita:</span> € {{ $rendita }}</div>
|
|
@endif
|
|
@if($u->millesimi_generali)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Proprietà:</span> {{ number_format((float) $u->millesimi_generali, 3, ',', '.') }}</div>
|
|
@endif
|
|
@if($u->millesimi_riscaldamento)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Riscaldamento:</span> {{ number_format((float) $u->millesimi_riscaldamento, 3, ',', '.') }}</div>
|
|
@endif
|
|
@if($u->millesimi_acqua)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Acqua:</span> {{ number_format((float) $u->millesimi_acqua, 3, ',', '.') }}</div>
|
|
@endif
|
|
</div>
|
|
<div class="space-y-2">
|
|
@if($occupanti->isNotEmpty())
|
|
@foreach($occupanti as $occupante)
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 text-xs {{ $occupante['ruolo_key'] === 'proprietario' ? 'bg-emerald-50 border border-emerald-200 rounded p-2' : ($occupante['ruolo_key'] === 'inquilino' ? 'bg-sky-50 border border-sky-200 rounded p-2' : 'bg-slate-50 border border-slate-200 rounded p-2') }}">
|
|
<div class="flex items-center gap-2">
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded border {{ $occupante['palette']['border'] }} {{ $occupante['palette']['bg'] }} {{ $occupante['palette']['text'] }}">
|
|
<i class="fas {{ $occupante['palette']['icon'] }} mr-1"></i>{{ \Illuminate\Support\Str::limit($occupante['nome'], 60) }}
|
|
</span>
|
|
<span class="text-xxs text-slate-500">{{ $occupante['ruolo_label'] }}</span>
|
|
</div>
|
|
<div class="text-xxs text-right text-slate-500 space-y-1">
|
|
@if(!is_null($occupante['percentuale']))
|
|
<div>Quota {{ rtrim(rtrim(number_format((float) $occupante['percentuale'], 4, ',', '.'), '0'), ',') }}%</div>
|
|
@endif
|
|
@if($occupante['inizio'] || $occupante['fine'])
|
|
<div>
|
|
@if($occupante['inizio'] && $occupante['fine'])
|
|
{{ $occupante['inizio'] }} → {{ $occupante['fine'] }}
|
|
@elseif($occupante['inizio'])
|
|
dal {{ $occupante['inizio'] }}
|
|
@else
|
|
fino al {{ $occupante['fine'] }}
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@else
|
|
<p class="text-xs text-slate-500 italic">Nessun soggetto associato a questa unità.</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<button type="button"
|
|
onclick="scrollFloor(this, 'right')"
|
|
class="absolute right-0 top-1/2 -translate-y-1/2 -mr-2 z-20 w-8 h-8 rounded-full bg-white/95 shadow-md border border-gray-200 flex items-center justify-center text-gray-700 hover:bg-gray-50 focus:outline-none opacity-0 group-hover/floor:opacity-100 transition-opacity duration-200">
|
|
<i class="fas fa-chevron-right text-xs"></i>
|
|
</button>
|
|
</div>
|
|
@else
|
|
<p class="text-sm text-slate-500">Nessuna unità su questo piano.</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@elseif($fallbackGroups->isNotEmpty())
|
|
@foreach($fallbackGroups as $codicePalazzina => $group)
|
|
@php
|
|
$pid = 'fb_'.str_replace(' ','_', $codicePalazzina ?: 'ND');
|
|
$unitCount = $group->count();
|
|
$withSubjectsCount = $group->filter(fn($u) => $u->soggetti->isNotEmpty())->count();
|
|
$localiTecniciPalCount = $group->filter(fn($u) => in_array($u->tipo_unita, ['locale_tecnico','centrale_termica','autorimessa','cantina','box','solaio']))->count();
|
|
$libereCount = $group->where('stato_occupazione', 'libera')->count();
|
|
$occupateProp = $group->where('stato_occupazione', 'occupata_proprietario')->count();
|
|
$occupateInq = $group->where('stato_occupazione', 'occupata_inquilino')->count();
|
|
@endphp
|
|
<div id="{{ $pid }}" class="palazzina-panel {{ $loop->first ? '' : 'hidden' }}">
|
|
<div class="bg-white rounded-lg border border-gray-200">
|
|
<div class="px-6 py-4 border-b flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
|
<div>
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fas fa-city text-blue-600"></i>
|
|
<h3 class="text-lg font-semibold text-gray-900">Palazzina {{ $codicePalazzina ?: '—' }} <span class="text-gray-500 text-sm">(fallback)</span></h3>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-2 text-xs md:text-sm">
|
|
<span class="px-2 py-0.5 rounded border border-blue-200 bg-blue-50 text-blue-800"><i class="fas fa-layer-group mr-1"></i>{{ $unitCount }} unità</span>
|
|
<span class="px-2 py-0.5 rounded border border-purple-200 bg-purple-50 text-purple-800"><i class="fas fa-users mr-1"></i>{{ $withSubjectsCount }} con soggetti</span>
|
|
<span class="px-2 py-0.5 rounded border border-amber-200 bg-amber-50 text-amber-700"><i class="fas fa-wrench mr-1"></i>{{ $localiTecniciPalCount }} locali tecnici</span>
|
|
<span class="px-2 py-0.5 rounded border border-emerald-200 bg-emerald-50 text-emerald-700"><i class="fas fa-user-check mr-1"></i>{{ $occupateProp }} proprietario</span>
|
|
<span class="px-2 py-0.5 rounded border border-sky-200 bg-sky-50 text-sky-700"><i class="fas fa-user mr-1"></i>{{ $occupateInq }} inquilino</span>
|
|
<span class="px-2 py-0.5 rounded border border-stone-200 bg-stone-50 text-stone-600"><i class="fas fa-door-open mr-1"></i>{{ $libereCount }} libere</span>
|
|
</div>
|
|
</div>
|
|
@php $perScala = $group->groupBy('scala'); @endphp
|
|
<div class="p-6 space-y-8">
|
|
@foreach($perScala as $scala => $unitaScala)
|
|
@php $pianiGrouped = $unitaScala->groupBy(fn($u) => (int) ($u->piano ?? 0))->sortKeysDesc(); @endphp
|
|
<div>
|
|
<h4 class="text-md font-medium text-gray-700 mb-3"><i class="fas fa-stairs mr-2"></i>Scala {{ $scala ?? '-' }}</h4>
|
|
<div class="flex flex-col gap-6">
|
|
@foreach($pianiGrouped as $piano => $unitaPiano)
|
|
<div class="border border-gray-200 rounded-lg">
|
|
<div class="bg-gray-50 px-3 py-2 border-b text-sm text-gray-700 font-semibold">{{ $labelPiano((int) $piano) }}</div>
|
|
<div class="p-4">
|
|
@if($unitaPiano->isNotEmpty())
|
|
<div class="relative group/floor">
|
|
<button type="button"
|
|
onclick="scrollFloor(this, 'left')"
|
|
class="absolute left-0 top-1/2 -translate-y-1/2 -ml-2 z-20 w-8 h-8 rounded-full bg-white/95 shadow-md border border-gray-200 flex items-center justify-center text-gray-700 hover:bg-gray-50 focus:outline-none opacity-0 group-hover/floor:opacity-100 transition-opacity duration-200">
|
|
<i class="fas fa-chevron-left text-xs"></i>
|
|
</button>
|
|
<div class="overflow-x-auto pb-3 -mx-4 px-4 no-scrollbar scroll-smooth snap-x snap-mandatory">
|
|
<div class="flex flex-row flex-nowrap gap-5" style="width: max-content; min-width: 100%;">
|
|
@foreach($unitaPiano->sortBy(fn ($u) => \App\Support\InternoSort::key($u->interno !== null ? (string) $u->interno : null)) as $u)
|
|
@php
|
|
$categoriaCompleta = strtoupper(trim($u->categoria_catastale ?? ''));
|
|
$categoriaGruppo = $categoriaCompleta !== '' ? substr($categoriaCompleta, 0, 1) : null;
|
|
$categoriaStyle = $categoriaColor[$categoriaGruppo] ?? ['bg' => 'bg-slate-50', 'text' => 'text-slate-600', 'border' => 'border-slate-200', 'icon' => 'fa-tags'];
|
|
$categoriaDescrizione = $categoriaLegenda[$categoriaGruppo]['descrizione'] ?? null;
|
|
$tipoUnitaLabel = $legacyTipologie[strtoupper(substr($u->tipo_unita ?? '', 0, 1))] ?? ucfirst(str_replace('_', ' ', $u->tipo_unita ?? 'ND'));
|
|
$utilizzo = $u->utilizzo_attuale ? ucfirst($u->utilizzo_attuale) : null;
|
|
$consistenza = $u->consistenza ? number_format((float) $u->consistenza, 2, ',', '.') : null;
|
|
$rendita = $u->rendita_catastale ? number_format((float) $u->rendita_catastale, 2, ',', '.') : null;
|
|
$occupanti = $u->soggetti->map(function ($soggetto) use ($formatSoggettoNome, $ruoliProprietario, $ruoliInquilino, $badgePalette, $rolePriority) {
|
|
$tipoPivot = strtolower(trim($soggetto->pivot?->tipo_diritto ?? ''));
|
|
$ruoloKey = in_array($tipoPivot, $ruoliProprietario, true)
|
|
? 'proprietario'
|
|
: (in_array($tipoPivot, $ruoliInquilino, true) ? 'inquilino' : 'altro');
|
|
$palette = $badgePalette[$ruoloKey] ?? $badgePalette['altro'];
|
|
return [
|
|
'nome' => $formatSoggettoNome($soggetto),
|
|
'ruolo_label' => $tipoPivot !== '' ? ucfirst(str_replace('_', ' ', $tipoPivot)) : ($ruoloKey === 'altro' ? 'Altro' : ucfirst($ruoloKey)),
|
|
'ruolo_key' => $ruoloKey,
|
|
'palette' => $palette,
|
|
'percentuale' => $soggetto->pivot?->percentuale_possesso,
|
|
'inizio' => optional(\Illuminate\Support\Carbon::make($soggetto->pivot?->data_inizio))?->format('d/m/Y'),
|
|
'fine' => optional(\Illuminate\Support\Carbon::make($soggetto->pivot?->data_fine))?->format('d/m/Y'),
|
|
];
|
|
})->sortBy(fn($item) => $rolePriority[$item['ruolo_key']] ?? 99)->values();
|
|
$statoLabel = match($u->stato_occupazione) {
|
|
'occupata_proprietario' => 'Proprietà',
|
|
'occupata_inquilino' => 'Locata',
|
|
'libera' => 'Libera',
|
|
default => ucfirst(str_replace('_', ' ', $u->stato_occupazione ?? 'Sconosciuta')),
|
|
};
|
|
$statoColor = match($u->stato_occupazione) {
|
|
'occupata_proprietario' => 'bg-emerald-100 text-emerald-700 border-emerald-200',
|
|
'occupata_inquilino' => 'bg-sky-100 text-sky-700 border-sky-200',
|
|
'libera' => 'bg-stone-100 text-stone-600 border-stone-200',
|
|
default => 'bg-slate-100 text-slate-600 border-slate-200',
|
|
};
|
|
@endphp
|
|
<div class="w-[320px] flex-shrink-0 border border-gray-200 rounded-lg bg-white shadow-sm p-4 space-y-4 unit-card snap-start"
|
|
data-unita-id="{{ $u->id }}"
|
|
data-scala="{{ strtoupper($scala ?? '') }}"
|
|
data-interno="{{ strtoupper($u->interno ?? '') }}">
|
|
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
|
|
<div class="flex items-start space-x-3">
|
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center border border-blue-200">
|
|
<span class="text-sm font-bold text-blue-700">{{ $u->interno ?: '—' }}</span>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-semibold text-gray-900">{{ $u->codice_unita ?: 'Unità '.$u->id }}</p>
|
|
<div class="mt-1 flex flex-wrap gap-2 text-xxs text-gray-600">
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-stairs mr-1"></i>Scala {{ $scala ?? '—' }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-layer-group mr-1"></i>{{ $labelPiano((int) ($u->piano ?? 0)) }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 {{ $categoriaStyle['bg'] }} {{ $categoriaStyle['text'] }} border {{ $categoriaStyle['border'] }} rounded-full" title="{{ $categoriaDescrizione }}"><i class="fas {{ $categoriaStyle['icon'] ?? 'fa-tags' }} mr-1"></i>{{ $categoriaCompleta ?: 'Cat. ND' }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-home mr-1"></i>{{ $tipoUnitaLabel }}</span>
|
|
@if($utilizzo)
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-briefcase mr-1"></i>{{ $utilizzo }}</span>
|
|
@endif
|
|
</div>
|
|
<div class="mt-2 flex flex-wrap items-center gap-2 text-xs">
|
|
<a href="{{ route('admin.unita-immobiliari.show', $u) }}" class="inline-flex items-center px-2 py-1 bg-blue-50 text-blue-700 border border-blue-200 rounded hover:bg-blue-100 transition"><i class="fas fa-eye mr-1"></i>Apri</a>
|
|
<a href="{{ route('admin.unita-immobiliari.edit', $u) }}" class="inline-flex items-center px-2 py-1 bg-emerald-50 text-emerald-700 border border-emerald-200 rounded hover:bg-emerald-100 transition"><i class="fas fa-pen mr-1"></i>Modifica</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col items-end space-y-2">
|
|
<span class="inline-flex items-center px-2 py-0.5 text-xxs font-semibold rounded border {{ $statoColor }}"><i class="fas fa-circle mr-1"></i>{{ $statoLabel }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-2 text-xxs text-gray-600">
|
|
@if($u->subalterno)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Subalterno:</span> {{ $u->subalterno }}</div>
|
|
@endif
|
|
@if($consistenza)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Consistenza:</span> {{ $consistenza }}</div>
|
|
@endif
|
|
@if($rendita)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Rendita:</span> € {{ $rendita }}</div>
|
|
@endif
|
|
@if($u->millesimi_generali)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Proprietà:</span> {{ number_format((float) $u->millesimi_generali, 3, ',', '.') }}</div>
|
|
@endif
|
|
@if($u->millesimi_riscaldamento)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Riscaldamento:</span> {{ number_format((float) $u->millesimi_riscaldamento, 3, ',', '.') }}</div>
|
|
@endif
|
|
@if($u->millesimi_acqua)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Acqua:</span> {{ number_format((float) $u->millesimi_acqua, 3, ',', '.') }}</div>
|
|
@endif
|
|
</div>
|
|
<div class="space-y-2">
|
|
@if($occupanti->isNotEmpty())
|
|
@foreach($occupanti as $occupante)
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 text-xs {{ $occupante['ruolo_key'] === 'proprietario' ? 'bg-emerald-50 border border-emerald-200 rounded p-2' : ($occupante['ruolo_key'] === 'inquilino' ? 'bg-sky-50 border border-sky-200 rounded p-2' : 'bg-slate-50 border border-slate-200 rounded p-2') }}">
|
|
<div class="flex items-center gap-2">
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded border {{ $occupante['palette']['border'] }} {{ $occupante['palette']['bg'] }} {{ $occupante['palette']['text'] }}">
|
|
<i class="fas {{ $occupante['palette']['icon'] }} mr-1"></i>{{ \Illuminate\Support\Str::limit($occupante['nome'], 60) }}
|
|
</span>
|
|
<span class="text-xxs text-slate-500">{{ $occupante['ruolo_label'] }}</span>
|
|
</div>
|
|
<div class="text-xxs text-right text-slate-500 space-y-1">
|
|
@if(!is_null($occupante['percentuale']))
|
|
<div>Quota {{ rtrim(rtrim(number_format((float) $occupante['percentuale'], 4, ',', '.'), '0'), ',') }}%</div>
|
|
@endif
|
|
@if($occupante['inizio'] || $occupante['fine'])
|
|
<div>
|
|
@if($occupante['inizio'] && $occupante['fine'])
|
|
{{ $occupante['inizio'] }} → {{ $occupante['fine'] }}
|
|
@elseif($occupante['inizio'])
|
|
dal {{ $occupante['inizio'] }}
|
|
@else
|
|
fino al {{ $occupante['fine'] }}
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@else
|
|
<p class="text-xs text-slate-500 italic">Nessun soggetto associato a questa unità.</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<button type="button"
|
|
onclick="scrollFloor(this, 'right')"
|
|
class="absolute right-0 top-1/2 -translate-y-1/2 -mr-2 z-20 w-8 h-8 rounded-full bg-white/95 shadow-md border border-gray-200 flex items-center justify-center text-gray-700 hover:bg-gray-50 focus:outline-none opacity-0 group-hover/floor:opacity-100 transition-opacity duration-200">
|
|
<i class="fas fa-chevron-right text-xs"></i>
|
|
</button>
|
|
</div>
|
|
@else
|
|
<p class="text-sm text-slate-500">Nessuna unità su questo piano.</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@elseif($fallbackGroups->isNotEmpty())
|
|
@foreach($fallbackGroups as $codicePalazzina => $group)
|
|
@php
|
|
$pid = 'fb_'.str_replace(' ','_', $codicePalazzina ?: 'ND');
|
|
$unitCount = $group->count();
|
|
$withSubjectsCount = $group->filter(fn($u) => $u->soggetti->isNotEmpty())->count();
|
|
$localiTecniciPalCount = $group->filter(fn($u) => in_array($u->tipo_unita, ['locale_tecnico','centrale_termica','autorimessa','cantina','box','solaio']))->count();
|
|
$libereCount = $group->where('stato_occupazione', 'libera')->count();
|
|
$occupateProp = $group->where('stato_occupazione', 'occupata_proprietario')->count();
|
|
$occupateInq = $group->where('stato_occupazione', 'occupata_inquilino')->count();
|
|
@endphp
|
|
<div id="{{ $pid }}" class="palazzina-panel {{ $loop->first ? '' : 'hidden' }}">
|
|
<div class="bg-white rounded-lg border border-gray-200">
|
|
<div class="px-6 py-4 border-b flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
|
<div>
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fas fa-city text-blue-600"></i>
|
|
<h3 class="text-lg font-semibold text-gray-900">Palazzina {{ $codicePalazzina ?: '—' }} <span class="text-gray-500 text-sm">(fallback)</span></h3>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-2 text-xs md:text-sm">
|
|
<span class="px-2 py-0.5 rounded border border-blue-200 bg-blue-50 text-blue-800"><i class="fas fa-layer-group mr-1"></i>{{ $unitCount }} unità</span>
|
|
<span class="px-2 py-0.5 rounded border border-purple-200 bg-purple-50 text-purple-800"><i class="fas fa-users mr-1"></i>{{ $withSubjectsCount }} con soggetti</span>
|
|
<span class="px-2 py-0.5 rounded border border-amber-200 bg-amber-50 text-amber-700"><i class="fas fa-wrench mr-1"></i>{{ $localiTecniciPalCount }} locali tecnici</span>
|
|
<span class="px-2 py-0.5 rounded border border-emerald-200 bg-emerald-50 text-emerald-700"><i class="fas fa-user-check mr-1"></i>{{ $occupateProp }} proprietario</span>
|
|
<span class="px-2 py-0.5 rounded border border-sky-200 bg-sky-50 text-sky-700"><i class="fas fa-user mr-1"></i>{{ $occupateInq }} inquilino</span>
|
|
<span class="px-2 py-0.5 rounded border border-stone-200 bg-stone-50 text-stone-600"><i class="fas fa-door-open mr-1"></i>{{ $libereCount }} libere</span>
|
|
</div>
|
|
</div>
|
|
@php $perScala = $group->groupBy('scala'); @endphp
|
|
<div class="p-6 space-y-8">
|
|
@foreach($perScala as $scala => $unitaScala)
|
|
@php $pianiGrouped = $unitaScala->groupBy(fn($u) => (int) ($u->piano ?? 0))->sortKeysDesc(); @endphp
|
|
<div>
|
|
<h4 class="text-md font-medium text-gray-700 mb-3"><i class="fas fa-stairs mr-2"></i>Scala {{ $scala ?? '-' }}</h4>
|
|
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-6">
|
|
@foreach($pianiGrouped as $piano => $unitaPiano)
|
|
<div class="border border-gray-200 rounded-lg">
|
|
<div class="bg-gray-50 px-3 py-2 border-b text-sm text-gray-700">{{ $labelPiano((int) $piano) }}</div>
|
|
<div class="p-3">
|
|
@if($unitaPiano->isNotEmpty())
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
|
@foreach($unitaPiano->sortBy(fn ($u) => \App\Support\InternoSort::key($u->interno !== null ? (string) $u->interno : null)) as $u)
|
|
@php
|
|
$categoriaCompleta = strtoupper(trim($u->categoria_catastale ?? ''));
|
|
$categoriaGruppo = $categoriaCompleta !== '' ? substr($categoriaCompleta, 0, 1) : null;
|
|
$categoriaStyle = $categoriaColor[$categoriaGruppo] ?? ['bg' => 'bg-slate-50', 'text' => 'text-slate-600', 'border' => 'border-slate-200', 'icon' => 'fa-tags'];
|
|
$categoriaDescrizione = $categoriaLegenda[$categoriaGruppo]['descrizione'] ?? null;
|
|
$tipoUnitaLabel = $legacyTipologie[strtoupper(substr($u->tipo_unita ?? '', 0, 1))] ?? ucfirst(str_replace('_', ' ', $u->tipo_unita ?? 'ND'));
|
|
$utilizzo = $u->utilizzo_attuale ? ucfirst($u->utilizzo_attuale) : null;
|
|
$consistenza = $u->consistenza ? number_format((float) $u->consistenza, 2, ',', '.') : null;
|
|
$rendita = $u->rendita_catastale ? number_format((float) $u->rendita_catastale, 2, ',', '.') : null;
|
|
$occupanti = $u->soggetti->map(function ($soggetto) use ($formatSoggettoNome, $ruoliProprietario, $ruoliInquilino, $badgePalette, $rolePriority) {
|
|
$tipoPivot = strtolower(trim($soggetto->pivot?->tipo_diritto ?? ''));
|
|
$ruoloKey = in_array($tipoPivot, $ruoliProprietario, true)
|
|
? 'proprietario'
|
|
: (in_array($tipoPivot, $ruoliInquilino, true) ? 'inquilino' : 'altro');
|
|
$palette = $badgePalette[$ruoloKey] ?? $badgePalette['altro'];
|
|
return [
|
|
'nome' => $formatSoggettoNome($soggetto),
|
|
'ruolo_label' => $tipoPivot !== '' ? ucfirst(str_replace('_', ' ', $tipoPivot)) : ($ruoloKey === 'altro' ? 'Altro' : ucfirst($ruoloKey)),
|
|
'ruolo_key' => $ruoloKey,
|
|
'palette' => $palette,
|
|
'percentuale' => $soggetto->pivot?->percentuale_possesso,
|
|
'inizio' => optional(\Illuminate\Support\Carbon::make($soggetto->pivot?->data_inizio))?->format('d/m/Y'),
|
|
'fine' => optional(\Illuminate\Support\Carbon::make($soggetto->pivot?->data_fine))?->format('d/m/Y'),
|
|
];
|
|
})->sortBy(fn($item) => $rolePriority[$item['ruolo_key']] ?? 99)->values();
|
|
$statoLabel = match($u->stato_occupazione) {
|
|
'occupata_proprietario' => 'Proprietà',
|
|
'occupata_inquilino' => 'Locata',
|
|
'libera' => 'Libera',
|
|
default => ucfirst(str_replace('_', ' ', $u->stato_occupazione ?? 'Sconosciuta')),
|
|
};
|
|
$statoColor = match($u->stato_occupazione) {
|
|
'occupata_proprietario' => 'bg-emerald-100 text-emerald-700 border-emerald-200',
|
|
'occupata_inquilino' => 'bg-sky-100 text-sky-700 border-sky-200',
|
|
'libera' => 'bg-stone-100 text-stone-600 border-stone-200',
|
|
default => 'bg-slate-100 text-slate-600 border-slate-200',
|
|
};
|
|
@endphp
|
|
<div class="border border-gray-200 rounded-lg bg-white shadow-sm p-4 space-y-4 unit-card"
|
|
data-unita-id="{{ $u->id }}"
|
|
data-scala="{{ strtoupper($scala ?? '') }}"
|
|
data-interno="{{ strtoupper($u->interno ?? '') }}">
|
|
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
|
|
<div class="flex items-start space-x-3">
|
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center border border-blue-200">
|
|
<span class="text-sm font-bold text-blue-700">{{ $u->interno ?: '—' }}</span>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-semibold text-gray-900">{{ $u->codice_unita ?: 'Unità '.$u->id }}</p>
|
|
<div class="mt-1 flex flex-wrap gap-2 text-xxs text-gray-600">
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-stairs mr-1"></i>Scala {{ $scala ?? '—' }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-layer-group mr-1"></i>{{ $labelPiano((int) ($u->piano ?? 0)) }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 {{ $categoriaStyle['bg'] }} {{ $categoriaStyle['text'] }} border {{ $categoriaStyle['border'] }} rounded-full" title="{{ $categoriaDescrizione }}"><i class="fas {{ $categoriaStyle['icon'] ?? 'fa-tags' }} mr-1"></i>{{ $categoriaCompleta ?: 'Cat. ND' }}</span>
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-home mr-1"></i>{{ $tipoUnitaLabel }}</span>
|
|
@if($utilizzo)
|
|
<span class="inline-flex items-center px-2 py-0.5 bg-slate-100 rounded-full border border-slate-200"><i class="fas fa-briefcase mr-1"></i>{{ $utilizzo }}</span>
|
|
@endif
|
|
</div>
|
|
<div class="mt-2 flex flex-wrap items-center gap-2 text-xs">
|
|
<a href="{{ route('admin.unita-immobiliari.show', $u) }}" class="inline-flex items-center px-2 py-1 bg-blue-50 text-blue-700 border border-blue-200 rounded hover:bg-blue-100 transition"><i class="fas fa-eye mr-1"></i>Apri</a>
|
|
<a href="{{ route('admin.unita-immobiliari.edit', $u) }}" class="inline-flex items-center px-2 py-1 bg-emerald-50 text-emerald-700 border border-emerald-200 rounded hover:bg-emerald-100 transition"><i class="fas fa-pen mr-1"></i>Modifica</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col items-end space-y-2">
|
|
<span class="inline-flex items-center px-2 py-0.5 text-xxs font-semibold rounded border {{ $statoColor }}"><i class="fas fa-circle mr-1"></i>{{ $statoLabel }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-2 text-xxs text-gray-600">
|
|
@if($u->subalterno)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Subalterno:</span> {{ $u->subalterno }}</div>
|
|
@endif
|
|
@if($consistenza)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Consistenza:</span> {{ $consistenza }}</div>
|
|
@endif
|
|
@if($rendita)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Rendita:</span> € {{ $rendita }}</div>
|
|
@endif
|
|
@if($u->millesimi_generali)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Proprietà:</span> {{ number_format((float) $u->millesimi_generali, 3, ',', '.') }}</div>
|
|
@endif
|
|
@if($u->millesimi_riscaldamento)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Riscaldamento:</span> {{ number_format((float) $u->millesimi_riscaldamento, 3, ',', '.') }}</div>
|
|
@endif
|
|
@if($u->millesimi_acqua)
|
|
<div class="px-2 py-1 bg-slate-50 border border-slate-200 rounded"><span class="font-semibold text-slate-700">Millesimi Acqua:</span> {{ number_format((float) $u->millesimi_acqua, 3, ',', '.') }}</div>
|
|
@endif
|
|
</div>
|
|
<div class="space-y-2">
|
|
@if($occupanti->isNotEmpty())
|
|
@foreach($occupanti as $occupante)
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 text-xs {{ $occupante['ruolo_key'] === 'proprietario' ? 'bg-emerald-50 border border-emerald-200 rounded p-2' : ($occupante['ruolo_key'] === 'inquilino' ? 'bg-sky-50 border border-sky-200 rounded p-2' : 'bg-slate-50 border border-slate-200 rounded p-2') }}">
|
|
<div class="flex items-center gap-2">
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded border {{ $occupante['palette']['border'] }} {{ $occupante['palette']['bg'] }} {{ $occupante['palette']['text'] }}">
|
|
<i class="fas {{ $occupante['palette']['icon'] }} mr-1"></i>{{ \Illuminate\Support\Str::limit($occupante['nome'], 60) }}
|
|
</span>
|
|
<span class="text-xxs text-slate-500">{{ $occupante['ruolo_label'] }}</span>
|
|
</div>
|
|
<div class="text-xxs text-right text-slate-500 space-y-1">
|
|
@if(!is_null($occupante['percentuale']))
|
|
<div>Quota {{ rtrim(rtrim(number_format((float) $occupante['percentuale'], 4, ',', '.'), '0'), ',') }}%</div>
|
|
@endif
|
|
@if($occupante['inizio'] || $occupante['fine'])
|
|
<div>
|
|
@if($occupante['inizio'] && $occupante['fine'])
|
|
{{ $occupante['inizio'] }} → {{ $occupante['fine'] }}
|
|
@elseif($occupante['inizio'])
|
|
dal {{ $occupante['inizio'] }}
|
|
@else
|
|
fino al {{ $occupante['fine'] }}
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@else
|
|
<p class="text-xs text-slate-500 italic">Nessun soggetto associato a questa unità.</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@else
|
|
<p class="text-sm text-slate-500">Nessuna unità su questo piano.</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@else
|
|
<div class="bg-white rounded-lg border border-gray-200 p-6 text-center text-slate-500">Nessuna palazzina o unità disponibili.</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function selectPalazzina(btn) {
|
|
const target = btn.getAttribute('data-target');
|
|
document.querySelectorAll('#palazzine-list button').forEach(b => {
|
|
b.classList.remove('bg-blue-100', 'border-blue-300');
|
|
b.classList.add('border-transparent');
|
|
});
|
|
btn.classList.add('bg-blue-100', 'border-blue-300');
|
|
document.querySelectorAll('.palazzina-panel').forEach(panel => panel.classList.add('hidden'));
|
|
const panelEl = document.querySelector(target);
|
|
if (panelEl) {
|
|
panelEl.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const wrapper = document.getElementById('palazzine-wrapper');
|
|
if (!wrapper) return;
|
|
|
|
const palId = wrapper.dataset.highlightPalazzinaId;
|
|
const palCode = wrapper.dataset.highlightPalazzinaCode;
|
|
const scala = wrapper.dataset.highlightScala;
|
|
const unitaId = wrapper.dataset.highlightUnita;
|
|
const interno = wrapper.dataset.highlightInterno;
|
|
|
|
if (palId || palCode) {
|
|
const btn = Array.from(document.querySelectorAll('#palazzine-list button')).find(b => {
|
|
return (palId && b.dataset.palazzinaId === palId) || (palCode && b.dataset.palazzinaCode === palCode);
|
|
});
|
|
if (btn) {
|
|
btn.click();
|
|
}
|
|
}
|
|
|
|
const cards = Array.from(document.querySelectorAll('.unit-card'));
|
|
const targetCard = cards.find(card => {
|
|
const matchScala = scala ? card.dataset.scala === scala : true;
|
|
const matchInterno = interno ? card.dataset.interno === interno : true;
|
|
const matchUnita = unitaId ? card.dataset.unitaId === unitaId : true;
|
|
return matchScala && matchInterno && matchUnita;
|
|
});
|
|
|
|
if (targetCard) {
|
|
targetCard.classList.add('ring', 'ring-2', 'ring-blue-500');
|
|
targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
|
|
// Implementazione Hover Spaziale Relazionale (Accanto / Sopra / Sotto)
|
|
cards.forEach(card => {
|
|
card.style.position = 'relative';
|
|
|
|
card.addEventListener('mouseenter', () => {
|
|
// Evidenzia unità attiva
|
|
card.classList.add('ring', 'ring-2', 'ring-blue-500', 'scale-[1.01]', 'z-10');
|
|
|
|
const currentFloorGrid = card.parentElement;
|
|
const currentFloor = currentFloorGrid.closest('.border.border-gray-200.rounded-lg');
|
|
if (!currentFloor) return;
|
|
|
|
// Individua i vicini sullo stesso piano (index in DOM)
|
|
const cardsOnFloor = Array.from(currentFloorGrid.querySelectorAll('.unit-card'));
|
|
const index = cardsOnFloor.indexOf(card);
|
|
|
|
// Vicino Sinistra
|
|
if (index > 0) {
|
|
highlightNeighbor(cardsOnFloor[index - 1], 'Fianco Sx', 'Accanto');
|
|
}
|
|
// Vicino Destra
|
|
if (index < cardsOnFloor.length - 1) {
|
|
highlightNeighbor(cardsOnFloor[index + 1], 'Fianco Dx', 'Accanto');
|
|
}
|
|
|
|
// Vicino Sopra (piano precedente nel DOM per ordinamento decrescente dei piani)
|
|
const floorAbove = currentFloor.previousElementSibling;
|
|
if (floorAbove && floorAbove.classList.contains('border')) {
|
|
const cardsAbove = Array.from(floorAbove.querySelectorAll('.unit-card'));
|
|
if (cardsAbove.length > 0) {
|
|
const targetAbove = cardsAbove[Math.min(index, cardsAbove.length - 1)];
|
|
highlightNeighbor(targetAbove, 'Sopra', 'Verticale');
|
|
}
|
|
}
|
|
|
|
// Vicino Sotto (piano successivo nel DOM)
|
|
const floorBelow = currentFloor.nextElementSibling;
|
|
if (floorBelow && floorBelow.classList.contains('border')) {
|
|
const cardsBelow = Array.from(floorBelow.querySelectorAll('.unit-card'));
|
|
if (cardsBelow.length > 0) {
|
|
const targetBelow = cardsBelow[Math.min(index, cardsBelow.length - 1)];
|
|
highlightNeighbor(targetBelow, 'Sotto', 'Verticale');
|
|
}
|
|
}
|
|
|
|
// Sfoca leggermente gli altri appartamenti non correlati per focalizzare lo sguardo
|
|
cards.forEach(c => {
|
|
if (c !== card && !c.classList.contains('ring-amber-500')) {
|
|
c.classList.add('opacity-40');
|
|
}
|
|
});
|
|
});
|
|
|
|
card.addEventListener('mouseleave', () => {
|
|
// Ripristina lo stato grafico originale
|
|
cards.forEach(c => {
|
|
c.classList.remove('ring', 'ring-2', 'ring-blue-500', 'ring-amber-500', 'scale-[1.01]', 'z-10', 'opacity-40');
|
|
const indicator = c.querySelector('.neighbor-indicator');
|
|
if (indicator) {
|
|
indicator.remove();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
function highlightNeighbor(element, label, type) {
|
|
if (!element) return;
|
|
element.classList.add('ring', 'ring-2', 'ring-amber-500', 'z-10');
|
|
|
|
// Crea etichetta badge fluttuante
|
|
if (!element.querySelector('.neighbor-indicator')) {
|
|
const indicator = document.createElement('span');
|
|
indicator.className = 'neighbor-indicator absolute top-2 right-2 inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold tracking-wider uppercase shadow-sm border ' +
|
|
(type === 'Verticale'
|
|
? 'bg-amber-100 text-amber-800 border-amber-300'
|
|
: 'bg-orange-100 text-orange-800 border-orange-300');
|
|
indicator.textContent = label;
|
|
element.appendChild(indicator);
|
|
}
|
|
}
|
|
});
|
|
|
|
function scrollFloor(btn, direction) {
|
|
const container = btn.parentElement.querySelector('.overflow-x-auto');
|
|
if (!container) return;
|
|
const cardWidth = 340; // 320px card + 20px gap
|
|
const scrollAmount = direction === 'left' ? -cardWidth : cardWidth;
|
|
container.scrollBy({ left: scrollAmount, behavior: 'smooth' });
|
|
}
|
|
</script>
|
|
</div>
|
|
</div>
|