Initial commit of the Asset Management System, including project structure, Docker configuration, database migrations, and core application files. Added user authentication, asset management features, and basic UI components.
This commit is contained in:
466
public/assets/js/app.js
Normal file
466
public/assets/js/app.js
Normal file
@@ -0,0 +1,466 @@
|
||||
/**
|
||||
* Asset Management System - JavaScript
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all components
|
||||
initAlerts();
|
||||
initDropdowns();
|
||||
initForms();
|
||||
initTables();
|
||||
initModals();
|
||||
initFileUploads();
|
||||
});
|
||||
|
||||
/**
|
||||
* Alert handling
|
||||
*/
|
||||
function initAlerts() {
|
||||
// Auto-hide alerts after 5 seconds
|
||||
const alerts = document.querySelectorAll('.alert');
|
||||
alerts.forEach(alert => {
|
||||
setTimeout(() => {
|
||||
if (alert.parentElement) {
|
||||
alert.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
alert.remove();
|
||||
}, 300);
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
// Close button functionality
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('alert-close')) {
|
||||
const alert = e.target.closest('.alert');
|
||||
if (alert) {
|
||||
alert.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
alert.remove();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Dropdown handling
|
||||
*/
|
||||
function initDropdowns() {
|
||||
// Close dropdowns when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('.nav-dropdown, .user-dropdown')) {
|
||||
const dropdowns = document.querySelectorAll('.nav-dropdown-menu, .user-dropdown-menu');
|
||||
dropdowns.forEach(dropdown => {
|
||||
dropdown.style.opacity = '0';
|
||||
dropdown.style.visibility = 'hidden';
|
||||
dropdown.style.transform = 'translateY(-10px)';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Form handling
|
||||
*/
|
||||
function initForms() {
|
||||
// Form validation
|
||||
const forms = document.querySelectorAll('form');
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const requiredFields = form.querySelectorAll('[required]');
|
||||
let isValid = true;
|
||||
|
||||
requiredFields.forEach(field => {
|
||||
if (!field.value.trim()) {
|
||||
isValid = false;
|
||||
field.classList.add('error');
|
||||
} else {
|
||||
field.classList.remove('error');
|
||||
}
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
e.preventDefault();
|
||||
showAlert('Bitte füllen Sie alle erforderlichen Felder aus.', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Real-time validation
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target.hasAttribute('required')) {
|
||||
if (e.target.value.trim()) {
|
||||
e.target.classList.remove('error');
|
||||
} else {
|
||||
e.target.classList.add('error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Table handling
|
||||
*/
|
||||
function initTables() {
|
||||
// Sortable tables
|
||||
const sortableHeaders = document.querySelectorAll('.table th[data-sort]');
|
||||
sortableHeaders.forEach(header => {
|
||||
header.addEventListener('click', function() {
|
||||
const table = this.closest('.table');
|
||||
const column = this.dataset.sort;
|
||||
const currentOrder = this.dataset.order || 'asc';
|
||||
const newOrder = currentOrder === 'asc' ? 'desc' : 'asc';
|
||||
|
||||
// Update URL with sort parameters
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('sort', column);
|
||||
url.searchParams.set('order', newOrder);
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
});
|
||||
|
||||
// Bulk actions
|
||||
const bulkCheckbox = document.getElementById('bulk-select-all');
|
||||
if (bulkCheckbox) {
|
||||
bulkCheckbox.addEventListener('change', function() {
|
||||
const checkboxes = document.querySelectorAll('.bulk-select');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = this.checked;
|
||||
});
|
||||
updateBulkActions();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target.classList.contains('bulk-select')) {
|
||||
updateBulkActions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bulk actions visibility
|
||||
*/
|
||||
function updateBulkActions() {
|
||||
const selectedItems = document.querySelectorAll('.bulk-select:checked');
|
||||
const bulkActions = document.querySelector('.bulk-actions');
|
||||
|
||||
if (bulkActions) {
|
||||
if (selectedItems.length > 0) {
|
||||
bulkActions.style.display = 'block';
|
||||
bulkActions.querySelector('.selected-count').textContent = selectedItems.length;
|
||||
} else {
|
||||
bulkActions.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modal handling
|
||||
*/
|
||||
function initModals() {
|
||||
// Open modal
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.hasAttribute('data-modal')) {
|
||||
const modalId = e.target.dataset.modal;
|
||||
const modal = document.getElementById(modalId);
|
||||
if (modal) {
|
||||
modal.style.display = 'block';
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('modal-close') || e.target.classList.contains('modal')) {
|
||||
const modal = e.target.closest('.modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal with Escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
const openModal = document.querySelector('.modal[style*="display: block"]');
|
||||
if (openModal) {
|
||||
openModal.style.display = 'none';
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* File upload handling
|
||||
*/
|
||||
function initFileUploads() {
|
||||
const fileInputs = document.querySelectorAll('input[type="file"]');
|
||||
fileInputs.forEach(input => {
|
||||
input.addEventListener('change', function() {
|
||||
const files = Array.from(this.files);
|
||||
const maxSize = this.dataset.maxSize || 52428800; // 50MB default
|
||||
const allowedTypes = this.dataset.allowedTypes ? this.dataset.allowedTypes.split(',') : [];
|
||||
|
||||
files.forEach(file => {
|
||||
// Check file size
|
||||
if (file.size > maxSize) {
|
||||
showAlert(`Datei "${file.name}" ist zu groß. Maximale Größe: ${formatBytes(maxSize)}`, 'error');
|
||||
this.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Check file type
|
||||
if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
|
||||
showAlert(`Dateityp "${file.type}" ist nicht erlaubt.`, 'error');
|
||||
this.value = '';
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search functionality
|
||||
*/
|
||||
function initSearch() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
if (searchInput) {
|
||||
let searchTimeout;
|
||||
|
||||
searchInput.addEventListener('input', function() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
const query = this.value.trim();
|
||||
if (query.length >= 2 || query.length === 0) {
|
||||
performSearch(query);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform search
|
||||
*/
|
||||
function performSearch(query) {
|
||||
const url = new URL(window.location);
|
||||
if (query) {
|
||||
url.searchParams.set('search', query);
|
||||
} else {
|
||||
url.searchParams.delete('search');
|
||||
}
|
||||
url.searchParams.delete('page'); // Reset to first page
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export functionality
|
||||
*/
|
||||
function exportData(format) {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('export', format);
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Print functionality
|
||||
*/
|
||||
function printPage() {
|
||||
window.print();
|
||||
}
|
||||
|
||||
/**
|
||||
* QR Code scanner
|
||||
*/
|
||||
function initQRScanner() {
|
||||
const scanButton = document.getElementById('scan-qr');
|
||||
if (scanButton) {
|
||||
scanButton.addEventListener('click', function() {
|
||||
// This would integrate with a QR code scanner library
|
||||
// For now, we'll show a prompt
|
||||
const code = prompt('Bitte QR-Code scannen oder Inventarnummer eingeben:');
|
||||
if (code) {
|
||||
document.getElementById('inventory-number').value = code;
|
||||
document.getElementById('scan-form').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asset assignment
|
||||
*/
|
||||
function assignAsset(assetId) {
|
||||
const userId = prompt('Bitte Benutzer-ID eingeben:');
|
||||
if (userId) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = `/assets/${assetId}/assign`;
|
||||
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.type = 'hidden';
|
||||
csrfInput.name = 'csrf_token';
|
||||
csrfInput.value = document.querySelector('input[name="csrf_token"]').value;
|
||||
|
||||
const userInput = document.createElement('input');
|
||||
userInput.type = 'hidden';
|
||||
userInput.name = 'user_id';
|
||||
userInput.value = userId;
|
||||
|
||||
form.appendChild(csrfInput);
|
||||
form.appendChild(userInput);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete confirmation
|
||||
*/
|
||||
function confirmDelete(message = 'Sind Sie sicher, dass Sie diesen Eintrag löschen möchten?') {
|
||||
return confirm(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show alert message
|
||||
*/
|
||||
function showAlert(message, type = 'info') {
|
||||
const alertContainer = document.querySelector('.flash-messages') || document.body;
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert alert-${type}`;
|
||||
alert.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="alert-close">×</button>
|
||||
`;
|
||||
|
||||
alertContainer.appendChild(alert);
|
||||
|
||||
// Auto-hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
alert.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
alert.remove();
|
||||
}, 300);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format bytes to human readable
|
||||
*/
|
||||
function formatBytes(bytes) {
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
let size = bytes;
|
||||
let unitIndex = 0;
|
||||
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce function
|
||||
*/
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Throttle function
|
||||
*/
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function() {
|
||||
const args = arguments;
|
||||
const context = this;
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX helper
|
||||
*/
|
||||
function ajax(url, options = {}) {
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
};
|
||||
|
||||
const config = { ...defaultOptions, ...options };
|
||||
|
||||
return fetch(url, config)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('AJAX error:', error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Local storage helper
|
||||
*/
|
||||
const Storage = {
|
||||
set: function(key, value) {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (e) {
|
||||
console.error('Error saving to localStorage', e);
|
||||
}
|
||||
},
|
||||
|
||||
get: function(key, defaultValue = null) {
|
||||
try {
|
||||
const item = localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : defaultValue;
|
||||
} catch (e) {
|
||||
console.error('Error reading from localStorage', e);
|
||||
return defaultValue;
|
||||
}
|
||||
},
|
||||
|
||||
remove: function(key) {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch (e) {
|
||||
console.error('Error removing from localStorage', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize search if on a page with search
|
||||
if (document.getElementById('search-input')) {
|
||||
initSearch();
|
||||
}
|
||||
|
||||
// Initialize QR scanner if on inventory page
|
||||
if (document.getElementById('scan-qr')) {
|
||||
initQRScanner();
|
||||
}
|
||||
Reference in New Issue
Block a user