feature: send thankscard page
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
@import "./fonts.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
@section('title', $mode === 'create' ? 'Thêm User Mới' : 'Chi tiết User')
|
||||
|
||||
@section('content')
|
||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 pl-1">
|
||||
<div>
|
||||
<h1 class="text-[28px] font-extrabold text-[#1a2b49] flex items-center gap-2 mb-1.5 tracking-tight">
|
||||
{{ $mode === 'create' ? 'Thêm User Mới' : 'Chi tiết User' }}
|
||||
</h1>
|
||||
<p class="text-gray-500 font-medium text-[14px]">
|
||||
{{ $mode === 'create' ? 'Tạo tài khoản nhân viên mới và thiết lập các thông tin cơ bản.' : 'Quản lý thông tin thẻ và theo dõi lịch sử nhận gửi của nhân viên.' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<x-user-page :mode="$mode" :user="$user ?? null" :administrations="$administrations ?? null" :selectedMonth="$selectedMonth ?? null" :addCards="$addCards ?? null" :admins="$admins ?? null" />
|
||||
@endsection
|
||||
|
||||
@@ -38,13 +38,7 @@
|
||||
</td>
|
||||
<td class="table-body-cell">
|
||||
@php
|
||||
$deptVal = $u->departments;
|
||||
if (is_numeric($deptVal)) {
|
||||
$idx = ((int)$deptVal) - 1;
|
||||
$deptName = config('constants.DEPARTMENTS')[$idx] ?? 'Team';
|
||||
} else {
|
||||
$deptName = $deptVal ?? 'Team';
|
||||
}
|
||||
$deptName = $u->department_name ?? 'Team';
|
||||
@endphp
|
||||
<div class="max-w-[200px] truncate font-medium text-gray-600" title="{{ $deptName }}">
|
||||
{{ $deptName }}
|
||||
|
||||
@@ -23,13 +23,7 @@
|
||||
<div class="w-10 h-10 rounded-full overflow-hidden shrink-0 border border-gray-200">
|
||||
@php
|
||||
$avatarUrl = Auth::user()->avatar ? Auth::user()->avatar : asset('images/avatar.png');
|
||||
$deptVal = Auth::user()->departments;
|
||||
if (is_numeric($deptVal)) {
|
||||
$idx = ((int)$deptVal) - 1;
|
||||
$deptName = config('constants.DEPARTMENTS')[$idx] ?? 'Team';
|
||||
} else {
|
||||
$deptName = $deptVal ?? 'Team';
|
||||
}
|
||||
$deptName = Auth::user()->department_name ?? 'Team';
|
||||
@endphp
|
||||
<img src="{{ $avatarUrl }}" alt="Avatar" class="w-full h-full object-cover" onerror="this.onerror=null; this.src='{{ asset('images/avatar.png') }}';">
|
||||
</div>
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
<!-- Department -->
|
||||
<div class="relative">
|
||||
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="departments">Phòng ban <span class="text-red-500">*</span></label>
|
||||
@php
|
||||
$deptVal = old('departments', $user?->department_name);
|
||||
@endphp
|
||||
@if($mode === 'self')
|
||||
<div class="relative flex items-center w-full">
|
||||
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
|
||||
@@ -72,8 +75,8 @@
|
||||
class="form-input form-input-with-icon !rounded-xl !bg-gray-50 !text-gray-500 cursor-not-allowed w-full"
|
||||
style="padding-left: 48px;"
|
||||
readonly
|
||||
title="{{ $user?->departments }}"
|
||||
value="{{ $user?->departments }}">
|
||||
title="{{ $deptVal }}"
|
||||
value="{{ $deptVal }}">
|
||||
</div>
|
||||
@else
|
||||
<div class="relative flex items-center w-full">
|
||||
@@ -85,7 +88,7 @@
|
||||
<select name="departments" id="departments" class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20 w-full" style="padding-left: 48px;" required>
|
||||
<option value="">-- Chọn phòng ban --</option>
|
||||
@foreach(config('constants.DEPARTMENTS') as $dept)
|
||||
<option value="{{ $dept }}" {{ old('departments', $user?->departments) == $dept ? 'selected' : '' }}>{{ $dept }}</option>
|
||||
<option value="{{ $dept }}" {{ $deptVal == $dept ? 'selected' : '' }}>{{ $dept }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<form id="filterForm" action="{{ $action }}" method="GET" class="flex flex-col md:flex-row md:flex-wrap items-stretch md:items-center gap-3.5 w-full mb-6 bg-white p-5 rounded-[24px] border border-white shadow-[0_10px_30px_rgba(15,23,42,0.05)]">
|
||||
<div class="search-wrapper flex-1 relative flex items-center w-full">
|
||||
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center z-10">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
@@ -19,7 +19,7 @@
|
||||
class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20 w-full"
|
||||
style="padding-left: 44px; padding-right: 40px;" />
|
||||
<button type="button" id="btnClearSearch" class="{{ empty($search) ? 'hidden' : '' }} absolute right-3.5 text-gray-400 hover:text-gray-600 focus:outline-none">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -84,7 +84,7 @@
|
||||
field: "text",
|
||||
direction: "asc"
|
||||
},
|
||||
placeholder: "🔍 Tìm phòng ban...",
|
||||
placeholder: "Tìm phòng ban...",
|
||||
render: {
|
||||
item: function(data, escape) {
|
||||
return '<div class="item" title="' + escape(data.text) + '">' + escape(data.text) + '</div>';
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
<div class="bg-gradient-to-r from-blue-50/70 via-indigo-50/50 to-purple-50/30 px-6 py-6 sm:px-8 border-b border-gray-100">
|
||||
<h3 class="text-[17px] font-extrabold text-[#1a2b49] leading-tight flex items-center gap-2">
|
||||
<span>{{ match($mode) {
|
||||
'create' => '👥 Thêm User Mới',
|
||||
'delete' => '⚠️ Xác nhận nghỉ việc: ' . ($user?->msnv),
|
||||
'self' => '👤 Thông tin cá nhân: ' . ($user?->msnv),
|
||||
'create' => 'Thêm User Mới',
|
||||
'delete' => 'Xác nhận nghỉ việc: ' . ($user?->name),
|
||||
'self' => 'Thông tin cá nhân',
|
||||
} }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -40,23 +40,20 @@
|
||||
<div class="w-12 h-12 rounded-full bg-blue-50 text-[#3462f7] flex items-center justify-center font-extrabold text-lg shadow-inner flex-shrink-0">
|
||||
{{ strtoupper(substr($user->name ?? $user->msnv, 0, 2)) }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h4 class="font-extrabold text-[#1a2b49] text-base truncate">{{ $user->name }}</h4>
|
||||
<p class="text-xs text-gray-500 font-medium truncate mb-1">{{ $user->mail }}</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 text-[10px] font-bold rounded-full bg-blue-50 text-blue-700 border border-blue-100">
|
||||
{{ $user->role == config('constants.ROLE_ADMIN') ? 'Admin' : 'Member' }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-300 font-semibold">•</span>
|
||||
<span class="text-xs font-bold text-gray-600">Thẻ: <span class="text-orange-500 font-extrabold text-sm">{{ $user->card }}</span></span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="text-base font-extrabold text-[#1a2b49]">{{ $user->name }}</h4>
|
||||
<p class="text-xs text-gray-500 font-semibold mt-1 flex items-center gap-2">
|
||||
<span>MSNV: {{ $user->msnv }}</span>
|
||||
<span class="w-1 h-1 bg-gray-300 rounded-full"></span>
|
||||
<span>{{ $user->mail }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($actionUrl)
|
||||
<form action="{{ $actionUrl }}" method="POST" enctype="multipart/form-data" class="flex flex-col flex-1 justify-between">
|
||||
<form action="{{ $actionUrl }}" method="POST" id="userForm" enctype="multipart/form-data" class="flex-1 flex flex-col justify-between">
|
||||
@csrf
|
||||
@if($method !== 'POST')
|
||||
@method($method)
|
||||
@@ -66,7 +63,7 @@
|
||||
<div class="p-6 sm:p-8 {{ !$isCreate && !$isEdit && !$isSelf ? 'pt-0' : '' }} space-y-5">
|
||||
@if($isDelete && $user)
|
||||
<p class="text-sm text-red-600 leading-relaxed font-semibold">
|
||||
Bạn có chắc chắn muốn cho nhân viên {{ $user->msnv }} nghỉ việc không? Tài khoản này sẽ không thể đăng nhập hoặc thực hiện các giao dịch thẻ sau khi xác nhận.
|
||||
Bạn có chắc chắn muốn cho nhân viên {{ $user->name }} (MSNV: {{ $user->msnv }}) nghỉ việc không? Tài khoản này sẽ không thể đăng nhập hoặc thực hiện các giao dịch thẻ sau khi xác nhận.
|
||||
</p>
|
||||
@else
|
||||
<!-- 2. User Basic Information Section -->
|
||||
|
||||
@@ -344,11 +344,15 @@
|
||||
|
||||
await reloadListsSection();
|
||||
} else {
|
||||
alert(result.message || 'Đã xảy ra lỗi. Vui lòng thử lại.');
|
||||
if (window.showToast) {
|
||||
window.showToast(result.message || 'Đã xảy ra lỗi. Vui lòng thử lại.', 'error');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('Đã xảy ra lỗi kết nối.');
|
||||
if (window.showToast) {
|
||||
window.showToast('Đã xảy ra lỗi kết nối.', 'error');
|
||||
}
|
||||
} finally {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z" />
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" name="password" id="password" class="form-input form-input-with-icon !rounded-xl !bg-gray-50 !text-gray-500 cursor-not-allowed" style="padding-left: 48px;" readonly required value="1234@Dcba">
|
||||
<input type="text" name="password" id="password" class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20" style="padding-left: 48px;" required value="{{ old('password', '1234@Dcba') }}">
|
||||
</div>
|
||||
<span class="text-[13px] text-gray-400 mt-2 block">Mật khẩu mặc định cho tất cả user mới tạo.</span>
|
||||
@error('password')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
|
||||
<span class="text-[13px] text-gray-400 mt-2 block">Thiết lập mật khẩu khởi tạo cho user (mặc định là 1234@Dcba).</span>
|
||||
</div>
|
||||
@elseif($mode === 'edit')
|
||||
<div class="relative">
|
||||
@@ -28,7 +29,7 @@
|
||||
@elseif($mode === 'self')
|
||||
<div class="border-t border-gray-100 pt-5 mt-5">
|
||||
<h4 class="text-sm font-bold text-[#1a2b49] mb-4 flex items-center gap-2">
|
||||
🔑 Bảo mật & Đổi mật khẩu
|
||||
Bảo mật & Đổi mật khẩu
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
<!-- Current Password -->
|
||||
|
||||
@@ -141,14 +141,47 @@
|
||||
closeBtn.addEventListener('click', closeSidebar);
|
||||
overlay.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// Client-side flash message support
|
||||
const flashSuccess = localStorage.getItem('flash_success');
|
||||
if (flashSuccess) {
|
||||
window.showToast(flashSuccess, 'success');
|
||||
localStorage.removeItem('flash_success');
|
||||
}
|
||||
const flashError = localStorage.getItem('flash_error');
|
||||
if (flashError) {
|
||||
window.showToast(flashError, 'error');
|
||||
localStorage.removeItem('flash_error');
|
||||
}
|
||||
});
|
||||
|
||||
window.showToast = function(message) {
|
||||
window.showToast = function(message, type = 'success') {
|
||||
const toast = document.getElementById('global-toast');
|
||||
if (!toast) return;
|
||||
|
||||
const msgEl = toast.querySelector('.text-sm');
|
||||
if (msgEl) msgEl.textContent = message;
|
||||
|
||||
// Dynamically set theme styling and icon
|
||||
const iconContainer = toast.querySelector('.shrink-0');
|
||||
if (iconContainer) {
|
||||
iconContainer.className = 'inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-lg';
|
||||
let iconHtml = '';
|
||||
if (type === 'success') {
|
||||
iconContainer.classList.add('bg-emerald-50', 'text-emerald-500');
|
||||
iconHtml = '<svg class="w-5.5 h-5.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>';
|
||||
} else if (type === 'error') {
|
||||
iconContainer.classList.add('bg-red-50', 'text-red-500');
|
||||
iconHtml = '<svg class="w-5.5 h-5.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>';
|
||||
} else if (type === 'warning') {
|
||||
iconContainer.classList.add('bg-amber-50', 'text-amber-500');
|
||||
iconHtml = '<svg class="w-5.5 h-5.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>';
|
||||
} else {
|
||||
iconContainer.classList.add('bg-blue-50', 'text-blue-500');
|
||||
iconHtml = '<svg class="w-5.5 h-5.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';
|
||||
}
|
||||
iconContainer.innerHTML = iconHtml;
|
||||
}
|
||||
|
||||
toast.classList.remove('hidden');
|
||||
toast.classList.add('flex');
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>GMO-Z.com RUNSYSTEM - {{ __('auth.login') }}</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@700&display=swap" rel="stylesheet">
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="min-h-screen flex items-center justify-center font-sans antialiased overflow-hidden relative bg-[#f0f4f9] p-4 sm:p-6 lg:p-10">
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
<div class="flex flex-col">
|
||||
@foreach($rankers as $item)
|
||||
@php
|
||||
$deptVal = $item->departments;
|
||||
if (is_numeric($deptVal)) {
|
||||
$idx = ((int)$deptVal) - 1;
|
||||
$teamName = config('constants.DEPARTMENTS')[$idx] ?? 'Team';
|
||||
} else {
|
||||
$teamName = $deptVal ?? 'Team';
|
||||
}
|
||||
$teamName = $item->department_name ?? 'Team';
|
||||
@endphp
|
||||
<x-ranking-item
|
||||
rank="{{ $item->rank }}"
|
||||
@@ -33,13 +27,7 @@
|
||||
<div class="mt-auto">
|
||||
<div class="my-3 border-t border-dashed border-gray-100 mx-2"></div>
|
||||
@php
|
||||
$currDeptVal = $currentUserRankItem->departments;
|
||||
if (is_numeric($currDeptVal)) {
|
||||
$idx = ((int)$currDeptVal) - 1;
|
||||
$currTeamName = config('constants.DEPARTMENTS')[$idx] ?? 'Team';
|
||||
} else {
|
||||
$currTeamName = $currDeptVal ?? 'Team';
|
||||
}
|
||||
$currTeamName = $currentUserRankItem->department_name ?? 'Team';
|
||||
@endphp
|
||||
<x-ranking-item
|
||||
rank="{{ $currentUserRankItem->rank > 0 ? $currentUserRankItem->rank : '-' }}"
|
||||
|
||||
+296
-168
@@ -2,189 +2,317 @@
|
||||
@section('title', 'Gửi Thank Card')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-[640px] mx-auto w-full pb-10">
|
||||
<div class="bg-white rounded-[24px] shadow-[0_10px_30px_rgba(15,23,42,0.05)] border border-white overflow-hidden">
|
||||
|
||||
<div class="bg-gradient-to-r from-[#eff4ff] to-[#faeffa] px-8 py-8 flex flex-col relative overflow-hidden">
|
||||
<div class="absolute -top-10 -right-10 w-32 h-32 bg-white/30 rounded-full blur-xl"></div>
|
||||
<div class="absolute -bottom-10 -left-10 w-24 h-24 bg-white/20 rounded-full blur-lg"></div>
|
||||
|
||||
<div class="relative z-10 flex items-center gap-3">
|
||||
<h3 class="text-[22px] font-extrabold text-[#1a2b49] tracking-tight">Gửi Thank Card</h3>
|
||||
<span class="text-xl animate-pulse">💌</span>
|
||||
</div>
|
||||
<p class="text-[13px] text-gray-500 font-medium mt-1.5 relative z-10">
|
||||
Gửi gắm lời cảm ơn chân thành đến người đồng nghiệp tuyệt vời của bạn!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form id="sendCardForm">
|
||||
@csrf
|
||||
|
||||
<div class="p-8 space-y-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-[14px] font-bold text-text-dark" for="receiver">
|
||||
Chọn người nhận <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="receiver" id="receiver" class="form-input rounded-xl border-[#d9dfe7]" required>
|
||||
<option value="" disabled selected>-- Chọn đồng nghiệp --</option>
|
||||
@foreach($users as $u)
|
||||
<option value="{{ $u->msnv }}">{{ $u->name }} - {{ $u->msnv }} ({{ $u->mail }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@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'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-[14px] font-bold text-text-dark" for="amount">
|
||||
Số lượng thẻ <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select name="amount" id="amount" class="form-input rounded-xl border-[#d9dfe7]" required>
|
||||
<option value="1">1 Thẻ</option>
|
||||
<option value="2">2 Thẻ</option>
|
||||
<option value="3">3 Thẻ</option>
|
||||
<option value="4">4 Thẻ</option>
|
||||
<option value="{{ \App\Models\Administration::MAX_SEND_CARD_PER_MONTH }}">{{ \App\Models\Administration::MAX_SEND_CARD_PER_MONTH }} Thẻ (Tối đa)</option>
|
||||
</select>
|
||||
<div class="mt-1 flex items-start gap-2 bg-[#eff4ff] p-3.5 rounded-xl border border-blue-50">
|
||||
<svg class="w-5 h-5 text-[#3462f7] shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<p class="text-[12px] text-gray-600 font-medium leading-relaxed">
|
||||
Lưu ý: Chỉ được gửi tối đa {{ \App\Models\Administration::MAX_SEND_CARD_PER_MONTH }} thẻ cho cùng 1 người trong 1 tháng để đảm bảo tính công bằng.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-w-[760px] mx-auto w-full pb-12 px-4 lg:px-0">
|
||||
|
||||
<div class="flex flex-col gap-2 pt-2">
|
||||
<div class="flex items-center gap-3 bg-gray-50/50 p-4 rounded-xl border border-[#d9dfe7]/60 hover:bg-gray-50 transition-colors">
|
||||
<input type="checkbox" name="confirm_written" id="confirm_written" value="1" class="w-5 h-5 text-[#3462f7] border-[#d9dfe7] rounded-md focus:ring-[#3462f7]/20 transition-all cursor-pointer" required>
|
||||
<label for="confirm_written" class="text-[13px] font-medium text-gray-700 cursor-pointer select-none">
|
||||
Xác nhận đã viết card <span class="text-red-500">*</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-8 py-5 border-t border-[#d9dfe7]/50 bg-gray-50/50 flex flex-col sm:flex-row justify-end gap-3">
|
||||
<a href="{{ route('user.dashboard') }}" class="btn-secondary rounded-xl font-bold">Hủy bỏ</a>
|
||||
<button type="button" id="btnSubmitSend" class="btn-primary rounded-xl font-bold !bg-[#3462f7] hover:!bg-blue-700 shadow-md shadow-blue-500/10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="w-4 h-4 mr-2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" />
|
||||
</svg>
|
||||
Gửi Card
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{-- Header --}}
|
||||
<div class="relative bg-gradient-to-r from-[#eff4ff] to-[#faeffa] rounded-[24px] p-7 md:p-10 mb-7 overflow-hidden border border-white shadow-[0_10px_30px_rgba(15,23,42,0.05)]">
|
||||
<div class="absolute -top-10 -right-10 w-44 h-44 bg-white/25 rounded-full blur-2xl"></div>
|
||||
<div class="absolute -bottom-8 -left-8 w-32 h-32 bg-white/20 rounded-full blur-xl"></div>
|
||||
<div class="relative z-10 flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl bg-white/80 shadow-sm flex items-center justify-center text-3xl shrink-0">💌</div>
|
||||
<div>
|
||||
<h1 class="text-[26px] md:text-[30px] font-extrabold text-[#1a2b49] tracking-tight leading-tight">Gửi Thank Card</h1>
|
||||
<p class="text-[14px] text-gray-500 font-medium mt-1 max-w-[380px] leading-relaxed">Gửi lời cảm ơn chân thành đến đồng nghiệp và lan tỏa văn hóa ghi nhận trong tổ chức.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="sendCardForm">
|
||||
@csrf
|
||||
<input type="hidden" name="template_id" id="template_id" value="1">
|
||||
|
||||
{{-- Template Gallery --}}
|
||||
<div class="bg-white rounded-[20px] shadow-[0_4px_20px_rgba(15,23,42,0.06)] border border-[#f0f4fa] p-6 mb-5">
|
||||
<p class="text-[13px] font-bold text-gray-400 uppercase tracking-widest mb-4">Chọn mẫu thiệp <span class="text-[#3462f7]">*</span></p>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
@foreach($tpls as $id => $t)
|
||||
<button type="button" onclick="selectTpl(this,{{$id}})"
|
||||
data-tpl="{{$id}}"
|
||||
class="tpl-btn relative group flex flex-col items-center gap-2 p-4 rounded-2xl border-2 transition-all duration-200 bg-gradient-to-br {{$t['thumb']}} cursor-pointer
|
||||
{{$loop->first ? 'border-[#3462f7] ring-2 ring-[#3462f7]/20 shadow-md' : 'border-[#e8edf4] hover:border-[#3462f7]/40 hover:shadow-md'}}">
|
||||
<span class="tpl-check absolute top-2 right-2 w-5 h-5 rounded-full bg-[#3462f7] flex items-center justify-center {{$loop->first ? 'opacity-100':'opacity-0'}} transition-opacity duration-200">
|
||||
<svg class="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||
</span>
|
||||
<span class="text-3xl group-hover:scale-110 transition-transform duration-200 select-none">{{$t['icon']}}</span>
|
||||
<span class="text-[12px] font-bold text-[#1a2b49] text-center leading-tight">{{$t['name']}}</span>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- e-Card (form inside the card) --}}
|
||||
<div id="eCard" class="relative rounded-[24px] overflow-hidden shadow-[0_20px_60px_rgba(15,23,42,0.12)] border-2 mb-5 transition-all duration-500"
|
||||
style="background:linear-gradient(145deg,#FFF9EC,#FFFDF7);border-color:#FFE4A0;">
|
||||
|
||||
{{-- Decorative SVG top --}}
|
||||
<div id="cardDeco" class="relative overflow-hidden" style="height:140px;background:linear-gradient(135deg,#FFF5CC,#FFE8A0);">
|
||||
{{-- blob 1 --}}
|
||||
<div class="absolute -top-8 -left-8 w-36 h-36 rounded-full opacity-40 blur-2xl" id="blob1" style="background:#FFD066;"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-28 h-28 rounded-full opacity-30 blur-xl" id="blob2" style="background:#FFAD00;"></div>
|
||||
{{-- SVG decoration --}}
|
||||
<div id="decoSvg" class="absolute inset-0 flex items-center justify-center select-none pointer-events-none">
|
||||
{{-- Template 1 default: envelope --}}
|
||||
<div class="flex flex-col items-center gap-1 z-10">
|
||||
<span id="cardBigIcon" class="text-[72px] leading-none drop-shadow-lg" style="filter:drop-shadow(0 4px 16px rgba(0,0,0,0.10))">💌</span>
|
||||
<span id="cardLabel" class="text-[13px] font-bold uppercase tracking-[0.2em] mt-1" style="color:#B8860B;">Thank Card</span>
|
||||
</div>
|
||||
</div>
|
||||
{{-- wavy bottom --}}
|
||||
<svg class="absolute bottom-0 left-0 w-full" viewBox="0 0 760 30" preserveAspectRatio="none" style="height:30px;">
|
||||
<path id="wavePath" d="M0,20 Q190,0 380,20 Q570,40 760,20 L760,30 L0,30 Z" fill="white" opacity="0.8"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{{-- Card body: form fields ON the card --}}
|
||||
<div class="px-8 py-7 bg-white/0">
|
||||
|
||||
{{-- To: Người nhận --}}
|
||||
<div class="mb-6">
|
||||
<label class="block text-[11px] font-bold uppercase tracking-widest mb-2" id="labelTo" style="color:#B8860B;">✉ Gửi đến</label>
|
||||
<select name="receiver" id="receiver" class="form-input rounded-xl border-[#e8edf4] bg-white/90" required>
|
||||
<option value="" disabled selected></option>
|
||||
@foreach($users as $u)
|
||||
@php $dept=$u->department_name??'Team'; @endphp
|
||||
<option value="{{$u->msnv}}">{{$u->name}} - {{$u->msnv}} ({{$u->mail}}) - {{$dept}}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Số lượng --}}
|
||||
<div class="mb-6">
|
||||
<label class="block text-[11px] font-bold uppercase tracking-widest mb-2" id="labelQty" style="color:#B8860B;">🎴 Số lượng thẻ</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<select name="amount" id="amount" class="form-input rounded-xl border-[#e8edf4] bg-white/90 !w-auto !px-5" required>
|
||||
<option value="1">1 Thẻ</option>
|
||||
<option value="2">2 Thẻ</option>
|
||||
<option value="3">3 Thẻ</option>
|
||||
<option value="4">4 Thẻ</option>
|
||||
<option value="{{App\Models\Administration::MAX_SEND_CARD_PER_MONTH}}">{{App\Models\Administration::MAX_SEND_CARD_PER_MONTH}} Thẻ (Tối đa)</option>
|
||||
</select>
|
||||
<span class="text-[12px] text-gray-400 font-medium">/ tháng</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Confirm checkbox --}}
|
||||
<label class="flex items-start gap-3 p-4 rounded-2xl border border-[#e8edf4] bg-white/70 hover:bg-white/90 transition-colors cursor-pointer mb-2">
|
||||
<input type="checkbox" name="confirm_written" id="confirm_written" value="1"
|
||||
class="w-4 h-4 mt-0.5 text-[#3462f7] border-[#d9dfe7] rounded focus:ring-[#3462f7]/20 cursor-pointer shrink-0" required>
|
||||
<span class="text-[13px] font-medium text-gray-600 leading-relaxed select-none">
|
||||
Tôi xác nhận đã viết nội dung trên ThankCard và sẵn sàng gửi đến người nhận. <span class="text-[#3462f7] font-bold">*</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{{-- Card footer line --}}
|
||||
<div class="mt-6 pt-4 border-t border-dashed" id="cardFooterLine" style="border-color:#FFE4A0;">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[11px] text-gray-400 font-medium">Từ: <span class="font-bold text-gray-500">{{ Auth::user()->name ?? 'Bạn' }}</span></span>
|
||||
<span id="cardFooterBadge" class="text-[10px] font-bold px-2.5 py-1 rounded-full" style="background:rgba(255,176,0,0.15);color:#B8860B;">Thư cảm ơn</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Info note --}}
|
||||
<div class="flex items-start gap-3 bg-gradient-to-r from-[#eff4ff] to-[#f5f0ff] p-4 rounded-2xl border border-[#dce7ff] mb-5">
|
||||
<div class="w-8 h-8 rounded-xl bg-white flex items-center justify-center shrink-0 shadow-sm">
|
||||
<svg class="w-4 h-4 text-[#3462f7]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-[12px] font-bold text-[#3462f7] mb-0.5">Lưu ý</p>
|
||||
<p class="text-[13px] text-gray-600 font-medium leading-relaxed">Chỉ được gửi tối đa <strong>{{ App\Models\Administration::MAX_SEND_CARD_PER_MONTH }} thẻ</strong> cho cùng một người trong tháng để đảm bảo tính công bằng.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Actions --}}
|
||||
<div class="flex flex-col sm:flex-row justify-end gap-3">
|
||||
<a href="{{ route('user.dashboard') }}" class="btn-secondary rounded-xl font-bold flex items-center justify-center gap-2 order-2 sm:order-1">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>Hủy bỏ
|
||||
</a>
|
||||
<button type="button" id="btnSubmitSend" class="btn-primary rounded-xl font-bold !bg-[#3462f7] hover:!bg-blue-700 shadow-md shadow-blue-500/15 flex items-center justify-center gap-2 order-1 sm:order-2">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"/></svg>Gửi Card
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<x-modal id="customModal" title="Thông báo" maxWidth="md">
|
||||
<p id="modalMessage" class="text-sm text-gray-600 leading-relaxed"></p>
|
||||
|
||||
<x-slot name="footer">
|
||||
<div id="modalActions" class="w-full flex flex-col sm:flex-row-reverse gap-3">
|
||||
</div>
|
||||
</x-slot>
|
||||
<p id="modalMessage" class="text-sm text-gray-600 leading-relaxed"></p>
|
||||
<x-slot name="footer">
|
||||
<div id="modalActions" class="w-full flex flex-col sm:flex-row-reverse gap-3"></div>
|
||||
</x-slot>
|
||||
</x-modal>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="{{ asset('js/jquery.min.js') }}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
new TomSelect("#receiver", {
|
||||
create: false,
|
||||
sortField: {
|
||||
field: "text",
|
||||
direction: "asc"
|
||||
},
|
||||
placeholder: "🔍 Nhập tên, mã NV hoặc email...",
|
||||
render: {
|
||||
no_results: function(data, escape) {
|
||||
return '<div class="no-results p-3 text-gray-500">Không tìm thấy nhân viên nào</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
const btnSubmit = document.getElementById('btnSubmitSend');
|
||||
const form = document.getElementById('sendCardForm');
|
||||
|
||||
const modalTitle = document.getElementById('modal-title-customModal');
|
||||
const modalMessage = document.getElementById('modalMessage');
|
||||
const modalActions = document.getElementById('modalActions');
|
||||
// ── 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 showModal = (title, message, isConfirm, confirmCallback) => {
|
||||
modalTitle.textContent = title;
|
||||
modalMessage.innerHTML = message;
|
||||
|
||||
if(isConfirm) {
|
||||
modalTitle.className = "text-lg font-bold text-primary";
|
||||
modalActions.innerHTML = `
|
||||
<button id="btnConfirmOk" class="w-full sm:w-auto btn-primary !px-6 shadow-sm rounded-xl font-bold">Xác nhận gửi</button>
|
||||
<button id="btnCancel" class="w-full sm:w-auto btn-secondary !px-6 shadow-sm rounded-xl font-bold">Hủy</button>
|
||||
`;
|
||||
document.getElementById('btnCancel').addEventListener('click', () => window.closeModal('customModal'));
|
||||
document.getElementById('btnConfirmOk').addEventListener('click', function() {
|
||||
this.disabled = true;
|
||||
this.innerHTML = '<span class="flex items-center gap-2"><svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Đang xử lý...</span>';
|
||||
confirmCallback();
|
||||
});
|
||||
} else {
|
||||
modalTitle.className = "text-lg font-bold text-red-600";
|
||||
modalActions.innerHTML = `
|
||||
<button id="btnOkOnly" class="w-full sm:w-auto btn-secondary !px-6 shadow-sm rounded-xl font-bold">Đóng</button>
|
||||
`;
|
||||
document.getElementById('btnOkOnly').addEventListener('click', () => window.closeModal('customModal'));
|
||||
}
|
||||
|
||||
window.openModal('customModal');
|
||||
};
|
||||
function applyTheme(id) {
|
||||
const t = T[id]; if (!t) return;
|
||||
const $card = document.getElementById('eCard');
|
||||
const $deco = document.getElementById('cardDeco');
|
||||
const $b1 = document.getElementById('blob1');
|
||||
const $b2 = document.getElementById('blob2');
|
||||
const $bigIcon = document.getElementById('cardBigIcon');
|
||||
const $label = document.getElementById('cardLabel');
|
||||
const $wave = document.getElementById('wavePath');
|
||||
const $lTo = document.getElementById('labelTo');
|
||||
const $lQty = document.getElementById('labelQty');
|
||||
const $badge = document.getElementById('cardFooterBadge');
|
||||
const $footerL = document.getElementById('cardFooterLine');
|
||||
|
||||
btnSubmit.addEventListener('click', function() {
|
||||
if(!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
$card.style.background = t.card;
|
||||
$card.style.borderColor = t.border;
|
||||
$deco.style.background = t.deco;
|
||||
$b1.style.background = t.blob1;
|
||||
$b2.style.background = t.blob2;
|
||||
$bigIcon.textContent = t.icon;
|
||||
$label.textContent = t.label;
|
||||
$label.style.color = t.accent;
|
||||
$wave.setAttribute('fill', t.wave);
|
||||
$lTo.style.color = t.accent;
|
||||
$lQty.style.color = t.accent;
|
||||
$badge.textContent = t.name;
|
||||
$badge.style.background = t.badgeBg;
|
||||
$badge.style.color = t.accent;
|
||||
$footerL.style.borderColor = t.footerBorder;
|
||||
}
|
||||
|
||||
const receiver = document.getElementById('receiver').value;
|
||||
const amount = document.getElementById('amount').value;
|
||||
|
||||
showModal(
|
||||
'Xác nhận gửi Thank Card',
|
||||
`Bạn có chắc chắn muốn gửi <b>${amount} thẻ</b> cho nhân viên <span class="font-bold text-primary">${receiver}</span> không? <br><br><span class="text-xs text-text-light">Hành động này không thể hoàn tác, tin nhắn CO sẽ được gửi đi!</span>`,
|
||||
true,
|
||||
() => {
|
||||
fetch('{{ route('user.store_send') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||
},
|
||||
body: JSON.stringify({
|
||||
receiver: receiver,
|
||||
amount: amount
|
||||
})
|
||||
})
|
||||
.then(response => response.json().then(data => ({status: response.status, body: data})))
|
||||
.then(res => {
|
||||
if (res.status === 200 && res.body.success) {
|
||||
alert(res.body.message);
|
||||
window.location.href = '{{ route('user.dashboard') }}';
|
||||
} else {
|
||||
window.closeModal('customModal');
|
||||
setTimeout(() => {
|
||||
showModal('Lỗi Giao Dịch', res.body.message || 'Có lỗi xảy ra, vui lòng thử lại.', false, null);
|
||||
}, 350);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
window.closeModal('customModal');
|
||||
setTimeout(() => {
|
||||
showModal('Lỗi Hệ Thống', 'Không thể kết nối đến máy chủ.', false, null);
|
||||
}, 350);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
// ── Template picker ──────────────────────────────────────────────
|
||||
window.selectTpl = function(el, id) {
|
||||
document.querySelectorAll('.tpl-btn').forEach(b => {
|
||||
b.classList.remove('border-[#3462f7]','ring-2','ring-[#3462f7]/20','shadow-md');
|
||||
b.classList.add('border-[#e8edf4]');
|
||||
b.querySelector('.tpl-check').classList.replace('opacity-100','opacity-0');
|
||||
});
|
||||
el.classList.remove('border-[#e8edf4]');
|
||||
el.classList.add('border-[#3462f7]','ring-2','ring-[#3462f7]/20','shadow-md');
|
||||
el.querySelector('.tpl-check').classList.replace('opacity-0','opacity-100');
|
||||
document.getElementById('template_id').value = id;
|
||||
applyTheme(id);
|
||||
};
|
||||
|
||||
applyTheme(1);
|
||||
|
||||
// ── TomSelect ────────────────────────────────────────────────────
|
||||
new TomSelect('#receiver', {
|
||||
create:false, searchField:['text','value'], maxOptions:10,
|
||||
sortField:{field:'text',direction:'asc'},
|
||||
placeholder:'Nhập tên hoặc mã nhân viên...',
|
||||
render:{
|
||||
option:function(d,e){
|
||||
const p=d.text.split(' - '),n=p[0]||'',i=p.slice(1).join(' · ')||'';
|
||||
return`<div class="px-3 py-2.5 flex flex-col gap-0.5 hover:bg-[#eff4ff] cursor-pointer transition-colors">
|
||||
<span class="font-bold text-[14px] text-[#1a2b49]">${e(n)}</span>
|
||||
<span class="text-[11px] text-gray-400">${e(i)}</span></div>`;
|
||||
},
|
||||
item:function(d,e){
|
||||
const n=d.text.split(' - ')[0]||'';
|
||||
return`<div class="font-semibold text-[14px] text-[#1a2b49]">${e(n)} <span class="text-gray-400 font-normal text-[12px]">(${e(d.value)})</span></div>`;
|
||||
},
|
||||
no_results:()=>'<div class="p-3 text-[13px] text-gray-400 text-center">Không tìm thấy nhân viên nào</div>'
|
||||
}
|
||||
});
|
||||
|
||||
// ── Modal ────────────────────────────────────────────────────────
|
||||
const $mTitle = document.getElementById('modal-title-customModal');
|
||||
const $mMsg = document.getElementById('modalMessage');
|
||||
const $mAct = document.getElementById('modalActions');
|
||||
|
||||
const showModal = (title, msg, confirm, cb) => {
|
||||
$mTitle.textContent = title;
|
||||
$mMsg.innerHTML = msg.replace(/\n/g,'<br>');
|
||||
if (confirm) {
|
||||
$mTitle.className = 'text-lg font-bold text-primary';
|
||||
$mAct.innerHTML = `<button id="btnOk" class="w-full sm:w-auto btn-primary !px-6 rounded-xl font-bold">Xác nhận gửi</button>
|
||||
<button id="btnCancel" class="w-full sm:w-auto btn-secondary !px-6 rounded-xl font-bold">Hủy</button>`;
|
||||
document.getElementById('btnCancel').onclick = () => window.closeModal('customModal');
|
||||
document.getElementById('btnOk').addEventListener('click', function(){
|
||||
this.disabled=true;
|
||||
this.innerHTML='<span class="flex items-center gap-2"><svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Đang xử lý...</span>';
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
$mTitle.className = 'text-lg font-bold text-[#1a2b49]';
|
||||
$mAct.innerHTML = `<button id="btnClose" class="w-full sm:w-auto btn-secondary !px-6 rounded-xl font-bold">Đóng</button>`;
|
||||
document.getElementById('btnClose').onclick = () => window.closeModal('customModal');
|
||||
}
|
||||
window.openModal('customModal');
|
||||
};
|
||||
|
||||
// ── Submit ───────────────────────────────────────────────────────
|
||||
document.getElementById('btnSubmitSend').addEventListener('click', function(){
|
||||
const form = document.getElementById('sendCardForm');
|
||||
if (!form.checkValidity()) { form.reportValidity(); return; }
|
||||
const msnv = document.getElementById('receiver').value;
|
||||
const amount = document.getElementById('amount').value;
|
||||
showModal('Xác nhận gửi Thank Card',
|
||||
'Đừng quên hoàn thành Thank Card của bạn nhé! Chúng mình sẽ gửi thông báo đến người nhận để họ đến khu vực nhận thư.',
|
||||
true, () => {
|
||||
$.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}),
|
||||
success:function(res){
|
||||
if(res.success){
|
||||
var email=res.receiver_email; email="antv@runsystem.net";
|
||||
$.ajax({
|
||||
url:'{{ config("constants.CHATOPS_API_URL") }}', method:'POST', contentType:'application/json',
|
||||
data:JSON.stringify({message:"💌 Bạn nhận được một Thank Card mới!\nNhanh chân đến khu vực nhận thư để nhận ngay nhé!",userEmail:email,token:'{{ config("constants.CHATOPS_API_TOKEN") }}'}),
|
||||
complete:function(){localStorage.setItem('flash_success',res.message);window.location.href='{{ route("user.dashboard") }}';}
|
||||
});
|
||||
} else {
|
||||
window.closeModal('customModal');
|
||||
setTimeout(()=>showModal('Lỗi Giao Dịch',res.message||'Có lỗi xảy ra.',false,null),350);
|
||||
}
|
||||
},
|
||||
error:function(xhr){
|
||||
window.closeModal('customModal');
|
||||
setTimeout(()=>showModal('Lỗi Hệ Thống',xhr.responseJSON?.message||'Không thể kết nối đến máy chủ.',false,null),350);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
Reference in New Issue
Block a user