m
This commit is contained in:
@@ -199,14 +199,4 @@ class AdminController extends Controller
|
|||||||
return redirect()->back()->withErrors(['error' => $e->getMessage()]);
|
return redirect()->back()->withErrors(['error' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset all cards for active users.
|
|
||||||
*/
|
|
||||||
public function resetCards(): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->adminService->resetAllCards();
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', __('messages.cards_reset_success'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,23 @@ class UserController extends Controller
|
|||||||
|
|
||||||
$receiverUser = \App\Models\User::where('msnv', $request->input('receiver'))->first();
|
$receiverUser = \App\Models\User::where('msnv', $request->input('receiver'))->first();
|
||||||
|
|
||||||
|
// Send ChatOps notification from backend to avoid CORS issues
|
||||||
|
$chatOpsUrl = config('constants.CHATOPS_API_URL');
|
||||||
|
$chatOpsToken = config('constants.CHATOPS_API_TOKEN');
|
||||||
|
$email = 'antv@runsystem.net';
|
||||||
|
|
||||||
|
if ($chatOpsUrl && $chatOpsToken) {
|
||||||
|
try {
|
||||||
|
\Illuminate\Support\Facades\Http::timeout(5)->post($chatOpsUrl, [
|
||||||
|
'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' => $chatOpsToken,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Illuminate\Support\Facades\Log::error('ChatOps API failed: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => __('messages.thank_card_send_success'),
|
'message' => __('messages.thank_card_send_success'),
|
||||||
|
|||||||
@@ -29,4 +29,18 @@ class SendThankCardRequest extends FormRequest
|
|||||||
'amount' => 'required|integer|min:1|max:' . Administration::MAX_SEND_CARD_PER_MONTH,
|
'amount' => 'required|integer|min:1|max:' . Administration::MAX_SEND_CARD_PER_MONTH,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a failed validation attempt.
|
||||||
|
*/
|
||||||
|
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
|
||||||
|
{
|
||||||
|
throw new \Illuminate\Http\Exceptions\HttpResponseException(
|
||||||
|
response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 422)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Thêm user mới
|
Thêm user mới
|
||||||
</a>
|
</a>
|
||||||
<button type="button" onclick="openModal('resetModal')" class="cursor-pointer border border-red-200 bg-red-50/50 hover:bg-red-50 text-red-600 font-bold py-2.5 px-5 rounded-xl text-sm transition-colors shadow-sm w-full sm:w-auto">Reset Card Tháng</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,28 +43,4 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-confirm-modal id="deleteModal" title="Xác nhận nghỉ việc" confirmText="Xác nhận Nghỉ việc" />
|
<x-confirm-modal id="deleteModal" title="Xác nhận nghỉ việc" confirmText="Xác nhận Nghỉ việc" />
|
||||||
|
|
||||||
<x-modal id="resetModal" title="Xác nhận reset card" maxWidth="md" hideHeader="true">
|
|
||||||
<div class="flex items-start gap-4">
|
|
||||||
<div class="flex-shrink-0 flex items-center justify-center w-12 h-12 bg-red-100 rounded-full">
|
|
||||||
<svg class="w-6 h-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 mt-1">
|
|
||||||
<h3 class="text-lg font-bold text-gray-900">Xác nhận reset card tháng</h3>
|
|
||||||
<p class="text-sm text-gray-600 mt-2 leading-relaxed">
|
|
||||||
Bạn có chắc chắn muốn reset toàn bộ số card hiện tại của các user về 0?<br><br>Hành động này thường chỉ thực hiện vào cuối tháng và không thể hoàn tác.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-slot name="footer">
|
|
||||||
<form action="{{ route('admin.reset_cards') }}" method="POST" class="w-full sm:w-auto">
|
|
||||||
@csrf
|
|
||||||
<button type="submit" class="w-full sm:w-auto inline-flex items-center justify-center bg-red-600 hover:bg-red-700 text-white font-bold py-2.5 px-6 rounded-xl text-sm transition-colors shadow-md shadow-red-500/10 min-w-[120px] h-[42px]">Đồng ý Reset</button>
|
|
||||||
</form>
|
|
||||||
<button type="button" onclick="closeModal('resetModal')" class="w-full sm:w-auto inline-flex items-center justify-center bg-white border border-[#d9dfe7] hover:bg-gray-50 text-[#1a2b49] font-bold py-2.5 px-6 rounded-xl text-sm transition-colors shadow-sm min-w-[120px] h-[42px]">Hủy bỏ</button>
|
|
||||||
</x-slot>
|
|
||||||
</x-modal>
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ $tpls = [
|
|||||||
<option value="{{$u->msnv}}">{{$u->name}} - {{$u->msnv}} ({{$u->mail}}) - {{$dept}}</option>
|
<option value="{{$u->msnv}}">{{$u->name}} - {{$u->msnv}} ({{$u->mail}}) - {{$dept}}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
|
<span id="error-receiver" class="text-red-500 text-[12px] font-semibold mt-1.5 hidden block"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Số lượng --}}
|
{{-- Số lượng --}}
|
||||||
@@ -100,16 +101,20 @@ $tpls = [
|
|||||||
</select>
|
</select>
|
||||||
<span class="text-[12px] text-gray-400 font-medium">/ tháng</span>
|
<span class="text-[12px] text-gray-400 font-medium">/ tháng</span>
|
||||||
</div>
|
</div>
|
||||||
|
<span id="error-amount" class="text-red-500 text-[12px] font-semibold mt-1.5 hidden block"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Confirm checkbox --}}
|
{{-- 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">
|
<div class="mb-2">
|
||||||
|
<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">
|
||||||
<input type="checkbox" name="confirm_written" id="confirm_written" value="1"
|
<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>
|
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">
|
<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>
|
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>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<span id="error-confirm_written" class="text-red-500 text-[12px] font-semibold mt-1.5 hidden block"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{-- Card footer line --}}
|
{{-- Card footer line --}}
|
||||||
<div class="mt-6 pt-4 border-t border-dashed" id="cardFooterLine" style="border-color:#FFE4A0;">
|
<div class="mt-6 pt-4 border-t border-dashed" id="cardFooterLine" style="border-color:#FFE4A0;">
|
||||||
@@ -132,6 +137,14 @@ $tpls = [
|
|||||||
</div>
|
</div>
|
||||||
</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 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">
|
||||||
|
<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>
|
||||||
|
<span id="generalErrorMessage"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{-- Actions --}}
|
{{-- Actions --}}
|
||||||
<div class="flex flex-col sm:flex-row justify-end gap-3">
|
<div class="flex flex-col sm:flex-row justify-end gap-3">
|
||||||
<a href="{{ route('user.dashboard') }}" id="btnCancelLink" class="btn-secondary rounded-xl font-bold flex items-center justify-center gap-2 order-2 sm:order-1">
|
<a href="{{ route('user.dashboard') }}" id="btnCancelLink" class="btn-secondary rounded-xl font-bold flex items-center justify-center gap-2 order-2 sm:order-1">
|
||||||
@@ -186,8 +199,26 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
footerBorder:'#DDD6FE'},
|
footerBorder:'#DDD6FE'},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Theme slugs ──────────────────────────────────────────────────
|
||||||
|
const templateSlugs = {
|
||||||
|
1: 'thu_cam_on',
|
||||||
|
2: 'sakura',
|
||||||
|
3: 'appreciation',
|
||||||
|
4: 'celebration'
|
||||||
|
};
|
||||||
|
const templateIds = {
|
||||||
|
'thu_cam_on': 1,
|
||||||
|
'sakura': 2,
|
||||||
|
'appreciation': 3,
|
||||||
|
'celebration': 4
|
||||||
|
};
|
||||||
|
|
||||||
// Current selected template ID (defaults to 1) // read from hidden input or localStorage
|
// Current selected template ID (defaults to 1) // read from hidden input or localStorage
|
||||||
let currentTpl = parseInt(localStorage.getItem('selectedTemplate')) || parseInt(document.getElementById('template_id').value) || 1;
|
const savedTemplateSlug = localStorage.getItem('thankcard_selected_template');
|
||||||
|
let currentTpl = (savedTemplateSlug && templateIds[savedTemplateSlug])
|
||||||
|
|| parseInt(localStorage.getItem('selectedTemplate'))
|
||||||
|
|| parseInt(document.getElementById('template_id').value)
|
||||||
|
|| 1;
|
||||||
|
|
||||||
function applyTheme(id) {
|
function applyTheme(id) {
|
||||||
const t = T[id]; if (!t) return;
|
const t = T[id]; if (!t) return;
|
||||||
@@ -256,6 +287,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
document.getElementById('template_id').value = id;
|
document.getElementById('template_id').value = id;
|
||||||
// persist selection
|
// persist selection
|
||||||
localStorage.setItem('selectedTemplate', id);
|
localStorage.setItem('selectedTemplate', id);
|
||||||
|
if (templateSlugs[id]) {
|
||||||
|
localStorage.setItem('thankcard_selected_template', templateSlugs[id]);
|
||||||
|
}
|
||||||
applyTheme(id);
|
applyTheme(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -299,7 +333,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── TomSelect ────────────────────────────────────────────────────
|
// ── TomSelect ────────────────────────────────────────────────────
|
||||||
new TomSelect('#receiver', {
|
const receiverSelect = new TomSelect('#receiver', {
|
||||||
create:false, searchField:['text','value'], maxOptions:10,
|
create:false, searchField:['text','value'], maxOptions:10,
|
||||||
sortField:{field:'text',direction:'asc'},
|
sortField:{field:'text',direction:'asc'},
|
||||||
placeholder:'Nhập tên hoặc mã nhân viên...',
|
placeholder:'Nhập tên hoặc mã nhân viên...',
|
||||||
@@ -327,7 +361,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
$mTitle.textContent = title;
|
$mTitle.textContent = title;
|
||||||
$mMsg.innerHTML = msg.replace(/\n/g,'<br>');
|
$mMsg.innerHTML = msg.replace(/\n/g,'<br>');
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
$mTitle.className = 'text-lg font-bold text-primary';
|
$mTitle.className = 'text-lg font-bold';
|
||||||
|
// Sync text color with current template's accent color
|
||||||
|
$mTitle.style.color = T[currentTpl].accent;
|
||||||
// Use accent color of current template for confirm button background
|
// Use accent color of current template for confirm button background
|
||||||
const accent = T[currentTpl].accent;
|
const accent = T[currentTpl].accent;
|
||||||
$mAct.innerHTML = `<button id="btnOk" class="w-full sm:w-auto btn-primary !px-6 rounded-xl font-bold" style="background:${accent};border-color:${accent};">Xác nhận gửi</button>
|
$mAct.innerHTML = `<button id="btnOk" class="w-full sm:w-auto btn-primary !px-6 rounded-xl font-bold" style="background:${accent};border-color:${accent};">Xác nhận gửi</button>
|
||||||
@@ -340,41 +376,149 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$mTitle.className = 'text-lg font-bold text-[#1a2b49]';
|
$mTitle.className = 'text-lg font-bold text-[#1a2b49]';
|
||||||
|
$mTitle.style.color = ''; // reset style
|
||||||
$mAct.innerHTML = `<button id="btnClose" class="w-full sm:w-auto btn-secondary !px-6 rounded-xl font-bold">Đóng</button>`;
|
$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');
|
document.getElementById('btnClose').onclick = () => window.closeModal('customModal');
|
||||||
}
|
}
|
||||||
window.openModal('customModal');
|
window.openModal('customModal');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Error Helpers ────────────────────────────────────────────────
|
||||||
|
function clearErrors() {
|
||||||
|
document.getElementById('formGeneralError').classList.add('hidden');
|
||||||
|
document.getElementById('generalErrorMessage').textContent = '';
|
||||||
|
|
||||||
|
document.querySelectorAll('[id^="error-"]').forEach(el => {
|
||||||
|
el.textContent = '';
|
||||||
|
el.classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateForm() {
|
||||||
|
clearErrors();
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
const receiver = document.getElementById('receiver').value;
|
||||||
|
if (!receiver) {
|
||||||
|
const errEl = document.getElementById('error-receiver');
|
||||||
|
if (errEl) {
|
||||||
|
errEl.textContent = 'Vui lòng chọn người nhận.';
|
||||||
|
errEl.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = document.getElementById('amount').value;
|
||||||
|
if (!amount) {
|
||||||
|
const errEl = document.getElementById('error-amount');
|
||||||
|
if (errEl) {
|
||||||
|
errEl.textContent = 'Vui lòng chọn số lượng thẻ.';
|
||||||
|
errEl.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirm = document.getElementById('confirm_written').checked;
|
||||||
|
if (!confirm) {
|
||||||
|
const errEl = document.getElementById('error-confirm_written');
|
||||||
|
if (errEl) {
|
||||||
|
errEl.textContent = 'Bạn cần xác nhận đã viết nội dung trên ThankCard.';
|
||||||
|
errEl.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Submit ───────────────────────────────────────────────────────
|
// ── Submit ───────────────────────────────────────────────────────
|
||||||
document.getElementById('btnSubmitSend').addEventListener('click', function(){
|
const $submitBtn = document.getElementById('btnSubmitSend');
|
||||||
const form = document.getElementById('sendCardForm');
|
const btnOriginalHtml = $submitBtn.innerHTML;
|
||||||
if (!form.checkValidity()) { form.reportValidity(); return; }
|
|
||||||
|
function setButtonLoading(loading) {
|
||||||
|
if (loading) {
|
||||||
|
$submitBtn.disabled = true;
|
||||||
|
$submitBtn.innerHTML = `<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><span>⏳ Đang gửi...</span>`;
|
||||||
|
} else {
|
||||||
|
$submitBtn.disabled = false;
|
||||||
|
$submitBtn.innerHTML = btnOriginalHtml;
|
||||||
|
// Keep selected template button accent style synchronized
|
||||||
|
const accent = T[currentTpl]?.accent;
|
||||||
|
if (accent) {
|
||||||
|
$submitBtn.style.background = accent;
|
||||||
|
$submitBtn.style.borderColor = accent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$submitBtn.addEventListener('click', function(){
|
||||||
|
if (!validateForm()) { return; }
|
||||||
|
|
||||||
const msnv = document.getElementById('receiver').value;
|
const msnv = document.getElementById('receiver').value;
|
||||||
const amount = document.getElementById('amount').value;
|
const amount = document.getElementById('amount').value;
|
||||||
|
|
||||||
showModal('Xác nhận gửi Thank Card',
|
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ư.',
|
'Đừ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, () => {
|
true, () => {
|
||||||
|
window.closeModal('customModal');
|
||||||
|
setButtonLoading(true);
|
||||||
|
clearErrors();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url:'{{ route("user.store_send") }}', method:'POST', contentType:'application/json',
|
url:'{{ route("user.store_send") }}', method:'POST', contentType:'application/json',
|
||||||
headers:{'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content')},
|
headers:{'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content')},
|
||||||
data:JSON.stringify({receiver:msnv,amount:amount}),
|
data:JSON.stringify({receiver:msnv,amount:amount}),
|
||||||
success:function(res){
|
success:function(res){
|
||||||
if(res.success){
|
if(res.success){
|
||||||
var email=res.receiver_email; email="antv@runsystem.net";
|
setButtonLoading(false);
|
||||||
$.ajax({
|
|
||||||
url:'{{ config("constants.CHATOPS_API_URL") }}', method:'POST', contentType:'application/json',
|
// Reset form fields
|
||||||
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") }}'}),
|
receiverSelect.clear();
|
||||||
complete:function(){localStorage.setItem('flash_success',res.message);window.location.href='{{ route("user.dashboard") }}';}
|
document.getElementById('amount').value = '1';
|
||||||
});
|
document.getElementById('confirm_written').checked = false;
|
||||||
|
|
||||||
|
// Clear local storage for inputs (but not template)
|
||||||
|
localStorage.removeItem('selectedReceiver');
|
||||||
|
localStorage.removeItem('selectedAmount');
|
||||||
|
|
||||||
|
// Display toast message
|
||||||
|
if (window.showToast) {
|
||||||
|
window.showToast("🎉 ThankCard đã được gửi thành công!", "success");
|
||||||
} else {
|
} else {
|
||||||
window.closeModal('customModal');
|
alert("🎉 ThankCard đã được gửi thành công!");
|
||||||
setTimeout(()=>showModal('Lỗi Giao Dịch',res.message||'Có lỗi xảy ra.',false,null),350);
|
}
|
||||||
|
} else {
|
||||||
|
setButtonLoading(false);
|
||||||
|
if (res.errors) {
|
||||||
|
for (const [field, messages] of Object.entries(res.errors)) {
|
||||||
|
const errEl = document.getElementById('error-' + field);
|
||||||
|
if (errEl) {
|
||||||
|
errEl.textContent = messages.join(' ');
|
||||||
|
errEl.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res.message) {
|
||||||
|
document.getElementById('generalErrorMessage').textContent = res.message;
|
||||||
|
document.getElementById('formGeneralError').classList.remove('hidden');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error:function(xhr){
|
error:function(xhr){
|
||||||
window.closeModal('customModal');
|
setButtonLoading(false);
|
||||||
setTimeout(()=>showModal('Lỗi Hệ Thống',xhr.responseJSON?.message||'Không thể kết nối đến máy chủ.',false,null),350);
|
const res = xhr.responseJSON;
|
||||||
|
if (res && res.errors) {
|
||||||
|
for (const [field, messages] of Object.entries(res.errors)) {
|
||||||
|
const errEl = document.getElementById('error-' + field);
|
||||||
|
if (errEl) {
|
||||||
|
errEl.textContent = messages.join(' ');
|
||||||
|
errEl.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,4 @@ Route::middleware(['auth', 'force_change_password', 'admin'])->prefix('admin')->
|
|||||||
|
|
||||||
Route::put('/add-cards/{id}', [AdminController::class, 'updateAddCard'])->name('add_cards.update');
|
Route::put('/add-cards/{id}', [AdminController::class, 'updateAddCard'])->name('add_cards.update');
|
||||||
Route::delete('/add-cards/{id}', [AdminController::class, 'destroyAddCard'])->name('add_cards.destroy');
|
Route::delete('/add-cards/{id}', [AdminController::class, 'destroyAddCard'])->name('add_cards.destroy');
|
||||||
|
|
||||||
Route::post('/reset-cards', [AdminController::class, 'resetCards'])->name('reset_cards');
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class SendThankCardTest extends TestCase
|
|||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
\Illuminate\Support\Facades\Http::fake();
|
||||||
$this->userService = $this->app->make(UserServiceInterface::class);
|
$this->userService = $this->app->make(UserServiceInterface::class);
|
||||||
|
|
||||||
// Create sender and receiver
|
// Create sender and receiver
|
||||||
@@ -92,4 +93,27 @@ class SendThankCardTest extends TestCase
|
|||||||
$this->sender->refresh();
|
$this->sender->refresh();
|
||||||
$this->assertEquals(8, $this->sender->card);
|
$this->assertEquals(8, $this->sender->card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_controller_validation_fails_returns_correct_json_format(): void
|
||||||
|
{
|
||||||
|
$this->actingAs($this->sender);
|
||||||
|
|
||||||
|
$response = $this->postJson(route('user.store_send'), [
|
||||||
|
'receiver' => '',
|
||||||
|
'amount' => 999,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(422);
|
||||||
|
$response->assertJson([
|
||||||
|
'success' => false,
|
||||||
|
]);
|
||||||
|
$response->assertJsonStructure([
|
||||||
|
'success',
|
||||||
|
'errors' => [
|
||||||
|
'receiver',
|
||||||
|
'amount',
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user