This commit is contained in:
antv
2026-07-17 00:11:23 +07:00
parent ad5465198e
commit c4e652e64c
5 changed files with 129 additions and 30 deletions
+14 -8
View File
@@ -94,7 +94,7 @@
</div>
</div>
<div class="text-right">
<p class="text-3xl font-black text-primary">{{ $receivedCurrentUserRankItem->score }}</p>
<p class="text-xl font-black text-primary">{{ $receivedCurrentUserRankItem->score }}</p>
<p class="text-xs font-medium text-gray-500 uppercase">Thẻ</p>
</div>
</div>
@@ -126,7 +126,7 @@
</div>
</div>
<div class="text-right">
<span class="text-primary font-black text-2xl">{{ $group->first()->score }}</span>
<span class="text-primary font-black text-lg">{{ $group->first()->score }}</span>
<span class="text-sm font-bold text-gray-400">thẻ</span>
</div>
</div>
@@ -138,8 +138,11 @@
<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">
<p class="font-bold text-[#1a2b49] truncate group-hover:text-primary transition-colors">{{ $user->name }}</p>
<p class="text-xs text-gray-500 truncate">{{ $user->department }}</p>
<p class="font-bold text-[#1a2b49] leading-tight line-clamp-2 group-hover:text-primary transition-colors">{{ $user->name }}</p>
<div class="mt-1 flex flex-col gap-0.5">
<p class="text-[11px] font-medium text-gray-500 uppercase tracking-wide">MSNV: {{ $user->msnv ?? '---' }}</p>
<p class="text-[11px] text-gray-400 truncate">{{ $user->department ?? $user->mail ?? '---' }}</p>
</div>
</div>
</div>
@endforeach
@@ -218,7 +221,7 @@
</div>
</div>
<div class="text-right">
<p class="text-3xl font-black text-primary">{{ $sentCurrentUserRankItem->score }}</p>
<p class="text-xl font-black text-primary">{{ $sentCurrentUserRankItem->score }}</p>
<p class="text-xs font-medium text-gray-500 uppercase">Thẻ</p>
</div>
</div>
@@ -250,7 +253,7 @@
</div>
</div>
<div class="text-right">
<span class="text-primary font-black text-2xl">{{ $group->first()->score }}</span>
<span class="text-primary font-black text-lg">{{ $group->first()->score }}</span>
<span class="text-sm font-bold text-gray-400">thẻ</span>
</div>
</div>
@@ -262,8 +265,11 @@
<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">
<p class="font-bold text-[#1a2b49] truncate group-hover:text-primary transition-colors">{{ $user->name }}</p>
<p class="text-xs text-gray-500 truncate">{{ $user->department }}</p>
<p class="font-bold text-[#1a2b49] leading-tight line-clamp-2 group-hover:text-primary transition-colors">{{ $user->name }}</p>
<div class="mt-1 flex flex-col gap-0.5">
<p class="text-[11px] font-medium text-gray-500 uppercase tracking-wide">MSNV: {{ $user->msnv ?? '---' }}</p>
<p class="text-[11px] text-gray-400 truncate">{{ $user->department ?? $user->mail ?? '---' }}</p>
</div>
</div>
</div>
@endforeach
+21 -10
View File
@@ -4,22 +4,33 @@
@section('content')
<div class="max-w-[1280px] mx-auto w-full pb-10 px-2 lg:px-8">
{{-- Hero Section same look as dashboard --}}
<x-hero-banner>
<h2 class="text-[32px] md:text-[42px] font-extrabold text-[#1a2b49] mb-3">💜 Thanks đã nhận</h2>
<p class="text-[16px] text-gray-600 mb-6">Các lời cảm ơn đồng nghiệp đã gửi đến bạn.</p>
</x-hero-banner>
{{-- Summary Card simple total count --}}
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8 mt-6">
<x-stat-card title="Tổng số Thanks" value="{{ count($cards ?? []) }}" iconBgClass="bg-purple-50" iconColorClass="text-purple-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="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
</svg>
</x-stat-card>
<div class="flex flex-col md:flex-row md:items-center gap-6 mb-8 mt-6">
<div class="flex-1 max-w-sm">
<x-stat-card title="Tổng số thanks nhận trong tháng" value="{{ count($cards ?? []) }}" iconBgClass="bg-purple-50" iconColorClass="text-purple-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="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
</svg>
</x-stat-card>
</div>
<form method="GET" class="flex items-center gap-4 bg-white p-3 md:p-4 pr-6 rounded-[24px] shadow-[0_4px_12px_rgba(0,0,0,0.03)] border border-gray-100 transition-all hover:shadow-md">
<div class="w-12 h-12 rounded-full bg-[#f5f7ff] flex items-center justify-center text-primary">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
</div>
<div>
<label for="month" class="block text-xs font-extrabold text-gray-400 uppercase tracking-wider mb-1">Lọc theo tháng</label>
<input type="month" id="month" name="month" class="block w-full border-0 bg-transparent p-0 text-lg font-bold text-[#1a2b49] focus:ring-0 cursor-pointer" value="{{ request('month', \Carbon\Carbon::now()->format('Y-m')) }}" onchange="this.form.submit()" />
</div>
</form>
</div>
{{-- Cards List use simple styled cards --}}
@if (!empty($cards) && $cards->count())
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach ($cards as $c)
@@ -33,7 +44,7 @@
@endforeach
</div>
@else
<x-empty-state title="Chưa có Thanks nào" description="Bạn chưa nhận bất kỳ Thank Card nào trong tháng này." icon="hero"/>
<x-empty-state title="Chưa có Thanks nào" description="Bạn chưa nhận bất kỳ Thank Card nào trong tháng đã chọn." icon="hero"/>
@endif
</div>
@endsection
+69 -2
View File
@@ -137,6 +137,18 @@ $tpls = [
</div>
</div>
<!-- Cards Info Summary -->
<div class="flex flex-col sm:flex-row gap-4 mb-5">
<div id="cardInfoBox1" class="flex-1 bg-white p-4 rounded-2xl border border-[#e8edf4] shadow-[0_4px_12px_rgba(0,0,0,0.02)] flex items-center justify-between transition-all hover:shadow-md">
<span class="text-[12px] font-bold text-gray-500 uppercase">Số thẻ đang </span>
<span class="text-xl font-black transition-colors duration-300" id="currentUserCardsCount" style="color:#3462f7;">{{ Auth::user()->card }}</span>
</div>
<div id="cardInfoBox2" class="flex-1 bg-white p-4 rounded-2xl border border-[#e8edf4] shadow-[0_4px_12px_rgba(0,0,0,0.02)] flex items-center justify-between transition-all hover:shadow-md">
<span class="text-[12px] font-bold text-gray-500 uppercase">Đã gửi cho người hiện tại</span>
<span class="text-xl font-black transition-colors duration-300" id="sentToUserCountContainer" style="color:#3462f7;"><span id="sentToUserCount">0</span> <span class="text-sm font-medium text-gray-400">/ {{ App\Models\Administration::MAX_SEND_CARD_PER_MONTH }}</span></span>
</div>
</div>
<!-- General Error notification block -->
<div id="formGeneralError" class="p-4 bg-red-50 border border-red-100 text-red-800 rounded-2xl text-xs font-semibold leading-relaxed flex items-start gap-2 shadow-sm mb-5 whitespace-pre-line hidden">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5 shrink-0 text-red-600">
@@ -269,6 +281,17 @@ document.addEventListener('DOMContentLoaded', function() {
if ($noteHeader) {
$noteHeader.style.color = t.accent;
}
// sync stats colors
const $stat1 = document.getElementById('currentUserCardsCount');
if ($stat1) $stat1.style.color = t.accent;
const $stat2 = document.getElementById('sentToUserCountContainer');
if ($stat2) $stat2.style.color = t.accent;
const $box1 = document.getElementById('cardInfoBox1');
if ($box1) $box1.style.borderColor = t.accent;
const $box2 = document.getElementById('cardInfoBox2');
if ($box2) $box2.style.borderColor = t.accent;
// store current template for modal sync
currentTpl = id;
}
@@ -299,10 +322,45 @@ document.addEventListener('DOMContentLoaded', function() {
const savedAmount = localStorage.getItem('selectedAmount');
if (savedAmount) document.getElementById('amount').value = savedAmount;
// Listen for changes to persist them
document.getElementById('receiver').addEventListener('change', function(){
localStorage.setItem('selectedReceiver', this.value);
const val = this.value;
localStorage.setItem('selectedReceiver', val);
if (val) {
$.ajax({
url: '{{ route("user.recipient_stats") }}?receiver=' + val,
method: 'GET',
success: function(res) {
if(res.success) {
document.getElementById('sentToUserCount').textContent = res.sent_count;
const maxLimit = {{ App\Models\Administration::MAX_SEND_CARD_PER_MONTH }};
const isLimitReached = res.sent_count >= maxLimit;
document.getElementById('amount').disabled = isLimitReached;
document.getElementById('confirm_written').disabled = isLimitReached;
document.getElementById('btnSubmitSend').disabled = isLimitReached;
if (isLimitReached) {
document.getElementById('btnSubmitSend').classList.add('opacity-50', 'cursor-not-allowed');
} else {
document.getElementById('btnSubmitSend').classList.remove('opacity-50', 'cursor-not-allowed');
}
}
}
});
} else {
document.getElementById('sentToUserCount').textContent = '0';
document.getElementById('amount').disabled = false;
document.getElementById('confirm_written').disabled = false;
document.getElementById('btnSubmitSend').disabled = false;
document.getElementById('btnSubmitSend').classList.remove('opacity-50', 'cursor-not-allowed');
}
});
// Initial fetch if receiver is already selected
if (document.getElementById('receiver').value) {
document.getElementById('receiver').dispatchEvent(new Event('change'));
}
document.getElementById('amount').addEventListener('change', function(){
localStorage.setItem('selectedAmount', this.value);
});
@@ -454,8 +512,10 @@ document.addEventListener('DOMContentLoaded', function() {
// User state for client-side validation
let userCard = {{ (int) auth()->user()->card }};
let userFlagSend = {{ (int) auth()->user()->flag_send }};
let isSubmittingApi = false;
$submitBtn.addEventListener('click', function(){
if (isSubmittingApi) return;
if (!validateForm()) { return; }
const msnv = document.getElementById('receiver').value;
@@ -471,6 +531,9 @@ document.addEventListener('DOMContentLoaded', function() {
showModal('Xác nhận gửi Thank Card',
'Đừng quên hoàn thành Thank Card của bạn nhé!\n Chúng mình sẽ gửi thông báo đến người nhận\nđể họ đến khu vực nhận thư.',
true, () => {
if (isSubmittingApi) return;
isSubmittingApi = true;
window.closeModal('customModal');
setButtonLoading(true);
clearErrors();
@@ -494,6 +557,7 @@ document.addEventListener('DOMContentLoaded', function() {
userCard = 0;
userFlagSend = 0;
}
document.getElementById('currentUserCardsCount').textContent = userCard;
// Clear local storage for inputs (but not template)
localStorage.removeItem('selectedReceiver');
@@ -537,6 +601,9 @@ document.addEventListener('DOMContentLoaded', function() {
const errMsg = res?.message || 'Không thể kết nối đến máy chủ.';
document.getElementById('generalErrorMessage').textContent = errMsg;
document.getElementById('formGeneralError').classList.remove('hidden');
},
complete: function() {
isSubmittingApi = false;
}
});
}
+20 -10
View File
@@ -4,22 +4,32 @@
@section('content')
<div class="max-w-[1280px] mx-auto w-full pb-10 px-2 lg:px-8">
{{-- Hero Section same look as dashboard --}}
<x-hero-banner>
<h2 class="text-[32px] md:text-[42px] font-extrabold text-[#1a2b49] mb-3">📨 Thanks đã gửi</h2>
<p class="text-[16px] text-gray-600 mb-6">Các lời cảm ơn bạn đã gửi tới đồng nghiệp.</p>
</x-hero-banner>
<div class="flex flex-col md:flex-row md:items-center gap-6 mb-8 mt-6">
<div class="flex-1 max-w-sm">
<x-stat-card title="Tổng số thanks gửi trong tháng" value="{{ count($cards ?? []) }}" iconBgClass="bg-blue-50" iconColorClass="text-blue-500">
<svg class="w-[26px] h-[26px] transform -rotate-45" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
</x-stat-card>
</div>
{{-- Summary Card simple total count --}}
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8 mt-6">
<x-stat-card title="Tổng số Thanks" value="{{ count($cards ?? []) }}" iconBgClass="bg-blue-50" iconColorClass="text-blue-500">
<svg class="w-[26px] h-[26px] transform -rotate-45" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
</x-stat-card>
<form method="GET" class="flex items-center gap-4 bg-white p-3 md:p-4 pr-6 rounded-[24px] shadow-[0_4px_12px_rgba(0,0,0,0.03)] border border-gray-100 transition-all hover:shadow-md">
<div class="w-12 h-12 rounded-full bg-[#f5f7ff] flex items-center justify-center text-primary">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
</div>
<div>
<label for="month" class="block text-xs font-extrabold text-gray-400 uppercase tracking-wider mb-1">Lọc theo tháng</label>
<input type="month" id="month" name="month" class="block w-full border-0 bg-transparent p-0 text-lg font-bold text-[#1a2b49] focus:ring-0 cursor-pointer" value="{{ request('month', \Carbon\Carbon::now()->format('Y-m')) }}" onchange="this.form.submit()" />
</div>
</form>
</div>
{{-- Cards List use simple styled cards --}}
@if (!empty($cards) && $cards->count())
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach ($cards as $c)
@@ -33,7 +43,7 @@
@endforeach
</div>
@else
<x-empty-state title="Chưa gửi Thanks" description="Bạn chưa gửi bất kỳ Thank Card nào trong tháng này." icon="hero"/>
<x-empty-state title="Chưa gửi Thanks" description="Bạn chưa gửi bất kỳ Thank Card nào trong tháng đã chọn." icon="hero"/>
@endif
</div>
@endsection