| Line 71... |
Line 71... |
| 71 |
state.homeName = '';
|
71 |
state.homeName = '';
|
| 72 |
state.activeBaseLocationId = null;
|
72 |
state.activeBaseLocationId = null;
|
| 73 |
state.baseLocations = [];
|
73 |
state.baseLocations = [];
|
| 74 |
state.viewDate = null;
|
74 |
state.viewDate = null;
|
| 75 |
state.originalDayCount = 0;
|
75 |
state.originalDayCount = 0;
|
| - |
|
76 |
state.visitRequests = [];
|
| - |
|
77 |
state.assignVisitRequest = null;
|
| - |
|
78 |
$('#visit-request-panel').hide();
|
| 76 |
// Clear the rendered calendar grid + route map / panel so nothing from
|
79 |
// Clear the rendered calendar grid + route map / panel so nothing from
|
| 77 |
// the previous user is briefly visible while the new fetch is in flight.
|
80 |
// the previous user is briefly visible while the new fetch is in flight.
|
| 78 |
$('#cal-grid-content').empty();
|
81 |
$('#cal-grid-content').empty();
|
| 79 |
$('#route-list').empty();
|
82 |
$('#route-list').empty();
|
| 80 |
if (typeof clearRoute === 'function') {
|
83 |
if (typeof clearRoute === 'function') {
|
| Line 129... |
Line 132... |
| 129 |
if ($('.panel-tab[data-tab="calendar"]').hasClass('active')) {
|
132 |
if ($('.panel-tab[data-tab="calendar"]').hasClass('active')) {
|
| 130 |
var now = new Date();
|
133 |
var now = new Date();
|
| 131 |
state.calMonth = now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2);
|
134 |
state.calMonth = now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2);
|
| 132 |
loadCalendarData();
|
135 |
loadCalendarData();
|
| 133 |
}
|
136 |
}
|
| - |
|
137 |
// Pending visit requests for this user's hierarchy. Hidden when empty.
|
| - |
|
138 |
if (typeof loadVisitRequests === 'function') {
|
| - |
|
139 |
loadVisitRequests();
|
| - |
|
140 |
}
|
| 134 |
}
|
141 |
}
|
| 135 |
});
|
142 |
});
|
| 136 |
});
|
143 |
});
|
| 137 |
|
144 |
|
| 138 |
// Called from the picker → swap the start location for the beat being
|
145 |
// Called from the picker → swap the start location for the beat being
|
| Line 1823... |
Line 1830... |
| 1823 |
if (state.assignDeferred) {
|
1830 |
if (state.assignDeferred) {
|
| 1824 |
assignDeferredToBeat(String(pg), String(date));
|
1831 |
assignDeferredToBeat(String(pg), String(date));
|
| 1825 |
return;
|
1832 |
return;
|
| 1826 |
}
|
1833 |
}
|
| 1827 |
|
1834 |
|
| - |
|
1835 |
// Visit-request scheduling mode: similar handoff but talks to /visitRequest/{id}/approve-schedule.
|
| - |
|
1836 |
if (state.assignVisitRequest) {
|
| - |
|
1837 |
approveVisitRequestOnBeat(String(pg), String(date));
|
| - |
|
1838 |
return;
|
| - |
|
1839 |
}
|
| - |
|
1840 |
|
| 1828 |
// Switch to Route tab and load that day's run
|
1841 |
// Switch to Route tab and load that day's run
|
| 1829 |
$('.panel-tab[data-tab="route"]').click();
|
1842 |
$('.panel-tab[data-tab="route"]').click();
|
| 1830 |
viewBeat(String(pg), String(date));
|
1843 |
viewBeat(String(pg), String(date));
|
| 1831 |
});
|
1844 |
});
|
| 1832 |
|
1845 |
|
| Line 2651... |
Line 2664... |
| 2651 |
error: function (xhr) {
|
2664 |
error: function (xhr) {
|
| 2652 |
console.error('[BEAT-AUTO] getBaseLocation failed:', xhr.status, xhr.responseText);
|
2665 |
console.error('[BEAT-AUTO] getBaseLocation failed:', xhr.status, xhr.responseText);
|
| 2653 |
}
|
2666 |
}
|
| 2654 |
});
|
2667 |
});
|
| 2655 |
});
|
2668 |
});
|
| - |
|
2669 |
|
| - |
|
2670 |
// ============ VISIT REQUEST PANEL ============
|
| - |
|
2671 |
// Lives on the route tab (sidebar) when a sales user is loaded. Fetches
|
| - |
|
2672 |
// PENDING requests addressed at this user's hierarchy and shows three actions
|
| - |
|
2673 |
// per row: Schedule on a beat, Reassign, Reject. Schedule reuses the calendar
|
| - |
|
2674 |
// chip-click flow by setting state.assignVisitRequest = {…} and switching
|
| - |
|
2675 |
// the panel to the Calendar tab — clicking a future chip then fires the POST.
|
| - |
|
2676 |
function loadVisitRequests() {
|
| - |
|
2677 |
if (!state.authUserId) return;
|
| - |
|
2678 |
$.ajax({
|
| - |
|
2679 |
url: context + '/visitRequest/list',
|
| - |
|
2680 |
type: 'GET', dataType: 'json',
|
| - |
|
2681 |
data: {assigneeAuthId: state.authUserId, status: 'PENDING'},
|
| - |
|
2682 |
success: function (r) {
|
| - |
|
2683 |
var data = r.response || r;
|
| - |
|
2684 |
var rows = (data && data.rows) || [];
|
| - |
|
2685 |
state.visitRequests = rows;
|
| - |
|
2686 |
renderVisitRequests(rows);
|
| - |
|
2687 |
},
|
| - |
|
2688 |
error: function () {
|
| - |
|
2689 |
$('#visit-request-panel').hide();
|
| - |
|
2690 |
}
|
| - |
|
2691 |
});
|
| - |
|
2692 |
}
|
| - |
|
2693 |
|
| - |
|
2694 |
function renderVisitRequests(rows) {
|
| - |
|
2695 |
if (!rows || rows.length === 0) {
|
| - |
|
2696 |
$('#visit-request-panel').hide();
|
| - |
|
2697 |
return;
|
| - |
|
2698 |
}
|
| - |
|
2699 |
$('#vr-count').text('(' + rows.length + ')');
|
| - |
|
2700 |
var html = '';
|
| - |
|
2701 |
rows.forEach(function (r) {
|
| - |
|
2702 |
var leadLine = '<strong style="color:#e2e8f0;">' + (r.leadName || ('Lead #' + r.leadId)) + '</strong>';
|
| - |
|
2703 |
if (r.leadOutlet) leadLine += ' · <span style="color:#cbd5e1;">' + r.leadOutlet + '</span>';
|
| - |
|
2704 |
if (r.leadMobile) leadLine += ' · <span style="color:#94a3b8;">' + r.leadMobile + '</span>';
|
| - |
|
2705 |
var nearLine = '';
|
| - |
|
2706 |
if (r.nearestStoreCode) nearLine = '<div style="color:#94a3b8;font-size:11px;">Near: ' + r.nearestStoreCode
|
| - |
|
2707 |
+ (r.nearestStoreOutlet ? ' — ' + r.nearestStoreOutlet : '') + '</div>';
|
| - |
|
2708 |
// Show "Name (#authUserId)" so the head can quickly match identities —
|
| - |
|
2709 |
// especially useful when they need the id for the Reassign prompt.
|
| - |
|
2710 |
var reqByLabel = (r.requestedByName || '-') + (r.requestedByAuthId ? ' (#' + r.requestedByAuthId + ')' : '');
|
| - |
|
2711 |
var assigneeLabel = (r.assigneeName || '-') + (r.assigneeAuthId ? ' (#' + r.assigneeAuthId + ')' : '');
|
| - |
|
2712 |
var reqMeta = '<div style="color:#94a3b8;font-size:11px;">'
|
| - |
|
2713 |
+ 'Assignee: <span style="color:#cbd5e1;">' + assigneeLabel + '</span>'
|
| - |
|
2714 |
+ ' · Requested by ' + reqByLabel
|
| - |
|
2715 |
+ (r.requestedDate ? ' · Preferred: <strong style="color:#fbbf24;">' + r.requestedDate + '</strong>' : '')
|
| - |
|
2716 |
+ (r.communicationType ? ' · ' + r.communicationType : '')
|
| - |
|
2717 |
+ '</div>';
|
| - |
|
2718 |
var safeName = String(r.leadName || '').replace(/"/g, '"');
|
| - |
|
2719 |
html += '<div data-rid="' + r.id + '" style="padding:8px;border:1px solid #334155;border-radius:6px;background:#0f172a;margin-bottom:6px;">'
|
| - |
|
2720 |
+ leadLine
|
| - |
|
2721 |
+ nearLine
|
| - |
|
2722 |
+ reqMeta
|
| - |
|
2723 |
+ '<div style="margin-top:6px;display:flex;gap:6px;flex-wrap:wrap;">'
|
| - |
|
2724 |
+ '<button class="vr-schedule" data-rid="' + r.id + '" data-lead="' + r.leadId + '" data-leadname="' + safeName + '" data-assignee="' + r.assigneeAuthId + '" style="font-size:11px;padding:3px 8px;border:none;border-radius:4px;background:#22c55e;color:#fff;cursor:pointer;">Schedule on a beat</button>'
|
| - |
|
2725 |
+ '<button class="vr-reassign" data-rid="' + r.id + '" style="font-size:11px;padding:3px 8px;border:1px solid #475569;border-radius:4px;background:none;color:#cbd5e1;cursor:pointer;">Reassign</button>'
|
| - |
|
2726 |
+ '<button class="vr-reject" data-rid="' + r.id + '" style="font-size:11px;padding:3px 8px;border:none;border-radius:4px;background:#ef4444;color:#fff;cursor:pointer;">Reject</button>'
|
| - |
|
2727 |
+ '</div>'
|
| - |
|
2728 |
+ '</div>';
|
| - |
|
2729 |
});
|
| - |
|
2730 |
$('#vr-list').html(html);
|
| - |
|
2731 |
$('#visit-request-panel').show();
|
| - |
|
2732 |
}
|
| - |
|
2733 |
|
| - |
|
2734 |
$(document).on('click', '#vr-refresh', function () {
|
| - |
|
2735 |
loadVisitRequests();
|
| - |
|
2736 |
});
|
| - |
|
2737 |
|
| - |
|
2738 |
// Enter "schedule a visit request" mode — same handoff to the calendar chip
|
| - |
|
2739 |
// click that the deferred-assign flow uses.
|
| - |
|
2740 |
$(document).on('click', '.vr-schedule', function () {
|
| - |
|
2741 |
var $b = $(this);
|
| - |
|
2742 |
state.assignVisitRequest = {
|
| - |
|
2743 |
id: parseInt($b.data('rid')),
|
| - |
|
2744 |
leadId: parseInt($b.data('lead')),
|
| - |
|
2745 |
leadName: String($b.data('leadname') || ''),
|
| - |
|
2746 |
assigneeAuthId: parseInt($b.data('assignee'))
|
| - |
|
2747 |
};
|
| - |
|
2748 |
alert('Click a future beat-chip on the calendar to schedule "'
|
| - |
|
2749 |
+ (state.assignVisitRequest.leadName || ('Lead #' + state.assignVisitRequest.leadId))
|
| - |
|
2750 |
+ '" onto it.');
|
| - |
|
2751 |
$('.panel-tab[data-tab="calendar"]').click();
|
| - |
|
2752 |
});
|
| - |
|
2753 |
|
| - |
|
2754 |
$(document).on('click', '.vr-reassign', function () {
|
| - |
|
2755 |
var rid = $(this).data('rid');
|
| - |
|
2756 |
var newId = prompt('New assignee auth user id (must be in your downline):');
|
| - |
|
2757 |
if (!newId) return;
|
| - |
|
2758 |
var newIdInt = parseInt(newId);
|
| - |
|
2759 |
if (!newIdInt) return;
|
| - |
|
2760 |
$.ajax({
|
| - |
|
2761 |
url: context + '/visitRequest/' + rid + '/reassign',
|
| - |
|
2762 |
type: 'POST', contentType: 'application/json',
|
| - |
|
2763 |
data: JSON.stringify({newAssigneeAuthId: newIdInt}),
|
| - |
|
2764 |
success: function () {
|
| - |
|
2765 |
loadVisitRequests();
|
| - |
|
2766 |
},
|
| - |
|
2767 |
error: function (xhr) {
|
| - |
|
2768 |
var msg = 'Reassign failed';
|
| - |
|
2769 |
try {
|
| - |
|
2770 |
var e = JSON.parse(xhr.responseText);
|
| - |
|
2771 |
if (e && e.response) msg = (typeof e.response === 'string') ? e.response : (e.response.message || msg);
|
| - |
|
2772 |
} catch (_) {
|
| - |
|
2773 |
}
|
| - |
|
2774 |
alert(msg);
|
| - |
|
2775 |
}
|
| - |
|
2776 |
});
|
| - |
|
2777 |
});
|
| - |
|
2778 |
|
| - |
|
2779 |
$(document).on('click', '.vr-reject', function () {
|
| - |
|
2780 |
var rid = $(this).data('rid');
|
| - |
|
2781 |
var reason = prompt('Reject reason (required):');
|
| - |
|
2782 |
if (!reason || !reason.trim()) return;
|
| - |
|
2783 |
$.ajax({
|
| - |
|
2784 |
url: context + '/visitRequest/' + rid + '/reject',
|
| - |
|
2785 |
type: 'POST', contentType: 'application/json',
|
| - |
|
2786 |
data: JSON.stringify({rejectReason: reason.trim()}),
|
| - |
|
2787 |
success: function () {
|
| - |
|
2788 |
loadVisitRequests();
|
| - |
|
2789 |
},
|
| - |
|
2790 |
error: function (xhr) {
|
| - |
|
2791 |
var msg = 'Reject failed';
|
| - |
|
2792 |
try {
|
| - |
|
2793 |
var e = JSON.parse(xhr.responseText);
|
| - |
|
2794 |
if (e && e.response) msg = (typeof e.response === 'string') ? e.response : (e.response.message || msg);
|
| - |
|
2795 |
} catch (_) {
|
| - |
|
2796 |
}
|
| - |
|
2797 |
alert(msg);
|
| - |
|
2798 |
}
|
| - |
|
2799 |
});
|
| - |
|
2800 |
});
|
| - |
|
2801 |
|
| - |
|
2802 |
function approveVisitRequestOnBeat(pg, date) {
|
| - |
|
2803 |
var vr = state.assignVisitRequest;
|
| - |
|
2804 |
if (!vr) return;
|
| - |
|
2805 |
var today = new Date();
|
| - |
|
2806 |
var todayStr = today.getFullYear() + '-' + ('0' + (today.getMonth() + 1)).slice(-2) + '-' + ('0' + today.getDate()).slice(-2);
|
| - |
|
2807 |
if (date <= todayStr) {
|
| - |
|
2808 |
alert('Pick a FUTURE date (after today).');
|
| - |
|
2809 |
return;
|
| - |
|
2810 |
}
|
| - |
|
2811 |
if (!confirm('Schedule "' + (vr.leadName || ('Lead #' + vr.leadId)) + '" onto this beat on ' + date + '?')) return;
|
| - |
|
2812 |
$.ajax({
|
| - |
|
2813 |
url: context + '/visitRequest/' + vr.id + '/approve-schedule',
|
| - |
|
2814 |
type: 'POST', contentType: 'application/json',
|
| - |
|
2815 |
data: JSON.stringify({beatId: parseInt(pg), scheduleDate: date}),
|
| - |
|
2816 |
success: function () {
|
| - |
|
2817 |
state.assignVisitRequest = null;
|
| - |
|
2818 |
alert('Scheduled. The request has been resolved.');
|
| - |
|
2819 |
$('.panel-tab[data-tab="route"]').click();
|
| - |
|
2820 |
loadVisitRequests();
|
| - |
|
2821 |
},
|
| - |
|
2822 |
error: function (xhr) {
|
| - |
|
2823 |
var msg = 'Schedule failed';
|
| - |
|
2824 |
try {
|
| - |
|
2825 |
var er = JSON.parse(xhr.responseText);
|
| - |
|
2826 |
if (er && er.response) msg = (typeof er.response === 'string') ? er.response : (er.response.message || msg);
|
| - |
|
2827 |
} catch (_) {
|
| - |
|
2828 |
}
|
| - |
|
2829 |
alert(msg);
|
| - |
|
2830 |
}
|
| - |
|
2831 |
});
|
| - |
|
2832 |
}
|