Subversion Repositories SmartDukaan

Rev

Rev 36681 | Rev 36700 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 36681 Rev 36698
Line 83... Line 83...
83
    });
83
    });
84
});
84
});
85
 
85
 
86
// Called from the picker → swap the start location for the beat being
86
// Called from the picker → swap the start location for the beat being
87
// created/edited. Updates state.home* + day 1's start point, then re-draws.
87
// created/edited. Updates state.home* + day 1's start point, then re-draws.
-
 
88
// Inline typeahead inside the route sidebar — same data source as the
-
 
89
// Partners tab (state.partners, fed by /beatPlan/getPartners, which mirrors
-
 
90
// the /getPartnerReadonlyInfo pattern used by the Partner Access tab).
-
 
91
// User types 2+ chars → suggestions appear → click adds to the current day.
-
 
92
function renderPartnerQuickAdd() {
-
 
93
	if (!state.partners || state.partners.length === 0) return '';
-
 
94
	return ''
-
 
95
		+ '<div style="position:relative; margin-bottom:10px;">'
-
 
96
		+ '  <input type="text" id="bp-partner-quick" autocomplete="off"'
-
 
97
		+ '         placeholder="Search & add a partner..."'
-
 
98
		+ '         style="width:100%; padding:8px 10px; border:1px solid #475569; border-radius:6px;'
-
 
99
		+ '                background:#0f172a; color:#e2e8f0; font-size:12px; font-family:inherit;">'
-
 
100
		+ '  <div id="bp-partner-suggest"'
-
 
101
		+ '       style="display:none; position:absolute; left:0; right:0; top:100%; z-index:50;'
-
 
102
		+ '              background:#1e293b; border:1px solid #334155; border-radius:6px;'
-
 
103
		+ '              margin-top:4px; max-height:260px; overflow-y:auto;'
-
 
104
		+ '              box-shadow:0 6px 16px rgba(0,0,0,0.4);"></div>'
-
 
105
		+ '</div>';
-
 
106
}
-
 
107
 
-
 
108
function refreshPartnerSuggestions() {
-
 
109
	var $input = $('#bp-partner-quick');
-
 
110
	if (!$input.length) return;
-
 
111
	var q = ($input.val() || '').trim().toLowerCase();
-
 
112
	var $sug = $('#bp-partner-suggest');
-
 
113
	if (q.length < 2) {
-
 
114
		$sug.hide().empty();
-
 
115
		return;
-
 
116
	}
-
 
117
 
-
 
118
	// Lookup of fofoId → day number for partners already on the route
-
 
119
	var inRoute = {};
-
 
120
	(state.days || []).forEach(function (d) {
-
 
121
		(d.visits || []).forEach(function (v) {
-
 
122
			if (!v.isLead && v.type !== 'lead') inRoute[v.id] = d.dayNumber;
-
 
123
		});
-
 
124
	});
-
 
125
 
-
 
126
	var matches = (state.partners || []).filter(function (p) {
-
 
127
		var hay = ((p.code || '') + ' '
-
 
128
			+ (p.outletName || '') + ' '
-
 
129
			+ (p.businessName || '') + ' '
-
 
130
			+ (p.city || '')).toLowerCase();
-
 
131
		return hay.indexOf(q) !== -1;
-
 
132
	}).slice(0, 10);
-
 
133
 
-
 
134
	if (matches.length === 0) {
-
 
135
		$sug.html('<div style="padding:10px; color:#94a3b8; font-size:11px; text-align:center;">No partners found</div>').show();
-
 
136
		return;
-
 
137
	}
-
 
138
 
-
 
139
	var html = '';
-
 
140
	matches.forEach(function (p) {
-
 
141
		var day = inRoute[p.fofoId];
-
 
142
		var nameLine = (p.outletName || p.businessName || '');
-
 
143
		if (p.city) nameLine += (nameLine ? ' - ' : '') + p.city;
-
 
144
		if (day) {
-
 
145
			html += '<div class="bp-partner-row in-route" style="padding:8px 10px; font-size:11px;'
-
 
146
				+ ' color:#94a3b8; border-bottom:1px solid #0f172a; cursor:not-allowed;">'
-
 
147
				+ '<div style="font-weight:600;">' + (p.code || '') + '</div>'
-
 
148
				+ '<div>' + nameLine + ' <span style="color:#22c55e;">(in D' + day + ')</span></div>'
-
 
149
				+ '</div>';
-
 
150
		} else {
-
 
151
			html += '<div class="bp-partner-row" data-fofoid="' + p.fofoId + '"'
-
 
152
				+ ' style="padding:8px 10px; font-size:12px; color:#e2e8f0;'
-
 
153
				+ ' border-bottom:1px solid #0f172a; cursor:pointer;">'
-
 
154
				+ '<div style="font-weight:600;">' + (p.code || '') + '</div>'
-
 
155
				+ '<div style="font-size:11px; color:#94a3b8;">' + nameLine + '</div>'
-
 
156
				+ '</div>';
-
 
157
		}
-
 
158
	});
-
 
159
	$sug.html(html).show();
-
 
160
}
-
 
161
 
-
 
162
$(document).on('input', '#bp-partner-quick', refreshPartnerSuggestions);
-
 
163
 
-
 
164
$(document).on('focus', '#bp-partner-quick', function () {
-
 
165
	if (($(this).val() || '').trim().length >= 2) refreshPartnerSuggestions();
-
 
166
});
-
 
167
 
-
 
168
// Hide on outside click
-
 
169
$(document).on('click', function (e) {
-
 
170
	if (!$(e.target).closest('#bp-partner-quick, #bp-partner-suggest').length) {
-
 
171
		$('#bp-partner-suggest').hide();
-
 
172
	}
-
 
173
});
-
 
174
 
-
 
175
$(document).on('mousedown', '#bp-partner-suggest .bp-partner-row', function (e) {
-
 
176
	if ($(this).hasClass('in-route')) return;
-
 
177
	e.preventDefault(); // keep input from losing focus before we read fofoid
-
 
178
	var fofoId = parseInt($(this).data('fofoid'));
-
 
179
	if (!fofoId) return;
-
 
180
	if (typeof addVisit === 'function') addVisit(fofoId, 'next');
-
 
181
	$('#bp-partner-quick').val('').focus();
-
 
182
	$('#bp-partner-suggest').hide().empty();
-
 
183
});
-
 
184
 
88
function renderBaseLocationPicker() {
185
function renderBaseLocationPicker() {
89
	var locs = state.baseLocations || [];
186
	var locs = state.baseLocations || [];
90
	if (locs.length === 0) return '';
187
	if (locs.length === 0) return '';
91
	var activeId = state.activeBaseLocationId;
188
	var activeId = state.activeBaseLocationId;
92
	if (locs.length === 1) {
189
	if (locs.length === 1) {
Line 404... Line 501...
404
									+ 'style="font-size:11px;padding:4px 10px;border:1px solid #475569;'
501
									+ 'style="font-size:11px;padding:4px 10px;border:1px solid #475569;'
405
									+ 'border-radius:4px;background:none;color:#94a3b8;cursor:pointer;'
502
									+ 'border-radius:4px;background:none;color:#94a3b8;cursor:pointer;'
406
									+ 'font-family:inherit;">← Back to list</button></div>'
503
									+ 'font-family:inherit;">← Back to list</button></div>'
407
									+ '<div style="font-size:14px;font-weight:600;color:#f59e0b;margin-bottom:4px;">'
504
									+ '<div style="font-size:14px;font-weight:600;color:#f59e0b;margin-bottom:4px;">'
408
									+ 'EDITING: ' + state.beatTitle + '</div>' + dateLabel
505
									+ 'EDITING: ' + state.beatTitle + '</div>' + dateLabel
409
									+ renderBaseLocationPicker());
506
									+ renderBaseLocationPicker()
-
 
507
									+ renderPartnerQuickAdd());
410
 
508
 
411
								initMap();
509
								initMap();
412
								// Add lead markers (orange) on the map
510
								// Add lead markers (orange) on the map
413
								state.days.forEach(function (day) {
511
								state.days.forEach(function (day) {
414
									day.visits.forEach(function (v) {
512
									day.visits.forEach(function (v) {
Line 910... Line 1008...
910
	}
1008
	}
911
 
1009
 
912
	recalcDay();
1010
	recalcDay();
913
	drawRoute();
1011
	drawRoute();
914
	renderRoutePanel();
1012
	renderRoutePanel();
915
	if (typeof renderPartnersPanel === 'function') renderPartnersPanel();
-
 
916
 
1013
 
917
	if (action === 'last') {
1014
	if (action === 'last') {
918
		// Last visit — end day, go home
1015
		// Last visit — end day, go home
919
		day.endAction = 'HOME';
1016
		day.endAction = 'HOME';
920
		day.stayLat = state.homeLat;
1017
		day.stayLat = state.homeLat;
Line 1107... Line 1204...
1107
	var html = '';
1204
	var html = '';
1108
    if (state.mode === 'create' && state.beatTitle) {
1205
    if (state.mode === 'create' && state.beatTitle) {
1109
        html += '<div style="margin-bottom:8px;"><button class="btn-back-to-list" style="font-size:11px;padding:4px 10px;border:1px solid #475569;border-radius:4px;background:none;color:#94a3b8;cursor:pointer;font-family:inherit;">← Back</button></div>';
1206
        html += '<div style="margin-bottom:8px;"><button class="btn-back-to-list" style="font-size:11px;padding:4px 10px;border:1px solid #475569;border-radius:4px;background:none;color:#94a3b8;cursor:pointer;font-family:inherit;">← Back</button></div>';
1110
        html += '<div style="font-size:14px;font-weight:600;color:#60a5fa;margin-bottom:10px;">' + state.beatTitle + '</div>';
1207
        html += '<div style="font-size:14px;font-weight:600;color:#60a5fa;margin-bottom:10px;">' + state.beatTitle + '</div>';
1111
		html += renderBaseLocationPicker();
1208
		html += renderBaseLocationPicker();
-
 
1209
		html += renderPartnerQuickAdd();
1112
    }
1210
    }
1113
	state.days.forEach(function (day) {
1211
	state.days.forEach(function (day) {
1114
		var isCurrent = day.dayNumber === state.currentDay;
1212
		var isCurrent = day.dayNumber === state.currentDay;
1115
        var isLastDay = day.dayNumber === state.days.length;
1213
        var isLastDay = day.dayNumber === state.days.length;
1116
		html += '<div class="route-day">';
1214
		html += '<div class="route-day">';
Line 1243... Line 1341...
1243
 
1341
 
1244
	recalcDay();
1342
	recalcDay();
1245
	drawRoute();
1343
	drawRoute();
1246
	renderRoutePanel();
1344
	renderRoutePanel();
1247
	updateBottomBar();
1345
	updateBottomBar();
1248
	if (typeof renderPartnersPanel === 'function') renderPartnersPanel();
-
 
1249
});
1346
});
1250
 
1347
 
1251
// ============ LAST VISIT (from sidebar) — marks beat as complete, return home ============
1348
// ============ LAST VISIT (from sidebar) — marks beat as complete, return home ============
1252
$(document).on('click', '.btn-last-visit', function (e) {
1349
$(document).on('click', '.btn-last-visit', function (e) {
1253
    e.stopPropagation();
1350
    e.stopPropagation();
Line 1413... Line 1510...
1413
						+ (extras.length ? '\n\n' + extras.join('\n') : '') + '.');
1510
						+ (extras.length ? '\n\n' + extras.join('\n') : '') + '.');
1414
				} else {
1511
				} else {
1415
					alert('Beat "' + beatTitle + '" saved successfully!');
1512
					alert('Beat "' + beatTitle + '" saved successfully!');
1416
				}
1513
				}
1417
 
1514
 
-
 
1515
				// Wipe in-progress beat state for this user — the next "+ New Beat"
-
 
1516
				// must start from a clean slate (no leftover days/visits/title/leads).
1418
				state.editingBeatId = null;
1517
				state.editingBeatId = null;
-
 
1518
				state.editingDate = null;
-
 
1519
				state.beatTitle = null;
-
 
1520
				state.days = [];
-
 
1521
				state.currentDay = 1;
-
 
1522
				state.originalDayCount = null;
-
 
1523
				state.originalLeads = [];
-
 
1524
				state.savedPlanGroupId = null;
-
 
1525
 
-
 
1526
				// Clear map artifacts from the just-saved route so the canvas
-
 
1527
				// doesn't show stale polylines/labels behind the beat list.
-
 
1528
				routeLines.forEach(function (l) {
-
 
1529
					l.setMap(null);
-
 
1530
				});
-
 
1531
				routeLines = [];
-
 
1532
				if (previewLine) {
-
 
1533
					previewLine.setMap(null);
-
 
1534
					previewLine = null;
-
 
1535
				}
-
 
1536
				if (typeof resetMarkerColors === 'function') resetMarkerColors();
-
 
1537
 
1419
				state.mode = 'list';
1538
				state.mode = 'list';
1420
				showBeatList();
1539
				showBeatList();
1421
			} else {
1540
			} else {
1422
				$('#btn-finish').prop('disabled', false).text(isEdit ? 'Save Changes' : 'Finish Plan');
1541
				$('#btn-finish').prop('disabled', false).text(isEdit ? 'Save Changes' : 'Finish Plan');
1423
				alert('Error saving beat plan');
1542
				alert('Error saving beat plan');
Line 1561... Line 1680...
1561
$('.panel-tab').on('click', function () {
1680
$('.panel-tab').on('click', function () {
1562
	var tab = $(this).data('tab');
1681
	var tab = $(this).data('tab');
1563
	$('.panel-tab').removeClass('active');
1682
	$('.panel-tab').removeClass('active');
1564
	$(this).addClass('active');
1683
	$(this).addClass('active');
1565
	$('#panel-route').toggle(tab === 'route');
1684
	$('#panel-route').toggle(tab === 'route');
1566
	$('#panel-partners').toggle(tab === 'partners');
-
 
1567
	$('#panel-calendar').toggle(tab === 'calendar');
1685
	$('#panel-calendar').toggle(tab === 'calendar');
1568
	$('#cal-grid-container').toggle(tab === 'calendar');
1686
	$('#cal-grid-container').toggle(tab === 'calendar');
1569
	$('#bottom-bar').toggle((tab === 'route' || tab === 'partners') && (state.mode === 'create' || state.mode === 'edit') && state.days && state.days.length > 0);
1687
	$('#bottom-bar').toggle(tab === 'route' && (state.mode === 'create' || state.mode === 'edit') && state.days && state.days.length > 0);
1570
 
1688
 
1571
	if (tab === 'partners') renderPartnersPanel();
-
 
1572
	if (tab === 'calendar' && !state.calMonth) {
1689
	if (tab === 'calendar' && !state.calMonth) {
1573
		var now = new Date();
1690
		var now = new Date();
1574
		state.calMonth = now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2);
1691
		state.calMonth = now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2);
1575
		loadCalendarData();
1692
		loadCalendarData();
1576
	}
1693
	}
1577
});
1694
});
1578
 
1695
 
1579
// ============ PARTNERS PANEL (search + tick to add) ============
-
 
1580
function renderPartnersPanel() {
-
 
1581
	var $list = $('#partners-list');
-
 
1582
	if (!$list.length) return;
-
 
1583
	if (!state.partners || state.partners.length === 0) {
-
 
1584
		$list.html('<p style="color:#64748b;font-size:12px;text-align:center;padding:20px 10px;">Load a user first.</p>');
-
 
1585
		$('#partners-count').text('0 / 0');
-
 
1586
		return;
-
 
1587
	}
-
 
1588
 
-
 
1589
	var q = ($('#partners-search').val() || '').trim().toLowerCase();
-
 
1590
 
-
 
1591
	// Build a lookup: fofoId -> dayNumber it's already in (current day's visits)
-
 
1592
	var inRoute = {};
-
 
1593
	(state.days || []).forEach(function (d) {
-
 
1594
		(d.visits || []).forEach(function (v) {
-
 
1595
			if (v.type === 'partner' || v.type == null) inRoute[v.id] = d.dayNumber;
-
 
1596
		});
-
 
1597
	});
-
 
1598
 
-
 
1599
	var filtered = state.partners.filter(function (p) {
-
 
1600
		if (!q) return true;
-
 
1601
		var hay = ((p.code || '') + ' ' + (p.outletName || '') + ' ' + (p.businessName || '') + ' ' + (p.city || '')).toLowerCase();
-
 
1602
		return hay.indexOf(q) !== -1;
-
 
1603
	});
-
 
1604
 
-
 
1605
	// Sort: in-route first (so user can see what's already added), then by code
-
 
1606
	filtered.sort(function (a, b) {
-
 
1607
		var aIn = inRoute[a.fofoId] ? 0 : 1;
-
 
1608
		var bIn = inRoute[b.fofoId] ? 0 : 1;
-
 
1609
		if (aIn !== bIn) return aIn - bIn;
-
 
1610
		return (a.code || '').localeCompare(b.code || '');
-
 
1611
	});
-
 
1612
 
-
 
1613
	var html = '';
-
 
1614
	filtered.forEach(function (p) {
-
 
1615
		var checked = inRoute[p.fofoId];
-
 
1616
		var name = (p.outletName || p.businessName || '');
-
 
1617
		var city = p.city || '';
-
 
1618
		html += '<label class="partner-row' + (checked ? ' in-route' : '') + '">'
-
 
1619
			+ '<input type="checkbox" class="partner-tick" data-fofoid="' + p.fofoId + '"' + (checked ? ' checked' : '') + '>'
-
 
1620
			+ '<div class="pr-info">'
-
 
1621
			+ '<div class="pr-code">' + (p.code || '') + (name ? ' <span class="pr-name">- ' + name + '</span>' : '') + '</div>'
-
 
1622
			+ (city ? '<div class="pr-name">' + city + '</div>' : '')
-
 
1623
			+ '</div>'
-
 
1624
			+ (checked ? '<span class="pr-day">D' + checked + '</span>' : '')
-
 
1625
			+ '</label>';
-
 
1626
	});
-
 
1627
	if (html === '') html = '<p style="color:#64748b;font-size:12px;text-align:center;padding:20px 10px;">No partners match.</p>';
-
 
1628
	$list.html(html);
-
 
1629
	$('#partners-count').text(filtered.length + ' / ' + state.partners.length);
-
 
1630
}
-
 
1631
 
-
 
1632
$(document).on('input', '#partners-search', function () {
-
 
1633
	renderPartnersPanel();
-
 
1634
});
-
 
1635
 
-
 
1636
$(document).on('change', '.partner-tick', function () {
-
 
1637
	var fofoId = parseInt($(this).data('fofoid'));
-
 
1638
	var checked = this.checked;
-
 
1639
 
-
 
1640
	if (checked) {
-
 
1641
		// Add to current day
-
 
1642
		if (!state.days || state.days.length === 0) {
-
 
1643
			alert('Set a home location first to start the route.');
-
 
1644
			this.checked = false;
-
 
1645
			return;
-
 
1646
		}
-
 
1647
		addVisit(fofoId, 'next');
-
 
1648
		renderPartnersPanel();
-
 
1649
		return;
-
 
1650
	}
-
 
1651
 
-
 
1652
	// Untick → remove from whichever day it's in
-
 
1653
	var foundDay = null;
-
 
1654
	(state.days || []).forEach(function (d) {
-
 
1655
		if (d.visits && d.visits.some(function (v) {
-
 
1656
			return v.id === fofoId;
-
 
1657
		})) foundDay = d;
-
 
1658
	});
-
 
1659
	if (!foundDay) {
-
 
1660
		renderPartnersPanel();
-
 
1661
		return;
-
 
1662
	}
-
 
1663
 
-
 
1664
	foundDay.visits = foundDay.visits.filter(function (v) {
-
 
1665
		return v.id !== fofoId;
-
 
1666
	});
-
 
1667
 
-
 
1668
	// Reset that marker to red
-
 
1669
	if (markers[fofoId]) {
-
 
1670
		var p = state.partners.find(function (x) {
-
 
1671
			return x.fofoId === fofoId;
-
 
1672
		});
-
 
1673
		markers[fofoId].setIcon({
-
 
1674
			path: google.maps.SymbolPath.CIRCLE, scale: 14,
-
 
1675
			fillColor: '#ef4444', fillOpacity: 0.9, strokeColor: '#fff', strokeWeight: 1
-
 
1676
		});
-
 
1677
		markers[fofoId].setLabel({text: (p ? p.code : '') || '', color: '#fff', fontSize: '10px', fontWeight: '600'});
-
 
1678
	}
-
 
1679
	// Re-number remaining markers on that day
-
 
1680
	foundDay.visits.forEach(function (v, i) {
-
 
1681
		if (markers[v.id]) {
-
 
1682
			markers[v.id].setLabel({text: '' + (i + 1), color: '#fff', fontSize: '11px', fontWeight: '700'});
-
 
1683
		}
-
 
1684
	});
-
 
1685
 
-
 
1686
	recalcDay();
-
 
1687
	drawRoute();
-
 
1688
	renderRoutePanel();
-
 
1689
	updateBottomBar();
-
 
1690
	renderPartnersPanel();
-
 
1691
});
-
 
1692
 
-
 
1693
// Click a beat chip on the calendar → view that specific day's route
1696
// Click a beat chip on the calendar → view that specific day's route
1694
// (partners + only the leads scheduled for that exact date)
1697
// (partners + only the leads scheduled for that exact date)
1695
$(document).on('click', '.cal-chip-view', function (e) {
1698
$(document).on('click', '.cal-chip-view', function (e) {
1696
	if ($(e.target).hasClass('cal-chip-remove')) return; // handled separately
1699
	if ($(e.target).hasClass('cal-chip-remove')) return; // handled separately
1697
	e.stopPropagation();
1700
	e.stopPropagation();