unclean SPARC
This commit is contained in:
@@ -0,0 +1,872 @@
|
||||
class MailingSheetView extends EICDomContent {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
Object.assign(this, app.helpers.activeAttributes)
|
||||
Object.assign(this, app.helpers.basicDialogs)
|
||||
Object.assign(this, app.helpers.validators)
|
||||
}
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
|
||||
for(let model in options.models) this[model] = options.models[model]
|
||||
const components = ui.eicfy(this.el)
|
||||
|
||||
this.setupTriggers(components)
|
||||
this.setupRefs(components)
|
||||
|
||||
this.currentMailing = {}
|
||||
this.mailingId = options.id
|
||||
|
||||
this.workflow = new NodeMap(this.find('.workflow'), {
|
||||
resizable: true,
|
||||
allowDrag: false,
|
||||
orientation: 'linear',
|
||||
entity: { width: 120, height: 40, gap: 40 }
|
||||
})
|
||||
this.workflow.click = this.onWorkflowSelect.bind(this)
|
||||
|
||||
this.recipientsGrid = new DataGrid(this.find('.recipients-grid'), {
|
||||
headers: [
|
||||
{label: 'date', type: 'date'},
|
||||
{label: 'source'},
|
||||
{label: 'type', type: 'markup'},
|
||||
{label: '<span small class="icon-user" title="recipients"></span>'}
|
||||
],
|
||||
actions:[],
|
||||
})
|
||||
this.recipientsGrid.enableFooter = true
|
||||
this.recipientsGrid.updateFooter = ()=>{} //We'll manage our own footer thx
|
||||
|
||||
this.templateShadowRoot = this.outputs.templateHtmlBody.attachShadow({ mode: 'open' })
|
||||
|
||||
this.components['tabs'] = new Tab()
|
||||
this.components['tabs'].addTabs(this.findAll('.content-menu li'), this.findAll('.email-content'))
|
||||
ui.hide(this.components.deleteMailing.el)
|
||||
this.fillScheduleTime()
|
||||
this.loadMailingInfo(this.mailingId )
|
||||
}
|
||||
|
||||
|
||||
/****************************** Loading & refresh tabs **************************************/
|
||||
async loadMailingInfo(mailingId) {
|
||||
this.setAsyncLoading(true)
|
||||
this.currentMailing = await this.mailings.get(mailingId)
|
||||
|
||||
const rawPaths = await this.mailings.getReadableFolders()
|
||||
const authorizedPaths = Array.isArray(rawPaths) ? rawPaths : (rawPaths?.authorizedPaths || [])
|
||||
|
||||
if (!authorizedPaths.includes(this.currentMailing.path)) {
|
||||
const wrapper = document.querySelector('.massmailer.mailing-sheet section')
|
||||
if (wrapper) {
|
||||
wrapper.innerHTML = `
|
||||
<div style="padding: 2rem; text-align: center;">
|
||||
<h2 style="color:#b00020;">Access Denied</h2>
|
||||
<p>You're not authorized to access to view this mailing</p>
|
||||
</div>`
|
||||
}
|
||||
this.setAsyncLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
this.refresh()
|
||||
this.setAsyncLoading(false)
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
this.workflow.data = app.meta.collections['wf-mailings'] //Clear
|
||||
if(this.currentMailing.status == 'sent'){
|
||||
this.components.chart = new PieChart(this.outputs.eicpiechart, { height: 120 })
|
||||
this.components.chart.data = [
|
||||
{ severity: 'danger', value: this.currentMailing.kpis.bouncesCount },
|
||||
{ severity: 'success', value: this.currentMailing.kpis.readCount },
|
||||
{
|
||||
severity: 'secondary',
|
||||
value: this.currentMailing.nbRecipients - this.currentMailing.kpis.bouncesCount - this.currentMailing.kpis.readCount
|
||||
}
|
||||
]
|
||||
//TODO Show stats & graph tab
|
||||
} else {
|
||||
this.fillGeneralInfo()
|
||||
this.fillLatestActivity()
|
||||
this.refreshCreated()
|
||||
await this.refreshContent()
|
||||
this.fillRecipients()
|
||||
this.refreshMappings()
|
||||
this.refreshReview()
|
||||
this.refreshSchedule()
|
||||
this.refreshWorflow()
|
||||
}
|
||||
|
||||
//this.setupTriggers()
|
||||
}
|
||||
|
||||
fillGeneralInfo(){
|
||||
this.output('name', this.currentMailing.name)
|
||||
this.output('created', ui.format.date(this.currentMailing.statusHistory[0].dateTime))
|
||||
this.output('author', this.currentMailing.statusHistory[0].changedBy.euLoginId)
|
||||
this.output('status', this.mailings.getStatusLabel()[this.currentMailing.status])
|
||||
this.output('infosPath', this.pathChips(this.currentMailing.path))
|
||||
this.output('recipients', this.currentMailing.nbRecipients)
|
||||
this.output('pending', (this.currentMailing.nbRecipients>0) ? this.currentMailing.nbRecipients - this.currentMailing.kpis.bouncesCount - this.currentMailing.kpis.readCount : 'N/A')
|
||||
this.output('bounced', this.currentMailing.kpis.bouncesCount || 'N/A')
|
||||
this.output('reached', this.currentMailing.kpis.readCount || 'N/A')
|
||||
if(['created','draft'].includes(this.currentMailing.status)){
|
||||
ui.show(this.components.deleteMailing.el)
|
||||
} else {
|
||||
ui.hide(this.components.deleteMailing.el)
|
||||
}
|
||||
}
|
||||
|
||||
fillLatestActivity(){
|
||||
this.output('history', '')
|
||||
let prevstatus = ''
|
||||
for(let historyEntry of [...this.currentMailing.statusHistory].reverse()){
|
||||
let action
|
||||
if((historyEntry.value=='draft') && (prevstatus!= 'rejected')) { action = 'Draft saved' }
|
||||
else action = this.mailings.getStatusLabel()[historyEntry.value]
|
||||
let severity
|
||||
switch(historyEntry.value) {
|
||||
case 'rejected': severity = 'danger'; break
|
||||
case 'approved': severity = 'success'; break
|
||||
case 'submitted': severity = 'warning'; break
|
||||
case 'sent': severity = 'primary'; break
|
||||
default : severity = 'info'; break
|
||||
}
|
||||
let markup = `<li>
|
||||
<div eicalert muted ${severity} xsmall>
|
||||
${action}: <b>${ (new Intl.DateTimeFormat("fr-FR", {timeZone: "Europe/Paris", dateStyle:'medium', timeStyle:'medium'})).format(new Date(historyEntry.dateTime))}</b><br>
|
||||
by <b>${historyEntry.changedBy.firstname} ${historyEntry.changedBy.lastname}</b>
|
||||
</div></li>`
|
||||
prevstatus = historyEntry.value
|
||||
this.outputs.history.append(ui.create(markup))
|
||||
}
|
||||
}
|
||||
|
||||
refreshCreated(){
|
||||
this.components.saveCreated.label = (this.currentMailing.status=='created') ? 'Save' : 'Change'
|
||||
this.components.saveCreated.severity = (this.currentMailing.status=='created') ? 'primary' : 'secondary'
|
||||
this.components.mailingName.value = this.currentMailing.name
|
||||
this.outputs.createPath.dataset.value = this.currentMailing.path
|
||||
this.outputs.createPath.innerHTML = this.pathChips(this.currentMailing.path)
|
||||
this.onNamePathChange()
|
||||
}
|
||||
|
||||
async refreshContent(){
|
||||
if(this.currentMailing.template.id) {
|
||||
this.setAsyncLoading(true, ['mainPane'])
|
||||
let templateInfo = await this.templates.get(this.currentMailing.template.id)
|
||||
this.setAsyncLoading(false, ['mainPane'])
|
||||
if(templateInfo) {
|
||||
this.currentMailing.template = templateInfo
|
||||
this.fillTemplate(templateInfo);
|
||||
}
|
||||
}
|
||||
this.components.changeTemplate.severity = (this.currentMailing.template.id) ? 'secondary' : 'primary'
|
||||
}
|
||||
|
||||
refreshWorflow() {
|
||||
const stepActivations = this.mailings.getStepActivations(this.currentMailing, this.templates.getTokens(this.currentMailing.template))
|
||||
this.workflow.data = app.meta.collections['wf-mailings']
|
||||
for(const entity of this.workflow.entities){
|
||||
entity.disabled = stepActivations[entity.id].disabled
|
||||
entity.severity = stepActivations[entity.id].severity
|
||||
}
|
||||
let entity = null
|
||||
if(this.currentMailing.status!='scheduled'){ entity = this.workflow.entities.find(e => e.severity == 'warning') }
|
||||
else { entity = this.workflow.entities.find(e => e.id == 'schedule') }
|
||||
this.onWorkflowSelect(entity)
|
||||
}
|
||||
|
||||
fillTemplate(templateInfo){
|
||||
const filterText = (text) => {
|
||||
let element = document.createElement('div')
|
||||
element.textContent = text
|
||||
text = element.innerHTML
|
||||
text = text.length > 100 ? text.slice(0, 100) + '...' : text
|
||||
return text
|
||||
}
|
||||
|
||||
let subject = filterText(templateInfo.meta.subject ? templateInfo.meta.subject : '')
|
||||
this.output('templateSubject', subject.replace(this.validators.mustacheTokenRegex,'<span class="token" eicchip success>$1</span>'))
|
||||
let html = atob(templateInfo.html).replace(this.validators.mustacheTokenRegex,'<span class="token" eicchip success>$1</span>')
|
||||
this.templateShadowRoot.innerHTML = `<style>
|
||||
span.token[eicchip]{
|
||||
border-radius: 60px !important;
|
||||
background-color: var(--eicui-base-color-success-100);
|
||||
color: var(--eicui-base-color-white);
|
||||
font-size: 0.75rem;
|
||||
padding: var(--eicui-base-spacing-2xs) var(--eicui-base-spacing-s);
|
||||
min-height: var(--eicui-base-spacing-xl);
|
||||
display: inline-grid;
|
||||
grid-template-columns: min-content min-content min-content;
|
||||
min-height: 0;
|
||||
align-items: center;
|
||||
margin: 1px var(--eicui-base-spacing-2xs);
|
||||
pointer-events: all;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
text-shadow: none;
|
||||
}
|
||||
</style>\n${html}`
|
||||
|
||||
let altText = filterText(templateInfo.meta.altText ? atob(templateInfo.meta.altText) : '')
|
||||
this.output('templateAlternateText', `<pre>${altText.replace(this.validators.mustacheTokenRegex,'<span class="token" eicchip success>$1</span>')}</pre>`)
|
||||
this.output('templateName', templateInfo.name.length > 60 ? templateInfo.name.slice(0, 60) + '...' : templateInfo.name)
|
||||
this.outputs['templateName'].setAttribute('title', templateInfo.name)
|
||||
}
|
||||
|
||||
fillRecipients() {
|
||||
this.recipientsGrid.clear()
|
||||
let totalUnfilteredRecipients = 0
|
||||
|
||||
if(this.mailings.canImport()) { ui.show(this.components.btnImport.el, 'inline-flex') }
|
||||
else { ui.hide(this.components.btnImport.el) }
|
||||
if(this.mailings.canImportExclusion){ ui.show(this.components.btnExclusion.el, 'inline-flex') }
|
||||
else{ui.hide(this.components.btnExclusion.el) }
|
||||
if(this.mailings.canFetch()) { ui.show(this.components.btnFetch.el, 'inline-flex') }
|
||||
else { ui.hide(this.components.btnFetch.el) }
|
||||
|
||||
for(const emailSource of this.currentMailing.sources){
|
||||
const oneImport = this.currentMailing.imports.find(i => i.sourceId==emailSource.id)
|
||||
if(!emailSource.negative) totalUnfilteredRecipients += oneImport.data.length
|
||||
let sourceType = (emailSource.sourceType=='Excel') ? `<span class="icon-xls" title="Imported Excel" ${emailSource.negative ? 'warning':''}></span>` : `<span class="icon-market" title="Database query (${emailSource.sourceType})"></span>`
|
||||
if(emailSource.negative) sourceType = `<span eicbadge="" warning="" xsmall="" class="icon-deny" title="Exclusion list"></span>${sourceType}`
|
||||
let row = this.recipientsGrid.addRow(emailSource.id, [
|
||||
oneImport.importDate, // expecting source import datestamp
|
||||
emailSource.sourceName,
|
||||
sourceType,
|
||||
oneImport.data.length
|
||||
], true)
|
||||
|
||||
if(emailSource.sourceType!='Excel'){
|
||||
// let btnEditQC = new Button(null,{ icon: 'icon-edit', severity: 'primary', size: 'xsmall', hint: 'Change the query'})
|
||||
// btnEditQC.click = this.onSourceEditQC.bind(this, oneImport.id )
|
||||
// row.querySelector('.cell.actions').appendChild(btnEditQC.el)
|
||||
|
||||
let btnRefreshQC = new Button(null,{ icon: 'icon-refresh', severity: 'primary', size: 'xsmall', hint: 'Refresh data by replaying the query now'})
|
||||
btnRefreshQC.click = this.onSourceRefreshQC.bind(this, oneImport.id, btnRefreshQC )
|
||||
row.querySelector('.cell.actions').appendChild(btnRefreshQC.el)
|
||||
}
|
||||
|
||||
let btnDel = new Button(null,{ icon: 'icon-bin', severity: 'danger', size: 'xsmall', hint: 'Remove'})
|
||||
btnDel.click = this.onSourceDel.bind(this, oneImport.id )
|
||||
row.querySelector('.cell.actions').appendChild(btnDel.el)
|
||||
|
||||
}
|
||||
this.recipientsGrid.filter() // because skipfilter = true because potentially Big Xls...
|
||||
this.recipientsGrid.footer.classList.add('row')
|
||||
this.recipientsGrid.footer.innerHTML = `
|
||||
<div class="cell"></div>
|
||||
<div class="cell"></div>
|
||||
<div class="cell" title="Total recipients"><i>Total sendable recipients</i></div>
|
||||
<div class="cell" title=""></div>
|
||||
<div class="cell" title="${this.currentMailing.nbRecipients}"><i>${this.currentMailing.nbRecipients}</i></div>
|
||||
<div class="cell actions"></div>
|
||||
`
|
||||
//this.currentMailing.nbRecipients = totalRecipients
|
||||
const dupes = this.mailings.findDupes(this.currentMailing.imports)
|
||||
this.output('totalRecipients', totalUnfilteredRecipients)
|
||||
this.output('totalRecipientsExcluded', totalUnfilteredRecipients-this.currentMailing.nbRecipients)
|
||||
if(totalUnfilteredRecipients-this.currentMailing.nbRecipients>0) {
|
||||
this.outputs.totalRecipientsExcluded.setAttribute('warning','')
|
||||
this.outputs.labelExcluded.setAttribute('warning','')
|
||||
} else {
|
||||
this.outputs.totalRecipientsExcluded.removeAttribute('warning')
|
||||
this.outputs.labelExcluded.removeAttribute('warning')
|
||||
}
|
||||
this.output('totalFinalRecipients', this.currentMailing.nbRecipients)
|
||||
this.output('totalRecipientsDupes', Object.keys(dupes).length)
|
||||
if(Object.keys(dupes).length>0){
|
||||
this.outputs.totalRecipients.parent
|
||||
this.outputs.totalRecipientsDupes.setAttribute('danger','')
|
||||
this.outputs.labelDupes.setAttribute('danger','')
|
||||
this.components.dupsInfo.dupes = dupes //hell yeah
|
||||
ui.show(this.components.dupsInfo.el, 'inline-flex')
|
||||
} else {
|
||||
this.outputs.totalRecipientsDupes.removeAttribute('danger')
|
||||
this.outputs.labelDupes.removeAttribute('danger')
|
||||
this.components.dupsInfo.dupes = null
|
||||
ui.hide(this.components.dupsInfo.el)
|
||||
}
|
||||
}
|
||||
|
||||
refreshMappings() {
|
||||
if((!this.currentMailing.template) || (!this.currentMailing.template.id)) return
|
||||
const templateTokens = this.templates.getTokens(this.currentMailing.template)
|
||||
this.output('mappingsPanel', '')
|
||||
const nbMapMissingPerSrc = this.mailings.nbMapMissingPerSrc(this.currentMailing, templateTokens)
|
||||
for(let source of this.currentMailing.sources){
|
||||
if(source.negative) continue
|
||||
const sourceType = (source.sourceType=='Excel') ? `<span class="icon-table" title="Imported Excel"></span>` : `<span class="icon-market" title="Database query (${source.sourceType})"></span>`
|
||||
let article = ui.create(`<div>
|
||||
<article eiccard collapsable ${nbMapMissingPerSrc[source.id] > 0 ? '': 'collapsed' }>
|
||||
<header>
|
||||
<h1>${sourceType} ${source.sourceName}</h1>
|
||||
<h2>${nbMapMissingPerSrc[source.id] > 0 ? `<span eicchip xsmall warning>${nbMapMissingPerSrc[source.id]} missing</span>`: '<span eicchip small success>complete</span>' } </h2>
|
||||
</header>
|
||||
<section>
|
||||
<ul nonbulleted>
|
||||
<li class="cols-2">
|
||||
<label large>Content variable</label>
|
||||
<label large>Recipient data</label>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</article></div>
|
||||
`)
|
||||
let oneMapping = this.currentMailing.mappings.find(i => i.sourceId==source.id)
|
||||
if(!oneMapping){
|
||||
oneMapping = {
|
||||
"sourceName": source.sourceName,
|
||||
"sourceType": source.sourceType,
|
||||
"mappings": { },
|
||||
"sourceId": source.id,
|
||||
}
|
||||
this.currentMailing.mappings.push(oneMapping)
|
||||
}
|
||||
let ul = article.querySelector('section ul')
|
||||
for(let variable of templateTokens) {
|
||||
let severity, label, title
|
||||
if(variable in oneMapping.mappings) {
|
||||
severity = 'secondary'
|
||||
label = oneMapping.mappings[variable].hint
|
||||
title = oneMapping.mappings[variable].hint
|
||||
} else {
|
||||
severity = 'primary'
|
||||
label = 'Select...'
|
||||
title = ''
|
||||
}
|
||||
let li = ui.create(`<li class="cols-2 middle">
|
||||
<div small><b>${variable}</b></div>
|
||||
<div>
|
||||
<button eicbutton small ${severity} rounded data-token="${variable}" data-sourceid="${source.id}" data-trigger="onMappingChange" title="${title}">
|
||||
<span>${label}</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>`)
|
||||
ul.appendChild(li)
|
||||
}
|
||||
let components = ui.eicfy(article)
|
||||
this.setupTriggers(components)
|
||||
this.outputs.mappingsPanel.append(article)
|
||||
}
|
||||
}
|
||||
|
||||
refreshReview(){
|
||||
const [block2display, contents] = this.mailings.getReviewBlocks(this.currentMailing)
|
||||
const allBlocks = ['revieweePane','reviewerPane',
|
||||
'revieweeOngoing','revieweeApproved','revieweeRejected','revieweeRequest',
|
||||
'reviewerChoice','reviewerApproved','reviewerRejected']
|
||||
allBlocks.forEach(blockName => ui.hide(this.outputs[blockName]))
|
||||
block2display.forEach(blockName => ui.show(this.outputs[blockName], 'grid'))
|
||||
for(let blockName in contents) this.output(blockName, contents[blockName])
|
||||
}
|
||||
|
||||
refreshSchedule(){
|
||||
if(this.currentMailing.status=='scheduled'){
|
||||
ui.hide(this.outputs.toSchedule)
|
||||
ui.show(this.outputs.scheduled)
|
||||
const scheduledStatus = this.mailings.getLatestStatus(this.currentMailing,'scheduled')
|
||||
const scheduleDate = (new Intl.DateTimeFormat("en-GB", {timeZone: "Europe/Paris", dateStyle:'medium', timeStyle:'medium'}))
|
||||
.format(new Date(scheduledStatus.meta.scheduleDate))
|
||||
this.output('scheduledDate', scheduleDate)
|
||||
|
||||
if(app.User.roles.includes('MAIL_Sender')) ui.show(this.outputs.unschedule, 'grid')
|
||||
else ui.hide(this.outputs.unschedule)
|
||||
} else {
|
||||
ui.hide(this.outputs.scheduled)
|
||||
ui.show(this.outputs.toSchedule)
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************** Event handlers ******************************************/
|
||||
async onPathBrowse() {
|
||||
const button = this.find('button[data-trigger="onPathBrowse"]')
|
||||
this.showLoading(button)
|
||||
try {
|
||||
let result = await this.mailings.getReadableFolders()
|
||||
if (!result || typeof result !== 'object') {
|
||||
console.error("getReadableFolders returned invalid result", result)
|
||||
return
|
||||
}
|
||||
|
||||
let { mailings, authorizedPaths, foldersWithFiles, rights } = result
|
||||
if (!Array.isArray(authorizedPaths)) authorizedPaths = []
|
||||
if (!authorizedPaths.includes('/mailing')) authorizedPaths.unshift('/mailing')
|
||||
this.mailings.ffs.authorizedPaths = authorizedPaths;
|
||||
this.mailings.ffs.foldersWithFiles = foldersWithFiles || []
|
||||
|
||||
if (!mailings?.mailings) {
|
||||
mailings = { mailings }
|
||||
}
|
||||
|
||||
const fileList = Array.isArray(mailings.mailings) ? mailings.mailings : []
|
||||
this.mailings.ffs.loadStructure({ mailing: mailings.mailings || mailings }, fileList)
|
||||
this.mailings.ffs.currentPath = '/mailing'
|
||||
this.mailings.ffs.authorizedPaths = authorizedPaths
|
||||
this.mailings.ffs.rights = rights || []
|
||||
|
||||
this.createPath = this.find('div[data-output="createPath"]')
|
||||
this.spanPath = this.createPath ? this.createPath.querySelector('span[data-path]') : null
|
||||
this.currentPath = this.spanPath ? this.spanPath.getAttribute('data-path') : '/mailing'
|
||||
|
||||
let dialogResult = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'templates/Ffs/dialogs/FileBrowserDialog',
|
||||
{ title: `Select a path...` },
|
||||
{
|
||||
ffs: this.mailings.ffs,
|
||||
editable: false,
|
||||
startPath: this.currentPath,
|
||||
canManage: true,
|
||||
mailings: this.mailings,
|
||||
context: 'mailings'
|
||||
}
|
||||
)
|
||||
)
|
||||
if (dialogResult) {
|
||||
this.currentMailing.path = dialogResult
|
||||
this.output('createPath', this.pathChips(this.currentMailing.path))
|
||||
this.outputs.createPath.dataset.value = dialogResult
|
||||
this.onNamePathChange()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error on dialog PathSearch loading :", error)
|
||||
} finally {
|
||||
this.hideLoading(button);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onNamePathChange(){
|
||||
this.components.saveCreated.disabled = ((this.components.mailingName.value=='') || (this.outputs.createPath.dataset.value ==''))
|
||||
}
|
||||
|
||||
async onSaveCreated(component, event){
|
||||
this.currentMailing.name = this.components.mailingName.value
|
||||
this.currentMailing.path = this.outputs.createPath.dataset.value
|
||||
this.currentMailing.status = 'draft'
|
||||
this.components.saveCreated.loading = true
|
||||
try {
|
||||
await this.mailings.save(this.currentMailing)
|
||||
ui.growl.append('Mailing saved !', 'success',3000)
|
||||
} catch(e) {}
|
||||
this.components.saveCreated.loading = false
|
||||
this.loadMailingInfo(this.currentMailing.id)
|
||||
}
|
||||
|
||||
onWorkflowSelect(entity) {
|
||||
if(!entity) return
|
||||
this.findAll('.sheet-content').forEach(el => {ui.hide(el)})
|
||||
let target = entity.el.dataset.id
|
||||
let tab = this.find(`[data-content="${target}"]`)
|
||||
const stepActivations = this.mailings.getStepActivations(this.currentMailing, this.templates.getTokens(this.currentMailing.template))
|
||||
if(stepActivations[target].disabled) return // Maybe this is the logical next step, brought automatically, but your role doesn't allow for it.
|
||||
if(tab) {
|
||||
ui.show(tab)
|
||||
switch(target){
|
||||
case 'template':
|
||||
// todo display content corresponding to status/role
|
||||
|
||||
break
|
||||
case 'recipients':
|
||||
// todo display content corresponding to status/role
|
||||
|
||||
break
|
||||
case 'mappings':
|
||||
// todo display content corresponding to status/role
|
||||
|
||||
break
|
||||
case 'approval':
|
||||
// todo display content corresponding to status/role
|
||||
break
|
||||
case 'schedule':
|
||||
// todo display content corresponding to status/role
|
||||
//if(!this.mailings.hasPrivilege('schedule')) return
|
||||
break
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
ui.growl.append('Content unavailable', 'danger')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async onTemplateChange() {
|
||||
const templateTokens = this.templates.getTokens(this.currentMailing.template)
|
||||
if(templateTokens && (templateTokens.length>0) && (this.currentMailing.mappings.length>0)){
|
||||
let result = await this.confirmDialog({
|
||||
title: `CAUTION: Existing mappings...`,
|
||||
message: `
|
||||
<p>You have existing mappings for the current Template.<br>
|
||||
If you do select a new template (after previewing it),<br>
|
||||
<b>these mappings will be lost !</b></p>`,
|
||||
okLabel: 'I understand',
|
||||
severity: 'warning',
|
||||
})
|
||||
if(!result) return
|
||||
}
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'comms/mailings/sheet/dialogs/MailingTemplatePreviewDialog',
|
||||
{ title: `Pick your content template` },
|
||||
{ status: ['prod'],
|
||||
currentMailing: this.currentMailing,
|
||||
templateModel: this.templates,
|
||||
mailingModel: this.mailings,
|
||||
}
|
||||
)
|
||||
)
|
||||
if(result) this.loadMailingInfo(this.currentMailing.id)
|
||||
}
|
||||
|
||||
async onSourceDel(importId, event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const oneImport = this.currentMailing.imports.find(i => i.id==importId)
|
||||
let result = await this.confirmDialog({
|
||||
title: (!oneImport.negative) ? `Remove these ${oneImport.data.length} recipients ?` : `Remove this exclusion list and re-allow ${oneImport.data.length} emails ?`,
|
||||
message: `<p>Are you sure ?</p>`,
|
||||
okLabel: 'Delete',
|
||||
severity: 'danger',
|
||||
okPromise: data => this.mailings.deleteImport(this.currentMailing.id, importId)
|
||||
})
|
||||
if(result) this.loadMailingInfo(this.currentMailing.id)
|
||||
}
|
||||
|
||||
async onSourceRefreshQC(importId, btn, event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
btn.loading = true
|
||||
|
||||
const curImport = this.currentMailing.imports.find(i => i.id==importId)
|
||||
const curSource = this.currentMailing.sources.find(i => i.mli_id==importId)
|
||||
|
||||
const tmpResults = await this.contactMgr.getSample(curSource.query_id, curSource.query_params)
|
||||
|
||||
console.log('=====>tmpResults', tmpResults)
|
||||
|
||||
let result = await this.confirmDialog({
|
||||
title: `
|
||||
`,
|
||||
message: `<p>You are about to re-run the query !<br>
|
||||
Currenly you have <b>${curImport.data.length} recipients</b>.<br>
|
||||
After re-running the query, you would have <b>${tmpResults.nbRows} recipients</b>.</p>
|
||||
<p>Update the data?</p>`,
|
||||
okLabel: 'Yes, rerun the query !',
|
||||
severity: 'warning',
|
||||
okPromise: data => this.mailings.refreshImport(this.currentMailing.id, {
|
||||
sourceType: curImport.sourceType,
|
||||
refreshOnly: true,
|
||||
importId: curImport.id,
|
||||
}, importId).then(
|
||||
() => btn.loading = false,
|
||||
() => btn.loading = false
|
||||
|
||||
)
|
||||
})
|
||||
if(result) this.loadMailingInfo(this.currentMailing.id)
|
||||
btn.loading = false
|
||||
}
|
||||
|
||||
// async onSourceEditQC(importId, event) {
|
||||
// event.preventDefault()
|
||||
// event.stopPropagation()
|
||||
// const curImport = this.currentMailing.imports.find(i => i.id==importId)
|
||||
// console.log('=====>onSourceEditQC:', curImport.data.length)
|
||||
// }
|
||||
|
||||
async onImport(){
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'comms/mailings/sheet/dialogs/MailingImportDialog',
|
||||
{ title: `Import recipients from Excel file...` },
|
||||
{ model: this.mailings,
|
||||
mid: this.currentMailing.id,
|
||||
}
|
||||
)
|
||||
)
|
||||
if(result) this.loadMailingInfo(this.currentMailing.id)
|
||||
}
|
||||
|
||||
async onExclusion(){
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'comms/mailings/sheet/dialogs/MailingExclusionDialog',
|
||||
{ title: `Import exclusion list from Excel file...` },
|
||||
{ model: this.mailings,
|
||||
mid: this.currentMailing.id,
|
||||
}
|
||||
)
|
||||
)
|
||||
if(result) this.loadMailingInfo(this.currentMailing.id)
|
||||
}
|
||||
|
||||
async onFetch(){
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'comms/mailings/sheet/dialogs/MailingFetchDialog',
|
||||
{ title: `Fetch recipients from MyEIC applications...` },
|
||||
{
|
||||
models: {
|
||||
contacts: this.contactMgr,
|
||||
mailings: this.mailings,
|
||||
},
|
||||
mid: this.currentMailing.id,
|
||||
}
|
||||
)
|
||||
)
|
||||
if(result) this.loadMailingInfo(this.currentMailing.id)
|
||||
}
|
||||
|
||||
async onDupsInfo(component, event) {
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'comms/mailings/sheet/dialogs/MailingDuplicatesDialog',
|
||||
{ title: `Duplicate emails...` },
|
||||
{ dupes: component.dupes,
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async onMappingChange(component, event) {
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'comms/mailings/sheet/dialogs/MailingMappingDialog',
|
||||
{ title: `Link template variable to a column in the Excel file...` },
|
||||
{
|
||||
model: this.mailings,
|
||||
token: component._el.dataset.token,
|
||||
sourceId: component._el.dataset.sourceid,
|
||||
currentMailing: this.currentMailing,
|
||||
}
|
||||
)
|
||||
)
|
||||
if(result) this.loadMailingInfo(this.currentMailing.id)
|
||||
}
|
||||
|
||||
async onTestSend(){
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'comms/mailings/sheet/dialogs/MailingTestDialog',
|
||||
{ title: `Sending a test mail...` },
|
||||
{ tokens: this.templates.getTokens(this.currentMailing.template) }
|
||||
)
|
||||
)
|
||||
|
||||
if(result){
|
||||
await this.mailings.test(this.currentMailing.id, this.currentMailing.template.id, result.email, result.tokenValues)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async onReviewRequest(){
|
||||
let result = await this.confirmDialog({
|
||||
title: 'Request review ?',
|
||||
message: `You're about to request for this mailing to be reviewed.<br>
|
||||
During the review process, the mailing won't be editable anymore.
|
||||
<div class="prompts">
|
||||
<label small>Remarks / comments for the reviewer:</label>
|
||||
<textarea name="comments" eictextarea maxlen="500" cols="50"/></textarea>
|
||||
</div>
|
||||
`,
|
||||
promptsClass: 'prompts',
|
||||
okLabel: 'Request review',
|
||||
severity: 'warning',
|
||||
okSeverity: 'primary',
|
||||
okPromise: (data) => {
|
||||
this.currentMailing.status = 'submitted'
|
||||
this.currentMailing.statusMeta = { comments: data.comments }
|
||||
return(this.mailings.save(this.currentMailing))
|
||||
}
|
||||
})
|
||||
|
||||
if(result) { this.loadMailingInfo(this.currentMailing.id) }
|
||||
}
|
||||
|
||||
async onReviewApprove() {
|
||||
let result = await this.confirmDialog({
|
||||
title: 'Approve mailing ?',
|
||||
message: `Approve this mailing for sending ?`,
|
||||
okLabel: 'Approve',
|
||||
severity: 'success',
|
||||
okSeverity: 'primary',
|
||||
okPromise: (data) => {
|
||||
this.currentMailing.status = 'approved'
|
||||
return(this.mailings.save(this.currentMailing))
|
||||
}
|
||||
})
|
||||
|
||||
if(result) { this.loadMailingInfo(this.currentMailing.id) }
|
||||
}
|
||||
|
||||
async onReviewReject(){
|
||||
let result = await this.confirmDialog({
|
||||
title: 'Reject mailing',
|
||||
message: `Reject sending of this mailing ?
|
||||
<div class="prompts">
|
||||
<label small>Changes requested / Reason for rejection:</label>
|
||||
<textarea name="reason" eictextarea maxlen="500" cols="50"/></textarea>
|
||||
</div>
|
||||
`,
|
||||
promptsClass: 'prompts',
|
||||
okLabel: 'Reject sending',
|
||||
severity: 'warning',
|
||||
okSeverity: 'primary',
|
||||
okPromise: (data) => {
|
||||
this.currentMailing.status = 'rejected'
|
||||
this.currentMailing.statusMeta = { reason: data.reason }
|
||||
return(this.mailings.save(this.currentMailing))
|
||||
}
|
||||
})
|
||||
|
||||
if(result) { this.loadMailingInfo(this.currentMailing.id) }
|
||||
}
|
||||
|
||||
onScheduleDateChange(){
|
||||
this.components.scheduleTimeLater.value = ''
|
||||
this.fillScheduleTime()
|
||||
this.components.scheduleButton.disabled = true
|
||||
}
|
||||
|
||||
onScheduleTimeChange() { this.components.scheduleButton.disabled = (this.components.scheduleTimeLater.value == '') }
|
||||
|
||||
fillScheduleTime(){
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const thisHour = (new Date()).getHours()
|
||||
this.components.scheduleTimeLater.el.innerHTML=''
|
||||
if(this.components.scheduleDateLater.value==today){
|
||||
if((thisHour+1) < 24) {
|
||||
for(let h=thisHour+1; h<24; h++) this.components.scheduleTimeLater.el.append(ui.create(`<option value="${h}">${h.toString().padStart(2, '0')}:00 (${h < 12 ? 'AM' : 'PM'})</option>`))
|
||||
}
|
||||
} else {
|
||||
for(let h=0; h<24; h++) this.components.scheduleTimeLater.el.append(ui.create(`<option value="${h}">${h.toString().padStart(2, '0')}:00 (${h < 12 ? 'AM' : 'PM'})</option>`))
|
||||
}
|
||||
}
|
||||
|
||||
async onSendingNow(component, event) { await this.sendMailing(this.components.scheduleDateNow.value, this.components.scheduleTimeNow.value) }
|
||||
|
||||
async onSendingLater(component, event) { await this.sendMailing(this.components.scheduleDateLater.value, this.components.scheduleTimeLater.value) }
|
||||
|
||||
async sendMailing(scheduleDate, scheduleTime) {
|
||||
const getCETOffset = () => {
|
||||
const date = new Date()
|
||||
const options = { timeZone: "Europe/Paris", timeZoneName: "short" }
|
||||
const formatter = new Intl.DateTimeFormat("en-US", options)
|
||||
const parts = formatter.formatToParts(date)
|
||||
const timeZoneOffset = parts.find(part => part.type === "timeZoneName").value
|
||||
return timeZoneOffset.includes("CEST") ? 2 : 1
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const [year, month, day] = scheduleDate.split("-")
|
||||
const europeanDate = `${day}/${month}/${year}`
|
||||
let when
|
||||
if(scheduleDate==today){
|
||||
when = scheduleTime=='5min' ? 'in five minutes' : `today at ${scheduleTime.toString().padStart(2, '0')}:00 (${scheduleTime < 12 ? 'AM' : 'PM'})`
|
||||
} else {
|
||||
when = `on ${europeanDate} at ${scheduleTime.toString().padStart(2, '0')}:00 (${scheduleTime < 12 ? 'AM' : 'PM'})`
|
||||
}
|
||||
let UTCScheduleTime
|
||||
if(scheduleTime!='5min'){
|
||||
UTCScheduleTime = (new Date(`${scheduleDate}T${scheduleTime.toString().padStart(2, '0')}:00:00+0${getCETOffset()}:00`)).toISOString()
|
||||
} else {
|
||||
UTCScheduleTime = (new Date((new Date).getTime() + 5 * 60 * 1000)).toISOString()
|
||||
}
|
||||
|
||||
let result = await this.confirmDialog({
|
||||
title: 'Send mailing ?',
|
||||
message: `You are about to schedule the sending of this mailing ${when}`,
|
||||
okLabel: 'Schedule sending',
|
||||
severity: 'secondary',
|
||||
okSeverity: 'danger',
|
||||
okPromise: (data) => {
|
||||
return(this.mailings.schedule(this.currentMailing.id, UTCScheduleTime))
|
||||
}
|
||||
})
|
||||
|
||||
if(result) { this.loadMailingInfo(this.currentMailing.id); }
|
||||
}
|
||||
|
||||
async onAbortSending(component, event) {
|
||||
let result = await this.confirmDialog({
|
||||
title: 'Abort the sending ?',
|
||||
message: `You are about to <b>abort the sending of this mailing !</b><br>
|
||||
If you proceed, it will be editable again, <br>
|
||||
and it will have to be reviewed again.<br>
|
||||
Is this really what you want to do ?`,
|
||||
okLabel: 'Abort',
|
||||
severity: 'warning',
|
||||
cancelSeverity: 'primary',
|
||||
okSeverity: 'secondary',
|
||||
okPromise: (data) => {
|
||||
this.currentMailing.status = 'draft'
|
||||
return(this.mailings.save(this.currentMailing))
|
||||
}
|
||||
})
|
||||
|
||||
if(result) { this.loadMailingInfo(this.currentMailing.id) }
|
||||
}
|
||||
|
||||
async onUnschedule(component, event) {
|
||||
let result = await this.confirmDialog({
|
||||
title: 'Unschedule the sending ?',
|
||||
message: `You are about to <b>unschedule this mailing !</b><br>
|
||||
If you proceed, it will not be sent, <br>
|
||||
and you will be able to re-schedule or to abort it.<br>
|
||||
Is this really what you want to do ?`,
|
||||
okLabel: 'Unschedule',
|
||||
severity: 'warning',
|
||||
cancelSeverity: 'primary',
|
||||
okSeverity: 'secondary',
|
||||
okPromise: (data) => {
|
||||
this.currentMailing.status = 'approved'
|
||||
return(this.mailings.save(this.currentMailing))
|
||||
}
|
||||
})
|
||||
|
||||
if(result) { this.loadMailingInfo(this.currentMailing.id) }
|
||||
}
|
||||
|
||||
|
||||
async onMailingDelete(component, event) {
|
||||
let result = await this.confirmDialog({
|
||||
title: 'DELETE this mailing ?',
|
||||
message: `You are about to <b>DELETE this mailing !</b><br>
|
||||
If you proceed, it will not be sent, <br>
|
||||
and everything it contains will be lost.<br>
|
||||
Is this really what you want to do ?`,
|
||||
okLabel: 'Delete',
|
||||
severity: 'danger',
|
||||
cancelSeverity: 'primary',
|
||||
okSeverity: 'secondary',
|
||||
okPromise: (data) => {
|
||||
return(this.mailings.delete(this.currentMailing.id))
|
||||
}
|
||||
})
|
||||
if(result) { app.Router.route(`/mailings`) }
|
||||
}
|
||||
|
||||
pathChips(path){
|
||||
const dirs = path.split('/').filter(item=>item)
|
||||
const markup = (dirs.length>0) ? dirs.map((dir, idx) => {
|
||||
const path = dirs.slice(0, idx+1).join('/')
|
||||
return(`<span eicchip xxsmall info data-path="/${path}">${dir}</span>`)
|
||||
}).join('') : '<span small secondary>[Please select it]</span>'
|
||||
return(markup)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
app.registerClass('MailingSheetView', MailingSheetView)
|
||||
|
||||
/*
|
||||
TODO
|
||||
-> Delete Draft (add in WF, add method in MT, add here)
|
||||
*/
|
||||
Reference in New Issue
Block a user