document.addEventListener('DOMContentLoaded', () => { // --- DOM Element References --- const parentFilterDropdown = document.getElementById('parentFilterDropdown'); const parentSearchInput = document.getElementById('parentSearchInput'); const clearFiltersBtn = document.getElementById('clearFiltersBtn'); const dataContainer = document.getElementById('dataContainer'); const loadingIndicator = document.getElementById('loadingIndicator'); const errorIndicator = document.getElementById('errorIndicator'); const initialMessage = document.getElementById('initialMessage'); // Store original options for the search filter const originalOptions = Array.from(parentFilterDropdown.options); let debounceTimeout = null; // --- Core Data Fetching Function --- async function fetchCustomerData(parentName) { // Show initial message if no parent is selected if (!parentName) { dataContainer.innerHTML = ''; // Clear previous results initialMessage.classList.remove('d-none'); errorIndicator.classList.add('d-none'); loadingIndicator.classList.add('d-none'); dataContainer.appendChild(initialMessage); return; } // --- Show Loading State --- initialMessage.classList.add('d-none'); errorIndicator.classList.add('d-none'); loadingIndicator.classList.remove('d-none'); dataContainer.innerHTML = ''; // Clear previous results dataContainer.appendChild(loadingIndicator); try { const apiUrl = `/api/customer_data_by_parent?parent_name=${encodeURIComponent(parentName)}`; const response = await fetch(apiUrl); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `Error: ${response.status}`); } const data = await response.json(); renderData(data); await fetchAndRenderParentApiUsage(parentName); } catch (error) { console.error("Fetch error:", error); showError(error.message); } finally { loadingIndicator.classList.add('d-none'); } } async function fetchAndRenderParentApiUsage(parentName, year = '') { try { const params = new URLSearchParams(); params.set('parent_name', parentName); if (year) { params.set('year', year); } const response = await fetch(`/api/customer_api_usage_by_parent?${params.toString()}`); if (!response.ok) { return; } const data = await response.json(); renderParentApiUsage(data); } catch (error) { console.error('Parent API usage summary failed:', error); } } function renderParentApiUsage(data) { if (!dataContainer || !data || data.available === false || !Array.isArray(data.provider_summaries) || data.provider_summaries.length === 0) { return; } const existing = document.getElementById('parent-api-usage-summary'); if (existing) existing.remove(); const selectedYear = Number(data.selected_year || new Date().getUTCFullYear()); const yearLabels = data.available_year_labels || {}; const yearOptions = (data.available_years || []) .map(y => { const label = yearLabels[String(y)] || y; return ``; }) .join(''); const rowsHtml = data.provider_summaries.map((row) => { const pct = Number(row.usage_percent || 0); let badgeClass = 'bg-success'; if (pct >= 90) { badgeClass = 'bg-danger'; } else if (pct >= 80) { badgeClass = 'bg-warning text-dark'; } const verboseUrl = `/api_submissions_usage?scope=parent&parent_name=${encodeURIComponent(data.parent_name || '')}&end_point=${encodeURIComponent(row.end_point || '')}`; return ` ${row.end_point || 'unknown'} ${Number(row.ytd_credits_used || 0)} ${Number(row.current_month_credits_used || 0)} ${Number(row.ytd_credit_limit || 0)} ${pct.toFixed(2)}% Verbose `; }).join(''); const card = document.createElement('div'); card.id = 'parent-api-usage-summary'; card.className = 'card mb-3'; card.innerHTML = `
API Credits (Parent Headline)
${rowsHtml}
Provider (end_point) YTD Used Month Used YTD Limit Usage % Details
`; dataContainer.insertBefore(card, dataContainer.firstChild); const yearSelect = document.getElementById('parent-api-usage-year-select'); if (yearSelect) { yearSelect.addEventListener('change', () => { fetchAndRenderParentApiUsage(data.parent_name || '', yearSelect.value); }); } } // --- Data Rendering Function --- function renderData(data) { dataContainer.innerHTML = ''; // Clear any indicators // Create Parent Header const header = document.createElement('h3'); header.className = 'mb-3 border-bottom pb-2'; const formatDate = (dateStr, useTimeOnlyForToday = false) => { if (dateStr === null || dateStr === undefined || dateStr === "") { return 'Never'; } const dateObj = new Date(dateStr); if (isNaN(dateObj.getTime())) { return dateStr; } if (useTimeOnlyForToday) { const today = new Date(); if (dateObj.getFullYear() === today.getFullYear() && dateObj.getMonth() === today.getMonth() && dateObj.getDate() === today.getDate()) { return dateObj.toLocaleTimeString(); } } return dateObj.toLocaleString(); }; const displayName = data.cancelled == 1 ? `${data.parent_name} (Cancelled)` : data.parent_name; // Create the Salesforce link or display a fallback message const sfdcLinkHtml = data.sfdc_id ? `Salesforce` : 'No SFDC ID'; // Create the Freshdesk link or display a fallback message const fdLinkHtml = data.fd_id ? `Freshdesk` : 'No FD ID'; // Use .innerHTML to render the anchor tags correctly header.innerHTML = `${displayName} (${sfdcLinkHtml}, ${fdLinkHtml})`; dataContainer.appendChild(header); // Check if there are any customers if (!data.customers || data.customers.length === 0) { const noCustomersMessage = document.createElement('div'); noCustomersMessage.className = 'alert alert-warning'; noCustomersMessage.textContent = 'No associated customers found for this parent company.'; dataContainer.appendChild(noCustomersMessage); return; } // Create Customer List Container const listGroup = document.createElement('div'); listGroup.className = 'list-group'; data.customers.forEach(customer => { const link = document.createElement('a'); link.className = 'list-group-item list-group-item-action'; const url = new URL('/magma_detail', window.location.origin); url.searchParams.set('licence_number', customer.licence_number); link.href = url.toString(); link.innerHTML = `
${customer.company_name || 'N/A'}
Last Seen: ${formatDate(customer.last_seen, true)}

Licence: ${customer.licence_number || 'N/A'} | Magma Version: ${customer.magma_version || 'N/A'} Runset Count: ${customer.runset_count ?? 0} PC: ${customer.computer_name || 'N/A'} DB: ${customer.DB_Machine_Name || 'N/A'} DB Size: ${customer.DB_dbsize || 'N/A'}

`; listGroup.appendChild(link); }); dataContainer.appendChild(listGroup); } // --- Helper to show error messages --- function showError(message) { dataContainer.innerHTML = ''; errorIndicator.textContent = message; errorIndicator.classList.remove('d-none'); dataContainer.appendChild(errorIndicator); } // --- Event Listeners --- // Handle manual selection from dropdown parentFilterDropdown.addEventListener('change', () => { const selectedParent = parentFilterDropdown.value; parentSearchInput.value = selectedParent; // Sync search input fetchCustomerData(selectedParent); }); // Handle typing in the search box with debouncing parentSearchInput.addEventListener('input', () => { const searchTerm = parentSearchInput.value.toLowerCase(); parentFilterDropdown.innerHTML = ''; const filteredOptions = originalOptions.filter(option => option.value === "" || option.textContent.toLowerCase().includes(searchTerm) ); filteredOptions.forEach(option => { parentFilterDropdown.add(option.cloneNode(true)); }); if (filteredOptions.length > 1) { parentFilterDropdown.value = filteredOptions[1].value; } else { parentFilterDropdown.value = ""; } clearTimeout(debounceTimeout); debounceTimeout = setTimeout(() => { fetchCustomerData(parentFilterDropdown.value); }, 400); }); // Allow 'Enter' key to trigger search immediately parentSearchInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') { clearTimeout(debounceTimeout); fetchCustomerData(parentFilterDropdown.value); } }); // Clear all filters and reset the view clearFiltersBtn.addEventListener('click', () => { clearTimeout(debounceTimeout); parentSearchInput.value = ''; parentFilterDropdown.innerHTML = ''; originalOptions.forEach(opt => parentFilterDropdown.add(opt.cloneNode(true))); parentFilterDropdown.value = ''; fetchCustomerData(''); }); if (parentSearchInput.value) { const event = new Event('input', { bubbles: true, cancelable: true, }); parentSearchInput.dispatchEvent(event); } });