Files
P42_UI/app/views/comms/mailings/sheet/MailingSheetView.js
T
2025-08-27 07:03:09 +00:00

872 lines
38 KiB
JavaScript

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)
*/