Subversion Repositories SmartDukaan

Rev

Rev 37248 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
36662 ranu 1
<section class="wrapper">
2
    <div class="row">
3
        <div class="col-lg-12">
4
            <h3 class="page-header"><i class="icon_calendar"></i> Scheduled Beats - Day View</h3>
5
            <ol class="breadcrumb">
6
                <li><i class="fa fa-home"></i><a href="${rc.contextPath}/dashboard">Home</a></li>
7
                <li><i class="icon_group"></i> Partner Acquisition</li>
8
                <li><i class="icon_calendar"></i> Beat Day View</li>
9
            </ol>
10
        </div>
11
    </div>
12
 
13
    <div class="row" style="margin-bottom:10px;">
14
        <div class="col-lg-12" style="display:flex; gap:10px; align-items:center; flex-wrap:wrap;">
36821 ranu 15
            <label style="margin:0;">Category:</label>
16
            <select id="dayViewCategory" class="form-control input-sm" style="width:140px;">
17
                <option value="$categoryId">Sales</option>
18
            </select>
19
            <label style="margin:0;">Level:</label>
20
            <select id="dayViewLevel" class="form-control input-sm" style="width:140px;">
21
                <option value="">All Levels</option>
22
                #foreach($lvl in $escalationTypes)
23
                    <option value="$lvl">$lvl</option>
24
                #end
25
            </select>
36761 ranu 26
            <label style="margin:0;">Date:</label>
27
            <input type="date" id="dayViewDate" class="form-control input-sm" style="width:160px;">
36662 ranu 28
            <button class="btn btn-primary btn-sm" id="dayViewLoad">Load</button>
29
            <span id="dayViewSummary" style="color:#777; font-size:12px; margin-left:10px;"></span>
30
        </div>
31
    </div>
32
 
33
    <div class="row">
34
        <div class="col-lg-12">
35
            <div class="table-responsive">
36
                <table class="table table-bordered table-striped table-hover" id="dayViewTable">
37
                    <thead>
38
                    <tr>
39
                        <th>User</th>
40
                        <th>Date</th>
41
                        <th>Beat</th>
42
                        <th>Day</th>
43
                        <th>Partners</th>
44
                        <th>Leads</th>
45
                        <th>Total Visits</th>
46
                        <th>Action</th>
47
                    </tr>
48
                    </thead>
49
                    <tbody id="dayViewRows">
50
                    <tr>
51
                        <td colspan="8" style="text-align:center; color:#999;">Pick a date range and click Load.</td>
52
                    </tr>
53
                    </tbody>
54
                </table>
55
            </div>
56
        </div>
57
    </div>
58
</section>
59
 
60
 
61
<script>
36962 vikas 62
    // Edit-beat / assign-visit in the Beat Planner are L2-and-above only.
63
    var CAN_EDIT_BEAT = $canEditBeat;
36662 ranu 64
    (function () {
36761 ranu 65
        // Default: today only. Other dates → use the per-user calendar from the row's View button.
36662 ranu 66
        var today = new Date();
67
 
68
        function fmt(d) {
69
            return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2);
70
        }
71
 
36761 ranu 72
        $('#dayViewDate').val(fmt(today));
36662 ranu 73
        loadDayView();
74
    })();
75
 
37494 amit 76
    // Fragment is re-injected with .html(), which re-runs this script; clear the
77
    // namespace first so handlers replace rather than stack (one click would
78
    // otherwise fire N identical requests and be rejected as duplicates).
79
    $(document).off('.bpdayview');
80
    $(document).on('click.bpdayview', '#dayViewLoad', function () {
36662 ranu 81
        loadDayView();
82
    });
83
 
84
    function loadDayView() {
36761 ranu 85
        var d = $('#dayViewDate').val();
86
        if (!d) {
87
            alert('Pick a date');
36662 ranu 88
            return;
89
        }
90
        $('#dayViewRows').html('<tr><td colspan="8" style="text-align:center;">Loading...</td></tr>');
91
        $('#dayViewSummary').text('');
36821 ranu 92
        var params = {startDate: d, endDate: d};
93
        var cat = $('#dayViewCategory').val();
94
        var lvl = $('#dayViewLevel').val();
95
        if (cat) params.categoryId = cat;
96
        if (lvl) params.escalationType = lvl;
97
        $.get(context + '/beatPlan/scheduledList', params).done(function (r) {
36662 ranu 98
            var data = r.response || r;
99
            renderDayViewRows(data.rows || []);
36761 ranu 100
            $('#dayViewSummary').text((data.rows || []).length + ' beats scheduled on ' + data.startDate);
36662 ranu 101
        }).fail(function (xhr) {
102
            $('#dayViewRows').html('<tr><td colspan="8" style="color:#d9534f; text-align:center;">Error: ' + (xhr.responseText || xhr.statusText) + '</td></tr>');
103
        });
104
    }
105
 
106
    function renderDayViewRows(rows) {
107
        if (!rows || rows.length === 0) {
108
            $('#dayViewRows').html('<tr><td colspan="8" style="text-align:center; color:#999;">No beats scheduled in this range.</td></tr>');
109
            return;
110
        }
36716 ranu 111
        // Assign Visit is only valid for TODAY's run — future/past dates hide the button.
112
        var todayStr = (function () {
113
            var d = new Date();
114
            var mm = ('0' + (d.getMonth() + 1)).slice(-2);
115
            var dd = ('0' + d.getDate()).slice(-2);
116
            return d.getFullYear() + '-' + mm + '-' + dd;
117
        })();
36662 ranu 118
        var html = '';
119
        rows.forEach(function (r) {
36716 ranu 120
            var isToday = r.scheduleDate === todayStr;
36662 ranu 121
            html += '<tr>'
122
                    + '<td>' + r.userName + '</td>'
123
                    + '<td><span class="label label-primary">' + r.scheduleDate + '</span></td>'
124
                    + '<td><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:' + (r.beatColor || '#3498db') + ';margin-right:6px;"></span>' + r.beatName + '</td>'
125
                    + '<td>' + r.dayNumber + '</td>'
126
                    + '<td>' + r.partnerCount + '</td>'
127
                    + '<td>' + (r.leadCount > 0 ? '<span class="label label-warning">' + r.leadCount + '</span>' : '0') + '</td>'
128
                    + '<td><strong>' + r.visitCount + '</strong></td>'
129
                    + '<td>'
130
                    + '<button class="btn btn-xs btn-info view-user-calendar" data-userid="' + r.authUserId + '" data-username="' + r.userName + '">View Calendar</button> '
36962 vikas 131
                    + (CAN_EDIT_BEAT
132
                            ? '<button class="btn btn-xs btn-warning edit-beat-on-date" '
133
                            + 'data-userid="' + r.authUserId + '" data-username="' + r.userName + '" '
134
                            + 'data-beatid="' + r.beatId + '" data-date="' + r.scheduleDate + '">Edit</button>'
135
                            + (isToday
136
                                    ? ' <button class="btn btn-xs btn-success assign-visit-btn"'
137
                                    + ' data-userid="' + r.authUserId + '" data-username="' + r.userName + '"'
138
                                    + ' data-beatid="' + r.beatId + '"'
139
                                    + ' data-date="' + r.scheduleDate + '">Assign Visit</button>'
140
                                    : '')
37120 vikas 141
                            // Add Lead is allowed for today AND future runs — a lead is a dated stop.
142
                            + ' <button class="btn btn-xs add-lead-btn"'
143
                            + ' style="background:#0d9488;color:#fff;border:none;"'
144
                            + ' data-userid="' + r.authUserId + '" data-username="' + r.userName + '"'
145
                            + ' data-beatid="' + r.beatId + '"'
146
                            + ' data-date="' + r.scheduleDate + '">Add Lead</button>'
36716 ranu 147
                            : '')
36662 ranu 148
                    + '</td>'
149
                    + '</tr>';
150
        });
151
        $('#dayViewRows').html(html);
152
    }
153
 
154
    // View -> open user's calendar inside the dashboard in a modal iframe (pre-selected)
37494 amit 155
    $(document).on('click.bpdayview', '.view-user-calendar', function () {
36662 ranu 156
        var userId = $(this).data('userid');
157
        var userName = $(this).data('username') || '';
158
        var url = context + '/beatPlanWindow?autoUserId=' + userId
159
                + '&autoUserName=' + encodeURIComponent(userName);
160
        $('#userCalendarTitle').text('Calendar - ' + userName);
161
        $('#userCalendarFrame').attr('src', url);
162
        $('#userCalendarModal').modal('show');
163
    });
164
 
165
    // Edit a beat on a specific date — opens beatPlanWindow in edit mode for that run
37494 amit 166
    $(document).on('click.bpdayview', '.edit-beat-on-date', function () {
36662 ranu 167
        var userId = $(this).data('userid');
168
        var userName = $(this).data('username') || '';
169
        var beatId = $(this).data('beatid');
170
        var date = $(this).data('date');
171
        var url = context + '/beatPlanWindow?autoUserId=' + userId
172
                + '&autoUserName=' + encodeURIComponent(userName)
173
                + '&editBeatId=' + beatId + '&editDate=' + date;
174
        $('#userCalendarTitle').text('Edit Beat - ' + userName + ' on ' + date);
175
        $('#userCalendarFrame').attr('src', url);
176
        $('#userCalendarModal').modal('show');
177
    });
36663 ranu 178
 
179
    // ---------- ASSIGN VISIT ----------
36716 ranu 180
    var avContext = {authUserId: null, userName: '', date: '', beatId: null, allParties: [], agendaOptions: []};
36663 ranu 181
 
37494 amit 182
    $(document).on('click.bpdayview', '.assign-visit-btn', function () {
36663 ranu 183
        avContext.authUserId = $(this).data('userid');
184
        avContext.userName = $(this).data('username') || '';
185
        avContext.date = $(this).data('date');
36716 ranu 186
        avContext.beatId = $(this).data('beatid');
36663 ranu 187
        avContext.allParties = [];
188
 
36716 ranu 189
        $('#avTitle').text('Assign Visit - ' + avContext.userName + ' on ' + avContext.date);
36663 ranu 190
        $('#avSearch').val('');
36716 ranu 191
        // The default-agenda multiselect is rebuilt (and re-initialized) below in rebuildDefaultAgenda
36663 ranu 192
        $('#avPartyList').html('<div style="padding:20px; color:#999; text-align:center;">Loading parties...</div>');
193
        $('#avMsg').text('');
194
        $('#avSelectedCount').text('0 selected');
195
        $('#avSubmit').prop('disabled', true);
196
        $('#assignVisitModal').modal('show');
197
 
36716 ranu 198
        $.get(context + '/beatPlan/assignVisit/parties', {
199
            authUserId: avContext.authUserId,
200
            date: avContext.date,
201
            beatId: avContext.beatId
202
        }).done(function (r) {
203
            var data = r.response || r;
37248 vikas 204
            // Block assignment until the executive has filled the day's agenda.
205
            if (data.agendaFilled === false) {
206
                $('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center; font-weight:600;">'
207
                        + (data.agendaNotFilledMessage || 'Agenda not filled yet you cant assign visit') + '</div>');
208
                $('#avSubmit').prop('disabled', true);
209
                return;
210
            }
36716 ranu 211
            // Drop parties already in the beat — they're handled by the beat plan itself,
212
            // Assign Visit is purely for adding extra (non-beat) visits.
213
            avContext.allParties = (data.parties || []).filter(function (p) {
214
                return !p.inBeat;
215
            });
216
            avContext.agendaOptions = data.agendaOptions || [];
217
            if (!data.dtrUserId) {
218
                $('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +
219
                        'This user has no dtr.users record (cannot create visit tasks).</div>');
220
                return;
221
            }
222
            rebuildDefaultAgenda();
223
            renderAvParties(avContext.allParties);
224
        }).fail(function (xhr) {
225
            $('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +
226
                    'Error loading parties: ' + (xhr.responseText || xhr.statusText) + '</div>');
227
        });
36663 ranu 228
    });
229
 
36716 ranu 230
    // Per-row agenda markup. Initialized as bootstrap-multiselect dropdown
231
    // (project standard — same as today-offer.vm, lead.vm, etc.) after render.
232
    // In-beat parties are filtered out of the list entirely, so only non-beat
233
    // (extra-visit) parties show up here. existingAgendas[] is pre-selected for
234
    // parties that already have a saved visit today (so editing existing ones works).
235
    function agendaSelectHtml(p) {
37182 amit 236
        // The dropdown offers ONLY manual-assignable agendas (Brand addition,
237
        // System training, New Stock Payment). Auto agendas cannot be assigned
238
        // by the planner — they surface as chips below and the server weaves
239
        // them into task_name on submit automatically.
36716 ranu 240
        var preset = {};
241
        (p.existingAgendas || []).forEach(function (a) {
242
            preset[a] = true;
243
        });
244
        var html = '<select multiple class="av-party-agenda"'
245
                + ' data-fofoid="' + p.fofoStoreId + '">';
246
        avContext.agendaOptions.forEach(function (opt) {
37182 amit 247
            html += '<option value="' + opt + '"' + (preset[opt] ? ' selected' : '') + '>' + opt + '</option>';
36716 ranu 248
        });
249
        html += '</select>';
37178 amit 250
        // Open-instance chips: AUTO = gear (metric-closed only), MANUAL = closable ✕.
251
        var chips = '';
252
        (p.openAgendas || []).forEach(function (oa) {
253
            chips += '<span class="label label-' + (oa.source === 'AUTO' ? 'warning' : 'default')
254
                    + '" style="margin-right:4px; display:inline-block; margin-top:3px;">'
255
                    + oa.label + (oa.source === 'AUTO'
256
                            ? ' ⚙'
257
                            : ' <a href="#" class="av-agenda-close" data-instanceid="' + oa.id
258
                              + '" style="color:#fff; text-decoration:none;" title="Close this agenda">&times;</a>')
259
                    + '</span>';
260
        });
261
        if (chips) html += '<div>' + chips + '</div>';
36716 ranu 262
        return html;
263
    }
264
 
37178 amit 265
    // Close a MANUAL agenda instance from its chip.
37494 amit 266
    $(document).on('click.bpdayview', '.av-agenda-close', function (e) {
37178 amit 267
        e.preventDefault();
268
        var chip = $(this).closest('span');
269
        var iid = $(this).data('instanceid');
270
        var remark = prompt('Closing remark (optional):');
271
        if (remark === null) return; // cancelled
272
        $.ajax({
273
            url: context + '/beatPlan/agenda/close',
274
            type: 'POST',
275
            contentType: 'application/json',
276
            data: JSON.stringify({instanceId: iid, remark: remark})
277
        }).done(function () {
278
            chip.remove();
279
        }).fail(function (xhr) {
280
            alert('Close failed: ' + (xhr.responseText || xhr.statusText));
281
        });
282
    });
283
 
36716 ranu 284
    var AGENDA_MULTISELECT_OPTS = {
285
        includeSelectAllOption: false,
286
        maxHeight: 220,
287
        buttonWidth: '240px',
288
        numberDisplayed: 2,
289
        nonSelectedText: 'Pick agenda(s)',
290
        nSelectedText: ' agendas',
291
        allSelectedText: 'All agendas',
292
        enableFiltering: true,
293
        enableCaseInsensitiveFiltering: true
294
    };
295
 
296
    function rebuildDefaultAgenda() {
297
        var $sel = $('#avDefaultAgenda');
298
        // Destroy any prior instance before re-initing (re-open of the modal)
299
        try {
300
                $sel.multiselect('destroy');
301
        } catch (e) {
302
        }
303
        var html = '';
304
        avContext.agendaOptions.forEach(function (opt) {
305
            html += '<option value="' + opt + '">' + opt + '</option>';
306
        });
307
            $sel.html(html);
308
        // Build opts as a variable — inline {...} object literal after $.extend()
309
        // confused Velocity's parser ("Encountered { ... was expecting ...").
310
        var defaultOpts = $.extend({}, AGENDA_MULTISELECT_OPTS);
311
        defaultOpts.nonSelectedText = 'Pick agenda(s) to apply';
312
            $sel.multiselect(defaultOpts);
313
    }
314
 
315
    function initRowMultiselects() {
316
        $('.av-party-agenda').each(function () {
317
            var $sel = $(this);
318
            try {
319
                    $sel.multiselect('destroy');
320
            } catch (e) {
321
            }
322
                $sel.multiselect(AGENDA_MULTISELECT_OPTS);
323
        });
324
    }
325
 
36663 ranu 326
    function renderAvParties(list) {
327
        if (!list || list.length === 0) {
328
            $('#avPartyList').html('<div style="padding:20px; color:#999; text-align:center;">No parties found.</div>');
329
            return;
330
        }
331
        var html = '<table class="table table-condensed table-hover" style="margin-bottom:0; font-size:13px;">';
332
        html += '<thead><tr>'
333
                + '<th style="width:30px;"><input type="checkbox" id="avSelectAll" title="Select all"></th>'
334
                + '<th>Code</th><th>Outlet</th><th>City</th>'
36716 ranu 335
                + '<th style="width:260px;">Agenda</th>'
336
                + '<th style="width:260px;">Description</th>'
36663 ranu 337
                + '</tr></thead><tbody>';
338
        list.forEach(function (p) {
339
            var safeName = (p.outletName || '').replace(/"/g, '&quot;');
36716 ranu 340
            var existingDesc = (p.existingDescription || '').replace(/"/g, '&quot;');
36663 ranu 341
            html += '<tr>'
342
                    + '<td><input type="checkbox" class="av-party-chk" '
343
                    + 'data-fofoid="' + p.fofoStoreId + '" '
344
                    + 'data-name="' + safeName + '" '
345
                    + 'data-lat="' + (p.latitude || '') + '" '
346
                    + 'data-lng="' + (p.longitude || '') + '"></td>'
347
                    + '<td>' + (p.code || '') + '</td>'
348
                    + '<td>' + (p.outletName || '') + '</td>'
349
                    + '<td>' + (p.city || '') + '</td>'
36716 ranu 350
                    + '<td>' + agendaSelectHtml(p) + '</td>'
351
                    + '<td><textarea class="form-control input-sm av-party-desc"'
352
                    + ' data-fofoid="' + p.fofoStoreId + '"'
353
                    + ' rows="2" placeholder="Optional notes..."'
354
                    + ' style="resize:vertical; min-height:34px;">' + existingDesc + '</textarea></td>'
36663 ranu 355
                    + '</tr>';
356
        });
357
        html += '</tbody></table>';
358
        $('#avPartyList').html(html);
36716 ranu 359
        initRowMultiselects();
36663 ranu 360
        updateAvSelectedCount();
361
    }
362
 
363
    // Search filter
37494 amit 364
    $(document).on('input.bpdayview', '#avSearch', function () {
36663 ranu 365
        var q = $(this).val().toLowerCase().trim();
366
        if (!q) {
367
            renderAvParties(avContext.allParties);
368
            return;
369
        }
370
        var filtered = avContext.allParties.filter(function (p) {
371
            return (p.code || '').toLowerCase().indexOf(q) !== -1
372
                    || (p.outletName || '').toLowerCase().indexOf(q) !== -1
373
                    || (p.city || '').toLowerCase().indexOf(q) !== -1;
374
        });
375
        renderAvParties(filtered);
376
    });
377
 
378
    // Select all toggle
37494 amit 379
    $(document).on('change.bpdayview', '#avSelectAll', function () {
36663 ranu 380
        $('.av-party-chk').prop('checked', this.checked);
381
        updateAvSelectedCount();
382
    });
383
 
37494 amit 384
    $(document).on('change.bpdayview', '.av-party-chk', updateAvSelectedCount);
36663 ranu 385
 
386
    function updateAvSelectedCount() {
387
        var n = $('.av-party-chk:checked').length;
388
        $('#avSelectedCount').text(n + ' selected');
389
        $('#avSubmit').prop('disabled', n === 0);
390
    }
391
 
36716 ranu 392
    // Apply the default agendas (multi-select) to all currently-selected rows.
393
    // MERGES with each row's existing selections so in-beat rows keep prior agendas.
394
    // After updating <select>.val(), call multiselect('refresh') so the checkbox
395
    // UI re-syncs with the underlying option state.
37494 amit 396
    $(document).on('click.bpdayview', '#avApplyAgenda', function () {
36716 ranu 397
        var defValues = $('#avDefaultAgenda').val() || [];
398
        if (defValues.length === 0) {
399
            alert('Pick at least one agenda above first');
36663 ranu 400
            return;
401
        }
402
        $('.av-party-chk:checked').each(function () {
403
            var fid = $(this).data('fofoid');
36716 ranu 404
            var $sel = $('.av-party-agenda[data-fofoid="' + fid + '"]');
405
            var existing = $sel.val() || [];
406
            var merged = existing.slice();
407
            defValues.forEach(function (v) {
408
                if (merged.indexOf(v) === -1) merged.push(v);
409
            });
410
                $sel.val(merged);
411
            try {
412
                    $sel.multiselect('refresh');
413
            } catch (e) {
414
            }
36663 ranu 415
        });
416
    });
417
 
418
    // Submit
37494 amit 419
    $(document).on('click.bpdayview', '#avSubmit', function () {
36663 ranu 420
        var picks = [];
421
        var missing = 0;
422
        $('.av-party-chk:checked').each(function () {
423
            var fid = parseInt($(this).data('fofoid'));
36716 ranu 424
            var agendas = $('.av-party-agenda[data-fofoid="' + fid + '"]').val() || [];
425
            var description = ($('.av-party-desc[data-fofoid="' + fid + '"]').val() || '').trim();
37182 amit 426
            // A row with open (auto) agendas is already covered — the server
427
            // weaves those into the visit; "missing" = neither picked nor open.
428
            var party = (avContext.allParties || []).filter(function (x) { return x.fofoStoreId === fid; })[0];
429
            var openCount = party && party.openAgendas ? party.openAgendas.length : 0;
430
            if (agendas.length === 0 && openCount === 0) missing++;
36663 ranu 431
            picks.push({
432
                fofoStoreId: fid,
433
                outletName: $(this).data('name'),
434
                latitude: $(this).data('lat') ? String($(this).data('lat')) : null,
435
                longitude: $(this).data('lng') ? String($(this).data('lng')) : null,
36716 ranu 436
                agendas: agendas,
437
                description: description
36663 ranu 438
            });
439
        });
440
        if (picks.length === 0) return;
441
        if (missing > 0) {
442
            if (!confirm(missing + ' selected row(s) have no agenda. Submit anyway with default "Visit"?')) return;
443
        }
444
 
445
        var btn = $(this);
446
        btn.prop('disabled', true).text('Assigning...');
447
        $('#avMsg').text('');
448
 
449
        $.ajax({
450
            url: context + '/beatPlan/assignVisit/submit',
451
            type: 'POST',
452
            contentType: 'application/json',
453
            data: JSON.stringify({
454
                authUserId: avContext.authUserId,
455
                planDate: avContext.date,
456
                parties: picks
457
            }),
458
            success: function (r) {
459
                var d = r.response || r;
36716 ranu 460
                $('#avMsg').html('<span style="color:#2e7d32;">' + (d.message || 'Saved') + '</span>');
461
                btn.prop('disabled', false).text('Save Assignments');
36663 ranu 462
                setTimeout(function () {
463
                    $('#assignVisitModal').modal('hide');
36716 ranu 464
                }, 1500);
36663 ranu 465
            },
466
            error: function (xhr) {
467
                var msg = 'Failed';
468
                try {
37248 vikas 469
                    var resp = JSON.parse(xhr.responseText).response;
470
                    msg = (resp && resp.message) ? resp.message : (typeof resp === 'string' ? resp : msg);
36663 ranu 471
                } catch (e) {
472
                }
473
                $('#avMsg').html('<span style="color:#c62828;">' + msg + '</span>');
36716 ranu 474
                btn.prop('disabled', false).text('Save Assignments');
36663 ranu 475
            }
476
        });
477
    });
37120 vikas 478
 
479
    // ---------- ADD LEAD ----------
480
    // Routes the rep's assigned leads onto this scheduled beat/date. Writes an
481
    // APPROVED lead_route per pick via /beatPlan/leadsForBeat/submit.
482
    var alCtx = {authUserId: null, userName: '', beatId: null, date: '', allLeads: []};
483
 
37494 amit 484
    $(document).on('click.bpdayview', '.add-lead-btn', function () {
37120 vikas 485
        alCtx.authUserId = $(this).data('userid');
486
        alCtx.userName = $(this).data('username') || '';
487
        alCtx.beatId = $(this).data('beatid');
488
        alCtx.date = $(this).data('date');
489
        alCtx.allLeads = [];
490
 
491
        $('#alTitle').text('Add Lead - ' + alCtx.userName + ' on ' + alCtx.date);
492
        $('#alSearch').val('');
493
        $('#alLeadList').html('<div style="padding:20px; color:#999; text-align:center;">Loading leads...</div>');
494
        $('#alMsg').text('');
495
        $('#alSelectedCount').text('0 selected');
496
        $('#alSubmit').prop('disabled', true);
497
        $('#addLeadModal').modal('show');
498
 
499
        $.get(context + '/beatPlan/assignedLeads', {
500
            authUserId: alCtx.authUserId,
501
            beatId: alCtx.beatId,
502
            date: alCtx.date
503
        }).done(function (r) {
504
            var data = r.response || r;
505
            alCtx.allLeads = data.rows || [];
506
            renderAlLeads(alCtx.allLeads);
507
        }).fail(function (xhr) {
508
            $('#alLeadList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +
509
                    'Error loading leads: ' + (xhr.responseText || xhr.statusText) + '</div>');
510
        });
511
    });
512
 
513
    function renderAlLeads(list) {
514
        if (!list || list.length === 0) {
515
            $('#alLeadList').html('<div style="padding:20px; color:#999; text-align:center;">No active leads assigned to this user.</div>');
516
            return;
517
        }
518
        var html = '<table class="table table-condensed table-hover" style="margin-bottom:0; font-size:13px;">';
519
        html += '<thead><tr>'
520
                + '<th style="width:30px;"><input type="checkbox" id="alSelectAll" title="Select all"></th>'
521
                + '<th>Lead</th><th>Outlet</th><th>City</th><th>Stage</th><th style="width:80px;"></th>'
522
                + '</tr></thead><tbody>';
523
        list.forEach(function (p) {
524
            var onBeat = !!p.alreadyOnBeat;
525
            html += '<tr' + (onBeat ? ' style="opacity:.55;"' : '') + '>'
526
                    + '<td><input type="checkbox" class="al-lead-chk" data-lead="' + p.leadId + '"'
527
                    + (onBeat ? ' checked disabled' : '') + '></td>'
528
                    + '<td>' + (p.leadName || '') + (p.mobile ? ' <span style="color:#999;">· ' + p.mobile + '</span>' : '') + '</td>'
529
                    + '<td>' + (p.outletName || '') + '</td>'
530
                    + '<td>' + (p.city || '') + '</td>'
531
                    + '<td>' + (p.stage || '') + (p.hasGeo ? '' : ' <span class="label label-warning" title="No approved location">no geo</span>') + '</td>'
532
                    + '<td>' + (onBeat ? '<span class="label label-success">On beat</span>' : '') + '</td>'
533
                    + '</tr>';
534
        });
535
        html += '</tbody></table>';
536
        $('#alLeadList').html(html);
537
        updateAlSelectedCount();
538
    }
539
 
37494 amit 540
    $(document).on('input.bpdayview', '#alSearch', function () {
37120 vikas 541
        var q = $(this).val().toLowerCase().trim();
542
        if (!q) {
543
            renderAlLeads(alCtx.allLeads);
544
            return;
545
        }
546
        var filtered = alCtx.allLeads.filter(function (p) {
547
            return (p.leadName || '').toLowerCase().indexOf(q) !== -1
548
                    || (p.outletName || '').toLowerCase().indexOf(q) !== -1
549
                    || (p.city || '').toLowerCase().indexOf(q) !== -1
550
                    || (p.mobile || '').toLowerCase().indexOf(q) !== -1;
551
        });
552
        renderAlLeads(filtered);
553
    });
554
 
37494 amit 555
    $(document).on('change.bpdayview', '#alSelectAll', function () {
37120 vikas 556
        $('.al-lead-chk:not(:disabled)').prop('checked', this.checked);
557
        updateAlSelectedCount();
558
    });
559
 
37494 amit 560
    $(document).on('change.bpdayview', '.al-lead-chk', updateAlSelectedCount);
37120 vikas 561
 
562
    function updateAlSelectedCount() {
563
        var n = $('.al-lead-chk:checked:not(:disabled)').length;
564
        $('#alSelectedCount').text(n + ' selected');
565
        $('#alSubmit').prop('disabled', n === 0);
566
    }
567
 
37494 amit 568
    $(document).on('click.bpdayview', '#alSubmit', function () {
37120 vikas 569
        var ids = [];
570
        $('.al-lead-chk:checked:not(:disabled)').each(function () {
571
            ids.push(parseInt($(this).data('lead')));
572
        });
573
        if (ids.length === 0) return;
574
        var btn = $(this);
575
        btn.prop('disabled', true).text('Adding...');
576
        $('#alMsg').text('');
577
        $.ajax({
578
            url: context + '/beatPlan/leadsForBeat/submit',
579
            type: 'POST',
580
            contentType: 'application/json',
581
            data: JSON.stringify({
582
                authUserId: alCtx.authUserId,
583
                beatId: alCtx.beatId,
584
                date: alCtx.date,
585
                leadIds: ids
586
            }),
587
            success: function (r) {
588
                var d = r.response || r;
589
                $('#alMsg').html('<span style="color:#2e7d32;">' + (d.message || 'Saved') + '</span>');
590
                btn.prop('disabled', false).text('Add Leads');
591
                setTimeout(function () {
592
                    $('#addLeadModal').modal('hide');
593
                    loadDayView();
594
                }, 1200);
595
            },
596
            error: function (xhr) {
597
                var msg = 'Failed';
598
                try {
599
                    msg = (JSON.parse(xhr.responseText).response.message) || msg;
600
                } catch (e) {
601
                }
602
                $('#alMsg').html('<span style="color:#c62828;">' + msg + '</span>');
603
                btn.prop('disabled', false).text('Add Leads');
604
            }
605
        });
606
    });
36662 ranu 607
</script>
608
 
37120 vikas 609
<!-- Add Lead Modal -->
610
<div class="modal fade" id="addLeadModal" tabindex="-1" style="overflow-y:auto;">
611
    <div class="modal-dialog" style="width:90%; max-width:820px; margin-top:30px;">
612
        <div class="modal-content">
613
            <div class="modal-header">
614
                <button type="button" class="close" data-dismiss="modal">&times;</button>
615
                <h4 class="modal-title" id="alTitle">Add Lead</h4>
616
            </div>
617
            <div class="modal-body">
618
                <div style="display:flex; gap:10px; align-items:center; margin-bottom:10px;">
619
                    <input type="text" id="alSearch" class="form-control input-sm"
620
                           placeholder="Search by name, outlet, city or mobile..." style="flex:1;">
621
                    <span id="alSelectedCount" style="font-size:12px; color:#666;">0 selected</span>
622
                </div>
623
                <div id="alLeadList"
624
                     style="max-height:50vh; overflow-y:auto; border:1px solid #e5e5e5; border-radius:4px;"></div>
625
                <div id="alMsg" style="margin-top:10px; font-size:12px;"></div>
626
            </div>
627
            <div class="modal-footer">
628
                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
629
                <button type="button" class="btn btn-success" id="alSubmit" disabled>Add Leads</button>
630
            </div>
631
        </div>
632
    </div>
633
</div>
634
 
36663 ranu 635
<!-- Assign Visit Modal -->
636
<div class="modal fade" id="assignVisitModal" tabindex="-1" style="overflow-y:auto;">
637
    <div class="modal-dialog" style="width:90%; max-width:900px; margin-top:30px;">
638
        <div class="modal-content">
639
            <div class="modal-header">
640
                <button type="button" class="close" data-dismiss="modal">&times;</button>
641
                <h4 class="modal-title" id="avTitle">Assign Visit</h4>
642
            </div>
643
            <div class="modal-body">
36716 ranu 644
                <div style="display:flex; gap:10px; align-items:flex-start; margin-bottom:10px;">
645
                    <div style="flex:1;">
646
                        <label style="font-size:11px; color:#666; font-weight:normal; margin-bottom:2px;">
647
                            Agenda - applies to selected rows
648
                        </label>
649
                        <select id="avDefaultAgenda" multiple></select>
650
                    </div>
36663 ranu 651
                    <button type="button" class="btn btn-default btn-sm" id="avApplyAgenda"
36716 ranu 652
                            style="margin-top:18px;"
653
                            title="Add the picked agendas into all selected rows (merges with existing)">
36663 ranu 654
                        Apply to Selected
655
                    </button>
656
                </div>
657
                <div style="display:flex; gap:10px; align-items:center; margin-bottom:10px;">
658
                    <input type="text" id="avSearch" class="form-control input-sm"
659
                           placeholder="Search by code, outlet name or city..." style="flex:1;">
660
                    <span id="avSelectedCount" style="font-size:12px; color:#666;">0 selected</span>
661
                </div>
662
                <div id="avPartyList"
663
                     style="max-height:50vh; overflow-y:auto; border:1px solid #e5e5e5; border-radius:4px;"></div>
664
                <div id="avMsg" style="margin-top:10px; font-size:12px;"></div>
665
            </div>
666
            <div class="modal-footer">
667
                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
36716 ranu 668
                <button type="button" class="btn btn-success" id="avSubmit" disabled>Save Assignments</button>
36663 ranu 669
            </div>
670
        </div>
671
    </div>
672
</div>
673
 
36662 ranu 674
<!-- Calendar modal (iframe loads /beatPlanWindow with auto-selected user) -->
675
<div class="modal fade" id="userCalendarModal" tabindex="-1" style="overflow-y:auto;">
676
    <div class="modal-dialog" style="width:95%; max-width:1300px; margin-top:20px;">
677
        <div class="modal-content">
678
            <div class="modal-header">
679
                <button type="button" class="close" data-dismiss="modal"
680
                        onclick="$('#userCalendarFrame').attr('src','');">&times;
681
                </button>
682
                <h4 class="modal-title" id="userCalendarTitle">Calendar</h4>
683
            </div>
684
            <div class="modal-body" style="padding:0;">
685
                <iframe id="userCalendarFrame" style="width:100%; height:80vh; border:none;"></iframe>
686
            </div>
687
        </div>
688
    </div>
689
</div>