| Line 75... |
Line 75... |
| 75 |
state.baseLocations = [];
|
75 |
state.baseLocations = [];
|
| 76 |
state.viewDate = null;
|
76 |
state.viewDate = null;
|
| 77 |
state.originalDayCount = 0;
|
77 |
state.originalDayCount = 0;
|
| 78 |
state.visitRequests = [];
|
78 |
state.visitRequests = [];
|
| 79 |
state.assignVisitRequest = null;
|
79 |
state.assignVisitRequest = null;
|
| - |
|
80 |
state.deferredItems = [];
|
| 80 |
$('#visit-request-panel').hide();
|
81 |
$('#visit-request-panel').hide();
|
| - |
|
82 |
// #deferred-items-panel now sits inside its own tab pane (#panel-deferred)
|
| - |
|
83 |
// which controls visibility — no need to hide the inner panel here.
|
| - |
|
84 |
$('#di-list').empty();
|
| - |
|
85 |
$('#di-count').text('');
|
| - |
|
86 |
$('#di-tab-count').text('');
|
| 81 |
// Clear the rendered calendar grid + route map / panel so nothing from
|
87 |
// Clear the rendered calendar grid + route map / panel so nothing from
|
| 82 |
// the previous user is briefly visible while the new fetch is in flight.
|
88 |
// the previous user is briefly visible while the new fetch is in flight.
|
| 83 |
$('#cal-grid-content').empty();
|
89 |
$('#cal-grid-content').empty();
|
| 84 |
$('#route-list').empty();
|
90 |
$('#route-list').empty();
|
| 85 |
if (typeof clearRoute === 'function') {
|
91 |
if (typeof clearRoute === 'function') {
|
| Line 138... |
Line 144... |
| 138 |
}
|
144 |
}
|
| 139 |
// Pending visit requests for this user's hierarchy. Hidden when empty.
|
145 |
// Pending visit requests for this user's hierarchy. Hidden when empty.
|
| 140 |
if (typeof loadVisitRequests === 'function') {
|
146 |
if (typeof loadVisitRequests === 'function') {
|
| 141 |
loadVisitRequests();
|
147 |
loadVisitRequests();
|
| 142 |
}
|
148 |
}
|
| - |
|
149 |
// Deferred items (partners + leads) for this user. Hidden when empty.
|
| - |
|
150 |
if (typeof loadDeferredItems === 'function') {
|
| - |
|
151 |
loadDeferredItems();
|
| - |
|
152 |
}
|
| 143 |
}
|
153 |
}
|
| 144 |
});
|
154 |
});
|
| 145 |
});
|
155 |
});
|
| 146 |
|
156 |
|
| 147 |
// Called from the picker → swap the start location for the beat being
|
157 |
// Called from the picker → swap the start location for the beat being
|
| Line 1806... |
Line 1816... |
| 1806 |
var tab = $(this).data('tab');
|
1816 |
var tab = $(this).data('tab');
|
| 1807 |
$('.panel-tab').removeClass('active');
|
1817 |
$('.panel-tab').removeClass('active');
|
| 1808 |
$(this).addClass('active');
|
1818 |
$(this).addClass('active');
|
| 1809 |
$('#panel-route').toggle(tab === 'route');
|
1819 |
$('#panel-route').toggle(tab === 'route');
|
| 1810 |
$('#panel-calendar').toggle(tab === 'calendar');
|
1820 |
$('#panel-calendar').toggle(tab === 'calendar');
|
| - |
|
1821 |
$('#panel-deferred').toggle(tab === 'deferred');
|
| 1811 |
$('#cal-grid-container').toggle(tab === 'calendar');
|
1822 |
$('#cal-grid-container').toggle(tab === 'calendar');
|
| 1812 |
$('#bottom-bar').toggle(tab === 'route' && (state.mode === 'create' || state.mode === 'edit') && state.days && state.days.length > 0);
|
1823 |
$('#bottom-bar').toggle(tab === 'route' && (state.mode === 'create' || state.mode === 'edit') && state.days && state.days.length > 0);
|
| 1813 |
|
1824 |
|
| 1814 |
if (tab === 'calendar' && !state.calMonth) {
|
1825 |
if (tab === 'calendar' && !state.calMonth) {
|
| 1815 |
var now = new Date();
|
1826 |
var now = new Date();
|
| 1816 |
state.calMonth = now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2);
|
1827 |
state.calMonth = now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2);
|
| 1817 |
loadCalendarData();
|
1828 |
loadCalendarData();
|
| 1818 |
}
|
1829 |
}
|
| - |
|
1830 |
if (tab === 'deferred' && typeof loadDeferredItems === 'function') {
|
| - |
|
1831 |
loadDeferredItems();
|
| - |
|
1832 |
}
|
| 1819 |
});
|
1833 |
});
|
| 1820 |
|
1834 |
|
| 1821 |
// Click a beat chip on the calendar → view that specific day's route
|
1835 |
// Click a beat chip on the calendar → view that specific day's route
|
| 1822 |
// (partners + only the leads scheduled for that exact date)
|
1836 |
// (partners + only the leads scheduled for that exact date)
|
| 1823 |
$(document).on('click', '.cal-chip-view', function (e) {
|
1837 |
$(document).on('click', '.cal-chip-view', function (e) {
|
| Line 1867... |
Line 1881... |
| 1867 |
$('#deferred-assign-banner').html('<span style="color:#bbf7d0;">' + msg + '</span>');
|
1881 |
$('#deferred-assign-banner').html('<span style="color:#bbf7d0;">' + msg + '</span>');
|
| 1868 |
// Confirm to the head (name included so they don't have to read the banner),
|
1882 |
// Confirm to the head (name included so they don't have to read the banner),
|
| 1869 |
// then auto-close the parent's iframe modal. The parent's `hidden.bs.modal`
|
1883 |
// then auto-close the parent's iframe modal. The parent's `hidden.bs.modal`
|
| 1870 |
// handler reloads the deferred list (rescheduled row drops off).
|
1884 |
// handler reloads the deferred list (rescheduled row drops off).
|
| 1871 |
alert(msg);
|
1885 |
alert(msg);
|
| - |
|
1886 |
// Clear the assign-mode so subsequent chip clicks don't try to
|
| - |
|
1887 |
// re-schedule the same (now-resolved) deferred row and trip the
|
| - |
|
1888 |
// "already scheduled" guard. Refresh the in-page deferred panel and
|
| - |
|
1889 |
// switch back to the route tab.
|
| - |
|
1890 |
state.assignDeferred = null;
|
| - |
|
1891 |
if (typeof loadDeferredItems === 'function') loadDeferredItems();
|
| - |
|
1892 |
$('.panel-tab[data-tab="route"]').click();
|
| 1872 |
if (window.parent && window.parent !== window) {
|
1893 |
if (window.parent && window.parent !== window) {
|
| 1873 |
try {
|
1894 |
try {
|
| 1874 |
window.parent.$('#defCalModal').modal('hide');
|
1895 |
window.parent.$('#defCalModal').modal('hide');
|
| 1875 |
} catch (e) {
|
1896 |
} catch (e) {
|
| 1876 |
}
|
1897 |
}
|
| Line 2799... |
Line 2820... |
| 2799 |
try {
|
2820 |
try {
|
| 2800 |
var e = JSON.parse(xhr.responseText);
|
2821 |
var e = JSON.parse(xhr.responseText);
|
| 2801 |
if (e && e.response) msg = (typeof e.response === 'string') ? e.response : (e.response.message || msg);
|
2822 |
if (e && e.response) msg = (typeof e.response === 'string') ? e.response : (e.response.message || msg);
|
| 2802 |
} catch (_) {
|
2823 |
} catch (_) {
|
| 2803 |
}
|
2824 |
}
|
| - |
|
2825 |
alert(msg);
|
| - |
|
2826 |
}
|
| - |
|
2827 |
});
|
| - |
|
2828 |
});
|
| - |
|
2829 |
|
| - |
|
2830 |
// ============ DEFERRED ITEMS PANEL (inside beat-plan-window) ============
|
| - |
|
2831 |
// Reuses the existing /beatPlan/deferred read (hierarchy-scoped) and filters
|
| - |
|
2832 |
// client-side to the currently-loaded sales user. Schedule → switches to the
|
| - |
|
2833 |
// Calendar tab in deferred-assign mode and reuses assignDeferredToBeat() on
|
| - |
|
2834 |
// chip click (existing flow). Cancel posts /beatPlan/deferred/action with a
|
| - |
|
2835 |
// required reason. The standalone Deferred Partners page is untouched.
|
| - |
|
2836 |
function loadDeferredItems() {
|
| - |
|
2837 |
if (!state.authUserId) return;
|
| - |
|
2838 |
// Wide window so recent misses + any not-yet-acted older ones surface.
|
| - |
|
2839 |
var now = new Date();
|
| - |
|
2840 |
|
| - |
|
2841 |
function fmt(d) {
|
| - |
|
2842 |
return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2);
|
| - |
|
2843 |
}
|
| - |
|
2844 |
|
| - |
|
2845 |
var start = new Date();
|
| - |
|
2846 |
start.setDate(now.getDate() - 30);
|
| - |
|
2847 |
var end = new Date();
|
| - |
|
2848 |
end.setDate(now.getDate() + 30);
|
| - |
|
2849 |
$.ajax({
|
| - |
|
2850 |
url: context + '/beatPlan/deferred',
|
| - |
|
2851 |
type: 'GET', dataType: 'json', cache: false,
|
| - |
|
2852 |
data: {startDate: fmt(start), endDate: fmt(end)},
|
| - |
|
2853 |
success: function (r) {
|
| - |
|
2854 |
var data = r.response || r;
|
| - |
|
2855 |
var all = (data && data.rows) || [];
|
| - |
|
2856 |
var mine = all.filter(function (row) {
|
| - |
|
2857 |
return row.authUserId === state.authUserId;
|
| - |
|
2858 |
});
|
| - |
|
2859 |
state.deferredItems = mine;
|
| - |
|
2860 |
renderDeferredItems(mine);
|
| - |
|
2861 |
},
|
| - |
|
2862 |
error: function () {
|
| - |
|
2863 |
$('#di-list').html('<p style="color:#d9534f;font-size:12px;text-align:center;padding:20px 10px;">Could not load deferred items.</p>');
|
| - |
|
2864 |
}
|
| - |
|
2865 |
});
|
| - |
|
2866 |
}
|
| - |
|
2867 |
|
| - |
|
2868 |
function renderDeferredItems(rows) {
|
| - |
|
2869 |
var n = (rows && rows.length) || 0;
|
| - |
|
2870 |
$('#di-count').text('(' + n + ')');
|
| - |
|
2871 |
// Tab pill counter — keeps the head aware of pending items while on other tabs.
|
| - |
|
2872 |
if (n > 0) {
|
| - |
|
2873 |
$('#di-tab-count').text('(' + n + ')').css('color', '#fca5a5');
|
| - |
|
2874 |
} else {
|
| - |
|
2875 |
$('#di-tab-count').text('').css('color', '');
|
| - |
|
2876 |
}
|
| - |
|
2877 |
if (n === 0) {
|
| - |
|
2878 |
$('#di-list').html('<p style="color:#64748b;font-size:12px;text-align:center;padding:20px 10px;">No deferred items for this user.</p>');
|
| - |
|
2879 |
return;
|
| - |
|
2880 |
}
|
| - |
|
2881 |
var html = '';
|
| - |
|
2882 |
rows.forEach(function (r) {
|
| - |
|
2883 |
var safeName = String(r.name || '').replace(/"/g, '"');
|
| - |
|
2884 |
var typeTag = r.type === 'Lead'
|
| - |
|
2885 |
? '<span style="background:#fbbf24;color:#0f172a;font-size:10px;padding:1px 5px;border-radius:3px;margin-right:6px;">Lead</span>'
|
| - |
|
2886 |
: '<span style="background:#60a5fa;color:#0f172a;font-size:10px;padding:1px 5px;border-radius:3px;margin-right:6px;">Visit</span>';
|
| - |
|
2887 |
var nextLine = r.nextScheduledDate
|
| - |
|
2888 |
? '<div style="color:#22c55e;font-size:11px;">Auto-covered on ' + r.nextScheduledDate + '</div>' : '';
|
| - |
|
2889 |
html += '<div data-did="' + r.id + '" style="padding:8px;border:1px solid #334155;border-radius:6px;background:#0f172a;margin-bottom:6px;">'
|
| - |
|
2890 |
+ typeTag
|
| - |
|
2891 |
+ '<strong style="color:#e2e8f0;">' + (r.name || '#' + r.fofoStoreId) + '</strong>'
|
| - |
|
2892 |
+ '<div style="color:#94a3b8;font-size:11px;margin-top:2px;">'
|
| - |
|
2893 |
+ 'Deferred: <span style="color:#fca5a5;">' + (r.deferredDate || '-') + '</span>'
|
| - |
|
2894 |
+ (r.reason ? ' · ' + r.reason : '')
|
| - |
|
2895 |
+ '</div>'
|
| - |
|
2896 |
+ nextLine
|
| - |
|
2897 |
+ '<div style="margin-top:6px;display:flex;gap:6px;flex-wrap:wrap;">'
|
| - |
|
2898 |
+ '<button class="di-schedule" data-did="' + r.id + '" data-type="' + (r.type === 'Lead' ? 'lead' : 'visit') + '" data-name="' + safeName + '" data-deferdate="' + (r.deferredDate || '') + '" style="font-size:11px;padding:3px 8px;border:none;border-radius:4px;background:#22c55e;color:#fff;cursor:pointer;">Schedule on a beat</button>'
|
| - |
|
2899 |
+ '<button class="di-cancel" data-did="' + r.id + '" data-name="' + safeName + '" style="font-size:11px;padding:3px 8px;border:none;border-radius:4px;background:#ef4444;color:#fff;cursor:pointer;">Cancel</button>'
|
| - |
|
2900 |
+ '</div>'
|
| - |
|
2901 |
+ '</div>';
|
| - |
|
2902 |
});
|
| - |
|
2903 |
$('#di-list').html(html);
|
| - |
|
2904 |
}
|
| - |
|
2905 |
|
| - |
|
2906 |
$(document).on('click', '#di-refresh', function () {
|
| - |
|
2907 |
loadDeferredItems();
|
| - |
|
2908 |
});
|
| - |
|
2909 |
|
| - |
|
2910 |
// Schedule: set state.assignDeferred (existing pattern) and switch to the
|
| - |
|
2911 |
// Calendar tab. The existing .cal-chip-view handler dispatches into
|
| - |
|
2912 |
// assignDeferredToBeat() when state.assignDeferred is set.
|
| - |
|
2913 |
$(document).on('click', '.di-schedule', function () {
|
| - |
|
2914 |
var $b = $(this);
|
| - |
|
2915 |
var deferDate = String($b.data('deferdate') || '');
|
| - |
|
2916 |
var todayObj = new Date();
|
| - |
|
2917 |
var todayStr = todayObj.getFullYear() + '-' + ('0' + (todayObj.getMonth() + 1)).slice(-2) + '-' + ('0' + todayObj.getDate()).slice(-2);
|
| - |
|
2918 |
// Floor: later of the deferred-date OR today — same guard the iframe flow uses.
|
| - |
|
2919 |
var floor = (deferDate && deferDate > todayStr) ? deferDate : todayStr;
|
| - |
|
2920 |
state.assignDeferred = {
|
| - |
|
2921 |
id: parseInt($b.data('did')),
|
| - |
|
2922 |
type: String($b.data('type') || 'visit'),
|
| - |
|
2923 |
name: String($b.data('name') || 'item'),
|
| - |
|
2924 |
deferDate: deferDate,
|
| - |
|
2925 |
minDate: floor
|
| - |
|
2926 |
};
|
| - |
|
2927 |
alert('Click a future beat-chip on the calendar to drop "' + (state.assignDeferred.name) + '" onto it (after ' + floor + ').');
|
| - |
|
2928 |
$('.panel-tab[data-tab="calendar"]').click();
|
| - |
|
2929 |
});
|
| - |
|
2930 |
|
| - |
|
2931 |
$(document).on('click', '.di-cancel', function () {
|
| - |
|
2932 |
var did = $(this).data('did');
|
| - |
|
2933 |
var name = String($(this).data('name') || 'this deferred item');
|
| - |
|
2934 |
var reason = prompt('Cancel reason (required) — why is "' + name + '" being cancelled?');
|
| - |
|
2935 |
if (!reason || !reason.trim()) return;
|
| - |
|
2936 |
$.ajax({
|
| - |
|
2937 |
url: context + '/beatPlan/deferred/action',
|
| - |
|
2938 |
type: 'POST', contentType: 'application/json',
|
| - |
|
2939 |
data: JSON.stringify({deferredId: did, action: 'cancel', reason: reason.trim()}),
|
| - |
|
2940 |
success: function () {
|
| - |
|
2941 |
alert('Cancelled. The row will drop off this panel.');
|
| - |
|
2942 |
loadDeferredItems();
|
| - |
|
2943 |
},
|
| - |
|
2944 |
error: function (xhr) {
|
| - |
|
2945 |
var msg = 'Cancel failed';
|
| - |
|
2946 |
try {
|
| - |
|
2947 |
var e = JSON.parse(xhr.responseText);
|
| - |
|
2948 |
if (e && e.response) msg = (typeof e.response === 'string') ? e.response : (e.response.message || msg);
|
| - |
|
2949 |
} catch (_) {
|
| - |
|
2950 |
}
|
| 2804 |
alert(msg);
|
2951 |
alert(msg);
|
| 2805 |
}
|
2952 |
}
|
| 2806 |
});
|
2953 |
});
|
| 2807 |
});
|
2954 |
});
|
| 2808 |
|
2955 |
|