/** * Admin Import JavaScript * Handles CSV file import functionality */ class ImportManager { constructor() { this.supportedTables = []; this.batchFileCount = 0; this.currentImportId = null; this.pollInterval = null; this.init(); } async init() { await this.loadSupportedTables(); this.setupEventListeners(); this.addInitialBatchFile(); } async loadSupportedTables() { try { console.log('Loading supported tables...'); const response = await window.http.wrappedFetch('/api/admin/import/tables'); if (response.ok) { const data = await response.json(); this.supportedTables = data.tables || []; console.log('Supported tables loaded:', this.supportedTables); } else { console.error('Failed to load supported tables, status:', response.status); } } catch (error) { console.error('Failed to load supported tables:', error); window.alerts.error('Failed to load supported tables'); } } setupEventListeners() { // Single import form document.getElementById('importForm').addEventListener('submit', (e) => { e.preventDefault(); this.handleSingleImport(); }); // Validate button document.getElementById('validateBtn').addEventListener('click', () => { this.validateHeaders(); }); // Table selection change document.getElementById('tableSelect').addEventListener('change', (e) => { this.onTableChange(e.target.value); }); // Batch import buttons document.getElementById('addBatchFile').addEventListener('click', () => { this.addBatchFile(); }); document.getElementById('batchImportBtn').addEventListener('click', () => { this.handleBatchImport(); }); } async onTableChange(tableName) { const schemaInfo = document.getElementById('schemaInfo'); const schemaDetails = document.getElementById('schemaDetails'); if (!tableName) { schemaInfo.classList.add('hidden'); return; } try { console.log('Loading schema for table:', tableName); const response = await window.http.wrappedFetch(`/api/admin/import/tables/${tableName}/schema`); if (response.ok) { const data = await response.json(); const schema = data.schema; console.log('Schema loaded for', tableName, ':', schema); let html = '
'; html += '

Required Fields:

'; html += '
'; html += '

All Available Fields:

'; html += '
'; html += '
'; Object.keys(schema.field_mapping).forEach(field => { html += `${field}`; }); html += '
'; schemaDetails.innerHTML = html; schemaInfo.classList.remove('hidden'); } } catch (error) { console.error('Failed to load schema:', error); } } async validateHeaders() { const tableSelect = document.getElementById('tableSelect'); const fileInput = document.getElementById('csvFile'); console.log('Starting header validation...'); if (!tableSelect.value) { console.warn('No table selected for validation'); window.alerts.error('Please select a table type'); return; } if (!fileInput.files[0]) { console.warn('No file selected for validation'); window.alerts.error('Please select a CSV file'); return; } console.log('Validating headers for table:', tableSelect.value, 'file:', fileInput.files[0].name); const formData = new FormData(); formData.append('table_name', tableSelect.value); formData.append('file', fileInput.files[0]); try { const response = await window.http.wrappedFetch('/api/admin/import/validate', { method: 'POST', body: formData }); console.log('Validation response status:', response.status); if (response.ok) { const result = await response.json(); console.log('Validation result:', result); if (result.success) { window.alerts.success('CSV headers validated successfully!'); } else { const errors = result.validation_result.errors.join('\\n'); console.error('Validation errors:', result.validation_result.errors); window.alerts.error(`Validation failed:\\n${errors}`); } } else { const error = await response.json(); console.error('Validation failed with error:', error); window.alerts.error(`Validation failed: ${error.detail}`); } } catch (error) { console.error('Validation error:', error); window.alerts.error('Failed to validate CSV headers'); } } async handleSingleImport() { const tableSelect = document.getElementById('tableSelect'); const fileInput = document.getElementById('csvFile'); console.log('Starting single import...'); if (!tableSelect.value) { console.warn('No table selected for import'); window.alerts.error('Please select a table type'); return; } if (!fileInput.files[0]) { console.warn('No file selected for import'); window.alerts.error('Please select a CSV file'); return; } console.log('Importing to table:', tableSelect.value, 'file:', fileInput.files[0].name); const formData = new FormData(); formData.append('table_name', tableSelect.value); formData.append('file', fileInput.files[0]); // Show progress this.showProgress(); try { const response = await window.http.wrappedFetch('/api/admin/import/csv', { method: 'POST', body: formData }); console.log('Import response status:', response.status); if (response.ok) { const result = await response.json(); console.log('Import started successfully:', result); this.currentImportId = result.import_id; this.updateProgress(`Import started for ${result.table_name} (ID: ${result.import_id})`, 'info'); this.startPolling(); } else { const error = await response.json(); console.error('Import failed:', error); this.updateProgress(`Import failed: ${error.detail}`, 'error'); } } catch (error) { console.error('Import error:', error); this.updateProgress('Import failed: Network error', 'error'); } } addInitialBatchFile() { this.addBatchFile(); } addBatchFile() { this.batchFileCount++; const container = document.getElementById('batchFiles'); const fileDiv = document.createElement('div'); fileDiv.className = 'flex space-x-3 items-center'; fileDiv.id = `batchFile${this.batchFileCount}`; fileDiv.innerHTML = ` `; container.appendChild(fileDiv); } removeBatchFile(fileId) { const fileDiv = document.getElementById(`batchFile${fileId}`); if (fileDiv) { fileDiv.remove(); } } async handleBatchImport() { const batchFiles = document.getElementById('batchFiles'); const fileDivs = batchFiles.children; const formData = new FormData(); const tableNames = []; let hasFiles = false; for (let i = 0; i < fileDivs.length; i++) { const div = fileDivs[i]; const tableSelect = div.querySelector('select'); const fileInput = div.querySelector('input[type="file"]'); if (tableSelect.value && fileInput.files[0]) { tableNames.push(tableSelect.value); formData.append('files', fileInput.files[0]); hasFiles = true; } } if (!hasFiles) { window.alerts.error('Please select at least one table and file'); return; } // Add table names to form data tableNames.forEach(name => { formData.append('table_names', name); }); // Show progress this.showProgress(); try { const response = await window.http.wrappedFetch('/api/admin/import/batch', { method: 'POST', body: formData }); if (response.ok) { const result = await response.json(); this.currentImportIds = result.import_ids; this.updateProgress(`Batch import started for ${result.total_files} files`, 'info'); this.startBatchPolling(); } else { const error = await response.json(); this.updateProgress(`Batch import failed: ${error.detail}`, 'error'); } } catch (error) { console.error('Batch import error:', error); this.updateProgress('Batch import failed: Network error', 'error'); } } showProgress() { document.getElementById('importProgress').classList.remove('hidden'); document.getElementById('importResults').classList.add('hidden'); } updateProgress(message, type = 'info') { const progressDetails = document.getElementById('progressDetails'); const timestamp = new Date().toLocaleTimeString(); let colorClass = 'text-blue-600'; if (type === 'error') colorClass = 'text-red-600'; if (type === 'success') colorClass = 'text-green-600'; if (type === 'warning') colorClass = 'text-yellow-600'; progressDetails.innerHTML += `
${timestamp} ${message}
`; // Scroll to bottom progressDetails.scrollTop = progressDetails.scrollHeight; } startPolling() { if (this.pollInterval) { clearInterval(this.pollInterval); } this.pollInterval = setInterval(async () => { await this.checkImportStatus(); }, 2000); // Poll every 2 seconds } startBatchPolling() { if (this.pollInterval) { clearInterval(this.pollInterval); } this.pollInterval = setInterval(async () => { await this.checkBatchStatus(); }, 2000); // Poll every 2 seconds } async checkImportStatus() { if (!this.currentImportId) return; try { const response = await window.http.wrappedFetch(`/api/admin/import/status/${this.currentImportId}`); if (response.ok) { const status = await response.json(); if (status.status === 'COMPLETED') { clearInterval(this.pollInterval); this.updateProgress('Import completed successfully!', 'success'); this.showResults(status.result); } else if (status.status === 'FAILED') { clearInterval(this.pollInterval); this.updateProgress(`Import failed: ${status.error || 'Unknown error'}`, 'error'); if (status.result) { this.showResults(status.result); } } else { this.updateProgress(`Import status: ${status.status}`, 'info'); } } } catch (error) { console.error('Status check error:', error); } } async checkBatchStatus() { if (!this.currentImportIds || !Array.isArray(this.currentImportIds)) return; let allCompleted = true; let anyFailed = false; for (const importId of this.currentImportIds) { try { const response = await window.http.wrappedFetch(`/api/admin/import/status/${importId}`); if (response.ok) { const status = await response.json(); if (status.status === 'PROCESSING') { allCompleted = false; } else if (status.status === 'FAILED') { anyFailed = true; this.updateProgress(`${status.table_name} import failed: ${status.error || 'Unknown error'}`, 'error'); } else if (status.status === 'COMPLETED') { this.updateProgress(`${status.table_name} import completed`, 'success'); } } } catch (error) { console.error('Batch status check error:', error); } } if (allCompleted) { clearInterval(this.pollInterval); const message = anyFailed ? 'Batch import completed with some failures' : 'Batch import completed successfully!'; const type = anyFailed ? 'warning' : 'success'; this.updateProgress(message, type); } } showResults(result) { const resultsContent = document.getElementById('resultsContent'); const resultsDiv = document.getElementById('importResults'); let html = '
'; // Summary html += `
${result.total_rows}
Total Rows
${result.imported_rows}
Imported
${result.skipped_rows}
Skipped
${result.error_rows}
Errors
`; // Errors if (result.errors && result.errors.length > 0) { html += '
'; html += '

Errors:

'; html += '
'; result.errors.forEach(error => { html += `
${this.escapeHtml(error)}
`; }); html += '
'; } // Warnings if (result.warnings && result.warnings.length > 0) { html += '
'; html += '

Warnings:

'; html += '
'; result.warnings.forEach(warning => { html += `
${this.escapeHtml(warning)}
`; }); html += '
'; } html += '
'; resultsContent.innerHTML = html; resultsDiv.classList.remove('hidden'); } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.importManager = new ImportManager(); });