This commit is contained in:
antv
2026-07-21 10:16:06 +07:00
parent f8385185c9
commit 781a0f8aa7
17 changed files with 436 additions and 122 deletions
@@ -2,7 +2,7 @@
@php
$classes = $active
? 'flex items-center gap-4 px-4 py-3 rounded-xl bg-[#eff4ff] text-[#3462f7] font-semibold transition-all duration-200'
? 'flex items-center gap-4 px-4 py-3 rounded-xl bg-[#eff4ff] text-[#3462f7] font-medium transition-all duration-200'
: 'flex items-center gap-4 px-4 py-3 rounded-xl text-gray-500 hover:bg-gray-50 hover:text-gray-900 font-medium transition-all duration-200';
@endphp
+5 -3
View File
@@ -29,9 +29,11 @@
Thanks đã gửi
</x-sidebar-item>
<x-sidebar-item href="{{ route('user.ranking') }}" :active="request()->routeIs('user.ranking')" icon="trophy">
Bảng xếp hạng
</x-sidebar-item>
@if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
<x-sidebar-item href="{{ route('user.ranking') }}" :active="request()->routeIs('user.ranking')" icon="trophy">
Bảng xếp hạng
</x-sidebar-item>
@endif
@if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
@@ -13,7 +13,10 @@
<div class="flex flex-col">
<span class="text-[13px] font-semibold text-gray-500 mb-1.5">{{ $title }}</span>
<div class="flex items-end gap-3 mb-1">
<span class="text-[34px] font-bold text-[#1e293b] leading-none">{{ $value }}</span>
@php
$isShort = is_numeric(str_replace('#', '', $value)) || strlen($value) <= 4;
@endphp
<span class="{{ $isShort ? 'text-[34px] leading-none' : 'text-[17px] leading-tight' }} font-bold text-[#1e293b]">{{ $value }}</span>
</div>
@if($growth)
@@ -67,12 +67,20 @@
@endif
@endif
</td>
<td class="px-6 py-3.5 whitespace-nowrap font-extrabold text-base">
<td class="px-6 py-3.5 whitespace-nowrap font-extrabold text-base flex items-center gap-1.5">
@if($isReceiver)
<span class="text-green-600">+{{ $admin_record->received }}</span>
@else
<span class="text-blue-600">-{{ $admin_record->sent }}</span>
@endif
@php
$themes = config('card_themes');
$themeId = $admin_record->template_id ?? 1;
$theme = $themes[$themeId] ?? $themes[1];
@endphp
<span class="text-xs font-semibold text-gray-500 flex items-center" title="{{ $theme['name'] }}">
<span>{{ $theme['icon'] }}</span>
</span>
</td>
</tr>
@empty
+96 -35
View File
@@ -38,15 +38,27 @@
</x-stat-card>
<x-stat-card
title="Thứ hạng"
value="#{{ $currentRank > 0 ? $currentRank : '-' }}"
iconBgClass="bg-green-50"
iconColorClass="text-green-500">
<svg class="w-[26px] h-[26px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</x-stat-card>
@if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
<x-stat-card
title="Thứ hạng"
value="#{{ $currentRank > 0 ? $currentRank : '-' }}"
iconBgClass="bg-green-50"
iconColorClass="text-green-500">
<svg class="w-[26px] h-[26px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</x-stat-card>
@else
<x-stat-card
title="Thứ hạng"
value="Không có quyền xem"
iconBgClass="bg-amber-50"
iconColorClass="text-amber-500">
<svg class="w-[26px] h-[26px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</x-stat-card>
@endif
</div>
<div class="flex flex-col gap-6 lg:flex-row lg:items-stretch lg:gap-8">
@@ -96,39 +108,86 @@
<div class="w-full lg:w-[32%] lg:min-w-[340px] lg:max-w-[400px] flex flex-col gap-6">
<div class="bg-white rounded-[24px] shadow-[0_10px_30px_rgba(15,23,42,0.05)] p-6 flex flex-col h-full">
<div class="bg-white rounded-[24px] shadow-[0_10px_30px_rgba(15,23,42,0.05)] p-6 flex flex-col h-full relative overflow-hidden">
<div class="flex items-center justify-between mb-3 pl-1 pr-1">
<h3 class="text-[17px] font-extrabold text-[#1a2b49]">Bảng xếp hạng</h3>
<a href="{{ route('user.ranking') }}" class="text-[13px] font-bold text-[#3462f7] hover:text-blue-800">Xem tất cả</a>
@if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
<a href="{{ route('user.ranking') }}" class="text-[13px] font-bold text-[#3462f7] hover:text-blue-800">Xem tất cả</a>
@endif
</div>
<div class="inline-flex bg-[#F5F7FB] rounded-xl p-1 mb-4 w-fit space-x-2">
<button id="tab-received" onclick="switchRankingTab('received')" class="cursor-pointer flex items-center justify-center gap-2 w-[120px] py-2 rounded-[10px] transition-all bg-white shadow-[0_2px_4px_rgba(0,0,0,0.04)] text-[#3462f7] font-bold text-[14px]">
<svg class="w-[18px] h-[18px]" fill="currentColor" viewBox="0 0 24 24">
<path d="M17.5 4h-11c-1.38 0-2.5 1.12-2.5 2.5V8c0 2.21 1.79 4 4 4h.34c1.17 2.19 3.52 3.56 6.16 3.56s4.99-1.37 6.16-3.56h.34c2.21 0 4-1.79 4-4v-1.5c0-1.38-1.12-2.5-2.5-2.5zm-11 6c-.55 0-1-.45-1-1V6.5c0-.28.22-.5.5-.5h2v4H6.5zm11 0h-2v-4h2c.28 0 .5.22.5.5V9c0 .55-.45 1-1 1zM11 16h2v4H-2v-4zm-2 4h6v2H9v-2z"/>
<path fill="#ffffff" d="M12 11.5L10 7h4z"/>
</svg>
TOP Nhận
</button>
<button id="tab-sent" onclick="switchRankingTab('sent')" class="cursor-pointer flex items-center justify-center gap-2 w-[120px] py-2 rounded-[10px] transition-all text-[#64748b] hover:text-gray-800 font-bold text-[14px]">
<svg class="w-[18px] h-[18px]" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 2L11 13"></path>
<path d="M22 2L15 22L11 13L2 9L22 2Z"></path>
</svg>
TOP Gửi
</button>
</div>
@if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
<div class="inline-flex bg-[#F5F7FB] rounded-xl p-1 mb-4 w-fit space-x-2">
<button id="tab-received" onclick="switchRankingTab('received')" class="cursor-pointer flex items-center justify-center gap-2 w-[120px] py-2 rounded-[10px] transition-all bg-white shadow-[0_2px_4px_rgba(0,0,0,0.04)] text-[#3462f7] font-bold text-[14px]">
<svg class="w-[18px] h-[18px]" fill="currentColor" viewBox="0 0 24 24">
<path d="M17.5 4h-11c-1.38 0-2.5 1.12-2.5 2.5V8c0 2.21 1.79 4 4 4h.34c1.17 2.19 3.52 3.56 6.16 3.56s4.99-1.37 6.16-3.56h.34c2.21 0 4-1.79 4-4v-1.5c0-1.38-1.12-2.5-2.5-2.5zm-11 6c-.55 0-1-.45-1-1V6.5c0-.28.22-.5.5-.5h2v4H6.5zm11 0h-2v-4h2c.28 0 .5.22.5.5V9c0 .55-.45 1-1 1zM11 16h2v4H-2v-4zm-2 4h6v2H9v-2z"/>
<path fill="#ffffff" d="M12 11.5L10 7h4z"/>
</svg>
TOP Nhận
</button>
<button id="tab-sent" onclick="switchRankingTab('sent')" class="cursor-pointer flex items-center justify-center gap-2 w-[120px] py-2 rounded-[10px] transition-all text-[#64748b] hover:text-gray-800 font-bold text-[14px]">
<svg class="w-[18px] h-[18px]" fill="none" stroke="currentColor" stroke-width="2.2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 2L11 13"></path>
<path d="M22 2L15 22L11 13L2 9L22 2Z"></path>
</svg>
TOP Gửi
</button>
</div>
<div id="ranking-list-container" class="flex-1 flex flex-col mb-2">
<!-- Pre-rendered ranking lists -->
<div id="ranking-received-content" class="flex-1 flex flex-col">
@include('user.partials.ranking_list', ['rankers' => array_slice($receivedRankers, 0, 4), 'currentUserRankItem' => $receivedCurrentUserRankItem])
<div id="ranking-list-container" class="flex-1 flex flex-col mb-2">
<!-- Pre-rendered ranking lists -->
<div id="ranking-received-content" class="flex-1 flex flex-col">
@include('user.partials.ranking_list', ['rankers' => array_slice($receivedRankers, 0, 4), 'currentUserRankItem' => $receivedCurrentUserRankItem])
</div>
<div id="ranking-sent-content" class="hidden flex-1 flex flex-col">
@include('user.partials.ranking_list', ['rankers' => array_slice($sentRankers, 0, 4), 'currentUserRankItem' => $sentCurrentUserRankItem])
</div>
</div>
<div id="ranking-sent-content" class="hidden flex-1 flex flex-col">
@include('user.partials.ranking_list', ['rankers' => array_slice($sentRankers, 0, 4), 'currentUserRankItem' => $sentCurrentUserRankItem])
@else
<div class="relative flex-1 flex flex-col h-full min-h-[220px]">
<!-- Blur container -->
<div class="filter blur-[5px] select-none pointer-events-none opacity-40 flex-1 flex flex-col">
<div class="inline-flex bg-[#F5F7FB] rounded-xl p-1 mb-4 w-fit space-x-2">
<button class="flex items-center justify-center gap-2 w-[120px] py-2 rounded-[10px] bg-white text-[#3462f7] font-bold text-[14px]">TOP Nhận</button>
<button class="flex items-center justify-center gap-2 w-[120px] py-2 rounded-[10px] text-[#64748b] font-bold text-[14px]">TOP Gửi</button>
</div>
<div class="space-y-3.5">
<div class="flex items-center gap-3 p-3 rounded-2xl bg-gray-50/80 border border-gray-100">
<div class="w-10 h-10 bg-gray-200 rounded-full"></div>
<div class="flex-1 space-y-2">
<div class="h-3.5 bg-gray-200 rounded w-24"></div>
<div class="h-2.5 bg-gray-200 rounded w-16"></div>
</div>
</div>
<div class="flex items-center gap-3 p-3 rounded-2xl bg-gray-50/80 border border-gray-100">
<div class="w-10 h-10 bg-gray-200 rounded-full"></div>
<div class="flex-1 space-y-2">
<div class="h-3.5 bg-gray-200 rounded w-28"></div>
<div class="h-2.5 bg-gray-200 rounded w-12"></div>
</div>
</div>
<div class="flex items-center gap-3 p-3 rounded-2xl bg-gray-50/80 border border-gray-100">
<div class="w-10 h-10 bg-gray-200 rounded-full"></div>
<div class="flex-1 space-y-2">
<div class="h-3.5 bg-gray-200 rounded w-20"></div>
<div class="h-2.5 bg-gray-200 rounded w-14"></div>
</div>
</div>
</div>
</div>
<!-- Overlay -->
<div class="absolute inset-0 flex flex-col items-center justify-center bg-white/40 backdrop-blur-[1px] rounded-2xl z-10 p-4 text-center">
<div class="w-14 h-14 bg-amber-500 text-white rounded-full flex items-center justify-center shadow-lg shadow-amber-500/30 mb-3.5 transform transition-transform hover:scale-105 duration-300">
<svg class="w-7 h-7" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<h4 class="text-sm font-extrabold text-[#1a2b49] mb-1">Không quyền xem</h4>
<p class="text-[11px] text-gray-500 font-semibold max-w-[200px] leading-relaxed">Bảng xếp hạng đã bị ẩn với tài khoản thành viên.</p>
</div>
</div>
</div>
</div>
@endif
</div>
</div>
</div>
@@ -142,6 +201,8 @@
const receivedContent = document.getElementById('ranking-received-content');
const sentContent = document.getElementById('ranking-sent-content');
if (!receivedTab || !sentTab || !receivedContent || !sentContent) return;
const activeClass = 'flex items-center justify-center gap-2 w-[120px] py-2 rounded-[10px] transition-all bg-white shadow-[0_2px_4px_rgba(0,0,0,0.04)] text-[#3462f7] font-bold text-[14px]';
const inactiveClass = 'flex items-center justify-center gap-2 w-[120px] py-2 rounded-[10px] transition-all text-[#64748b] hover:text-gray-800 font-bold text-[14px]';
@@ -1,20 +1,31 @@
@php
$themes = config('card_themes');
@endphp
@foreach ($cards as $c)
<div class="bg-white rounded-[24px] border border-gray-100 p-6 shadow-sm hover:shadow-md hover:border-[#8b5cf6] hover:-translate-y-1 transition-all duration-300 flex flex-col justify-between h-full group">
@php
$themeId = $c->template_id ?? 1;
$theme = $themes[$themeId] ?? $themes[1];
@endphp
<div class="rounded-[24px] border p-6 shadow-sm hover:shadow-md hover:-translate-y-1 transition-all duration-300 flex flex-col justify-between h-full group"
style="background: {{ $theme['card_bg'] }}; border-color: {{ $theme['border_color'] }};">
<div>
<div class="flex items-center justify-between mb-4">
<span class="text-xs font-bold text-gray-400 flex items-center gap-1.5">
<span class="text-[14px]">💜</span> Từ
<span class="text-[14px]">{{ $theme['icon'] }}</span> Từ
</span>
<span class="text-[11px] font-extrabold text-[#6d28d9] bg-[#f5f3ff] px-2 py-0.5 rounded-full">
{{ $c->received }} Thẻ
<span class="text-[11px] font-extrabold px-2.5 py-0.5 rounded-full"
style="background-color: {{ $theme['badge_bg'] }}; color: {{ $theme['accent_color'] }};">
{{ $c->received }} Thẻ ({{ $theme['name'] }})
</span>
</div>
<div class="flex items-start gap-3.5">
<div class="relative shrink-0">
@if($c->senderUser)
<x-avatar :user="$c->senderUser" class="w-12 h-12 rounded-full object-cover border-2 border-purple-50" />
<x-avatar :user="$c->senderUser" class="w-12 h-12 rounded-full object-cover border-2" style="border-color: {{ $theme['border_color'] }};" />
@else
<div class="w-12 h-12 rounded-full bg-gray-50 border border-gray-200 flex items-center justify-center font-bold text-gray-400">
<div class="w-12 h-12 rounded-full bg-gray-50/50 border flex items-center justify-center font-bold text-gray-400"
style="border-color: {{ $theme['border_color'] }};">
{{ strtoupper(substr($c->sender ?? 'U', 0, 1)) }}
</div>
@endif
@@ -33,9 +44,10 @@
</div>
</div>
<div class="flex items-center justify-between pt-4 border-t border-gray-50 text-[11px] font-semibold text-gray-400 mt-6">
<div class="flex items-center justify-between pt-4 border-t text-[11px] font-semibold text-gray-400 mt-6"
style="border-top-color: {{ $theme['footer_border'] }}; border-top-style: dashed;">
<span>Ngày nhận:</span>
<span>{{ \Carbon\Carbon::parse($c->date)->format('d/m/Y') }}</span>
<span class="font-bold" style="color: {{ $theme['accent_color'] }};">{{ \Carbon\Carbon::parse($c->date)->format('d/m/Y') }}</span>
</div>
</div>
@endforeach
@@ -1,20 +1,31 @@
@php
$themes = config('card_themes');
@endphp
@foreach ($cards as $c)
<div class="bg-white rounded-[24px] border border-gray-100 p-6 shadow-sm hover:shadow-md hover:border-[#3462f7] hover:-translate-y-1 transition-all duration-300 flex flex-col justify-between h-full group">
@php
$themeId = $c->template_id ?? 1;
$theme = $themes[$themeId] ?? $themes[1];
@endphp
<div class="rounded-[24px] border p-6 shadow-sm hover:shadow-md hover:-translate-y-1 transition-all duration-300 flex flex-col justify-between h-full group"
style="background: {{ $theme['card_bg'] }}; border-color: {{ $theme['border_color'] }};">
<div>
<div class="flex items-center justify-between mb-4">
<span class="text-xs font-bold text-gray-400 flex items-center gap-1.5">
<span class="text-[14px]">💌</span> Gửi tới
<span class="text-[14px]">{{ $theme['icon'] }}</span> Gửi tới
</span>
<span class="text-[11px] font-extrabold text-blue-600 bg-blue-50 px-2 py-0.5 rounded-full">
{{ $c->sent }} Thẻ
<span class="text-[11px] font-extrabold px-2.5 py-0.5 rounded-full"
style="background-color: {{ $theme['badge_bg'] }}; color: {{ $theme['accent_color'] }};">
{{ $c->sent }} Thẻ ({{ $theme['name'] }})
</span>
</div>
<div class="flex items-start gap-3.5">
<div class="relative shrink-0">
@if($c->receiverUser)
<x-avatar :user="$c->receiverUser" class="w-12 h-12 rounded-full object-cover border-2 border-blue-50" />
<x-avatar :user="$c->receiverUser" class="w-12 h-12 rounded-full object-cover border-2" style="border-color: {{ $theme['border_color'] }};" />
@else
<div class="w-12 h-12 rounded-full bg-gray-50 border border-gray-200 flex items-center justify-center font-bold text-gray-400">
<div class="w-12 h-12 rounded-full bg-gray-50/50 border flex items-center justify-center font-bold text-gray-400"
style="border-color: {{ $theme['border_color'] }};">
{{ strtoupper(substr($c->receiver ?? 'U', 0, 1)) }}
</div>
@endif
@@ -33,9 +44,10 @@
</div>
</div>
<div class="flex items-center justify-between pt-4 border-t border-gray-50 text-[11px] font-semibold text-gray-400 mt-6">
<div class="flex items-center justify-between pt-4 border-t text-[11px] font-semibold text-gray-400 mt-6"
style="border-top-color: {{ $theme['footer_border'] }}; border-top-style: dashed;">
<span>Ngày gửi:</span>
<span>{{ \Carbon\Carbon::parse($c->date)->format('d/m/Y') }}</span>
<span class="font-bold" style="color: {{ $theme['accent_color'] }};">{{ \Carbon\Carbon::parse($c->date)->format('d/m/Y') }}</span>
</div>
</div>
@endforeach
+81 -5
View File
@@ -10,7 +10,6 @@
<h2 class="text-[32px] md:text-[42px] font-extrabold text-[#1a2b49] mb-3 drop-shadow-sm">🏆 Bảng xếp hạng</h2>
<p class="text-[16px] md:text-[18px] text-gray-700 mb-4 max-w-2xl">Tôn vinh những nhân lan tỏa văn hóa cảm ơn ghi nhận trong tổ chức.</p>
</x-hero-banner>
<img src="{{ asset('images/illustrations/trophy.svg') }}" alt="trophy" class="absolute right-4 bottom-0 w-40 h-40 md:w-56 md:h-56 opacity-20 pointer-events-none">
</div>
<!-- Month Filter -->
@@ -147,8 +146,13 @@
{{-- Users in Group --}}
<div class="p-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
@foreach($group as $user)
<div class="group bg-white rounded-[16px] p-4 flex items-center gap-4 transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_12px_24px_rgba(0,0,0,0.06)] border border-gray-100 cursor-default relative overflow-hidden">
@php
$total = $group->count();
$desktopLimit = $total > 12 ? 11 : 12;
$mobileLimit = $total > 4 ? 3 : 4;
@endphp
@foreach($group as $index => $user)
<div class="group user-card bg-white rounded-[16px] p-4 flex items-center gap-4 transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_12px_24px_rgba(0,0,0,0.06)] border border-gray-100 cursor-default relative overflow-hidden @if($index >= $desktopLimit) hide-all @elseif($index >= $mobileLimit) hide-mobile @endif">
<div class="absolute inset-0 bg-gradient-to-r from-blue-50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"></div>
<x-avatar :user="$user" class="w-12 h-12" />
<div class="flex-1 min-w-0 relative z-10">
@@ -160,6 +164,30 @@
</div>
</div>
@endforeach
@if($total > 4)
{{-- Mobile Xem Them Button --}}
<button type="button" class="xem-them-btn-mobile md:hidden flex flex-col items-center justify-center bg-white rounded-[16px] border border-gray-200 border-dashed hover:border-primary hover:bg-blue-50 transition-all p-4 text-primary font-bold min-h-[80px]" onclick="expandGroup(this)">
<div class="flex items-center gap-1 mb-1 text-lg">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
<span>+{{ $total - 3 }}</span>
</div>
<span class="text-xs">Xem thêm</span>
</button>
@endif
@if($total > 12)
{{-- Desktop Xem Them Button --}}
<button type="button" class="xem-them-btn-desktop hidden md:flex flex-col items-center justify-center bg-white rounded-[16px] border border-gray-200 border-dashed hover:border-primary hover:bg-blue-50 transition-all p-4 text-primary font-bold min-h-[80px]" onclick="expandGroup(this)">
<div class="flex items-center gap-1 mb-1 text-lg">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
<span>+{{ $total - 11 }}</span>
</div>
<span class="text-xs">Xem thêm</span>
</button>
@endif
</div>
</div>
@endforeach
@@ -274,8 +302,13 @@
{{-- Users in Group --}}
<div class="p-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
@foreach($group as $user)
<div class="group bg-white rounded-[16px] p-4 flex items-center gap-4 transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_12px_24px_rgba(0,0,0,0.06)] border border-gray-100 cursor-default relative overflow-hidden">
@php
$total = $group->count();
$desktopLimit = $total > 12 ? 11 : 12;
$mobileLimit = $total > 4 ? 3 : 4;
@endphp
@foreach($group as $index => $user)
<div class="group bg-white rounded-[16px] p-4 flex items-center gap-4 transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_12px_24px_rgba(0,0,0,0.06)] border border-gray-100 cursor-default relative overflow-hidden @if($index >= $desktopLimit) hide-all @elseif($index >= $mobileLimit) hide-mobile @endif">
<div class="absolute inset-0 bg-gradient-to-r from-purple-50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"></div>
<x-avatar :user="$user" class="w-12 h-12" />
<div class="flex-1 min-w-0 relative z-10">
@@ -287,6 +320,30 @@
</div>
</div>
@endforeach
@if($total > 4)
{{-- Mobile Xem Them Button --}}
<button type="button" class="xem-them-btn-mobile md:hidden flex flex-col items-center justify-center bg-white rounded-[16px] border border-gray-200 border-dashed hover:border-primary hover:bg-blue-50 transition-all p-4 text-primary font-bold min-h-[80px]" onclick="expandGroup(this)">
<div class="flex items-center gap-1 mb-1 text-lg">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
<span>+{{ $total - 3 }}</span>
</div>
<span class="text-xs">Xem thêm</span>
</button>
@endif
@if($total > 12)
{{-- Desktop Xem Them Button --}}
<button type="button" class="xem-them-btn-desktop hidden md:flex flex-col items-center justify-center bg-white rounded-[16px] border border-gray-200 border-dashed hover:border-primary hover:bg-blue-50 transition-all p-4 text-primary font-bold min-h-[80px]" onclick="expandGroup(this)">
<div class="flex items-center gap-1 mb-1 text-lg">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
<span>+{{ $total - 11 }}</span>
</div>
<span class="text-xs">Xem thêm</span>
</button>
@endif
</div>
</div>
@endforeach
@@ -307,6 +364,12 @@
.animate-fade-in {
animation: fadeIn 0.4s ease-out forwards;
}
/* Custom classes for hiding excess items */
.hide-all { display: none !important; }
@media (max-width: 767px) {
.hide-mobile { display: none !important; }
}
</style>
@endsection
@@ -344,5 +407,18 @@
contentReceived.classList.add('hidden');
}
}
function expandGroup(btn) {
const container = btn.closest('.grid');
const hiddenItems = container.querySelectorAll('.hide-all, .hide-mobile');
hiddenItems.forEach(el => {
el.classList.remove('hide-all', 'hide-mobile');
});
// Hide all xem-them buttons in this container
const btns = container.querySelectorAll('.xem-them-btn-mobile, .xem-them-btn-desktop');
btns.forEach(b => b.style.display = 'none');
}
</script>
@endpush
+25 -33
View File
@@ -3,12 +3,7 @@
@section('content')
@php
$tpls = [
1=>['icon'=>'💌','name'=>'Thư cảm ơn', 'thumb'=>'from-[#FFF9EC] to-[#FFF5DD]','thumbAccent'=>'#FFD066'],
2=>['icon'=>'🌸','name'=>'Sakura', 'thumb'=>'from-[#FFF5F9] to-[#FFECF4]','thumbAccent'=>'#F9A8D4'],
3=>['icon'=>'⭐','name'=>'Appreciation','thumb'=>'from-[#EFF8FF] to-[#DBEAFE]','thumbAccent'=>'#93C5FD'],
4=>['icon'=>'🎉','name'=>'Celebration', 'thumb'=>'from-[#F5F0FF] to-[#EDE9FE]','thumbAccent'=>'#C4B5FD'],
];
$tpls = config('card_themes');
@endphp
<div class="max-w-[760px] mx-auto w-full pb-12 px-4 lg:px-0">
@@ -184,32 +179,25 @@ $tpls = [
document.addEventListener('DOMContentLoaded', function() {
// ── Theme data ───────────────────────────────────────────────────
const T = {
1:{icon:'💌',name:'Thư cảm ơn', label:'THANK CARD',
deco:'linear-gradient(135deg,#FFF5CC,#FFE8A0)',
card:'linear-gradient(145deg,#FFF9EC,#FFFDF7)',
border:'#FFE4A0', blob1:'#FFD066', blob2:'#FFAD00',
accent:'#B8860B', badgeBg:'rgba(255,176,0,0.15)', wave:'white',
footerBorder:'#FFE4A0'},
2:{icon:'🌸',name:'Sakura', label:'SAKURA CARD',
deco:'linear-gradient(135deg,#FFE8F4,#FFD6EC)',
card:'linear-gradient(145deg,#FFF8FB,#FFF3F8)',
border:'#FFBCD9', blob1:'#F9A8D4', blob2:'#F472B6',
accent:'#be185d', badgeBg:'rgba(244,114,182,0.12)', wave:'white',
footerBorder:'#FFBCD9'},
3:{icon:'⭐',name:'Appreciation',label:'APPRECIATION',
deco:'linear-gradient(135deg,#DBEAFE,#BFDBFE)',
card:'linear-gradient(145deg,#F0F8FF,#EAF3FF)',
border:'#BFDBFE', blob1:'#93C5FD', blob2:'#60A5FA',
accent:'#1d4ed8', badgeBg:'rgba(96,165,250,0.12)', wave:'white',
footerBorder:'#BFDBFE'},
4:{icon:'🎉',name:'Celebration', label:'CELEBRATION',
deco:'linear-gradient(135deg,#EDE9FE,#DDD6FE)',
card:'linear-gradient(145deg,#F8F5FF,#F3EFFF)',
border:'#DDD6FE', blob1:'#C4B5FD', blob2:'#A78BFA',
accent:'#6d28d9', badgeBg:'rgba(167,139,250,0.12)', wave:'white',
footerBorder:'#DDD6FE'},
};
const cardThemesData = @json(config('card_themes'));
const T = {};
Object.keys(cardThemesData).forEach(id => {
const item = cardThemesData[id];
T[id] = {
icon: item.icon,
name: item.name,
label: item.label,
deco: item.deco_bg,
card: item.card_bg,
border: item.border_color,
blob1: item.blob1,
blob2: item.blob2,
accent: item.accent_color,
badgeBg: item.badge_bg,
wave: item.wave,
footerBorder: item.footer_border
};
});
// ── Theme slugs ──────────────────────────────────────────────────
const templateSlugs = {
@@ -541,7 +529,11 @@ document.addEventListener('DOMContentLoaded', function() {
$.ajax({
url:'{{ route("user.store_send") }}', method:'POST', contentType:'application/json',
headers:{'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content')},
data:JSON.stringify({receiver:msnv,amount:amount}),
data:JSON.stringify({
receiver: msnv,
amount: amount,
template_id: parseInt(document.getElementById('template_id').value, 10)
}),
success:function(res){
if(res.success){
setButtonLoading(false);