diff --git a/data/campaign.db b/data/campaign.db index e75bff5..33f7935 100644 Binary files a/data/campaign.db and b/data/campaign.db differ diff --git a/src/__pycache__/app.cpython-311.pyc b/src/__pycache__/app.cpython-311.pyc index caaba71..158c365 100644 Binary files a/src/__pycache__/app.cpython-311.pyc and b/src/__pycache__/app.cpython-311.pyc differ diff --git a/src/app.py b/src/app.py index 9d999e5..8d7254f 100644 --- a/src/app.py +++ b/src/app.py @@ -1684,13 +1684,29 @@ def upload_campaign_csv(): if not contacts: return jsonify({'success': False, 'error': 'No valid contacts found in CSV'}), 400 - + + # Save as contact list for reuse (like the regular CSV upload) + list_id = None + list_name = None + try: + from models.contact_list import ContactList + cl = ContactList(app.config.get('DATABASE')) + cl.ensure_schema() + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + list_name = f"{file.filename}_{timestamp}" + list_id = cl.create_list(list_name, file.filename, contacts) + logger.info(f"Auto-saved campaign contacts as list ID {list_id}: {list_name}") + except Exception as e: + logger.error(f"Failed to save contact list: {e}") + return jsonify({ 'success': True, 'total_contacts': len(contacts), 'contacts': contacts, 'preview': preview_contacts, - 'message': f'Successfully loaded {len(contacts)} contacts' + 'list_id': list_id, + 'list_name': list_name, + 'message': f'Successfully loaded {len(contacts)} contacts' + (f' and saved as "{list_name}"' if list_name else '') }) except Exception as e: diff --git a/src/routes/__pycache__/lists.cpython-311.pyc b/src/routes/__pycache__/lists.cpython-311.pyc index 115c1db..e4eea6b 100644 Binary files a/src/routes/__pycache__/lists.cpython-311.pyc and b/src/routes/__pycache__/lists.cpython-311.pyc differ diff --git a/src/routes/lists.py b/src/routes/lists.py index 509cf3e..ecb0f7c 100644 --- a/src/routes/lists.py +++ b/src/routes/lists.py @@ -17,6 +17,26 @@ def get_lists(): return jsonify({'success': False, 'error': str(e)}), 500 +@lists_bp.route('/api/lists', methods=['POST']) +def create_list(): + try: + data = request.get_json() + if not data: + return jsonify({'success': False, 'error': 'No data provided'}), 400 + + name = data.get('name', '') + contacts = data.get('contacts', []) + filename = data.get('filename', 'manual_entry') + + if not name or not contacts: + return jsonify({'success': False, 'error': 'Name and contacts are required'}), 400 + + list_id = model.create_list(name, filename, contacts) + return jsonify({'success': True, 'list_id': list_id, 'message': f'List created with ID {list_id}'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + @lists_bp.route('/api/lists/', methods=['GET']) def get_list(list_id): try: diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index 8972fbc..4d931a9 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -78,6 +78,12 @@ function campaignApp() { }, _lastLoadedTemplate: '', // Track the last loaded template content + // List management + listUploadName: '', + listUploadPreview: [], + viewingList: null, + viewingListContacts: [], + // Initialization async init() { // Start monitoring connection status @@ -151,7 +157,8 @@ function campaignApp() { async loadSavedLists() { try { const response = await fetch('/api/lists'); - this.savedLists = await response.json(); + const data = await response.json(); + this.savedLists = data.success ? data.lists : []; } catch (error) { console.error('Failed to load lists:', error); this.savedLists = []; // Set empty array on error @@ -235,9 +242,10 @@ function campaignApp() { try { const response = await fetch(`/api/lists/${listId}`); - const list = await response.json(); + const data = await response.json(); - if (list && list.contacts) { + if (data.success && data.list && data.list.contacts) { + const list = data.list; this.uploadedContacts = list.contacts; this.contactsPreview = list.contacts.slice(0, 10); this.totalContacts = list.contacts.length; @@ -249,7 +257,7 @@ function campaignApp() { console.log(`Loaded saved list: ${list.name} with ${this.totalContacts} contacts`); } else { - alert('Error loading saved list'); + alert('Error loading saved list: ' + (data.error || 'Unknown error')); this.resetContactData(); } } catch (error) { @@ -533,6 +541,162 @@ function campaignApp() { } }, + // === LIST MANAGEMENT METHODS === + async handleListUpload(event) { + const file = event.target.files[0]; + if (file) { + const formData = new FormData(); + formData.append('file', file); + + try { + const response = await fetch('/api/csv/upload', { + method: 'POST', + body: formData + }); + const data = await response.json(); + + if (data.success) { + this.listUploadPreview = data.recipients.slice(0, 10); + + // Auto-save the list if no custom name is provided + if (!this.listUploadName.trim()) { + await this.loadSavedLists(); + alert(`Contact list saved automatically as "${data.list_name}"`); + this.listUploadPreview = []; + event.target.value = ''; // Clear file input + } + } else { + alert('Error uploading file: ' + data.error); + } + } catch (error) { + console.error('Upload failed:', error); + alert('Upload failed: ' + error.message); + } + } + }, + + async saveListFromPreview() { + if (this.listUploadPreview.length === 0) { + alert('No contacts to save'); + return; + } + + try { + const listName = this.listUploadName.trim() || + `Custom_List_${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '_')}`; + + const response = await fetch('/api/lists', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: listName, + contacts: this.listUploadPreview, + filename: 'custom_upload' + }) + }); + + const data = await response.json(); + if (data.success) { + alert(`Contact list saved as "${listName}"`); + this.listUploadPreview = []; + this.listUploadName = ''; + await this.loadSavedLists(); + } else { + alert('Error saving list: ' + data.error); + } + } catch (error) { + console.error('Error saving list:', error); + alert('Error saving list: ' + error.message); + } + }, + + async viewListContacts(list) { + try { + const response = await fetch(`/api/lists/${list.id}`); + const data = await response.json(); + + if (data.success) { + this.viewingList = data.list; + this.viewingListContacts = data.list.contacts || []; + } else { + alert('Error loading list details: ' + data.error); + } + } catch (error) { + console.error('Error loading list details:', error); + alert('Error loading list details: ' + error.message); + } + }, + + async useListForCampaign(list) { + // Switch to campaigns tab + this.activeTab = 'campaigns'; + + // Load the list data + this.selectedList = list.id; + await this.loadSavedList(list.id); + + alert(`List "${list.name}" is now loaded for your campaign!`); + }, + + async downloadList(list) { + try { + const response = await fetch(`/api/lists/${list.id}`); + const data = await response.json(); + + if (data.success && data.list.contacts) { + // Convert to CSV + const contacts = data.list.contacts; + const headers = ['name', 'phone', 'email']; + const csvContent = [ + headers.join(','), + ...contacts.map(contact => + headers.map(header => + (contact[header] || '').toString().replace(/"/g, '""') + ).map(field => `"${field}"`).join(',') + ) + ].join('\n'); + + // Download + const blob = new Blob([csvContent], { type: 'text/csv' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${list.name}.csv`; + a.click(); + window.URL.revokeObjectURL(url); + } else { + alert('Error downloading list: ' + (data.error || 'Unknown error')); + } + } catch (error) { + console.error('Error downloading list:', error); + alert('Error downloading list: ' + error.message); + } + }, + + async deleteContactList(listId, listName) { + if (!confirm(`Are you sure you want to delete the list "${listName}"?`)) { + return; + } + + try { + const response = await fetch(`/api/lists/${listId}`, { + method: 'DELETE' + }); + const result = await response.json(); + + if (result.success) { + alert('List deleted!'); + await this.loadSavedLists(); + } else { + alert('Error deleting list: ' + result.error); + } + } catch (error) { + console.error('Error deleting list:', error); + alert('Error deleting list: ' + error.message); + } + }, + + // === TESTING METHODS === async testSMS() { if (!this.messageTemplate) { alert('Please enter a message template first'); diff --git a/src/templates/dashboard.html b/src/templates/dashboard.html index 34492ff..4b4488c 100644 --- a/src/templates/dashboard.html +++ b/src/templates/dashboard.html @@ -62,6 +62,11 @@ class="px-4 py-2 rounded-lg font-medium transition-colors"> 📝 Templates + + + + + +
+
+

Saved Lists

+ +
+ +
+ No contact lists saved yet. Upload a CSV file above to get started! +
+ +
+ +
+ + +
+
+
+

+ + + ( contacts) + +

+ +
+ +
+
+ +
+
+ +
+ +
+
+
+
+ +