{{-- Animated Stat Card Component - Stats with counting animation and sparkline Usage: --}} @props([ 'value' => 0, 'label' => 'Stat', 'icon' => 'chart-line', 'color' => 'blue', 'trend' => null, 'trendLabel' => 'vs mois dernier', 'link' => null, 'description' => null, 'prefix' => '', 'suffix' => '', 'animate' => true ]) @php $colorClasses = [ 'blue' => ['bg' => 'bg-blue-50', 'icon' => 'text-blue-600', 'border' => 'border-blue-200', 'trend' => 'text-blue-600'], 'green' => ['bg' => 'bg-green-50', 'icon' => 'text-green-600', 'border' => 'border-green-200', 'trend' => 'text-green-600'], 'purple' => ['bg' => 'bg-purple-50', 'icon' => 'text-purple-600', 'border' => 'border-purple-200', 'trend' => 'text-purple-600'], 'orange' => ['bg' => 'bg-orange-50', 'icon' => 'text-orange-600', 'border' => 'border-orange-200', 'trend' => 'text-orange-600'], 'red' => ['bg' => 'bg-red-50', 'icon' => 'text-red-600', 'border' => 'border-red-200', 'trend' => 'text-red-600'], 'cyan' => ['bg' => 'bg-cyan-50', 'icon' => 'text-cyan-600', 'border' => 'border-cyan-200', 'trend' => 'text-cyan-600'], 'indigo' => ['bg' => 'bg-indigo-50', 'icon' => 'text-indigo-600', 'border' => 'border-indigo-200', 'trend' => 'text-indigo-600'], 'emerald' => ['bg' => 'bg-emerald-50', 'icon' => 'text-emerald-600', 'border' => 'border-emerald-200', 'trend' => 'text-emerald-600'], ]; $colors = $colorClasses[$color] ?? $colorClasses['blue']; $tag = $link ? 'a' : 'div'; @endphp <{{ $tag }} @if($link) href="{{ $link }}" @endif x-data="{ displayValue: 0, targetValue: {{ is_numeric($value) ? $value : 0 }}, animated: false, init() { if (!{{ $animate ? 'true' : 'false' }}) { this.displayValue = this.targetValue; return; } const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting && !this.animated) { this.animated = true; this.animateValue(); } }); }, { threshold: 0.5 }); observer.observe(this.$el); }, animateValue() { const duration = 1500; const start = performance.now(); const startValue = 0; const step = (timestamp) => { const progress = Math.min((timestamp - start) / duration, 1); const easeOutQuart = 1 - Math.pow(1 - progress, 4); this.displayValue = Math.floor(startValue + (this.targetValue - startValue) * easeOutQuart); if (progress < 1) { requestAnimationFrame(step); } else { this.displayValue = this.targetValue; } }; requestAnimationFrame(step); } }" class="stat-card bg-white rounded-xl shadow-md border {{ $colors['border'] }} p-4 hover:shadow-lg transition-all duration-300 group {{ $link ? 'cursor-pointer' : '' }}" @if($description) title="{{ $description }}" @endif >
@if($trend !== null)
{{ $trend >= 0 ? '+' : '' }}{{ $trend }}%
@endif

{{ $prefix }}{{ $value }}{{ $suffix }}

{{ $label }}

@if($trend !== null)

{{ $trendLabel }}

@endif @if($link)
@endif