netgescon-master/docs/05-INTERFACCIA-UNIVERSALE.md
Pikappa2 480e7eafbd 🎯 NETGESCON - Setup iniziale repository completo
📋 Commit iniziale con:
-  Documentazione unificata in docs/
-  Codice Laravel in netgescon-laravel/
-  Script automazione in scripts/
-  Configurazione sync rsync
-  Struttura organizzata e pulita

🔄 Versione: 2025.07.19-1644
🎯 Sistema pronto per Git distribuito
2025-07-19 16:44:47 +02:00

38 KiB

5. INTERFACCIA UNIVERSALE - GUIDA COMPLETA

📋 INDICE CAPITOLO


5.1 Architettura Layout

Layout Base Universale

File: resources/views/layouts/universal.blade.php

<!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>

Header Universale

File: resources/views/layouts/partials/header.blade.php

<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

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

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

<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

<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

<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

<!-- 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

private static $menuPermissions = [
    // ...existing permissions...
    'nuova_sezione' => ['admin', 'amministratore'],
];

Passo 2: Aggiungere voce menu in buildSidebarMenu()

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

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

@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:

    1. Sistema Multi-Ruolo
    1. API e Integrazioni
    1. Frontend e UX
    1. Gestione Stabili e Condomini
    1. Sistema Contabile
  • ... (tutti gli altri capitoli)