| @props([ | |
| 'title', | |
| 'value', | |
| 'previousValue' => 0, | |
| 'icon', | |
| 'color' => 'emerald', | |
| 'prefix' => '', | |
| 'suffix' => '', | |
| 'showTrend' => true, | |
| 'loading' => false | |
| ]) | |
| @php | |
| $percentageChange = $previousValue > 0 ? (($value - $previousValue) / $previousValue) * 100 : 0; | |
| $isPositive = $percentageChange >= 0; | |
| $colorClasses = [ | |
| 'emerald' => [ | |
| 'text' => 'text-emerald-400', | |
| 'bg' => 'from-emerald-400/20 to-emerald-600/10', | |
| 'glow' => 'rgba(52, 211, 153, 0.3)' | |
| ], | |
| 'purple' => [ | |
| 'text' => 'text-purple-400', | |
| 'bg' => 'from-purple-400/20 to-purple-600/10', | |
| 'glow' => 'rgba(168, 85, 247, 0.3)' | |
| ], | |
| 'blue' => [ | |
| 'text' => 'text-blue-400', | |
| 'bg' => 'from-blue-400/20 to-blue-600/10', | |
| 'glow' => 'rgba(59, 130, 246, 0.3)' | |
| ], | |
| 'orange' => [ | |
| 'text' => 'text-orange-400', | |
| 'bg' => 'from-orange-400/20 to-orange-600/10', | |
| 'glow' => 'rgba(251, 146, 60, 0.3)' | |
| ] | |
| ]; | |
| $currentColor = $colorClasses[$color] ?? $colorClasses['emerald']; | |
| @endphp | |
| <div class="bg-white/5 backdrop-blur-2xl border border-white/10 rounded-2xl p-6 shadow-2xl hover:bg-white/10 transition-all duration-300 group" | |
| style="box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.1);"> | |
| <!-- Glassmorphic overlay --> | |
| <div class="absolute inset-0 bg-gradient-to-br {{ $currentColor['bg'] }} rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> | |
| <!-- Content --> | |
| <div class="relative z-10"> | |
| <!-- Header --> | |
| <div class="flex items-center justify-between mb-4"> | |
| <div class="flex items-center space-x-3"> | |
| @if($icon) | |
| <div class="w-10 h-10 bg-gradient-to-br {{ $currentColor['bg'] }} rounded-xl flex items-center justify-center border border-white/20"> | |
| <i class="{{ $icon }} {{ $currentColor['text'] }} text-lg"></i> | |
| </div> | |
| @endif | |
| <h3 class="text-white/80 text-sm font-medium uppercase tracking-wider">{{ $title }}</h3> | |
| </div> | |
| @if($showTrend && !$loading) | |
| <div class="flex items-center space-x-1 {{ $isPositive ? 'bg-emerald-400/10' : 'bg-red-400/10' }} px-2 py-1 rounded-full"> | |
| <span class="{{ $isPositive ? 'text-emerald-400' : 'text-red-400' }} text-sm font-bold"> | |
| {{ $isPositive ? '↗' : '↘' }} | |
| </span> | |
| <span class="{{ $isPositive ? 'text-emerald-400' : 'text-red-400' }} text-xs font-semibold"> | |
| {{ number_format(abs($percentageChange), 1) }}% | |
| </span> | |
| </div> | |
| @endif | |
| </div> | |
| <!-- Value --> | |
| <div class="mb-2"> | |
| @if($loading) | |
| <div class="animate-pulse"> | |
| <div class="h-8 bg-white/10 rounded w-3/4 mb-2"></div> | |
| <div class="h-4 bg-white/10 rounded w-1/2"></div> | |
| </div> | |
| @else | |
| <div class="text-3xl font-bold {{ $currentColor['text'] }} mb-1 drop-shadow-sm" | |
| style="text-shadow: 0 0 15px {{ $currentColor['glow'] }};" | |
| data-countup="{{ $value }}" | |
| data-prefix="{{ $prefix }}" | |
| data-suffix="{{ $suffix }}"> | |
| {{ $prefix }}{{ number_format($value) }}{{ $suffix }} | |
| </div> | |
| @if($previousValue > 0) | |
| <p class="text-white/60 text-xs"> | |
| Previous: {{ $prefix }}{{ number_format($previousValue) }}{{ $suffix }} | |
| </p> | |
| @endif | |
| @endif | |
| </div> | |
| <!-- Additional Info Slot --> | |
| @if(isset($slot) && !empty(trim($slot))) | |
| <div class="pt-3 border-t border-white/10"> | |
| {{ $slot }} | |
| </div> | |
| @endif | |
| </div> | |
| </div> | |
| @push('scripts') | |
| <script type="module"> | |
| import { initCounter } from '/resources/js/dashboard/chartUtils.js'; | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize counters for all metric cards | |
| const counterElements = document.querySelectorAll('[data-countup]'); | |
| counterElements.forEach(element => { | |
| const value = parseFloat(element.dataset.countup) || 0; | |
| const prefix = element.dataset.prefix || ''; | |
| const suffix = element.dataset.suffix || ''; | |
| // Initialize counter with custom options | |
| initCounter(element, value, { | |
| prefix: prefix, | |
| suffix: suffix, | |
| duration: 2.5, | |
| useEasing: true, | |
| useGrouping: true | |
| }); | |
| }); | |
| }); | |
| </script> | |
| @endpush |