// EPG Mapping Manager class EPGMappingManager { constructor() { this.providers = []; this.currentProvider = null; this.epgChannels = []; this.channelData = []; this.fuzzySet = null; this.unsavedChanges = false; this.autoSaveTimeout = null; // Get API base from current location this.apiBase = window.location.origin; this.init(); } init() { // Don't load providers on init - wait for tab activation this.setupEventListeners(); this.createSaveIndicator(); this.initialized = false; } async loadProviders() { try { const response = await fetch(`${this.apiBase}/api/providers`); if (!response.ok) throw new Error('Failed to load providers'); const data = await response.json(); this.providers = data.providers; this.renderProviderSelector(); this.initialized = true; } catch (error) { this.showError('Failed to load providers', error); } } renderProviderSelector() { const container = document.getElementById('epg-mapping-container'); if (!container) return; container.innerHTML = `
No program data available for this channel.
'} `; modal.style.display = 'flex'; } catch (error) { this.showError('Failed to load EPG preview', error); } } bulkConfirmRecommended() { const recommendedChannels = this.channelData.filter(c => c.status === 'recommended'); if (recommendedChannels.length === 0) { alert('No recommended channels to confirm.'); return; } if (!confirm(`Confirm ${recommendedChannels.length} recommended mappings?`)) { return; } recommendedChannels.forEach(channel => { if (channel.suggestions.length > 0) { const bestSuggestion = channel.suggestions[0]; channel.currentEpgId = bestSuggestion.epgId; channel.status = 'mapped'; } }); this.updateStats(); this.renderChannelList(); this.scheduleAutoSave(); alert(`Confirmed ${recommendedChannels.length} mappings. Don't forget to save!`); } scheduleAutoSave() { this.unsavedChanges = true; this.showSaveIndicator('Unsaved changes'); if (this.autoSaveTimeout) { clearTimeout(this.autoSaveTimeout); } this.autoSaveTimeout = setTimeout(() => { this.saveMappings(true); }, 5000); } async saveMappings(isAutoSave = false) { if (!this.currentProvider) return; if (this.autoSaveTimeout) { clearTimeout(this.autoSaveTimeout); this.autoSaveTimeout = null; } const mappings = {}; this.channelData.forEach(channel => { if (channel.currentEpgId) { mappings[channel.id] = channel.currentEpgId; } }); try { this.showSaveIndicator('Saving...'); const response = await fetch(`${this.apiBase}/api/providers/${this.currentProvider}/epg-mapping`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mapping: mappings }) }); if (response.ok) { this.unsavedChanges = false; this.showSaveIndicator('Saved successfully!', true); if (!isAutoSave) { setTimeout(() => { this.hideSaveIndicator(); }, 2000); } } else { throw new Error('Save failed'); } } catch (error) { this.showSaveIndicator('Save failed!', false); console.error('Save error:', error); } } exportMappings() { if (!this.currentProvider) { alert('Please load a provider first.'); return; } const mappings = {}; this.channelData.forEach(channel => { if (channel.currentEpgId) { mappings[channel.id] = channel.currentEpgId; } }); const data = { provider: this.currentProvider, mapping: mappings, exported_at: new Date().toISOString() }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${this.currentProvider}_epg_mapping_${new Date().toISOString().slice(0,10)}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert('Mappings exported successfully!'); } importMappings() { if (!this.currentProvider) { alert('Please load a provider first.'); return; } const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; try { const text = await file.text(); const data = JSON.parse(text); if (!data.mapping || typeof data.mapping !== 'object') { throw new Error('Invalid mapping file format'); } Object.entries(data.mapping).forEach(([channelId, mappingValue]) => { const channel = this.channelData.find(c => c.id === channelId); if (channel) { if (typeof mappingValue === 'string') { channel.currentEpgId = mappingValue; } else if (mappingValue && mappingValue.epg_id) { channel.currentEpgId = mappingValue.epg_id; } channel.status = 'mapped'; } }); this.updateStats(); this.renderChannelList(); this.scheduleAutoSave(); alert(`Imported ${Object.keys(data.mapping).length} mappings successfully!`); } catch (error) { alert(`Import failed: ${error.message}`); } }; input.click(); } createSaveIndicator() { const indicator = document.createElement('div'); indicator.className = 'save-indicator'; indicator.id = 'save-indicator'; indicator.style.display = 'none'; document.body.appendChild(indicator); } showSaveIndicator(message, isSuccess = null) { const indicator = document.getElementById('save-indicator'); if (!indicator) return; indicator.innerHTML = ` ${isSuccess === true ? '' : isSuccess === false ? '' : ''} ${message} `; indicator.style.display = 'flex'; indicator.style.background = isSuccess === true ? 'var(--success-color)' : isSuccess === false ? 'var(--danger-color)' : 'var(--primary-color)'; } hideSaveIndicator() { const indicator = document.getElementById('save-indicator'); if (indicator) { indicator.style.display = 'none'; } } showLoading(show) { const container = document.getElementById('epg-mapping-container'); if (!container) return; if (show) { container.innerHTML = `Loading EPG data...
${this.escapeHtml(error.message || String(error))}