m
This commit is contained in:
@@ -31,22 +31,52 @@ class UserController extends Controller
|
||||
$user = Auth::user();
|
||||
|
||||
$administrations = $this->userService->getDashboard($user, $selectedMonth);
|
||||
$stats = $this->userService->getPersonalStats($user, $selectedMonth);
|
||||
|
||||
$receivedRanking = $this->userService->getRankingList('received', $selectedMonth, $user);
|
||||
$sentRanking = $this->userService->getRankingList('sent', $selectedMonth, $user);
|
||||
$isAdmin = $user->role == config('constants.ROLE_ADMIN');
|
||||
|
||||
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(
|
||||
administrations: $administrations,
|
||||
selectedMonth: $selectedMonth,
|
||||
user: $user,
|
||||
receivedRankers: $receivedRanking['rankers'],
|
||||
receivedCurrentUserRankItem: $receivedRanking['currentUserRankItem'],
|
||||
sentRankers: $sentRanking['rankers'],
|
||||
sentCurrentUserRankItem: $sentRanking['currentUserRankItem'],
|
||||
receivedCount: (int) $stats['receivedCount'],
|
||||
sentCount: (int) $stats['sentCount'],
|
||||
currentRank: (int) $stats['currentRank']
|
||||
receivedRankers: $receivedRankers,
|
||||
receivedCurrentUserRankItem: $receivedCurrentUserRankItem,
|
||||
sentRankers: $sentRankers,
|
||||
sentCurrentUserRankItem: $sentCurrentUserRankItem,
|
||||
receivedCount: $receivedCount,
|
||||
sentCount: $sentCount,
|
||||
currentRank: $currentRank
|
||||
);
|
||||
|
||||
return view('user.dashboard', $dashboardData->toArray());
|
||||
@@ -86,7 +116,8 @@ class UserController extends Controller
|
||||
$this->userService->sendThankcards(
|
||||
Auth::user(),
|
||||
(string) $request->input('receiver'),
|
||||
(int) $request->input('amount')
|
||||
(int) $request->input('amount'),
|
||||
(int) $request->input('template_id', 1)
|
||||
);
|
||||
} catch (ThankCardException $e) {
|
||||
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
|
||||
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'));
|
||||
$user = Auth::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'))
|
||||
],
|
||||
'amount' => 'required|integer|min:1|max:' . Administration::MAX_SEND_CARD_PER_MONTH,
|
||||
'template_id' => 'nullable|integer|in:1,2,3,4',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class Administration extends Model
|
||||
'sent',
|
||||
'receiver',
|
||||
'date',
|
||||
'template_id',
|
||||
];
|
||||
|
||||
public function senderUser()
|
||||
|
||||
@@ -11,7 +11,7 @@ interface UserServiceInterface
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class UserService implements UserServiceInterface
|
||||
->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')) {
|
||||
throw new \App\Exceptions\ThankCardException(__('messages.error.no_send_permission'));
|
||||
@@ -47,7 +47,7 @@ class UserService implements UserServiceInterface
|
||||
$startOfMonth = Carbon::now()->startOfMonth();
|
||||
$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
|
||||
$lockedSender = User::where('id', $sender->id)->lockForUpdate()->first();
|
||||
|
||||
@@ -76,21 +76,23 @@ class UserService implements UserServiceInterface
|
||||
}
|
||||
|
||||
Administration::create([
|
||||
'msnv' => $receiverMsnv,
|
||||
'received' => $amount,
|
||||
'sender' => $lockedSender->msnv,
|
||||
'sent' => 0,
|
||||
'receiver' => null,
|
||||
'date' => Carbon::today(),
|
||||
'msnv' => $receiverMsnv,
|
||||
'received' => $amount,
|
||||
'sender' => $lockedSender->msnv,
|
||||
'sent' => 0,
|
||||
'receiver' => null,
|
||||
'date' => Carbon::today(),
|
||||
'template_id' => $templateId,
|
||||
]);
|
||||
|
||||
Administration::create([
|
||||
'msnv' => $lockedSender->msnv,
|
||||
'received' => 0,
|
||||
'sender' => null,
|
||||
'sent' => $amount,
|
||||
'receiver' => $receiverMsnv,
|
||||
'date' => Carbon::today(),
|
||||
'msnv' => $lockedSender->msnv,
|
||||
'received' => 0,
|
||||
'sender' => null,
|
||||
'sent' => $amount,
|
||||
'receiver' => $receiverMsnv,
|
||||
'date' => Carbon::today(),
|
||||
'template_id' => $templateId,
|
||||
]);
|
||||
|
||||
$lockedSender->card -= $amount;
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
];
|
||||
@@ -13,6 +13,11 @@ return [
|
||||
'FIRST_LOGIN_FALSE' => 0,
|
||||
'FIRST_LOGIN_TRUE' => 1,
|
||||
|
||||
'TEMPLATE_THU_CAM_ON' => 1,
|
||||
'TEMPLATE_SAKURA' => 2,
|
||||
'TEMPLATE_APPRECIATION' => 3,
|
||||
'TEMPLATE_CELEBRATION' => 4,
|
||||
|
||||
'DEPARTMENTS' => [
|
||||
'BIZ-IID - Internet Infra Business Division / BIZ-IID - Sales Enterprise Team (HCM)',
|
||||
'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
|
||||
$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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 có 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
|
||||
|
||||
@@ -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 cá nhân lan tỏa văn hóa cảm ơn và 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user