Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37125 vikas 1
/* ============================================================================
2
   LMS dashboards front-end — shared behaviour for Command Center + Dashboards.
3
   - menu click handlers load the page fragments into #main-content
4
   - window.LmsDash exposes render/refresh/drill helpers the .vm fragments call
5
   - charts use the app's existing Chart.js global (Chart1); heat-map / tiles / SLA
6
     bars are CSS (see lms-dashboard.css). Drill-downs open a right-side drawer.
7
   Backed by /lms/dashboard/* JSON endpoints (LmsDashboardController).
8
   ============================================================================ */
9
(function () {
10
    'use strict';
11
 
12
    function ctx() { return (typeof context !== 'undefined' && context) ? context : ''; }
13
    function api(path) { return ctx() + path; }
14
 
15
    // responseSender.ok(...) wraps the payload under `.response`.
16
    function unwrap(r) {
17
        var o = (typeof r === 'string') ? JSON.parse(r) : r;
18
        return (o && o.response !== undefined) ? o.response : o;
19
    }
20
 
21
    function esc(s) {
22
        if (s === null || s === undefined) return '';
23
        return ('' + s).replace(/[&<>"]/g, function (c) {
24
            return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
25
        });
26
    }
27
 
28
    function fmtInt(n) {
29
        if (n === null || n === undefined) return '0';
30
        return ('' + n).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
31
    }
32
 
33
    function fmtInr(v) {
34
        if (v === null || v === undefined || v === '' || isNaN(v)) return '—';
35
        v = Number(v);
36
        if (v >= 10000000) return '₹' + (v / 10000000).toFixed(1) + ' Cr';
37
        if (v >= 100000) return '₹' + (v / 100000).toFixed(1) + ' L';
38
        if (v >= 1000) return '₹' + Math.round(v / 1000) + 'K';
39
        return '₹' + Math.round(v);
40
    }
41
 
42
    function pctClass(p) { return p >= 70 ? 'pct-good' : (p >= 45 ? 'pct-warn' : 'pct-bad'); }
43
 
44
    function slaFill(state) {
45
        switch (state) {
46
            case 'MET': return { w: 100, c: '#2f9e44' };
47
            case 'RUNNING': return { w: 55, c: '#1f2a5e' };
48
            case 'AT_RISK': return { w: 85, c: '#f08c00' };
49
            case 'BREACHED': return { w: 100, c: '#e03131' };
50
            default: return { w: 0, c: '#c9d0dd' };
51
        }
52
    }
53
 
54
    function toast(msg) {
55
        if (typeof showToast === 'function') { showToast(msg); return; }
56
        alert(msg);
57
    }
58
 
59
    // ---- drill-down drawer (one shared instance) -------------------------------
60
    function ensureDrawer() {
61
        if (document.getElementById('lmsDashDrawer')) return;
62
        var ov = document.createElement('div');
63
        ov.className = 'lms-dash-ov';
64
        ov.id = 'lmsDashOverlay';
65
        var dr = document.createElement('div');
66
        dr.className = 'lms-dash-drawer';
67
        dr.id = 'lmsDashDrawer';
68
        dr.innerHTML =
69
            '<div class="d-head" style="position:relative;">' +
70
            '  <button class="d-close" id="lmsDashDrawerClose">&times;</button>' +
71
            '  <div class="d-crumb" id="lmsDashDrawerCrumb"></div>' +
72
            '  <div class="d-title" id="lmsDashDrawerTitle"></div>' +
73
            '  <div class="d-sub" id="lmsDashDrawerSub"></div>' +
74
            '</div>' +
75
            '<div class="d-body" id="lmsDashDrawerBody"></div>';
76
        document.body.appendChild(ov);
77
        document.body.appendChild(dr);
78
        ov.addEventListener('click', closeDrawer);
79
        document.getElementById('lmsDashDrawerClose').addEventListener('click', closeDrawer);
80
        document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeDrawer(); });
81
    }
82
 
83
    function openDrawer(crumb, title, sub, bodyHtml) {
84
        ensureDrawer();
85
        document.getElementById('lmsDashDrawerCrumb').textContent = crumb || '';
86
        document.getElementById('lmsDashDrawerTitle').textContent = title || '';
87
        document.getElementById('lmsDashDrawerSub').textContent = sub || '';
88
        document.getElementById('lmsDashDrawerBody').innerHTML = bodyHtml || '';
89
        document.getElementById('lmsDashOverlay').classList.add('open');
90
        document.getElementById('lmsDashDrawer').classList.add('open');
91
    }
92
 
93
    function closeDrawer() {
94
        var ov = document.getElementById('lmsDashOverlay');
95
        var dr = document.getElementById('lmsDashDrawer');
96
        if (ov) ov.classList.remove('open');
97
        if (dr) dr.classList.remove('open');
98
    }
99
 
100
    function leadRows(leads) {
101
        if (!leads || !leads.length) return '<div class="d-empty">No leads in this bucket.</div>';
102
        var h = '<table class="lms-tbl"><thead><tr>' +
103
            '<th>LMS ID</th><th>Business / Retailer</th><th>Stage</th><th>Owner</th><th>SLA</th><th>Value</th>' +
104
            '</tr></thead><tbody>';
105
        leads.forEach(function (l) {
106
            var f = slaFill(l.slaState);
107
            h += '<tr class="row-click" data-leadid="' + l.id + '">' +
108
                '<td><span class="idpill">' + esc(l.lmsCode) + '</span></td>' +
109
                '<td>' + esc(l.name || '—') + (l.outlet ? ' <span style="color:#97a1b5">· ' + esc(l.outlet) + '</span>' : '') + '</td>' +
110
                '<td>' + stagePill(l.stage, l.stageLabel) + '</td>' +
111
                '<td>' + esc(l.owner) + '</td>' +
112
                '<td><span class="sla-wrap"><span class="slabar"><i style="width:' + f.w + '%;background:' + f.c + '"></i></span>' +
113
                '<span class="sla-clock sla-' + esc(l.slaState) + '">' + esc(l.slaState || 'NA') + '</span></span></td>' +
114
                '<td>' + fmtInr(l.value) + '</td>' +
115
                '</tr>';
116
        });
117
        h += '</tbody></table>';
118
        return h;
119
    }
120
 
121
    function stagePill(stage, label) {
122
        if (!stage) return '<span class="stage st-NEW">—</span>';
123
        return '<span class="stage st-' + esc(stage) + '">' + esc(label || stage) + '</span>';
124
    }
125
 
126
    // Fetch a bucket's leads and open the drawer.
127
    function drill(crumb, title, sub, bucketType, bucketKey) {
128
        var q = '?bucketType=' + encodeURIComponent(bucketType) +
129
            (bucketKey ? '&bucketKey=' + encodeURIComponent(bucketKey) : '') +
130
            filterQS('&');
131
        openDrawer(crumb, title, 'Loading…', '<div class="d-empty">Loading…</div>');
132
        doGetAjaxRequestHandler(api('/lms/dashboard/leads' + q), function (resp) {
133
            var data = unwrap(resp);
134
            var leads = (data && data.leads) ? data.leads : [];
135
            openDrawer(crumb, title, (sub || '') + ' · ' + leads.length + ' lead(s)', leadRows(leads));
136
        });
137
    }
138
 
139
    // ---- shared filter state ----------------------------------------------------
140
    var STATE = { regionId: '', from: '', to: '' };
141
 
142
    function readFilters() {
143
        var reg = document.getElementById('lmsFilterRegion');
144
        var fr = document.getElementById('lmsFilterFrom');
145
        var to = document.getElementById('lmsFilterTo');
146
        if (reg) STATE.regionId = reg.value || '';
147
        if (fr) STATE.from = fr.value || '';
148
        if (to) STATE.to = to.value || '';
149
    }
150
 
151
    function filterQS(sep) {
152
        var q = '';
153
        if (STATE.regionId) q += (sep || '?') + 'regionId=' + encodeURIComponent(STATE.regionId);
154
        if (STATE.from) q += (q ? '&' : (sep || '?')) + 'from=' + encodeURIComponent(STATE.from);
155
        if (STATE.to) q += (q ? '&' : (sep || '?')) + 'to=' + encodeURIComponent(STATE.to);
156
        return q;
157
    }
158
 
159
    // ---- charts (Chart1 = legacy Chart.js global) ------------------------------
160
    var CHARTS = {};
161
 
162
    function destroyChart(id) {
163
        if (CHARTS[id]) { try { CHARTS[id].destroy(); } catch (e) { } delete CHARTS[id]; }
164
    }
165
 
166
    function barChart(canvasId, labels, values, colors, horizontal, onIndexClick) {
167
        var cv = document.getElementById(canvasId);
168
        if (!cv || typeof Chart1 === 'undefined') return;
169
        destroyChart(canvasId);
170
        var chart = new Chart1(cv.getContext('2d'), {
171
            type: horizontal ? 'horizontalBar' : 'bar',
172
            data: { labels: labels, datasets: [{ data: values, backgroundColor: colors, borderWidth: 0, barPercentage: 0.72 }] },
173
            options: {
174
                responsive: true, maintainAspectRatio: false,
175
                legend: { display: false },
176
                tooltips: { enabled: true },
177
                scales: {
178
                    xAxes: [{ ticks: { beginAtZero: true, precision: 0, fontColor: '#97a1b5' }, gridLines: { color: '#eef1f6' } }],
179
                    yAxes: [{ ticks: { fontColor: '#69748c' }, gridLines: { color: '#eef1f6' } }]
180
                }
181
            }
182
        });
183
        CHARTS[canvasId] = chart;
184
        if (onIndexClick) {
185
            cv.onclick = function (evt) {
186
                var pts = chart.getElementsAtEvent(evt);
187
                if (pts && pts.length) onIndexClick(pts[0]._index);
188
            };
189
        }
190
        return chart;
191
    }
192
 
193
    // ======================= COMMAND CENTER ==================================
194
    function renderSummary(data) {
195
        // KPI tiles
196
        var kh = '';
197
        (data.kpis || []).forEach(function (k) {
198
            kh += '<div class="lms-kpi tone-' + esc(k.tone) + '" data-kpikey="' + esc(k.key) + '">' +
199
                '<div class="k-label">' + esc(k.label) + '</div>' +
200
                '<div class="k-value">' + fmtInt(k.value) + '</div>' +
201
                '<div class="k-foot">' + esc(k.foot) + '</div></div>';
202
        });
203
        setHtml('lmsKpis', kh);
204
 
205
        // Funnel — Chart1 horizontal bar + clickable
206
        var f = data.funnel || [];
207
        var labels = f.map(function (s) { return s.label; });
208
        var vals = f.map(function (s) { return s.count; });
209
        var cols = f.map(function () { return '#3b5bdb'; });
210
        barChart('lmsFunnelChart', labels, vals, cols, true, function (i) {
211
            var step = f[i];
212
            drill('Lifecycle funnel', step.label, 'Reached ' + step.label + ' (' + step.pct + '%)',
213
                step.stage === 'ALL' ? 'ALL' : 'STAGE', step.stage === 'ALL' ? '' : step.stage);
214
        });
215
 
216
        // Attention queue
217
        var ah = '';
218
        (data.attention || []).forEach(function (a) {
219
            ah += '<div class="att tone-' + esc(a.tone) + '" data-attkey="' + esc(a.key) + '" data-atttitle="' + esc(a.title) + '">' +
220
                '<span class="a-dot"></span>' +
221
                '<span class="a-body"><span class="a-title">' + esc(a.title) + '</span><br>' +
222
                '<span class="a-detail">' + esc(a.detail) + '</span></span>' +
223
                '<span class="a-count">' + fmtInt(a.count) + '</span></div>';
224
        });
225
        setHtml('lmsAttention', ah);
226
    }
227
 
228
    function loadPipeline() {
229
        var box = document.getElementById('lmsPipeline');
230
        if (!box) return;
231
        box.innerHTML = '<div class="lms-ph">Loading pipeline…</div>';
232
        doGetAjaxRequestHandler(api('/lms/dashboard/leads?bucketType=ALL' + filterQS('&')), function (resp) {
233
            var data = unwrap(resp);
234
            var leads = (data && data.leads) ? data.leads : [];
235
            box.innerHTML = leadRows(leads);
236
        });
237
    }
238
 
239
    function refreshCommandCenter() {
240
        readFilters();
241
        doGetAjaxRequestHandler(api('/lms/dashboard/summary' + filterQS('?')), function (resp) {
242
            renderSummary(unwrap(resp));
243
        });
244
        loadPipeline();
245
    }
246
 
247
    function kpiBucket(key) {
248
        switch (key) {
249
            case 'new': return { t: 'STAGE', k: 'NEW' };
250
            case 'awaiting': return { t: 'STAGE', k: 'ASSIGNED' };
251
            case 'sla': return { t: 'ATTENTION', k: 'BREACHED_NO_CONTACT' };
252
            case 'contacted': return { t: 'STAGE', k: 'CONTACTED' };
253
            case 'qualified': return { t: 'STAGE', k: 'QUALIFIED' };
254
            case 'onboarded': return { t: 'STAGE', k: 'ONBOARDED' };
255
            default: return { t: 'ALL', k: '' };
256
        }
257
    }
258
 
259
    // ======================= DASHBOARDS ======================================
260
    function renderStateFunnel(data) {
261
        var rows = (data && data.rows) ? data.rows : [];
262
        var h = '<table class="lms-tbl"><thead><tr>' +
263
            '<th>Region</th><th>Leads</th><th>Contacted</th><th>Qualified</th><th>Visited</th><th>Onboarded</th><th>Conv%</th><th>SLA%</th>' +
264
            '</tr></thead><tbody>';
265
        rows.forEach(function (r) {
266
            var rc = r.convPct < 20 ? 'row-bad' : (r.slaPct < 60 ? 'row-warn' : '');
267
            h += '<tr class="row-click ' + rc + '" data-region="' + esc(r.regionCode) + '" data-regionname="' + esc(r.regionName) + '">' +
268
                '<td><b>' + esc(r.regionName) + '</b> <span class="idpill">' + esc(r.regionCode) + '</span></td>' +
269
                '<td>' + fmtInt(r.newCount) + '</td>' +
270
                '<td>' + fmtInt(r.contacted) + '</td>' +
271
                '<td>' + fmtInt(r.qualified) + '</td>' +
272
                '<td>' + fmtInt(r.visited) + '</td>' +
273
                '<td>' + fmtInt(r.onboarded) + '</td>' +
274
                '<td><span class="pctchip ' + pctClass(r.convPct) + '">' + r.convPct + '%</span></td>' +
275
                '<td><span class="pctchip ' + pctClass(r.slaPct) + '">' + r.slaPct + '%</span></td>' +
276
                '</tr>';
277
        });
278
        h += '</tbody></table>';
279
        setHtml('lmsStateFunnel', h);
280
 
281
        // Conversion-by-region Chart1 bar.
282
        barChart('lmsConvChart',
283
            rows.map(function (r) { return r.regionCode; }),
284
            rows.map(function (r) { return r.convPct; }),
285
            rows.map(function (r) { return r.convPct < 20 ? '#e03131' : (r.convPct < 40 ? '#f59f00' : '#2f9e44'); }),
286
            false,
287
            function (i) {
288
                var r = rows[i];
289
                drill('State funnel', r.regionName, 'Region ' + r.regionCode, 'REGION', r.regionCode);
290
            });
291
 
292
        // Dashboards KPIs derived from summary + state + ageing (set by initDashboards).
293
        buildDashKpis(rows);
294
    }
295
 
296
    var DASH = { summary: null, ageing: null };
297
 
298
    function buildDashKpis(stateRows) {
299
        if (!DASH.summary) return;
300
        var s = DASH.summary;
301
        var contacted = 0;
302
        (s.funnel || []).forEach(function (f) { if (f.stage === 'CONTACTED') contacted = f.count; });
303
        // Weighted SLA% across regions.
304
        var totLeads = 0, slaAcc = 0;
305
        (stateRows || []).forEach(function (r) { totLeads += r.newCount; slaAcc += r.slaPct * r.newCount; });
306
        var slaPct = totLeads > 0 ? Math.round(slaAcc / totLeads) : 0;
307
        var days = 90;
308
        var kpis = [
309
            { label: 'LMS created', value: fmtInt(s.total), foot: 'in period', tone: 'navy' },
310
            { label: 'Conversion', value: s.conversionPct + '%', foot: 'onboarded / created', tone: 'good' },
311
            { label: 'SLA compliance', value: slaPct + '%', foot: 'first contact in SLA', tone: slaPct < 60 ? 'red' : 'navy' },
312
            { label: 'Calls/day (BGC)', value: fmtInt(Math.round(contacted / days)), foot: 'proxy · contacted ÷ days', tone: 'amber' },
313
            { label: 'Ageing > 7 days', value: DASH.ageing ? fmtInt(DASH.ageing.total) : '—', foot: 'stuck open leads', tone: 'red' },
314
            { label: 'Onboarded value', value: fmtInr(s.onboardedValue), foot: 'sum of potential', tone: 'good' }
315
        ];
316
        var kh = '';
317
        kpis.forEach(function (k) {
318
            kh += '<div class="lms-kpi tone-' + k.tone + '">' +
319
                '<div class="k-label">' + esc(k.label) + '</div>' +
320
                '<div class="k-value">' + k.value + '</div>' +
321
                '<div class="k-foot">' + esc(k.foot) + '</div></div>';
322
        });
323
        setHtml('lmsDashKpis', kh);
324
    }
325
 
326
    function renderHeatmap(data) {
327
        var weeks = (data && data.weeks) ? data.weeks : [];
328
        var bms = (data && data.bms) ? data.bms : [];
329
        if (!bms.length) { setHtml('lmsHeatmap', '<div class="lms-ph">No SLA data for the selected period.</div>'); return; }
330
        var h = '<table class="heat"><thead><tr><th></th>';
331
        weeks.forEach(function (w) { h += '<th>' + esc(w) + '</th>'; });
332
        h += '</tr></thead><tbody>';
333
        bms.forEach(function (bm) {
334
            h += '<tr><td class="bm">' + esc(bm.bmName) + '</td>';
335
            (bm.cells || []).forEach(function (c) {
336
                if (c.pct === null || c.pct === undefined) {
337
                    h += '<td class="cell empty">–</td>';
338
                } else {
339
                    h += '<td class="cell" style="background:' + heatColor(c.pct) + '">' + c.pct + '</td>';
340
                }
341
            });
342
            h += '</tr>';
343
        });
344
        h += '</tbody></table>';
345
        setHtml('lmsHeatmap', h);
346
    }
347
 
348
    function heatColor(p) {
349
        if (p >= 90) return '#2f9e44';
350
        if (p >= 75) return '#82c91e';
351
        if (p >= 60) return '#f59f00';
352
        if (p >= 40) return '#e8590c';
353
        return '#e03131';
354
    }
355
 
356
    function renderAgeing(data) {
357
        DASH.ageing = data;
358
        var rows = (data && data.rows) ? data.rows : [];
359
        if (!rows.length) { setHtml('lmsAgeing', '<div class="lms-ph">No ageing leads. 🎉</div>'); return; }
360
        var h = '<table class="lms-tbl"><thead><tr><th>Stuck at stage</th><th>Leads &gt; ' + (data.days || 7) + 'd</th></tr></thead><tbody>';
361
        rows.forEach(function (r) {
362
            h += '<tr class="row-click" data-agestage="' + esc(r.stage) + '">' +
363
                '<td>' + stagePill(r.stage, r.label) + '</td>' +
364
                '<td><b>' + fmtInt(r.count) + '</b></td></tr>';
365
        });
366
        h += '</tbody></table>';
367
        setHtml('lmsAgeing', h);
368
    }
369
 
370
    function refreshDashboards() {
371
        readFilters();
372
        doGetAjaxRequestHandler(api('/lms/dashboard/summary' + filterQS('?')), function (resp) {
373
            DASH.summary = unwrap(resp);
374
            // re-run KPI build after state loads; also refresh command-style summary if present
375
            if (document.getElementById('lmsStateFunnel')) fetchState();
376
        });
377
        doGetAjaxRequestHandler(api('/lms/dashboard/sla-heatmap' + filterQS('?')), function (resp) { renderHeatmap(unwrap(resp)); });
378
        doGetAjaxRequestHandler(api('/lms/dashboard/ageing'), function (resp) { renderAgeing(unwrap(resp)); });
379
    }
380
 
381
    function fetchState() {
382
        doGetAjaxRequestHandler(api('/lms/dashboard/state-funnel' + filterQS('?')), function (resp) { renderStateFunnel(unwrap(resp)); });
383
    }
384
 
385
    // ---- DOM helpers ------------------------------------------------------------
386
    function setHtml(id, html) { var el = document.getElementById(id); if (el) el.innerHTML = html; }
387
 
388
    // ---- delegated clicks (drill) ----------------------------------------------
389
    $(document).off('click.lmsdash');
390
    $(document).on('click.lmsdash', '.lms-dash .lms-kpi[data-kpikey]', function () {
391
        var key = $(this).data('kpikey');
392
        var b = kpiBucket(key);
393
        drill('Command Center', $(this).find('.k-label').text(), 'KPI drill', b.t, b.k);
394
    });
395
    $(document).on('click.lmsdash', '.lms-dash .att[data-attkey]', function () {
396
        drill('Needs attention', $(this).data('atttitle'), 'Attention queue', 'ATTENTION', $(this).data('attkey'));
397
    });
398
    $(document).on('click.lmsdash', '.lms-dash table.lms-tbl tr.row-click[data-leadid]', function () {
399
        var id = $(this).data('leadid');
400
        closeDrawer();
401
        doGetAjaxRequestHandler(api('/leadRecord?leadId=' + id), function (r) { $('#main-content').html(r); });
402
    });
403
    $(document).on('click.lmsdash', '.lms-dash tr.row-click[data-region]', function () {
404
        drill('State funnel', $(this).data('regionname'), 'Region ' + $(this).data('region'), 'REGION', $(this).data('region'));
405
    });
406
    $(document).on('click.lmsdash', '.lms-dash tr.row-click[data-agestage]', function () {
407
        drill('Ageing', $(this).find('.stage').text(), 'Stuck leads', 'STAGE', $(this).data('agestage'));
408
    });
409
    // Filter changes.
410
    $(document).on('change.lmsdash', '#lmsFilterRegion, #lmsFilterFrom, #lmsFilterTo', function () {
411
        if (document.getElementById('lmsKpis') && document.getElementById('lmsPipeline')) {
412
            refreshCommandCenter();
413
        } else {
414
            refreshDashboards();
415
        }
416
    });
417
 
418
    // ---- public API the .vm fragments call --------------------------------------
419
    window.LmsDash = {
420
        initCommandCenter: function (cfg) {
421
            cfg = cfg || {};
422
            STATE.regionId = cfg.regionId || '';
423
            STATE.from = cfg.from || '';
424
            STATE.to = cfg.to || '';
425
            ensureDrawer();
426
            if (cfg.summary) renderSummary(cfg.summary);
427
            loadPipeline();
428
        },
429
        initDashboards: function (cfg) {
430
            cfg = cfg || {};
431
            STATE.regionId = cfg.regionId || '';
432
            STATE.from = cfg.from || '';
433
            STATE.to = cfg.to || '';
434
            ensureDrawer();
435
            DASH.summary = cfg.summary || null;
436
            DASH.ageing = cfg.ageing || null;
437
            if (cfg.stateFunnel) renderStateFunnel(cfg.stateFunnel);
438
            if (cfg.slaHeatmap) renderHeatmap(cfg.slaHeatmap);
439
            if (cfg.ageing) renderAgeing(cfg.ageing);
440
        },
441
        refreshCommandCenter: refreshCommandCenter,
442
        refreshDashboards: refreshDashboards
443
    };
444
 
445
    // ---- sidebar menu entry points ----------------------------------------------
446
    // The LMS console is a full standalone page (own top-bar + 6 tabs, sts-mockup UI). To keep
447
    // the app's left sidebar + header visible, embed it in an iframe inside #main-content
448
    // (style/script isolated), deep-linked to the relevant tab.
449
    function openLmsConsole(hash) {
450
        var url = api('/lms/dashboard') + (hash || '');
451
        $('#main-content').html(
452
            '<iframe title="LMS Console" src="' + url + '" ' +
453
            'style="width:100%;height:calc(100vh - 92px);min-height:620px;border:0;display:block"></iframe>'
454
        );
455
    }
456
    $(document).on('click', '.lms-command-center', function (e) {
457
        if (e && e.preventDefault) e.preventDefault();
458
        openLmsConsole('#cmd');
459
    });
460
    $(document).on('click', '.lms-dashboards', function (e) {
461
        if (e && e.preventDefault) e.preventDefault();
462
        openLmsConsole('#dash');
463
    });
464
})();