add dự án
This commit is contained in:
472
public/js/app.js
472
public/js/app.js
@@ -46,6 +46,8 @@ class AccountManager {
|
||||
this.assetBorrowProductLoading = false;
|
||||
this.assetDepartments = [];
|
||||
this.assetDepartmentSearchTerm = '';
|
||||
this.assetProjects = [];
|
||||
this.assetProjectSearchTerm = '';
|
||||
this.selectedAssetIds = new Set();
|
||||
this.mobileBreakpoint = 900;
|
||||
this.boundResizeHandler = null;
|
||||
@@ -56,6 +58,8 @@ class AccountManager {
|
||||
this.pendingBorrowAssetId = undefined;
|
||||
this.editingAssetDepartmentId = undefined;
|
||||
this.pendingDeleteAssetDepartmentId = undefined;
|
||||
this.editingAssetProjectId = undefined;
|
||||
this.pendingDeleteAssetProjectId = undefined;
|
||||
this.assetBorrowRequestType = 'borrow';
|
||||
this.pendingAssetRequestRejectId = undefined;
|
||||
this.assetBorrowAutoRefreshTimer = undefined;
|
||||
@@ -174,6 +178,7 @@ class AccountManager {
|
||||
await this.fetchAssets();
|
||||
await this.fetchAssetBorrows();
|
||||
await this.fetchAssetDepartments();
|
||||
await this.fetchAssetProjects();
|
||||
|
||||
if (this.canCurrentUserManageAssets()) {
|
||||
await this.fetchUsers();
|
||||
@@ -230,6 +235,10 @@ class AccountManager {
|
||||
mainContent.innerHTML = this.getAssetDepartmentsContent();
|
||||
this.setupAssetDepartmentListeners();
|
||||
this.setupAddButtonListeners();
|
||||
} else if (page === 'asset-projects') {
|
||||
mainContent.innerHTML = this.getAssetProjectsContent();
|
||||
this.setupAssetProjectListeners();
|
||||
this.setupAddButtonListeners();
|
||||
} else if (page === 'accounts') {
|
||||
mainContent.innerHTML = this.getAccountsContent();
|
||||
this.setupAccountRowListeners();
|
||||
@@ -784,6 +793,59 @@ class AccountManager {
|
||||
}
|
||||
}
|
||||
|
||||
getUniqueAssetProjectNames() {
|
||||
const rows = Array.isArray(this.assetProjects) ? this.assetProjects : [];
|
||||
const seen = new Set();
|
||||
return rows
|
||||
.map(item => String(item?.ProjectName || '').trim())
|
||||
.filter(name => {
|
||||
if (!name) return false;
|
||||
const key = name.toLowerCase();
|
||||
if (seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => a.localeCompare(b, 'vi', { sensitivity: 'base' }));
|
||||
}
|
||||
|
||||
refreshAssetProjectOptions(selectedValue = '') {
|
||||
const select = document.getElementById('assetProjectInput');
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
async fetchAssets() {
|
||||
try {
|
||||
const res = await fetch(`${this.apiBase}/assets`);
|
||||
@@ -838,6 +900,21 @@ class AccountManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fetchAssetProjects() {
|
||||
try {
|
||||
const res = await fetch(`${this.apiBase}/asset-projects`);
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
this.assetProjects = Array.isArray(data.data) ? data.data : [];
|
||||
this.refreshAssetProjectOptions(document.getElementById('assetProjectInput')?.value || '');
|
||||
} else {
|
||||
console.error('Load asset projects failed:', data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Fetch asset projects error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchRoles() {
|
||||
try {
|
||||
const res = await fetch(`${this.apiBase}/roles`);
|
||||
@@ -874,6 +951,7 @@ class AccountManager {
|
||||
this.setupAddButtonListeners();
|
||||
this.setupFilters();
|
||||
this.refreshAssetDepartmentOptions(document.getElementById('assetDepartmentInput')?.value || '');
|
||||
this.refreshAssetProjectOptions(document.getElementById('assetProjectInput')?.value || '');
|
||||
} catch (error) {
|
||||
console.error('Lỗi load modals:', error);
|
||||
}
|
||||
@@ -885,6 +963,7 @@ class AccountManager {
|
||||
const assetSearch = document.getElementById('assetSearch');
|
||||
const assetBorrowSearch = document.getElementById('assetBorrowSearch');
|
||||
const assetDepartmentSearch = document.getElementById('assetDepartmentSearch');
|
||||
const assetProjectSearch = document.getElementById('assetProjectSearch');
|
||||
|
||||
if (accountSearch && accountSearch.dataset.focused === 'true') {
|
||||
const pos = accountSearch.selectionStart || accountSearch.value.length;
|
||||
@@ -915,6 +994,12 @@ class AccountManager {
|
||||
assetDepartmentSearch.focus();
|
||||
assetDepartmentSearch.setSelectionRange(pos, pos);
|
||||
}
|
||||
|
||||
if (assetProjectSearch && assetProjectSearch.dataset.focused === 'true') {
|
||||
const pos = assetProjectSearch.selectionStart || assetProjectSearch.value.length;
|
||||
assetProjectSearch.focus();
|
||||
assetProjectSearch.setSelectionRange(pos, pos);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
@@ -981,6 +1066,7 @@ class AccountManager {
|
||||
assetForm.dataset.boundSubmit = 'true';
|
||||
}
|
||||
this.refreshAssetDepartmentOptions(document.getElementById('assetDepartmentInput')?.value || '');
|
||||
this.refreshAssetProjectOptions(document.getElementById('assetProjectInput')?.value || '');
|
||||
this.setupAssetStockListeners();
|
||||
this.setupAssetFormValidationListeners();
|
||||
}
|
||||
@@ -1043,6 +1129,23 @@ class AccountManager {
|
||||
btn.dataset.boundClick = 'true';
|
||||
});
|
||||
|
||||
const assetProjectForm = document.getElementById('assetProjectForm');
|
||||
if (assetProjectForm) {
|
||||
if (!assetProjectForm.dataset.boundSubmit) {
|
||||
assetProjectForm.addEventListener('submit', (e) => this.handleAssetProjectSubmit(e));
|
||||
assetProjectForm.dataset.boundSubmit = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('.confirm-delete-asset-project').forEach(btn => {
|
||||
if (btn.dataset.boundClick === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.addEventListener('click', () => this.confirmDeleteAssetProject());
|
||||
btn.dataset.boundClick = 'true';
|
||||
});
|
||||
|
||||
// Close when clicking backdrop outside modal content
|
||||
document.querySelectorAll('.modal-backdrop').forEach(backdrop => {
|
||||
backdrop.addEventListener('click', (evt) => {
|
||||
@@ -2160,6 +2263,351 @@ class AccountManager {
|
||||
}
|
||||
}
|
||||
|
||||
getFilteredAssetProjects() {
|
||||
const search = String(this.assetProjectSearchTerm || '').toLowerCase();
|
||||
const source = Array.isArray(this.assetProjects) ? this.assetProjects : [];
|
||||
|
||||
return source.filter(item => {
|
||||
if (!search) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const name = String(item?.ProjectName || '').toLowerCase();
|
||||
return name.includes(search);
|
||||
});
|
||||
}
|
||||
|
||||
buildAssetProjectsRowsHtml(projects = []) {
|
||||
const canManageAssets = this.canCurrentUserManageAssets();
|
||||
if (!projects.length) {
|
||||
return `
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-8 text-sm text-center text-slate-500">Chưa có dự án nào.</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
return projects.map((item, index) => {
|
||||
const projectId = Number(item?.ProjectId);
|
||||
const assetCount = Number(item?.AssetCount) || 0;
|
||||
const projectName = this.escapeHtml(item?.ProjectName || '-');
|
||||
|
||||
return `
|
||||
<tr class="hover:bg-slate-50/80 transition-colors">
|
||||
<td class="px-4 py-3 text-sm text-slate-600">${index + 1}</td>
|
||||
<td class="px-4 py-3 text-sm font-semibold text-slate-700">${projectName}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-600">${assetCount}</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<div class="inline-flex items-center gap-1.5">
|
||||
<button
|
||||
class="p-1.5 text-slate-400 transition-colors edit-asset-project ${canManageAssets ? 'hover:text-primary' : 'opacity-40 cursor-not-allowed'}"
|
||||
data-project-id="${projectId}"
|
||||
${canManageAssets ? '' : 'disabled'}
|
||||
title="${canManageAssets ? 'Sửa dự án' : 'Chỉ xem'}"
|
||||
>
|
||||
<span class="material-symbols-outlined text-lg">edit</span>
|
||||
</button>
|
||||
<button
|
||||
class="p-1.5 text-slate-400 transition-colors delete-asset-project ${canManageAssets ? 'hover:text-error' : 'opacity-40 cursor-not-allowed'}"
|
||||
data-project-id="${projectId}"
|
||||
${canManageAssets ? '' : 'disabled'}
|
||||
title="${canManageAssets ? 'Xóa dự án' : 'Chỉ xem'}"
|
||||
>
|
||||
<span class="material-symbols-outlined text-lg">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
getAssetProjectsContent() {
|
||||
const filteredProjects = this.getFilteredAssetProjects();
|
||||
const canManageAssets = this.canCurrentUserManageAssets();
|
||||
|
||||
return `
|
||||
<div class="asset-projects-page flex flex-col p-4 md:p-6 overflow-hidden h-full">
|
||||
<div class="page-header flex items-center justify-between gap-4 mb-5 shrink-0">
|
||||
<div>
|
||||
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight">Quản Lý Dự Án</h1>
|
||||
<p class="text-sm text-on-surface-variant">Thêm, sửa, xóa danh mục dự án sử dụng trong tài sản.</p>
|
||||
</div>
|
||||
<button
|
||||
id="addAssetProjectBtn"
|
||||
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">workspaces</span>
|
||||
Thêm dự án
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="page-filters flex items-center gap-3 mb-4 shrink-0">
|
||||
<div class="flex items-center gap-1.5 flex-1">
|
||||
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Tìm kiếm</span>
|
||||
<input
|
||||
id="assetProjectSearch"
|
||||
value="${this.escapeHtml(this.assetProjectSearchTerm)}"
|
||||
class="flex-1 bg-surface-container-low border-slate-200 rounded-md text-[11px] py-1 px-2 focus:ring-1 focus:ring-primary shadow-sm"
|
||||
placeholder="Nhập tên dự án..."
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden min-h-0">
|
||||
<div class="table-wrap overflow-y-auto overflow-x-auto flex-1">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead class="sticky top-0 z-10 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">STT</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">Sd tài sản</th>
|
||||
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500 text-right">Thao tác</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 asset-projects-table-body">
|
||||
${this.buildAssetProjectsRowsHtml(filteredProjects)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="px-4 py-2 border-t border-slate-200 bg-slate-50 text-xs text-slate-600">
|
||||
Tổng dự án: <span id="assetProjectCount">${filteredProjects.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderAssetProjectsTableBody() {
|
||||
const tbody = document.querySelector('.asset-projects-table-body');
|
||||
if (!tbody) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredProjects = this.getFilteredAssetProjects();
|
||||
tbody.innerHTML = this.buildAssetProjectsRowsHtml(filteredProjects);
|
||||
|
||||
const countElement = document.getElementById('assetProjectCount');
|
||||
if (countElement) {
|
||||
countElement.textContent = String(filteredProjects.length);
|
||||
}
|
||||
|
||||
this.setupAssetProjectActionListeners();
|
||||
}
|
||||
|
||||
setupAssetProjectActionListeners() {
|
||||
document.querySelectorAll('.edit-asset-project').forEach(btn => {
|
||||
if (btn.dataset.boundClick === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
const projectId = Number(btn.dataset.projectId);
|
||||
if (!Number.isFinite(projectId)) {
|
||||
return;
|
||||
}
|
||||
this.handleUpdateAssetProject(projectId);
|
||||
});
|
||||
|
||||
btn.dataset.boundClick = 'true';
|
||||
});
|
||||
|
||||
document.querySelectorAll('.delete-asset-project').forEach(btn => {
|
||||
if (btn.dataset.boundClick === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
const projectId = Number(btn.dataset.projectId);
|
||||
if (!Number.isFinite(projectId)) {
|
||||
return;
|
||||
}
|
||||
this.handleDeleteAssetProject(projectId);
|
||||
});
|
||||
|
||||
btn.dataset.boundClick = 'true';
|
||||
});
|
||||
}
|
||||
|
||||
setupAssetProjectListeners() {
|
||||
const searchInput = document.getElementById('assetProjectSearch');
|
||||
if (searchInput && searchInput.dataset.boundInput !== 'true') {
|
||||
searchInput.addEventListener('input', (event) => {
|
||||
this.assetProjectSearchTerm = String(event.target.value || '').trim();
|
||||
this.renderAssetProjectsTableBody();
|
||||
});
|
||||
searchInput.addEventListener('focus', () => {
|
||||
searchInput.dataset.focused = 'true';
|
||||
});
|
||||
searchInput.addEventListener('blur', () => {
|
||||
searchInput.dataset.focused = 'false';
|
||||
});
|
||||
searchInput.dataset.boundInput = 'true';
|
||||
}
|
||||
|
||||
this.setupAssetProjectActionListeners();
|
||||
}
|
||||
|
||||
async refreshAssetProjectsUI() {
|
||||
await this.fetchAssetProjects();
|
||||
if (this.currentPage === 'asset-projects') {
|
||||
this.renderAssetProjectsTableBody();
|
||||
}
|
||||
}
|
||||
|
||||
getAssetProjectById(projectId) {
|
||||
return this.assetProjects.find(item => Number(item?.ProjectId) === Number(projectId)) || null;
|
||||
}
|
||||
|
||||
openAssetProjectModal(project = null) {
|
||||
const modal = document.getElementById('assetProjectModal');
|
||||
const titleNode = document.getElementById('assetProjectModalTitle');
|
||||
const nameInput = document.getElementById('assetProjectNameInput');
|
||||
if (!modal || !nameInput) {
|
||||
this.notifyFailure('Không mở được biểu mẫu dự án');
|
||||
return;
|
||||
}
|
||||
|
||||
const editing = project && Number.isFinite(Number(project.ProjectId));
|
||||
this.editingAssetProjectId = editing ? Number(project.ProjectId) : undefined;
|
||||
|
||||
if (titleNode) {
|
||||
titleNode.textContent = editing ? 'Sửa dự án' : 'Thêm dự án';
|
||||
}
|
||||
nameInput.value = editing ? String(project.ProjectName || '') : '';
|
||||
modal.classList.add('open');
|
||||
nameInput.focus();
|
||||
nameInput.select();
|
||||
}
|
||||
|
||||
openDeleteAssetProjectModal(project) {
|
||||
const modal = document.getElementById('deleteAssetProjectModal');
|
||||
const nameNode = document.getElementById('deleteAssetProjectName');
|
||||
if (!modal) {
|
||||
this.notifyFailure('Không mở được hộp thoại xóa dự án');
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingDeleteAssetProjectId = Number(project?.ProjectId);
|
||||
if (nameNode) {
|
||||
nameNode.textContent = String(project?.ProjectName || '-');
|
||||
}
|
||||
modal.classList.add('open');
|
||||
}
|
||||
|
||||
async handleCreateAssetProject() {
|
||||
if (!this.ensureAssetManagePermission('thêm dự án')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openAssetProjectModal(null);
|
||||
}
|
||||
|
||||
async handleAssetProjectSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.ensureAssetManagePermission('thêm hoặc sửa dự án')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameInput = document.getElementById('assetProjectNameInput');
|
||||
const projectName = String(nameInput?.value || '').trim();
|
||||
if (!projectName) {
|
||||
this.notifyWarning('Tên dự án là bắt buộc');
|
||||
return;
|
||||
}
|
||||
|
||||
const isEdit = Number.isFinite(Number(this.editingAssetProjectId));
|
||||
const endpoint = isEdit
|
||||
? `${this.apiBase}/asset-projects/${this.editingAssetProjectId}`
|
||||
: `${this.apiBase}/asset-projects`;
|
||||
const method = isEdit ? 'PUT' : 'POST';
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method,
|
||||
headers: this.getAuthHeaders(true),
|
||||
body: JSON.stringify({ projectName })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
this.notifyFailure(data.message || 'Lưu dự án thất bại');
|
||||
return;
|
||||
}
|
||||
|
||||
this.editingAssetProjectId = undefined;
|
||||
closeAssetProjectModal();
|
||||
this.notifySuccess(isEdit ? 'Cập nhật dự án thành công' : 'Thêm dự án thành công');
|
||||
await this.refreshAssetProjectsUI();
|
||||
await this.refreshAssetsUI();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.notifyFailure('Lưu dự án thất bại');
|
||||
}
|
||||
}
|
||||
|
||||
async handleUpdateAssetProject(projectId) {
|
||||
if (!this.ensureAssetManagePermission('sửa dự án')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetProject = this.getAssetProjectById(projectId);
|
||||
if (!targetProject) {
|
||||
this.notifyWarning('Không tìm thấy dự án');
|
||||
return;
|
||||
}
|
||||
|
||||
this.openAssetProjectModal(targetProject);
|
||||
}
|
||||
|
||||
async handleDeleteAssetProject(projectId) {
|
||||
if (!this.ensureAssetManagePermission('xóa dự án')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetProject = this.getAssetProjectById(projectId);
|
||||
if (!targetProject) {
|
||||
this.notifyWarning('Không tìm thấy dự án');
|
||||
return;
|
||||
}
|
||||
|
||||
this.openDeleteAssetProjectModal(targetProject);
|
||||
}
|
||||
|
||||
async confirmDeleteAssetProject() {
|
||||
if (!this.ensureAssetManagePermission('xóa dự án')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Number.isFinite(Number(this.pendingDeleteAssetProjectId))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiBase}/asset-projects/${this.pendingDeleteAssetProjectId}`, {
|
||||
method: 'DELETE',
|
||||
headers: this.getAuthHeaders(false)
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
this.notifyFailure(data.message || 'Xóa dự án thất bại');
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingDeleteAssetProjectId = undefined;
|
||||
closeDeleteAssetProjectModal();
|
||||
this.notifySuccess('Xóa dự án thành công');
|
||||
await this.refreshAssetProjectsUI();
|
||||
await this.refreshAssetsUI();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.notifyFailure('Xóa dự án thất bại');
|
||||
}
|
||||
}
|
||||
|
||||
buildAssetBorrowRowHtml(item, rowNumber) {
|
||||
const assetName = item.AssetName || '-';
|
||||
const assetCode = item.AssetCode ? `<div class="text-[11px] text-slate-500">${this.escapeHtml(item.AssetCode)}</div>` : '';
|
||||
@@ -3351,7 +3799,7 @@ class AccountManager {
|
||||
document.getElementById('assetImportInPeriodInput').value = this.parseNonNegativeInteger(sourceAsset?.ImportInPeriod, 0);
|
||||
document.getElementById('assetUnitInput').value = sourceAsset?.Unit || '';
|
||||
this.refreshAssetDepartmentOptions(sourceAsset?.Department || '');
|
||||
document.getElementById('assetProjectInput').value = sourceAsset?.Project || '';
|
||||
this.refreshAssetProjectOptions(sourceAsset?.Project || '');
|
||||
document.getElementById('assetLocationInput').value = sourceAsset?.Location || '';
|
||||
this.refreshAssetCustodianOptions(sourceAsset?.Custodian || '');
|
||||
|
||||
@@ -3383,6 +3831,7 @@ class AccountManager {
|
||||
}
|
||||
|
||||
this.refreshAssetDepartmentOptions(document.getElementById('assetDepartmentInput')?.value || '');
|
||||
this.refreshAssetProjectOptions(document.getElementById('assetProjectInput')?.value || '');
|
||||
|
||||
if (!this.users.length) {
|
||||
this.fetchUsers();
|
||||
@@ -3796,6 +4245,7 @@ class AccountManager {
|
||||
async refreshAssetsUI() {
|
||||
await this.fetchAssets();
|
||||
await this.fetchAssetDepartments();
|
||||
await this.fetchAssetProjects();
|
||||
if (this.currentPage === 'assets') {
|
||||
this.renderView('assets');
|
||||
}
|
||||
@@ -5110,6 +5560,12 @@ class AccountManager {
|
||||
addAssetDepartmentBtn.dataset.boundClick = 'true';
|
||||
}
|
||||
|
||||
const addAssetProjectBtn = document.getElementById('addAssetProjectBtn');
|
||||
if (addAssetProjectBtn && !addAssetProjectBtn.dataset.boundClick) {
|
||||
addAssetProjectBtn.addEventListener('click', () => this.handleCreateAssetProject());
|
||||
addAssetProjectBtn.dataset.boundClick = 'true';
|
||||
}
|
||||
|
||||
const addAssetBorrowRequestBtn = document.getElementById('addAssetBorrowRequestBtn');
|
||||
if (addAssetBorrowRequestBtn && !addAssetBorrowRequestBtn.dataset.boundClick) {
|
||||
addAssetBorrowRequestBtn.addEventListener('click', () => this.openAssetBorrowRequestModal('borrow'));
|
||||
@@ -6406,6 +6862,20 @@ function closeDeleteAssetDepartmentModal() {
|
||||
}
|
||||
}
|
||||
|
||||
function closeAssetProjectModal() {
|
||||
const modal = document.getElementById('assetProjectModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('open');
|
||||
}
|
||||
}
|
||||
|
||||
function closeDeleteAssetProjectModal() {
|
||||
const modal = document.getElementById('deleteAssetProjectModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('open');
|
||||
}
|
||||
}
|
||||
|
||||
function closeUserModal() {
|
||||
const userModalContainer = document.getElementById('userModalContainer');
|
||||
if (userModalContainer) {
|
||||
|
||||
@@ -265,7 +265,9 @@
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Dự án</label>
|
||||
<input type="text" id="assetProjectInput" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" placeholder="AGV / SS demo">
|
||||
<select id="assetProjectInput" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3">
|
||||
<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">Vị trí</label>
|
||||
@@ -566,3 +568,42 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Asset Project Modal -->
|
||||
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="assetProjectModal">
|
||||
<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 flex items-center justify-between bg-slate-50">
|
||||
<h3 class="text-base font-extrabold text-slate-900" id="assetProjectModalTitle">Thêm dự án</h3>
|
||||
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" onclick="closeAssetProjectModal()">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="assetProjectForm" class="p-6 space-y-4">
|
||||
<div>
|
||||
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Tên dự án</label>
|
||||
<input type="text" id="assetProjectNameInput" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="Ví dụ: AGV">
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeAssetProjectModal()">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">Lưu</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Asset Project Modal -->
|
||||
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="deleteAssetProjectModal">
|
||||
<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óa dự án</h3>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<p class="text-sm text-slate-600 mb-6">Bạn có chắc muốn xóa dự án <strong id="deleteAssetProjectName">-</strong>? Các tài sản đang gắn dự án này sẽ được để trống dự án.</p>
|
||||
<div class="flex gap-3">
|
||||
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeDeleteAssetProjectModal()">Hủy</button>
|
||||
<button type="button" class="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-xs font-bold confirm-delete-asset-project">Xóa</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -248,6 +248,10 @@
|
||||
<span class="material-symbols-outlined">apartment</span>
|
||||
<span> Phòng Ban</span>
|
||||
</a>
|
||||
<a href="#asset-projects" data-nav="asset-projects" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer rounded-r-lg">
|
||||
<span class="material-symbols-outlined">workspaces</span>
|
||||
<span>Dự án</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user