Subversion Repositories SmartDukaan

Rev

Rev 37060 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 37060 Rev 37120
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, '&quot;');
2833
		var safeName = String(r.leadName || '').replace(/"/g, '&quot;');
-
 
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 ? '&#9656;' : '&#9662;');
-
 
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, '&quot;');
-
 
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 ? '&#9656;' : '&#9662;');
-
 
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