687 lines
18 KiB
Markdown
687 lines
18 KiB
Markdown
# 🎯 NETGESCON CONTENT MENU STANDARD
|
|
## Manuale per replicare struttura menu e contenuti
|
|
|
|
### 📅 Versione: 2.0 - 5 Agosto 2025
|
|
### 🎯 Obiettivo: Standardizzazione interfaccia per tutte le pagine
|
|
|
|
---
|
|
|
|
## 🎨 **PATTERN FUNZIONANTE IDENTIFICATO**
|
|
|
|
### ✅ **Strutture Menu che Funzionano**:
|
|
1. **Menu Principale con Sub-tabs** (Contabilità, Stabili)
|
|
2. **Sub-menu Navigazione** (Tabelle Millesimali)
|
|
3. **Cards Statistiche** (Dashboard style)
|
|
4. **Layout Standard NetGescon**
|
|
|
|
---
|
|
|
|
## 🗂️ **TEMPLATE MASTER - STRUTTURA STANDARD**
|
|
|
|
### 📋 **1. LAYOUT BASE OBBLIGATORIO**
|
|
```blade
|
|
@extends('admin.layouts.netgescon')
|
|
|
|
@section('title', 'Nome Modulo')
|
|
|
|
@section('content')
|
|
<div class="content-wrapper">
|
|
<!-- HEADER BREADCRUMB -->
|
|
<div class="content-header">
|
|
<div class="container-fluid">
|
|
<div class="row mb-2">
|
|
<div class="col-sm-6">
|
|
<h1 class="m-0">Nome Modulo</h1>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<ol class="breadcrumb float-sm-right">
|
|
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Home</a></li>
|
|
<li class="breadcrumb-item active">Nome Modulo</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SUB-MENU TABS PATTERN -->
|
|
<div class="content-header">
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card card-primary card-tabs">
|
|
<div class="card-header p-0 pt-1">
|
|
<ul class="nav nav-tabs" id="custom-tabs-one-tab" role="tablist">
|
|
<!-- TAB ITEMS - PATTERN REPLICABILE -->
|
|
@yield('menu-tabs')
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- STATISTICHE CARDS PATTERN -->
|
|
<section class="content">
|
|
<div class="container-fluid">
|
|
<!-- STATS ROW - PATTERN REPLICABILE -->
|
|
@yield('stats-cards')
|
|
|
|
<!-- MAIN CONTENT AREA -->
|
|
@yield('main-content')
|
|
</div>
|
|
</section>
|
|
</div>
|
|
@endsection
|
|
|
|
<!-- STYLES PATTERN -->
|
|
@push('styles')
|
|
@yield('module-styles')
|
|
@endpush
|
|
```
|
|
|
|
---
|
|
|
|
## 🏗️ **PATTERN 1: MENU PRINCIPALE CON SUB-TABS**
|
|
|
|
### 🎯 **Utilizzo**: Moduli principali (Contabilità, Stabili, Condomini)
|
|
|
|
### 📝 **Template Tab Menu**:
|
|
```blade
|
|
@section('menu-tabs')
|
|
<li class="nav-item">
|
|
<a class="nav-link active"
|
|
href="{{ route('admin.MODULO.index') }}"
|
|
role="tab">
|
|
<i class="fas fa-list"></i> Lista Principale
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link"
|
|
href="{{ route('admin.MODULO.create') }}"
|
|
role="tab">
|
|
<i class="fas fa-plus"></i> Nuovo Item
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link"
|
|
href="{{ route('admin.MODULO.reports') }}"
|
|
role="tab">
|
|
<i class="fas fa-chart-bar"></i> Reports
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link"
|
|
href="{{ route('admin.MODULO.config') }}"
|
|
role="tab">
|
|
<i class="fas fa-cog"></i> Configurazione
|
|
</a>
|
|
</li>
|
|
@endsection
|
|
```
|
|
|
|
### 💡 **Icone Standard per Moduli**:
|
|
```php
|
|
// Mapping icone per consistency
|
|
$moduleIcons = [
|
|
'list' => 'fas fa-list',
|
|
'create' => 'fas fa-plus',
|
|
'edit' => 'fas fa-edit',
|
|
'show' => 'fas fa-eye',
|
|
'reports' => 'fas fa-chart-bar',
|
|
'config' => 'fas fa-cog',
|
|
'export' => 'fas fa-download',
|
|
'import' => 'fas fa-upload',
|
|
'search' => 'fas fa-search',
|
|
'filter' => 'fas fa-filter'
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## 🏗️ **PATTERN 2: SUB-MENU NAVIGAZIONE**
|
|
|
|
### 🎯 **Utilizzo**: Sezioni dettaglio con tabs (Stabile Detail, etc.)
|
|
|
|
### 📝 **Template Tabs Detail**:
|
|
```blade
|
|
<!-- Tabs Container -->
|
|
<div class="bg-white rounded-lg shadow-sm border">
|
|
<!-- Tab Navigation -->
|
|
<div class="border-b border-gray-200 px-6 pt-6">
|
|
<nav class="-mb-px flex space-x-8" aria-label="Tabs" role="tablist">
|
|
<button class="netgescon-tab-btn active"
|
|
data-tab="dati-generali"
|
|
type="button"
|
|
role="tab">
|
|
<i class="fas fa-info-circle mr-2"></i>
|
|
Dati Generali
|
|
</button>
|
|
<button class="netgescon-tab-btn"
|
|
data-tab="dettagli"
|
|
type="button"
|
|
role="tab">
|
|
<i class="fas fa-list mr-2"></i>
|
|
Dettagli
|
|
</button>
|
|
<button class="netgescon-tab-btn"
|
|
data-tab="documenti"
|
|
type="button"
|
|
role="tab">
|
|
<i class="fas fa-file mr-2"></i>
|
|
Documenti
|
|
</button>
|
|
<!-- Altri tabs del modulo -->
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="p-6">
|
|
<div id="dati-generali" class="netgescon-tab-content active">
|
|
@yield('tab-dati-generali')
|
|
</div>
|
|
<div id="dettagli" class="netgescon-tab-content">
|
|
@yield('tab-dettagli')
|
|
</div>
|
|
<div id="documenti" class="netgescon-tab-content">
|
|
@yield('tab-documenti')
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 🏗️ **PATTERN 3: CARDS STATISTICHE**
|
|
|
|
### 🎯 **Utilizzo**: Dashboard e pagine overview
|
|
|
|
### 📝 **Template Stats Cards**:
|
|
```blade
|
|
@section('stats-cards')
|
|
<div class="row mb-3">
|
|
<div class="col-md-3">
|
|
<div class="small-box bg-primary">
|
|
<div class="inner">
|
|
<h3>{{ $stats['totale'] ?? 0 }}</h3>
|
|
<p>Totale Items</p>
|
|
</div>
|
|
<div class="icon">
|
|
<i class="fas fa-list"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="small-box bg-success">
|
|
<div class="inner">
|
|
<h3>{{ $stats['attivi'] ?? 0 }}</h3>
|
|
<p>Attivi</p>
|
|
</div>
|
|
<div class="icon">
|
|
<i class="fas fa-check"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="small-box bg-warning">
|
|
<div class="inner">
|
|
<h3>{{ $stats['pending'] ?? 0 }}</h3>
|
|
<p>In Sospeso</p>
|
|
</div>
|
|
<div class="icon">
|
|
<i class="fas fa-clock"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="small-box bg-danger">
|
|
<div class="inner">
|
|
<h3>{{ $stats['problemi'] ?? 0 }}</h3>
|
|
<p>Problemi</p>
|
|
</div>
|
|
<div class="icon">
|
|
<i class="fas fa-exclamation"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
```
|
|
|
|
### 🎨 **Colori Standard Cards**:
|
|
```css
|
|
/* Color mapping per consistency */
|
|
.small-box.bg-primary { background: #007bff; } /* Totali */
|
|
.small-box.bg-success { background: #28a745; } /* Positivi/Attivi */
|
|
.small-box.bg-warning { background: #ffc107; } /* In Attesa/Sospeso */
|
|
.small-box.bg-danger { background: #dc3545; } /* Errori/Problemi */
|
|
.small-box.bg-info { background: #17a2b8; } /* Info/Neutro */
|
|
.small-box.bg-secondary { background: #6c757d; } /* Secondario */
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 **TEMPLATE CONTROLLER STANDARD**
|
|
|
|
### 🎯 **Controller Pattern per Menu Funzionante**:
|
|
```php
|
|
<?php
|
|
// app/Http/Controllers/Admin/{Module}Controller.php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use Illuminate\Http\Request;
|
|
|
|
class ModuleController extends Controller
|
|
{
|
|
/**
|
|
* Display main listing with stats and menu
|
|
*/
|
|
public function index()
|
|
{
|
|
// Calcola statistiche per cards
|
|
$stats = $this->calculateStats();
|
|
|
|
// Dati principale per listing
|
|
$items = $this->getMainData();
|
|
|
|
// Menu configuration
|
|
$menuTabs = $this->getMenuTabs();
|
|
|
|
return view('admin.module.index', compact('stats', 'items', 'menuTabs'));
|
|
}
|
|
|
|
/**
|
|
* Show detail with sub-tabs
|
|
*/
|
|
public function show($id)
|
|
{
|
|
$item = $this->findItem($id);
|
|
$detailTabs = $this->getDetailTabs($item);
|
|
$tabContent = $this->getTabContent($item);
|
|
|
|
return view('admin.module.show', compact('item', 'detailTabs', 'tabContent'));
|
|
}
|
|
|
|
/**
|
|
* Calculate statistics for cards
|
|
*/
|
|
private function calculateStats()
|
|
{
|
|
return [
|
|
'totale' => Model::count(),
|
|
'attivi' => Model::where('stato', 'attivo')->count(),
|
|
'pending' => Model::where('stato', 'pending')->count(),
|
|
'problemi' => Model::where('stato', 'errore')->count(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get menu tabs configuration
|
|
*/
|
|
private function getMenuTabs()
|
|
{
|
|
return [
|
|
['label' => 'Lista', 'route' => 'admin.module.index', 'icon' => 'fas fa-list', 'active' => true],
|
|
['label' => 'Nuovo', 'route' => 'admin.module.create', 'icon' => 'fas fa-plus'],
|
|
['label' => 'Reports', 'route' => 'admin.module.reports', 'icon' => 'fas fa-chart-bar'],
|
|
['label' => 'Config', 'route' => 'admin.module.config', 'icon' => 'fas fa-cog'],
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🗄️ **DATABASE STANDARD CONNECTION**
|
|
|
|
### 🔑 **Credenziali Database NetGescon**:
|
|
```env
|
|
# File: .env
|
|
DB_CONNECTION=mysql
|
|
DB_HOST=127.0.0.1
|
|
DB_PORT=3306
|
|
DB_DATABASE=netgescon
|
|
DB_USERNAME=netgescon_user
|
|
DB_PASSWORD=NetGescon2024!
|
|
```
|
|
|
|
### 📊 **Pattern Query Standard**:
|
|
```php
|
|
// In Controller
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
// Query con relazioni standard
|
|
$items = DB::table('main_table')
|
|
->leftJoin('stabili', 'main_table.stabile_id', '=', 'stabili.id')
|
|
->leftJoin('gestioni', 'main_table.gestione_id', '=', 'gestioni.id_gestione')
|
|
->select([
|
|
'main_table.*',
|
|
'stabili.denominazione as stabile_nome',
|
|
'gestioni.anno_gestione',
|
|
'gestioni.tipo_gestione'
|
|
])
|
|
->where('stabili.amministratore_id', auth()->user()->amministratore_id ?? null)
|
|
->orderBy('main_table.created_at', 'desc')
|
|
->paginate(20);
|
|
|
|
// Stats query standard
|
|
$stats = [
|
|
'totale' => DB::table('main_table')->count(),
|
|
'questo_mese' => DB::table('main_table')
|
|
->whereMonth('created_at', now()->month)
|
|
->count(),
|
|
'attivi' => DB::table('main_table')
|
|
->where('stato', 'attivo')
|
|
->count()
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 **CSS STANDARD NETGESCON**
|
|
|
|
### 📁 **File:** `/public/css/netgescon-standard.css`
|
|
```css
|
|
/* NetGescon Standard Module Styles */
|
|
|
|
/* Menu Tabs Standard */
|
|
.nav-tabs .nav-link {
|
|
border: 1px solid transparent;
|
|
border-radius: 0.375rem 0.375rem 0 0;
|
|
color: #6c757d;
|
|
font-weight: 500;
|
|
padding: 0.75rem 1rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.nav-tabs .nav-link:hover {
|
|
border-color: #e9ecef #e9ecef #dee2e6;
|
|
color: #007bff;
|
|
}
|
|
|
|
.nav-tabs .nav-link.active {
|
|
background-color: #fff;
|
|
border-color: #007bff #007bff #fff;
|
|
color: #007bff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* NetGescon Tab Buttons */
|
|
.netgescon-tab-btn {
|
|
border-bottom: 2px solid transparent;
|
|
color: #6b7280;
|
|
font-weight: 500;
|
|
padding: 0.75rem 0;
|
|
transition: all 0.3s ease;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.netgescon-tab-btn:hover {
|
|
border-bottom-color: #3b82f6;
|
|
color: #3b82f6;
|
|
}
|
|
|
|
.netgescon-tab-btn.active {
|
|
border-bottom-color: #3b82f6;
|
|
color: #3b82f6;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Tab Content */
|
|
.netgescon-tab-content {
|
|
display: none;
|
|
}
|
|
|
|
.netgescon-tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
/* Small Box Cards */
|
|
.small-box {
|
|
border-radius: 0.375rem;
|
|
color: #fff;
|
|
overflow: hidden;
|
|
position: relative;
|
|
margin-bottom: 1rem;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.small-box:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.small-box .inner {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.small-box h3 {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.small-box p {
|
|
font-size: 1rem;
|
|
margin: 0.5rem 0 0 0;
|
|
}
|
|
|
|
.small-box .icon {
|
|
position: absolute;
|
|
top: auto;
|
|
bottom: 10px;
|
|
right: 10px;
|
|
font-size: 3rem;
|
|
color: rgba(255,255,255,0.15);
|
|
}
|
|
|
|
/* Responsive Cards */
|
|
@media (max-width: 768px) {
|
|
.small-box h3 {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.small-box .icon {
|
|
font-size: 2rem;
|
|
bottom: 5px;
|
|
right: 5px;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ⚡ **JAVASCRIPT STANDARD**
|
|
|
|
### 📁 **File:** `/public/js/netgescon-modules.js`
|
|
```javascript
|
|
/**
|
|
* NetGescon Standard Module JavaScript
|
|
*/
|
|
|
|
// Inizializzazione moduli
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeNetGesconTabs();
|
|
initializeStatCards();
|
|
initializeMenuHighlight();
|
|
});
|
|
|
|
// Tab Management
|
|
function initializeNetGesconTabs() {
|
|
const tabButtons = document.querySelectorAll('.netgescon-tab-btn');
|
|
const tabContents = document.querySelectorAll('.netgescon-tab-content');
|
|
|
|
tabButtons.forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const tabId = this.getAttribute('data-tab');
|
|
|
|
// Remove active class from all buttons and contents
|
|
tabButtons.forEach(btn => btn.classList.remove('active'));
|
|
tabContents.forEach(content => content.classList.remove('active'));
|
|
|
|
// Add active class to clicked button and corresponding content
|
|
this.classList.add('active');
|
|
const targetContent = document.getElementById(tabId);
|
|
if (targetContent) {
|
|
targetContent.classList.add('active');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Stat Cards Animation
|
|
function initializeStatCards() {
|
|
const cards = document.querySelectorAll('.small-box');
|
|
|
|
cards.forEach(card => {
|
|
card.addEventListener('mouseenter', function() {
|
|
this.style.transform = 'translateY(-3px)';
|
|
});
|
|
|
|
card.addEventListener('mouseleave', function() {
|
|
this.style.transform = 'translateY(0)';
|
|
});
|
|
});
|
|
}
|
|
|
|
// Menu Active Highlight
|
|
function initializeMenuHighlight() {
|
|
const currentPath = window.location.pathname;
|
|
const menuLinks = document.querySelectorAll('.nav-tabs .nav-link');
|
|
|
|
menuLinks.forEach(link => {
|
|
if (link.getAttribute('href') === currentPath) {
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Utility: Show loading state
|
|
function showLoading(element) {
|
|
element.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Caricamento...';
|
|
element.disabled = true;
|
|
}
|
|
|
|
// Utility: Hide loading state
|
|
function hideLoading(element, originalText) {
|
|
element.innerHTML = originalText;
|
|
element.disabled = false;
|
|
}
|
|
|
|
// Utility: Show notification
|
|
function showNotification(message, type = 'info') {
|
|
const notification = document.createElement('div');
|
|
notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
|
notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; max-width: 300px;';
|
|
notification.innerHTML = `
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.remove();
|
|
}, 5000);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 **CHECKLIST IMPLEMENTAZIONE**
|
|
|
|
### ✅ **Per Ogni Nuovo Modulo**:
|
|
- [ ] **Layout Base** - Extends admin.layouts.netgescon
|
|
- [ ] **Breadcrumb** - Header con navigazione corretta
|
|
- [ ] **Sub-menu Tabs** - Pattern menu funzionante
|
|
- [ ] **Stats Cards** - 4 cards statistiche standard
|
|
- [ ] **Main Content** - Area contenuto principale
|
|
- [ ] **CSS Classes** - NetGescon design system
|
|
- [ ] **JavaScript** - Tab functionality standard
|
|
- [ ] **Controller** - Stats calculation e data retrieval
|
|
- [ ] **Database** - Query con relazioni standard
|
|
- [ ] **Routes** - RESTful con naming consistency
|
|
|
|
### ✅ **File da Creare per Modulo**:
|
|
```
|
|
resources/views/admin/{module}/
|
|
├── index.blade.php # Lista principale con menu
|
|
├── show.blade.php # Dettaglio con sub-tabs
|
|
├── create.blade.php # Form creazione
|
|
├── edit.blade.php # Form modifica
|
|
└── _form.blade.php # Form partial
|
|
|
|
app/Http/Controllers/Admin/
|
|
└── {Module}Controller.php
|
|
|
|
routes/
|
|
└── admin.php (aggiungere routes del modulo)
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 **ESEMPI MODULI DA REPLICARE**
|
|
|
|
### 1. **Modulo Semplice** (es: Fornitori):
|
|
```bash
|
|
# Usa Pattern 1: Menu principale con sub-tabs
|
|
# Stats: Totale, Attivi, Inattivi, Questo mese
|
|
# Tabs: Lista, Nuovo, Categorie, Reports
|
|
```
|
|
|
|
### 2. **Modulo Complesso** (es: Gestioni):
|
|
```bash
|
|
# Usa Pattern 1 + Pattern 2
|
|
# Menu principale: Lista, Nuovo, Archivio, Reports
|
|
# Detail sub-tabs: Dati, Movimenti, Documenti, Bilanci
|
|
```
|
|
|
|
### 3. **Modulo Report** (es: Analytics):
|
|
```bash
|
|
# Usa Pattern 3: Solo cards statistiche
|
|
# Focus su visualizzazione dati
|
|
# Grafici e tabelle dinamiche
|
|
```
|
|
|
|
---
|
|
|
|
## 🔗 **RIFERIMENTI E CONNESSIONI**
|
|
|
|
### 📁 **Files Sorgente Pattern**:
|
|
- **Contabilità**: `/resources/views/modules/contabilita/index.blade.php`
|
|
- **Stabili**: `/resources/views/admin/stabili/show.blade.php`
|
|
- **Layout**: `/resources/views/admin/layouts/netgescon.blade.php`
|
|
|
|
### 🗄️ **Database Schema Principale**:
|
|
```sql
|
|
-- Tabelle core sempre presenti
|
|
stabili (id, denominazione, ...)
|
|
gestioni (id_gestione, anno_gestione, ...)
|
|
unita_immobiliari (id, stabile_id, ...)
|
|
amministratori (id, ...)
|
|
|
|
-- Pattern naming nuove tabelle
|
|
{modulo}_registrazioni
|
|
{modulo}_configurazioni
|
|
{modulo}_statistiche
|
|
```
|
|
|
|
### 🎯 **Route Pattern Standard**:
|
|
```php
|
|
// routes/admin.php
|
|
Route::prefix('admin')->name('admin.')->group(function () {
|
|
Route::resource('{modulo}', '{Modulo}Controller');
|
|
Route::get('{modulo}/{id}/detail', ['{Modulo}Controller', 'detail'])->name('{modulo}.detail');
|
|
Route::get('{modulo}/reports', ['{Modulo}Controller', 'reports'])->name('{modulo}.reports');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
**🎯 MANUALE STANDARD CREATO**
|
|
**Per ogni nuovo modulo: segui questo pattern e avrai consistenza UI/UX**
|