/** * Main JavaScript for Delphi Consulting Group Database System */ // Global application state const app = { token: localStorage.getItem('auth_token'), user: null, initialized: false }; // Initialize application document.addEventListener('DOMContentLoaded', function() { initializeApp(); }); async function initializeApp() { // Initialize keyboard shortcuts if (window.keyboardShortcuts) { window.keyboardShortcuts.initialize(); } // Remove Bootstrap-dependent tooltips/popovers; use native title/tooltips if needed // Add form validation classes initializeFormValidation(); // Initialize API helpers setupAPIHelpers(); app.initialized = true; console.log('Delphi Database System initialized'); } // Form validation function initializeFormValidation() { // Native validation handling without Bootstrap classes const forms = document.querySelectorAll('form'); forms.forEach(form => { form.addEventListener('submit', function(event) { if (!form.checkValidity()) { event.preventDefault(); event.stopPropagation(); form.reportValidity(); } }); }); // Real-time validation for required fields (Tailwind styles) const requiredFields = document.querySelectorAll('input[required], select[required], textarea[required]'); requiredFields.forEach(field => { field.addEventListener('blur', function() { validateField(field); }); }); } function validateField(field) { const isValid = field.checkValidity(); field.setAttribute('aria-invalid', String(!isValid)); field.classList.toggle('border-danger-500', !isValid); } // API helpers function setupAPIHelpers() { // Set up default headers for all API calls window.apiHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json' }; if (app.token) { window.apiHeaders['Authorization'] = `Bearer ${app.token}`; } } // API utility functions async function apiCall(url, options = {}) { const config = { headers: { ...window.apiHeaders, ...options.headers }, ...options }; try { const response = await fetch(url, config); if (response.status === 401) { // Token expired or invalid logout(); throw new Error('Authentication required'); } if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: 'Request failed' })); throw new Error(errorData.detail || `HTTP ${response.status}`); } return await response.json(); } catch (error) { console.error('API call failed:', error); showNotification(`Error: ${error.message}`, 'error'); throw error; } } async function apiGet(url) { return apiCall(url, { method: 'GET' }); } async function apiPost(url, data) { return apiCall(url, { method: 'POST', body: JSON.stringify(data) }); } async function apiPut(url, data) { return apiCall(url, { method: 'PUT', body: JSON.stringify(data) }); } async function apiDelete(url) { return apiCall(url, { method: 'DELETE' }); } // Authentication functions function setAuthToken(token) { app.token = token; localStorage.setItem('auth_token', token); window.apiHeaders['Authorization'] = `Bearer ${token}`; } function logout() { app.token = null; app.user = null; localStorage.removeItem('auth_token'); delete window.apiHeaders['Authorization']; window.location.href = '/login'; } // Notification system (delegates to shared alerts utility) function showNotification(message, type = 'info', duration = 5000) { if (window.alerts && typeof window.alerts.show === 'function') { return window.alerts.show(message, type, { duration }); } // Fallback if alerts module not yet loaded return alert(String(message)); } // Loading states function showLoading(element, text = 'Loading...') { const spinner = ``; const originalContent = element.innerHTML; element.innerHTML = `${spinner}${text}`; element.disabled = true; element.dataset.originalContent = originalContent; } function hideLoading(element) { if (element.dataset.originalContent) { element.innerHTML = element.dataset.originalContent; delete element.dataset.originalContent; } element.disabled = false; } // Table helpers function initializeDataTable(tableId, options = {}) { const table = document.getElementById(tableId); if (!table) return null; // Add sorting capability const headers = table.querySelectorAll('th[data-sort]'); headers.forEach(header => { header.classList.add('sortable-header'); header.addEventListener('click', () => sortTable(table, header)); }); // Add row selection if enabled if (options.selectable) { addRowSelection(table); } return table; } function sortTable(table, header) { const columnIndex = Array.from(header.parentNode.children).indexOf(header); const sortType = header.dataset.sort; const tbody = table.querySelector('tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); const isAscending = !header.classList.contains('sort-asc'); // Remove sort classes from all headers table.querySelectorAll('th').forEach(th => { th.classList.remove('sort-asc', 'sort-desc'); }); // Add sort class to current header header.classList.add(isAscending ? 'sort-asc' : 'sort-desc'); rows.sort((a, b) => { const aValue = a.children[columnIndex].textContent.trim(); const bValue = b.children[columnIndex].textContent.trim(); let comparison = 0; if (sortType === 'number') { comparison = parseFloat(aValue) - parseFloat(bValue); } else if (sortType === 'date') { comparison = new Date(aValue) - new Date(bValue); } else { comparison = aValue.localeCompare(bValue); } return isAscending ? comparison : -comparison; }); // Re-append sorted rows rows.forEach(row => tbody.appendChild(row)); } function addRowSelection(table) { const tbody = table.querySelector('tbody'); tbody.addEventListener('click', function(e) { const row = e.target.closest('tr'); if (row && e.target.type !== 'checkbox') { const isSelected = row.classList.toggle('bg-neutral-100'); row.classList.toggle('dark:bg-neutral-700', isSelected); // Trigger custom event const event = new CustomEvent('rowSelect', { detail: { row, selected: isSelected } }); table.dispatchEvent(event); } }); } // Form helpers function serializeForm(form) { const formData = new FormData(form); const data = {}; for (let [key, value] of formData.entries()) { // Handle multiple values (checkboxes, multi-select) if (data.hasOwnProperty(key)) { if (!Array.isArray(data[key])) { data[key] = [data[key]]; } data[key].push(value); } else { data[key] = value; } } return data; } function populateForm(form, data) { Object.keys(data).forEach(key => { const field = form.querySelector(`[name="${key}"]`); if (field) { if (field.type === 'checkbox' || field.type === 'radio') { field.checked = data[key]; } else { field.value = data[key]; } } }); } // Search functionality function initializeSearch(searchInput, resultsContainer, searchFunction) { let searchTimeout; searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.trim(); if (query.length < 2) { resultsContainer.innerHTML = ''; return; } searchTimeout = setTimeout(async () => { try { showLoading(resultsContainer, 'Searching...'); const results = await searchFunction(query); displaySearchResults(resultsContainer, results); } catch (error) { resultsContainer.innerHTML = '
Search failed
'; } }, 300); }); } function displaySearchResults(container, results) { if (!results || results.length === 0) { container.innerHTML = 'No results found
'; return; } const resultsHtml = results.map(result => `