// =================================== // Order Dashboard - Main JavaScript // =================================== // Global state let allData = []; let filteredData = []; let currentSort = { column: null, direction: null }; let filters = {}; let columnTextFilters = {}; let hideOkFilter = false; let apiCredentials = null; // API Configuration const API_URL = 'https://n8n.finorbrands.com/webhook/61f89d5c-8474-4045-b52c-50ee608435c0'; // Initialize document.addEventListener('DOMContentLoaded', () => { updateAuthUI(); loadData(); setupClickOutside(); }); // =================================== // Authentication // =================================== function updateAuthUI() { const btnText = document.getElementById('authBtnText'); if (!btnText) return; const storedUsername = localStorage.getItem('api_username'); if (storedUsername) { btnText.textContent = `Logged in as ${storedUsername}`; } else { btnText.textContent = 'Enter Credentials'; } } function toggleAuth() { const storedUsername = localStorage.getItem('api_username'); if (storedUsername) { localStorage.removeItem('api_username'); localStorage.removeItem('api_password'); apiCredentials = null; updateAuthUI(); loadData(); } else { showCredentialPrompt(); } } // =================================== // Data Loading // =================================== async function loadData() { const tableBody = document.getElementById('tableBody'); if (!tableBody) return; tableBody.innerHTML = `
Loading data... `; try { console.group('%c Webhook Call', 'background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;'); console.log('%c API URL:', 'font-weight: bold; color: #6366f1;', API_URL); const storedUsername = localStorage.getItem('api_username'); const storedPassword = localStorage.getItem('api_password'); const startTime = performance.now(); const fetchOptions = {}; if (storedUsername && storedPassword) { apiCredentials = { username: storedUsername, password: storedPassword }; const credentials = btoa(`${storedUsername}:${storedPassword}`); fetchOptions.headers = { 'Authorization': `Basic ${credentials}`, 'Accept': 'application/json' }; console.log('%c Using Basic Auth', 'color: #10b981;', `User: ${storedUsername}`); } const response = await fetch(API_URL, fetchOptions); const duration = performance.now() - startTime; console.log('%c Response Status:', response.ok ? 'color: #10b981' : 'color: #ef4444', response.status, response.statusText); console.log('%c Response Time:', 'font-weight: bold;', `${duration.toFixed(0)}ms`); if (response.status === 401) { throw new Error('Authorization required'); } if (response.ok) { const result = await response.json(); console.log('%c Response Data:', 'font-weight: bold;', result); if (result.data) { allData = result.data; console.log('%c Data loaded:', 'color: #10b981; font-weight: bold;', allData.length, 'records'); console.log('%c First record:', 'color: #6366f1;', allData[0]); } else { throw new Error('Invalid response format'); } } else { throw new Error('API error'); } console.groupEnd(); } catch (error) { console.error('%c Fetch Error:', 'background: #ef4444; color: white; padding: 4px 8px; border-radius: 4px;', error.message); if (error.message === 'Authorization required') { showCredentialPrompt(); } console.log('Using sample data:', error.message); } filteredData = [...allData]; console.group('%c Filter State', 'background: #8b5cf6; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;'); console.log('%c Current filters:', 'font-weight: bold;', filters); console.log('%c Total records after filter:', 'font-weight: bold;', filteredData.length); console.groupEnd(); applyFilters(); updateStats(); } // =================================== // Filtering & Sorting // =================================== function applyFilters() { let result = [...filteredData]; // Apply column text filters Object.entries(columnTextFilters).forEach(([column, searchText]) => { if (searchText && searchText.trim()) { const searchLower = searchText.toLowerCase().trim(); result = result.filter(item => { const value = item[column]; return value !== null && value !== undefined && String(value).toLowerCase().includes(searchLower); }); } }); // Apply checkbox filters Object.entries(filters).forEach(([column, selectedValues]) => { if (selectedValues.length > 0) { result = result.filter(item => selectedValues.includes(item[column] === null ? 'null' : item[column]) ); } }); // Apply "hide OK" filter if (hideOkFilter) { result = result.filter(item => { const remark = item.Remark || ''; return !String(remark).trim().toLowerCase().includes('ok'); }); } renderTable(result); updateStats(result.length); console.group('%c Table View', 'background: #06b6d4; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;'); console.log('%c Rendering', 'font-weight: bold;', result.length, 'records'); console.log('%c Active text filters:', Object.entries(columnTextFilters) .filter(([_, v]) => v && v.trim()) .map(([k, v]) => `${k}: "${v}"`), 'color: #64748b;'); if (hideOkFilter) { console.log('%c Filter:', 'color: #10b981;', 'Hide OK remarks is ON'); } console.groupEnd(); } function sortTable(column) { const direction = currentSort.column === column && currentSort.direction === 'asc' ? 'desc' : 'asc'; filteredData.sort((a, b) => { let valA = a[column]; let valB = b[column]; if (valA === null) valA = ''; if (valB === null) valB = ''; if (typeof valA === 'string' && typeof valB === 'string') { valA = valA.toLowerCase(); valB = valB.toLowerCase(); } if (valA < valB) return direction === 'asc' ? -1 : 1; if (valA > valB) return direction === 'asc' ? 1 : -1; return 0; }); currentSort = { column, direction }; renderTable(filteredData); updateSortIndicators(column, direction); } function updateSortIndicators(column, direction) { document.querySelectorAll('.sort-indicator').forEach(indicator => { indicator.classList.remove('active'); }); const th = Array.from(document.querySelectorAll('th')).find( th => th.textContent.includes(column) ); if (th) { const indicator = th.querySelector('.sort-indicator'); if (indicator) { indicator.classList.add('active'); indicator.innerHTML = direction === 'asc' ? '' : ''; } } } // =================================== // Table Rendering // =================================== function renderTable(data) { const tableBody = document.getElementById('tableBody'); if (!tableBody) return; if (data.length === 0) { tableBody.innerHTML = `

No orders found matching your filters

`; return; } tableBody.innerHTML = data.map(item => ` ${item.Month || ''} ${item.Odoo_AR_Nr || ''} ${item.Order_ID || ''}
${item.Country_Name || 'N/A'}
${item.VAT || 'N/A'}
${item.Remark || 'N/A'}
`).join(''); } function getCountryClass(country) { if (country === null || country === undefined) return 'unknown'; const str = String(country); if (str.includes('Finnland')) return 'finland'; if (str.includes('Spanien')) return 'spanien'; if (str.includes('Deutschland')) return 'deutschland'; return 'unknown'; } function getRemarkClass(remark) { if (remark === null || remark === undefined) return ''; const str = String(remark); if (str.includes('NOK')) return 'remark-nok'; if (str.includes('found')) return 'remark-danger'; if (str.includes('valid') || str.includes('OK')) return 'remark-success'; return ''; } function getRemarkIcon(remark) { if (remark === null || remark === undefined) return 'fa-file-alt'; const str = String(remark); if (str.includes('NOK')) return 'fa-times-circle'; if (str.includes('found')) return 'fa-exclamation-triangle'; if (str.includes('valid') || str.includes('OK')) return 'fa-check-circle'; return 'fa-file-alt'; } // =================================== // Recheck Action // =================================== async function handleRecheck(id, isChecked) { console.group('%c Recheck', 'background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;'); console.log('%c ID:', 'font-weight: bold;', id); console.log('%c Status:', isChecked ? 'color: #10b981' : 'color: #94a3b8', isChecked ? 'Checked' : 'Unchecked'); try { const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(apiCredentials ? { 'Authorization': `Basic ${btoa(`${apiCredentials.username}:${apiCredentials.password}`)}` } : {}) }, body: JSON.stringify({ ID: id, recheck: isChecked }) }); if (response.ok) { console.log('%c Success:', 'color: #10b981; font-weight: bold;', 'Recheck status updated'); } else { console.error('%c Error:', 'background: #ef4444; color: white; padding: 4px 8px; border-radius: 4px;', response.status, response.statusText); } console.groupEnd(); } catch (error) { console.error('%c Request Error:', 'background: #ef4444; color: white; padding: 4px 8px; border-radius: 4px;', error.message); console.groupEnd(); } } // =================================== // Column Filters // =================================== function toggleFilter(column) { const container = document.getElementById('filterContainer'); document.querySelectorAll('.filter-dropdown').forEach(dropdown => { if (dropdown.dataset.column !== column) { dropdown.classList.remove('show'); } }); let dropdown = document.querySelector(`.filter-dropdown[data-column="${column}"]`); if (!dropdown) { dropdown = createFilterDropdown(column); container.appendChild(dropdown); } if (dropdown.classList.contains('show')) { dropdown.classList.remove('show'); } else { dropdown.classList.add('show'); positionDropdown(dropdown, column); } } function createFilterDropdown(column) { const dropdown = document.createElement('div'); dropdown.className = 'filter-dropdown'; dropdown.dataset.column = column; const uniqueValues = [...new Set( allData.map(item => item[column] === null ? 'null' : item[column]) )]; dropdown.innerHTML = `

Filter ${column}

${uniqueValues.map(value => ` `).join('')}
`; dropdown.addEventListener('change', (e) => { if (e.target.type === 'checkbox') { updateFilter(column, e.target.value, e.target.checked); } }); return dropdown; } function positionDropdown(dropdown, column) { const th = Array.from(document.querySelectorAll('th')).find( th => th.textContent.includes(column) ); if (th) { const rect = th.getBoundingClientRect(); const tableRect = document.querySelector('table').getBoundingClientRect(); dropdown.style.left = `${rect.left - tableRect.left}px`; dropdown.style.width = `${rect.width}px`; } } function updateFilter(column, value, isChecked) { if (!filters[column]) { filters[column] = []; } const valueStr = value === 'null' ? 'null' : value; if (isChecked) { if (!filters[column].includes(valueStr)) { filters[column].push(valueStr); } } else { filters[column] = filters[column].filter(v => v !== valueStr); } updateFilterIndicators(column); applyFilters(); } function updateFilterIndicators(column) { const th = Array.from(document.querySelectorAll('th')).find( th => th.textContent.includes(column) ); if (th) { const indicator = th.querySelector('.filter-indicator'); if (indicator) { if (filters[column] && filters[column].length > 0) { indicator.classList.add('active'); } else { indicator.classList.remove('active'); } } } } function clearFilter(column) { if (filters[column]) { filters[column] = []; } document.querySelectorAll(`.filter-dropdown[data-column="${column}"] input[type="checkbox"]`) .forEach(checkbox => checkbox.checked = false); updateFilterIndicators(column); applyFilters(); } function setupClickOutside() { document.addEventListener('click', (e) => { if (!e.target.closest('.filter-indicator') && !e.target.closest('.filter-dropdown')) { document.querySelectorAll('.filter-dropdown').forEach(dropdown => { dropdown.classList.remove('show'); }); } }); } function handleColumnFilter(input) { const column = input.dataset.column; const value = input.value; if (value) { columnTextFilters[column] = value; input.parentElement.classList.add('filter-active'); } else { delete columnTextFilters[column]; input.parentElement.classList.remove('filter-active'); } applyFilters(); } function clearAllFilters() { // Clear column text filters columnTextFilters = {}; // Clear checkbox filters filters = {}; // Reset hide OK filter hideOkFilter = false; updateHideOkButton(); // Clear all input fields document.querySelectorAll('.column-filter').forEach(input => { input.value = ''; input.parentElement.classList.remove('filter-active'); }); // Reset filter indicators document.querySelectorAll('.filter-indicator').forEach(indicator => { indicator.classList.remove('active'); }); applyFilters(); } function toggleHideOkFilter() { hideOkFilter = !hideOkFilter; updateHideOkButton(); applyFilters(); } function updateHideOkButton() { const btn = document.getElementById('hideOkBtn'); if (!btn) return; if (hideOkFilter) { btn.style.boxShadow = '0 0 0 2px var(--danger)'; } else { btn.style.boxShadow = 'none'; } } // =================================== // Statistics // =================================== function updateStats(count = filteredData.length) { const totalOrders = document.getElementById('totalOrders'); const totalCountries = document.getElementById('totalCountries'); const totalVAT = document.getElementById('totalVAT'); const tableCount = document.getElementById('tableCount'); if (totalOrders) totalOrders.textContent = allData.length; if (totalCountries) totalCountries.textContent = [...new Set(allData.map(item => item.Country_Name))].length; if (totalVAT) totalVAT.textContent = allData.filter(item => item.VAT !== null).length; if (tableCount) tableCount.textContent = `${count} order${count !== 1 ? 's' : ''}`; } // =================================== // Credential Prompt // =================================== function showCredentialPrompt() { const existing = document.getElementById('credentialPrompt'); if (existing) existing.remove(); const prompt = document.createElement('div'); prompt.id = 'credentialPrompt'; prompt.innerHTML = `

API Credentials Required

The webhook API requires authentication. Please enter your credentials below:

Your credentials will be used for this session only. They are not stored permanently.
`; document.body.appendChild(prompt); document.getElementById('credentialForm').addEventListener('submit', handleCredentials); document.querySelector('.credential-overlay').addEventListener('click', function(e) { if (e.target === this) { closeCredentialPrompt(); } }); } function closeCredentialPrompt() { const prompt = document.getElementById('credentialPrompt'); if (prompt) { document.body.removeChild(prompt); } } function handleCredentials(e) { e.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; console.group('%c Credentials', 'background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;'); console.log('%c Username:', 'font-weight: bold;', username); console.log('%c Password length:', 'font-weight: bold;', password.length, 'characters'); console.groupEnd(); localStorage.setItem('api_username', username); localStorage.setItem('api_password', password); apiCredentials = { username, password }; console.log('%c Credentials saved:', 'color: #10b981; font-weight: bold;', { username, passwordLength: password.length, authHeader: `Basic ${btoa(`${username}:${password}`)}` }); closeCredentialPrompt(); console.log('%c Retrying connection with credentials...', 'color: #10b981; font-weight: bold;'); loadData(); }