25 KiB
Executable File
25 KiB
Executable File
📄 CONTENT AREA - Documentazione Completa NetGescon
Componente Content Area dell'interfaccia universale
📅 Creato: 21/07/2025
🔄 Ultimo aggiornamento: 21/07/2025
🎯 Priorità: 🔥 CRITICA
📋 OVERVIEW CONTENT AREA
Il Content Area NetGescon è la ZONA PRINCIPALE dove vengono visualizzati:
- 🗺️ Breadcrumb Navigation
- 📝 Page Headers con Actions
- 📊 Dashboard Cards
- 📋 Forms & Tables
- 🔔 Alert Messages
- 📄 Main Page Content
📐 Specifiche Layout
- Posizione:
margin-left: 280pxdesktop,margin-left: 0mobile - Altezza:
calc(100vh - 140px)(header + footer esclusi) - Padding:
24pxstandard,16pxmobile - Overflow:
scrollquando contenuto > viewport
🎨 DESIGN SPECIFICATIONS
🌈 Color Palette
/* Content Background */
.content-area {
background: var(--netgescon-gray-50);
min-height: calc(100vh - 140px);
}
/* Page Headers */
.page-header {
background: white;
border-bottom: 1px solid var(--netgescon-gray-200);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* Content Containers */
.content-container {
background: white;
border: 1px solid var(--netgescon-gray-200);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
📏 Dimensions
.content-area {
margin-left: 280px; /* Desktop con sidebar */
margin-top: 70px; /* Header height */
margin-bottom: 70px; /* Footer height */
padding: 24px;
min-height: calc(100vh - 140px);
}
/* Mobile */
@media (max-width: 767px) {
.content-area {
margin-left: 0;
padding: 16px;
}
}
🧩 STRUTTURA CONTENT AREA
📁 Layout Template
<!-- resources/views/layouts/content.blade.php -->
<main class="content-area">
<!-- Breadcrumb Navigation -->
@if(isset($breadcrumbs) && count($breadcrumbs) > 0)
@include('components.layout.breadcrumb', ['breadcrumbs' => $breadcrumbs])
@endif
<!-- Page Header -->
@if(isset($pageTitle) || isset($pageActions))
@include('components.layout.page-header', [
'title' => $pageTitle ?? '',
'subtitle' => $pageSubtitle ?? '',
'actions' => $pageActions ?? []
])
@endif
<!-- Alert Messages -->
@include('components.layout.alerts')
<!-- Main Content -->
<div class="main-content">
@yield('content')
</div>
</main>
🗺️ BREADCRUMB COMPONENT
📁 File: components/layout/breadcrumb.blade.php
<nav class="breadcrumb-nav mb-6" aria-label="breadcrumb">
<ol class="breadcrumb flex items-center space-x-2 text-sm">
<!-- Home sempre presente -->
<li class="breadcrumb-item">
<a href="{{ route('dashboard') }}"
class="text-gray-600 hover:text-blue-600 transition-colors">
<i class="fas fa-home mr-1"></i>
Dashboard
</a>
</li>
@foreach($breadcrumbs as $breadcrumb)
<li class="breadcrumb-item flex items-center">
<i class="fas fa-chevron-right text-gray-400 mx-2"></i>
@if(isset($breadcrumb['url']) && !$loop->last)
<a href="{{ $breadcrumb['url'] }}"
class="text-gray-600 hover:text-blue-600 transition-colors">
@if(isset($breadcrumb['icon']))
<i class="{{ $breadcrumb['icon'] }} mr-1"></i>
@endif
{{ $breadcrumb['title'] }}
</a>
@else
<span class="text-gray-900 font-medium">
@if(isset($breadcrumb['icon']))
<i class="{{ $breadcrumb['icon'] }} mr-1"></i>
@endif
{{ $breadcrumb['title'] }}
</span>
@endif
</li>
@endforeach
</ol>
</nav>
💻 Usage Examples
// In Controller
$breadcrumbs = [
['title' => 'Gestione Stabili', 'url' => route('admin.stabili.index'), 'icon' => 'fas fa-building'],
['title' => 'Nuovo Stabile', 'icon' => 'fas fa-plus']
];
return view('admin.stabili.create', compact('breadcrumbs'));
📝 PAGE HEADER COMPONENT
📁 File: components/layout/page-header.blade.php
<div class="page-header bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
<!-- Title Section -->
<div class="mb-4 lg:mb-0">
@if(isset($title))
<h1 class="text-2xl font-bold text-gray-900">
{{ $title }}
</h1>
@endif
@if(isset($subtitle))
<p class="text-gray-600 mt-1">
{{ $subtitle }}
</p>
@endif
<!-- Stats or Meta Info -->
@if(isset($stats))
<div class="flex items-center space-x-4 mt-3 text-sm text-gray-500">
@foreach($stats as $stat)
<span class="flex items-center">
@if(isset($stat['icon']))
<i class="{{ $stat['icon'] }} mr-1"></i>
@endif
{{ $stat['label'] }}:
<strong class="ml-1 text-gray-900">{{ $stat['value'] }}</strong>
</span>
@endforeach
</div>
@endif
</div>
<!-- Actions Section -->
@if(isset($actions) && count($actions) > 0)
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-3">
@foreach($actions as $action)
@if($action['type'] === 'primary')
<a href="{{ $action['url'] }}"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg
flex items-center justify-center transition-colors">
@if(isset($action['icon']))
<i class="{{ $action['icon'] }} mr-2"></i>
@endif
{{ $action['label'] }}
</a>
@elseif($action['type'] === 'secondary')
<a href="{{ $action['url'] }}"
class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-lg
flex items-center justify-center transition-colors border border-gray-300">
@if(isset($action['icon']))
<i class="{{ $action['icon'] }} mr-2"></i>
@endif
{{ $action['label'] }}
</a>
@elseif($action['type'] === 'danger')
<button onclick="{{ $action['onclick'] ?? '' }}"
class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg
flex items-center justify-center transition-colors">
@if(isset($action['icon']))
<i class="{{ $action['icon'] }} mr-2"></i>
@endif
{{ $action['label'] }}
</button>
@endif
@endforeach
</div>
@endif
</div>
</div>
💻 Usage Examples
// In Controller
$pageTitle = 'Gestione Stabili';
$pageSubtitle = 'Visualizza e gestisci tutti gli stabili del sistema';
$pageActions = [
[
'type' => 'primary',
'label' => 'Nuovo Stabile',
'url' => route('admin.stabili.create'),
'icon' => 'fas fa-plus'
],
[
'type' => 'secondary',
'label' => 'Esporta Excel',
'url' => route('admin.stabili.export'),
'icon' => 'fas fa-download'
]
];
$stats = [
['label' => 'Totale Stabili', 'value' => '45', 'icon' => 'fas fa-building'],
['label' => 'Condomini', 'value' => '128', 'icon' => 'fas fa-home'],
['label' => 'Unità', 'value' => '1,247', 'icon' => 'fas fa-door-open']
];
return view('admin.stabili.index', compact('pageTitle', 'pageSubtitle', 'pageActions', 'stats'));
🔔 ALERT MESSAGES COMPONENT
📁 File: components/layout/alerts.blade.php
<!-- Flash Messages -->
@if(session('success'))
<div class="alert alert-success flex items-center p-4 mb-6 bg-green-50 border border-green-200 rounded-lg">
<i class="fas fa-check-circle text-green-600 mr-3"></i>
<div class="flex-1">
<h4 class="font-medium text-green-800">Operazione completata!</h4>
<p class="text-green-700 text-sm mt-1">{{ session('success') }}</p>
</div>
<button class="alert-close text-green-600 hover:text-green-800 ml-4">
<i class="fas fa-times"></i>
</button>
</div>
@endif
@if(session('error'))
<div class="alert alert-error flex items-center p-4 mb-6 bg-red-50 border border-red-200 rounded-lg">
<i class="fas fa-exclamation-circle text-red-600 mr-3"></i>
<div class="flex-1">
<h4 class="font-medium text-red-800">Errore!</h4>
<p class="text-red-700 text-sm mt-1">{{ session('error') }}</p>
</div>
<button class="alert-close text-red-600 hover:text-red-800 ml-4">
<i class="fas fa-times"></i>
</button>
</div>
@endif
@if(session('warning'))
<div class="alert alert-warning flex items-center p-4 mb-6 bg-yellow-50 border border-yellow-200 rounded-lg">
<i class="fas fa-exclamation-triangle text-yellow-600 mr-3"></i>
<div class="flex-1">
<h4 class="font-medium text-yellow-800">Attenzione!</h4>
<p class="text-yellow-700 text-sm mt-1">{{ session('warning') }}</p>
</div>
<button class="alert-close text-yellow-600 hover:text-yellow-800 ml-4">
<i class="fas fa-times"></i>
</button>
</div>
@endif
@if(session('info'))
<div class="alert alert-info flex items-center p-4 mb-6 bg-blue-50 border border-blue-200 rounded-lg">
<i class="fas fa-info-circle text-blue-600 mr-3"></i>
<div class="flex-1">
<h4 class="font-medium text-blue-800">Informazione</h4>
<p class="text-blue-700 text-sm mt-1">{{ session('info') }}</p>
</div>
<button class="alert-close text-blue-600 hover:text-blue-800 ml-4">
<i class="fas fa-times"></i>
</button>
</div>
@endif
<!-- Validation Errors -->
@if($errors->any())
<div class="alert alert-validation flex items-start p-4 mb-6 bg-red-50 border border-red-200 rounded-lg">
<i class="fas fa-exclamation-circle text-red-600 mr-3 mt-0.5"></i>
<div class="flex-1">
<h4 class="font-medium text-red-800">Errori di validazione</h4>
<ul class="text-red-700 text-sm mt-2 space-y-1">
@foreach($errors->all() as $error)
<li>• {{ $error }}</li>
@endforeach
</ul>
</div>
<button class="alert-close text-red-600 hover:text-red-800 ml-4">
<i class="fas fa-times"></i>
</button>
</div>
@endif
📊 DASHBOARD CARDS PATTERN
🃏 Stats Cards
<!-- Stats Grid -->
<div class="stats-grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<!-- Card Stabili -->
<div class="stat-card bg-gradient-to-br from-blue-50 to-blue-100 border border-blue-200 rounded-lg p-6
hover:shadow-lg transition-shadow duration-200">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-600 text-sm font-medium uppercase tracking-wide">Stabili</p>
<p class="text-3xl font-bold text-blue-900 mt-1">{{ $stats['stabili'] ?? 0 }}</p>
<p class="text-blue-700 text-sm mt-1">
<i class="fas fa-arrow-up mr-1"></i>
+3 questo mese
</p>
</div>
<div class="bg-blue-500 rounded-full p-3">
<i class="fas fa-building text-white text-xl"></i>
</div>
</div>
</div>
<!-- Card Condomini -->
<div class="stat-card bg-gradient-to-br from-green-50 to-green-100 border border-green-200 rounded-lg p-6
hover:shadow-lg transition-shadow duration-200">
<div class="flex items-center justify-between">
<div>
<p class="text-green-600 text-sm font-medium uppercase tracking-wide">Condomini</p>
<p class="text-3xl font-bold text-green-900 mt-1">{{ $stats['condomini'] ?? 0 }}</p>
<p class="text-green-700 text-sm mt-1">
<i class="fas fa-arrow-up mr-1"></i>
+8 questo mese
</p>
</div>
<div class="bg-green-500 rounded-full p-3">
<i class="fas fa-home text-white text-xl"></i>
</div>
</div>
</div>
<!-- Card Unità -->
<div class="stat-card bg-gradient-to-br from-purple-50 to-purple-100 border border-purple-200 rounded-lg p-6
hover:shadow-lg transition-shadow duration-200">
<div class="flex items-center justify-between">
<div>
<p class="text-purple-600 text-sm font-medium uppercase tracking-wide">Unità</p>
<p class="text-3xl font-bold text-purple-900 mt-1">{{ $stats['unita'] ?? 0 }}</p>
<p class="text-purple-700 text-sm mt-1">
<i class="fas fa-minus mr-1"></i>
Nessuna variazione
</p>
</div>
<div class="bg-purple-500 rounded-full p-3">
<i class="fas fa-door-open text-white text-xl"></i>
</div>
</div>
</div>
<!-- Card Revenue -->
<div class="stat-card bg-gradient-to-br from-yellow-50 to-yellow-100 border border-yellow-200 rounded-lg p-6
hover:shadow-lg transition-shadow duration-200">
<div class="flex items-center justify-between">
<div>
<p class="text-yellow-600 text-sm font-medium uppercase tracking-wide">Fatturato</p>
<p class="text-3xl font-bold text-yellow-900 mt-1">€{{ number_format($stats['fatturato'] ?? 0, 0, ',', '.') }}</p>
<p class="text-yellow-700 text-sm mt-1">
<i class="fas fa-arrow-up mr-1"></i>
+12% vs mese scorso
</p>
</div>
<div class="bg-yellow-500 rounded-full p-3">
<i class="fas fa-euro-sign text-white text-xl"></i>
</div>
</div>
</div>
</div>
📋 FORMS PATTERN
📝 Standard Form Container
<div class="form-container bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<form method="POST" action="{{ $formAction }}" enctype="multipart/form-data">
@csrf
@if(isset($method) && $method !== 'POST')
@method($method)
@endif
<!-- Form Header -->
<div class="form-header pb-4 border-b border-gray-200 mb-6">
<h2 class="text-xl font-semibold text-gray-900">{{ $formTitle }}</h2>
@if(isset($formDescription))
<p class="text-gray-600 mt-1">{{ $formDescription }}</p>
@endif
</div>
<!-- Form Fields -->
<div class="form-fields space-y-6">
@yield('form-fields')
</div>
<!-- Form Actions -->
<div class="form-actions flex justify-between items-center pt-6 border-t border-gray-200 mt-8">
<div class="form-info text-sm text-gray-500">
<i class="fas fa-info-circle mr-1"></i>
I campi con * sono obbligatori
</div>
<div class="form-buttons flex space-x-3">
<a href="{{ $cancelUrl ?? url()->previous() }}"
class="btn btn-secondary">
<i class="fas fa-times mr-2"></i>
Annulla
</a>
@if(isset($draftButton) && $draftButton)
<button type="button" onclick="saveDraft()"
class="btn btn-outline">
<i class="fas fa-save mr-2"></i>
Salva Bozza
</button>
@endif
<button type="submit" class="btn btn-primary">
<i class="fas fa-check mr-2"></i>
{{ $submitLabel ?? 'Salva' }}
</button>
</div>
</div>
</form>
</div>
📊 TABLES PATTERN
📋 Data Table Container
<div class="table-container bg-white rounded-lg shadow-sm border border-gray-200">
<!-- Table Header -->
<div class="table-header flex flex-col lg:flex-row lg:items-center lg:justify-between p-6 border-b border-gray-200">
<div class="table-title">
<h3 class="text-lg font-semibold text-gray-900">{{ $tableTitle }}</h3>
@if(isset($tableDescription))
<p class="text-gray-600 text-sm mt-1">{{ $tableDescription }}</p>
@endif
</div>
<div class="table-actions flex items-center space-x-3 mt-4 lg:mt-0">
<!-- Search -->
<div class="relative">
<input type="text" id="table-search" placeholder="Cerca..."
class="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
<!-- Filters -->
@if(isset($filters))
<select id="table-filter" class="border border-gray-300 rounded-lg px-3 py-2">
<option value="">Tutti</option>
@foreach($filters as $filter)
<option value="{{ $filter['value'] }}">{{ $filter['label'] }}</option>
@endforeach
</select>
@endif
<!-- Export -->
<button class="btn btn-outline btn-sm">
<i class="fas fa-download mr-2"></i>
Esporta
</button>
</div>
</div>
<!-- Table Content -->
<div class="table-content overflow-x-auto">
<table class="table w-full">
<thead class="table-header bg-gray-50">
@yield('table-header')
</thead>
<tbody class="table-body">
@yield('table-body')
</tbody>
</table>
</div>
<!-- Table Footer (Pagination) -->
@if(isset($pagination))
<div class="table-footer p-6 border-t border-gray-200">
{{ $pagination->links('components.pagination') }}
</div>
@endif
</div>
⚡ JAVASCRIPT FUNCTIONALITY
📁 File: /public/js/components/content.js
/**
* Content Area - JavaScript Functions
*/
// Inizializza content area
function initializeContentArea() {
initializeAlerts();
initializeScrollToTop();
initializeStickyHeaders();
initializeInfiniteScroll();
}
// Alert management
function initializeAlerts() {
// Auto-hide alerts
const alerts = document.querySelectorAll('.alert');
alerts.forEach(alert => {
const closeBtn = alert.querySelector('.alert-close');
// Close button
if (closeBtn) {
closeBtn.addEventListener('click', () => {
hideAlert(alert);
});
}
// Auto-hide after 5 seconds
setTimeout(() => {
hideAlert(alert);
}, 5000);
});
}
function hideAlert(alert) {
alert.style.opacity = '0';
alert.style.transform = 'translateY(-10px)';
setTimeout(() => {
if (alert.parentNode) {
alert.parentNode.removeChild(alert);
}
}, 300);
}
// Scroll to top functionality
function initializeScrollToTop() {
const scrollBtn = document.createElement('button');
scrollBtn.className = 'scroll-to-top fixed bottom-20 right-6 bg-blue-600 text-white p-3 rounded-full shadow-lg opacity-0 transition-opacity duration-300 z-50';
scrollBtn.innerHTML = '<i class="fas fa-arrow-up"></i>';
scrollBtn.onclick = () => window.scrollTo({ top: 0, behavior: 'smooth' });
document.body.appendChild(scrollBtn);
window.addEventListener('scroll', () => {
if (window.scrollY > 300) {
scrollBtn.style.opacity = '1';
} else {
scrollBtn.style.opacity = '0';
}
});
}
// Sticky headers for long content
function initializeStickyHeaders() {
const headers = document.querySelectorAll('.page-header');
headers.forEach(header => {
const observer = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) {
header.classList.add('sticky-header');
} else {
header.classList.remove('sticky-header');
}
},
{ threshold: 0.1 }
);
observer.observe(header);
});
}
// Infinite scroll for tables/lists
function initializeInfiniteScroll() {
const container = document.querySelector('[data-infinite-scroll]');
if (!container) return;
let loading = false;
let page = 2;
window.addEventListener('scroll', () => {
if (loading) return;
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 5) {
loading = true;
loadMoreContent(page++).then(() => {
loading = false;
});
}
});
}
// Load more content via AJAX
async function loadMoreContent(page) {
try {
const response = await fetch(`${window.location.pathname}?page=${page}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const html = await response.text();
const container = document.querySelector('[data-infinite-scroll]');
container.insertAdjacentHTML('beforeend', html);
} catch (error) {
console.error('Error loading more content:', error);
}
}
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', initializeContentArea);
📱 RESPONSIVE BEHAVIOR
🖥️ Desktop (≥1024px)
.content-area {
margin-left: 280px;
padding: 24px;
}
.page-header {
flex-direction: row;
align-items: center;
}
.stats-grid {
grid-template-columns: repeat(4, 1fr);
}
📱 Tablet (768px - 1023px)
.content-area {
margin-left: 0;
padding: 20px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.page-header .flex {
flex-direction: column;
align-items: stretch;
}
📱 Mobile (≤767px)
.content-area {
padding: 16px;
}
.stats-grid {
grid-template-columns: 1fr;
}
.table-container {
overflow-x: scroll;
}
.form-actions {
flex-direction: column;
align-items: stretch;
}
🧪 TESTING CHECKLIST
✅ Layout Tests
- Content area positioning correct
- Responsive margins working
- Scroll behavior smooth
- Cards grid responsive
- Forms layout consistent
✅ Component Tests
- Breadcrumb navigation functional
- Page header actions working
- Alerts display/hide correctly
- Tables search/filter working
- Forms validation display
✅ Interaction Tests
- Scroll to top button
- Infinite scroll loading
- Alert auto-hide
- Mobile form usability
🐛 COMMON ISSUES & FIXES
🚨 Content nascosto sotto header/footer
Causa: Margin/padding calculations incorrect
Fix: Verifica calcoli height e margins CSS
🚨 Cards non responsive
Causa: Grid breakpoints non configurati
Fix: Aggiorna grid-template-columns per ogni breakpoint
🚨 Alerts non si chiudono
Causa: JavaScript event listeners non agganciati
Fix: Verifica inizializzazione initializeAlerts()
🚨 Forms troppo larghi su mobile
Causa: Responsive form layout non implementato
Fix: Aggiorna CSS per form mobile-first
📚 FILES CORRELATI
🧩 Templates
/resources/views/layouts/content.blade.php/resources/views/components/layout/(directory)
🎨 Styles
/public/css/components/content.css/public/css/components/forms.css/public/css/components/tables.css
⚡ JavaScript
/public/js/components/content.js/public/js/components/forms.js/public/js/components/tables.js
🎯 NEXT STEPS
🔮 Planned Improvements
- Virtual Scrolling - Performance per grandi dataset
- Advanced Filters - Multi-criteria filtering
- Bulk Actions - Multiple selections
- Export Templates - Custom export formats
- Real-time Updates - WebSocket integration
📝 Documento mantenuto da: GitHub Copilot AI Assistant
🔗 Componente: Content Area NetGescon Interface v2.1.0