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)