460 lines
17 KiB
JavaScript
Executable File
460 lines
17 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) {
|
|
// Create a mock button to reuse handleEditItem
|
|
const mockBtn = { getAttribute: (attr) => attr === 'data-id' ? id : null };
|
|
handleEditItem.call(mockBtn);
|
|
};
|
|
|
|
// Function to toggle category children
|
|
window.toggleCategory = function(span) {
|
|
const li = span.parentElement;
|
|
const ul = li.querySelector('ul'); // First ul is children
|
|
if (ul) {
|
|
ul.style.display = ul.style.display === 'none' ? 'block' : 'none';
|
|
}
|
|
};
|
|
|
|
// --- Helper Functions ---
|
|
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 to fetch content and update the main view
|
|
function fetchContent(path, pushState = true) {
|
|
showLoading();
|
|
|
|
// Determine the API endpoint based on the path
|
|
let apiPath = '';
|
|
let pageTitle = '';
|
|
let routePath = path;
|
|
|
|
if (path === '/' || path === '/overview') {
|
|
apiPath = '/api/items';
|
|
pageTitle = 'Overview';
|
|
routePath = '/';
|
|
} else if (path === '/categories') {
|
|
apiPath = '/api/categories';
|
|
pageTitle = 'Categories';
|
|
} else if (path === '/parts') {
|
|
apiPath = '/api/parts';
|
|
pageTitle = 'Parts';
|
|
} else {
|
|
// Fallback
|
|
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);
|
|
// Re-initialize any scripts specific to the loaded content
|
|
initPageSpecificScripts();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching content:', error);
|
|
mainContent.innerHTML = `<div class="alert alert-danger">${error.message}</div>`;
|
|
});
|
|
}
|
|
|
|
// Update active class on sidebar links
|
|
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');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle browser back/forward buttons
|
|
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 {
|
|
// If no state, force a full load of the current path
|
|
fetchContent(window.location.pathname, false);
|
|
}
|
|
};
|
|
|
|
// --- Event Listeners ---
|
|
|
|
// Navigation links (Navbar and Sidebar)
|
|
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);
|
|
}
|
|
});
|
|
|
|
// Initial page load
|
|
const initialPath = window.location.pathname === '/' ? '/items' : window.location.pathname;
|
|
fetchContent(initialPath, false);
|
|
|
|
// Page-specific scripts (e.g., form submissions)
|
|
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 saveEditBtn = document.getElementById('saveEditCategoryBtn');
|
|
if (saveEditBtn) {
|
|
saveEditBtn.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);
|
|
});
|
|
}
|
|
|
|
// 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); // Go to overview to see the new part
|
|
} else {
|
|
alert(data.error || 'Error adding part');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error adding part');
|
|
});
|
|
}
|
|
|
|
function handleAddCategoryFromItem() {
|
|
const name = document.getElementById('new_category_name').value.trim();
|
|
if (!name) return;
|
|
const formData = new FormData();
|
|
formData.append('category_name', name);
|
|
fetch('/api/categories', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const select = document.getElementById('category_id');
|
|
const option = new Option(name, data.id);
|
|
select.appendChild(option);
|
|
select.value = data.id;
|
|
document.getElementById('new_category_name').value = '';
|
|
document.getElementById('addCategoryForm').classList.add('hidden');
|
|
} else {
|
|
alert(data.error || 'Error adding category');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error adding category');
|
|
});
|
|
}
|
|
|
|
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(); // Re-init for new buttons
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
});
|
|
}
|
|
|
|
function handleEditItem() {
|
|
const id = this.getAttribute('data-id');
|
|
// Fetch current item data
|
|
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>';
|
|
}
|
|
// Populate category select
|
|
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'));
|
|
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();
|
|
} 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);
|
|
} 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) {
|
|
bootstrap.Modal.getInstance(document.getElementById('editItemModal')).hide();
|
|
fetchContent('/', false); // Reload overview
|
|
} else {
|
|
alert(data.error || 'Error updating part');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error updating part');
|
|
});
|
|
}
|
|
|
|
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) {
|
|
bootstrap.Modal.getInstance(document.getElementById('editCategoryModal')).hide();
|
|
fetchContent('/categories', false);
|
|
} else {
|
|
alert(data.error || 'Error updating category');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error updating category');
|
|
});
|
|
}
|
|
|
|
function handleEditCategory() {
|
|
const id = this.getAttribute('data-id');
|
|
// Fetch current category data
|
|
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;
|
|
// Populate parent select, excluding current category
|
|
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'));
|
|
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();
|
|
} else {
|
|
alert(data.error || 'Error deleting category');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error deleting category');
|
|
});
|
|
}
|
|
}
|
|
});
|