857 lines
22 KiB
Markdown
Executable File
857 lines
22 KiB
Markdown
Executable File
# 🃏 CARDS - Documentazione Completa NetGescon
|
||
|
||
> **Sistema Cards dell'interfaccia universale**
|
||
> **📅 Creato:** 21/07/2025
|
||
> **🔄 Ultimo aggiornamento:** 21/07/2025
|
||
> **🎯 Priorità:** ⚡ ALTO
|
||
|
||
---
|
||
|
||
## 📋 **OVERVIEW CARDS SYSTEM**
|
||
|
||
Il Sistema Cards NetGescon fornisce **COMPONENTI MODULARI** per:
|
||
- 📊 **Dashboard Stats Cards**
|
||
- 📋 **Info Cards**
|
||
- 🎯 **Action Cards**
|
||
- 📈 **Chart Cards**
|
||
- 📄 **Content Cards**
|
||
- ⚠️ **Alert Cards**
|
||
|
||
### 🎨 **Design Philosophy**
|
||
- **Consistenza Visiva** - Stile uniforme in tutta l'app
|
||
- **Interattività** - Hover effects e animazioni smooth
|
||
- **Responsiveness** - Adattamento perfetto a ogni device
|
||
- **Accessibilità** - WCAG 2.1 AA compliant
|
||
|
||
---
|
||
|
||
## 🎨 **DESIGN SPECIFICATIONS**
|
||
|
||
### 🌈 **Color Palette**
|
||
```css
|
||
/* Base Card Styles */
|
||
.netgescon-card {
|
||
background: white;
|
||
border: 1px solid var(--netgescon-gray-200);
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.netgescon-card:hover {
|
||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
/* Gradient Cards */
|
||
.card-gradient-blue {
|
||
background: linear-gradient(135deg, #ebf4ff 0%, #dbeafe 100%);
|
||
border-color: var(--netgescon-primary-light);
|
||
}
|
||
|
||
.card-gradient-green {
|
||
background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
|
||
border-color: var(--netgescon-success);
|
||
}
|
||
|
||
.card-gradient-yellow {
|
||
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
|
||
border-color: var(--netgescon-warning);
|
||
}
|
||
|
||
.card-gradient-red {
|
||
background: linear-gradient(135deg, #fef2f2 0%, #fecaca 100%);
|
||
border-color: var(--netgescon-danger);
|
||
}
|
||
```
|
||
|
||
### 📏 **Base Dimensions**
|
||
```css
|
||
/* Standard Card */
|
||
.netgescon-card {
|
||
padding: 24px;
|
||
min-height: 120px;
|
||
}
|
||
|
||
/* Compact Card */
|
||
.netgescon-card-compact {
|
||
padding: 16px;
|
||
min-height: 80px;
|
||
}
|
||
|
||
/* Large Card */
|
||
.netgescon-card-large {
|
||
padding: 32px;
|
||
min-height: 200px;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 **DASHBOARD STATS CARDS**
|
||
|
||
### 🔢 **Stats Card Template**
|
||
```blade
|
||
<!-- resources/views/components/cards/stats-card.blade.php -->
|
||
@props([
|
||
'title' => '',
|
||
'value' => '',
|
||
'icon' => 'fas fa-chart-line',
|
||
'color' => 'blue',
|
||
'trend' => null,
|
||
'trendDirection' => 'up',
|
||
'subtitle' => '',
|
||
'url' => null
|
||
])
|
||
|
||
@php
|
||
$cardClass = "card-gradient-{$color}";
|
||
$iconColorClass = match($color) {
|
||
'blue' => 'text-blue-600',
|
||
'green' => 'text-green-600',
|
||
'yellow' => 'text-yellow-600',
|
||
'red' => 'text-red-600',
|
||
'purple' => 'text-purple-600',
|
||
default => 'text-gray-600'
|
||
};
|
||
$bgIconClass = match($color) {
|
||
'blue' => 'bg-blue-500',
|
||
'green' => 'bg-green-500',
|
||
'yellow' => 'bg-yellow-500',
|
||
'red' => 'bg-red-500',
|
||
'purple' => 'bg-purple-500',
|
||
default => 'bg-gray-500'
|
||
};
|
||
@endphp
|
||
|
||
<div class="netgescon-card {{ $cardClass }} {{ $url ? 'cursor-pointer' : '' }}"
|
||
@if($url) onclick="window.location='{{ $url }}'" @endif>
|
||
|
||
<div class="flex items-center justify-between">
|
||
<!-- Content Section -->
|
||
<div class="flex-1">
|
||
<!-- Title -->
|
||
<p class="{{ $iconColorClass }} text-sm font-medium uppercase tracking-wide mb-1">
|
||
{{ $title }}
|
||
</p>
|
||
|
||
<!-- Value -->
|
||
<p class="text-3xl font-bold text-gray-900 mb-1">
|
||
{{ $value }}
|
||
</p>
|
||
|
||
<!-- Trend & Subtitle -->
|
||
<div class="flex items-center space-x-2">
|
||
@if($trend)
|
||
<span class="flex items-center text-sm {{ $trendDirection === 'up' ? 'text-green-600' : 'text-red-600' }}">
|
||
<i class="fas fa-arrow-{{ $trendDirection }} mr-1"></i>
|
||
{{ $trend }}
|
||
</span>
|
||
@endif
|
||
|
||
@if($subtitle)
|
||
<span class="text-sm text-gray-600">{{ $subtitle }}</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Icon Section -->
|
||
<div class="{{ $bgIconClass }} rounded-full p-3 shadow-lg">
|
||
<i class="{{ $icon }} text-white text-xl"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### 💻 **Usage Examples**
|
||
```blade
|
||
<!-- In Dashboard View -->
|
||
<div class="stats-grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||
<x-cards.stats-card
|
||
title="Stabili Gestiti"
|
||
value="{{ $stats['stabili'] }}"
|
||
icon="fas fa-building"
|
||
color="blue"
|
||
trend="+3"
|
||
trendDirection="up"
|
||
subtitle="questo mese"
|
||
url="{{ route('admin.stabili.index') }}"
|
||
/>
|
||
|
||
<x-cards.stats-card
|
||
title="Condomini Attivi"
|
||
value="{{ $stats['condomini'] }}"
|
||
icon="fas fa-home"
|
||
color="green"
|
||
trend="+8"
|
||
trendDirection="up"
|
||
subtitle="questo mese"
|
||
url="{{ route('admin.condomini.index') }}"
|
||
/>
|
||
|
||
<x-cards.stats-card
|
||
title="Unità Immobiliari"
|
||
value="{{ number_format($stats['unita']) }}"
|
||
icon="fas fa-door-open"
|
||
color="purple"
|
||
trend="0"
|
||
subtitle="nessuna variazione"
|
||
url="{{ route('admin.unita.index') }}"
|
||
/>
|
||
|
||
<x-cards.stats-card
|
||
title="Fatturato Mensile"
|
||
value="€{{ number_format($stats['fatturato'], 0, ',', '.') }}"
|
||
icon="fas fa-euro-sign"
|
||
color="yellow"
|
||
trend="+12%"
|
||
trendDirection="up"
|
||
subtitle="vs mese scorso"
|
||
url="{{ route('admin.reports.financial') }}"
|
||
/>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 **INFO CARDS**
|
||
|
||
### ℹ️ **Info Card Template**
|
||
```blade
|
||
<!-- resources/views/components/cards/info-card.blade.php -->
|
||
@props([
|
||
'title' => '',
|
||
'content' => '',
|
||
'icon' => 'fas fa-info-circle',
|
||
'type' => 'info', // info, success, warning, danger
|
||
'dismissible' => false
|
||
])
|
||
|
||
@php
|
||
$typeClasses = [
|
||
'info' => 'bg-blue-50 border-blue-200 text-blue-800',
|
||
'success' => 'bg-green-50 border-green-200 text-green-800',
|
||
'warning' => 'bg-yellow-50 border-yellow-200 text-yellow-800',
|
||
'danger' => 'bg-red-50 border-red-200 text-red-800'
|
||
];
|
||
$iconClasses = [
|
||
'info' => 'text-blue-600',
|
||
'success' => 'text-green-600',
|
||
'warning' => 'text-yellow-600',
|
||
'danger' => 'text-red-600'
|
||
];
|
||
@endphp
|
||
|
||
<div class="netgescon-card {{ $typeClasses[$type] }} border">
|
||
<div class="flex items-start">
|
||
<!-- Icon -->
|
||
<div class="flex-shrink-0">
|
||
<i class="{{ $icon }} {{ $iconClasses[$type] }} text-xl"></i>
|
||
</div>
|
||
|
||
<!-- Content -->
|
||
<div class="ml-3 flex-1">
|
||
@if($title)
|
||
<h4 class="font-medium mb-1">{{ $title }}</h4>
|
||
@endif
|
||
|
||
<div class="text-sm">
|
||
{!! $content !!}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Dismiss Button -->
|
||
@if($dismissible)
|
||
<button class="ml-4 text-current hover:opacity-75 transition-opacity"
|
||
onclick="this.parentElement.parentElement.remove()">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 **ACTION CARDS**
|
||
|
||
### ⚡ **Action Card Template**
|
||
```blade
|
||
<!-- resources/views/components/cards/action-card.blade.php -->
|
||
@props([
|
||
'title' => '',
|
||
'description' => '',
|
||
'icon' => 'fas fa-plus',
|
||
'buttonText' => 'Azione',
|
||
'buttonUrl' => '#',
|
||
'buttonColor' => 'primary',
|
||
'image' => null
|
||
])
|
||
|
||
<div class="netgescon-card text-center">
|
||
<!-- Image or Icon -->
|
||
@if($image)
|
||
<img src="{{ $image }}" alt="{{ $title }}" class="w-16 h-16 mx-auto mb-4 rounded-full">
|
||
@else
|
||
<div class="w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center">
|
||
<i class="{{ $icon }} text-gray-600 text-2xl"></i>
|
||
</div>
|
||
@endif
|
||
|
||
<!-- Title -->
|
||
<h3 class="text-lg font-semibold text-gray-900 mb-2">{{ $title }}</h3>
|
||
|
||
<!-- Description -->
|
||
@if($description)
|
||
<p class="text-gray-600 text-sm mb-6">{{ $description }}</p>
|
||
@endif
|
||
|
||
<!-- Action Button -->
|
||
<a href="{{ $buttonUrl }}"
|
||
class="btn btn-{{ $buttonColor }} inline-flex items-center">
|
||
<i class="{{ $icon }} mr-2"></i>
|
||
{{ $buttonText }}
|
||
</a>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 **CHART CARDS**
|
||
|
||
### 📊 **Chart Card Template**
|
||
```blade
|
||
<!-- resources/views/components/cards/chart-card.blade.php -->
|
||
@props([
|
||
'title' => '',
|
||
'chartId' => '',
|
||
'chartType' => 'line',
|
||
'height' => '300px',
|
||
'data' => [],
|
||
'options' => []
|
||
])
|
||
|
||
<div class="netgescon-card">
|
||
<!-- Card Header -->
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h3 class="text-lg font-semibold text-gray-900">{{ $title }}</h3>
|
||
|
||
<!-- Chart Controls -->
|
||
<div class="flex items-center space-x-2">
|
||
<select class="chart-period text-sm border border-gray-300 rounded px-2 py-1"
|
||
onchange="updateChart('{{ $chartId }}', this.value)">
|
||
<option value="7d">7 giorni</option>
|
||
<option value="30d" selected>30 giorni</option>
|
||
<option value="3m">3 mesi</option>
|
||
<option value="1y">1 anno</option>
|
||
</select>
|
||
|
||
<button class="chart-refresh text-gray-500 hover:text-gray-700 p-1"
|
||
onclick="refreshChart('{{ $chartId }}')">
|
||
<i class="fas fa-sync-alt"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chart Container -->
|
||
<div class="chart-container" style="height: {{ $height }}">
|
||
<canvas id="{{ $chartId }}"></canvas>
|
||
</div>
|
||
|
||
<!-- Chart Script -->
|
||
@push('scripts')
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const ctx = document.getElementById('{{ $chartId }}').getContext('2d');
|
||
|
||
window.charts = window.charts || {};
|
||
window.charts['{{ $chartId }}'] = new Chart(ctx, {
|
||
type: '{{ $chartType }}',
|
||
data: @json($data),
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
...@json($options)
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
@endpush
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## 📄 **CONTENT CARDS**
|
||
|
||
### 📰 **Content Card Template**
|
||
```blade
|
||
<!-- resources/views/components/cards/content-card.blade.php -->
|
||
@props([
|
||
'title' => '',
|
||
'content' => '',
|
||
'image' => null,
|
||
'footer' => null,
|
||
'tags' => [],
|
||
'url' => null
|
||
])
|
||
|
||
<div class="netgescon-card {{ $url ? 'cursor-pointer hover:shadow-lg' : '' }}"
|
||
@if($url) onclick="window.location='{{ $url }}'" @endif>
|
||
|
||
<!-- Image -->
|
||
@if($image)
|
||
<div class="card-image mb-4 -mx-6 -mt-6">
|
||
<img src="{{ $image }}" alt="{{ $title }}"
|
||
class="w-full h-48 object-cover rounded-t-lg">
|
||
</div>
|
||
@endif
|
||
|
||
<!-- Content -->
|
||
<div class="card-content">
|
||
<!-- Title -->
|
||
@if($title)
|
||
<h3 class="text-lg font-semibold text-gray-900 mb-3">{{ $title }}</h3>
|
||
@endif
|
||
|
||
<!-- Content -->
|
||
<div class="text-gray-600 text-sm leading-relaxed">
|
||
{!! $content !!}
|
||
</div>
|
||
|
||
<!-- Tags -->
|
||
@if(count($tags) > 0)
|
||
<div class="card-tags flex flex-wrap gap-2 mt-4">
|
||
@foreach($tags as $tag)
|
||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||
bg-gray-100 text-gray-800">
|
||
{{ $tag }}
|
||
</span>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<!-- Footer -->
|
||
@if($footer)
|
||
<div class="card-footer border-t border-gray-200 mt-4 pt-4 text-sm text-gray-500">
|
||
{!! $footer !!}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## ⚠️ **ALERT CARDS**
|
||
|
||
### 🚨 **Alert Card Template**
|
||
```blade
|
||
<!-- resources/views/components/cards/alert-card.blade.php -->
|
||
@props([
|
||
'type' => 'info', // info, success, warning, danger
|
||
'title' => '',
|
||
'message' => '',
|
||
'actions' => [],
|
||
'dismissible' => true
|
||
])
|
||
|
||
@php
|
||
$alertClasses = [
|
||
'info' => 'border-l-blue-500 bg-blue-50',
|
||
'success' => 'border-l-green-500 bg-green-50',
|
||
'warning' => 'border-l-yellow-500 bg-yellow-50',
|
||
'danger' => 'border-l-red-500 bg-red-50'
|
||
];
|
||
$iconClasses = [
|
||
'info' => 'fas fa-info-circle text-blue-500',
|
||
'success' => 'fas fa-check-circle text-green-500',
|
||
'warning' => 'fas fa-exclamation-triangle text-yellow-500',
|
||
'danger' => 'fas fa-exclamation-circle text-red-500'
|
||
];
|
||
@endphp
|
||
|
||
<div class="netgescon-card {{ $alertClasses[$type] }} border-l-4">
|
||
<div class="flex items-start">
|
||
<!-- Icon -->
|
||
<div class="flex-shrink-0">
|
||
<i class="{{ $iconClasses[$type] }} text-xl"></i>
|
||
</div>
|
||
|
||
<!-- Content -->
|
||
<div class="ml-3 flex-1">
|
||
@if($title)
|
||
<h4 class="font-medium text-gray-900 mb-1">{{ $title }}</h4>
|
||
@endif
|
||
|
||
<div class="text-sm text-gray-700">
|
||
{!! $message !!}
|
||
</div>
|
||
|
||
<!-- Actions -->
|
||
@if(count($actions) > 0)
|
||
<div class="mt-4 flex space-x-3">
|
||
@foreach($actions as $action)
|
||
<a href="{{ $action['url'] }}"
|
||
class="text-sm font-medium {{ $action['class'] ?? 'text-blue-600 hover:text-blue-500' }}">
|
||
{{ $action['text'] }}
|
||
</a>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<!-- Dismiss -->
|
||
@if($dismissible)
|
||
<div class="ml-4">
|
||
<button class="text-gray-400 hover:text-gray-600 transition-colors"
|
||
onclick="this.closest('.netgescon-card').remove()">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
## ⚡ **JAVASCRIPT ENHANCEMENTS**
|
||
|
||
### 📁 **File:** `/public/js/components/cards.js`
|
||
|
||
```javascript
|
||
/**
|
||
* Cards NetGescon - JavaScript Enhancements
|
||
*/
|
||
|
||
// Initialize cards functionality
|
||
function initializeCards() {
|
||
initializeStatsCards();
|
||
initializeChartCards();
|
||
initializeContentCards();
|
||
initializeAnimations();
|
||
}
|
||
|
||
// Stats cards animations
|
||
function initializeStatsCards() {
|
||
const statsCards = document.querySelectorAll('.stats-grid .netgescon-card');
|
||
|
||
// Animate numbers on load
|
||
statsCards.forEach((card, index) => {
|
||
const valueElement = card.querySelector('.text-3xl');
|
||
if (valueElement) {
|
||
const targetValue = valueElement.textContent;
|
||
const numericValue = parseInt(targetValue.replace(/[^\d]/g, ''));
|
||
|
||
if (!isNaN(numericValue)) {
|
||
animateNumber(valueElement, 0, numericValue, 1000, index * 100);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Add click analytics
|
||
statsCards.forEach(card => {
|
||
card.addEventListener('click', function() {
|
||
const title = this.querySelector('.text-sm').textContent;
|
||
trackCardClick('stats', title);
|
||
});
|
||
});
|
||
}
|
||
|
||
// Animate number counting
|
||
function animateNumber(element, start, end, duration, delay = 0) {
|
||
setTimeout(() => {
|
||
const range = end - start;
|
||
const startTime = performance.now();
|
||
const originalText = element.textContent;
|
||
const prefix = originalText.replace(/[\d,]/g, '');
|
||
|
||
function updateNumber(currentTime) {
|
||
const elapsed = currentTime - startTime;
|
||
const progress = Math.min(elapsed / duration, 1);
|
||
|
||
const current = Math.floor(start + (range * easeOutQuart(progress)));
|
||
element.textContent = prefix + current.toLocaleString();
|
||
|
||
if (progress < 1) {
|
||
requestAnimationFrame(updateNumber);
|
||
}
|
||
}
|
||
|
||
requestAnimationFrame(updateNumber);
|
||
}, delay);
|
||
}
|
||
|
||
// Easing function
|
||
function easeOutQuart(t) {
|
||
return 1 - (--t) * t * t * t;
|
||
}
|
||
|
||
// Chart cards functionality
|
||
function initializeChartCards() {
|
||
// Auto-refresh charts
|
||
setInterval(() => {
|
||
const autoRefreshCharts = document.querySelectorAll('[data-auto-refresh="true"]');
|
||
autoRefreshCharts.forEach(chart => {
|
||
const chartId = chart.querySelector('canvas').id;
|
||
refreshChart(chartId);
|
||
});
|
||
}, 300000); // 5 minutes
|
||
}
|
||
|
||
// Refresh chart data
|
||
async function refreshChart(chartId) {
|
||
try {
|
||
const response = await fetch(`/api/charts/${chartId}/data`);
|
||
const data = await response.json();
|
||
|
||
if (window.charts && window.charts[chartId]) {
|
||
window.charts[chartId].data = data;
|
||
window.charts[chartId].update();
|
||
}
|
||
} catch (error) {
|
||
console.warn('Failed to refresh chart:', chartId, error);
|
||
}
|
||
}
|
||
|
||
// Update chart period
|
||
async function updateChart(chartId, period) {
|
||
try {
|
||
const response = await fetch(`/api/charts/${chartId}/data?period=${period}`);
|
||
const data = await response.json();
|
||
|
||
if (window.charts && window.charts[chartId]) {
|
||
window.charts[chartId].data = data;
|
||
window.charts[chartId].update();
|
||
}
|
||
} catch (error) {
|
||
console.warn('Failed to update chart:', chartId, error);
|
||
}
|
||
}
|
||
|
||
// Content cards enhancements
|
||
function initializeContentCards() {
|
||
const contentCards = document.querySelectorAll('.netgescon-card[data-url]');
|
||
|
||
contentCards.forEach(card => {
|
||
card.addEventListener('click', function() {
|
||
const url = this.dataset.url;
|
||
const title = this.querySelector('h3')?.textContent || 'Unknown';
|
||
|
||
trackCardClick('content', title);
|
||
window.location.href = url;
|
||
});
|
||
});
|
||
}
|
||
|
||
// Animation enhancements
|
||
function initializeAnimations() {
|
||
// Intersection Observer for scroll animations
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add('animate-in');
|
||
}
|
||
});
|
||
}, { threshold: 0.1 });
|
||
|
||
// Observe all cards
|
||
document.querySelectorAll('.netgescon-card').forEach(card => {
|
||
observer.observe(card);
|
||
});
|
||
}
|
||
|
||
// Analytics tracking
|
||
function trackCardClick(type, title) {
|
||
// Send analytics data
|
||
if (window.gtag) {
|
||
gtag('event', 'card_click', {
|
||
card_type: type,
|
||
card_title: title,
|
||
page_location: window.location.href
|
||
});
|
||
}
|
||
|
||
// Send to internal analytics
|
||
fetch('/api/analytics/card-click', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
||
},
|
||
body: JSON.stringify({
|
||
type: type,
|
||
title: title,
|
||
url: window.location.href,
|
||
timestamp: new Date().toISOString()
|
||
})
|
||
}).catch(() => {}); // Silent fail
|
||
}
|
||
|
||
// Initialize on DOM ready
|
||
document.addEventListener('DOMContentLoaded', initializeCards);
|
||
```
|
||
|
||
---
|
||
|
||
## 📱 **RESPONSIVE GRID SYSTEM**
|
||
|
||
### 📐 **Grid Layouts**
|
||
```css
|
||
/* Stats Cards Grid */
|
||
.stats-grid {
|
||
display: grid;
|
||
gap: 1.5rem;
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
@media (min-width: 640px) {
|
||
.stats-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1024px) {
|
||
.stats-grid {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
}
|
||
}
|
||
|
||
/* Content Cards Grid */
|
||
.content-grid {
|
||
display: grid;
|
||
gap: 2rem;
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
.content-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1024px) {
|
||
.content-grid {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
}
|
||
}
|
||
|
||
/* Mixed Layout */
|
||
.cards-mixed {
|
||
display: grid;
|
||
gap: 1.5rem;
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
.cards-mixed {
|
||
grid-template-columns: 2fr 1fr;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 **TESTING CHECKLIST**
|
||
|
||
### ✅ **Visual Tests**
|
||
- [ ] Cards consistent spacing
|
||
- [ ] Hover effects smooth
|
||
- [ ] Icons properly aligned
|
||
- [ ] Colors match brand palette
|
||
- [ ] Responsive grid working
|
||
|
||
### ✅ **Functional Tests**
|
||
- [ ] Click handlers working
|
||
- [ ] Chart updates functional
|
||
- [ ] Animations performant
|
||
- [ ] Dismiss buttons working
|
||
- [ ] Analytics tracking
|
||
|
||
### ✅ **Accessibility Tests**
|
||
- [ ] Keyboard navigation
|
||
- [ ] Screen reader compatibility
|
||
- [ ] Color contrast sufficient
|
||
- [ ] Focus indicators visible
|
||
- [ ] Alternative text present
|
||
|
||
---
|
||
|
||
## 🚀 **PERFORMANCE OPTIMIZATION**
|
||
|
||
### ⚡ **Best Practices**
|
||
```css
|
||
/* CSS Optimizations */
|
||
.netgescon-card {
|
||
/* Use transform instead of changing box-shadow directly */
|
||
will-change: transform, box-shadow;
|
||
}
|
||
|
||
.netgescon-card:hover {
|
||
/* Hardware acceleration */
|
||
transform: translateY(-2px) translateZ(0);
|
||
}
|
||
|
||
/* Reduce repaints */
|
||
.card-animation {
|
||
backface-visibility: hidden;
|
||
perspective: 1000px;
|
||
}
|
||
```
|
||
|
||
```javascript
|
||
// JavaScript Optimizations
|
||
function throttle(func, wait) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func(...args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
}
|
||
|
||
// Throttled scroll handler
|
||
window.addEventListener('scroll', throttle(handleScroll, 16));
|
||
```
|
||
|
||
---
|
||
|
||
## 📚 **FILES CORRELATI**
|
||
|
||
### 🧩 **Components**
|
||
- `/resources/views/components/cards/` (directory)
|
||
- `/resources/views/components/cards/stats-card.blade.php`
|
||
- `/resources/views/components/cards/info-card.blade.php`
|
||
- `/resources/views/components/cards/action-card.blade.php`
|
||
- `/resources/views/components/cards/chart-card.blade.php`
|
||
- `/resources/views/components/cards/content-card.blade.php`
|
||
- `/resources/views/components/cards/alert-card.blade.php`
|
||
|
||
### 🎨 **Styles**
|
||
- `/public/css/components/cards.css`
|
||
- `/public/css/netgescon-admin.css`
|
||
|
||
### ⚡ **JavaScript**
|
||
- `/public/js/components/cards.js`
|
||
- `/public/js/charts/chart-manager.js`
|
||
|
||
---
|
||
|
||
## 🎯 **NEXT STEPS**
|
||
|
||
### 🔮 **Planned Improvements**
|
||
- [ ] **Advanced Charts** - More chart types and interactions
|
||
- [ ] **Card Templates** - Pre-built card collections
|
||
- [ ] **Drag & Drop** - Rearrangeable dashboard cards
|
||
- [ ] **Real-time Data** - WebSocket live updates
|
||
- [ ] **Custom Themes** - User-customizable card appearances
|
||
|
||
---
|
||
|
||
*📝 Documento mantenuto da: GitHub Copilot AI Assistant*
|
||
*🔗 Componente: Cards System NetGescon Interface v2.1.0*
|