- Add reloadTree() function to centralize tree updates - Fix handleSaveEditItem to reload tree after item updates - Fix handleAddItem to reload tree after adding items - Fix handleDeleteItem to reload tree after item deletion - Fix all category functions to reload tree after operations - Replace broken Bootstrap.Modal.getInstance() with direct DOM manipulation - Fix JavaScript syntax errors and missing braces - Ensure tree updates automatically without manual page reload Resolves issue where tree navigation was not updating after item modifications.
526 lines
20 KiB
JavaScript
Executable File
526 lines
20 KiB
JavaScript
Executable File
document.addEventListener('DOMContentLoaded', function() {
|
|
const mainContent = document.getElementById('main-content');
|
|
const appName = document.querySelector('.navbar-brand').textContent;
|
|
|
|
// Global function for editing item from tree
|
|
window.editItem = function(id) {
|
|
const mockBtn = { getAttribute: (attr) => attr === 'data-id' ? id : null };
|
|
handleEditItem.call(mockBtn);
|
|
};
|
|
|
|
// Function to toggle tree nodes
|
|
window.toggleNode = function(element) {
|
|
const li = element.closest('li');
|
|
const childUl = li.querySelector(':scope > ul');
|
|
if (childUl) {
|
|
const isHidden = childUl.style.display === 'none';
|
|
childUl.style.display = isHidden ? 'block' : 'none';
|
|
const icon = element.querySelector('i');
|
|
if (icon) {
|
|
if (isHidden) {
|
|
icon.className = 'bi bi-folder-fill text-warning';
|
|
} else {
|
|
icon.className = 'bi bi-folder text-warning';
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
function setPageTitle(title) {
|
|
document.title = `${appName} - ${title}`;
|
|
}
|
|
|
|
function showLoading() {
|
|
mainContent.innerHTML = '<div class="text-center p-5"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>';
|
|
}
|
|
|
|
function reloadTree() {
|
|
fetch('/api/tree')
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
const sidebarTree = document.getElementById('sidebar-tree');
|
|
if (sidebarTree) {
|
|
sidebarTree.innerHTML = html;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading tree:', error);
|
|
});
|
|
}
|
|
|
|
function fetchContent(path, pushState = true) {
|
|
console.log('fetchContent called with path:', path);
|
|
showLoading();
|
|
|
|
let apiPath = '';
|
|
let pageTitle = '';
|
|
let routePath = path;
|
|
|
|
if (path === '/' || path === '/overview') {
|
|
apiPath = '/api/items';
|
|
pageTitle = 'Overview';
|
|
routePath = '/';
|
|
} else if (path.startsWith('/items')) {
|
|
apiPath = '/api/items';
|
|
pageTitle = 'Overview';
|
|
routePath = '/';
|
|
} else if (path.startsWith('/categories')) {
|
|
if (path.includes('?category=')) {
|
|
const categoryId = path.split('?category=')[1];
|
|
apiPath = '/api/categories?category=' + categoryId;
|
|
pageTitle = 'Category Details';
|
|
} else {
|
|
apiPath = '/api/categories';
|
|
pageTitle = 'Categories';
|
|
}
|
|
} else if (path.startsWith('/parts')) {
|
|
if (path.includes('?item=')) {
|
|
const itemId = path.split('?item=')[1];
|
|
apiPath = '/api/parts?item=' + itemId;
|
|
pageTitle = 'Part Details';
|
|
} else {
|
|
apiPath = '/api/parts';
|
|
pageTitle = 'Parts';
|
|
}
|
|
} else {
|
|
apiPath = '/api/items';
|
|
pageTitle = 'Overview';
|
|
routePath = '/';
|
|
}
|
|
|
|
fetch(apiPath)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
return response.text();
|
|
})
|
|
.then(html => {
|
|
mainContent.innerHTML = html;
|
|
if (pushState) {
|
|
history.pushState({ html: html, path: routePath }, '', routePath);
|
|
}
|
|
setPageTitle(pageTitle);
|
|
updateActiveLink(routePath);
|
|
initPageSpecificScripts();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching content:', error);
|
|
mainContent.innerHTML = `<div class="alert alert-danger">${error.message}</div>`;
|
|
});
|
|
}
|
|
|
|
function updateActiveLink(path) {
|
|
document.querySelectorAll('.nav-link[data-route]').forEach(link => {
|
|
link.classList.remove('active');
|
|
if (link.getAttribute('data-route') === path) {
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
window.onpopstate = function(event) {
|
|
if (event.state && event.state.html) {
|
|
mainContent.innerHTML = event.state.html;
|
|
setPageTitle(event.state.path.substring(1) || 'Home');
|
|
updateActiveLink(event.state.path);
|
|
initPageSpecificScripts();
|
|
} else {
|
|
fetchContent(window.location.pathname, false);
|
|
}
|
|
};
|
|
|
|
document.body.addEventListener('click', function(e) {
|
|
const link = e.target.closest('a[data-route]');
|
|
if (link) {
|
|
e.preventDefault();
|
|
const route = link.getAttribute('data-route');
|
|
fetchContent(route);
|
|
}
|
|
});
|
|
|
|
// Load category tree first
|
|
fetch('/api/tree')
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
const sidebarTree = document.getElementById('sidebar-tree');
|
|
if (sidebarTree) {
|
|
sidebarTree.innerHTML = html;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading tree:', error);
|
|
});
|
|
|
|
// Initial page load
|
|
const initialPath = window.location.pathname === '/' ? '/items' : window.location.pathname;
|
|
fetchContent(initialPath, false);
|
|
|
|
function initPageSpecificScripts() {
|
|
// Overview page: filter form
|
|
const filterForm = document.getElementById('filterForm');
|
|
if (filterForm) {
|
|
filterForm.addEventListener('submit', handleFilter);
|
|
}
|
|
|
|
// Overview page: edit/delete buttons
|
|
document.querySelectorAll('.edit-btn').forEach(btn => {
|
|
btn.addEventListener('click', handleEditItem);
|
|
});
|
|
document.querySelectorAll('.delete-btn').forEach(btn => {
|
|
btn.addEventListener('click', handleDeleteItem);
|
|
});
|
|
|
|
// Parts page: add item form
|
|
const addItemForm = document.getElementById('addItemForm');
|
|
if (addItemForm) {
|
|
addItemForm.addEventListener('submit', handleAddItem);
|
|
}
|
|
|
|
// Categories page: add category form
|
|
const addCategoryForm = document.getElementById('addCategoryForm');
|
|
if (addCategoryForm) {
|
|
addCategoryForm.addEventListener('submit', handleAddCategory);
|
|
}
|
|
|
|
// Overview page: save edit item
|
|
const saveEditItemBtn = document.getElementById('saveEditItemBtn');
|
|
if (saveEditItemBtn) {
|
|
saveEditItemBtn.addEventListener('click', handleSaveEditItem);
|
|
}
|
|
|
|
// Categories page: save edit category
|
|
const saveEditCategoryBtn = document.getElementById('saveEditCategoryBtn');
|
|
if (saveEditCategoryBtn) {
|
|
saveEditCategoryBtn.addEventListener('click', handleSaveEditCategory);
|
|
}
|
|
|
|
// Categories page: edit/delete buttons
|
|
document.querySelectorAll('.edit-category-btn').forEach(btn => {
|
|
btn.addEventListener('click', handleEditCategory);
|
|
});
|
|
document.querySelectorAll('.delete-category-btn').forEach(btn => {
|
|
btn.addEventListener('click', handleDeleteCategory);
|
|
});
|
|
|
|
// Item edit page: delete button
|
|
const deleteItemBtn = document.getElementById('deleteItemBtn');
|
|
if (deleteItemBtn) {
|
|
deleteItemBtn.addEventListener('click', function() {
|
|
const itemId = document.getElementById('edit_item_id').value;
|
|
if (confirm(window.translations.deletePartConfirm)) {
|
|
fetch('/api/items/' + itemId, {
|
|
method: 'DELETE'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
fetchContent('/', false);
|
|
reloadTree(); // Reload tree after deletion
|
|
} else {
|
|
alert(data.error || 'Error deleting item');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error deleting item');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Handler functions
|
|
function handleAddItem(e) {
|
|
e.preventDefault();
|
|
const formData = new FormData(this);
|
|
fetch('/api/items', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
fetchContent('/', false);
|
|
reloadTree(); // Reload tree after adding
|
|
} else {
|
|
alert(data.error || 'Error adding part');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error adding part');
|
|
});
|
|
}
|
|
|
|
function handleFilter(e) {
|
|
e.preventDefault();
|
|
const search = document.getElementById('search').value;
|
|
const categoryId = document.getElementById('category_filter').value;
|
|
let url = '/api/items?';
|
|
if (search) url += 'search=' + encodeURIComponent(search) + '&';
|
|
if (categoryId) url += 'category_id=' + categoryId;
|
|
fetch(url)
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
document.getElementById('itemsList').outerHTML = html.match(/<ul[^>]*id="itemsList"[^>]*>[\s\S]*?<\/ul>/)[0];
|
|
initPageSpecificScripts();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
});
|
|
}
|
|
|
|
function handleEditItem() {
|
|
const id = this.getAttribute('data-id');
|
|
fetch('/api/items/' + id)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data) {
|
|
document.getElementById('edit_item_id').value = data.id;
|
|
document.getElementById('edit_item_name').value = data.name;
|
|
document.getElementById('edit_item_description').value = data.description || '';
|
|
document.getElementById('edit_location').value = data.location || '';
|
|
const currentImageDiv = document.getElementById('current_image');
|
|
if (data.image) {
|
|
currentImageDiv.innerHTML = '<p>Huidige afbeelding:</p><img src="' + data.image + '" style="max-width: 100px;">';
|
|
} else {
|
|
currentImageDiv.innerHTML = '<p>Geen afbeelding</p>';
|
|
}
|
|
const categorySelect = document.getElementById('edit_item_category_id');
|
|
categorySelect.innerHTML = '<option value="">-- Select Category --</option>';
|
|
fetch('/api/categories/list')
|
|
.then(resp => resp.json())
|
|
.then(categories => {
|
|
categories.forEach(cat => {
|
|
const option = document.createElement('option');
|
|
option.value = cat.id;
|
|
option.textContent = cat.path;
|
|
categorySelect.appendChild(option);
|
|
});
|
|
categorySelect.value = data.category_id || '';
|
|
const modal = new bootstrap.Modal(document.getElementById('editItemModal'));
|
|
const saveEditItemBtn = document.getElementById('saveEditItemBtn');
|
|
if (saveEditItemBtn) {
|
|
saveEditItemBtn.addEventListener('click', handleSaveEditItem);
|
|
}
|
|
modal.show();
|
|
});
|
|
} else {
|
|
alert('Part not found');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error fetching part');
|
|
});
|
|
}
|
|
|
|
function handleDeleteItem() {
|
|
const id = this.getAttribute('data-id');
|
|
if (confirm(window.translations.deletePartConfirm)) {
|
|
fetch('/api/items/' + id, {
|
|
method: 'DELETE'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
this.closest('li').remove();
|
|
reloadTree(); // Reload tree after deletion
|
|
} else {
|
|
alert(data.error || 'Error deleting part');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error deleting part');
|
|
});
|
|
}
|
|
}
|
|
|
|
function handleAddCategory() {
|
|
const formData = new FormData(this);
|
|
fetch('/api/categories', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
fetchContent('/categories', false);
|
|
reloadTree(); // Reload tree after adding
|
|
} else {
|
|
alert(data.error || 'Error adding category');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error adding category');
|
|
});
|
|
}
|
|
|
|
function handleSaveEditItem() {
|
|
const id = document.getElementById('edit_item_id').value;
|
|
const name = document.getElementById('edit_item_name').value.trim();
|
|
const description = document.getElementById('edit_item_description').value;
|
|
const categoryId = document.getElementById('edit_item_category_id').value;
|
|
const location = document.getElementById('edit_location').value.trim();
|
|
const imageFile = document.getElementById('edit_image').files[0];
|
|
|
|
if (!name) {
|
|
alert('Part name is required');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('item_name', name);
|
|
formData.append('item_description', description);
|
|
formData.append('category_id', categoryId || '');
|
|
formData.append('location', location);
|
|
if (imageFile) {
|
|
formData.append('image', imageFile);
|
|
}
|
|
|
|
fetch('/api/items/' + id, {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Hide modal
|
|
const modalElement = document.getElementById('editItemModal');
|
|
if (modalElement) {
|
|
modalElement.classList.remove('show', 'showing');
|
|
modalElement.style.display = 'none';
|
|
modalElement.setAttribute('aria-hidden', 'true');
|
|
document.body.classList.remove('modal-open');
|
|
const backdrops = document.querySelectorAll(' .modal-backdrop');
|
|
backdrops.forEach(backdrop => backdrop.remove());
|
|
}
|
|
fetchContent('/', false);
|
|
reloadTree(); // Reload tree after updating
|
|
} else {
|
|
alert(data.error || 'Error updating part');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Update error:', error);
|
|
alert('Error updating part: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function handleSaveEditCategory() {
|
|
const id = document.getElementById('edit_category_id').value;
|
|
const name = document.getElementById('edit_category_name').value.trim();
|
|
const parentId = document.getElementById('edit_parent_category_id').value;
|
|
|
|
if (!name) {
|
|
alert('Category name is required');
|
|
return;
|
|
}
|
|
|
|
const data = {
|
|
category_name: name,
|
|
parent_category_id: parentId || null
|
|
};
|
|
|
|
fetch('/api/categories/' + id, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Hide modal
|
|
const modalElement = document.getElementById('editCategoryModal');
|
|
if (modalElement) {
|
|
modalElement.classList.remove('show', 'showing');
|
|
modalElement.style.display = 'none';
|
|
modalElement.setAttribute('aria-hidden', 'true');
|
|
document.body.classList.remove('modal-open');
|
|
const backdrops = document.querySelectorAll(' .modal-backdrop');
|
|
backdrops.forEach(backdrop => backdrop.remove());
|
|
}
|
|
fetchContent('/categories', false);
|
|
reloadTree(); // Reload tree after updating
|
|
} else {
|
|
alert(data.error || 'Error updating category');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Update error:', error);
|
|
alert('Error updating category: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function handleEditCategory() {
|
|
const id = this.getAttribute('data-id');
|
|
fetch('/api/categories/' + id)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('HTTP ' + response.status + ': ' + response.statusText);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data && !data.error) {
|
|
document.getElementById('edit_category_id').value = data.id;
|
|
document.getElementById('edit_category_name').value = data.name;
|
|
const parentSelect = document.getElementById('edit_parent_category_id');
|
|
parentSelect.innerHTML = '<option value="">-- No Parent --</option>';
|
|
fetch('/api/categories/list')
|
|
.then(resp => resp.json())
|
|
.then(categories => {
|
|
categories.forEach(cat => {
|
|
if (cat.id != data.id) {
|
|
const option = document.createElement('option');
|
|
option.value = cat.id;
|
|
option.textContent = cat.path;
|
|
parentSelect.appendChild(option);
|
|
}
|
|
});
|
|
parentSelect.value = data.parent_id || '';
|
|
const modal = new bootstrap.Modal(document.getElementById('editCategoryModal'));
|
|
const saveEditCategoryBtn = document.getElementById('saveEditCategoryBtn');
|
|
if (saveEditCategoryBtn) {
|
|
saveEditCategoryBtn.addEventListener('click', handleSaveEditCategory);
|
|
}
|
|
modal.show();
|
|
});
|
|
} else {
|
|
alert(data.error || 'Category not found');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error fetching category: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function handleDeleteCategory() {
|
|
const id = this.getAttribute('data-id');
|
|
if (confirm(window.translations.deleteCategoryConfirm)) {
|
|
fetch('/api/categories/' + id, {
|
|
method: 'DELETE'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
this.closest('li').remove();
|
|
reloadTree(); // Reload tree after deletion
|
|
} else {
|
|
alert(data.error || 'Error deleting category');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error deleting category');
|
|
});
|
|
}
|
|
}
|
|
});
|