| Line 77... |
Line 77... |
| 77 |
state.baseLocations = [];
|
77 |
state.baseLocations = [];
|
| 78 |
state.viewDate = null;
|
78 |
state.viewDate = null;
|
| 79 |
state.originalDayCount = 0;
|
79 |
state.originalDayCount = 0;
|
| 80 |
state.visitRequests = [];
|
80 |
state.visitRequests = [];
|
| 81 |
state.assignVisitRequest = null;
|
81 |
state.assignVisitRequest = null;
|
| - |
|
82 |
state.assignedLeads = [];
|
| - |
|
83 |
state.assignLead = null;
|
| 82 |
state.deferredItems = [];
|
84 |
state.deferredItems = [];
|
| 83 |
$('#visit-request-panel').hide();
|
85 |
$('#visit-request-panel').hide();
|
| - |
|
86 |
$('#assigned-leads-panel').hide();
|
| - |
|
87 |
// A fresh load starts both collapsibles expanded.
|
| - |
|
88 |
$('#vr-body, #al-body').show();
|
| - |
|
89 |
$('#vr-caret, #al-caret').html('▾');
|
| 84 |
// #deferred-items-panel now sits inside its own tab pane (#panel-deferred)
|
90 |
// #deferred-items-panel now sits inside its own tab pane (#panel-deferred)
|
| 85 |
// which controls visibility — no need to hide the inner panel here.
|
91 |
// which controls visibility — no need to hide the inner panel here.
|
| 86 |
$('#di-list').empty();
|
92 |
$('#di-list').empty();
|
| 87 |
$('#di-count').text('');
|
93 |
$('#di-count').text('');
|
| 88 |
$('#di-tab-count').text('');
|
94 |
$('#di-tab-count').text('');
|
| Line 146... |
Line 152... |
| 146 |
}
|
152 |
}
|
| 147 |
// Pending visit requests for this user's hierarchy. Hidden when empty.
|
153 |
// Pending visit requests for this user's hierarchy. Hidden when empty.
|
| 148 |
if (typeof loadVisitRequests === 'function') {
|
154 |
if (typeof loadVisitRequests === 'function') {
|
| 149 |
loadVisitRequests();
|
155 |
loadVisitRequests();
|
| 150 |
}
|
156 |
}
|
| - |
|
157 |
// This user's active assigned leads (routable onto a beat). Hidden when empty.
|
| - |
|
158 |
if (typeof loadAssignedLeads === 'function') {
|
| - |
|
159 |
loadAssignedLeads();
|
| - |
|
160 |
}
|
| 151 |
// Deferred items (partners + leads) for this user. Hidden when empty.
|
161 |
// Deferred items (partners + leads) for this user. Hidden when empty.
|
| 152 |
if (typeof loadDeferredItems === 'function') {
|
162 |
if (typeof loadDeferredItems === 'function') {
|
| 153 |
loadDeferredItems();
|
163 |
loadDeferredItems();
|
| 154 |
}
|
164 |
}
|
| 155 |
}
|
165 |
}
|
| Line 844... |
Line 854... |
| 844 |
clearRoute();
|
854 |
clearRoute();
|
| 845 |
}
|
855 |
}
|
| 846 |
});
|
856 |
});
|
| 847 |
|
857 |
|
| 848 |
// New Beat button
|
858 |
// New Beat button
|
| 849 |
$(document).on('click', '#btn-new-beat', function () {
|
859 |
// Start the full map-first new-beat planner (shared by the big "+ New Beat"
|
| - |
|
860 |
// button and the "+ New beat" buttons on the visit-request / assigned-lead cards).
|
| - |
|
861 |
function startNewBeat() {
|
| 850 |
var beatTitle = prompt('Enter beat name:', '');
|
862 |
var beatTitle = prompt('Enter beat name:', '');
|
| 851 |
if (!beatTitle || !beatTitle.trim()) return;
|
863 |
if (!beatTitle || !beatTitle.trim()) return;
|
| 852 |
|
864 |
|
| 853 |
state.beatTitle = beatTitle.trim();
|
865 |
state.beatTitle = beatTitle.trim();
|
| 854 |
state.mode = 'create';
|
866 |
state.mode = 'create';
|
| 855 |
state.savedPlanGroupId = null; // reset — this is a fresh beat
|
867 |
state.savedPlanGroupId = null; // reset — this is a fresh beat
|
| 856 |
state.days = []; // clear any leftover route data
|
868 |
state.days = []; // clear any leftover route data
|
| 857 |
isSubmittingBeat = false;
|
869 |
isSubmittingBeat = false;
|
| 858 |
|
870 |
|
| - |
|
871 |
// Cards live on the Route tab already, but ensure we're there when invoked.
|
| - |
|
872 |
$('.panel-tab[data-tab="route"]').click();
|
| - |
|
873 |
|
| 859 |
if (!state.homeLat) {
|
874 |
if (!state.homeLat) {
|
| 860 |
showHomeModal();
|
875 |
showHomeModal();
|
| 861 |
} else {
|
876 |
} else {
|
| 862 |
loadPartners();
|
877 |
loadPartners();
|
| 863 |
}
|
878 |
}
|
| 864 |
});
|
879 |
}
|
| - |
|
880 |
|
| - |
|
881 |
$(document).on('click', '#btn-new-beat', startNewBeat);
|
| 865 |
|
882 |
|
| 866 |
// ============ HOME LOCATION MODAL ============
|
883 |
// ============ HOME LOCATION MODAL ============
|
| 867 |
var homeMap, homeMapMarker;
|
884 |
var homeMap, homeMapMarker;
|
| 868 |
|
885 |
|
| 869 |
function showHomeModal() {
|
886 |
function showHomeModal() {
|
| Line 1933... |
Line 1950... |
| 1933 |
if (state.assignVisitRequest) {
|
1950 |
if (state.assignVisitRequest) {
|
| 1934 |
approveVisitRequestOnBeat(String(pg), String(date));
|
1951 |
approveVisitRequestOnBeat(String(pg), String(date));
|
| 1935 |
return;
|
1952 |
return;
|
| 1936 |
}
|
1953 |
}
|
| 1937 |
|
1954 |
|
| - |
|
1955 |
// Assigned-lead scheduling mode: drop the picked lead onto this beat+date.
|
| - |
|
1956 |
if (state.assignLead) {
|
| - |
|
1957 |
approveLeadOnBeat(String(pg), String(date));
|
| - |
|
1958 |
return;
|
| - |
|
1959 |
}
|
| - |
|
1960 |
|
| 1938 |
// Switch to Route tab and load that day's run
|
1961 |
// Switch to Route tab and load that day's run
|
| 1939 |
$('.panel-tab[data-tab="route"]').click();
|
1962 |
$('.panel-tab[data-tab="route"]').click();
|
| 1940 |
viewBeat(String(pg), String(date));
|
1963 |
viewBeat(String(pg), String(date));
|
| 1941 |
});
|
1964 |
});
|
| 1942 |
|
1965 |
|
| Line 2805... |
Line 2828... |
| 2805 |
return;
|
2828 |
return;
|
| 2806 |
}
|
2829 |
}
|
| 2807 |
$('#vr-count').text('(' + rows.length + ')');
|
2830 |
$('#vr-count').text('(' + rows.length + ')');
|
| 2808 |
var html = '';
|
2831 |
var html = '';
|
| 2809 |
rows.forEach(function (r) {
|
2832 |
rows.forEach(function (r) {
|
| 2810 |
var leadLine = '<strong style="color:#e2e8f0;">' + (r.leadName || ('Lead #' + r.leadId)) + '</strong>';
|
- |
|
| 2811 |
if (r.leadOutlet) leadLine += ' · <span style="color:#cbd5e1;">' + r.leadOutlet + '</span>';
|
- |
|
| 2812 |
if (r.leadMobile) leadLine += ' · <span style="color:#94a3b8;">' + r.leadMobile + '</span>';
|
- |
|
| 2813 |
var locParts = [];
|
- |
|
| 2814 |
if (r.leadCity) locParts.push(r.leadCity);
|
- |
|
| 2815 |
if (r.leadState) locParts.push(r.leadState);
|
- |
|
| 2816 |
var locLine = locParts.length
|
- |
|
| 2817 |
? '<div style="color:#94a3b8;font-size:11px;">Location: <span style="color:#cbd5e1;">' + locParts.join(', ') + '</span></div>'
|
- |
|
| 2818 |
: '';
|
- |
|
| 2819 |
var nearLine = '';
|
- |
|
| 2820 |
if (r.nearestStoreCode) nearLine = '<div style="color:#94a3b8;font-size:11px;">Near: ' + r.nearestStoreCode
|
- |
|
| 2821 |
+ (r.nearestStoreOutlet ? ' — ' + r.nearestStoreOutlet : '') + '</div>';
|
- |
|
| 2822 |
// Show "Name (#authUserId)" so the head can quickly match identities —
|
- |
|
| 2823 |
// especially useful when they need the id for the Reassign prompt.
|
- |
|
| 2824 |
var reqByLabel = (r.requestedByName || '-') + (r.requestedByAuthId ? ' (#' + r.requestedByAuthId + ')' : '');
|
- |
|
| 2825 |
var assigneeLabel = (r.assigneeName || '-') + (r.assigneeAuthId ? ' (#' + r.assigneeAuthId + ')' : '');
|
- |
|
| 2826 |
var reqMeta = '<div style="color:#94a3b8;font-size:11px;">'
|
- |
|
| 2827 |
+ 'Assignee: <span style="color:#cbd5e1;">' + assigneeLabel + '</span>'
|
- |
|
| 2828 |
+ ' · Requested by ' + reqByLabel
|
- |
|
| 2829 |
+ (r.requestedDate ? ' · Preferred: <strong style="color:#fbbf24;">' + r.requestedDate + '</strong>' : '')
|
- |
|
| 2830 |
+ (r.communicationType ? ' · ' + r.communicationType : '')
|
- |
|
| 2831 |
+ '</div>';
|
- |
|
| 2832 |
var safeName = String(r.leadName || '').replace(/"/g, '"');
|
2833 |
var safeName = String(r.leadName || '').replace(/"/g, '"');
|
| - |
|
2834 |
// Line 1: outlet · mobile · city, state
|
| - |
|
2835 |
var top = [];
|
| - |
|
2836 |
if (r.leadOutlet) top.push(r.leadOutlet);
|
| - |
|
2837 |
if (r.leadMobile) top.push(r.leadMobile);
|
| - |
|
2838 |
var loc = [r.leadCity, r.leadState].filter(Boolean).join(', ');
|
| - |
|
2839 |
if (loc) top.push(loc);
|
| 2833 |
html += '<div data-rid="' + r.id + '" style="padding:8px;border:1px solid #334155;border-radius:6px;background:#0f172a;margin-bottom:6px;">'
|
2840 |
var topLine = top.length ? '<div class="beat-meta">' + top.join(' · ') + '</div>' : '';
|
| - |
|
2841 |
// Line 2: nearest store (only when known)
|
| - |
|
2842 |
var nearLine = r.nearestStoreCode
|
| - |
|
2843 |
? '<div class="beat-meta">Near ' + r.nearestStoreCode
|
| - |
|
2844 |
+ (r.nearestStoreOutlet ? ' — ' + r.nearestStoreOutlet : '') + '</div>'
|
| 2834 |
+ leadLine
|
2845 |
: '';
|
| - |
|
2846 |
// Line 3: by requester · Pref date. Assignee + communication type are intentionally
|
| - |
|
2847 |
// not shown here (assignee id still travels on the button data for the Reassign prompt).
|
| - |
|
2848 |
var reqBits = [];
|
| - |
|
2849 |
if (r.requestedByName) reqBits.push('by ' + r.requestedByName + (r.requestedByAuthId ? ' (#' + r.requestedByAuthId + ')' : ''));
|
| - |
|
2850 |
if (r.requestedDate) reqBits.push('Pref <span style="color:#fbbf24;">' + r.requestedDate + '</span>');
|
| - |
|
2851 |
var reqLine = reqBits.length ? '<div class="beat-meta">' + reqBits.join(' · ') + '</div>' : '';
|
| - |
|
2852 |
html += '<div class="beat-card" data-rid="' + r.id + '" style="cursor:default;">'
|
| - |
|
2853 |
+ '<div class="beat-name"><span style="display:inline-block;width:12px;height:12px;border-radius:2px;flex:none;background:#fbbf24;"></span> ' + (r.leadName || ('Lead #' + r.leadId)) + '</div>'
|
| 2835 |
+ locLine
|
2854 |
+ topLine
|
| 2836 |
+ nearLine
|
2855 |
+ nearLine
|
| 2837 |
+ reqMeta
|
2856 |
+ reqLine
|
| 2838 |
+ '<div style="margin-top:6px;display:flex;gap:6px;flex-wrap:wrap;">'
|
2857 |
+ '<div class="beat-actions" style="flex-wrap:wrap;">'
|
| 2839 |
+ '<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>'
|
2858 |
+ '<button class="vr-schedule btn-sm" data-rid="' + r.id + '" data-lead="' + r.leadId + '" data-leadname="' + safeName + '" data-assignee="' + r.assigneeAuthId + '" style="background:#22c55e;color:#fff;">Schedule</button>'
|
| 2840 |
+ '<button class="vr-new-beat" data-rid="' + r.id + '" data-leadname="' + safeName + '" data-preferred="' + (r.requestedDate || '') + '" style="font-size:11px;padding:3px 8px;border:none;border-radius:4px;background:#3b82f6;color:#fff;cursor:pointer;" title="Use this when the assignee has no beats yet">+ New beat</button>'
|
2859 |
+ '<button class="vr-reassign btn-sm" data-rid="' + r.id + '" style="background:none;border:1px solid #475569;color:#cbd5e1;">Reassign</button>'
|
| 2841 |
+ '<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>'
|
- |
|
| 2842 |
+ '<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>'
|
2860 |
+ '<button class="vr-reject btn-sm" data-rid="' + r.id + '" style="background:#ef4444;color:#fff;">Reject</button>'
|
| 2843 |
+ '</div>'
|
2861 |
+ '</div>'
|
| 2844 |
+ '</div>';
|
2862 |
+ '</div>';
|
| 2845 |
});
|
2863 |
});
|
| 2846 |
$('#vr-list').html(html);
|
2864 |
$('#vr-list').html(html);
|
| 2847 |
$('#visit-request-panel').show();
|
2865 |
$('#visit-request-panel').show();
|
| Line 2849... |
Line 2867... |
| 2849 |
|
2867 |
|
| 2850 |
$(document).on('click', '#vr-refresh', function () {
|
2868 |
$(document).on('click', '#vr-refresh', function () {
|
| 2851 |
loadVisitRequests();
|
2869 |
loadVisitRequests();
|
| 2852 |
});
|
2870 |
});
|
| 2853 |
|
2871 |
|
| - |
|
2872 |
// Collapse / expand the visit-requests list (accordion header).
|
| - |
|
2873 |
$(document).on('click', '#vr-toggle', function () {
|
| - |
|
2874 |
var open = $('#vr-body').is(':visible');
|
| - |
|
2875 |
$('#vr-body').slideToggle(120);
|
| - |
|
2876 |
$('#vr-caret').html(open ? '▸' : '▾');
|
| - |
|
2877 |
});
|
| - |
|
2878 |
|
| 2854 |
// Enter "schedule a visit request" mode — same handoff to the calendar chip
|
2879 |
// Enter "schedule a visit request" mode — same handoff to the calendar chip
|
| 2855 |
// click that the deferred-assign flow uses.
|
2880 |
// click that the deferred-assign flow uses.
|
| 2856 |
$(document).on('click', '.vr-schedule', function () {
|
2881 |
$(document).on('click', '.vr-schedule', function () {
|
| 2857 |
var $b = $(this);
|
2882 |
var $b = $(this);
|
| 2858 |
state.assignVisitRequest = {
|
2883 |
state.assignVisitRequest = {
|
| Line 2865... |
Line 2890... |
| 2865 |
+ (state.assignVisitRequest.leadName || ('Lead #' + state.assignVisitRequest.leadId))
|
2890 |
+ (state.assignVisitRequest.leadName || ('Lead #' + state.assignVisitRequest.leadId))
|
| 2866 |
+ '" onto it.');
|
2891 |
+ '" onto it.');
|
| 2867 |
$('.panel-tab[data-tab="calendar"]').click();
|
2892 |
$('.panel-tab[data-tab="calendar"]').click();
|
| 2868 |
});
|
2893 |
});
|
| 2869 |
|
2894 |
|
| 2870 |
// "+ New beat" — create a single-day beat with just this lead as the Day-1 stop.
|
2895 |
// "+ New beat" — open the full map-first planner, same as the big "+ New Beat"
|
| 2871 |
// For the case the screen card was added to handle: assignee has zero beats,
|
- |
|
| 2872 |
// so "Schedule on a beat" has nothing to land on.
|
2896 |
// button (no date prompt / no auto 1-day beat).
|
| 2873 |
$(document).on('click', '.vr-new-beat', function () {
|
2897 |
$(document).on('click', '.vr-new-beat', function () {
|
| 2874 |
var $b = $(this);
|
- |
|
| 2875 |
var rid = $b.data('rid');
|
- |
|
| 2876 |
var leadName = String($b.data('leadname') || 'this lead');
|
- |
|
| 2877 |
var preferred = String($b.data('preferred') || '');
|
- |
|
| 2878 |
// Prompt for date — preferred wins as default, otherwise tomorrow.
|
- |
|
| 2879 |
var tmrw = new Date();
|
2898 |
startNewBeat();
|
| 2880 |
tmrw.setDate(tmrw.getDate() + 1);
|
- |
|
| 2881 |
var def = preferred || tmrw.toISOString().slice(0, 10);
|
- |
|
| 2882 |
var date = prompt('Create a new 1-day beat for "' + leadName + '". Date (YYYY-MM-DD):', def);
|
- |
|
| 2883 |
if (!date) return;
|
- |
|
| 2884 |
date = date.trim();
|
- |
|
| 2885 |
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
- |
|
| 2886 |
alert('Date must be YYYY-MM-DD');
|
- |
|
| 2887 |
return;
|
- |
|
| 2888 |
}
|
- |
|
| 2889 |
if (!confirm('Create new beat for "' + leadName + '" on ' + date + '?')) return;
|
- |
|
| 2890 |
$.ajax({
|
- |
|
| 2891 |
url: context + '/visitRequest/' + rid + '/create-beat-and-schedule',
|
- |
|
| 2892 |
type: 'POST', contentType: 'application/json',
|
- |
|
| 2893 |
data: JSON.stringify({scheduleDate: date}),
|
- |
|
| 2894 |
success: function (resp) {
|
- |
|
| 2895 |
var r = resp && resp.response ? resp.response : resp;
|
- |
|
| 2896 |
alert('Beat "' + (r.beatName || 'New beat') + '" created on ' + (r.scheduleDate || date) + '.');
|
- |
|
| 2897 |
loadVisitRequests();
|
- |
|
| 2898 |
// Refresh the beat list so the new beat shows up in the left rail.
|
- |
|
| 2899 |
$('#bp-load').click();
|
- |
|
| 2900 |
},
|
- |
|
| 2901 |
error: function (xhr) {
|
- |
|
| 2902 |
var msg = 'Could not create the beat';
|
- |
|
| 2903 |
try {
|
- |
|
| 2904 |
var e = JSON.parse(xhr.responseText);
|
- |
|
| 2905 |
if (e && e.response) msg = (typeof e.response === 'string') ? e.response : (e.response.message || msg);
|
- |
|
| 2906 |
} catch (_) {
|
- |
|
| 2907 |
}
|
- |
|
| 2908 |
alert(msg);
|
- |
|
| 2909 |
}
|
- |
|
| 2910 |
});
|
- |
|
| 2911 |
});
|
2899 |
});
|
| 2912 |
|
2900 |
|
| 2913 |
$(document).on('click', '.vr-reassign', function () {
|
2901 |
$(document).on('click', '.vr-reassign', function () {
|
| 2914 |
var rid = $(this).data('rid');
|
2902 |
var rid = $(this).data('rid');
|
| 2915 |
var newId = prompt('New assignee auth user id (must be in your downline):');
|
2903 |
var newId = prompt('New assignee auth user id (must be in your downline):');
|
| Line 2958... |
Line 2946... |
| 2958 |
alert(msg);
|
2946 |
alert(msg);
|
| 2959 |
}
|
2947 |
}
|
| 2960 |
});
|
2948 |
});
|
| 2961 |
});
|
2949 |
});
|
| 2962 |
|
2950 |
|
| - |
|
2951 |
// ============ ASSIGNED LEADS PANEL ============
|
| - |
|
2952 |
// Lives on the route tab (sidebar) below Pending Visit Requests. Lists the
|
| - |
|
2953 |
// loaded user's active/open leads (pending + follow-up, yellow/green). Each row
|
| - |
|
2954 |
// offers "Schedule on a beat" (reuses the calendar-chip handoff, like visit
|
| - |
|
2955 |
// requests) and "New beat" (a 1-day beat with just this lead). Hidden when empty.
|
| - |
|
2956 |
function loadAssignedLeads() {
|
| - |
|
2957 |
if (!state.authUserId) return;
|
| - |
|
2958 |
$.ajax({
|
| - |
|
2959 |
url: context + '/beatPlan/assignedLeads',
|
| - |
|
2960 |
type: 'GET', dataType: 'json',
|
| - |
|
2961 |
cache: false,
|
| - |
|
2962 |
data: {authUserId: state.authUserId},
|
| - |
|
2963 |
success: function (r) {
|
| - |
|
2964 |
var data = r.response || r;
|
| - |
|
2965 |
var rows = (data && data.rows) || [];
|
| - |
|
2966 |
state.assignedLeads = rows;
|
| - |
|
2967 |
renderAssignedLeads(rows);
|
| - |
|
2968 |
},
|
| - |
|
2969 |
error: function () {
|
| - |
|
2970 |
$('#assigned-leads-panel').hide();
|
| - |
|
2971 |
}
|
| - |
|
2972 |
});
|
| - |
|
2973 |
}
|
| - |
|
2974 |
|
| - |
|
2975 |
function renderAssignedLeads(rows) {
|
| - |
|
2976 |
if (!rows || rows.length === 0) {
|
| - |
|
2977 |
$('#assigned-leads-panel').hide();
|
| - |
|
2978 |
return;
|
| - |
|
2979 |
}
|
| - |
|
2980 |
$('#al-count').text('(' + rows.length + ')');
|
| - |
|
2981 |
$('#al-search').val('');
|
| - |
|
2982 |
$('#al-list').html(alRowsHtml(rows));
|
| - |
|
2983 |
$('#assigned-leads-panel').show();
|
| - |
|
2984 |
}
|
| - |
|
2985 |
|
| - |
|
2986 |
// Lead rows reuse the beat-card look (.beat-card/.beat-name/.beat-meta/.beat-actions)
|
| - |
|
2987 |
// so leads and routes read as one system. The colour dot mirrors the beat colour
|
| - |
|
2988 |
// swatch — here it's the lead's yellow/green priority colour.
|
| - |
|
2989 |
function alRowsHtml(rows) {
|
| - |
|
2990 |
if (!rows || rows.length === 0) {
|
| - |
|
2991 |
return '<div style="padding:12px;color:#64748b;font-size:11px;text-align:center;">No matching leads.</div>';
|
| - |
|
2992 |
}
|
| - |
|
2993 |
var html = '';
|
| - |
|
2994 |
rows.forEach(function (r) {
|
| - |
|
2995 |
var safeName = String(r.leadName || '').replace(/"/g, '"');
|
| - |
|
2996 |
var dot = r.color === 'green' ? '#22c55e' : (r.color === 'yellow' ? '#eab308' : '#64748b');
|
| - |
|
2997 |
var metaParts = [];
|
| - |
|
2998 |
if (r.outletName) metaParts.push(r.outletName);
|
| - |
|
2999 |
if (r.mobile) metaParts.push(r.mobile);
|
| - |
|
3000 |
var loc = [r.city, r.state].filter(Boolean).join(', ');
|
| - |
|
3001 |
if (loc) metaParts.push(loc);
|
| - |
|
3002 |
if (r.stage) metaParts.push(r.stage);
|
| - |
|
3003 |
var meta = metaParts.join(' · ')
|
| - |
|
3004 |
+ (r.hasGeo ? '' : ' <span style="color:#f59e0b;" title="No approved location — the field rep won\'t get a map pin">· no location</span>');
|
| - |
|
3005 |
html += '<div class="beat-card" data-lid="' + r.leadId + '" style="cursor:default;">'
|
| - |
|
3006 |
+ '<div class="beat-name"><span style="display:inline-block;width:12px;height:12px;border-radius:2px;flex:none;background:' + dot + ';"></span> ' + (r.leadName || ('Lead #' + r.leadId)) + '</div>'
|
| - |
|
3007 |
+ '<div class="beat-meta">' + meta + '</div>'
|
| - |
|
3008 |
+ '<div class="beat-actions">'
|
| - |
|
3009 |
+ '<button class="al-schedule btn-sm" data-lead="' + r.leadId + '" data-leadname="' + safeName + '" style="background:#22c55e;color:#fff;">Schedule on a beat</button>'
|
| - |
|
3010 |
+ '<button class="al-new-beat btn-sm" data-lead="' + r.leadId + '" data-leadname="' + safeName + '" style="background:#3b82f6;color:#fff;" title="Use this when the user has no beat to land on">+ New beat</button>'
|
| - |
|
3011 |
+ '</div>'
|
| - |
|
3012 |
+ '</div>';
|
| - |
|
3013 |
});
|
| - |
|
3014 |
return html;
|
| - |
|
3015 |
}
|
| - |
|
3016 |
|
| - |
|
3017 |
$(document).on('click', '#al-refresh', function () {
|
| - |
|
3018 |
loadAssignedLeads();
|
| - |
|
3019 |
});
|
| - |
|
3020 |
|
| - |
|
3021 |
// Collapse / expand the assigned-leads list (accordion header).
|
| - |
|
3022 |
$(document).on('click', '#al-toggle', function () {
|
| - |
|
3023 |
var open = $('#al-body').is(':visible');
|
| - |
|
3024 |
$('#al-body').slideToggle(120);
|
| - |
|
3025 |
$('#al-caret').html(open ? '▸' : '▾');
|
| - |
|
3026 |
});
|
| - |
|
3027 |
|
| - |
|
3028 |
// Client-side filter over the loaded leads — keeps the panel short without a round-trip.
|
| - |
|
3029 |
$(document).on('input', '#al-search', function () {
|
| - |
|
3030 |
var q = $(this).val().toLowerCase().trim();
|
| - |
|
3031 |
var src = state.assignedLeads || [];
|
| - |
|
3032 |
var list = !q ? src : src.filter(function (p) {
|
| - |
|
3033 |
return (p.leadName || '').toLowerCase().indexOf(q) !== -1
|
| - |
|
3034 |
|| (p.outletName || '').toLowerCase().indexOf(q) !== -1
|
| - |
|
3035 |
|| (p.city || '').toLowerCase().indexOf(q) !== -1
|
| - |
|
3036 |
|| (p.mobile || '').toLowerCase().indexOf(q) !== -1;
|
| - |
|
3037 |
});
|
| - |
|
3038 |
$('#al-list').html(alRowsHtml(list));
|
| - |
|
3039 |
});
|
| - |
|
3040 |
|
| - |
|
3041 |
// Enter "schedule a lead" mode — same calendar-chip handoff as visit requests.
|
| - |
|
3042 |
$(document).on('click', '.al-schedule', function () {
|
| - |
|
3043 |
var $b = $(this);
|
| - |
|
3044 |
state.assignLead = {
|
| - |
|
3045 |
leadId: parseInt($b.data('lead')),
|
| - |
|
3046 |
leadName: String($b.data('leadname') || '')
|
| - |
|
3047 |
};
|
| - |
|
3048 |
alert('Click a future beat-chip on the calendar to schedule "'
|
| - |
|
3049 |
+ (state.assignLead.leadName || ('Lead #' + state.assignLead.leadId))
|
| - |
|
3050 |
+ '" onto it.');
|
| - |
|
3051 |
$('.panel-tab[data-tab="calendar"]').click();
|
| - |
|
3052 |
});
|
| - |
|
3053 |
|
| - |
|
3054 |
// "+ New beat" — open the full map-first planner, same as the big "+ New Beat"
|
| - |
|
3055 |
// button (no date prompt / no auto 1-day beat).
|
| - |
|
3056 |
$(document).on('click', '.al-new-beat', function () {
|
| - |
|
3057 |
startNewBeat();
|
| - |
|
3058 |
});
|
| - |
|
3059 |
|
| - |
|
3060 |
// Drop the picked lead onto the chosen beat+date (strictly-future chip).
|
| - |
|
3061 |
function approveLeadOnBeat(pg, date) {
|
| - |
|
3062 |
var al = state.assignLead;
|
| - |
|
3063 |
if (!al) return;
|
| - |
|
3064 |
var today = new Date();
|
| - |
|
3065 |
var todayStr = today.getFullYear() + '-' + ('0' + (today.getMonth() + 1)).slice(-2) + '-' + ('0' + today.getDate()).slice(-2);
|
| - |
|
3066 |
if (date <= todayStr) {
|
| - |
|
3067 |
alert('Pick a FUTURE date (after today).');
|
| - |
|
3068 |
return;
|
| - |
|
3069 |
}
|
| - |
|
3070 |
if (!confirm('Schedule "' + (al.leadName || ('Lead #' + al.leadId)) + '" onto this beat on ' + date + '?')) return;
|
| - |
|
3071 |
$.ajax({
|
| - |
|
3072 |
url: context + '/beatPlan/lead/scheduleOnBeat',
|
| - |
|
3073 |
type: 'POST', contentType: 'application/json',
|
| - |
|
3074 |
data: JSON.stringify({leadId: al.leadId, beatId: parseInt(pg), date: date}),
|
| - |
|
3075 |
success: function (resp) {
|
| - |
|
3076 |
var r = resp && resp.response ? resp.response : resp;
|
| - |
|
3077 |
state.assignLead = null;
|
| - |
|
3078 |
alert(r.message || 'Scheduled.');
|
| - |
|
3079 |
$('.panel-tab[data-tab="route"]').click();
|
| - |
|
3080 |
loadAssignedLeads();
|
| - |
|
3081 |
},
|
| - |
|
3082 |
error: function (xhr) {
|
| - |
|
3083 |
var msg = 'Schedule failed';
|
| - |
|
3084 |
try {
|
| - |
|
3085 |
var er = JSON.parse(xhr.responseText);
|
| - |
|
3086 |
if (er && er.response) msg = (typeof er.response === 'string') ? er.response : (er.response.message || msg);
|
| - |
|
3087 |
} catch (_) {
|
| - |
|
3088 |
}
|
| - |
|
3089 |
alert(msg);
|
| - |
|
3090 |
}
|
| - |
|
3091 |
});
|
| - |
|
3092 |
}
|
| - |
|
3093 |
|
| 2963 |
// ============ DEFERRED ITEMS PANEL (inside beat-plan-window) ============
|
3094 |
// ============ DEFERRED ITEMS PANEL (inside beat-plan-window) ============
|
| 2964 |
// Reuses the existing /beatPlan/deferred read (hierarchy-scoped) and filters
|
3095 |
// Reuses the existing /beatPlan/deferred read (hierarchy-scoped) and filters
|
| 2965 |
// client-side to the currently-loaded sales user. Schedule → switches to the
|
3096 |
// client-side to the currently-loaded sales user. Schedule → switches to the
|
| 2966 |
// Calendar tab in deferred-assign mode and reuses assignDeferredToBeat() on
|
3097 |
// Calendar tab in deferred-assign mode and reuses assignDeferredToBeat() on
|
| 2967 |
// chip click (existing flow). Cancel posts /beatPlan/deferred/action with a
|
3098 |
// chip click (existing flow). Cancel posts /beatPlan/deferred/action with a
|