260 lines
11 KiB
JavaScript
260 lines
11 KiB
JavaScript
class MailingImportDialog extends EICDialogContent {
|
|
|
|
actions = [
|
|
{
|
|
label: 'Cancel',
|
|
onclick: this.cancel.bind(this),
|
|
severity: 'secondary',
|
|
role: 'cancel'
|
|
},
|
|
{
|
|
label: 'Import recipients',
|
|
onclick: this.add.bind(this),
|
|
severity: 'primary',
|
|
role: 'add',
|
|
disabled: true
|
|
}
|
|
]
|
|
|
|
constructor(...args) {
|
|
super(...args)
|
|
Object.assign(this, app.helpers.validators)
|
|
}
|
|
|
|
DOMContentLoaded(options) {
|
|
this.model = options.model
|
|
this.mid = options.mid
|
|
let components = ui.eicfy(this.el)
|
|
this.fileUpload = new FileUpload(this.find('.import-dialog .browseBtn'), {
|
|
uploadUrl: '#',
|
|
uploadMethod: '',
|
|
submitLabel:'Analyse file',
|
|
allowedExtensions: ['xls','xlsx'],
|
|
allowedMimes:['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
'application/vnd.ms-excel'],
|
|
allowDrop: true,
|
|
maxFiles: 1,
|
|
minFiles: 1,
|
|
dndLabel: 'Drop your recipents Excel list here !',
|
|
noUpload: true,
|
|
});
|
|
this.fileUpload.onFileAdded = this.onFileAdded.bind(this)
|
|
this.fileUpload.onFileRemoved = this.onFileRemoved.bind(this)
|
|
this.fileUpload.onWrongFileType = this.onWrongFileType.bind(this)
|
|
this.report = this.find('.import-dialog .report')
|
|
this.reportTitle = this.find('.import-dialog .report-title')
|
|
this.reportDetails = this.find('.import-dialog .report-details')
|
|
this.removeDups = components.find(item => (item.el.classList.contains('remove-dups')))
|
|
this.recipientsToImport = {}
|
|
}
|
|
onFileAdded(fileIdx, file){
|
|
let reader = new FileReader()
|
|
reader.onload = this.verifyData.bind(this, reader, file)
|
|
this.fileUpload.browseBtn.loading = true
|
|
reader.readAsArrayBuffer(file)
|
|
}
|
|
|
|
onWrongFileType(file){
|
|
ui.show(this.report)
|
|
this.fileUpload.browseBtn.loading = false
|
|
// because stopping the loading reactivates the button by default (god's will)
|
|
if(this.fileUpload.filesList.length==0) this.fileUpload.browseBtn.disabled = false
|
|
else this.fileUpload.browseBtn.disabled = true
|
|
this.reportTitle.classList.add('error')
|
|
this.reportTitle.innerHTML = `Could not import file !`
|
|
this.reportDetails.innerHTML = `Wrong file type (${file.type})`
|
|
}
|
|
|
|
verifyData(reader, file){
|
|
this.fileUpload.browseBtn.loading = false
|
|
// because stopping the loading reactivates the button by default (god's will)
|
|
if(this.fileUpload.filesList.length==0) this.fileUpload.browseBtn.disabled = false
|
|
else this.fileUpload.browseBtn.disabled = true
|
|
let error
|
|
let workbook = null
|
|
let recipientsArray = null
|
|
let recipientsList = null
|
|
try{
|
|
workbook = XLSX.read(reader.result)
|
|
} catch(e) {
|
|
console.warn('Trying to parse Excel file:',e)
|
|
error = 'Error parsing this as Excel file !'
|
|
}
|
|
|
|
if((!error) && workbook){
|
|
let first_sheet = workbook.Sheets[workbook.SheetNames[0]]
|
|
try{
|
|
recipientsArray = XLSX.utils.sheet_to_json(first_sheet, {
|
|
header: 1
|
|
})
|
|
} catch(e) {
|
|
recipientsArray = null
|
|
error = 'Error parsing this as Excel sheet !'
|
|
}
|
|
if(recipientsArray && (!first_sheet['!margins'])) {
|
|
recipientsArray = null
|
|
error = 'Could not parse this as Excel file !'
|
|
}
|
|
}
|
|
let result = {}
|
|
if(recipientsArray){
|
|
result = this.getEmailRows(recipientsArray, file)
|
|
}
|
|
ui.show(this.report)
|
|
|
|
if(result.recipientsList){
|
|
this.reportTitle.classList.remove('error')
|
|
this.reportTitle.innerHTML = `Ready to import ${result.recipientsList.length} recipients !`
|
|
let headersList = ''
|
|
if(result.availableColumns.some(item => item.hint)){
|
|
headersList = result.availableColumns.map(item=>item.hint).join(', ')
|
|
}
|
|
this.reportDetails.innerHTML = `
|
|
<ul>
|
|
<li>Column used for the emails: <span success>${result.emailColName}</span></li>
|
|
<li><span success>${(result.rejectedRows.length>0) ? result.rejectedRows.length : 'No'}</span> ${ (result.rejectedRows.length==1) ? 'row was' : 'rows where' } rejected because column ${result.emailColName} did not contain an email.</li>
|
|
<li> ${(this.removeDups.value=='yes') ?
|
|
`Removed <span success>${result.nbDups}</span> emails that ${result.nbDups.length>1 ? 'where duplicates' : 'was a duplicate'}`:
|
|
`<span danger >I detected ${result.nbDups} duplicate${result.nbDups.length>1 ? 's' : ''} !</span> (which I did not remove)`}
|
|
</li>
|
|
<li><span ${headersList ? 'success': 'warning'}>${headersList ? 'Potential headers row: ': 'None headers found.'}</span>${headersList}</li>
|
|
</ul>
|
|
`
|
|
this.recipientsToImport = result
|
|
this.recipientsToImport.sourceName = file.name
|
|
this.recipientsToImport.sourceType = 'Excel'
|
|
} else {
|
|
this.reportTitle.classList.add('error')
|
|
this.reportTitle.innerHTML = error
|
|
this.reportDetails.innerHTML = ``
|
|
this.recipientsToImport = {}
|
|
}
|
|
|
|
if(this.fileUpload.filesList.length==0) this.actions.find(o => o.role == 'add').button.disabled = true
|
|
else this.actions.find(o => o.role == 'add').button.disabled = false
|
|
}
|
|
|
|
getEmailRows(recipientsArray, file){
|
|
if((!Array.isArray(recipientsArray)) || (recipientsArray.length<1)){
|
|
return({
|
|
err: 'The parsed Excel is not an array or has no rows !',
|
|
recipientsList:[],
|
|
importedData:[],
|
|
rejectedRows: [],
|
|
nbDups: 0,
|
|
emailColIdx:null,
|
|
emailColName:null,
|
|
availableColumns: null,
|
|
})
|
|
}
|
|
//const mailRFC5322Regex = /^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/
|
|
//Spot the column where there are the most email-addresses
|
|
let colsCount = []
|
|
for(let row of recipientsArray){
|
|
let colIdx = 0
|
|
for(let cell of row){
|
|
if(typeof(cell) === null) cell = ''
|
|
if(typeof(cell)=='string'){
|
|
if(typeof(colsCount[colIdx])=='undefined') colsCount[colIdx] = 0
|
|
if(this.validators.isValidEmail(cell)) colsCount[colIdx]++
|
|
}
|
|
colIdx++
|
|
}
|
|
}
|
|
|
|
// cleanup empty slots => don't use "in", or "of" !
|
|
for(let idx=0; idx<colsCount.length; idx++){ if(typeof(colsCount[idx])!='number') colsCount[idx]=0 }
|
|
// find winning column
|
|
let emailCol = colsCount.reduce((acc,val, idx)=>(val>colsCount[acc]?idx:acc),0)
|
|
|
|
// Now that we know where to look, remove rows without email in that col
|
|
let cleanRecipients = []
|
|
let rejectedRows = []
|
|
let firstValidRow = null
|
|
let rowIdx = 1
|
|
for(let row of recipientsArray){
|
|
let cell = row[emailCol]
|
|
if(typeof(cell)!='string'){
|
|
rejectedRows.push(rowIdx)
|
|
} else {
|
|
if(this.validators.isValidEmail(cell)){
|
|
cleanRecipients.push(row)
|
|
if(!firstValidRow) firstValidRow = rowIdx
|
|
}
|
|
else rejectedRows.push(rowIdx)
|
|
}
|
|
rowIdx++
|
|
}
|
|
|
|
// The row before the first valid email row might be headers
|
|
let availableColumns
|
|
if(firstValidRow>1){
|
|
let headersRow = recipientsArray[firstValidRow-2] // rowIdx & firstValidRow start at 1 => row before the first valid is -2 in recipientsArray
|
|
availableColumns = headersRow.map( (item, idx) => ({value: idx, hint: ((item.length > 150) ? item.slice(0, 20) + '...' : item ), isEmail: (idx==emailCol) }) )
|
|
} else {
|
|
availableColumns = recipientsArray[firstValidRow].map( (item, idx) => ({value: idx, hint: '', isEmail: (idx==emailCol) }) )
|
|
}
|
|
|
|
let nodups=[]
|
|
const uniqueRecipients = cleanRecipients.filter(item=>{ if(!nodups.includes(item[emailCol])) { nodups.push(item[emailCol]); return(true) } return(false) })
|
|
|
|
let finalRecipients
|
|
if(this.removeDups.value=='yes'){
|
|
finalRecipients = uniqueRecipients
|
|
} else {
|
|
finalRecipients = cleanRecipients
|
|
}
|
|
|
|
let recipientsList=[]
|
|
for(let recipient of finalRecipients) {
|
|
recipientsList.push({
|
|
sourceType: "Excel",
|
|
sourceName: file.name,
|
|
email:recipient[emailCol],
|
|
meta:{}, //Empty mappings
|
|
})
|
|
}
|
|
|
|
return({
|
|
err: false,
|
|
recipientsList: recipientsList,
|
|
importedData: finalRecipients,
|
|
rejectedRows: rejectedRows,
|
|
nbDups: cleanRecipients.length - uniqueRecipients.length,
|
|
emailColIdx: emailCol,
|
|
emailColName: XLSX.utils.encode_col(emailCol),
|
|
availableColumns: availableColumns,
|
|
})
|
|
}
|
|
|
|
onFileRemoved(fileIdx, file){
|
|
if(this.fileUpload.filesList.length==0){ // normally the case as maxFiles=1
|
|
this.reportTitle.innerHTML = ''
|
|
this.reportDetails.innerHTML = ''
|
|
ui.hide(this.report)
|
|
this.recipientsToImport = {}
|
|
this.actions.find(o => o.role == 'add').button.disabled = true
|
|
} else {
|
|
this.actions.find(o => o.role == 'add').button.disabled = false
|
|
}
|
|
}
|
|
|
|
async add() {
|
|
this.actions.find(o => o.role == 'add').button.loading = true
|
|
this.recipientsToImport.importDate = (new Date()).toISOString()
|
|
await this.model.saveImport(this.mid, {
|
|
sourceName: this.recipientsToImport.sourceName,
|
|
sourceType: this.recipientsToImport.sourceType,
|
|
data: this.recipientsToImport.importedData,
|
|
availableColumns: this.recipientsToImport.availableColumns,
|
|
})
|
|
this.commit(true)
|
|
}
|
|
|
|
onFailed() {
|
|
this.abort()
|
|
}
|
|
|
|
}
|
|
|
|
app.registerClass('MailingImportDialog',MailingImportDialog); |