From 5795ec737b6f937a5d977ab68c18d15e0cb301b4 Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 25 Aug 2025 12:27:33 -0600 Subject: [PATCH] Small bug fixes --- data/campaign.db | Bin 114688 -> 114688 bytes src/__pycache__/app.cpython-311.pyc | Bin 103088 -> 104124 bytes src/app.py | 20 ++- src/routes/__pycache__/lists.cpython-311.pyc | Bin 4228 -> 5473 bytes src/routes/lists.py | 20 +++ src/static/js/dashboard.js | 172 ++++++++++++++++++- src/templates/dashboard.html | 140 ++++++++++++++- 7 files changed, 345 insertions(+), 7 deletions(-) diff --git a/data/campaign.db b/data/campaign.db index e75bff5104f1414326a1d5bc4eac50ba5d7f77aa..33f7935aa0fb48f33d53814fa963c010d0c4e29f 100644 GIT binary patch delta 535 zcmZo@U~gz(pCHA={Ai+#6Oi1PV5ZN=F?qVarU;uL3$r?7a(-S(Vsc4*PG)gQG27-V z`UVaHY`iTD{L}b#_zv+!@V?`1+1OaX%ap-3`9rNHtA&A?so7+?It4WgD+4ntQ!^t2 zBU4=i3tb~q1w*J%1}hgssHw3rCj$q^qzP)@oe9{{m+4;Er**F*&6-9Z&joBt=_1kbE?An~u_eX%+ z!pg|p%E&Zh&1&As#q*nx`I{E70DZ)NkAeRW|2O^*{IB?*@ZZ}k7;uW8jg^^^k&SKg zllKZh1V-sZr_ zQXs;?wVQ!omuolg5}x}!iriPYd--;3Y+TH>efKWLCCrR56B{Gyi#fO$L`y}bIXH}s zgHlsdJTeuk^AdAYYZbCei%S$rQj1G6^U@WHOG?wy^b|re67#Z)71HvH6e{yeixf&S qb5r#cK>E^C^HPfvb5Px4U~Xk>h()L|fRUTQu+cqQe!BU7#*+Z^S)QH% delta 303 zcmZo@U~gz(pCHA=^lGAv6Oi1PV5ZN=K6$#nrU%!$d^rsK8~Ee-`S|wo;@h0l_eX%++{(nv%E&ll&BFZ2#q*nx z`I{E704?T!!odHB{~P}Y{#X1@HVXz^;-CEFy#i2t8w39*{>S`R_>b~$1Bx%^N7@6uCSm+v=Di}h=w>dDf6bP|%?PlQD<=V}=gy%kwBKH;U b-i?h*xwh}##kho-@#4hBv(t6=GhPM&=pJbZ diff --git a/src/__pycache__/app.cpython-311.pyc b/src/__pycache__/app.cpython-311.pyc index caaba71bd317c8b553b5005482d807058a58129c..158c36513e6e1094cee49adadb4f1810e4bb75f8 100644 GIT binary patch delta 2100 zcmY+E3s6*57{~8-@7{%l%kCnuyQnO8*9R<^h7XboOj26`!OBW}WE+cu!GdQOA-bEO zmE`o^rjsKIm{u6hIFowmH9n>+wVGwd8gjy(Q`&>snw+uc^nD^wch3IK|9t2B&Uf~l zZ_c6p#@dVN!kA7a3cN`li;7=PpPxiTf<2ph=P_Ba?{Eq-^K}%zc*w?tnXX+ zUz5Nhn>BNHDsSHa(~@#WeFR~nEz*TOS6E;et)4K$}O|$G0c1vMJ zGe=Hbwz)OszYd!8GCIFD77$Jh&5rzj)Mc`3i5TqAlt@oasv|IWiV(^lT~!dK3M?yZ zMc0N-mz1tqBYEGX=2Qy!pR5mC1Mwq$Gc*e=Ql-!gA$dI1KQVL@|EdKhDw?Djv_wtu zPr7DNG2`UjlBO`DKSNV+eX$e@ueOxCl!t- z)U4d^0hi0QUaS&F_ju>Ey^inE2M`uCwq`GE3N>68^lh&8 zt5v@GMqllQfLhfM2>Gf)LDd&j>uQ4`b?G9N^;2L9#_-AO#Z$gsR}?b*$Ql zDcLN3doGk@jqQ6RXD<0ZKr8ehg2@P#KE5v(vQloq*?J?||G<5;2{B_a=tH?+6O&^` zA;sPiH<;?^FvUcZsfbrhfi+t)I}4+Zd~B~4jPxB#2W<`>crMUgdB|`$slR2w<~?Wg zcHB0kOnACW|XpWlnx!CY+KB@R|B50Z|~hB*>^&F9X8!|*M?G!Ncz715XzlXl`l@vPB|q_d*K) zG#}qDFHXLx|Bw&A0$k)n^WlPAil(3S;{~wXATLM#tDaT_GYv3>FDiyFe7(;LrIP*bI81+$N;iv2@DeHyu-mw~44m@)=vboX zm4Qn#PDITz_{s_>F-{|0%U`X4IdUWFmHL+z&|r`^qi)n!tbn^Dc#t2s2iD51XnICh zR>C?7T6o=RI3kD9)T>Wl16vKQa@6cQ%DfB}X6_|jLzqpd;HU3{0l5t)-_yH%utgEg z@wys-^&&+0_C{C_oDVmG4PM~#Cdia~(eoSKvkBH13|mo0j{RfPyPDvNVd^#PZ+ z_VbfraLavYougk0!^a|I@cuSXVK4uz4ff#Cw6sGvJf){^1reZ!Pu>P~iR15_n@6_6 zyZ6nYW$A@sRKJW0jcuW%R9}n=i%}^tsv$;I!>A${T{)w>W_t*q(4VAxW^}WR?uyYh yus*Io0vQPdsL@3TzNKGy1cnowivSivOt{+pA_?NWb1*k1j#$e?{rjikwD>Ozv>HDE delta 1362 zcmZ8gTTE0}6us--xkH^nD#FYF@|c;@4ikiyQ5wMFGa~Pj(pGInpv(|HK%J?tIK@p%kKmwhnnu0tN&3;QcUJ?#6hn5K2vKZB-C(DS6cWmx~8;;h%}^qpGJ zwa?yduY5ALqh0GTc53bL$H}2|T$D#v!>jvE`QvK%rJezIcDX-R*r}{==me3>oXj$r zxtJvo%vpn>y5?X=B(jnsU(Y~lppbq@9OJxvhDL@K0)M24;IvZ|pR?7qzEdqiwe+sY zly@^wyljkeTKP-z)lB$SzDT)ER+4@o(yPr|1L~tpICcN;PdjH_t8OKGM$?Wr4L8lk zxo2IzSy#dNx(jJ%o6a@ea1|`wa$PFojlUnSkmXyjCnuXq_j?K*+V~3ummM1jzjJJj zq~UvSfbg2bR^&6TrQ{cRjp;-XpZ1zXK6yWYt7Z=U#kl%%4bpYQsN31tZQ!QN&&5}m zkqf!_#*@#BThFk8VH3gJvij!6#^(06mR7Mvmwh4f%WrvzGPhCi3-u@u3&50%-2&9`naPMo3poe?y0K|H?S=PyFnb0QEf;u&rtMd z6==gQ3;NW@orpHA{iKNjIo6FB`)GE59l z!cQAl(u+~6sAmarC^Ft0H_ YG&DVX25aZ_MXga@I)r#-AHt;mF95SnmjD0& 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 115c1dbdbc8d459a756b4729aca7a6e1ec38830a..e4eea6b7f4bb92ca7c95a9c63d7a342af9c5c670 100644 GIT binary patch delta 1302 zcmZuwO>7%Q6rP#gwKx9T#A`P+g=Q^RwH+sI!jBvz+RCJYG({oQksu(&tUMdXjbqdC zrU{Wv0?HvmRcV+?M4|}y5=zrUrATn#)(XUd!y2ssS!#g;;u@&LffF<9nka>*nQv#l z@4dJ4-t4YLe(ei<=J$I5vpY9$&3@zA2t0;*&_?`s!G%3E2-CC+hM=F$!XymPt1vy_ z!(DlJOWr~NY_r)i99#oiY`NVg!0TN`N8Lv^+@V3NV$c7Cg#Hr}#6kun!aE{*7s*X= z;^oX_4$$jj2;QgX+)1$ps(?Na`(Tw`c6W!dgkAfOQEMyiJa|MwPRuzh;ta|FQ*Q48s7tKG@DQvDRkS5AUp8A-6ZhrwRz>Wq ziXVed5cXda=#Zoi_}abz904R*llDbGQ`yt261^ZrRsu69ho%4qHR&BG^YFO{7r~M+ z1r}jK8Gm89bG)o!-PEN&I0lxGS>!(n~% zdTBu~W{r|b-d;XD&O9}}gtfM-rjr#zBgTb=0x|I9a`>e}#nf`d&`kqtiv@F5J9An~ z2!xl_bQhVLE#Rhm-l$acyg_(137LpomGg#~Jzpu82=94QU|Z73SKd~>ERQBZ#t@Z3 z!qpUcNh_p>Q;&0af+&8IXs9V$O|8o;lBdtY&EeMb;cH&C4!y=kL!GkKDeH}COP#iS z)4v6~e+)*y4@T>!Z^UnpHi9SY;E8oNJ8}K#+b>$92MgAld26)5NB4O)8p@ol%vtgr zz2utgPq3Qbi$9+N+v$T0??^)&Q=XB2;V#rhV#3`B4~LY|Iri0=%#iYOf8d*>G2&MlG%Bj^`$4EECd@_?tG>l~ii33qfN?@_+z-yVt4 zLGLl', 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) + +

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