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
+47 -12
View File
@@ -31,22 +31,52 @@ class UserController extends Controller
$user = Auth::user(); $user = Auth::user();
$administrations = $this->userService->getDashboard($user, $selectedMonth); $administrations = $this->userService->getDashboard($user, $selectedMonth);
$stats = $this->userService->getPersonalStats($user, $selectedMonth);
$receivedRanking = $this->userService->getRankingList('received', $selectedMonth, $user); $isAdmin = $user->role == config('constants.ROLE_ADMIN');
$sentRanking = $this->userService->getRankingList('sent', $selectedMonth, $user);
if ($isAdmin) {
$stats = $this->userService->getPersonalStats($user, $selectedMonth);
$receivedRanking = $this->userService->getRankingList('received', $selectedMonth, $user);
$sentRanking = $this->userService->getRankingList('sent', $selectedMonth, $user);
$receivedCount = (int) $stats['receivedCount'];
$sentCount = (int) $stats['sentCount'];
$currentRank = (int) $stats['currentRank'];
$receivedRankers = $receivedRanking['rankers'];
$receivedCurrentUserRankItem = $receivedRanking['currentUserRankItem'];
$sentRankers = $sentRanking['rankers'];
$sentCurrentUserRankItem = $sentRanking['currentUserRankItem'];
} else {
// For member, do NOT fetch ranking lists or calculate rank to ensure security and improve DB performance
$startOfMonth = Carbon::parse($selectedMonth)->startOfMonth();
$endOfMonth = Carbon::parse($selectedMonth)->endOfMonth();
$receivedCount = (int) \App\Models\Administration::where('msnv', $user->msnv)
->whereBetween('date', [$startOfMonth, $endOfMonth])
->sum('received');
$sentCount = (int) \App\Models\Administration::where('msnv', $user->msnv)
->whereBetween('date', [$startOfMonth, $endOfMonth])
->sum('sent');
$currentRank = 0;
$receivedRankers = [];
$receivedCurrentUserRankItem = null;
$sentRankers = [];
$sentCurrentUserRankItem = null;
}
$dashboardData = new DashboardDataDto( $dashboardData = new DashboardDataDto(
administrations: $administrations, administrations: $administrations,
selectedMonth: $selectedMonth, selectedMonth: $selectedMonth,
user: $user, user: $user,
receivedRankers: $receivedRanking['rankers'], receivedRankers: $receivedRankers,
receivedCurrentUserRankItem: $receivedRanking['currentUserRankItem'], receivedCurrentUserRankItem: $receivedCurrentUserRankItem,
sentRankers: $sentRanking['rankers'], sentRankers: $sentRankers,
sentCurrentUserRankItem: $sentRanking['currentUserRankItem'], sentCurrentUserRankItem: $sentCurrentUserRankItem,
receivedCount: (int) $stats['receivedCount'], receivedCount: $receivedCount,
sentCount: (int) $stats['sentCount'], sentCount: $sentCount,
currentRank: (int) $stats['currentRank'] currentRank: $currentRank
); );
return view('user.dashboard', $dashboardData->toArray()); return view('user.dashboard', $dashboardData->toArray());
@@ -86,7 +116,8 @@ class UserController extends Controller
$this->userService->sendThankcards( $this->userService->sendThankcards(
Auth::user(), Auth::user(),
(string) $request->input('receiver'), (string) $request->input('receiver'),
(int) $request->input('amount') (int) $request->input('amount'),
(int) $request->input('template_id', 1)
); );
} catch (ThankCardException $e) { } catch (ThankCardException $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 422); return response()->json(['success' => false, 'message' => $e->getMessage()], 422);
@@ -175,8 +206,12 @@ class UserController extends Controller
} }
// New page: ranking (both received and sent) with tabs // New page: ranking (both received and sent) with tabs
public function ranking(Request $request): View public function ranking(Request $request): \Illuminate\View\View
{ {
if (Auth::user()->role !== config('constants.ROLE_ADMIN')) {
abort(404);
}
$selectedMonth = $request->input('month', Carbon::now()->format('Y-m')); $selectedMonth = $request->input('month', Carbon::now()->format('Y-m'));
$user = Auth::user(); $user = Auth::user();
$receivedRanking = $this->userService->getRankingList('received', $selectedMonth, $user); $receivedRanking = $this->userService->getRankingList('received', $selectedMonth, $user);
@@ -27,6 +27,7 @@ class SendThankCardRequest extends FormRequest
Rule::exists('user', 'msnv')->where('status', config('constants.STATUS_ACTIVE')) Rule::exists('user', 'msnv')->where('status', config('constants.STATUS_ACTIVE'))
], ],
'amount' => 'required|integer|min:1|max:' . Administration::MAX_SEND_CARD_PER_MONTH, 'amount' => 'required|integer|min:1|max:' . Administration::MAX_SEND_CARD_PER_MONTH,
'template_id' => 'nullable|integer|in:1,2,3,4',
]; ];
} }
+1
View File
@@ -22,6 +22,7 @@ class Administration extends Model
'sent', 'sent',
'receiver', 'receiver',
'date', 'date',
'template_id',
]; ];
public function senderUser() public function senderUser()
@@ -11,7 +11,7 @@ interface UserServiceInterface
public function getOtherActiveUsers(User $currentUser): \Illuminate\Database\Eloquent\Collection; public function getOtherActiveUsers(User $currentUser): \Illuminate\Database\Eloquent\Collection;
public function sendThankcards(User $sender, string $receiverMsnv, int $amount): void; public function sendThankcards(User $sender, string $receiverMsnv, int $amount, ?int $templateId = 1): void;
public function updatePassword(User $user, string $newPassword): void; public function updatePassword(User $user, string $newPassword): void;
+16 -14
View File
@@ -33,7 +33,7 @@ class UserService implements UserServiceInterface
->get(); ->get();
} }
public function sendThankcards(User $sender, string $receiverMsnv, int $amount): void public function sendThankcards(User $sender, string $receiverMsnv, int $amount, ?int $templateId = 1): void
{ {
if ($sender->flag_send == config('constants.FLAG_SEND_DISABLED')) { if ($sender->flag_send == config('constants.FLAG_SEND_DISABLED')) {
throw new \App\Exceptions\ThankCardException(__('messages.error.no_send_permission')); throw new \App\Exceptions\ThankCardException(__('messages.error.no_send_permission'));
@@ -47,7 +47,7 @@ class UserService implements UserServiceInterface
$startOfMonth = Carbon::now()->startOfMonth(); $startOfMonth = Carbon::now()->startOfMonth();
$endOfMonth = Carbon::now()->endOfMonth(); $endOfMonth = Carbon::now()->endOfMonth();
DB::transaction(function () use ($sender, $receiverMsnv, $amount, $startOfMonth, $endOfMonth) { DB::transaction(function () use ($sender, $receiverMsnv, $amount, $startOfMonth, $endOfMonth, $templateId) {
// Lock sender record to prevent concurrent transaction modifications // Lock sender record to prevent concurrent transaction modifications
$lockedSender = User::where('id', $sender->id)->lockForUpdate()->first(); $lockedSender = User::where('id', $sender->id)->lockForUpdate()->first();
@@ -76,21 +76,23 @@ class UserService implements UserServiceInterface
} }
Administration::create([ Administration::create([
'msnv' => $receiverMsnv, 'msnv' => $receiverMsnv,
'received' => $amount, 'received' => $amount,
'sender' => $lockedSender->msnv, 'sender' => $lockedSender->msnv,
'sent' => 0, 'sent' => 0,
'receiver' => null, 'receiver' => null,
'date' => Carbon::today(), 'date' => Carbon::today(),
'template_id' => $templateId,
]); ]);
Administration::create([ Administration::create([
'msnv' => $lockedSender->msnv, 'msnv' => $lockedSender->msnv,
'received' => 0, 'received' => 0,
'sender' => null, 'sender' => null,
'sent' => $amount, 'sent' => $amount,
'receiver' => $receiverMsnv, 'receiver' => $receiverMsnv,
'date' => Carbon::today(), 'date' => Carbon::today(),
'template_id' => $templateId,
]); ]);
$lockedSender->card -= $amount; $lockedSender->card -= $amount;
+76
View File
@@ -0,0 +1,76 @@
<?php
return [
1 => [
'id' => 1,
'slug' => 'thu_cam_on',
'name' => 'Thư cảm ơn',
'label' => 'THANK CARD',
'icon' => '💌',
'deco_bg' => 'linear-gradient(135deg,#FFF5CC,#FFE8A0)',
'card_bg' => 'linear-gradient(145deg,#FFF9EC,#FFFDF7)',
'border_color' => '#FFE4A0',
'blob1' => '#FFD066',
'blob2' => '#FFAD00',
'accent_color' => '#B8860B',
'badge_bg' => 'rgba(255,176,0,0.15)',
'wave' => 'white',
'footer_border' => '#FFE4A0',
'thumb' => 'from-[#FFF9EC] to-[#FFF5DD]',
'thumb_accent' => '#FFD066',
],
2 => [
'id' => 2,
'slug' => 'sakura',
'name' => 'Sakura',
'label' => 'SAKURA CARD',
'icon' => '🌸',
'deco_bg' => 'linear-gradient(135deg,#FFE8F4,#FFD6EC)',
'card_bg' => 'linear-gradient(145deg,#FFF8FB,#FFF3F8)',
'border_color' => '#FFBCD9',
'blob1' => '#F9A8D4',
'blob2' => '#F472B6',
'accent_color' => '#be185d',
'badge_bg' => 'rgba(244,114,182,0.12)',
'wave' => 'white',
'footer_border' => '#FFBCD9',
'thumb' => 'from-[#FFF5F9] to-[#FFECF4]',
'thumb_accent' => '#F9A8D4',
],
3 => [
'id' => 3,
'slug' => 'appreciation',
'name' => 'Appreciation',
'label' => 'APPRECIATION',
'icon' => '⭐',
'deco_bg' => 'linear-gradient(135deg,#DBEAFE,#BFDBFE)',
'card_bg' => 'linear-gradient(145deg,#F0F8FF,#EAF3FF)',
'border_color' => '#BFDBFE',
'blob1' => '#93C5FD',
'blob2' => '#60A5FA',
'accent_color' => '#1d4ed8',
'badge_bg' => 'rgba(96,165,250,0.12)',
'wave' => 'white',
'footer_border' => '#BFDBFE',
'thumb' => 'from-[#EFF8FF] to-[#DBEAFE]',
'thumb_accent' => '#93C5FD',
],
4 => [
'id' => 4,
'slug' => 'celebration',
'name' => 'Celebration',
'label' => 'CELEBRATION',
'icon' => '🎉',
'deco_bg' => 'linear-gradient(135deg,#EDE9FE,#DDD6FE)',
'card_bg' => 'linear-gradient(145deg,#F8F5FF,#F3EFFF)',
'border_color' => '#DDD6FE',
'blob1' => '#C4B5FD',
'blob2' => '#A78BFA',
'accent_color' => '#6d28d9',
'badge_bg' => 'rgba(167,139,250,0.12)',
'wave' => 'white',
'footer_border' => '#DDD6FE',
'thumb' => 'from-[#F5F0FF] to-[#EDE9FE]',
'thumb_accent' => '#C4B5FD',
]
];
+5
View File
@@ -13,6 +13,11 @@ return [
'FIRST_LOGIN_FALSE' => 0, 'FIRST_LOGIN_FALSE' => 0,
'FIRST_LOGIN_TRUE' => 1, 'FIRST_LOGIN_TRUE' => 1,
'TEMPLATE_THU_CAM_ON' => 1,
'TEMPLATE_SAKURA' => 2,
'TEMPLATE_APPRECIATION' => 3,
'TEMPLATE_CELEBRATION' => 4,
'DEPARTMENTS' => [ 'DEPARTMENTS' => [
'BIZ-IID - Internet Infra Business Division / BIZ-IID - Sales Enterprise Team (HCM)', 'BIZ-IID - Internet Infra Business Division / BIZ-IID - Sales Enterprise Team (HCM)',
'BIZ-ITOVN - Vietnam Business Division / BIZ-ITOVN - Ho Chi Minh', 'BIZ-ITOVN - Vietnam Business Division / BIZ-ITOVN - Ho Chi Minh',
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('administration', function (Blueprint $table) {
$table->integer('template_id')->nullable()->default(1)->after('date');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('administration', function (Blueprint $table) {
$table->dropColumn('template_id');
});
}
};
@@ -2,7 +2,7 @@
@php @php
$classes = $active $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'; : '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 @endphp
+5 -3
View File
@@ -29,9 +29,11 @@
Thanks đã gửi Thanks đã gửi
</x-sidebar-item> </x-sidebar-item>
<x-sidebar-item href="{{ route('user.ranking') }}" :active="request()->routeIs('user.ranking')" icon="trophy"> @if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
Bảng xếp hạng <x-sidebar-item href="{{ route('user.ranking') }}" :active="request()->routeIs('user.ranking')" icon="trophy">
</x-sidebar-item> Bảng xếp hạng
</x-sidebar-item>
@endif
@if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN')) @if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
@@ -13,7 +13,10 @@
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-[13px] font-semibold text-gray-500 mb-1.5">{{ $title }}</span> <span class="text-[13px] font-semibold text-gray-500 mb-1.5">{{ $title }}</span>
<div class="flex items-end gap-3 mb-1"> <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> </div>
@if($growth) @if($growth)
@@ -67,12 +67,20 @@
@endif @endif
@endif @endif
</td> </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) @if($isReceiver)
<span class="text-green-600">+{{ $admin_record->received }}</span> <span class="text-green-600">+{{ $admin_record->received }}</span>
@else @else
<span class="text-blue-600">-{{ $admin_record->sent }}</span> <span class="text-blue-600">-{{ $admin_record->sent }}</span>
@endif @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> </td>
</tr> </tr>
@empty @empty
+96 -35
View File
@@ -38,15 +38,27 @@
</x-stat-card> </x-stat-card>
<x-stat-card @if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
title="Thứ hạng" <x-stat-card
value="#{{ $currentRank > 0 ? $currentRank : '-' }}" title="Thứ hạng"
iconBgClass="bg-green-50" value="#{{ $currentRank > 0 ? $currentRank : '-' }}"
iconColorClass="text-green-500"> iconBgClass="bg-green-50"
<svg class="w-[26px] h-[26px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"> iconColorClass="text-green-500">
<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 class="w-[26px] h-[26px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
</svg> <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" />
</x-stat-card> </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>
<div class="flex flex-col gap-6 lg:flex-row lg:items-stretch lg:gap-8"> <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="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"> <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> <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>
<div class="inline-flex bg-[#F5F7FB] rounded-xl p-1 mb-4 w-fit space-x-2"> @if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN'))
<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]"> <div class="inline-flex bg-[#F5F7FB] rounded-xl p-1 mb-4 w-fit space-x-2">
<svg class="w-[18px] h-[18px]" fill="currentColor" viewBox="0 0 24 24"> <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]">
<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"/> <svg class="w-[18px] h-[18px]" fill="currentColor" viewBox="0 0 24 24">
<path fill="#ffffff" d="M12 11.5L10 7h4z"/> <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"/>
</svg> <path fill="#ffffff" d="M12 11.5L10 7h4z"/>
TOP Nhận </svg>
</button> TOP Nhận
<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]"> </button>
<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"> <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]">
<path d="M22 2L11 13"></path> <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 2L15 22L11 13L2 9L22 2Z"></path> <path d="M22 2L11 13"></path>
</svg> <path d="M22 2L15 22L11 13L2 9L22 2Z"></path>
TOP Gửi </svg>
</button> TOP Gửi
</div> </button>
</div>
<div id="ranking-list-container" class="flex-1 flex flex-col mb-2"> <div id="ranking-list-container" class="flex-1 flex flex-col mb-2">
<!-- Pre-rendered ranking lists --> <!-- Pre-rendered ranking lists -->
<div id="ranking-received-content" class="flex-1 flex flex-col"> <div id="ranking-received-content" class="flex-1 flex flex-col">
@include('user.partials.ranking_list', ['rankers' => array_slice($receivedRankers, 0, 4), 'currentUserRankItem' => $receivedCurrentUserRankItem]) @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>
<div id="ranking-sent-content" class="hidden flex-1 flex flex-col"> @else
@include('user.partials.ranking_list', ['rankers' => array_slice($sentRankers, 0, 4), 'currentUserRankItem' => $sentCurrentUserRankItem]) <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>
</div> </div>
</div> </div>
@@ -142,6 +201,8 @@
const receivedContent = document.getElementById('ranking-received-content'); const receivedContent = document.getElementById('ranking-received-content');
const sentContent = document.getElementById('ranking-sent-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 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]'; 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) @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>
<div class="flex items-center justify-between mb-4"> <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-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>
<span class="text-[11px] font-extrabold text-[#6d28d9] bg-[#f5f3ff] px-2 py-0.5 rounded-full"> <span class="text-[11px] font-extrabold px-2.5 py-0.5 rounded-full"
{{ $c->received }} Thẻ style="background-color: {{ $theme['badge_bg'] }}; color: {{ $theme['accent_color'] }};">
{{ $c->received }} Thẻ ({{ $theme['name'] }})
</span> </span>
</div> </div>
<div class="flex items-start gap-3.5"> <div class="flex items-start gap-3.5">
<div class="relative shrink-0"> <div class="relative shrink-0">
@if($c->senderUser) @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 @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)) }} {{ strtoupper(substr($c->sender ?? 'U', 0, 1)) }}
</div> </div>
@endif @endif
@@ -33,9 +44,10 @@
</div> </div>
</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>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>
</div> </div>
@endforeach @endforeach
@@ -1,20 +1,31 @@
@php
$themes = config('card_themes');
@endphp
@foreach ($cards as $c) @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>
<div class="flex items-center justify-between mb-4"> <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-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>
<span class="text-[11px] font-extrabold text-blue-600 bg-blue-50 px-2 py-0.5 rounded-full"> <span class="text-[11px] font-extrabold px-2.5 py-0.5 rounded-full"
{{ $c->sent }} Thẻ style="background-color: {{ $theme['badge_bg'] }}; color: {{ $theme['accent_color'] }};">
{{ $c->sent }} Thẻ ({{ $theme['name'] }})
</span> </span>
</div> </div>
<div class="flex items-start gap-3.5"> <div class="flex items-start gap-3.5">
<div class="relative shrink-0"> <div class="relative shrink-0">
@if($c->receiverUser) @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 @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)) }} {{ strtoupper(substr($c->receiver ?? 'U', 0, 1)) }}
</div> </div>
@endif @endif
@@ -33,9 +44,10 @@
</div> </div>
</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>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>
</div> </div>
@endforeach @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> <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> <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> </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> </div>
<!-- Month Filter --> <!-- Month Filter -->
@@ -147,8 +146,13 @@
{{-- Users in Group --}} {{-- 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"> <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) @php
<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"> $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> <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" /> <x-avatar :user="$user" class="w-12 h-12" />
<div class="flex-1 min-w-0 relative z-10"> <div class="flex-1 min-w-0 relative z-10">
@@ -160,6 +164,30 @@
</div> </div>
</div> </div>
@endforeach @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>
</div> </div>
@endforeach @endforeach
@@ -274,8 +302,13 @@
{{-- Users in Group --}} {{-- 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"> <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) @php
<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"> $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> <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" /> <x-avatar :user="$user" class="w-12 h-12" />
<div class="flex-1 min-w-0 relative z-10"> <div class="flex-1 min-w-0 relative z-10">
@@ -287,6 +320,30 @@
</div> </div>
</div> </div>
@endforeach @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>
</div> </div>
@endforeach @endforeach
@@ -307,6 +364,12 @@
.animate-fade-in { .animate-fade-in {
animation: fadeIn 0.4s ease-out forwards; 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> </style>
@endsection @endsection
@@ -344,5 +407,18 @@
contentReceived.classList.add('hidden'); 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> </script>
@endpush @endpush
+25 -33
View File
@@ -3,12 +3,7 @@
@section('content') @section('content')
@php @php
$tpls = [ $tpls = config('card_themes');
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'],
];
@endphp @endphp
<div class="max-w-[760px] mx-auto w-full pb-12 px-4 lg:px-0"> <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() { document.addEventListener('DOMContentLoaded', function() {
// ── Theme data ─────────────────────────────────────────────────── // ── Theme data ───────────────────────────────────────────────────
const T = { const cardThemesData = @json(config('card_themes'));
1:{icon:'💌',name:'Thư cảm ơn', label:'THANK CARD', const T = {};
deco:'linear-gradient(135deg,#FFF5CC,#FFE8A0)', Object.keys(cardThemesData).forEach(id => {
card:'linear-gradient(145deg,#FFF9EC,#FFFDF7)', const item = cardThemesData[id];
border:'#FFE4A0', blob1:'#FFD066', blob2:'#FFAD00', T[id] = {
accent:'#B8860B', badgeBg:'rgba(255,176,0,0.15)', wave:'white', icon: item.icon,
footerBorder:'#FFE4A0'}, name: item.name,
2:{icon:'🌸',name:'Sakura', label:'SAKURA CARD', label: item.label,
deco:'linear-gradient(135deg,#FFE8F4,#FFD6EC)', deco: item.deco_bg,
card:'linear-gradient(145deg,#FFF8FB,#FFF3F8)', card: item.card_bg,
border:'#FFBCD9', blob1:'#F9A8D4', blob2:'#F472B6', border: item.border_color,
accent:'#be185d', badgeBg:'rgba(244,114,182,0.12)', wave:'white', blob1: item.blob1,
footerBorder:'#FFBCD9'}, blob2: item.blob2,
3:{icon:'⭐',name:'Appreciation',label:'APPRECIATION', accent: item.accent_color,
deco:'linear-gradient(135deg,#DBEAFE,#BFDBFE)', badgeBg: item.badge_bg,
card:'linear-gradient(145deg,#F0F8FF,#EAF3FF)', wave: item.wave,
border:'#BFDBFE', blob1:'#93C5FD', blob2:'#60A5FA', footerBorder: item.footer_border
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'},
};
// ── Theme slugs ────────────────────────────────────────────────── // ── Theme slugs ──────────────────────────────────────────────────
const templateSlugs = { const templateSlugs = {
@@ -541,7 +529,11 @@ document.addEventListener('DOMContentLoaded', function() {
$.ajax({ $.ajax({
url:'{{ route("user.store_send") }}', method:'POST', contentType:'application/json', url:'{{ route("user.store_send") }}', method:'POST', contentType:'application/json',
headers:{'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content')}, 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){ success:function(res){
if(res.success){ if(res.success){
setButtonLoading(false); setButtonLoading(false);