Edwin d5ac2a21b3 Fix JavaScript tree reload after CRUD operations
- 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.
2025-11-12 16:29:35 +00:00

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');
});
}
}
});