Subversion Repositories SmartDukaan

Rev

Rev 36662 | Rev 36716 | Go to most recent revision | 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;">
15
            <label style="margin:0;">From:</label>
16
            <input type="date" id="dayViewStart" class="form-control input-sm" style="width:160px;">
17
            <label style="margin:0;">To:</label>
18
            <input type="date" id="dayViewEnd" class="form-control input-sm" style="width:160px;">
19
            <button class="btn btn-primary btn-sm" id="dayViewLoad">Load</button>
20
            <span id="dayViewSummary" style="color:#777; font-size:12px; margin-left:10px;"></span>
21
        </div>
22
    </div>
23
 
24
    <div class="row">
25
        <div class="col-lg-12">
26
            <div class="table-responsive">
27
                <table class="table table-bordered table-striped table-hover" id="dayViewTable">
28
                    <thead>
29
                    <tr>
30
                        <th>User</th>
31
                        <th>Date</th>
32
                        <th>Beat</th>
33
                        <th>Day</th>
34
                        <th>Partners</th>
35
                        <th>Leads</th>
36
                        <th>Total Visits</th>
37
                        <th>Action</th>
38
                    </tr>
39
                    </thead>
40
                    <tbody id="dayViewRows">
41
                    <tr>
42
                        <td colspan="8" style="text-align:center; color:#999;">Pick a date range and click Load.</td>
43
                    </tr>
44
                    </tbody>
45
                </table>
46
            </div>
47
        </div>
48
    </div>
49
</section>
50
 
51
 
52
<script>
53
    (function () {
54
        // Default: today → today + 7 days
55
        var today = new Date();
56
        var week = new Date();
57
        week.setDate(today.getDate() + 7);
58
 
59
        function fmt(d) {
60
            return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2);
61
        }
62
 
63
        $('#dayViewStart').val(fmt(today));
64
        $('#dayViewEnd').val(fmt(week));
65
        loadDayView();
66
    })();
67
 
68
    $(document).on('click', '#dayViewLoad', function () {
69
        loadDayView();
70
    });
71
 
72
    function loadDayView() {
73
        var s = $('#dayViewStart').val();
74
        var e = $('#dayViewEnd').val();
75
        if (!s || !e) {
76
            alert('Pick a date range');
77
            return;
78
        }
79
        $('#dayViewRows').html('<tr><td colspan="8" style="text-align:center;">Loading...</td></tr>');
80
        $('#dayViewSummary').text('');
81
        $.get(context + '/beatPlan/scheduledList', {startDate: s, endDate: e}).done(function (r) {
82
            var data = r.response || r;
83
            renderDayViewRows(data.rows || []);
84
            $('#dayViewSummary').text(data.rows.length + ' beats scheduled between ' + data.startDate + ' and ' + data.endDate);
85
        }).fail(function (xhr) {
86
            $('#dayViewRows').html('<tr><td colspan="8" style="color:#d9534f; text-align:center;">Error: ' + (xhr.responseText || xhr.statusText) + '</td></tr>');
87
        });
88
    }
89
 
90
    function renderDayViewRows(rows) {
91
        if (!rows || rows.length === 0) {
92
            $('#dayViewRows').html('<tr><td colspan="8" style="text-align:center; color:#999;">No beats scheduled in this range.</td></tr>');
93
            return;
94
        }
95
        var html = '';
96
        rows.forEach(function (r) {
97
            html += '<tr>'
98
                    + '<td>' + r.userName + '</td>'
99
                    + '<td><span class="label label-primary">' + r.scheduleDate + '</span></td>'
100
                    + '<td><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:' + (r.beatColor || '#3498db') + ';margin-right:6px;"></span>' + r.beatName + '</td>'
101
                    + '<td>' + r.dayNumber + '</td>'
102
                    + '<td>' + r.partnerCount + '</td>'
103
                    + '<td>' + (r.leadCount > 0 ? '<span class="label label-warning">' + r.leadCount + '</span>' : '0') + '</td>'
104
                    + '<td><strong>' + r.visitCount + '</strong></td>'
105
                    + '<td>'
106
                    + '<button class="btn btn-xs btn-info view-user-calendar" data-userid="' + r.authUserId + '" data-username="' + r.userName + '">View Calendar</button> '
107
                    + '<button class="btn btn-xs btn-warning edit-beat-on-date" '
108
                    + 'data-userid="' + r.authUserId + '" data-username="' + r.userName + '" '
36663 ranu 109
                    + 'data-beatid="' + r.beatId + '" data-date="' + r.scheduleDate + '">Edit</button> '
110
                    + '<button class="btn btn-xs btn-success assign-visit-btn" '
111
                    + 'data-userid="' + r.authUserId + '" data-username="' + r.userName + '" '
112
                    + 'data-date="' + r.scheduleDate + '">Assign Visit</button>'
36662 ranu 113
                    + '</td>'
114
                    + '</tr>';
115
        });
116
        $('#dayViewRows').html(html);
117
    }
118
 
119
    // View -> open user's calendar inside the dashboard in a modal iframe (pre-selected)
120
    $(document).on('click', '.view-user-calendar', function () {
121
        var userId = $(this).data('userid');
122
        var userName = $(this).data('username') || '';
123
        var url = context + '/beatPlanWindow?autoUserId=' + userId
124
                + '&autoUserName=' + encodeURIComponent(userName);
125
        $('#userCalendarTitle').text('Calendar - ' + userName);
126
        $('#userCalendarFrame').attr('src', url);
127
        $('#userCalendarModal').modal('show');
128
    });
129
 
130
    // Edit a beat on a specific date — opens beatPlanWindow in edit mode for that run
131
    $(document).on('click', '.edit-beat-on-date', function () {
132
        var userId = $(this).data('userid');
133
        var userName = $(this).data('username') || '';
134
        var beatId = $(this).data('beatid');
135
        var date = $(this).data('date');
136
        var url = context + '/beatPlanWindow?autoUserId=' + userId
137
                + '&autoUserName=' + encodeURIComponent(userName)
138
                + '&editBeatId=' + beatId + '&editDate=' + date;
139
        $('#userCalendarTitle').text('Edit Beat - ' + userName + ' on ' + date);
140
        $('#userCalendarFrame').attr('src', url);
141
        $('#userCalendarModal').modal('show');
142
    });
36663 ranu 143
 
144
    // ---------- ASSIGN VISIT ----------
145
    var avContext = {authUserId: null, userName: '', date: '', allParties: []};
146
 
147
    $(document).on('click', '.assign-visit-btn', function () {
148
        avContext.authUserId = $(this).data('userid');
149
        avContext.userName = $(this).data('username') || '';
150
        avContext.date = $(this).data('date');
151
        avContext.allParties = [];
152
 
153
        $('#avTitle').text('Assign Visit — ' + avContext.userName + ' on ' + avContext.date);
154
        $('#avSearch').val('');
155
        $('#avPartyList').html('<div style="padding:20px; color:#999; text-align:center;">Loading parties...</div>');
156
        $('#avMsg').text('');
157
        $('#avSelectedCount').text('0 selected');
158
        $('#avSubmit').prop('disabled', true);
159
        $('#assignVisitModal').modal('show');
160
 
161
        $.get(context + '/beatPlan/assignVisit/parties', {authUserId: avContext.authUserId})
162
                .done(function (r) {
163
                    var data = r.response || r;
164
                    avContext.allParties = data.parties || [];
165
                    if (!data.dtrUserId) {
166
                        $('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +
167
                                'This user has no dtr.users record (cannot create visit tasks).</div>');
168
                        return;
169
                    }
170
                    renderAvParties(avContext.allParties);
171
                })
172
                .fail(function (xhr) {
173
                    $('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +
174
                            'Error loading parties: ' + (xhr.responseText || xhr.statusText) + '</div>');
175
                });
176
    });
177
 
178
    function renderAvParties(list) {
179
        if (!list || list.length === 0) {
180
            $('#avPartyList').html('<div style="padding:20px; color:#999; text-align:center;">No parties found.</div>');
181
            return;
182
        }
183
        var html = '<table class="table table-condensed table-hover" style="margin-bottom:0; font-size:13px;">';
184
        html += '<thead><tr>'
185
                + '<th style="width:30px;"><input type="checkbox" id="avSelectAll" title="Select all"></th>'
186
                + '<th>Code</th><th>Outlet</th><th>City</th>'
187
                + '<th style="width:220px;">Agenda</th>'
188
                + '</tr></thead><tbody>';
189
        list.forEach(function (p) {
190
            var safeName = (p.outletName || '').replace(/"/g, '&quot;');
191
            html += '<tr>'
192
                    + '<td><input type="checkbox" class="av-party-chk" '
193
                    + 'data-fofoid="' + p.fofoStoreId + '" '
194
                    + 'data-name="' + safeName + '" '
195
                    + 'data-lat="' + (p.latitude || '') + '" '
196
                    + 'data-lng="' + (p.longitude || '') + '"></td>'
197
                    + '<td>' + (p.code || '') + '</td>'
198
                    + '<td>' + (p.outletName || '') + '</td>'
199
                    + '<td>' + (p.city || '') + '</td>'
200
                    + '<td><input type="text" class="form-control input-sm av-party-agenda" '
201
                    + 'placeholder="e.g. Stock review" '
202
                    + 'data-fofoid="' + p.fofoStoreId + '"></td>'
203
                    + '</tr>';
204
        });
205
        html += '</tbody></table>';
206
        $('#avPartyList').html(html);
207
        updateAvSelectedCount();
208
    }
209
 
210
    // Search filter
211
    $(document).on('input', '#avSearch', function () {
212
        var q = $(this).val().toLowerCase().trim();
213
        if (!q) {
214
            renderAvParties(avContext.allParties);
215
            return;
216
        }
217
        var filtered = avContext.allParties.filter(function (p) {
218
            return (p.code || '').toLowerCase().indexOf(q) !== -1
219
                    || (p.outletName || '').toLowerCase().indexOf(q) !== -1
220
                    || (p.city || '').toLowerCase().indexOf(q) !== -1;
221
        });
222
        renderAvParties(filtered);
223
    });
224
 
225
    // Select all toggle
226
    $(document).on('change', '#avSelectAll', function () {
227
        $('.av-party-chk').prop('checked', this.checked);
228
        updateAvSelectedCount();
229
    });
230
 
231
    // Per-row checkbox
232
    $(document).on('change', '.av-party-chk', updateAvSelectedCount);
233
 
234
    function updateAvSelectedCount() {
235
        var n = $('.av-party-chk:checked').length;
236
        $('#avSelectedCount').text(n + ' selected');
237
        $('#avSubmit').prop('disabled', n === 0);
238
    }
239
 
240
    // Apply the default agenda value to all currently-selected rows
241
    $(document).on('click', '#avApplyAgenda', function () {
242
        var def = $('#avDefaultAgenda').val();
243
        if (!def) {
244
            alert('Type an agenda first');
245
            return;
246
        }
247
        $('.av-party-chk:checked').each(function () {
248
            var fid = $(this).data('fofoid');
249
            $('.av-party-agenda[data-fofoid="' + fid + '"]').val(def);
250
        });
251
    });
252
 
253
    // Submit
254
    $(document).on('click', '#avSubmit', function () {
255
        var picks = [];
256
        var missing = 0;
257
        $('.av-party-chk:checked').each(function () {
258
            var fid = parseInt($(this).data('fofoid'));
259
            var agenda = $('.av-party-agenda[data-fofoid="' + fid + '"]').val();
260
            agenda = agenda ? agenda.trim() : '';
261
            if (!agenda) missing++;
262
            picks.push({
263
                fofoStoreId: fid,
264
                outletName: $(this).data('name'),
265
                latitude: $(this).data('lat') ? String($(this).data('lat')) : null,
266
                longitude: $(this).data('lng') ? String($(this).data('lng')) : null,
267
                agenda: agenda
268
            });
269
        });
270
        if (picks.length === 0) return;
271
        if (missing > 0) {
272
            if (!confirm(missing + ' selected row(s) have no agenda. Submit anyway with default "Visit"?')) return;
273
        }
274
 
275
        var btn = $(this);
276
        btn.prop('disabled', true).text('Assigning...');
277
        $('#avMsg').text('');
278
 
279
        $.ajax({
280
            url: context + '/beatPlan/assignVisit/submit',
281
            type: 'POST',
282
            contentType: 'application/json',
283
            data: JSON.stringify({
284
                authUserId: avContext.authUserId,
285
                planDate: avContext.date,
286
                parties: picks
287
            }),
288
            success: function (r) {
289
                var d = r.response || r;
290
                $('#avMsg').html('<span style="color:#2e7d32;">' + (d.message || 'Assigned successfully') + '</span>');
291
                btn.prop('disabled', false).text('Assign Selected');
292
                setTimeout(function () {
293
                    $('#assignVisitModal').modal('hide');
294
                }, 1200);
295
            },
296
            error: function (xhr) {
297
                var msg = 'Failed';
298
                try {
299
                    msg = (JSON.parse(xhr.responseText).response.message) || msg;
300
                } catch (e) {
301
                }
302
                $('#avMsg').html('<span style="color:#c62828;">' + msg + '</span>');
303
                btn.prop('disabled', false).text('Assign Selected');
304
            }
305
        });
306
    });
36662 ranu 307
</script>
308
 
36663 ranu 309
<!-- Assign Visit Modal -->
310
<div class="modal fade" id="assignVisitModal" tabindex="-1" style="overflow-y:auto;">
311
    <div class="modal-dialog" style="width:90%; max-width:900px; margin-top:30px;">
312
        <div class="modal-content">
313
            <div class="modal-header">
314
                <button type="button" class="close" data-dismiss="modal">&times;</button>
315
                <h4 class="modal-title" id="avTitle">Assign Visit</h4>
316
            </div>
317
            <div class="modal-body">
318
                <div style="display:flex; gap:10px; align-items:center; margin-bottom:10px;">
319
                    <input type="text" id="avDefaultAgenda" class="form-control input-sm"
320
                           placeholder="Agenda (applies to selected rows)" style="flex:1;">
321
                    <button type="button" class="btn btn-default btn-sm" id="avApplyAgenda"
322
                            title="Fill agenda into all selected rows">
323
                        Apply to Selected
324
                    </button>
325
                </div>
326
                <div style="display:flex; gap:10px; align-items:center; margin-bottom:10px;">
327
                    <input type="text" id="avSearch" class="form-control input-sm"
328
                           placeholder="Search by code, outlet name or city..." style="flex:1;">
329
                    <span id="avSelectedCount" style="font-size:12px; color:#666;">0 selected</span>
330
                </div>
331
                <div id="avPartyList"
332
                     style="max-height:50vh; overflow-y:auto; border:1px solid #e5e5e5; border-radius:4px;"></div>
333
                <div id="avMsg" style="margin-top:10px; font-size:12px;"></div>
334
            </div>
335
            <div class="modal-footer">
336
                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
337
                <button type="button" class="btn btn-success" id="avSubmit" disabled>Assign Selected</button>
338
            </div>
339
        </div>
340
    </div>
341
</div>
342
 
36662 ranu 343
<!-- Calendar modal (iframe loads /beatPlanWindow with auto-selected user) -->
344
<div class="modal fade" id="userCalendarModal" tabindex="-1" style="overflow-y:auto;">
345
    <div class="modal-dialog" style="width:95%; max-width:1300px; margin-top:20px;">
346
        <div class="modal-content">
347
            <div class="modal-header">
348
                <button type="button" class="close" data-dismiss="modal"
349
                        onclick="$('#userCalendarFrame').attr('src','');">&times;
350
                </button>
351
                <h4 class="modal-title" id="userCalendarTitle">Calendar</h4>
352
            </div>
353
            <div class="modal-body" style="padding:0;">
354
                <iframe id="userCalendarFrame" style="width:100%; height:80vh; border:none;"></iframe>
355
            </div>
356
        </div>
357
    </div>
358
</div>