fix mượn trả
This commit is contained in:
File diff suppressed because one or more lines are too long
165
public/js/app.js
165
public/js/app.js
@@ -59,6 +59,7 @@ class AccountManager {
|
||||
this.assetBorrowRequestType = 'borrow';
|
||||
this.pendingAssetRequestRejectId = undefined;
|
||||
this.assetBorrowAutoRefreshTimer = undefined;
|
||||
this.pendingAssetRequestDeleteConfirmResolver = undefined;
|
||||
}
|
||||
|
||||
configureNotifications() {
|
||||
@@ -1008,6 +1009,21 @@ class AccountManager {
|
||||
}
|
||||
}
|
||||
|
||||
const confirmAssetRequestDeleteBtn = document.getElementById('confirmAssetRequestDeleteBtn');
|
||||
if (confirmAssetRequestDeleteBtn && confirmAssetRequestDeleteBtn.dataset.boundClick !== 'true') {
|
||||
confirmAssetRequestDeleteBtn.addEventListener('click', () => this.resolveAssetRequestDeleteConfirm(true));
|
||||
confirmAssetRequestDeleteBtn.dataset.boundClick = 'true';
|
||||
}
|
||||
|
||||
document.querySelectorAll('.cancel-asset-request-delete-confirm').forEach(btn => {
|
||||
if (btn.dataset.boundClick === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.addEventListener('click', () => this.resolveAssetRequestDeleteConfirm(false));
|
||||
btn.dataset.boundClick = 'true';
|
||||
});
|
||||
|
||||
this.setupAssetBorrowRequestModalListeners();
|
||||
|
||||
const assetDepartmentForm = document.getElementById('assetDepartmentForm');
|
||||
@@ -2151,6 +2167,11 @@ class AccountManager {
|
||||
const statusMeta = this.getAssetRequestStatusMeta(item.RequestStatus);
|
||||
const note = String(item?.RequestNote || '').trim();
|
||||
const rejectReason = String(item?.RejectReason || '').trim();
|
||||
const canCancel = this.canCurrentUserCancelAssetRequest(item);
|
||||
const requestId = Number(item?.BorrowId) || 0;
|
||||
const cancelActionHtml = canCancel
|
||||
? `<button class="asset-borrow-cancel-btn px-3 py-1.5 rounded-md bg-red-600 hover:bg-red-700 text-white text-xs font-bold whitespace-nowrap" data-request-id="${requestId}">Hủy đơn</button>`
|
||||
: `<span class="text-xs text-slate-400">-</span>`;
|
||||
|
||||
return `
|
||||
<tr class="hover:bg-slate-50/80 transition-colors">
|
||||
@@ -2171,14 +2192,33 @@ class AccountManager {
|
||||
<td class="px-4 py-3 text-sm text-slate-600 whitespace-nowrap">${this.formatDateOnly(item.BorrowDate)}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-600 max-w-xs">${this.escapeHtml(note || '-')}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-600 max-w-xs">${this.escapeHtml(rejectReason || '-')}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-600">${cancelActionHtml}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
canCurrentUserCancelAssetRequest(item) {
|
||||
const status = this.normalizeAssetRequestStatus(item?.RequestStatus);
|
||||
if (status !== 'pending') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.canCurrentUserManageAssets()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const currentUserId = Number(this.getUserId());
|
||||
const createdBy = Number(item?.CreatedBy);
|
||||
return Number.isFinite(currentUserId)
|
||||
&& currentUserId > 0
|
||||
&& Number.isFinite(createdBy)
|
||||
&& createdBy === currentUserId;
|
||||
}
|
||||
|
||||
buildAssetBorrowEmptyRowHtml() {
|
||||
return `
|
||||
<tr>
|
||||
<td colspan="10" class="px-4 py-8 text-sm text-center text-slate-500">Chưa có đơn mượn/trả tài sản nào.</td>
|
||||
<td colspan="11" class="px-4 py-8 text-sm text-center text-slate-500">Chưa có đơn mượn/trả tài sản nào.</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
@@ -2235,7 +2275,7 @@ class AccountManager {
|
||||
>
|
||||
<span class="material-symbols-outlined text-base">notifications_active</span>
|
||||
Đơn chờ
|
||||
<span id="pendingAssetBorrowsCountBadge" class="${pendingCount > 0 ? '' : 'hidden'} absolute -top-2.5 -right-2.5 min-w-[22px] h-[22px] px-1.5 rounded-full bg-red-600 text-white text-xs font-extrabold leading-[22px] text-center ring-2 ring-white">${pendingCount > 99 ? '99+' : pendingCount}</span>
|
||||
<span id="pendingAssetBorrowsCountBadge" class="${pendingCount > 0 ? '' : 'hidden'} absolute -top-2.5 -right-2.5 min-w-[22px] h-[22px] px-1.5 rounded-[999px] bg-red-600 text-white text-xs font-extrabold leading-[22px] text-center ring-2 ring-white">${pendingCount > 99 ? '99+' : pendingCount}</span>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
@@ -2268,6 +2308,7 @@ class AccountManager {
|
||||
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Ngày</th>
|
||||
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Ghi chú</th>
|
||||
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Lý do</th>
|
||||
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Hành động</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 asset-borrows-table-body">
|
||||
@@ -2339,6 +2380,30 @@ class AccountManager {
|
||||
searchInput.dataset.boundInput = 'true';
|
||||
}
|
||||
|
||||
const tableBody = document.querySelector('.asset-borrows-table-body');
|
||||
if (tableBody && tableBody.dataset.boundActions !== 'true') {
|
||||
tableBody.addEventListener('click', (event) => {
|
||||
const cancelButton = event.target.closest('.asset-borrow-cancel-btn');
|
||||
if (!cancelButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = Number(cancelButton.dataset.requestId);
|
||||
if (!Number.isFinite(requestId) || requestId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deletePendingAssetBorrowRequest(requestId, {
|
||||
confirmMessage: `Bạn có chắc muốn hủy đơn #${requestId}?`,
|
||||
confirmButtonText: 'Hủy đơn',
|
||||
successMessage: 'Đã hủy đơn thành công',
|
||||
failureMessage: 'Hủy đơn thất bại'
|
||||
});
|
||||
});
|
||||
|
||||
tableBody.dataset.boundActions = 'true';
|
||||
}
|
||||
|
||||
this.setupAssetBorrowPagerListeners();
|
||||
}
|
||||
|
||||
@@ -2489,7 +2554,8 @@ class AccountManager {
|
||||
this.notifyFailure('Tạo đơn thất bại');
|
||||
}
|
||||
}
|
||||
buildPendingAssetRequestCardHtml(item) {
|
||||
|
||||
buildPendingAssetRequestCardHtml(item) {
|
||||
const typeMeta = this.getAssetRequestTypeMeta(item?.RequestType);
|
||||
const dateLabel = typeMeta.value === 'return' ? 'Ngày trả' : 'Ngày mượn';
|
||||
const note = String(item?.RequestNote || '').trim();
|
||||
@@ -2508,10 +2574,10 @@ buildPendingAssetRequestCardHtml(item) {
|
||||
<div><span class="font-bold">Ghi chú:</span> ${this.escapeHtml(note || '-')}</div>
|
||||
<div><span class="font-bold">${dateLabel}:</span> ${this.formatDateOnly(item?.BorrowDate)}</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-2 pt-1">
|
||||
<button class="asset-request-approve-btn w-full px-3 py-1.5 rounded-md bg-primary hover:bg-primary-dim text-on-primary text-xs font-bold" data-request-id="${requestId}">Chấp nhận</button>
|
||||
<button class="asset-request-reject-btn w-full px-3 py-1.5 rounded-md bg-red-600 hover:bg-red-700 text-white text-xs font-bold" data-request-id="${requestId}">Từ chối</button>
|
||||
<button class="asset-request-delete-btn w-full px-3 py-1.5 rounded-md bg-slate-700 hover:bg-slate-800 text-white text-xs font-bold" data-request-id="${requestId}">Xóa đơn</button>
|
||||
<div class="flex items-center gap-2 pt-1">
|
||||
<button class="asset-request-approve-btn flex-1 min-w-0 px-3 py-1.5 rounded-md bg-primary hover:bg-primary-dim text-on-primary text-xs font-bold whitespace-nowrap" data-request-id="${requestId}">Chấp nhận</button>
|
||||
<button class="asset-request-reject-btn flex-1 min-w-0 px-3 py-1.5 rounded-md bg-red-600 hover:bg-red-700 text-white text-xs font-bold whitespace-nowrap" data-request-id="${requestId}">Từ chối</button>
|
||||
<button class="asset-request-delete-btn flex-1 min-w-0 px-3 py-1.5 rounded-md bg-slate-700 hover:bg-slate-800 text-white text-xs font-bold whitespace-nowrap" data-request-id="${requestId}">Xóa đơn</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -2639,6 +2705,43 @@ buildPendingAssetRequestCardHtml(item) {
|
||||
reasonInput.focus();
|
||||
}
|
||||
|
||||
resolveAssetRequestDeleteConfirm(confirmed) {
|
||||
const modal = document.getElementById('assetRequestDeleteConfirmModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('open');
|
||||
}
|
||||
|
||||
const resolver = this.pendingAssetRequestDeleteConfirmResolver;
|
||||
this.pendingAssetRequestDeleteConfirmResolver = undefined;
|
||||
if (typeof resolver === 'function') {
|
||||
resolver(Boolean(confirmed));
|
||||
}
|
||||
}
|
||||
|
||||
async confirmAssetRequestDelete(message, confirmButtonText = 'Xóa đơn') {
|
||||
const modal = document.getElementById('assetRequestDeleteConfirmModal');
|
||||
const messageNode = document.getElementById('assetRequestDeleteConfirmMessage');
|
||||
const confirmButton = document.getElementById('confirmAssetRequestDeleteBtn');
|
||||
|
||||
if (!modal || !messageNode || !confirmButton) {
|
||||
return window.confirm(message);
|
||||
}
|
||||
|
||||
messageNode.textContent = String(message || 'Bạn có chắc muốn thực hiện thao tác này?');
|
||||
confirmButton.textContent = String(confirmButtonText || 'Xóa đơn');
|
||||
|
||||
if (this.pendingAssetRequestDeleteConfirmResolver) {
|
||||
this.resolveAssetRequestDeleteConfirm(false);
|
||||
}
|
||||
|
||||
modal.style.zIndex = '140';
|
||||
modal.classList.add('open');
|
||||
|
||||
return new Promise(resolve => {
|
||||
this.pendingAssetRequestDeleteConfirmResolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
async handleAssetRequestRejectSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -2686,7 +2789,10 @@ buildPendingAssetRequestCardHtml(item) {
|
||||
&& failureMessage.toLowerCase().includes('xóa đơn chờ');
|
||||
|
||||
if (canAutoSuggestDelete) {
|
||||
const shouldDelete = window.confirm(`Đơn #${requestId} không còn hợp lệ. Bạn có muốn xóa đơn chờ này không?`);
|
||||
const shouldDelete = await this.confirmAssetRequestDelete(
|
||||
`Đơn #${requestId} không còn hợp lệ. Bạn có muốn xóa đơn chờ này không?`,
|
||||
'Xóa đơn'
|
||||
);
|
||||
if (shouldDelete) {
|
||||
await this.deletePendingAssetBorrowRequest(requestId);
|
||||
}
|
||||
@@ -2722,19 +2828,18 @@ buildPendingAssetRequestCardHtml(item) {
|
||||
}
|
||||
}
|
||||
|
||||
async deletePendingAssetBorrowRequest(requestId) {
|
||||
if (!this.canCurrentUserManageAssets()) {
|
||||
this.notifyWarning('Chỉ role Asset/Admin mới được xóa đơn chờ.');
|
||||
return;
|
||||
}
|
||||
|
||||
async deletePendingAssetBorrowRequest(requestId, options = {}) {
|
||||
const targetId = Number(requestId);
|
||||
if (!Number.isFinite(targetId) || targetId <= 0) {
|
||||
this.notifyWarning('Không xác định được đơn cần xóa.');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = window.confirm(`Bạn có chắc muốn xóa đơn chờ #${targetId}?`);
|
||||
const confirmMessage = String(options?.confirmMessage || `Bạn có chắc muốn xóa đơn chờ #${targetId}?`);
|
||||
const confirmButtonText = String(options?.confirmButtonText || 'Xóa đơn');
|
||||
const successMessage = String(options?.successMessage || 'Đã xóa đơn chờ');
|
||||
const failureMessage = String(options?.failureMessage || 'Xóa đơn chờ thất bại');
|
||||
const confirmed = await this.confirmAssetRequestDelete(confirmMessage, confirmButtonText);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
@@ -2747,11 +2852,16 @@ buildPendingAssetRequestCardHtml(item) {
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
this.notifyFailure(data.message || 'Xóa đơn chờ thất bại');
|
||||
const resolvedFailureMessage = data.message || failureMessage;
|
||||
if (response.status === 403) {
|
||||
this.notifyWarning(resolvedFailureMessage);
|
||||
} else {
|
||||
this.notifyFailure(resolvedFailureMessage);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.notifySuccess(data.message || 'Đã xóa đơn chờ');
|
||||
this.notifySuccess(data.message || successMessage);
|
||||
await this.fetchAssetBorrows();
|
||||
|
||||
if (this.currentPage === 'asset-borrows') {
|
||||
@@ -2766,7 +2876,7 @@ buildPendingAssetRequestCardHtml(item) {
|
||||
this.updatePendingAssetRequestsBadge();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.notifyFailure('Xóa đơn chờ thất bại');
|
||||
this.notifyFailure(failureMessage);
|
||||
}
|
||||
}
|
||||
getAssetsContent() {
|
||||
@@ -5279,6 +5389,9 @@ buildPendingAssetRequestCardHtml(item) {
|
||||
}
|
||||
|
||||
closeModals() {
|
||||
if (this.pendingAssetRequestDeleteConfirmResolver) {
|
||||
this.resolveAssetRequestDeleteConfirm(false);
|
||||
}
|
||||
document.querySelectorAll('.modal-backdrop').forEach(modal => {
|
||||
modal.classList.remove('open');
|
||||
});
|
||||
@@ -6186,6 +6299,9 @@ buildPendingAssetRequestCardHtml(item) {
|
||||
|
||||
// Global modal close functions
|
||||
function closeAllModals() {
|
||||
if (app?.pendingAssetRequestDeleteConfirmResolver) {
|
||||
app.resolveAssetRequestDeleteConfirm(false);
|
||||
}
|
||||
document.querySelectorAll('.modal-backdrop').forEach(modal => {
|
||||
modal.classList.remove('open');
|
||||
});
|
||||
@@ -6250,6 +6366,7 @@ function closeAssetPendingRequestsModal() {
|
||||
if (modal) {
|
||||
modal.classList.remove('open');
|
||||
}
|
||||
closeAssetRequestDeleteConfirmModal();
|
||||
const rejectModal = document.getElementById('assetRequestRejectModal');
|
||||
if (rejectModal) {
|
||||
rejectModal.classList.remove('open');
|
||||
@@ -6263,6 +6380,18 @@ function closeAssetRequestRejectModal() {
|
||||
}
|
||||
}
|
||||
|
||||
function closeAssetRequestDeleteConfirmModal() {
|
||||
if (app?.pendingAssetRequestDeleteConfirmResolver) {
|
||||
app.resolveAssetRequestDeleteConfirm(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById('assetRequestDeleteConfirmModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('open');
|
||||
}
|
||||
}
|
||||
|
||||
function closeAssetDepartmentModal() {
|
||||
const modal = document.getElementById('assetDepartmentModal');
|
||||
if (modal) {
|
||||
|
||||
@@ -454,6 +454,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirm Delete/Cancel Asset Request Modal -->
|
||||
<div class="modal-backdrop fixed inset-0 z-[120] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="assetRequestDeleteConfirmModal" style="z-index: 140;">
|
||||
<div class="modal-content w-full max-w-md bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
|
||||
<div class="px-6 py-4 border-b border-slate-100 bg-red-50 flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-red-600 text-2xl">warning</span>
|
||||
<h3 class="text-base font-extrabold text-red-700">Xác nhận thao tác</h3>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p id="assetRequestDeleteConfirmMessage" class="text-sm text-slate-600 mb-6">Bạn có chắc muốn thực hiện thao tác này?</p>
|
||||
<div class="flex gap-3">
|
||||
<button type="button" class="cancel-asset-request-delete-confirm flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg">Đóng</button>
|
||||
<button type="button" id="confirmAssetRequestDeleteBtn" class="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-xs font-bold">Xóa đơn</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reject Asset Request Modal -->
|
||||
<div class="modal-backdrop fixed inset-0 z-[110] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="assetRequestRejectModal" style="z-index: 130;">
|
||||
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
<button id="pendingAssetRequestsBtn" type="button" class="hidden relative flex items-center gap-2 px-3 py-2 rounded-lg border border-amber-200 bg-amber-50 hover:bg-amber-100 text-amber-800 transition-colors" title="Đơn chờ xử lý">
|
||||
<span class="material-symbols-outlined text-base">notifications_active</span>
|
||||
<span class="text-xs font-bold">Đơn chờ</span>
|
||||
<span id="pendingAssetRequestsBadge" class="hidden absolute -top-2.5 -right-2.5 min-w-[22px] h-[22px] px-1.5 rounded-full bg-red-600 text-white text-xs font-extrabold leading-[22px] text-center ring-2 ring-white">0</span>
|
||||
<span id="pendingAssetRequestsBadge" class="hidden absolute -top-2.5 -right-2.5 min-w-[22px] h-[22px] px-1.5 rounded-[999px] bg-red-600 text-white text-xs font-extrabold leading-[22px] text-center ring-2 ring-white">0</span>
|
||||
</button>
|
||||
<button id="profileBtn" type="button" class="profile-btn flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" title="Sửa hồ sơ">
|
||||
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
|
||||
|
||||
Reference in New Issue
Block a user