class MailingDashboardView extends EICDomContent {
constructor() {
super()
Object.assign(this, app.helpers.activeAttributes)
this.selectedMailingId = null
this.tileMarkup = app.Assets.Store.html['/app/assets/html/mailing/tile.html']
}
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.setupStatusButtons()
this.initializeData()
}
DOMContentFocused(options) {
// Avoid 2nd refesh on DomContentLoaded
if(this.wasBlured){ this.refreshSearch() }
this.wasBlured = false
}
DOMContentBlured(options) { this.wasBlured = true }
async initializeData() {
const result = await this.mailings.getReadableFolders()
this.authorizedPaths = result.authorizedPaths || []
if (this.authorizedPaths.length === 0) {
this.output('searchResults', `
Access Denied
You're not authorized to access to mailing dashboard
`)
return
}
this.components.searchCriteria.value = [app.User.identity.uuid]
}
setupStatusButtons(){
this.statusButtons = {}
this.statusCounters = {}
for(let status in this.mailings.getStatusLabel()){
this.statusButtons[status] = new Button(null, { rounded: true, size: 'xsmall', severity: 'info', label: `${this.mailings.getStatusLabel()[status]}`})
this.statusButtons[status].click = this.onStatusChange.bind(this, status)
this.statusCounters[status] = new Badge(null, {size: 'xxsmall'})
this.statusButtons[status].el.append(this.statusCounters[status].el)
this.find('.filters').append(this.statusButtons[status].el)
}
}
onStatusChange(status, event){
event.stopPropagation()
event.preventDefault()
this.statusButtons[status].severity = (this.statusButtons[status].severity == 'secondary') ? 'info' : 'secondary'
if(this.getSelectedStatus().length == 0) { // turning off last status resets them all (no-status is meaningless)
for(let status in this.statusButtons) this.statusButtons[status].severity = 'info'
}
this.refreshSearch()
}
onOneStatus(event){
event.stopPropagation()
event.preventDefault()
for(let status in this.statusButtons){
this.statusButtons[status].severity = (status == event.target.dataset.status) ? 'info' : 'secondary'
}
this.refreshSearch()
}
onSearchChange(component, event){
event.stopPropagation()
event.preventDefault()
this.refreshSearch()
}
async refreshSearch(){
this.output('searchResults','')
this.setAsyncLoading(true)
const mailings = await this.mailings.search(this.components.searchCriteria.value, this.getSelectedStatus())
this.setAsyncLoading(false)
this.fillResults(mailings)
}
getSelectedStatus(){
return(
Object.keys(this.mailings.getStatusLabel()).reduce( (acc, status) => {
if(!this.statusButtons[status].el.hasAttribute('secondary')) acc.push(status)
return(acc)
}, [] )
)
}
updateStatusCounts(counts){
for(let status in this.mailings.getStatusLabel()){
this.statusCounters[status].value = (status in counts) ? counts[status] : 0
}
}
fillResults(mailings){
this.output('searchResults', '')
if (!this.authorizedPaths || this.authorizedPaths.length === 0) {
this.output('searchResults', `
Access Denied
You're not authorized to access to view this mailing
`)
return
}
mailings = mailings.filter(mailing =>
this.authorizedPaths.some(path => mailing.path.startsWith(path))
)
let statusCounts = {}
if (mailings.length === 0) {
this.output('searchResults', `No mailings found for your search and access rights.
`)
this.updateStatusCounts(statusCounts)
return
}
for(let mailing of mailings){
if(mailing.status in statusCounts) statusCounts[mailing.status]++
else statusCounts[mailing.status] = 1
const lastStatus = this.mailings.getLatestStatus(mailing)
const dirs = mailing.path.split('/').filter(item=>item)
const pathChips = dirs.map((dir, idx) => {
const path = dirs.slice(0, idx+1).join('/')
return(`${dir}`)
}).join('')
const tile = ui.create(Controller.processTemplate('resultTile',
this.tileMarkup,
{ mid: mailing.id,
name: mailing.name ? mailing.name : 'Untitled',
status: mailing.status,
statusLabel: this.mailings.getStatusLabel()[mailing.status],
path: mailing.path,
pathChips: pathChips,
lastUpdate: (new Intl.DateTimeFormat("fr-FR", {timeZone: "Europe/Paris", dateStyle:'medium', timeStyle:'medium'})).format(new Date(lastStatus.dateTime)), //'23 Aug 2024 16:48:01',
lastContributor: `${lastStatus.changedBy.firstname} ${lastStatus.changedBy.lastname}`,
kpis: {
nbRecipients: mailing.nbRecipients || 0,
nbPending: (mailing.status=='sent') ? mailing.nbRecipients - mailing.kpis.bouncesCount -mailing.kpis.readCount : 'N/A',
nbBounced: (mailing.status=='sent') ? mailing.kpis.bouncesCount : 'N/A',
nbReached: (mailing.status=='sent') ? mailing.kpis.readCount : 'N/A',
},
}
))
if(!mailing.nbRecipients || (mailing.nbRecipients.length==0)) {
tile.querySelector('[data-trigger="onRecipientsList"]').closest('li[menuitem]').setAttribute('disabled','')
}
if(!mailing.kpis.bouncesCount || (mailing.kpis.bouncesCount.length==0)) {
tile.querySelector('[data-trigger="onBouncesList"]').closest('li[menuitem]').setAttribute('disabled','')
}
tile.querySelectorAll('[eicdropdown]').forEach(el => {
const dropDown = new DropDown(el)
dropDown.menu.el.querySelectorAll('[data-trigger]').forEach(el => {
el.addEventListener('click', this[el.dataset.trigger].bind(this, dropDown))
})
})
tile.querySelectorAll('[data-path]').forEach(el => el.addEventListener('click', this.onPathClicked.bind(this)))
tile.querySelectorAll('button[data-trigger]').forEach(el => {
const btn = new Button(el)
btn.click = this[el.dataset.trigger].bind(this, btn)
})
tile.querySelector('[data-trigger="onOneStatus"]').addEventListener('click', this.onOneStatus.bind(this))
//temporary until we have stats & unschedule screens
if(['sent'].includes(mailing.status)) ui.hide(tile.querySelector('[data-trigger="onMailingSelect"]'))
this.outputs.searchResults.append(tile)
if(mailing.status=='sent'){
new PieChart(tile.querySelector('[eicpiechart]'), {
title: '',
height: parseInt(getComputedStyle(document.querySelector('.mailings')).getPropertyValue('--chart-height'))+10,
data: [
{ severity: 'danger', value: mailing.kpis.bouncesCount },
{ severity: 'success', value: mailing.kpis.readCount },
{ severity: 'secondary', value: mailing.nbRecipients-mailing.kpis.bouncesCount-mailing.kpis.readCount }
]
})
} else {
tile.querySelector('[eicpiechart]').innerHTML = ''
}
}
this.updateStatusCounts(statusCounts)
}
onPathClicked(event){
event.stopPropagation()
event.preventDefault()
const criteria = this.components.searchCriteria.value.filter(item => (!item.startsWith('/'))) // Don't filter straight in the component, (dbl-trigger)
this.components.searchCriteria.value = [...criteria, event.target.dataset.path] // push not foreseen in component
}
async onPathBrowse() {
const button = this.find('[data-trigger="onPathBrowse"]')
this.showLoading(button)
let result = await this.mailings.getReadableFolders()
let { mailings, authorizedPaths } = result
if (!Array.isArray(authorizedPaths)) {
authorizedPaths = []
}
if (!authorizedPaths.includes('/mailing')) authorizedPaths.unshift('/mailing')
this.mailings.ffs.authorizedPaths = authorizedPaths
if (!mailings.mailings) {
mailings = { mailings }
}
this.mailings.ffs.loadStructure(mailings || {}, [])
this.mailings.ffs.currentPath = '/mailing'
this.mailings.ffs.authorizedPaths = authorizedPaths
let dialogResult = await this.openDialog(
await this.loadContent(
'templates/Ffs/dialogs/FileBrowserDialog',
{ title: `Select a path...` },
{
ffs: this.mailings.ffs,
mailings: this.mailings.mailings,
editable: false,
canManage: false,
startPath: '/mailing',
}
)
)
if (dialogResult) {
const criteria = this.components.searchCriteria.value.filter(item => (!item.startsWith('/')))
this.components.searchCriteria.value = [...criteria, dialogResult]
this.hideLoading(button)
}
this.hideLoading(button)
}
onMailingSelect(component, event){
event.stopPropagation()
event.preventDefault()
app.Router.route(`/mailings/${component.el.dataset.id}`)
}
async onRecipientsList(component, event){
const mid = event.target.dataset.id
component.button.loading = true
let recipients = await this.mailings.getRecipients(mid)
component.button.loading = false
this.toCSV( // Kick id & meta
recipients.map(row => { const { id, meta , ...filtered } = row; return(filtered) } ),
{ headers: ['Email', 'Source name', 'Source type' ],
filename: `Recipient`
}
)
}
async onBouncesList(component, event){
const mid = event.target.dataset.id
component.button.loading = true
const bounces = await this.mailings.getBounces(mid)
component.button.loading = false
this.toCSV( // Kick id & meta
bounces.map(row => { const { id, ...filtered } = row; return(filtered) } ),
{ headers: ['Email', 'Date bounced' ],
filename: `Bounces`
}
)
}
async onMailingCreate(component, event){
event.stopPropagation()
event.preventDefault()
component.loading = true
const path = this.authorizedPaths?.[0]
const response = await this.mailings.save({
status: "created",
path: path
})
component.loading = false
if(response && response.id) {
app.Router.route(`/mailings/${response.id}`)
}
}
}
app.registerClass('MailingDashboardView', MailingDashboardView)