Subversion Repositories SmartDukaan

Rev

Rev 36662 | Rev 36716 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 36662 Rev 36663
Line 104... Line 104...
104
                    + '<td><strong>' + r.visitCount + '</strong></td>'
104
                    + '<td><strong>' + r.visitCount + '</strong></td>'
105
                    + '<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> '
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" '
107
                    + '<button class="btn btn-xs btn-warning edit-beat-on-date" '
108
                    + 'data-userid="' + r.authUserId + '" data-username="' + r.userName + '" '
108
                    + 'data-userid="' + r.authUserId + '" data-username="' + r.userName + '" '
109
                    + 'data-beatid="' + r.beatId + '" data-date="' + r.scheduleDate + '">Edit</button>'
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>'
110
                    + '</td>'
113
                    + '</td>'
111
                    + '</tr>';
114
                    + '</tr>';
112
        });
115
        });
113
        $('#dayViewRows').html(html);
116
        $('#dayViewRows').html(html);
114
    }
117
    }
Line 135... Line 138...
135
                + '&editBeatId=' + beatId + '&editDate=' + date;
138
                + '&editBeatId=' + beatId + '&editDate=' + date;
136
        $('#userCalendarTitle').text('Edit Beat - ' + userName + ' on ' + date);
139
        $('#userCalendarTitle').text('Edit Beat - ' + userName + ' on ' + date);
137
        $('#userCalendarFrame').attr('src', url);
140
        $('#userCalendarFrame').attr('src', url);
138
        $('#userCalendarModal').modal('show');
141
        $('#userCalendarModal').modal('show');
139
    });
142
    });
-
 
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
    });
140
</script>
307
</script>
141
 
308
 
-
 
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
 
142
<!-- Calendar modal (iframe loads /beatPlanWindow with auto-selected user) -->
343
<!-- Calendar modal (iframe loads /beatPlanWindow with auto-selected user) -->
143
<div class="modal fade" id="userCalendarModal" tabindex="-1" style="overflow-y:auto;">
344
<div class="modal fade" id="userCalendarModal" tabindex="-1" style="overflow-y:auto;">
144
    <div class="modal-dialog" style="width:95%; max-width:1300px; margin-top:20px;">
345
    <div class="modal-dialog" style="width:95%; max-width:1300px; margin-top:20px;">
145
        <div class="modal-content">
346
        <div class="modal-content">
146
            <div class="modal-header">
347
            <div class="modal-header">