xuất tài sản

This commit is contained in:
2026-05-06 16:04:56 +07:00
parent 8b2a9d7afe
commit d88aa39bd6
4 changed files with 639 additions and 52 deletions

View File

@@ -48,6 +48,7 @@ class AccountManager {
this.assetDepartmentSearchTerm = '';
this.assetProjects = [];
this.assetProjectSearchTerm = '';
this.assetExportHistories = [];
this.selectedAssetIds = new Set();
this.mobileBreakpoint = 900;
this.boundResizeHandler = null;
@@ -467,6 +468,44 @@ class AccountManager {
});
}
refreshBorrowAssetProjectOptions(selectedValue = '') {
const select = document.getElementById('borrowAssetProjectInput');
if (!select) {
return;
}
const normalizedSelected = String(selectedValue || select.value || '').trim();
const projectNames = this.getUniqueAssetProjectNames();
select.innerHTML = '';
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.textContent = '-- Chọn dự án --';
select.appendChild(emptyOption);
let hasSelected = false;
projectNames.forEach(name => {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
if (normalizedSelected && name === normalizedSelected) {
option.selected = true;
hasSelected = true;
}
select.appendChild(option);
});
if (normalizedSelected && !hasSelected) {
const legacyOption = document.createElement('option');
legacyOption.value = normalizedSelected;
legacyOption.textContent = normalizedSelected;
legacyOption.selected = true;
select.appendChild(legacyOption);
} else if (!normalizedSelected) {
select.value = '';
}
}
getAssetBorrowProductDisplayName(asset) {
if (!asset) {
return '-- Chọn tài sản --';
@@ -907,6 +946,7 @@ class AccountManager {
if (data.success) {
this.assetProjects = Array.isArray(data.data) ? data.data : [];
this.refreshAssetProjectOptions(document.getElementById('assetProjectInput')?.value || '');
this.refreshBorrowAssetProjectOptions(document.getElementById('borrowAssetProjectInput')?.value || '');
} else {
console.error('Load asset projects failed:', data.message);
}
@@ -915,6 +955,82 @@ class AccountManager {
}
}
async fetchAssetExportHistories(limit = 300) {
try {
const safeLimit = Number.isFinite(Number(limit)) ? Math.max(1, Math.min(Number(limit), 2000)) : 300;
const res = await fetch(`${this.apiBase}/asset-export-history?limit=${safeLimit}`, {
headers: this.getAuthHeaders(false)
});
const data = await res.json();
if (data.success) {
this.assetExportHistories = Array.isArray(data.data) ? data.data : [];
} else {
console.error('Load asset export history failed:', data.message);
}
} catch (err) {
console.error('Fetch asset export history error:', err);
}
}
buildAssetExportHistoryRowsHtml(rows = []) {
if (!Array.isArray(rows) || rows.length === 0) {
return `
<tr>
<td colspan="7" class="px-4 py-8 text-sm text-center text-slate-500">Chưa có dữ liệu lịch sử xuất.</td>
</tr>
`;
}
return rows.map(item => {
const assetLabel = [String(item?.AssetCode || '').trim(), String(item?.AssetName || '').trim()]
.filter(Boolean)
.join(' - ') || '-';
return `
<tr class="hover:bg-slate-50/80 transition-colors">
<td class="px-4 py-3 text-sm text-slate-600 whitespace-nowrap">${this.formatDateTime(item?.ExportedDate || item?.CreatedDate)}</td>
<td class="px-4 py-3 text-sm text-slate-700">${this.escapeHtml(assetLabel)}</td>
<td class="px-4 py-3 text-sm text-slate-700 font-semibold">${Number(item?.ExportQuantity) || 0}</td>
<td class="px-4 py-3 text-sm text-slate-600">${this.escapeHtml(item?.ProjectName || '-')}</td>
<td class="px-4 py-3 text-sm text-slate-600">${this.escapeHtml(item?.CustodianName || '-')}</td>
<td class="px-4 py-3 text-sm text-slate-600">${this.escapeHtml(item?.ExportedByName || '-')}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-pre-line">${this.escapeHtml(item?.ExportNote || '-')}</td>
</tr>
`;
}).join('');
}
renderAssetExportHistoryModal() {
const tbody = document.getElementById('assetExportHistoryTableBody');
if (!tbody) {
return;
}
tbody.innerHTML = this.buildAssetExportHistoryRowsHtml(this.assetExportHistories);
}
async openAssetExportHistoryModal() {
if (!this.ensureAssetManagePermission('xem lich su xuat tai san')) {
return;
}
const modal = document.getElementById('assetExportHistoryModal');
const tbody = document.getElementById('assetExportHistoryTableBody');
if (!modal || !tbody) {
this.notifyFailure('Không tìm thấy biểu mẫu lịch sử xuất tài sản.');
return;
}
tbody.innerHTML = `
<tr>
<td colspan="7" class="px-4 py-8 text-sm text-center text-slate-500">Đang tải lịch sử xuất...</td>
</tr>
`;
modal.classList.add('open');
await this.fetchAssetExportHistories();
this.renderAssetExportHistoryModal();
}
async fetchRoles() {
try {
const res = await fetch(`${this.apiBase}/roles`);
@@ -952,6 +1068,7 @@ class AccountManager {
this.setupFilters();
this.refreshAssetDepartmentOptions(document.getElementById('assetDepartmentInput')?.value || '');
this.refreshAssetProjectOptions(document.getElementById('assetProjectInput')?.value || '');
this.refreshBorrowAssetProjectOptions(document.getElementById('borrowAssetProjectInput')?.value || '');
} catch (error) {
console.error('Lỗi load modals:', error);
}
@@ -1835,7 +1952,7 @@ class AccountManager {
return entries
.map(entry => this.formatBorrowerDisplay(entry.name, entry.quantity))
.filter(Boolean)
.map(item => `<div class="leading-5">${this.escapeHtml(item)}</div>`)
.map(item => `<div class="leading-5 whitespace-nowrap">${this.escapeHtml(item)}</div>`)
.join('');
}
@@ -1866,15 +1983,10 @@ class AccountManager {
const borrowerExportInPeriod = borrowerEntries.reduce((sum, entry) => (
sum + this.parseNonNegativeInteger(entry?.quantity, 0)
), 0);
const storedExportInPeriod = this.parseOptionalNonNegativeInteger(asset?.ExportInPeriod ?? asset?.exportInPeriod);
const exportInPeriod = storedExportInPeriod !== null
? storedExportInPeriod
: borrowerExportInPeriod;
const computedEndingBalance = Math.max(quantity + importInPeriod - exportInPeriod, 0);
const storedEndingBalance = this.parseOptionalNonNegativeInteger(asset?.EndingBalance ?? asset?.endingBalance);
const endingBalance = storedEndingBalance !== null
? storedEndingBalance
: computedEndingBalance;
// Borrower entries are the source of truth for exported quantity.
// This keeps UI consistent even when legacy rows have stale stored balances.
const exportInPeriod = borrowerExportInPeriod;
const endingBalance = Math.max(quantity + importInPeriod - exportInPeriod, 0);
return {
quantity,
@@ -3375,13 +3487,17 @@ class AccountManager {
<span class="material-symbols-outlined text-base">download</span>
Xuất Excel
</button>
<button id="openAssetExportHistoryBtn" class="border border-amber-300 text-amber-700 px-3 py-2 rounded-lg text-xs font-bold flex items-center gap-1.5 transition-all active:scale-95 ${canManageAssets ? 'hover:bg-amber-50' : 'opacity-50 cursor-not-allowed'}" ${canManageAssets ? '' : 'disabled'}>
<span class="material-symbols-outlined text-base">history</span>
Lịch sử xuất
</button>
<button id="addAssetBtn" class="bg-primary text-on-primary px-4 py-2 rounded-lg text-xs font-bold flex items-center gap-1.5 transition-all active:scale-95 ${canManageAssets ? 'hover:bg-primary-dim' : 'opacity-50 cursor-not-allowed'}" ${canManageAssets ? '' : 'disabled'}>
<span class="material-symbols-outlined text-base">add_box</span>
Thêm tài sản
</button>
<button id="borrowAssetBtn" class="border border-primary text-primary px-4 py-2 rounded-lg text-xs font-bold flex items-center gap-1.5 transition-all active:scale-95 ${canManageAssets ? 'hover:bg-primary/5' : 'opacity-50 cursor-not-allowed'}" ${canManageAssets ? '' : 'disabled'}>
<span class="material-symbols-outlined text-base">handshake</span>
Mượn tài sản
Xuất tài sản
</button>
<input id="assetImportInput" type="file" accept=".xlsx,.xls" class="hidden" />
</div>
@@ -3433,7 +3549,7 @@ class AccountManager {
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500" style="width: 132px; min-width: 132px; white-space: nowrap;">Trạng thái</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Vị trí</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Ngày mua</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Người mượn</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500" style="width: 240px; min-width: 240px;">Người mượn</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">Ngày tạo</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Người xuất</th>
@@ -3469,7 +3585,7 @@ class AccountManager {
</td>
<td class="px-4 py-3 text-sm text-slate-600">${asset.Location || '-'}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-nowrap">${this.formatDateOnly(asset.PurchaseDate)}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-normal">${this.formatBorrowerTableHtml(asset.Borrower)}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-normal" style="width: 240px; min-width: 240px;">${this.formatBorrowerTableHtml(asset.Borrower)}</td>
<td class="px-4 py-3 text-sm text-slate-600 max-w-xs truncate" title="${asset.Notes || ''}">${asset.Notes || '-'}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-nowrap">${this.formatDateOnly(asset.CreatedDate)}</td>
<td class="px-4 py-3 text-sm text-slate-600">${asset.ExportedBy || '-'}</td>
@@ -3553,7 +3669,7 @@ class AccountManager {
</td>
<td class="px-4 py-3 text-sm text-slate-600">${asset.Location || '-'}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-nowrap">${this.formatDateOnly(asset.PurchaseDate)}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-normal">${this.formatBorrowerTableHtml(asset.Borrower)}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-normal" style="width: 240px; min-width: 240px;">${this.formatBorrowerTableHtml(asset.Borrower)}</td>
<td class="px-4 py-3 text-sm text-slate-600 max-w-xs truncate" title="${asset.Notes || ''}">${asset.Notes || '-'}</td>
<td class="px-4 py-3 text-sm text-slate-600 whitespace-nowrap">${this.formatDateOnly(asset.CreatedDate)}</td>
<td class="px-4 py-3 text-sm text-slate-600">${asset.ExportedBy || '-'}</td>
@@ -3996,7 +4112,7 @@ class AccountManager {
};
}
buildAssetPayloadFromAsset(asset, borrowerEntriesOverride = null) {
buildAssetPayloadFromAsset(asset, borrowerEntriesOverride = null, fieldOverrides = {}) {
if (!asset) {
return null;
}
@@ -4045,9 +4161,9 @@ class AccountManager {
endingBalance,
unit: String(asset?.Unit || '').trim(),
department: String(asset?.Department || '').trim(),
project: String(asset?.Project || '').trim(),
project: String(fieldOverrides?.project ?? asset?.Project ?? '').trim(),
location: String(asset?.Location || '').trim(),
custodian: String(asset?.Custodian || '').trim(),
custodian: String(fieldOverrides?.custodian ?? asset?.Custodian ?? '').trim(),
borrower,
purchaseDate: this.toDateInputValue(asset?.PurchaseDate) || null,
purchasePrice: normalizedPrice,
@@ -4059,14 +4175,14 @@ class AccountManager {
const selectedIds = [...this.selectedAssetIds];
if (!selectedIds.length) {
if (showWarning) {
this.notifyWarning('Vui lòng chọn 1 tài sản để mượn.');
this.notifyWarning('Vui lòng chọn 1 tài sản để xuất.');
}
return null;
}
if (selectedIds.length > 1) {
if (showWarning) {
this.notifyWarning('Chỉ chọn đúng 1 tài sản cho mỗi lần mượn.');
this.notifyWarning('Chỉ chọn đúng 1 tài sản cho mỗi lần xuất.');
}
return null;
}
@@ -4080,7 +4196,7 @@ class AccountManager {
}
async openBorrowAssetModal() {
if (!this.ensureAssetManagePermission('muon tai san')) {
if (!this.ensureAssetManagePermission('xuat tai san')) {
return;
}
@@ -4092,10 +4208,13 @@ class AccountManager {
if (!this.users.length) {
await this.fetchUsers();
}
if (!this.assetProjects.length) {
await this.fetchAssetProjects();
}
const metrics = this.buildAssetQuantityMetrics(asset);
if (metrics.endingBalance <= 0) {
this.notifyWarning('Tài sản đã hết tồn cuối kỳ, không thể mượn thêm.');
this.notifyWarning('Tài sản đã hết tồn cuối kỳ, không thể xuất thêm.');
return;
}
@@ -4104,12 +4223,14 @@ class AccountManager {
const assetNameInput = document.getElementById('borrowAssetNameInput');
const endingInput = document.getElementById('borrowCurrentEndingInput');
const quantityInput = document.getElementById('borrowQuantityInput');
const projectInput = document.getElementById('borrowAssetProjectInput');
const noteInput = document.getElementById('borrowAssetNoteInput');
const borrowByInput = document.getElementById('borrowByInput');
const borrowRoleInput = document.getElementById('borrowRoleInput');
const modal = document.getElementById('borrowAssetModal');
if (!modal || !assetNameInput || !endingInput || !quantityInput) {
this.notifyFailure('Không tìm thấy biểu mẫu mượn tài sản.');
if (!modal || !assetNameInput || !endingInput || !quantityInput || !projectInput || !noteInput) {
this.notifyFailure('Không tìm thấy biểu mẫu xuất tài sản.');
return;
}
@@ -4124,6 +4245,8 @@ class AccountManager {
quantityInput.max = String(metrics.endingBalance);
this.refreshBorrowAssetUserOptions('');
this.refreshBorrowAssetProjectOptions(String(asset?.Project || '').trim());
noteInput.value = '';
if (borrowByInput) {
borrowByInput.value = this.getCurrentUserDisplayName();
@@ -4139,23 +4262,25 @@ class AccountManager {
async handleBorrowAssetSubmit(e) {
e.preventDefault();
if (!this.ensureAssetManagePermission('muon tai san')) {
if (!this.ensureAssetManagePermission('xuat tai san')) {
return;
}
const assetIdInput = document.getElementById('borrowAssetIdInput');
const borrowerInput = document.getElementById('borrowAssetUserInput');
const projectInput = document.getElementById('borrowAssetProjectInput');
const quantityInput = document.getElementById('borrowQuantityInput');
const noteInput = document.getElementById('borrowAssetNoteInput');
const selectedAssetId = Number(assetIdInput?.value || this.pendingBorrowAssetId);
if (!Number.isFinite(selectedAssetId) || selectedAssetId <= 0) {
this.notifyFailure('Không xác định được tài sản cần mượn.');
this.notifyFailure('Không xác định được tài sản cần xuất.');
return;
}
const asset = this.assets.find(item => Number(item?.AssetId) === selectedAssetId);
if (!asset) {
this.notifyFailure('Không tìm thấy tài sản cần mượn.');
this.notifyFailure('Không tìm thấy tài sản cần xuất.');
return;
}
@@ -4165,50 +4290,61 @@ class AccountManager {
return;
}
const borrowQuantity = this.parseNonNegativeInteger(quantityInput?.value ?? 0, 0);
if (borrowQuantity <= 0) {
this.notifyWarning('Số lượng mượn phải lớn hơn 0.');
const projectName = String(projectInput?.value || '').trim();
if (!projectName) {
this.notifyWarning('Vui lòng chọn dự án cần xuất.');
return;
}
const borrowQuantity = this.parseNonNegativeInteger(quantityInput?.value ?? 0, 0);
if (borrowQuantity <= 0) {
this.notifyWarning('Số lượng xuất phải lớn hơn 0.');
return;
}
const exportNote = String(noteInput?.value || '').trim();
const currentMetrics = this.buildAssetQuantityMetrics(asset);
if (currentMetrics.endingBalance <= 0) {
this.notifyWarning('Tài sản đã hết tồn cuối kỳ, không thể mượn thêm.');
this.notifyWarning('Tài sản đã hết tồn cuối kỳ, không thể xuất thêm.');
return;
}
if (borrowQuantity > currentMetrics.endingBalance) {
this.notifyWarning(`Số lượng mượn (${borrowQuantity}) vượt quá tồn cuối kỳ (${currentMetrics.endingBalance}).`);
return;
}
const updatedEntries = this.mergeBorrowerEntries(currentMetrics.borrowerEntries, borrowerName, borrowQuantity);
const payload = this.buildAssetPayloadFromAsset(asset, updatedEntries);
if (!payload) {
this.notifyFailure('Không tạo được dữ liệu mượn tài sản.');
this.notifyWarning(`Số lượng xuất (${borrowQuantity}) vượt quá tồn cuối kỳ (${currentMetrics.endingBalance}).`);
return;
}
try {
const response = await fetch(`${this.apiBase}/assets/${selectedAssetId}`, {
method: 'PUT',
const response = await fetch(`${this.apiBase}/assets/${selectedAssetId}/export`, {
method: 'POST',
headers: this.getAuthHeaders(true),
body: JSON.stringify(payload)
body: JSON.stringify({
quantity: borrowQuantity,
borrowerName,
custodianName: borrowerName,
projectName,
note: exportNote
})
});
const data = await response.json();
if (!response.ok || !data.success) {
this.notifyFailure(data.message || 'Mượn tài sản thất bại');
this.notifyFailure(data.message || 'Xuất tài sản thất bại');
return;
}
this.pendingBorrowAssetId = undefined;
this.notifySuccess('Mượn tài sản thành công');
this.notifySuccess('Xuất tài sản thành công');
this.closeModals();
await this.refreshAssetsUI();
const exportHistoryModal = document.getElementById('assetExportHistoryModal');
if (exportHistoryModal?.classList.contains('open')) {
await this.fetchAssetExportHistories();
this.renderAssetExportHistoryModal();
}
} catch (err) {
console.error(err);
this.notifyFailure('Mượn tài sản thất bại');
this.notifyFailure('Xuất tài sản thất bại');
}
}
@@ -5622,6 +5758,7 @@ class AccountManager {
const importAssetBtn = document.getElementById('importAssetBtn');
const assetImportInput = document.getElementById('assetImportInput');
const exportAssetBtn = document.getElementById('exportAssetBtn');
const openAssetExportHistoryBtn = document.getElementById('openAssetExportHistoryBtn');
if (importAssetBtn && assetImportInput && !importAssetBtn.dataset.boundClick) {
importAssetBtn.addEventListener('click', () => {
@@ -5642,6 +5779,11 @@ class AccountManager {
exportAssetBtn.addEventListener('click', () => this.exportAssetsToExcel());
exportAssetBtn.dataset.boundClick = 'true';
}
if (openAssetExportHistoryBtn && !openAssetExportHistoryBtn.dataset.boundClick) {
openAssetExportHistoryBtn.addEventListener('click', () => this.openAssetExportHistoryModal());
openAssetExportHistoryBtn.dataset.boundClick = 'true';
}
}
setupFilters() {
@@ -6835,6 +6977,13 @@ function closeBorrowAssetModal() {
}
}
function closeAssetExportHistoryModal() {
const modal = document.getElementById('assetExportHistoryModal');
if (modal) {
modal.classList.remove('open');
}
}
function closeAssetBorrowRequestModal() {
const modal = document.getElementById('assetBorrowRequestModal');
const dropdown = document.getElementById('assetBorrowProductDropdown');

View File

@@ -304,11 +304,11 @@
</div>
</div>
<!-- Borrow Asset Modal -->
<!-- Export Asset Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="borrowAssetModal">
<div class="modal-content w-full max-w-lg 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 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Mượn tài sản</h3>
<h3 class="text-base font-extrabold text-slate-900">Xuất tài sản</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" onclick="closeBorrowAssetModal()">
<span class="material-symbols-outlined">close</span>
</button>
@@ -325,7 +325,7 @@
<input type="number" id="borrowCurrentEndingInput" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50" readonly>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Số lượng mượn</label>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Số lượng xuất</label>
<input type="number" id="borrowQuantityInput" min="1" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" value="1" required>
</div>
<div>
@@ -334,6 +334,12 @@
<option value="">-- Chọn người mượn --</option>
</select>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Xuất cho dự án</label>
<select id="borrowAssetProjectInput" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required>
<option value="">-- Chọn dự án --</option>
</select>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Người xuất</label>
<input type="text" id="borrowByInput" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50" readonly>
@@ -342,15 +348,55 @@
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Role người thao tác</label>
<input type="text" id="borrowRoleInput" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50" readonly>
</div>
<div class="md:col-span-2">
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Ghi chú xuất</label>
<textarea id="borrowAssetNoteInput" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 h-20 resize-none" placeholder="Nhập ghi chú (nếu có)"></textarea>
</div>
</div>
<div class="flex gap-3 pt-2">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeBorrowAssetModal()">Hủy</button>
<button type="submit" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold">Xác nhận mượn</button>
<button type="submit" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold">Xác nhận xuất</button>
</div>
</form>
</div>
</div>
<!-- Asset Export History Modal -->
<div class="modal-backdrop fixed inset-0 z-[110] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="assetExportHistoryModal" style="z-index: 125;">
<div class="modal-content w-full max-w-6xl bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4 flex flex-col" style="max-height: calc(100vh - 2rem);">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Lịch sử xuất tài sản</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" onclick="closeAssetExportHistoryModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<div class="p-6 pt-4 overflow-auto">
<div class="rounded-xl border border-slate-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse" style="min-width: 1100px;">
<thead class="bg-slate-50 border-b border-slate-200">
<tr>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Ngày giờ</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Tài sản</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Số lượng</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Dự án</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Người phụ trách</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Người xuất</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Ghi chú</th>
</tr>
</thead>
<tbody id="assetExportHistoryTableBody" class="divide-y divide-slate-100">
<tr>
<td colspan="7" class="px-4 py-8 text-sm text-center text-slate-500">Chưa có dữ liệu lịch sử xuất.</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Asset Borrow Request Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="assetBorrowRequestModal">
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-visible m-4">