netgescon-day0/docs/05-INTERFACCIA-UNIVERSALE.md

1149 lines
38 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 5. INTERFACCIA UNIVERSALE - GUIDA COMPLETA
## 📋 **INDICE CAPITOLO**
- [5.1 Architettura Layout](#51-architettura-layout)
- [5.2 Sistema Navigazione AJAX](#52-sistema-navigazione-ajax)
- [5.3 Helper Menu Personalizzati](#53-helper-menu-personalizzati)
- [5.4 Gestione Sidebar Dinamica](#54-gestione-sidebar-dinamica)
- [5.5 Dashboard Cards Interattive](#55-dashboard-cards-interattive)
- [5.6 Breadcrumb e Messaggi](#56-breadcrumb-e-messaggi)
- [5.7 Esempi Pratici](#57-esempi-pratici)
- [5.8 Best Practices](#58-best-practices)
---
## 5.1 Architettura Layout
### Layout Base Universale
**File**: `resources/views/layouts/universal.blade.php`
```blade
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $pageTitle ?? 'NetGescon' }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@stack('styles')
<!-- CSS per interfaccia universale -->
<style>
.sidebar {
background: #343a40;
min-height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 100;
padding-top: 70px;
}
.sidebar .nav-link {
color: #adb5bd;
padding: 0.75rem 1rem;
border-radius: 0.375rem;
margin: 0.125rem 0.5rem;
}
.sidebar .nav-link:hover {
background: #495057;
color: #fff;
}
.sidebar .nav-link.active {
background: #007bff;
color: #fff;
}
.main-content {
margin-left: 250px;
padding-top: 70px;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.sidebar.show {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
}
</style>
</head>
<body>
<div id="app">
<!-- Header Universale -->
@include('layouts.partials.header')
<!-- Sidebar Dinamica per Ruolo -->
@if($showSidebar ?? true)
<nav class="sidebar">
@include('layouts.partials.sidebar')
</nav>
@endif
<!-- Contenuto Principale -->
<main class="main-content">
<div class="container-fluid px-4">
<!-- Breadcrumb -->
@if($showBreadcrumb ?? true)
@include('layouts.partials.breadcrumb')
@endif
<!-- Alert Messaggi -->
@include('layouts.partials.alerts')
<!-- Contenuto Pagina -->
{{ $slot }}
</div>
</main>
</div>
@stack('scripts')
</body>
</html>
```
### Layout Base Universale V2 (Bootstrap + Sidebar NetGescon)
**File**: `resources/views/layouts/app-universal-v2.blade.php`
- Evoluzione del layout universale classico con sidebar brandizzata (`components.menu.sidebar-netgescon`) e top navbar gradiente.
- Include un contenitore `<aside id="sidebar" class="sidebar">` fisso largo 280px che rimane ancorato a sinistra mentre `.main-content` applica `margin-left: 280px` su desktop e torna full-width sotto i 992px.
- In modalità mobile (`max-width: 991.98px`) la sidebar viene nascosta tramite `transform: translateX(-100%)` e riappare quando il bottone `#mobile-menu-toggle` aggiunge la classe `.show`; contestualmente il layer `#sidebar-overlay` gestisce il focus dell'utente.
- Le viste universali devono rispettare il wrapper `div.container-fluid.p-4` fornito dal layout (es. `resources/views/anagrafica/show.blade.php`) per garantire che gradienti, sticky panel e colonne risultino perfettamente allineati.
#### Scheda Anagrafica Demo (allineamento grafico)
- **File**: `resources/views/anagrafica/show.blade.php`
- La scheda utilizza gli stessi token grafici della Rubrica (`netgescon-card`, `netgescon-btn`, chip e pill) per garantire coerenza visiva tra dashboard e dettagli soggetto.
- Le colonne sono organizzate con una griglia `grid grid-cols-1 2xl:grid-cols-4 gap-6` così da replicare limpostazione “Lista · Dettaglio · Rail azioni” descritta nelle bozze UI.
- I tab usano ancora il motore Bootstrap (`data-bs-toggle="pill"`) ma con pulsanti brandizzati (`scheda-tabs`) per mantenere funzionalità accessibile e look&feel identico alle schermate di riferimento.
- Ogni nuova sezione demo deve essere documentata accennando a: header hero con chip ruolo, blocchi indicatori uniformi e azioni contestuali sulla rail destra.
### Header Universale
**File**: `resources/views/layouts/partials/header.blade.php`
```blade
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<!-- Logo e Nome -->
<a class="navbar-brand" href="{{ route('dashboard') }}">
<i class="fas fa-building me-2"></i>
NetGescon
</a>
<!-- Bottone Mobile Menu -->
<button class="navbar-toggler d-lg-none" type="button" id="sidebarToggle">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Menu Utente -->
<div class="navbar-nav ms-auto">
<div class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>
{{ Auth::user()->name }}
<span class="badge bg-primary ms-1">
{{ Auth::user()->getRoleNames()->first() }}
</span>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="{{ route('profile.edit') }}">
<i class="fas fa-user-edit me-2"></i>Profilo
</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="dropdown-item">
<i class="fas fa-sign-out-alt me-2"></i>Logout
</button>
</form>
</li>
</ul>
</div>
</div>
</div>
</nav>
```
---
## 5.2 Sistema Navigazione AJAX
### JavaScript Navigazione Dashboard
**File**: `resources/js/dashboard-navigation.js`
```javascript
class DashboardNavigation {
constructor() {
this.currentSection = null;
this.init();
}
init() {
// Gestione click cards dashboard
$(document).on('click', '.clickable-card', (e) => {
const section = $(e.currentTarget).data('section');
this.loadSection(section);
});
// Gestione navigazione sidebar
$(document).on('click', '.dashboard-nav-link', (e) => {
e.preventDefault();
const section = $(e.currentTarget).data('section');
const action = $(e.currentTarget).data('action');
this.loadSection(section, action);
});
// Gestione breadcrumb
$(document).on('click', '.breadcrumb-back', (e) => {
e.preventDefault();
this.showDashboard();
});
// Gestione pulsanti azione
$(document).on('click', '.section-action-btn', (e) => {
e.preventDefault();
const action = $(e.currentTarget).data('action');
this.handleSectionAction(action);
});
// Mobile sidebar toggle
$('#sidebarToggle').on('click', () => {
$('.sidebar').toggleClass('show');
});
}
loadSection(section, action = null) {
// Mostra loader
this.showLoader();
// Nascondi dashboard principale
$('#main-dashboard').addClass('d-none');
$('#section-content').removeClass('d-none');
// Aggiorna sidebar attiva
this.updateSidebarActive(section);
// Aggiorna breadcrumb
this.updateBreadcrumb(section);
// Carica contenuto specifico
this.loadSectionContent(section, action);
}
loadSectionContent(section, action) {
const sectionHandlers = {
'stabili': () => this.loadStabiliContent(action),
'condomini': () => this.loadCondominiContent(action),
'unita': () => this.loadUnitaContent(action),
'soggetti': () => this.loadSoggettiContent(action),
'contabilita': () => this.loadContabilitaContent(action),
'documenti': () => this.loadDocumentiContent(action),
'tickets': () => this.loadTicketsContent(action)
};
if (sectionHandlers[section]) {
sectionHandlers[section]();
} else {
this.showError(`Sezione '${section}' non trovata`);
}
}
showDashboard() {
$('#section-content').addClass('d-none');
$('#main-dashboard').removeClass('d-none');
this.updateSidebarActive('dashboard');
this.updateBreadcrumb('dashboard');
}
updateSidebarActive(section) {
$('.sidebar .nav-link').removeClass('active');
$(`.sidebar .nav-link[data-section="${section}"]`).addClass('active');
}
updateBreadcrumb(section) {
const breadcrumbMap = {
'dashboard': 'Dashboard',
'stabili': 'Gestione Stabili',
'condomini': 'Anagrafica Condomini',
'unita': 'Unità Immobiliari',
'soggetti': 'Anagrafica Soggetti',
'contabilita': 'Contabilità',
'documenti': 'Gestione Documenti',
'tickets': 'Comunicazioni'
};
const breadcrumbHtml = `
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="#" class="breadcrumb-back">Dashboard</a>
</li>
<li class="breadcrumb-item active">${breadcrumbMap[section] || section}</li>
</ol>
</nav>
`;
$('#breadcrumb-container').html(breadcrumbHtml);
}
showLoader() {
const loader = `
<div class="d-flex justify-content-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Caricamento...</span>
</div>
</div>
`;
$('#section-content').html(loader);
}
showError(message) {
const error = `
<div class="alert alert-danger" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
${message}
</div>
`;
$('#section-content').html(error);
}
}
// Inizializza quando DOM è pronto
$(document).ready(() => {
new DashboardNavigation();
});
```
---
## 5.3 Helper Menu Personalizzati
### MenuHelper Class Completa
**File**: `app/Helpers/MenuHelper.php`
```php
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
class MenuHelper
{
/**
* Mapping completo permessi per menu
*/
private static $menuPermissions = [
'stabili' => ['admin', 'amministratore'],
'condomini' => ['admin', 'amministratore'],
'unita' => ['admin', 'amministratore'],
'soggetti' => ['admin', 'amministratore'],
'contabilita' => ['admin', 'amministratore'],
'documenti' => ['admin', 'amministratore', 'condomino'],
'tickets' => ['admin', 'amministratore', 'condomino'],
'comunicazioni' => ['admin', 'amministratore'],
'backup' => ['admin', 'amministratore'],
'utenti' => ['super-admin'],
'comuni' => ['super-admin'],
'configurazione' => ['super-admin'],
'log' => ['super-admin', 'admin']
];
/**
* Verifica se l'utente può accedere a un menu specifico
*/
public static function canUserAccessMenu(string $menuKey): bool
{
$user = Auth::user();
if (!$user) {
return false;
}
// SuperAdmin può accedere a tutto
if ($user->hasRole('super-admin')) {
return true;
}
if (!isset(self::$menuPermissions[$menuKey])) {
return false;
}
return $user->hasAnyRole(self::$menuPermissions[$menuKey]);
}
/**
* Costruisce menu sidebar completo per ruolo utente
*/
public static function buildSidebarMenu(): array
{
$user = Auth::user();
$userRoles = $user->getRoleNames()->toArray();
$menu = [];
// Menu base sempre presente
$menu[] = [
'label' => 'Dashboard',
'icon' => 'fas fa-tachometer-alt',
'route' => 'dashboard',
'active' => request()->routeIs('dashboard'),
'type' => 'route'
];
// Menu per SuperAdmin
if ($user->hasRole('super-admin')) {
$menu[] = [
'label' => 'Gestione Utenti',
'icon' => 'fas fa-users',
'route' => 'superadmin.users.index',
'active' => request()->routeIs('superadmin.users.*'),
'type' => 'route'
];
$menu[] = [
'label' => 'Comuni Italiani',
'icon' => 'fas fa-map',
'route' => 'superadmin.comuni.index',
'active' => request()->routeIs('superadmin.comuni.*'),
'type' => 'route'
];
$menu[] = [
'label' => 'Configurazione',
'icon' => 'fas fa-cog',
'route' => 'superadmin.config.index',
'active' => request()->routeIs('superadmin.config.*'),
'type' => 'route'
];
$menu[] = [
'label' => 'Log Sistema',
'icon' => 'fas fa-file-alt',
'route' => 'superadmin.logs.index',
'active' => request()->routeIs('superadmin.logs.*'),
'type' => 'route'
];
}
// Menu per Admin/Amministratore
if ($user->hasAnyRole(['admin', 'amministratore'])) {
// Sezione Anagrafica
$menu[] = [
'label' => 'ANAGRAFICA',
'type' => 'header'
];
if (self::canUserAccessMenu('stabili')) {
$menu[] = [
'label' => 'Gestione Stabili',
'icon' => 'fas fa-building',
'section' => 'stabili',
'type' => 'ajax',
'badge' => $user->stabiliAmministrati()->count()
];
}
if (self::canUserAccessMenu('condomini')) {
$menu[] = [
'label' => 'Anagrafica Condomini',
'icon' => 'fas fa-home',
'section' => 'condomini',
'type' => 'ajax'
];
}
if (self::canUserAccessMenu('soggetti')) {
$menu[] = [
'label' => 'Anagrafica Soggetti',
'icon' => 'fas fa-users',
'section' => 'soggetti',
'type' => 'ajax'
];
}
if (self::canUserAccessMenu('unita')) {
$menu[] = [
'label' => 'Unità Immobiliari',
'icon' => 'fas fa-door-open',
'section' => 'unita',
'type' => 'ajax'
];
}
// Sezione Gestionale
$menu[] = [
'label' => 'GESTIONALE',
'type' => 'header'
];
if (self::canUserAccessMenu('contabilita')) {
$menu[] = [
'label' => 'Contabilità',
'icon' => 'fas fa-calculator',
'section' => 'contabilita',
'type' => 'ajax'
];
}
if (self::canUserAccessMenu('documenti')) {
$menu[] = [
'label' => 'Gestione Documenti',
'icon' => 'fas fa-file-alt',
'section' => 'documenti',
'type' => 'ajax'
];
}
if (self::canUserAccessMenu('tickets')) {
$menu[] = [
'label' => 'Comunicazioni',
'icon' => 'fas fa-envelope',
'section' => 'tickets',
'type' => 'ajax',
'badge' => self::getTicketsApertiCount()
];
}
}
// Menu per Condomino
if ($user->hasRole('condomino')) {
$menu[] = [
'label' => 'Le Mie Proprietà',
'icon' => 'fas fa-home',
'route' => 'condomino.unita.index',
'active' => request()->routeIs('condomino.unita.*'),
'type' => 'route'
];
$menu[] = [
'label' => 'I Miei Documenti',
'icon' => 'fas fa-file',
'route' => 'condomino.documenti.index',
'active' => request()->routeIs('condomino.documenti.*'),
'type' => 'route'
];
$menu[] = [
'label' => 'Comunicazioni',
'icon' => 'fas fa-envelope',
'route' => 'condomino.tickets.index',
'active' => request()->routeIs('condomino.tickets.*'),
'type' => 'route'
];
}
return $menu;
}
/**
* Genera HTML per menu sidebar
*/
public static function renderSidebarMenu(): string
{
$menu = self::buildSidebarMenu();
$html = '<ul class="nav flex-column">';
foreach ($menu as $item) {
$html .= self::renderMenuItem($item);
}
$html .= '</ul>';
return $html;
}
/**
* Renderizza singolo elemento menu
*/
private static function renderMenuItem(array $item): string
{
if ($item['type'] === 'header') {
return '<li class="nav-item mt-3 mb-2">
<h6 class="sidebar-heading text-muted">' . $item['label'] . '</h6>
</li>';
}
$activeClass = ($item['active'] ?? false) ? 'active' : '';
$badge = isset($item['badge']) ? '<span class="badge bg-primary ms-auto">' . $item['badge'] . '</span>' : '';
if ($item['type'] === 'route') {
$href = route($item['route']);
$attributes = '';
} else {
$href = '#' . $item['section'];
$attributes = 'data-section="' . $item['section'] . '"';
}
return '<li class="nav-item">
<a class="nav-link ' . $activeClass . ' ' . ($item['type'] === 'ajax' ? 'dashboard-nav-link' : '') . '"
href="' . $href . '" ' . $attributes . '>
<i class="' . $item['icon'] . ' me-2"></i>
' . $item['label'] . '
' . $badge . '
</a>
</li>';
}
/**
* Conta tickets aperti per badge
*/
private static function getTicketsApertiCount(): int
{
$user = Auth::user();
if ($user->hasRole('condomino')) {
// Conta tickets del condomino
return \App\Models\Ticket::where('user_id', $user->id)
->where('stato', 'aperto')
->count();
}
if ($user->hasAnyRole(['admin', 'amministratore'])) {
// Conta tickets degli stabili amministrati
$stabiliIds = $user->stabiliAmministrati()->pluck('id');
return \App\Models\Ticket::whereIn('stabile_id', $stabiliIds)
->where('stato', 'aperto')
->count();
}
return 0;
}
}
```
---
## 5.4 Gestione Sidebar Dinamica
### Sidebar Blade Template
**File**: `resources/views/layouts/partials/sidebar.blade.php`
```blade
<div class="position-sticky pt-3">
{!! App\Helpers\MenuHelper::renderSidebarMenu() !!}
<!-- Informazioni Utente in fondo -->
<div class="mt-auto pt-4 border-top">
<div class="small text-muted px-3">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-user me-2"></i>
<span>{{ Auth::user()->name }}</span>
</div>
<div class="d-flex align-items-center">
<i class="fas fa-shield-alt me-2"></i>
<span class="badge bg-secondary">{{ Auth::user()->getRoleNames()->first() }}</span>
</div>
</div>
</div>
</div>
<!-- CSS aggiuntivo per sidebar -->
<style>
.sidebar-heading {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0 1rem;
}
.nav-link {
position: relative;
display: flex;
align-items: center;
transition: all 0.2s ease;
}
.nav-link:hover {
transform: translateX(5px);
}
.nav-link.active::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: #007bff;
}
.badge {
font-size: 0.65rem;
}
</style>
```
---
## 5.5 Dashboard Cards Interattive
### Dashboard Cards Template
**File**: `resources/views/admin/dashboard-cards.blade.php`
```blade
<div class="row g-4 mb-4">
<!-- Card Stabili -->
@if(App\Helpers\MenuHelper::canUserAccessMenu('stabili'))
<div class="col-md-6 col-lg-4">
<div class="card clickable-card h-100" data-section="stabili">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h5 class="card-title">
<i class="fas fa-building text-primary me-2"></i>
Gestione Stabili
</h5>
<p class="card-text text-muted">
Amministra anagrafica e dati degli stabili
</p>
</div>
<div class="text-end">
<h3 class="text-primary mb-0">{{ $stats['stabili_count'] ?? 0 }}</h3>
<small class="text-muted">Stabili</small>
</div>
</div>
<div class="mt-3">
<div class="d-flex justify-content-between">
<small class="text-muted">Attivi</small>
<small class="text-success">{{ $stats['stabili_attivi'] ?? 0 }}</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">
<i class="fas fa-mouse-pointer me-1"></i>
Clicca per gestire
</small>
</div>
</div>
</div>
@endif
<!-- Card Condomini -->
@if(App\Helpers\MenuHelper::canUserAccessMenu('condomini'))
<div class="col-md-6 col-lg-4">
<div class="card clickable-card h-100" data-section="condomini">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h5 class="card-title">
<i class="fas fa-home text-success me-2"></i>
Anagrafica Condomini
</h5>
<p class="card-text text-muted">
Gestisce proprietari e inquilini
</p>
</div>
<div class="text-end">
<h3 class="text-success mb-0">{{ $stats['condomini_count'] ?? 0 }}</h3>
<small class="text-muted">Condomini</small>
</div>
</div>
<div class="mt-3">
<div class="d-flex justify-content-between">
<small class="text-muted">Proprietari</small>
<small class="text-info">{{ $stats['proprietari_count'] ?? 0 }}</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">
<i class="fas fa-mouse-pointer me-1"></i>
Clicca per gestire
</small>
</div>
</div>
</div>
@endif
<!-- Card Contabilità -->
@if(App\Helpers\MenuHelper::canUserAccessMenu('contabilita'))
<div class="col-md-6 col-lg-4">
<div class="card clickable-card h-100" data-section="contabilita">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h5 class="card-title">
<i class="fas fa-calculator text-warning me-2"></i>
Contabilità
</h5>
<p class="card-text text-muted">
Movimenti e bilanci condominiali
</p>
</div>
<div class="text-end">
<h3 class="text-warning mb-0">{{ $stats['movimenti_count'] ?? 0 }}</h3>
<small class="text-muted">Movimenti</small>
</div>
</div>
<div class="mt-3">
<div class="d-flex justify-content-between">
<small class="text-muted">Saldo</small>
<small class="text-{{ ($stats['saldo_totale'] ?? 0) >= 0 ? 'success' : 'danger' }}">
€{{ number_format($stats['saldo_totale'] ?? 0, 2) }}
</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">
<i class="fas fa-mouse-pointer me-1"></i>
Clicca per gestire
</small>
</div>
</div>
</div>
@endif
<!-- Card Documenti -->
@if(App\Helpers\MenuHelper::canUserAccessMenu('documenti'))
<div class="col-md-6 col-lg-4">
<div class="card clickable-card h-100" data-section="documenti">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h5 class="card-title">
<i class="fas fa-file-alt text-info me-2"></i>
Gestione Documenti
</h5>
<p class="card-text text-muted">
Archivia e organizza documenti
</p>
</div>
<div class="text-end">
<h3 class="text-info mb-0">{{ $stats['documenti_count'] ?? 0 }}</h3>
<small class="text-muted">Documenti</small>
</div>
</div>
<div class="mt-3">
<div class="d-flex justify-content-between">
<small class="text-muted">Questo mese</small>
<small class="text-primary">{{ $stats['documenti_mese'] ?? 0 }}</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">
<i class="fas fa-mouse-pointer me-1"></i>
Clicca per gestire
</small>
</div>
</div>
</div>
@endif
<!-- Card Comunicazioni -->
@if(App\Helpers\MenuHelper::canUserAccessMenu('tickets'))
<div class="col-md-6 col-lg-4">
<div class="card clickable-card h-100" data-section="tickets">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h5 class="card-title">
<i class="fas fa-envelope text-danger me-2"></i>
Comunicazioni
</h5>
<p class="card-text text-muted">
Ticket e richieste condomini
</p>
</div>
<div class="text-end">
<h3 class="text-danger mb-0">{{ $stats['tickets_aperti'] ?? 0 }}</h3>
<small class="text-muted">Aperti</small>
</div>
</div>
<div class="mt-3">
<div class="d-flex justify-content-between">
<small class="text-muted">Totali</small>
<small class="text-secondary">{{ $stats['tickets_totali'] ?? 0 }}</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">
<i class="fas fa-mouse-pointer me-1"></i>
Clicca per gestire
</small>
</div>
</div>
</div>
@endif
</div>
<!-- CSS per cards interattive -->
<style>
.clickable-card {
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid #dee2e6;
}
.clickable-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
border-color: #007bff;
}
.clickable-card:active {
transform: translateY(-2px);
}
.card-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.card-text {
font-size: 0.9rem;
line-height: 1.4;
}
.card-footer {
border-top: 1px solid #f8f9fa;
padding: 0.5rem 1rem;
}
</style>
```
---
## 5.6 Breadcrumb e Messaggi
### Breadcrumb Template
**File**: `resources/views/layouts/partials/breadcrumb.blade.php`
```blade
<div id="breadcrumb-container" class="mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="{{ route('dashboard') }}">Dashboard</a>
</li>
@if(isset($breadcrumbs) && is_array($breadcrumbs))
@foreach($breadcrumbs as $breadcrumb)
@if($loop->last)
<li class="breadcrumb-item active" aria-current="page">
{{ $breadcrumb['label'] }}
</li>
@else
<li class="breadcrumb-item">
<a href="{{ $breadcrumb['url'] }}">{{ $breadcrumb['label'] }}</a>
</li>
@endif
@endforeach
@endif
</ol>
</nav>
</div>
```
### Alert Messaggi Template
**File**: `resources/views/layouts/partials/alerts.blade.php`
```blade
<!-- Messaggi di successo -->
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle me-2"></i>
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
<!-- Messaggi di errore -->
@if(session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle me-2"></i>
{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
<!-- Messaggi di warning -->
@if(session('warning'))
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
{{ session('warning') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
<!-- Messaggi di info -->
@if(session('info'))
<div class="alert alert-info alert-dismissible fade show" role="alert">
<i class="fas fa-info-circle me-2"></i>
{{ session('info') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
<!-- Errori di validazione -->
@if($errors->any())
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle me-2"></i>
<strong>Errori di validazione:</strong>
<ul class="mb-0 mt-2">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
```
---
## 5.7 Esempi Pratici
### Come Aggiungere Nuova Sezione Menu
**Passo 1**: Aggiungere permesso in `MenuHelper.php`
```php
private static $menuPermissions = [
// ...existing permissions...
'nuova_sezione' => ['admin', 'amministratore'],
];
```
**Passo 2**: Aggiungere voce menu in `buildSidebarMenu()`
```php
if (self::canUserAccessMenu('nuova_sezione')) {
$menu[] = [
'label' => 'Nuova Sezione',
'icon' => 'fas fa-star',
'section' => 'nuova_sezione',
'type' => 'ajax'
];
}
```
**Passo 3**: Aggiungere handler JavaScript in `dashboard-navigation.js`
```javascript
const sectionHandlers = {
// ...existing handlers...
'nuova_sezione': () => this.loadNuovaSezionePage(action),
};
loadNuovaSezionePage(action) {
let content = `
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Nuova Sezione</h2>
<button class="btn btn-primary section-action-btn" data-action="nuovo">
<i class="fas fa-plus me-2"></i>Nuovo
</button>
</div>
<div class="card">
<div class="card-body">
<p>Contenuto della nuova sezione</p>
</div>
</div>
`;
$('#section-content').html(content);
}
```
### Come Aggiungere Nuova Card Dashboard
**Passo 1**: Aggiungere in `dashboard-cards.blade.php`
```blade
@if(App\Helpers\MenuHelper::canUserAccessMenu('nuova_sezione'))
<div class="col-md-6 col-lg-4">
<div class="card clickable-card h-100" data-section="nuova_sezione">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h5 class="card-title">
<i class="fas fa-star text-purple me-2"></i>
Nuova Sezione
</h5>
<p class="card-text text-muted">
Descrizione della nuova sezione
</p>
</div>
<div class="text-end">
<h3 class="text-purple mb-0">{{ $stats['nuova_count'] ?? 0 }}</h3>
<small class="text-muted">Elementi</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<small class="text-muted">
<i class="fas fa-mouse-pointer me-1"></i>
Clicca per gestire
</small>
</div>
</div>
</div>
@endif
```
---
## 5.8 Best Practices
### Convenzioni Naming
- **Sezioni**: snake_case (es: `gestione_stabili`)
- **Icone**: Font Awesome 6 (es: `fas fa-building`)
- **Colori**: Bootstrap classes (es: `text-primary`)
- **Route**: kebab-case (es: `admin.stabili.index`)
### Performance
- **Caricamento lazy**: Solo il contenuto necessario
- **Cache menu**: MenuHelper usa cache per permessi
- **Minify assets**: Vite ottimizza JS/CSS
- **AJAX progressive**: Carica solo sezioni attive
### Responsive Design
- **Mobile-first**: Bootstrap 5 grid system
- **Sidebar collapsible**: Hidden su mobile
- **Touch-friendly**: Buttons e cards ottimizzati
- **Breakpoints**: sm, md, lg, xl, xxl
### Sicurezza
- **CSRF protection**: Token in tutti i form
- **Route protection**: Middleware per ogni sezione
- **Permission checking**: Helper per ogni menu
- **Input validation**: Laravel Form Requests
### Debugging
- **Console logs**: JavaScript errors
- **Laravel debugbar**: Query monitoring
- **Error pages**: Custom 403/404/500
- **Log channels**: Separate logs per sezione
---
**📝 COMPLETATO: Capitolo 5 - Interfaccia Universale**
Questo capitolo fornisce una guida completa per:
- ✅ Implementare layout universale
- ✅ Gestire navigazione AJAX
- ✅ Creare helper menu personalizzati
- ✅ Configurare sidebar dinamica
- ✅ Implementare dashboard interattive
- ✅ Aggiungere nuove sezioni/menu
- ✅ Seguire best practices
**🔄 Prossimi capitoli da completare:**
- 6. Sistema Multi-Ruolo
- 7. API e Integrazioni
- 8. Frontend e UX
- 9. Gestione Stabili e Condomini
- 10. Sistema Contabile
- ... (tutti gli altri capitoli)