Refactor API endpoints and update invoice generation

- Updated API calls in equipment.php, equipment_manage.php, and equipments_mass_update.php to use v2 endpoints.
- Changed payload decoding from decode_payload to json_decode for consistency.
- Enhanced invoice generation in factuur.php and webhook files to use a new email template and PDF structure.
- Added new email and PDF templates for invoices to improve formatting and readability.
- Improved marketing folder handling in marketing.php with better payload management.
- Updated CSS for marketing to enhance UI interactions.
- Added JavaScript checks for browser compatibility in softwaretool.php.
- Adjusted user permissions in settingsprofiles.php to reflect new features.
This commit is contained in:
“VeLiTi”
2026-01-14 13:31:22 +01:00
parent a0e1d386ad
commit 7aebb762d3
19 changed files with 1141 additions and 631 deletions

View File

@@ -17,6 +17,13 @@ class MarketingFileManager {
this.folders = []; // Store folders data
this.loadRequestId = 0; // Track the latest load request
// Get permissions from PHP
this.permissions = window.marketingPermissions || {
canCreate: 0,
canUpdate: 0,
canDelete: 0
};
this.init();
}
@@ -105,6 +112,18 @@ class MarketingFileManager {
document.getElementById('saveEdit')?.addEventListener('click', () => {
this.saveEdit();
});
// Edit folder
document.getElementById('saveEditFolder')?.addEventListener('click', () => {
this.saveEditFolder();
});
// Delete folder
document.getElementById('deleteFolder')?.addEventListener('click', () => {
if (this.selectedFolder) {
this.deleteFolder(this.selectedFolder);
}
});
}
bindUploadEvents() {
@@ -317,15 +336,29 @@ class MarketingFileManager {
const hasChildren = folder.children && folder.children.length > 0;
const expandIcon = hasChildren ? '<i class="fa fa-chevron-right expand-icon"></i>' : '';
// Only show edit button if user has update permission
const editButton = this.permissions.canUpdate === 1
? `<button class="folder-edit-btn" title="Edit folder" data-folder-id="${folder.id}">
<i class="fa fa-edit"></i>
</button>`
: '';
folderItem.innerHTML = `
${expandIcon}
<i class="fa fa-folder"></i>
<span class="folder-name">${this.escapeHtml(folder.folder_name)}</span>
<span class="file-count">(${folder.file_count})</span>
${editButton}
`;
folderItem.addEventListener('click', () => {
this.selectFolder(folder.id);
folderItem.addEventListener('click', (e) => {
// Don't select folder if edit button was clicked
if (e.target.closest('.folder-edit-btn')) {
e.stopPropagation();
this.editFolder(folder);
} else {
this.selectFolder(folder.id);
}
});
container.appendChild(folderItem);
@@ -929,13 +962,20 @@ class MarketingFileManager {
const folderId = document.getElementById('editFolder').value;
const tags = document.getElementById('editTags').value.trim();
// Prepare update data
const updateData = {
file_id: this.selectedFile.id,
title: title || null,
folder_id: folderId || null,
tags: tags ? tags.split(',').map(tag => tag.trim()).filter(tag => tag) : []
};
// Compare with original values to detect changes
const originalTitle = this.selectedFile.title || '';
const originalFolderId = this.selectedFile.folder_id || '';
const originalTags = (this.selectedFile.tags || []).join(', ');
const hasChanges = title !== originalTitle ||
folderId !== originalFolderId ||
tags !== originalTags;
if (!hasChanges) {
this.showToast('No changes detected', 'info');
this.closeModal(document.getElementById('editModal'));
return;
}
// Show loading state
const saveBtn = document.getElementById('saveEdit');
@@ -943,22 +983,35 @@ class MarketingFileManager {
saveBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Saving...';
saveBtn.disabled = true;
// Prepare FormData with only changed fields
const formData = new FormData();
formData.append('file_id', this.selectedFile.id);
if (title !== originalTitle) {
formData.append('title', title);
}
if (folderId !== originalFolderId) {
formData.append('folder_id', folderId);
}
if (tags !== originalTags) {
formData.append('tags', tags);
}
// Send update request
fetch('./marketing.php?action=update_file', {
fetch('index.php?page=marketing&action=marketing_update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updateData)
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.showToast('File updated successfully!', 'success');
this.closeModal(document.getElementById('editModal'));
this.loadFiles(); // Reload to show changes
// Reload window to reflect changes
setTimeout(() => {
window.location.reload();
}, 500);
} else {
throw new Error(data.message || 'Failed to update file');
throw new Error(data.error || data.message || 'Failed to update file');
}
})
.catch(error => {
@@ -994,6 +1047,193 @@ class MarketingFileManager {
this.renderUploadQueue();
document.getElementById('startUpload').disabled = this.uploadQueue.length === 0;
}
// Edit folder functionality
editFolder(folder) {
this.selectedFolder = folder;
this.showEditFolderModal();
this.populateEditFolderModal(folder);
}
showEditFolderModal() {
const modal = document.getElementById('editFolderModal');
if (modal) {
this.showModal(modal);
}
}
populateEditFolderModal(folder) {
// Populate folder name
const nameInput = document.getElementById('editFolderName');
if (nameInput) {
nameInput.value = folder.folder_name || '';
}
// Populate parent folder select
const parentSelect = document.getElementById('editParentFolder');
if (parentSelect) {
parentSelect.innerHTML = '<option value="">Root Folder</option>';
this.addFolderOptionsExcluding(parentSelect, this.folders, folder.id);
// Select current parent
if (folder.parent_id) {
parentSelect.value = folder.parent_id;
}
}
// Populate description
const descInput = document.getElementById('editFolderDescription');
if (descInput) {
descInput.value = folder.description || '';
}
}
addFolderOptionsExcluding(select, folders, excludeId, level = 0) {
// Add folders but exclude the current folder and its children
folders.forEach(folder => {
if (folder.id !== excludeId && !this.isFolderDescendant(folder, excludeId)) {
const option = document.createElement('option');
option.value = folder.id;
option.textContent = ' '.repeat(level) + folder.folder_name;
select.appendChild(option);
if (folder.children && folder.children.length > 0) {
this.addFolderOptionsExcluding(select, folder.children, excludeId, level + 1);
}
}
});
}
isFolderDescendant(folder, ancestorId) {
// Check if folder is a descendant of ancestorId
if (folder.id === ancestorId) return true;
if (folder.children) {
for (let child of folder.children) {
if (this.isFolderDescendant(child, ancestorId)) {
return true;
}
}
}
return false;
}
saveEditFolder() {
if (!this.selectedFolder) return;
const folderName = document.getElementById('editFolderName').value.trim();
const parentId = document.getElementById('editParentFolder').value;
const description = document.getElementById('editFolderDescription').value.trim();
// Compare with original values
const originalName = this.selectedFolder.folder_name || '';
const originalParentId = this.selectedFolder.parent_id || '';
const originalDescription = this.selectedFolder.description || '';
const hasChanges = folderName !== originalName ||
parentId !== originalParentId ||
description !== originalDescription;
if (!hasChanges) {
this.showToast('No changes detected', 'info');
this.closeModal(document.getElementById('editFolderModal'));
return;
}
if (!folderName) {
this.showToast('Folder name is required', 'error');
return;
}
// Show loading state
const saveBtn = document.getElementById('saveEditFolder');
const originalText = saveBtn.innerHTML;
saveBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Saving...';
saveBtn.disabled = true;
// Prepare FormData
const formData = new FormData();
formData.append('id', this.selectedFolder.id);
if (folderName !== originalName) {
formData.append('folder_name', folderName);
}
if (parentId !== originalParentId) {
formData.append('parent_id', parentId);
}
if (description !== originalDescription) {
formData.append('description', description);
}
// Send update request
fetch('index.php?page=marketing&action=marketing_folders', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.showToast('Folder updated successfully!', 'success');
// Reload window to reflect changes
setTimeout(() => {
window.location.reload();
}, 500);
} else {
throw new Error(data.error || data.message || 'Failed to update folder');
}
})
.catch(error => {
console.error('Update folder error:', error);
this.showToast('Error updating folder: ' + error.message, 'error');
})
.finally(() => {
// Restore button state
saveBtn.innerHTML = originalText;
saveBtn.disabled = false;
});
}
async deleteFolder(folder) {
if (!confirm(`Are you sure you want to delete the folder "${folder.folder_name}"? This action cannot be undone.`)) {
return;
}
try {
const formData = new FormData();
formData.append('id', folder.id);
formData.append('delete', 'true');
const response = await fetch('index.php?page=marketing&action=marketing_folders', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const text = await response.text();
if (!text || text.trim() === '') {
throw new Error('Empty response from server');
}
const data = JSON.parse(text);
if (data && (data.success || !data.error)) {
this.closeModal(document.getElementById('editFolderModal'));
this.showToast('Folder deleted successfully!', 'success');
// Reload window to reflect changes
setTimeout(() => {
window.location.reload();
}, 500);
} else if (data.error) {
throw new Error(data.error);
} else {
throw new Error('Unexpected response format');
}
} catch (error) {
this.showToast(error.message || 'Error deleting folder', 'error');
}
}
}
// Initialize when DOM is ready