📋 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
38 KiB
38 KiB
5. INTERFACCIA UNIVERSALE - GUIDA COMPLETA
📋 INDICE CAPITOLO
- 5.1 Architettura Layout
- 5.2 Sistema Navigazione AJAX
- 5.3 Helper Menu Personalizzati
- 5.4 Gestione Sidebar Dinamica
- 5.5 Dashboard Cards Interattive
- 5.6 Breadcrumb e Messaggi
- 5.7 Esempi Pratici
- 5.8 Best Practices
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:
-
- Sistema Multi-Ruolo
-
- API e Integrazioni
-
- Frontend e UX
-
- Gestione Stabili e Condomini
-
- Sistema Contabile
- ... (tutti gli altri capitoli)