Rev 36662 | Rev 36716 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<section class="wrapper"><div class="row"><div class="col-lg-12"><h3 class="page-header"><i class="icon_calendar"></i> Scheduled Beats - Day View</h3><ol class="breadcrumb"><li><i class="fa fa-home"></i><a href="${rc.contextPath}/dashboard">Home</a></li><li><i class="icon_group"></i> Partner Acquisition</li><li><i class="icon_calendar"></i> Beat Day View</li></ol></div></div><div class="row" style="margin-bottom:10px;"><div class="col-lg-12" style="display:flex; gap:10px; align-items:center; flex-wrap:wrap;"><label style="margin:0;">From:</label><input type="date" id="dayViewStart" class="form-control input-sm" style="width:160px;"><label style="margin:0;">To:</label><input type="date" id="dayViewEnd" class="form-control input-sm" style="width:160px;"><button class="btn btn-primary btn-sm" id="dayViewLoad">Load</button><span id="dayViewSummary" style="color:#777; font-size:12px; margin-left:10px;"></span></div></div><div class="row"><div class="col-lg-12"><div class="table-responsive"><table class="table table-bordered table-striped table-hover" id="dayViewTable"><thead><tr><th>User</th><th>Date</th><th>Beat</th><th>Day</th><th>Partners</th><th>Leads</th><th>Total Visits</th><th>Action</th></tr></thead><tbody id="dayViewRows"><tr><td colspan="8" style="text-align:center; color:#999;">Pick a date range and click Load.</td></tr></tbody></table></div></div></div></section><script>(function () {// Default: today → today + 7 daysvar today = new Date();var week = new Date();week.setDate(today.getDate() + 7);function fmt(d) {return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2);}$('#dayViewStart').val(fmt(today));$('#dayViewEnd').val(fmt(week));loadDayView();})();$(document).on('click', '#dayViewLoad', function () {loadDayView();});function loadDayView() {var s = $('#dayViewStart').val();var e = $('#dayViewEnd').val();if (!s || !e) {alert('Pick a date range');return;}$('#dayViewRows').html('<tr><td colspan="8" style="text-align:center;">Loading...</td></tr>');$('#dayViewSummary').text('');$.get(context + '/beatPlan/scheduledList', {startDate: s, endDate: e}).done(function (r) {var data = r.response || r;renderDayViewRows(data.rows || []);$('#dayViewSummary').text(data.rows.length + ' beats scheduled between ' + data.startDate + ' and ' + data.endDate);}).fail(function (xhr) {$('#dayViewRows').html('<tr><td colspan="8" style="color:#d9534f; text-align:center;">Error: ' + (xhr.responseText || xhr.statusText) + '</td></tr>');});}function renderDayViewRows(rows) {if (!rows || rows.length === 0) {$('#dayViewRows').html('<tr><td colspan="8" style="text-align:center; color:#999;">No beats scheduled in this range.</td></tr>');return;}var html = '';rows.forEach(function (r) {html += '<tr>'+ '<td>' + r.userName + '</td>'+ '<td><span class="label label-primary">' + r.scheduleDate + '</span></td>'+ '<td><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:' + (r.beatColor || '#3498db') + ';margin-right:6px;"></span>' + r.beatName + '</td>'+ '<td>' + r.dayNumber + '</td>'+ '<td>' + r.partnerCount + '</td>'+ '<td>' + (r.leadCount > 0 ? '<span class="label label-warning">' + r.leadCount + '</span>' : '0') + '</td>'+ '<td><strong>' + r.visitCount + '</strong></td>'+ '<td>'+ '<button class="btn btn-xs btn-info view-user-calendar" data-userid="' + r.authUserId + '" data-username="' + r.userName + '">View Calendar</button> '+ '<button class="btn btn-xs btn-warning edit-beat-on-date" '+ 'data-userid="' + r.authUserId + '" data-username="' + r.userName + '" '+ 'data-beatid="' + r.beatId + '" data-date="' + r.scheduleDate + '">Edit</button> '+ '<button class="btn btn-xs btn-success assign-visit-btn" '+ 'data-userid="' + r.authUserId + '" data-username="' + r.userName + '" '+ 'data-date="' + r.scheduleDate + '">Assign Visit</button>'+ '</td>'+ '</tr>';});$('#dayViewRows').html(html);}// View -> open user's calendar inside the dashboard in a modal iframe (pre-selected)$(document).on('click', '.view-user-calendar', function () {var userId = $(this).data('userid');var userName = $(this).data('username') || '';var url = context + '/beatPlanWindow?autoUserId=' + userId+ '&autoUserName=' + encodeURIComponent(userName);$('#userCalendarTitle').text('Calendar - ' + userName);$('#userCalendarFrame').attr('src', url);$('#userCalendarModal').modal('show');});// Edit a beat on a specific date — opens beatPlanWindow in edit mode for that run$(document).on('click', '.edit-beat-on-date', function () {var userId = $(this).data('userid');var userName = $(this).data('username') || '';var beatId = $(this).data('beatid');var date = $(this).data('date');var url = context + '/beatPlanWindow?autoUserId=' + userId+ '&autoUserName=' + encodeURIComponent(userName)+ '&editBeatId=' + beatId + '&editDate=' + date;$('#userCalendarTitle').text('Edit Beat - ' + userName + ' on ' + date);$('#userCalendarFrame').attr('src', url);$('#userCalendarModal').modal('show');});// ---------- ASSIGN VISIT ----------var avContext = {authUserId: null, userName: '', date: '', allParties: []};$(document).on('click', '.assign-visit-btn', function () {avContext.authUserId = $(this).data('userid');avContext.userName = $(this).data('username') || '';avContext.date = $(this).data('date');avContext.allParties = [];$('#avTitle').text('Assign Visit — ' + avContext.userName + ' on ' + avContext.date);$('#avSearch').val('');$('#avPartyList').html('<div style="padding:20px; color:#999; text-align:center;">Loading parties...</div>');$('#avMsg').text('');$('#avSelectedCount').text('0 selected');$('#avSubmit').prop('disabled', true);$('#assignVisitModal').modal('show');$.get(context + '/beatPlan/assignVisit/parties', {authUserId: avContext.authUserId}).done(function (r) {var data = r.response || r;avContext.allParties = data.parties || [];if (!data.dtrUserId) {$('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +'This user has no dtr.users record (cannot create visit tasks).</div>');return;}renderAvParties(avContext.allParties);}).fail(function (xhr) {$('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +'Error loading parties: ' + (xhr.responseText || xhr.statusText) + '</div>');});});function renderAvParties(list) {if (!list || list.length === 0) {$('#avPartyList').html('<div style="padding:20px; color:#999; text-align:center;">No parties found.</div>');return;}var html = '<table class="table table-condensed table-hover" style="margin-bottom:0; font-size:13px;">';html += '<thead><tr>'+ '<th style="width:30px;"><input type="checkbox" id="avSelectAll" title="Select all"></th>'+ '<th>Code</th><th>Outlet</th><th>City</th>'+ '<th style="width:220px;">Agenda</th>'+ '</tr></thead><tbody>';list.forEach(function (p) {var safeName = (p.outletName || '').replace(/"/g, '"');html += '<tr>'+ '<td><input type="checkbox" class="av-party-chk" '+ 'data-fofoid="' + p.fofoStoreId + '" '+ 'data-name="' + safeName + '" '+ 'data-lat="' + (p.latitude || '') + '" '+ 'data-lng="' + (p.longitude || '') + '"></td>'+ '<td>' + (p.code || '') + '</td>'+ '<td>' + (p.outletName || '') + '</td>'+ '<td>' + (p.city || '') + '</td>'+ '<td><input type="text" class="form-control input-sm av-party-agenda" '+ 'placeholder="e.g. Stock review" '+ 'data-fofoid="' + p.fofoStoreId + '"></td>'+ '</tr>';});html += '</tbody></table>';$('#avPartyList').html(html);updateAvSelectedCount();}// Search filter$(document).on('input', '#avSearch', function () {var q = $(this).val().toLowerCase().trim();if (!q) {renderAvParties(avContext.allParties);return;}var filtered = avContext.allParties.filter(function (p) {return (p.code || '').toLowerCase().indexOf(q) !== -1|| (p.outletName || '').toLowerCase().indexOf(q) !== -1|| (p.city || '').toLowerCase().indexOf(q) !== -1;});renderAvParties(filtered);});// Select all toggle$(document).on('change', '#avSelectAll', function () {$('.av-party-chk').prop('checked', this.checked);updateAvSelectedCount();});// Per-row checkbox$(document).on('change', '.av-party-chk', updateAvSelectedCount);function updateAvSelectedCount() {var n = $('.av-party-chk:checked').length;$('#avSelectedCount').text(n + ' selected');$('#avSubmit').prop('disabled', n === 0);}// Apply the default agenda value to all currently-selected rows$(document).on('click', '#avApplyAgenda', function () {var def = $('#avDefaultAgenda').val();if (!def) {alert('Type an agenda first');return;}$('.av-party-chk:checked').each(function () {var fid = $(this).data('fofoid');$('.av-party-agenda[data-fofoid="' + fid + '"]').val(def);});});// Submit$(document).on('click', '#avSubmit', function () {var picks = [];var missing = 0;$('.av-party-chk:checked').each(function () {var fid = parseInt($(this).data('fofoid'));var agenda = $('.av-party-agenda[data-fofoid="' + fid + '"]').val();agenda = agenda ? agenda.trim() : '';if (!agenda) missing++;picks.push({fofoStoreId: fid,outletName: $(this).data('name'),latitude: $(this).data('lat') ? String($(this).data('lat')) : null,longitude: $(this).data('lng') ? String($(this).data('lng')) : null,agenda: agenda});});if (picks.length === 0) return;if (missing > 0) {if (!confirm(missing + ' selected row(s) have no agenda. Submit anyway with default "Visit"?')) return;}var btn = $(this);btn.prop('disabled', true).text('Assigning...');$('#avMsg').text('');$.ajax({url: context + '/beatPlan/assignVisit/submit',type: 'POST',contentType: 'application/json',data: JSON.stringify({authUserId: avContext.authUserId,planDate: avContext.date,parties: picks}),success: function (r) {var d = r.response || r;$('#avMsg').html('<span style="color:#2e7d32;">' + (d.message || 'Assigned successfully') + '</span>');btn.prop('disabled', false).text('Assign Selected');setTimeout(function () {$('#assignVisitModal').modal('hide');}, 1200);},error: function (xhr) {var msg = 'Failed';try {msg = (JSON.parse(xhr.responseText).response.message) || msg;} catch (e) {}$('#avMsg').html('<span style="color:#c62828;">' + msg + '</span>');btn.prop('disabled', false).text('Assign Selected');}});});</script><!-- Assign Visit Modal --><div class="modal fade" id="assignVisitModal" tabindex="-1" style="overflow-y:auto;"><div class="modal-dialog" style="width:90%; max-width:900px; margin-top:30px;"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal">×</button><h4 class="modal-title" id="avTitle">Assign Visit</h4></div><div class="modal-body"><div style="display:flex; gap:10px; align-items:center; margin-bottom:10px;"><input type="text" id="avDefaultAgenda" class="form-control input-sm"placeholder="Agenda (applies to selected rows)" style="flex:1;"><button type="button" class="btn btn-default btn-sm" id="avApplyAgenda"title="Fill agenda into all selected rows">Apply to Selected</button></div><div style="display:flex; gap:10px; align-items:center; margin-bottom:10px;"><input type="text" id="avSearch" class="form-control input-sm"placeholder="Search by code, outlet name or city..." style="flex:1;"><span id="avSelectedCount" style="font-size:12px; color:#666;">0 selected</span></div><div id="avPartyList"style="max-height:50vh; overflow-y:auto; border:1px solid #e5e5e5; border-radius:4px;"></div><div id="avMsg" style="margin-top:10px; font-size:12px;"></div></div><div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button><button type="button" class="btn btn-success" id="avSubmit" disabled>Assign Selected</button></div></div></div></div><!-- Calendar modal (iframe loads /beatPlanWindow with auto-selected user) --><div class="modal fade" id="userCalendarModal" tabindex="-1" style="overflow-y:auto;"><div class="modal-dialog" style="width:95%; max-width:1300px; margin-top:20px;"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal"onclick="$('#userCalendarFrame').attr('src','');">×</button><h4 class="modal-title" id="userCalendarTitle">Calendar</h4></div><div class="modal-body" style="padding:0;"><iframe id="userCalendarFrame" style="width:100%; height:80vh; border:none;"></iframe></div></div></div></div>